ABEMA Little Tools

ABEMAをちょっとだけ便利にするかもしれない機能をまとめました。

目前為 2023-07-29 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         ABEMA Little Tools
// @namespace    https://greasyfork.org/ja/scripts/465585
// @version      7
// @description  ABEMAをちょっとだけ便利にするかもしれない機能をまとめました。
// @match        https://abema.tv/*
// @grant        none
// @license      MIT License
// @noframes
// ==/UserScript==

(() => {
  'use strict';
  const sid = 'LittleTools',
    ls = JSON.parse(localStorage.getItem(sid) || '{}') || {},
    lsWord = JSON.parse(localStorage.getItem(`${sid}-Word`) || '{}') || {},
    lsId = JSON.parse(localStorage.getItem(`${sid}-Id`) || '{}') || {},
    data = {
      blockedUserId: '',
      commentId: new Set(),
      commentMouseEnter: false,
      href: '',
      ngId: new Set(lsId.ngId),
      /** @type {string[]} */
      ngWordText: [],
      ngWordRe: [{}],
      version: 7,
      videoSource: '',
    },
    interval = {
      changeTargetQuality: 0,
      comment: 0,
      footer: 0,
      init: 0,
      navigation: 0,
      newcomment: 0,
      notification: 0,
      resizeVideo: 0,
      resolution: 0,
      videoelement: 0,
      videoskip: 0,
      videosource: 0,
    },
    selector = {
      comment: `:is(.com-tv-CommentBlock, .com-comment-CommentItem):has(> div[data-${sid.toLowerCase()}-hidden="false"])`,
      commentBefore: `:is(.com-tv-CommentBlock, .com-comment-CommentItem):has(> div:not([data-${sid.toLowerCase()}-hidden]))`,
      commentDuplicate: `:is(.com-tv-CommentBlock, .com-comment-CommentItem):has(> div[data-${sid.toLowerCase()}-duplicate])`,
      commentHidden: `:is(.com-tv-CommentBlock, .com-comment-CommentItem):has(> div[data-${sid.toLowerCase()}-hidden="true"])`,
      comenntAll: '.com-tv-CommentBlock, .com-comment-CommentItem',
      commentArea: '.com-tv-CommentArea, .com-comment-CommentContainerView',
      commentButton: 'button:has(svg[aria-label^="コメント"])',
      commentContinue:
        '.com-tv-CommentArea__continue-button, .com-comment-CommentContinueButton',
      commentForm:
        '.com-o-CommentForm__opened-textarea,.com-comment-CommentTextarea__textarea',
      commentInner: '.com-tv-CommentBlock__inner, .com-comment-CommentItem__inner',
      commentList:
        '.com-a-OnReachTop > div, .com-comment-CommentList__inner > ul',
      commentMessage:
        '.com-tv-CommentBlock__message > span, .com-comment-CommentItem__body',
      commentReport: `.com-tv-CommentReportForm:not([data-${sid.toLowerCase()}-commentreportform]), .com-comment-CommentReportForm:not([data-${sid.toLowerCase()}-commentreportform])`,
      commentReportCancel:
        '.com-tv-CommentReportForm__cancel-button, .com-comment-CommentReportForm__cancel-button',
      commentReportSubmitTv: 'com-tv-CommentReportForm__submit-button',
      commentReportSubmitLe: 'com-comment-CommentReportForm__submit-button',
      commentReportTv: '.com-tv-CommentReportForm',
      commentReportLe: '.com-comment-CommentReportForm',
      footer:
        '.com-tv-LinearFooter,.com-vod-VideoControlBar,.com-live-event-LiveEventVideoController,.com-vod-PayperviewLinearControlBar',
      footerVisible:
        '.com-tv-TVScreen__footer-container:not(.com-tv-TVScreen__footer-container--hidden),.com-vod-VODScreen-container:not(.com-vod-VODScreen-container--cursor-hidden),.com-live-event-LiveEventPlayerAreaLayout--controllers-visible',
      headerMenu: '.com-m-HeaderMenu',
      inner: '.c-application-DesktopAppContainer__content',
      main: '#main',
      mypageMenu: '.com-application-MypageMenu__menu',
      nextCancel: '.com-vod-VODNextProgramInfo__cancel-button',
      nextCancelMini:
        '.com-vod-VODScreenOverlayForMiniPlayer__cancel-next-program-button',
      notification: '.com-m-NotificationManager',
      notificationClose: '.com-m-Notification__button[aria-label="閉じる"]',
      notificationMessage: '.com-m-Notification__message span',
      sideNavi: '.c-application-SideNavigation',
      sideNaviColl: 'c-application-SideNavigation--collapsed',
      sideNaviWrapColl: 'c-application-SideNavigation__wrapper--collapsed',
      sidePanelClose:
        '.com-tv-FeedSidePanel__close-button,.com-live-event-LiveEventStatsSidePanel__close,.com-comment-CommentAreaHeader__close-button',
      video: 'video[src]:not([style*="display: none;"])',
      videoContainer: '.com-a-Video__container',
      videoDblclick:
        '.com-vod-VideoControlBar,.c-vod-EpisodePlayerContainer-ad-container,.c-tv-TimeshiftPlayerContainerView__ad-container,.com-live-event-LiveEventPlayerSectionLayout__player-area',
      videoMainPlayer:
        '.com-vod-VODMiniPlayerWrapper__player:not(.com-vod-VODMiniPlayerWrapper__player--bg):not(.com-vod-VODMiniPlayerWrapper__player--mini),.com-live-event-LiveEventPlayerAreaLayout__player',
      videoSkip: '.com-video_ad-AdSkipButton',
      videoSkip2: '.com-video_ad-AdSkipButton:not([disabled])',
    },
    setting = {
      _ngid: [0, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000],
      closeNotification: ls.closeNotification,
      closeSidePanel: ls.closeSidePanel,
      commentFontSize: ls.commentFontSize,
      commentFontSizeNum: ls.commentFontSizeNum,
      dblclickScroll: ls.dblclickScroll,
      enterKey: ls.enterKey,
      escKey: ls.escKey,
      headerPosition: ls.headerPosition,
      hiddenButtonText: ls.hiddenButtonText,
      hiddenCommentList: ls.hiddenCommentList,
      hiddenCommentListNum: ls.hiddenCommentListNum,
      hiddenCommentListNum2: ls.hiddenCommentListNum2,
      highlightNewComment: ls.highlightNewComment,
      mouseoverNavigation: ls.mouseoverNavigation,
      newCommentOneByOne: ls.newCommentOneByOne,
      nextProgramInfo: ls.nextProgramInfo,
      ngConsole: ls.ngConsole,
      ngId: lsId.ngId ? [...lsId.ngId] : [],
      ngIdEnable: ls.ngIdEnable,
      ngIdMaxSize: ls.ngIdMaxSize,
      ngWord: lsWord.ngWord,
      ngWordEnable: ls.ngWordEnable,
      overlapSidePanel: ls.overlapSidePanel,
      pageKey: ls.pageKey,
      qualityEnable: ls.qualityEnable,
      reduceCommentSpace: ls.reduceCommentSpace,
      reduceNavigation: ls.reduceNavigation,
      scrollNewComment: ls.scrollNewComment,
      semiTransparent: ls.semiTransparent,
      sidePanelBackground: ls.sidePanelBackground,
      sidePanelCloseButton: ls.sidePanelCloseButton,
      sidePanelSize: ls.sidePanelSize,
      sidePanelSizeNum: ls.sidePanelSizeNum,
      showProgramDetail: ls.showProgramDetail,
      skipVideo: ls.skipVideo,
      smallFontSize: ls.smallFontSize,
      stopCommentScroll: ls.stopCommentScroll,
      targetQuality: ls.targetQuality,
      videoPadding: ls.videoPadding,
      videoResolution: ls.videoResolution,
    },
    video = {
      clientHeight: 0,
      clientWidth: 0,
      pixelRatio: 0,
      src: '',
      videoHeight: 0,
      videoWidth: 0,
    };

  /**
   * NG IDを追加する
   */
  const addNgId = () => {
    log('addNgId');
    clearInterval(interval.newcomment);
    const blocked = checkBlockedUser(false);
    if (
      blocked >= 100 &&
      setting.ngIdMaxSize &&
      setting._ngid[setting.ngIdMaxSize] > data.ngId.size &&
      data.blockedUserId &&
      !data.ngId.has(data.blockedUserId)
    ) {
      setting.ngId.push(data.blockedUserId);
      lsId.ngId.push(data.blockedUserId);
      data.ngId.add(data.blockedUserId);
      log('addNgId', data.blockedUserId, data.ngId.size);
      saveStorage();
      setTimeout(() => {
        checkBlockedUser(true);
      }, 1000);
    } else {
      log(
        'not add NGiD',
        blocked,
        setting.ngIdMaxSize,
        setting._ngid[setting.ngIdMaxSize],
        data.ngId.size,
        data.blockedUserId,
        data.ngId.has(data.blockedUserId)
      );
    }
  };

  /**
   * スタイルを追加
   * @param {string} s
   */
  const addStyle = (s) => {
    const init = `
.com-tv-CommentBlock:has(> div[data-${sid.toLowerCase()}-hidden="true"]),
.com-comment-CommentItem:has(> div[data-${sid.toLowerCase()}-hidden="true"]),
.com-tv-CommentBlock:has(> div[data-${sid.toLowerCase()}-ngword]),
.com-comment-CommentItem:has(> div[data-${sid.toLowerCase()}-ngword]),
.com-tv-CommentBlock:has(> div[data-${sid.toLowerCase()}-ngid]),
.com-comment-CommentItem:has(> div[data-${sid.toLowerCase()}-ngid]),
.${sid}_Duplicate_hidden,
.${sid}_Settings_hidden,
.${sid}_Settings-tab-switch {
  display: none;
}
#${sid}_Settings {
  background-color: #F9FCFF;
  border: 2px solid #CCCCCC;
  border-radius: 8px;
  box-shadow: 4px 4px 16px rgba(0,0,0,0.5);
  color: black;
  left: 20px;
  max-height: calc(100vh - 40px);
  max-width: calc(100vw - 40px);
  min-width: 45em;
  overflow: auto;
  padding: 8px;
  position: fixed;
  top: 20px;
  user-select: none;
  width: min-content;
  z-index: 9900;
}
#${sid}_Settings label[title] {
  cursor: help;
}
#main:has(.c-tv-NowOnAirContainer__side-panel--shown) ~ #${sid}_Settings {
  max-width: calc(100vw - 460px);
}
#${sid}_Settings-header {
  text-align: center;
}
#${sid}_Settings-main fieldset {
  border: 1px solid #CCCCCC;
  margin: 2px 0;
  padding: 4px 8px;
}
#${sid}_Settings-main fieldset + label {
  margin-top: 2px;
}
#${sid}_Settings-main pre {
  background-color: #FFFFEE;
  border: 1px solid #DDDDDD;
  margin-left: 1em;
  padding: 4px;
  user-select: text;
  width: min-content;
}
#${sid}_Settings-main :is(label, details):not(.${sid}_Settings-tab-label) {
  display: inline-block;
  width: 100%;
}
#${sid}_Settings-main :is(label, details):not(.${sid}_Settings-tab-label):hover {
  background-color: #E3ECF6;
}
#${sid}_Settings-main details {
  transition: 0.5s;
}
#${sid}_Settings-main input[type="checkbox"] {
  position: relative;
  top: 2px;
  margin-right: 4px;
}
#${sid}_Settings-main summary {
  cursor: pointer;
  display: list-item;
}
#${sid}_Settings-main summary::before {
  background: #88AA88;
  border-radius: 50%;
  color: #FFFFFF;
  content: "?";
  display: inline-block;
  font-size: 85%;
  font-weight: bold;
  height: 1.5em;
  line-height: 1.5;
  margin-right: 4px;
  text-align: center;
  vertical-align: 2px;
  width: 1.5em;
}
#${sid}_Settings-main select {
  appearance: auto;
  cursor: pointer;
  margin-top: 8px;
  padding: 4px 8px;
}
#${sid}_Settings-main input + input,
#${sid}_Settings-main input + input + input,
#${sid}_Settings-main legend:has(input),
#${sid}_Settings-main legend:has(input) ~ label {
  color: gray;
}
#${sid}_Settings-main input:checked + input,
#${sid}_Settings-main input:checked + input + input,
#${sid}_Settings-main legend:has(input:checked),
#${sid}_Settings-main legend:has(input:checked) ~ label {
  color: black;
}
#${sid}_Settings-commentFontSizeNum {
  text-align: center;
  width: 3.5em;
}
#${sid}_Settings-hiddenCommentListNum,
#${sid}_Settings-hiddenCommentListNum2 {
  text-align: center;
  width: 4em;
}
#${sid}_Settings-sidePanelSizeNum {
  text-align: center;
  width: 5em;
}
#${sid}_Settings input[type="number"],
#${sid}_Settings select {
  margin-left: 8px;
  margin-right: 2px;
}
#${sid}_Settings-main input + input,
#${sid}_Settings-main input + input + input,
legend:has(input) ~ label #${sid}_Settings-targetQuality,
legend:has(input) ~ #${sid}_Settings-ngWord,
legend:has(input) ~ label #${sid}_Settings-ngIdMaxSize {
  background-color: #DDDDDD;
}
#${sid}_Settings-main input:checked + input,
#${sid}_Settings-main input:checked + input + input,
legend:has(input:checked) ~ label #${sid}_Settings-targetQuality,
legend:has(input:checked) ~ #${sid}_Settings-ngWord,
legend:has(input:checked) ~ label #${sid}_Settings-ngIdMaxSize {
  background-color: white;
}
#${sid}_Settings-ngWord {
  height: 6em;
  margin-top: 8px;
  max-width: calc(100vw - 118px);
  min-height: 6em;
  min-width: 40em;
  width: 100%;
}
#${sid}_Settings-ngWord-error {
  display: none;
  margin-bottom: 4px;
}
#${sid}_Settings-ngWord-error p {
  color: red;
}
#${sid}_Settings-ngWord-error pre {
  font-weight: bold;
  margin-top: 4px;
  padding: 4px 8px;
}
#${sid}_Settings-ngIdMaxSize {
  text-align: right;
}
#${sid}_Settings-ngId-record {
  margin-left: 1em;
}
#${sid}_Settings-footer {
  text-align: right;
}
#${sid}_Settings-footer button {
  border: 1px solid gray;
  border-radius: 4px;
  margin: 8px;
  padding: 4px;
  width: 8em;
}
#${sid}_Settings-ok {
  background-color: #EEEEEE;
}
#${sid}_Settings-cancel {
  background-color: #EEEEEE;
}
#${sid}_Settings-main {
  display: flex;
  flex-wrap: wrap;
  margin: 8px 0;
}
#${sid}_Settings-main:after {
  background: #8899aa;
  content: '';
  display: block;
  height: 1px;
  order: -1;
  width: 100%;
}
.${sid}_Settings-tab-label {
  background: #BCBCBC;
  border-radius: 4px 4px 0 0;
  color: White;
  cursor: pointer;
  flex: 1;
  font-weight: bold;
  order: -1;
  padding: 2px 4px;
  position: relative;
  text-align: center;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
  white-space: nowrap;
  z-index: 1;
}
.${sid}_Settings-tab-label:not(:last-of-type) {
  margin-right: 5px;
}
.${sid}_Settings-tab-content {
  height: 0;
  opacity: 0;
  overflow: hidden;
  width: 100%;
}
.${sid}_Settings-tab-switch:checked + .${sid}_Settings-tab-label {
  background: #8899aa;
}
.${sid}_Settings-tab-switch:checked + .${sid}_Settings-tab-label + .${sid}_Settings-tab-content {
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
  height: auto;
  opacity: 1;
  overflow: auto;
  padding: 8px;
  transition: .5s opacity;
}
#${sid}_CommentReportForm-NgComment {
  color: #E6E6E6;
  font-size: 13px;
  margin-top: 12px;
}
#${sid}_CommentReportForm-NgCommentHeader {
  font-size: 12px;
}
#${sid}_CommentReportForm-NgCommentList {
  background-color: #333333;
  margin-top: 4px;
  padding: 8px 4px;
}
#${sid}_CommentReportForm-NgCommentList p + p {
  margin-top: 0.8em;
}
#${sid}_VideoResolution {
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.2), #000000);
  bottom: 0px;
  color: #ccc;
  display: none;
  font-size: 12px;
  height: 16px;
  left: 155px;
  padding: 0 2px;
  position: absolute;
  user-select: none;
  white-space: nowrap;
}
    `,
      overlapSidePanel = `
/*右側のサイドパネルを動画に重ねて表示する*/
.c-tv-NowOnAirContainer__screen--with-side-panel {
  width: 100vw !important;
}
.com-tv-TVScreen__player {
  height: 100vh !important;
}
.com-a-Text--info.com-a-Text--dark,
.com-a-Text--info.com-a-Text--light {
  color: #DDDDDD !important;
}
.com-tv-CommentBlock,
.com-tv-FeedProgramDetailContainerView__contents :is(h2, h3, h2+p, h3+p, dd),
.com-tv-FeedProgramDetailContainerView__contents span:not(.com-tv-FeedProgramDetailExternalLink__button-text),
.com-tv-FeedSidePanel__close-button-text,
.com-comment-CommentItem__body {
  text-shadow:
    -1px -1px 1px black, -1px 0px 1px black, -1px 1px 1px black,
     0px -1px 1px black,  0px 1px 1px black,
     1px -1px 1px black,  1px 0px 1px black,  1px 1px 1px black !important;
}
.com-tv-CommentBlock__message > span,
.com-comment-CommentItem__body {
  color: white !important;
}
.c-tv-NowOnAirContainer__side-panel,
.com-tv-CommentArea,
.com-tv-CommentArea__comment-form,
.com-tv-CommentBlock,
.com-tv-FeedProgramDetailContainerView__contents,
.com-tv-FeedSidePanel__header,
.com-comment-CommentContainerView,
.com-live-event-LiveEventStatsSidePanel {
  background-color: rgba(0, 0, 0, 0) !important;
}
.com-tv-CommentBlock__inner:hover,
.com-comment-TwitterSigninButton:hover,
.com-comment-CommentItem__inner:hover {
  background-color: rgba(0, 0, 0, 0.3) !important;
}
.com-tv-CommentBlock__time {
  opacity: 0.6 !important;
}
.c-tv-NowOnAirContainer__screen--with-side-panel .com-tv-TVScreen__footer-container {
  padding-right: ${
    setting.sidePanelSize ? setting.sidePanelSizeNum : '320'
  }px !important;
}
.com-o-CommentForm__can-post .com-o-CommentForm__opened-textarea-wrapper,
.com-comment-CommentTextarea__textarea {
  background-color: rgba(255, 255, 255, 0.2) !important;
}
.com-o-CommentForm__can-post .com-o-CommentForm__opened-textarea:focus,
.com-comment-CommentTextarea__textarea:focus {
  background-color: rgba(255, 255, 255, 0.8) !important;
}
.com-o-CommentForm__opened-textarea-wrapper {
  background-color: rgba(0, 0, 0, 0.2) !important;
}
.com-o-CommentForm__twitter-button,
.com-comment-TwitterSigninButton,
.com-comment-TwitterSignoutButton,
.com-comment-CommentAreaHeader__comment-count {
  opacity: 0.2 !important;
}
.com-o-CommentForm__twitter-button:hover,
.com-comment-TwitterSigninButton:hover,
.com-comment-TwitterSignoutButton:hover,
.com-comment-CommentAreaHeader__comment-count:hover {
  opacity: 1 !important;
}
.com-tv-FeedSidePanel__contents {
  height: 98% !important;
}
.com-a-Button--primary,
.com-comment-CommentSubmitButton {
  background-color: rgba(221, 170, 0, 0.5) !important;
}
.com-a-Button--primary:hover:not([disabled]),
.com-comment-CommentSubmitButton:hover:not([disabled]) {
  background-color: rgba(221, 170, 0, 1) !important;
}
.c-tv-NowOnAirContainer__screen--with-side-panel :is(
  .com-tv-SlotMyListButtonOnPlayerContainerView,
  .com-tv-TVScreen__ad-link-button
) {
  bottom: 124px !important;
  right: ${
    setting.sidePanelSize ? setting.sidePanelSizeNum : '320'
  }px !important;
  transform: translateX(500px) !important;
}
.com-tv-SlotMyListButtonOnPlayerContainerView--shown,
.com-tv-TVScreen__ad-link-button--shown {
  transform: translateX(0) !important;
}
.c-tv-NowOnAirContainer__screen--with-side-panel .c-tv-NowOnAirContainer__remote-controller,
.com-question-QuestionContainerView,
.com-vod-VODResponsiveMainContent--with-side-panel .com-live-event-LiveEventOverlayControllerLayout__bottom-buttons {
  right: ${
    setting.sidePanelSize ? setting.sidePanelSizeNum : '320'
  }px !important;
}
.com-vod-VODResponsiveMainContent--with-side-panel .com-vod-PayperviewLinearControlBar {
  margin-right: ${
    setting.sidePanelSize ? setting.sidePanelSizeNum : '320'
  }px !important;
}
.com-tv-FeedProgramDetailCommentCounter,
.com-tv-FeedProgramDetailHeader__date,
.com-tv-FeedProgramDetailViewCounter {
  color: #CCCCCC !important;
}
.com-live-event-LiveEventPlayerSectionLayout__side-panel {
  height: calc(100vh - 10px);
  position: absolute;
  right: 0;
}
.com-comment-CommentAreaHeader__close-button:hover {
  color: #FFFFFF;
}
    `,
      highlightNewComment = `
/*新規コメントと自分のコメントを強調表示する*/
.com-tv-CommentBlock__inner--active,
.com-tv-CommentBlock--new,
.com-comment-CommentItem[data-${sid.toLowerCase()}-Own] .com-comment-CommentItem__body,
.com-comment-CommentItem__inner[data-${sid.toLowerCase()}-Own] .com-comment-CommentItem__body {
  text-shadow:
    -1px -1px 1px black, -1px 0px 1px black, -1px 1px 1px black,
     0px -1px 1px black,  0px 1px 1px black,
     1px -1px 1px black,  1px 0px 1px black,  1px 1px 1px black,
    -2px -2px 1px rgba(255,165,0,0.3), -2px 0px 1px rgba(255,165,0,0.3), -2px 2px 1px rgba(255,165,0,0.3),
     0px -2px 1px rgba(255,165,0,0.3),  0px 2px 1px rgba(255,165,0,0.3),
     2px -2px 1px rgba(255,165,0,0.3),  2px 0px 1px rgba(255,165,0,0.3),  2px 2px 1px rgba(255,165,0,0.3) !important;
}
.com-tv-CommentBlock__inner--active,
.com-comment-CommentItem[data-${sid.toLowerCase()}-Own],
.com-comment-CommentItem__inner[data-${sid.toLowerCase()}-Own] {
  background-color: rgba(128,83,0,0.3) !important;
}
/*
.com-tv-CommentBlock:not(.com-tv-CommentBlock--new) + .com-tv-CommentBlock--new {
  border-top: 4px dotted rgba(255,165,0,0.4) !important;
}
*/
    `,
      commentFontSize = `
/*コメントの文字サイズを変更する*/
.com-tv-CommentBlock__message > span,
.com-comment-CommentItem__body {
  font-size: ${setting.commentFontSizeNum}px !important;
}
      `,
      reduceCommentSpace = `
/*コメントの余白を減らす&行間を狭める*/
.com-tv-CommentBlock__inner {
  padding: 2px 4px !important;
}
.com-comment-CommentItem__body {
  padding: 4px 0 !important;
}
.com-comment-CommentItem__body-sub-wrapper {
  padding: 0 4px !important;
}
.com-comment-CommentItem__sub {
  width: auto !important;
}
    `,
      sidePanelCloseButton = `
/*サイドパネル上端にマウスカーソルを近づけたとき閉じるボタンを表示*/
.com-tv-FeedSidePanel__close-button {
  background-color: rgba(64,64,64,0.8) !important;
}
.com-tv-FeedSidePanel__contents {
  transform: translateY(10px) !important;
}
.com-tv-FeedSidePanel__header {
  position: absolute !important;
  transform: translateY(-58px) !important;
  transition: transform 0.2s !important;
  z-index: 99 !important;
}
.com-tv-FeedSidePanel__header:hover {
  transform: translateY(0px) !important;
}
    `,
      sidePanelSize = `
/*サイドパネルの幅を変更する*/
.c-tv-NowOnAirContainer__side-panel,
.com-tv-FeedSidePanel,
.com-live-event-LiveEventPlayerSectionLayout__side-panel {
  width: ${
    setting.sidePanelSize ? setting.sidePanelSizeNum : '320'
  }px !important;
}
.com-application-Header--shrunk,
.c-common-HeaderContainer-header--shrunk {
  width: calc(100% - ${
    setting.sidePanelSize ? setting.sidePanelSizeNum : '320'
  }px) !important;
}
      `,
      showProgramDetail = `
/*サイドパネルに記載された番組情報の詳細を常に表示する*/
.com-tv-FeedSidePanel__contents #com-vod-VODDetailsAccordion__details {
  height: auto !important;
}
.com-tv-FeedSidePanel__contents .com-vod-VODDetailsAccordion__details--collapsed {
  visibility: visible !important;
}
.com-tv-FeedSidePanel__contents .com-vod-VODDetailsAccordion__toggle-collapsed-details-button-paragraph {
  display: none !important;
}
    `,
      hiddenButtonText = `
/*最初から見る・番組情報・コメントボタンのテキストを隠す*/
.com-tv-LinearFooterChasePlayButton,
.com-tv-LinearFooterProgramDetailButton,
.com-tv-LinearFooterCommentButton {
  transition: all 0.2s !important;
  width: 44px !important;
}
.com-tv-LinearFooterChasePlayButton:hover:not(.com-tv-LinearFooterChasePlayButton--shrunk) {
  width: 132px !important;
}
.com-tv-LinearFooterProgramDetailButton:hover:not(.com-tv-LinearFooterProgramDetailButton--shrunk),
.com-tv-LinearFooterCommentButton:hover:not(.com-tv-LinearFooterCommentButton--shrunk) {
  width: 100px !important;
}
.com-tv-LinearFooterChasePlayButton__text,
.com-tv-LinearFooterProgramDetailButton__text,
.com-tv-LinearFooterCommentButton__text {
  overflow: hidden;
  white-space: nowrap;
  width: 0;
}
.com-tv-LinearFooterChasePlayButton:hover .com-tv-LinearFooterChasePlayButton__text,
.com-tv-LinearFooterProgramDetailButton:hover .com-tv-LinearFooterProgramDetailButton__text,
.com-tv-LinearFooterCommentButton:hover .com-tv-LinearFooterCommentButton__text {
  width: auto;
}
    `,
      hiddenCommentList = `
/*コメントリストを半透明化*/
.com-a-OnReachTop, .com-comment-CommentList__inner {
  opacity: ${Number(setting.hiddenCommentListNum) / 100};
}
    `,
      hiddenCommentList2 = `
/*コメントリストを半透明化2*/
.com-a-OnReachTop, .com-comment-CommentList__inner {
  opacity: ${Number(setting.hiddenCommentListNum2) / 100};
}
    `,
      videoResolution = `
#${sid}_VideoResolution {
  display: block;
}
      `,
      semiTransparent = `
/*ヘッダーやナビゲーションなどを半透明にする*/
.com-application-Header,
.com-application-Header:before {
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), #000000) !important;
}
.com-InputText__input--dark-strong:not(:focus),
.com-live-event-LiveEventOverlayControllerLayout__bottom-buttons .com-a-Button--dark:not(:hover) {
  background: rgba(33, 33, 33, 0.2) !important;
}
.c-application-SideNavigation__wrapper {
  background-color: rgba(0, 0, 0, 0.2) !important;
  background-image: linear-gradient(270deg,transparent, #000) !important;
}
.c-application-SideNavigation__footer {
  background-color: rgba(0, 0, 0, 0) !important;
}
.com-tv-LinearFooter__button button {
  background-color: rgba(0, 0, 0, 0.2) !important;
}
.c-application-SideNavigation__wrapper:hover,
.com-tv-LinearFooter__button button:hover {
  background-color: rgba(0, 0, 0, 0.8) !important;
}
.com-tv-LinearChannelSwitcher__button {
  opacity: 0.5 !important;
}
.com-playback-Volume__icon {
  opacity: 0.8 !important;
}
.com-tv-LinearChannelSwitcher__button:hover,
.com-playback-Volume__icon:hover {
  opacity: 1 !important;
}
.com-tv-RemoteController__button:hover {
  border: 2px outset #555555 !important;
}
.com-a-Tooltip {
  background-color: rgba(33, 33, 33, 0.5) !important;
}
.com-question-QuestionContainerView .com-question-VoteContent {
  background-color: rgba(23, 23, 23, 0.6) !important;
}
.com-question-QuestionContainerView .com-question-VoteContent:hover {
  background-color: rgba(23, 23, 23, 1) !important;
}
.com-question-VoteButton:hover:not(.com-question-VoteButton--highest) {
  background-color: #171717 !important;
}
      `,
      smallFontSize = `
/*ヘッダーやフッターの一部の文字サイズを小さくする*/
.com-InputText__input {
  font-size: 14px !important;
}
.com-tv-LinearFooter__feed-super {
  font-size: 16px !important;
}
      `,
      headerPosition = `
/*ヘッダーを追従表示にする*/
body:not(.com-timetable-TimeTable__body-timetable) :is(.c-common-HeaderContainer-header, .com-application-Header:not(.com-application-Header--shrunk)) {
  position: relative !important;
}
body:not(.com-timetable-TimeTable__body-timetable) .c-application-SideNavigation__wrapper {
  padding-top: 0 !important;
}
.com-a-ResponsiveMainContent {
  margin-top: 0 !important;
}
      `,
      videoPadding = `
/*動画周辺の余白を減らす*/
.com-vod-VODResponsiveMainContent {
  margin: 0 !important;
  padding: 0 !important;
}
.com-vod-VODResponsiveMainContent .com-m-BreadcrumbList {
  padding: 7px 0;
}
.com-vod-VODResponsiveMainContent__inner {
  max-width: none !important;
}
      `,
      mouseoverNavigation = `
/*左端にマウスオーバーしたときだけナビゲーションを表示する*/
.c-application-SideNavigation--collapsed,
.c-application-SideNavigation__wrapper--collapsed {
  width: 8px !important;
}
.c-application-SideNavigation--collapsed:not(:hover) {
  opacity: 0 !important;
}
.com-tv-TVScreen__footer-container--sidenav-collapsed {
  padding-left: 0 !important;
}
.com-timetable-DesktopTimeTableWrapper__channel-content-header-wrapper--side-navigation-collapsed,
.com-timetable-TimeTableListTimeAxis--is-collapsed-side-navigation {
  left: 8px !important;
}
      `,
      sidePanelBackground = `
/*サイドパネルの背景を半透明にする*/
.c-tv-NowOnAirContainer__side-panel,
.com-live-event-LiveEventPlayerSectionLayout__side-panel {
  background-color: rgba(0, 0, 0, 0.5) !important;
}
      `,
      style = document.createElement('style');
    if (s === 'init') {
      style.textContent = init;
    } else if (s === 'highlightNewComment') {
      style.textContent = highlightNewComment;
    } else if (s === 'overlapSidePanel') {
      style.textContent = overlapSidePanel;
    } else if (s === 'commentFontSize') {
      style.textContent = commentFontSize;
    } else if (s === 'reduceCommentSpace') {
      style.textContent = reduceCommentSpace;
    } else if (s === 'sidePanelCloseButton') {
      style.textContent = sidePanelCloseButton;
    } else if (s === 'showProgramDetail') {
      style.textContent = showProgramDetail;
    } else if (s === 'sidePanelSize') {
      style.textContent = sidePanelSize;
    } else if (s === 'hiddenButtonText') {
      style.textContent = hiddenButtonText;
    } else if (s === 'videoResolution') {
      style.textContent = videoResolution;
    } else if (s === 'semiTransparent') {
      style.textContent = semiTransparent;
    } else if (s === 'smallFontSize') {
      style.textContent = smallFontSize;
    } else if (s === 'headerPosition') {
      style.textContent = headerPosition;
    } else if (s === 'videoPadding') {
      style.textContent = videoPadding;
    } else if (s === 'mouseoverNavigation') {
      style.textContent = mouseoverNavigation;
    } else if (s === 'hiddenCommentList') {
      style.textContent = hiddenCommentList;
    } else if (s === 'hiddenCommentList2') {
      style.textContent = hiddenCommentList2;
    } else if (s === 'sidePanelBackground') {
      style.textContent = sidePanelBackground;
    }
    style.id = `${sid}_style_${s}`;
    document.head.appendChild(style);
  };

  /**
   * 動画を構成している要素に変更があったとき
   */
  const changeElements = () => {
    if (/^https:\/\/abema\.tv\/now-on-air\/.+$/.test(location.href)) {
      if (setting.closeSidePanel) checkSidePanel();
    } else if (
      /^https:\/\/abema\.tv\/(?:video\/episode|channels)\/.+$/.test(
        location.href
      )
    ) {
      closeNextProgramInfo();
    }
    hasCommentElement();
    const inner = document.querySelector(selector.inner),
      report = document.querySelector(selector.commentReport),
      reports = document.querySelectorAll(selector.commentReport);
    if (inner) {
      setTimeout(() => {
        hasVideoElement();
        checkVisibleFooter();
      }, 50);
    }
    if (reports.length > 1) {
      for (let i = 1; i < reports.length; i++) {
        reports[i].classList.add(`${sid}_Duplicate_hidden`);
      }
    }
    if (report instanceof HTMLFormElement) {
      report.dataset[`${sid.toLowerCase()}Commentreportform`] = '';
      let uid;
      if (/^https:\/\/abema\.tv\/now-on-air\/.+$/.test(location.href)) {
        uid = getCommentProps(report, 'userId');
      } else if (/^https:\/\/abema\.tv\/live-event\/.+$/.test(location.href)) {
        const eProps = report.parentElement?.parentElement;
        if (eProps instanceof HTMLLIElement) {
          uid = getCommentProps(eProps, 'userId');
        }
      }
      log('Comment Report Form: userId', uid);
      if (uid) {
        const list = document.querySelectorAll(selector.comenntAll),
          comments = [],
          ids = new Set();
        for (let i = 0; i < list.length; i++) {
          const co = list[i];
          if (co instanceof HTMLDivElement || co instanceof HTMLLIElement) {
            if (getCommentProps(co, 'userId') === uid) {
              const message = co.querySelector(selector.commentMessage),
                id = getCommentProps(co, 'id');
              if (message && !ids.has(id)) {
                comments.push(message.textContent);
                ids.add(id);
              }
            }
          }
        }
        if (comments.length) {
          const eWrapper = document.createElement('div'),
            eHeader = document.createElement('div'),
            eList = document.createElement('div');
          eWrapper.id = `${sid}_CommentReportForm-NgComment`;
          eHeader.id = `${sid}_CommentReportForm-NgCommentHeader`;
          eHeader.textContent = 'このユーザーのコメント履歴:';
          eList.id = `${sid}_CommentReportForm-NgCommentList`;
          eWrapper.appendChild(eHeader);
          for (let i = 0; i < comments.length; i++) {
            const p = document.createElement('p');
            p.textContent = comments[i];
            eList.appendChild(p);
          }
          eWrapper.appendChild(eList);
          report.appendChild(eWrapper);
        }
      }
    }
  };

  /**
   * ページタイトル変更&URL移動したとき
   */
  const changePageTitle = () => {
    if (data.href !== location.href) {
      if (/^https:\/\/abema\.tv\/now-on-air\/.+$/.test(location.href)) {
        removeStyle('headerPosition');
      } else reStyle('headerPosition', setting.headerPosition);
      data.href = location.href;
    }
  };

  /**
   * 動画の画質を変更する
   * @param {Number} n
   */
  const changeTargetQuality = (n) => {
    const vi = returnVideo(),
      vt = returnVideoTracks();
    if (vt?.qualities) {
      const qualities = vt.qualities,
        len = qualities.length,
        heightList = [0, 180, 240, 360, 480, 720, 1080],
        targetHeight = heightList[n];
      let target = -1;
      if (qualities.length === 1) {
        target = 0;
      } else if (qualities[0].height <= 240) {
        for (let i = 0; i < len; i++) {
          if (qualities[i].height <= targetHeight) {
            target = i;
          }
        }
      } else {
        for (let i = len - 1; i >= 0; i--) {
          if (qualities[i].height <= targetHeight) {
            target = i;
          }
        }
      }
      log(
        'changeTargetQuality',
        n,
        len,
        target,
        targetHeight,
        qualities[target]?.height,
        vi?.videoHeight
      );
      if (target >= 0) {
        const tqHeight = vt.targetQuality?.height;
        if (tqHeight !== qualities[target]?.height) {
          log(
            'changeTargetQuality A',
            vt.activeQuality?.height || 'null',
            vt.targetQuality?.height || 'null',
            vi?.videoHeight
          );
          vt.targetQuality = qualities[target];
        }
      } else {
        if (vt.targetQuality) {
          log(
            'changeTargetQuality B',
            vt.activeQuality?.height || 'null',
            vt.targetQuality?.height || 'null',
            vi?.videoHeight
          );
          vt.targetQuality = null;
        }
      }
      clearInterval(interval.changeTargetQuality);
      interval.changeTargetQuality = setInterval(() => {
        if (vt) {
          if (target >= 0) {
            const tqHeight = vt.targetQuality?.height;
            if (tqHeight !== qualities[target]?.height) {
              log(
                'changeTargetQuality C',
                vt.activeQuality?.height || 'null',
                vt.targetQuality?.height || 'null',
                vi?.videoHeight
              );
              vt.targetQuality = qualities[target];
            }
          } else {
            if (vt.targetQuality) {
              log(
                'changeTargetQuality D',
                vt.activeQuality?.height || 'null',
                vt.targetQuality?.height || 'null',
                vi?.videoHeight
              );
              vt.targetQuality = null;
            }
          }
        } else {
          clearInterval(interval.changeTargetQuality);
        }
      }, 2000);
    } else if (!/^https:\/\/abema\.tv\/live-event\/.+$/.test(location.href)) {
      log('changeTargetQuality: not found targetQuality', 'warn');
    }
  };

  /**
   * 動画のソースが切り替わったとき
   */
  const changeVideoSource = () => {
    clearInterval(interval.videosource);
    interval.videosource = setInterval(() => {
      const vi = returnVideo();
      if (vi) {
        clearInterval(interval.videosource);
        if (vi.hasAttribute('src')) {
          const src = vi.getAttribute('src');
          if (src && src !== data.videoSource) {
            log('changeVideoSource');
            data.videoSource = src;
            if (setting.qualityEnable) {
              changeTargetQuality(setting.targetQuality);
            }
            if (setting.videoResolution) showVideoResolution();
          }
        }
      }
    }, 500);
  };

  /**
   * ブロックしたユーザー数を確認する
   * @param {boolean} b trueならdata.ngIdReserveを操作する
   * @returns {number} ブロックしたユーザー数
   */
  const checkBlockedUser = (b) => {
    log('checkBlockedUser', b, data.blockedUserId);
    const sBui = localStorage.getItem('abm_blockedUserIds');
    if (sBui) {
      const aBui = sBui.split(',');
      if (b) {
        if (aBui.length >= 100) {
          data.blockedUserId = aBui[0];
          log('checkBlockedUser', data.blockedUserId, aBui[0], aBui[1]);
        } else {
          data.blockedUserId = '';
        }
      }
      return aBui.length;
    }
    log('checkBlockedUser: not found abm_blockedUserIds', 'warn');
    return 0;
  };

  /**
   * クリックしたとき
   * @param {MouseEvent} e
   */
  const checkClick = (e) => {
    if (
      (/^https:\/\/abema\.tv\/now-on-air\/.+$/.test(location.href) &&
        e.target instanceof HTMLElement &&
        e.target.parentElement?.className === selector.commentReportSubmitTv) ||
      (/^https:\/\/abema\.tv\/live-event\/.+$/.test(location.href) &&
        e.target instanceof HTMLElement &&
        e.target.parentElement?.className === selector.commentReportSubmitLe)
    ) {
      addNgId();
    }
  };

  /**
   * ダブルクリックしたとき
   * @param {MouseEvent} e
   */
  const checkDblclick = (e) => {
    if (
      setting.dblclickScroll &&
      /^https:\/\/abema\.tv\/(?:video\/episode|channels|live-event)\/.+$/.test(
        location.href
      ) &&
      e.target instanceof HTMLElement &&
      e.target.closest(selector.videoDblclick)
    ) {
      adjustScrollPosition();
    }
  };

  /**
   * キーボードのキーを押したとき
   * @param {KeyboardEvent} e
   */
  const checkKeyDown = (e) => {
    const isInput =
      e.target instanceof HTMLInputElement ||
      e.target instanceof HTMLTextAreaElement
        ? true
        : false;
    if (e.shiftKey && e.key === 'O' && (!isInput || (isInput && e.altKey))) {
      openSettings();
    } else if (!isInput && e.key === 'Escape') {
      const submit = document.querySelectorAll(selector.commentReportCancel);
      for (let i = 0; i < submit.length; i++) {
        const button = submit[i];
        if (button instanceof HTMLButtonElement) button.click();
      }
    } else if (setting.enterKey && e.key === 'Enter') {
      if (!isInput) {
        const ca = document.querySelector(selector.commentArea);
        if (!ca) {
          const cb = document.querySelector(selector.commentButton);
          if (cb instanceof HTMLButtonElement) {
            cb.click();
          }
        }
        const ta = document.querySelector(selector.commentForm);
        if (ta instanceof HTMLTextAreaElement) {
          ta.focus();
          e.preventDefault();
        }
      }
      const cc = document.querySelector(selector.commentContinue);
      if (cc instanceof HTMLButtonElement) {
        const cf = document.querySelector(selector.commentForm);
        if (
          cf instanceof HTMLTextAreaElement &&
          ((isInput && !cf.value) || !isInput)
        ) {
          cc.click();
        }
      }
    } else if (
      setting.pageKey &&
      isInput &&
      (e.key === 'PageUp' || e.key === 'PageDown')
    ) {
      const cl = document.querySelector(selector.commentList)?.parentElement;
      if (cl) {
        const ch = cl.clientHeight;
        if (ch) {
          const scroll = e.key === 'PageUp' ? -(ch - 40) : ch - 40;
          cl.scrollBy({ top: scroll, behavior: 'smooth' });
          e.preventDefault();
        }
      }
    } else if (
      setting.sidePanelBackground &&
      e.shiftKey &&
      e.key === 'B' &&
      (!isInput || (isInput && e.altKey))
    ) {
      const style = document.getElementById(`${sid}_style_sidePanelBackground`);
      if (style) removeStyle('sidePanelBackground');
      else addStyle('sidePanelBackground');
    } else if (
      setting.hiddenCommentList &&
      e.shiftKey &&
      e.key === 'C' &&
      (!isInput || (isInput && e.altKey))
    ) {
      const style1 = document.getElementById(`${sid}_style_hiddenCommentList`),
        style2 = document.getElementById(`${sid}_style_hiddenCommentList2`);
      if (!style1 && !style2) {
        addStyle('hiddenCommentList');
      } else if (style1) {
        removeStyle('hiddenCommentList');
        if (setting.hiddenCommentListNum2) addStyle('hiddenCommentList2');
      } else if (style2) {
        removeStyle('hiddenCommentList2');
      }
    } else if (
      e.target instanceof HTMLElement &&
      (e.target.closest(selector.commentReportTv) ||
        e.target.closest(selector.commentReportLe))
    ) {
      if (
        setting.ngIdEnable &&
        e.target.textContent === 'ブロック' &&
        (e.key === 'Enter' || e.key === ' ')
      ) {
        addNgId();
      }
    }
  };

  /**
   * マウスカーソルをコメントリストに重ねたとき
   * @param {MouseEvent} e
   */
  const checkMouseEnter = (e) => {
    const cl = document.querySelector(selector.commentList)?.parentElement;
    if (cl === e.target) data.commentMouseEnter = true;
  };

  /**
   * マウスカーソルをコメントリストから外したとき
   * @param {MouseEvent} e
   */
  const checkMouseLeave = (e) => {
    const cl = document.querySelector(selector.commentList)?.parentElement;
    if (cl === e.target) data.commentMouseEnter = false;
  };

  /**
   * 新規コメントを1行ずつ表示&スクロール表示する
   */
  const checkNewComments = () => {
    if (setting.newCommentOneByOne) {
      const service = /^https:\/\/abema\.tv\/now-on-air\/.+$/.test(
        location.href
      )
        ? 'tv'
        : /^https:\/\/abema\.tv\/live-event\/.+$/.test(location.href)
        ? 'le'
        : '';
      if (!service) return;
      const ca = document.querySelectorAll(selector.comenntAll),
        /** @type {NodeListOf<HTMLDivElement>|null} */
        cb = service ? document.querySelectorAll(selector.commentBefore) : null;
      if (!cb?.length) return;
      if (ca?.length === cb.length) data.commentId.clear();
      const listP =
        service === 'tv'
          ? document.querySelector(selector.commentList)?.parentElement
          : service === 'le'
          ? document.querySelector(selector.commentList)?.parentElement
              ?.parentElement
          : null;
      if (listP) {
        for (let i = 0; i < cb.length; i++) {
          const eMessage = cb[i].querySelector(selector.commentMessage),
            /** @type {HTMLDivElement|null} */
            eInner = cb[i].querySelector(selector.commentInner),
            message = eMessage?.textContent,
            cid = getCommentProps(cb[i], 'id'),
            uid = getCommentProps(cb[i], 'userId');
          let ngFlag = false;
          if (!eInner) continue;
          //重複コメントの処理
          if (data.commentId.has(cid)) {
            ngFlag = true;
            if (!(`${sid.toLowerCase()}Duplicate` in eInner.dataset)) {
              log(`Duplicate: ${uid} / ${cid} / ${message}`);
              eInner.dataset[`${sid.toLowerCase()}Duplicate`] = '';
            }
          } else data.commentId.add(cid);
          //NG IDの処理
          if (!ngFlag && setting.ngIdEnable && data.ngId.has(uid)) {
            ngFlag = true;
            if (!(`${sid.toLowerCase()}Ngid` in eInner.dataset)) {
              log(`NG ID: ${uid} / ${cid} / ${message}`);
              eInner.dataset[`${sid.toLowerCase()}Ngid`] = '';
            }
          }
          //NGワードの処理
          if (!ngFlag && setting.ngWordEnable && message) {
            for (let j = 0; j < data.ngWordText.length; j++) {
              if (data.ngWordText[j] && message.includes(data.ngWordText[j])) {
                ngFlag = true;
                eInner.dataset[`${sid.toLowerCase()}Ngword`] = '';
                if (setting.ngConsole) {
                  console.log(
                    `${sid} NG Word: ${data.ngWordText[j]} / Comment: ${message} / UserID: ${uid}`
                  );
                }
                break;
              }
            }
            if (!ngFlag) {
              for (let j = 0; j < data.ngWordRe.length; j++) {
                const wr = data.ngWordRe[j],
                  re = new RegExp(wr.r, wr.f);
                if (data.ngWordRe[j] && re.test(message)) {
                  ngFlag = true;
                  eInner.dataset[`${sid.toLowerCase()}Ngword`] = '';
                  if (setting.ngConsole) {
                    const ng = re.exec(message);
                    if (ng) {
                      console.log(
                        `${sid} NG Word: ${ng[0]} / Comment: ${ng.input} / UserID: ${uid}`
                      );
                    }
                  }
                  break;
                }
              }
            }
          }
          if (ngFlag) eInner.dataset[`${sid.toLowerCase()}Hidden`] = '';
          else eInner.dataset[`${sid.toLowerCase()}Hidden`] = 'true';
          if (
            /^https:\/\/abema\.tv\/live-event\/.+$/.test(location.href) &&
            getCommentProps(cb[i], 'isOwnComment')
          ) {
            eInner.dataset[`${sid.toLowerCase()}Own`] = '';
          }
        }
        const dupli = document.querySelectorAll(selector.commentDuplicate);
        for (const e of dupli) {
          const inner = e.firstChild;
          if (inner) inner.remove();
        }
        const hidden = document.querySelectorAll(selector.commentHidden),
          time =
            service === 'tv'
              ? hidden.length > 7
                ? (6.5 / hidden.length) * 1000
                : 920
              : service === 'le'
              ? hidden.length > 5
                ? (4.5 / hidden.length) * 1000
                : 900
              : 1000;
        clearInterval(interval.newcomment);
        interval.newcomment = setInterval(() => {
          /** @type {HTMLDivElement|null} */
          const ch = document.querySelector(selector.commentHidden),
            rf = document.querySelector(selector.commentReport);
          if (ch && !rf && !data.commentMouseEnter) {
            /** @type {HTMLDivElement|null} */
            const chi = ch.querySelector(selector.commentInner);
            if (chi) chi.dataset[`${sid.toLowerCase()}Hidden`] = 'false';
            if (
              listP.scrollHeight - listP.scrollTop - listP.clientHeight <
              500
            ) {
              if (setting.scrollNewComment && hidden.length < 30) {
                listP.scroll({
                  top: listP.scrollHeight,
                  behavior: 'auto',
                });
                listP.scrollBy({
                  top: -ch.clientHeight,
                  behavior: 'auto',
                });
                listP.scrollBy({
                  top: ch.clientHeight + 1,
                  behavior: 'smooth',
                });
              } else {
                listP.scrollBy({
                  top: ch.clientHeight + 1,
                  behavior: 'auto',
                });
              }
            }
            /** @type {HTMLButtonElement|null} */
            const cButton = document.querySelector(selector.commentContinue);
            if (cButton) {
              if (
                listP.scrollHeight - listP.scrollTop - listP.clientHeight <
                500
              ) {
                cButton.click();
              }
            }
          } else if (!rf && !data.commentMouseEnter) {
            clearInterval(interval.newcomment);
            if (
              listP.scrollHeight - listP.scrollTop - listP.clientHeight <
              500
            ) {
              if (setting.scrollNewComment && hidden.length < 30) {
                listP.scroll({
                  top: listP.scrollHeight,
                  behavior: 'smooth',
                });
              } else {
                listP.scroll({
                  top: listP.scrollHeight,
                  behavior: 'auto',
                });
              }
            }
          }
        }, time);
      }
    }
  };

  /**
   * サイドパネルが最初から開いているかを調べる
   */
  const checkSidePanel = () => {
    if (!document.querySelector(`.${sid}_SidePanelCloseed`)) {
      /** @type {HTMLButtonElement|null} */
      const button = document.querySelector(selector.sidePanelClose);
      button?.classList.add(`${sid}_SidePanelCloseed`);
      button?.click();
    }
  };

  /**
   * スクリプトとストレージのバージョンを確認
   * (現時点ではバージョンを登録するだけ)
   */
  const checkVersion = () => {
    ls.version = data.version;
    saveStorage();
  };

  /**
   * 動画のフッターが表示されているかを調べる
   */
  const checkVisibleFooter = () => {
    const cvf = () => {
      const fo = document.querySelector(selector.footerVisible);
      if (setting.videoResolution && fo) showVideoResolution();
    };
    clearInterval(interval.footer);
    interval.footer = setInterval(cvf, 500);
    cvf();
  };

  /**
   * 「次のエピソード」が表示されたらキャンセルボタンを押す
   * 可能ならスキップボタンを押す
   */
  const closeNextProgramInfo = () => {
    const nc = document.querySelector(selector.nextCancel),
      ncm = document.querySelector(selector.nextCancelMini),
      vs = document.querySelector(selector.videoSkip);
    if (setting.nextProgramInfo && nc instanceof HTMLButtonElement) {
      setTimeout(() => {
        nc.click();
      }, 2000);
    }
    if (setting.nextProgramInfo && ncm instanceof HTMLButtonElement) {
      setTimeout(() => {
        ncm.click();
      }, 2000);
    }
    if (setting.skipVideo && vs instanceof HTMLButtonElement) {
      clearInterval(interval.videoskip);
      interval.videoskip = setInterval(() => {
        const vs2 = document.querySelector(selector.videoSkip2);
        if (vs2 instanceof HTMLButtonElement) {
          clearInterval(interval.videoskip);
          vs2.click();
        } else if (!vs && !vs2) clearInterval(interval.videoskip);
      }, 500);
    }
  };

  /**
   * 一部の通知を閉じる
   */
  const closeNotificationToast = () => {
    log('closeNotificationToast');
    /** @type {HTMLButtonElement|null} */
    const closeButton = document.querySelector(selector.notificationClose),
      /** @type {HTMLVideoElement|null} */
      message = document.querySelector(selector.notificationMessage);
    if (closeButton && message) {
      if (/推奨環境外のため/.test(message.innerText)) {
        closeButton.click();
      }
    }
  };

  /**
   * 設定欄を閉じる
   */
  const closeSettings = () => {
    const settings = document.querySelector(`#${sid}_Settings`);
    settings?.classList.add(`${sid}_Settings_hidden`);
  };

  /**
   * 記入されたNGワードを処理しやすくするため正規表現用とそれ以外用に分ける
   * @param {string} t
   * @returns {string}
   */
  const convertNgword = (t) => {
    log('convertNgword');
    const aWord = t.split(/\r\n|\n|\r/),
      aText = [],
      aRe = [];
    let sError = '';
    for (let i = 0, j = aWord.length; i < j; i++) {
      const str = aWord[i].trim();
      if (str.slice(0, 2) !== '//') {
        if (/^\/.+\/[dgimsuvy]{0,}$/.test(str)) {
          try {
            RegExp(str);
            const re = str.slice(1, str.lastIndexOf('/')),
              flag = str.slice(str.lastIndexOf('/') + 1) || '';
            aRe.push({ r: re, f: flag });
          } catch (error) {
            sError += `${i + 1}行目: ${str}\n`;
          }
        } else {
          aText.push(str);
        }
      }
    }
    if (!sError) {
      data.ngWordText = [...aText];
      data.ngWordRe = [...aRe];
    }
    return sError;
  };

  /**
   * 設定欄を作成する
   */
  const createSettings = () => {
    const sSettings = `
<div id="${sid}_Settings-header">
  ${sid} 設定
</div>
<div id="${sid}_Settings-main">
  <input id="${sid}_Settings-Tab-General" type="radio" name="Tab" class="${sid}_Settings-tab-switch" checked="checked">
  <label class="${sid}_Settings-tab-label" for="${sid}_Settings-Tab-General">全般</label>
  <div id="${sid}_Settings-General" class="${sid}_Settings-tab-content">
    <fieldset>
      <legend>全般</legend>
      <label>
        <input id="${sid}_Settings-reduceNavigation" type="checkbox">
        ページを開いたとき左側のナビゲーションを縮める
      </label>
      <br>
      <label title="ナビゲーションを縮めているとき、ウィンドウ左端にマウスカーソルを合わせるとナビゲーションを表示します。">
        <input id="${sid}_Settings-mouseoverNavigation" type="checkbox">
        左端にマウスオーバーしたときナビゲーションを表示する
      </label>
      <br>
      <label>
        <input id="${sid}_Settings-closeNotification" type="checkbox">
        右上に表示される一部の通知を閉じる
      </label>
      <br>
      <label title="テレビ・ビデオでは最大解像度も表示します。">
        <input id="${sid}_Settings-videoResolution" type="checkbox">
        動画の解像度と表示領域サイズを表示する
      </label>
      <br>
      <label title="ABEMAトップページなどで、ヘッダーは固定表示せずにページのスクロールに追従するようにします。">
        <input id="${sid}_Settings-headerPosition" type="checkbox">
        ヘッダーを追従表示にする
      </label>
      <br>
      <label title="ヘッダー・ナビゲーションなどの背景や一部のボタンを半透明にします。">
        <input id="${sid}_Settings-semiTransparent" type="checkbox">
        ヘッダーやナビゲーションなどを半透明にする
      </label>
      <br>
      <label>
        <input id="${sid}_Settings-smallFontSize" type="checkbox">
        ヘッダーやフッターの一部の文字サイズを小さくする
      </label>
      <br>
      <label title="動画の表示幅を縮めずに右側のサイドパネルを動画に重ねて表示します。">
        <input id="${sid}_Settings-overlapSidePanel" type="checkbox">
        サイドパネルを動画に重ねて表示する
      </label>
      <br>
      <label title="「サイドパネルを動画に重ねて表示する」がONのときサイドパネルの背景が透明になりますが、Shift+Bキーで背景を半透明の黒色にします。&#13;&#10;入力欄にフォーカスしているときはAlt+Shift+Bキーで動作します。">
        <input id="${sid}_Settings-sidePanelBackground" type="checkbox">
        Shift+Bキーでサイドパネルの背景を半透明にする
      </label>
      <br>
      <label title="サイドパネルの幅を100px~1000pxに変更できます。&#13;&#10;初期値:320">
        <input id="${sid}_Settings-sidePanelSize" type="checkbox">
        サイドパネルの幅を変更する
        <input id="${sid}_Settings-sidePanelSizeNum" type="number" min="100" max="1000" step="10" ${
        setting.sidePanelSize ? '' : 'disabled'
      }>px
      </label>
      <br>
    </fieldset>
  </div>
  <input id="${sid}_Settings-Tab-Tv" type="radio" name="Tab" class="${sid}_Settings-tab-switch">
  <label class="${sid}_Settings-tab-label" for="${sid}_Settings-Tab-Tv">テレビ</label>
  <div id="${sid}_Settings-Tv" class="${sid}_Settings-tab-content">
    <fieldset>
      <legend>テレビ</legend>
      <label>
        <input id="${sid}_Settings-closeSidePanel" type="checkbox">
        ページを開いたとき右側のサイドパネルを閉じる
      </label>
      <br>
      <label>
        <input id="${sid}_Settings-sidePanelCloseButton" type="checkbox">
        サイドパネル上端にマウスオーバーしたとき閉じるボタンを表示する
      </label>
      <br>
      <label title="番組情報を開いたとき詳細情報を自動的に表示します。">
        <input id="${sid}_Settings-showProgramDetail" type="checkbox">
        サイドパネルに記載された番組情報の詳細を常に表示する
      </label>
      <br>
      <label title="ボタンにマウスオーバーしたときテキストを表示します。">
        <input id="${sid}_Settings-hiddenButtonText" type="checkbox">
        [最初から見る・番組情報・コメント]ボタンのテキストを隠す
      </label>
      <br>
    </fieldset>
  </div>
  <input id="${sid}_Settings-Tab-Video" type="radio" name="Tab" class="${sid}_Settings-tab-switch">
  <label class="${sid}_Settings-tab-label" for="${sid}_Settings-Tab-Video">ビデオ</label>
  <div id="${sid}_Settings-Video" class="${sid}_Settings-tab-content">
    <fieldset>
      <legend>ビデオ</legend>
        <label title="自動的に次のエピソードへ移動せずに最後まで再生できるようにします。">
          <input id="${sid}_Settings-nextProgramInfo" type="checkbox">
          再生中に[次のエピソード]が表示されたらキャンセルボタンを押す
        </label>
        <br>
        <label>
          <input id="${sid}_Settings-skipVideo" type="checkbox">
          再生中に可能ならスキップボタンを押す
        </label>
        <br>
        <label title="デフォルト表示では動画の上や左の余白を減らします。&#13;&#10;ワイド表示では動画の大きさをウィンドウ幅に合わせます。">
          <input id="${sid}_Settings-videoPadding" type="checkbox">
          動画周辺の余白を減らす
        </label>
        <br>
        <label title="可能であれば動画の上や左に隙間がなくなるようにページをスクロールします。">
          <input id="${sid}_Settings-dblclickScroll" type="checkbox">
          動画のコントローラーをダブルクリックしてスクロール位置を調整
        </label>
        <br>
    </fieldset>
  </div>
  <input id="${sid}_Settings-Tab-Comment" type="radio" name="Tab" class="${sid}_Settings-tab-switch">
  <label class="${sid}_Settings-tab-label" for="${sid}_Settings-Tab-Comment">コメント</label>
  <div id="${sid}_Settings-Comment" class="${sid}_Settings-tab-content">
    <fieldset>
      <legend>コメント</legend>
      <label>
        <input id="${sid}_Settings-newCommentOneByOne" type="checkbox">
        コメントを1つずつ表示する
      </label>
      <br>
      <label title="新着コメントが多い場合はスクロールせずに瞬時に表示します。">
        <input id="${sid}_Settings-scrollNewComment" type="checkbox">
        コメントを1つずつスクロールする
      </label>
      <br>
      <label title="いずれかのコメントにマウスカーソルを合わせている間、一時的に新着コメントを表示しません。">
        <input id="${sid}_Settings-stopCommentScroll" type="checkbox">
        コメントにマウスオーバーしたときスクロールを止める
      </label>
      <br>
      <label title="自分のコメントは背景を色付きの半透明で表示します。">
        <input id="${sid}_Settings-highlightNewComment" type="checkbox">
        自分のコメントとテレビでの新規コメントを強調表示する
      </label>
      <br>
      <label title="文字サイズを10px~32pxに変更できます。&#13;&#10;初期値:13">
        <input id="${sid}_Settings-commentFontSize" type="checkbox">
        コメントの文字サイズを変更する
        <input id="${sid}_Settings-commentFontSizeNum" type="number" min="10" max="32" ${
        setting.commentFontSize ? '' : 'disabled'
      }>px
      </label>
      <br>
      <label>
        <input id="${sid}_Settings-reduceCommentSpace" type="checkbox">
        コメント周辺の余白を減らす&行間を縮める
      </label>
      <br>
      <label title="コメントの透明度を0%~100%に変更できます。透明度は2つまで指定できます。&#13;&#10;入力欄にフォーカスしているときはAlt+Shift+Cキーで動作します。&#13;&#10;入力欄1の初期値:50">
        <input id="${sid}_Settings-hiddenCommentList" type="checkbox">
        Shift+Cキーでコメントリストを半透明化
        <input id="${sid}_Settings-hiddenCommentListNum" type="number" min="0" max="100" step="5" ${
        setting.hiddenCommentList ? '' : 'disabled'
      }>%
        <input id="${sid}_Settings-hiddenCommentListNum2" type="number" min="0" max="100" step="5" ${
        setting.hiddenCommentList ? '' : 'disabled'
      }>%
      </label>
      <br>
      <label title="各コメント右端のブロックアイコンをクリックすると表示されるフォームをEscキーで閉じます。">
        <input id="${sid}_Settings-escKey" type="checkbox">
        Escキーで[このユーザーをブロックします]をすべてキャンセルする
      </label>
      <br>
      <label title="コメントリストを上へスクロールしたときに表示される[新着コメント↓]ボタンもEnterキーで押します。">
        <input id="${sid}_Settings-enterKey" type="checkbox">
        Enterキーでコメント欄を開く&コメント入力欄にフォーカスする
      </label>
      <br>
      <label>
        <input id="${sid}_Settings-pageKey" type="checkbox">
        入力欄フォーカス時にPageUp/PageDownキーでコメントをスクロールする
      </label>
      <br>
    </fieldset>
  </div>
  <input id="${sid}_Settings-Tab-Quality" type="radio" name="Tab" class="${sid}_Settings-tab-switch">
  <label class="${sid}_Settings-tab-label" for="${sid}_Settings-Tab-Quality">画質</label>
  <div id="${sid}_Settings-Quality" class="${sid}_Settings-tab-content">
    <fieldset>
      <legend>
        <label title="チェックボックスONで画質機能を有効にします">
          <input id="${sid}_Settings-qualityEnable" type="checkbox">
          画質
        </label>
      </legend>
      <details>
        <summary>[説明]</summary>
        <p>番組の画質を設定します。ライブイベントには対応していません。</p>
        <p>番組を開いた直後やCM直後などから数秒後に設定した画質が反映されます。</p>
        <p>設定した画質が用意されていない番組ではそれよりも低い画質で再生します。</p>
      </details>
      <label>
        画質:
        <select id="${sid}_Settings-targetQuality" ${
        setting.qualityEnable ? '' : 'disabled'
      }>
          <option value="0">自動</option>
          <option value="1">通信節約モード(180p)</option>
          <option value="2">最低画質(240p)</option>
          <option value="3">低画質(360p)</option>
          <option value="4">中画質(480p)</option>
          <option value="5">高画質(720p)</option>
          <option value="6">最高画質(1080p)</option>
        </select>
      </label>
    </fieldset>
  </div>
  <input id="${sid}_Settings-Tab-Ng" type="radio" name="Tab" class="${sid}_Settings-tab-switch">
  <label class="${sid}_Settings-tab-label" for="${sid}_Settings-Tab-Ng">NG</label>
  <div id="${sid}_Settings-Ng" class="${sid}_Settings-tab-content">
    <fieldset>
      <legend>
        <label title="チェックボックスONでNGワード機能を有効にします">
          <input id="${sid}_Settings-ngWordEnable" type="checkbox">
          NGワード
        </label>
      </legend>
      <details>
        <summary>[説明]</summary>
        <p>NGワードに該当するコメントを表示しません。</p>
        <p>1行に1つのNGワードを記入してください。<br>
        先頭と末尾に / を付けると正規表現として扱います。<br>
        正規表現の末尾に i や u などを追記してフラグを使用できます。</p>
        <p>先頭が // の行はコメントとして扱うのでNGワードとして使用しません。</p>
        <p>記入例:
          <pre>
Aaa
bbb
/Ccc|DDD|eEe/

//大文字小文字を区別しません
/fff|ggg|hhh/i

//10桁以上の数字
/\\d{10,}/

//5文字以上の同じ文字を3回以上繰り返している
//(例:あいうえおあいうえおあいうえお)
/(.{5,})\\1{2,}/

//[ひらがな・カタカナ・漢字・絵文字・全角英数字・一部の全角記号]の
//いずれも含まない(Chromeなど一部のブラウザのみ有効)
/^(?!.*[\\p{scx=Hira}\\p{scx=Kana}\\p{scx=Han}\\p{RGI_Emoji}\\uFF01-\\uFF65]).*$/v
</pre>
        </p>
        <p>NGワードが多すぎると動作が重くなるのでご注意ください。</p>
      </details>
      <textarea id="${sid}_Settings-ngWord" ${
        setting.ngWordEnable ? '' : 'disabled'
      }></textarea>
      <div id="${sid}_Settings-ngWord-error">
        <p>エラー:下記の正規表現を修正してください。</p>
        <pre id="${sid}_Settings-ngWord-error-pre"></pre>
      </div>
      <label>
        <input id="${sid}_Settings-ngConsole" type="checkbox" ${
        setting.ngWordEnable ? '' : 'disabled'
      }>
        NGワードに該当したコメントをブラウザのコンソールに出力する
      </label>
    </fieldset>
    <fieldset>
      <legend>
        <label title="チェックボックスONでNG ID機能を有効にします">
          <input id="${sid}_Settings-ngIdEnable" type="checkbox">
          NG ID
        </label>
      </legend>
      <details>
        <summary>[説明]</summary>
        <p>ABEMAではコメント欄からブロックしたユーザーIDをブラウザに100件まで保存していて、それ以上ブロックしたときは古い方から破棄されます。<br>
        このNG ID機能ではその破棄されるユーザーIDを別枠で保存しておいてそのユーザーのコメントもブロックします。</p>
        <p>現在の保存数よりも少ない保存数に変更した場合は古いほうから溢れた分を破棄します。<br>
        0に変更するとすべて破棄します。</p>
      </details>
      <label>
        最大保存数:
        <select id="${sid}_Settings-ngIdMaxSize" ${
        setting.ngIdEnable ? '' : 'disabled'
      }>
          <option value="0">${setting._ngid[0]}</option>
          <option value="1">${setting._ngid[1]}</option>
          <option value="2">${setting._ngid[2]}</option>
          <option value="3">${setting._ngid[3]}</option>
          <option value="4">${setting._ngid[4]}</option>
          <option value="5">${setting._ngid[5]}</option>
          <option value="6">${setting._ngid[6]}</option>
          <option value="7">${setting._ngid[7]}</option>
          <option value="8">${setting._ngid[8]}</option>
          <option value="9">${setting._ngid[9]}</option>
        </select>
        <span id="${sid}_Settings-ngId-record"></span>
      </label>
    </fieldset>
  </div>
</div>
<div id="${sid}_Settings-footer">
  <button id="${sid}_Settings-ok">OK</button>
  <button id="${sid}_Settings-cancel">キャンセル</button>
</div>`,
      sMenu = `
<a class="com-a-Link com-a-Link--block" href="javascript:void(0);" id="${sid}_MenuItem">
  <div class="com-application-SideNavigationSubMenuItem__inner">
    <span class="com-application-SideNavigationSubMenuItem__text">${sid}</span>
  </div>
</a>
      `,
      eSettings = document.createElement('div'),
      eMenuItem = document.createElement('li');
    eSettings.id = `${sid}_Settings`;
    eSettings.className = `${sid}_Settings_hidden`;
    eSettings.innerHTML = sSettings;
    eMenuItem.className = 'com-application-SideNavigationSubMenuItem';
    eMenuItem.innerHTML = sMenu;
    eMenuItem.addEventListener('click', openSettings);
    document.body.appendChild(eSettings);
    document
      .getElementById(`${sid}_Settings-overlapSidePanel`)
      ?.addEventListener('change', () => {
        const osp = document.getElementById(`${sid}_Settings-overlapSidePanel`),
          spb = document.getElementById(`${sid}_Settings-sidePanelBackground`);
        if (
          osp instanceof HTMLInputElement &&
          spb instanceof HTMLInputElement
        ) {
          spb.disabled = osp.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-sidePanelSize`)
      ?.addEventListener('change', () => {
        const sps = document.getElementById(`${sid}_Settings-sidePanelSize`),
          spsn = document.getElementById(`${sid}_Settings-sidePanelSizeNum`);
        if (
          sps instanceof HTMLInputElement &&
          spsn instanceof HTMLInputElement
        ) {
          spsn.disabled = sps.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-newCommentOneByOne`)
      ?.addEventListener('change', () => {
        const snc = document.getElementById(`${sid}_Settings-scrollNewComment`),
          obo = document.getElementById(`${sid}_Settings-newCommentOneByOne`);
        if (
          snc instanceof HTMLInputElement &&
          obo instanceof HTMLInputElement
        ) {
          snc.disabled = obo.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-commentFontSize`)
      ?.addEventListener('change', () => {
        const cfs = document.getElementById(`${sid}_Settings-commentFontSize`),
          cfsn = document.getElementById(`${sid}_Settings-commentFontSizeNum`);
        if (
          cfs instanceof HTMLInputElement &&
          cfsn instanceof HTMLInputElement
        ) {
          cfsn.disabled = cfs.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-hiddenCommentList`)
      ?.addEventListener('change', () => {
        const hcl = document.getElementById(
            `${sid}_Settings-hiddenCommentList`
          ),
          hcln = document.getElementById(
            `${sid}_Settings-hiddenCommentListNum`
          ),
          hcln2 = document.getElementById(
            `${sid}_Settings-hiddenCommentListNum2`
          );
        if (
          hcl instanceof HTMLInputElement &&
          hcln instanceof HTMLInputElement &&
          hcln2 instanceof HTMLInputElement
        ) {
          hcln.disabled = hcl.checked ? false : true;
          hcln2.disabled = hcl.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-qualityEnable`)
      ?.addEventListener('change', () => {
        const qe = document.getElementById(`${sid}_Settings-qualityEnable`),
          tq = document.getElementById(`${sid}_Settings-targetQuality`);
        if (qe instanceof HTMLInputElement && tq instanceof HTMLSelectElement) {
          tq.disabled = qe.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-ngWordEnable`)
      ?.addEventListener('change', () => {
        const ngwe = document.getElementById(`${sid}_Settings-ngWordEnable`),
          ngw = document.getElementById(`${sid}_Settings-ngWord`),
          ngc = document.getElementById(`${sid}_Settings-ngConsole`);
        if (
          ngwe instanceof HTMLInputElement &&
          ngw instanceof HTMLTextAreaElement &&
          ngc instanceof HTMLInputElement
        ) {
          ngw.disabled = ngwe.checked ? false : true;
          ngc.disabled = ngwe.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-ngIdEnable`)
      ?.addEventListener('change', () => {
        const ngie = document.getElementById(`${sid}_Settings-ngIdEnable`),
          ngims = document.getElementById(`${sid}_Settings-ngIdMaxSize`);
        if (
          ngie instanceof HTMLInputElement &&
          ngims instanceof HTMLSelectElement
        ) {
          ngims.disabled = ngie.checked ? false : true;
        }
      });
    document
      .getElementById(`${sid}_Settings-ok`)
      ?.addEventListener('click', () => {
        const eWord = document.getElementById(`${sid}_Settings-ngWord`),
          eWordError = document.getElementById(`${sid}_Settings-ngWord-error`),
          eWordPre = document.getElementById(
            `${sid}_Settings-ngWord-error-pre`
          );
        let sError = '';
        if (eWord instanceof HTMLTextAreaElement) {
          const sWord = eWord.value;
          if (sWord) {
            sError = convertNgword(sWord);
          }
        }
        if (
          eWordError instanceof HTMLDivElement &&
          eWordPre instanceof HTMLPreElement
        ) {
          if (sError) {
            const eTabNg = document.getElementById(`${sid}_Settings-Tab-Ng`);
            if (eTabNg instanceof HTMLInputElement) {
              eWordPre.innerText = sError;
              eWordError.style.display = 'block';
              eTabNg.checked = true;
            }
          } else {
            eWordPre.innerText = '';
            eWordError.style.display = 'none';
            saveSettings();
            closeSettings();
          }
        }
      });
    document
      .getElementById(`${sid}_Settings-cancel`)
      ?.addEventListener('click', () => {
        closeSettings();
        loadSettings();
      });
    setTimeout(() => {
      const mypageMenu = document.querySelector(selector.mypageMenu);
      if (mypageMenu) {
        mypageMenu.appendChild(eMenuItem);
      } else log('createSettings: not found mypageMenu', 'warn');
    }, 1000);
  };

  /**
   * コメントのプロパティを取得する
   * @param {HTMLDivElement|HTMLFormElement|HTMLLIElement} e 調べる要素
   * @param {string} k キー名
   * @returns {boolean|string|undefined}
   */
  const getCommentProps = (e, k) => {
    let flag = false;
    for (const key in e) {
      if (key.startsWith('__reactFiber$')) {
        flag = true;
        if (k === 'isOwnComment') {
          if ('isOwnComment' in e[key].return.pendingProps) {
            return e[key].return?.pendingProps?.isOwnComment;
          }
          log('getCommentProps: not found key', k);
          console.debug(e);
          return false;
        }
        if (
          e[key].return?.pendingProps?.comment &&
          e[key].return.pendingProps.comment[`_${k}`]
        ) {
          return e[key].return.pendingProps.comment[`_${k}`];
        }
        if (
          e[key].return?.pendingProps?.commentItem &&
          e[key].return.pendingProps.commentItem[k]
        ) {
          return e[key].return.pendingProps.commentItem[k];
        }
      }
    }
    log('getCommentProps: not found key', k, flag);
    console.debug(e);
    return undefined;
  };

  /**
   * コメント欄の要素があるか調べる
   */
  const hasCommentElement = () => {
    const check = () => {
      const ca = document.querySelector(selector.commentArea);
      if (ca) {
        clearInterval(interval.comment);
        if (!ca.classList.contains(`${sid}_CommentElement`)) {
          log('hasCommentElement');
          setTimeout(() => {
            const cl = ca.querySelector(selector.commentList);
            if (cl) {
              ca.classList.add(`${sid}_CommentElement`);
              if (setting.stopCommentScroll) {
                const cl2 = cl.parentElement;
                if (cl2) {
                  cl2.addEventListener('mouseenter', checkMouseEnter);
                  cl2.addEventListener('mouseleave', checkMouseLeave);
                }
              }
              if (setting.newCommentOneByOne) {
                observerC.observe(cl, { childList: true });
                checkNewComments();
              }
            } else log('hasCommentElement: Not found element.', 'warn');
          }, 1000);
        }
      } else {
        clearInterval(interval.newcomment);
      }
    };
    clearInterval(interval.comment);
    interval.comment = setInterval(check, 500);
    check();
  };

  /**
   * 通知の要素があるか調べる
   */
  const hasNotification = () => {
    clearInterval(interval.notification);
    interval.notification = setInterval(() => {
      const noti = document.querySelector(selector.notification);
      if (noti) {
        clearInterval(interval.notification);
        closeNotificationToast();
      }
    }, 1000);
  };

  /**
   * サイドナビゲーションの要素があるか調べる
   */
  const hasSideNavigation = () => {
    log('hasSideNavigation');
    clearInterval(interval.navigation);
    interval.navigation = setInterval(() => {
      const navi = document.querySelector(selector.sideNavi);
      if (navi) {
        clearInterval(interval.navigation);
        if (
          !navi.classList.contains(selector.sideNaviColl) &&
          !navi.classList.contains(selector.sideNaviWrapColl)
        ) {
          reduceSideNavigation();
        }
      }
    }, 4000);
  };

  /**
   * VIDEO要素があるか調べる
   */
  const hasVideoElement = () => {
    clearInterval(interval.videoelement);
    interval.videoelement = setInterval(() => {
      const vi = returnVideo();
      if (vi) {
        clearInterval(interval.videoelement);
        if (!vi.classList.contains(`${sid}_VideoElement`)) {
          log('hasVideoElement');
          vi.classList.add(`${sid}_VideoElement`);
          observerV.observe(vi, { attributes: true });
          observerR.observe(vi);
          if (setting.qualityEnable) {
            changeTargetQuality(setting.targetQuality);
          }
        }
      }
    }, 500);
  };

  /**
   * ページを開いたときに実行
   */
  const init = () => {
    log('init');
    addStyle('init');
    checkVersion();
    setInitialValue();
    if (!document.getElementById(`${sid}_Settings`)) createSettings();
    convertNgword(setting.ngWord);
    checkBlockedUser(true);
    setTimeout(startFirstObserve, 1000);
    if (setting.highlightNewComment) addStyle('highlightNewComment');
    if (setting.sidePanelCloseButton) addStyle('sidePanelCloseButton');
    if (setting.showProgramDetail) addStyle('showProgramDetail');
    if (setting.overlapSidePanel) addStyle('overlapSidePanel');
    if (setting.sidePanelSize) addStyle('sidePanelSize');
    if (setting.hiddenButtonText) addStyle('hiddenButtonText');
    if (setting.videoResolution) addStyle('videoResolution');
    if (setting.semiTransparent) addStyle('semiTransparent');
    if (setting.smallFontSize) addStyle('smallFontSize');
    if (setting.commentFontSize) addStyle('commentFontSize');
    if (setting.reduceCommentSpace) addStyle('reduceCommentSpace');
    if (
      setting.headerPosition &&
      !/^https:\/\/abema\.tv\/now-on-air\/.+$/.test(location.href)
    ) {
      addStyle('headerPosition');
    }
    if (setting.videoPadding) addStyle('videoPadding');
    if (setting.mouseoverNavigation) addStyle('mouseoverNavigation');
    if (setting.reduceNavigation) hasSideNavigation();
    if (setting.closeNotification) hasNotification();
  };

  /**
   * 設定を読み込んで設定欄に反映する
   */
  const loadSettings = () => {
    /**
     * 変数aの型がsとは異なる場合trueを返す
     * @param {any} a 判別したい変数
     * @param {string} t 型
     * @returns {boolean}
     */
    const notType = (a, t) =>
      Object.prototype.toString.call(a).slice(8, -1) !== t ? true : false;
    /**
     * 保存している値を設定欄のチェックボックスに反映する
     * @param {string} s 変数名
     * @param {string} t 型
     */
    const setCheck = (s, t) => {
      const e = document.getElementById(`${sid}_Settings-${s}`);
      if (e instanceof HTMLInputElement && !notType(setting[s], t)) {
        e.checked = setting[s];
      }
    };
    /**
     * 保存している値を設定欄のセレクトボックスに反映する
     * @param {string} s 変数名
     * @param {string} t 型
     */
    const setSelect = (s, t) => {
      const e = document.getElementById(`${sid}_Settings-${s}`);
      if (e) {
        if (e instanceof HTMLSelectElement && !notType(setting[s], t)) {
          e.options.selectedIndex = setting[s];
        }
      }
    };
    /**
     * 保存している値を設定欄の入力ボックス・テキストエリアに反映する
     * @param {string} s 変数名
     * @param {string} t 型
     */
    const setValue = (s, t) => {
      const e = document.getElementById(`${sid}_Settings-${s}`);
      if (e instanceof HTMLTextAreaElement) {
        if (s === 'ngWord' && !notType(lsWord[s], t)) e.value = lsWord[s];
      } else if (e instanceof HTMLInputElement) {
        if (!notType(setting[s], t)) e.value = setting[s];
      }
    };
    setCheck('reduceNavigation', 'Boolean');
    setCheck('hiddenButtonText', 'Boolean');
    setCheck('closeNotification', 'Boolean');
    setCheck('overlapSidePanel', 'Boolean');
    setCheck('videoResolution', 'Boolean');
    setCheck('semiTransparent', 'Boolean');
    setCheck('smallFontSize', 'Boolean');
    setCheck('closeSidePanel', 'Boolean');
    setCheck('sidePanelCloseButton', 'Boolean');
    setCheck('showProgramDetail', 'Boolean');
    setCheck('sidePanelBackground', 'Boolean');
    setCheck('sidePanelSize', 'Boolean');
    setValue('sidePanelSizeNum', 'String');
    setCheck('nextProgramInfo', 'Boolean');
    setCheck('skipVideo', 'Boolean');
    setCheck('headerPosition', 'Boolean');
    setCheck('videoPadding', 'Boolean');
    setCheck('dblclickScroll', 'Boolean');
    setCheck('mouseoverNavigation', 'Boolean');
    setCheck('newCommentOneByOne', 'Boolean');
    setCheck('scrollNewComment', 'Boolean');
    setCheck('stopCommentScroll', 'Boolean');
    setCheck('highlightNewComment', 'Boolean');
    setCheck('commentFontSize', 'Boolean');
    setValue('commentFontSizeNum', 'String');
    setCheck('reduceCommentSpace', 'Boolean');
    setCheck('hiddenCommentList', 'Boolean');
    setValue('hiddenCommentListNum', 'String');
    setValue('hiddenCommentListNum2', 'String');
    setCheck('escKey', 'Boolean');
    setCheck('enterKey', 'Boolean');
    setCheck('pageKey', 'Boolean');
    setCheck('qualityEnable', 'Boolean');
    setSelect('targetQuality', 'Number');
    setCheck('ngWordEnable', 'Boolean');
    setValue('ngWord', 'String');
    setSelect('ngIdMaxSize', 'Number');
    setCheck('ngConsole', 'Boolean');
    setCheck('ngIdEnable', 'Boolean');
    const spb = document.getElementById(`${sid}_Settings-sidePanelBackground`);
    if (spb instanceof HTMLInputElement) {
      spb.disabled = setting.overlapSidePanel ? false : true;
    }
    const snc = document.getElementById(`${sid}_Settings-scrollNewComment`);
    if (snc instanceof HTMLInputElement) {
      snc.disabled = setting.newCommentOneByOne ? false : true;
    }
    const tq = document.getElementById(`${sid}_Settings-targetQuality`);
    if (tq instanceof HTMLSelectElement) {
      tq.disabled = setting.qualityEnable ? false : true;
    }
    const ngw = document.getElementById(`${sid}_Settings-ngWord`),
      ngc = document.getElementById(`${sid}_Settings-ngConsole`);
    if (ngw instanceof HTMLTextAreaElement && ngc instanceof HTMLInputElement) {
      ngw.disabled = setting.ngWordEnable ? false : true;
      ngc.disabled = setting.ngWordEnable ? false : true;
    }
    const ngims = document.getElementById(`${sid}_Settings-ngIdMaxSize`);
    if (ngims instanceof HTMLSelectElement) {
      ngims.disabled = setting.ngIdEnable ? false : true;
    }
    const ngwe = document.getElementById(`${sid}_Settings-ngWord-error`),
      ngwep = document.getElementById(`${sid}_Settings-ngWord-error-pre`);
    if (ngwe instanceof HTMLDivElement && ngwep instanceof HTMLPreElement) {
      ngwep.innerText = '';
      ngwe.style.display = 'none';
    }
    const record = document.getElementById(`${sid}_Settings-ngId-record`);
    if (record instanceof HTMLSpanElement) {
      record.textContent = data.ngId.size
        ? `(現在の保存数:${data.ngId.size})`
        : '';
    }
  };

  /**
   * デバッグ用ログ
   * @param {...any} a
   */
  const log = (...a) => {
    if (ls.debug) {
      try {
        if (/^debug$|^error$|^info$|^warn$/.test(a[a.length - 1])) {
          const b = a.pop();
          console[b](sid, a.join('  '));
        } else console.log(sid, a.join('  '));
      } catch (e) {
        if (e instanceof Error) console.error(e.message, ...a);
        else if (typeof e === 'string') console.error(e, ...a);
        else console.error('log error', ...a);
      }
    }
  };

  /**
   * 設定欄を開く
   */
  const openSettings = () => {
    const settings = document.querySelector(`#${sid}_Settings`);
    if (settings && settings.classList.contains(`${sid}_Settings_hidden`)) {
      loadSettings();
      settings.classList.remove(`${sid}_Settings_hidden`);
    }
  };

  /**
   * サイドナビゲーションを縮める
   */
  const reduceSideNavigation = () => {
    log('reduceSideNavigation');
    /** @type {HTMLButtonElement|null} */
    const headerMenu = document.querySelector(selector.headerMenu);
    headerMenu?.click();
  };

  /**
   * 動画の大きさが変わったとき
   */
  const resizeVideo = () => {
    if (setting.qualityEnable) {
      clearTimeout(interval.resizeVideo);
      interval.resizeVideo = setTimeout(() => {
        changeTargetQuality(setting.targetQuality);
        checkVisibleFooter();
      }, 500);
    }
  };

  /**
   * スタイルを追加・削除する
   * @param {string} s スタイルの設定名
   * @param {boolean} b trueならスタイルを追加
   */
  const reStyle = (s, b) => {
    removeStyle(s);
    if (b) addStyle(s);
  };

  const removeStyle = (s) => {
    const e = document.getElementById(`${sid}_style_${s}`);
    if (e instanceof HTMLStyleElement) e.remove();
  };

  /**
   * video要素を返す
   * @returns {HTMLVideoElement|null}
   */
  const returnVideo = () => {
    /** @type {HTMLVideoElement|null} */
    const vi = document.querySelector(selector.video);
    return vi ? vi : null;
  };

  /**
   * 動画のvideoTracksを返す
   * @returns {object|undefined}
   */
  const returnVideoTracks = () => {
    const vc = document.querySelector(selector.videoContainer);
    for (const key in vc) {
      if (
        key.startsWith('__reactFiber$') &&
        vc[key].return?.stateNode?.player?.videoTracks[0]
      ) {
        return vc[key].return.stateNode.player.videoTracks[0];
      }
    }
    return undefined;
  };

  /**
   * 設定を保存する
   */
  const saveSettings = () => {
    /**
     * 設定欄のチェックボックスの値を取得する
     * @param {string} s 変数名
     */
    const getCheck = (s) => {
      const e = document.getElementById(`${sid}_Settings-${s}`);
      if (e instanceof HTMLInputElement) {
        setting[s] = e.checked ? true : false;
      }
    };
    /**
     * 設定欄のセレクトボックスの値を取得する
     * @param {string} s 変数名
     */
    const getSelect = (s) => {
      const e = document.getElementById(`${sid}_Settings-${s}`);
      if (e instanceof HTMLSelectElement) {
        const index = e.options.selectedIndex;
        if (Number.isInteger(index) && index >= 0) {
          setting[s] = index;
        }
      }
    };
    /**
     * 設定欄の入力ボックス・テキストエリアの値を取得する
     * @param {string} s 変数名
     */
    const getValue = (s) => {
      const e = document.getElementById(`${sid}_Settings-${s}`);
      if (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) {
        if (s === 'commentFontSizeNum') {
          if (!e.value || isNaN(Number(e.value)) || /e/.test(e.value)) {
            e.value = '13';
          } else if (Number(e.value) < 10) e.value = '10';
          else if (Number(e.value) > 32) e.value = '32';
        } else if (
          s === 'hiddenCommentListNum' ||
          s === 'hiddenCommentListNum2'
        ) {
          if (!e.value || isNaN(Number(e.value)) || /e/.test(e.value)) {
            if (s === 'hiddenCommentListNum') e.value = '50';
            else if (s === 'hiddenCommentListNum2') e.value = '';
          } else if (Number(e.value) < 0) e.value = '0';
          else if (Number(e.value) > 100) e.value = '100';
        } else if (s === 'sidePanelSizeNum') {
          if (!e.value || isNaN(Number(e.value)) || /e/.test(e.value)) {
            e.value = '320';
          } else if (Number(e.value) < 100) e.value = '100';
          else if (Number(e.value) > 1000) e.value = '1000';
        }
        setting[s] = e.value ? e.value : '';
      }
    };
    document.getElementById(`${sid}_Settings-ok`)?.blur();
    getCheck('reduceNavigation');
    getCheck('hiddenButtonText');
    getCheck('closeNotification');
    getCheck('overlapSidePanel');
    getCheck('videoResolution');
    getCheck('semiTransparent');
    getCheck('smallFontSize');
    getCheck('closeSidePanel');
    getCheck('sidePanelBackground');
    getCheck('sidePanelCloseButton');
    getCheck('showProgramDetail');
    getCheck('sidePanelSize');
    getValue('sidePanelSizeNum');
    getCheck('nextProgramInfo');
    getCheck('skipVideo');
    getCheck('headerPosition');
    getCheck('videoPadding');
    getCheck('dblclickScroll');
    getCheck('mouseoverNavigation');
    getCheck('newCommentOneByOne');
    getCheck('scrollNewComment');
    getCheck('stopCommentScroll');
    getCheck('highlightNewComment');
    getCheck('commentFontSize');
    getValue('commentFontSizeNum');
    getCheck('reduceCommentSpace');
    getCheck('hiddenCommentList');
    getValue('hiddenCommentListNum');
    getValue('hiddenCommentListNum2');
    getCheck('escKey');
    getCheck('enterKey');
    getCheck('pageKey');
    getCheck('qualityEnable');
    getSelect('targetQuality');
    getCheck('ngWordEnable');
    getValue('ngWord');
    getSelect('ngIdMaxSize');
    getCheck('ngConsole');
    getCheck('ngIdEnable');
    ls.reduceNavigation = setting.reduceNavigation;
    if (ls.hiddenButtonText !== setting.hiddenButtonText) {
      reStyle('hiddenButtonText', setting.hiddenButtonText);
    }
    ls.hiddenButtonText = setting.hiddenButtonText;
    ls.closeNotification = setting.closeNotification;
    if (ls.videoResolution !== setting.videoResolution) {
      reStyle('videoResolution', setting.videoResolution);
    }
    ls.videoResolution = setting.videoResolution;
    if (ls.semiTransparent !== setting.semiTransparent) {
      reStyle('semiTransparent', setting.semiTransparent);
    }
    ls.semiTransparent = setting.semiTransparent;
    if (ls.smallFontSize !== setting.smallFontSize) {
      reStyle('smallFontSize', setting.smallFontSize);
    }
    ls.smallFontSize = setting.smallFontSize;
    ls.closeSidePanel = setting.closeSidePanel;
    if (ls.overlapSidePanel !== setting.overlapSidePanel) {
      reStyle('overlapSidePanel', setting.overlapSidePanel);
      if (setting.highlightNewComment) reStyle('highlightNewComment', true);
    }
    ls.overlapSidePanel = setting.overlapSidePanel;
    ls.sidePanelBackground = setting.sidePanelBackground;
    if (ls.sidePanelCloseButton !== setting.sidePanelCloseButton) {
      reStyle('sidePanelCloseButton', setting.sidePanelCloseButton);
    }
    ls.sidePanelCloseButton = setting.sidePanelCloseButton;
    if (ls.showProgramDetail !== setting.showProgramDetail) {
      reStyle('showProgramDetail', setting.showProgramDetail);
    }
    ls.showProgramDetail = setting.showProgramDetail;
    if (ls.sidePanelSize || ls.sidePanelSize !== setting.sidePanelSize) {
      reStyle('overlapSidePanel', setting.overlapSidePanel);
      reStyle('sidePanelSize', setting.sidePanelSize);
    }
    ls.sidePanelSize = setting.sidePanelSize;
    ls.sidePanelSizeNum = setting.sidePanelSizeNum;
    ls.nextProgramInfo = setting.nextProgramInfo;
    ls.skipVideo = setting.skipVideo;
    if (ls.headerPosition !== setting.headerPosition) {
      reStyle('headerPosition', setting.headerPosition);
    }
    if (/^https:\/\/abema\.tv\/now-on-air\/.+$/.test(location.href)) {
      removeStyle('headerPosition');
    }
    ls.headerPosition = setting.headerPosition;
    if (ls.videoPadding !== setting.videoPadding) {
      reStyle('videoPadding', setting.videoPadding);
    }
    ls.videoPadding = setting.videoPadding;
    if (ls.mouseoverNavigation !== setting.mouseoverNavigation) {
      reStyle('mouseoverNavigation', setting.mouseoverNavigation);
    }
    ls.mouseoverNavigation = setting.mouseoverNavigation;
    ls.newCommentOneByOne = setting.newCommentOneByOne;
    ls.scrollNewComment = setting.scrollNewComment;
    ls.stopCommentScroll = setting.stopCommentScroll;
    if (ls.highlightNewComment !== setting.highlightNewComment) {
      reStyle('highlightNewComment', setting.highlightNewComment);
    }
    ls.highlightNewComment = setting.highlightNewComment;
    if (ls.commentFontSizeNum !== setting.commentFontSizeNum) {
      reStyle('commentFontSize', setting.commentFontSize);
    }
    ls.commentFontSizeNum = setting.commentFontSizeNum;
    if (ls.commentFontSize !== setting.commentFontSize) {
      reStyle('commentFontSize', setting.commentFontSize);
    }
    ls.commentFontSize = setting.commentFontSize;
    if (ls.reduceCommentSpace !== setting.reduceCommentSpace) {
      reStyle('reduceCommentSpace', setting.reduceCommentSpace);
    }
    ls.reduceCommentSpace = setting.reduceCommentSpace;
    if (ls.hiddenCommentList && !setting.hiddenCommentList) {
      removeStyle('hiddenCommentList');
      removeStyle('hiddenCommentList2');
    }
    ls.hiddenCommentList = setting.hiddenCommentList;
    if (
      setting.hiddenCommentList &&
      ls.hiddenCommentListNum !== setting.hiddenCommentListNum &&
      document.getElementById(`${sid}_style_hiddenCommentList`)
    ) {
      reStyle('hiddenCommentList', true);
    }
    ls.hiddenCommentListNum = setting.hiddenCommentListNum;
    if (
      setting.hiddenCommentList &&
      ls.hiddenCommentListNum2 !== setting.hiddenCommentListNum2 &&
      document.getElementById(`${sid}_style_hiddenCommentList2`)
    ) {
      reStyle('hiddenCommentList2', setting.hiddenCommentListNum2);
    }
    ls.hiddenCommentListNum2 = setting.hiddenCommentListNum2;
    ls.escKey = setting.escKey;
    ls.enterKey = setting.enterKey;
    ls.pageKey = setting.pageKey;
    ls.qualityEnable = setting.qualityEnable;
    if (setting.qualityEnable && setting.targetQuality !== ls.targetQuality) {
      changeTargetQuality(setting.targetQuality);
    }
    ls.targetQuality = setting.targetQuality;
    ls.ngWordEnable = setting.ngWordEnable;
    lsWord.ngWord = setting.ngWord;
    ls.ngIdEnable = setting.ngIdEnable;
    lsId.ngId = setting.ngId ? [...setting.ngId] : [];
    data.ngId = new Set(setting.ngId);
    if (
      ls.ngIdMaxSize < setting.ngIdMaxSize &&
      setting.ngId.length > setting._ngid[ls.ngIdMaxSize]
    ) {
      setting.ngId.splice(
        0,
        setting.ngId.length - setting._ngid[ls.ngIdMaxSize]
      );
      data.ngId = new Set(setting.ngId);
    }
    ls.ngIdMaxSize = setting.ngIdMaxSize;
    ls.ngConsole = setting.ngConsole;
    ls.ngIdEnable = setting.ngIdEnable;
    saveStorage();
  };

  /**
   * ローカルストレージに保存する
   */
  const saveStorage = () => {
    localStorage.setItem(sid, JSON.stringify(ls));
    localStorage.setItem(`${sid}-Word`, JSON.stringify(lsWord));
    localStorage.setItem(`${sid}-Id`, JSON.stringify(lsId));
  };

  /**
   * 可能であれば動画の上側や左側に隙間がなくなるようにページをスクロールする
   */
  const adjustScrollPosition = () => {
    log('adjustScrollPosition');
    const player = document.querySelector(selector.videoMainPlayer);
    if (player) {
      player.scrollIntoView({ behavior: 'smooth', inline: 'start' });
    }
  };

  /**
   * このスクリプトを初めて使うときやローカルストレージを削除したとき初期値を登録する
   */
  const setInitialValue = () => {
    /**
     * 変数aの型がsとは異なる場合trueを返す
     * @param {string} t 型
     * @param {any} a 判別したい変数
     * @returns {boolean}
     */
    const notType = (t, a) =>
      Object.prototype.toString.call(a).slice(8, -1) !== t ? true : false;
    /**
     * 設定用の変数が異なる型の場合は初期値を登録する
     * @param {string} s 変数名
     * @param {string} t 型の先頭3文字
     * @param {any} a 初期値
     */
    const setValue = (s, t, a) => {
      if (notType(t, setting[s])) {
        setting[s] = a;
        if (s === 'ngWord') lsWord[s] = '';
        else if (s === 'ngId') lsId[s] = [];
        else setting[s] = a;
      }
    };
    if (!lsWord.ngWord) lsWord.ngWord = '';
    if (!lsId.ngId) lsId.ngId = [];
    setValue('reduceNavigation', 'Boolean', true);
    setValue('hiddenButtonText', 'Boolean', true);
    setValue('closeNotification', 'Boolean', false);
    setValue('videoResolution', 'Boolean', true);
    setValue('semiTransparent', 'Boolean', true);
    setValue('smallFontSize', 'Boolean', true);
    setValue('closeSidePanel', 'Boolean', true);
    setValue('overlapSidePanel', 'Boolean', true);
    setValue('sidePanelBackground', 'Boolean', true);
    setValue('sidePanelCloseButton', 'Boolean', true);
    setValue('showProgramDetail', 'Boolean', true);
    setValue('sidePanelSize', 'Boolean', false);
    setValue('sidePanelSizeNum', 'String', '320');
    setValue('nextProgramInfo', 'Boolean', true);
    setValue('skipVideo', 'Boolean', false);
    setValue('headerPosition', 'Boolean', true);
    setValue('videoPadding', 'Boolean', true);
    setValue('dblclickScroll', 'Boolean', true);
    setValue('mouseoverNavigation', 'Boolean', true);
    setValue('newCommentOneByOne', 'Boolean', true);
    setValue('scrollNewComment', 'Boolean', true);
    setValue('stopCommentScroll', 'Boolean', true);
    setValue('highlightNewComment', 'Boolean', true);
    setValue('commentFontSize', 'Boolean', false);
    setValue('commentFontSizeNum', 'String', '13');
    setValue('reduceCommentSpace', 'Boolean', true);
    setValue('hiddenCommentList', 'Boolean', true);
    setValue('hiddenCommentListNum', 'String', '50');
    setValue('hiddenCommentListNum2', 'String', '');
    setValue('escKey', 'Boolean', true);
    setValue('enterKey', 'Boolean', true);
    setValue('pageKey', 'Boolean', false);
    setValue('qualityEnable', 'Boolean', true);
    setValue('targetQuality', 'Number', 0);
    setValue('ngWordEnable', 'Boolean', true);
    setValue('ngWord', 'String', '');
    setValue('ngIdMaxSize', 'Number', 0);
    setValue('ngId', 'Array', '');
    setValue('ngConsole', 'Boolean', false);
    setValue('ngIdEnable', 'Boolean', true);
    saveStorage();
  };

  /**
   * 動画の解像度と表示領域サイズを調べて表示する
   */
  const showVideoResolution = () => {
    clearTimeout(interval.resolution);
    interval.resolution = setTimeout(() => {
      const dpr = window.devicePixelRatio,
        footer = document.querySelector(selector.footer),
        vi = returnVideo(),
        vr = document.getElementById(`${sid}_VideoResolution`),
        ch = vi?.clientHeight,
        cw = vi?.clientWidth,
        vh = vi?.videoHeight,
        vw = vi?.videoWidth;
      if (vi && dpr && ch && cw && vh && vw) {
        if (
          video.pixelRatio !== dpr ||
          video.clientHeight !== ch ||
          video.clientWidth !== cw ||
          video.videoHeight !== vh ||
          video.videoWidth !== vw
        ) {
          log('showVideoResolution');
          let desc = `動画解像度: ${vw}×${vh}`,
            maxHeight = '';
          if (
            /^https:\/\/abema\.tv\/(?:now-on-air|live-event)\/.+$/.test(
              location.href
            )
          ) {
            const vt = returnVideoTracks();
            if (vt?.qualities?.length > 1) {
              maxHeight =
                vt.qualities[0].height <= 240
                  ? vt.qualities[vt.qualities.length - 1].height
                  : vt.qualities[0].height;
            }
            if (maxHeight) desc += ` (Max: ${maxHeight}p)`;
          }
          desc += ` / 表示領域: ${cw}×${ch}`;
          if (dpr !== 1) desc += ` * ${dpr}`;
          if (vr) {
            vr.innerText = desc;
          } else {
            const div = document.createElement('div');
            div.id = `${sid}_VideoResolution`;
            div.innerText = desc;
            if (footer) footer.appendChild(div);
          }
          video.pixelRatio = dpr;
          video.clientHeight = ch;
          video.clientWidth = cw;
          video.videoHeight = vh;
          video.videoWidth = vw;
        }
      }
    }, 100);
  };

  /**
   * ページを開いて動画が表示されたら1度だけ実行
   */
  const startFirstObserve = () => {
    log('startFirstObserve');
    document.addEventListener('keydown', checkKeyDown, true);
    document.addEventListener('click', checkClick);
    document.addEventListener('dblclick', checkDblclick);
    const main = document.querySelector(selector.main);
    if (main) {
      observerE.observe(main, { childList: true, subtree: true });
      observerT.observe(main, { childList: true });
    } else log('startFirstObserve: Not found element.', 'warn');
  };

  const observerC = new MutationObserver(checkNewComments),
    observerE = new MutationObserver(changeElements),
    observerR = new ResizeObserver(resizeVideo),
    observerT = new MutationObserver(changePageTitle),
    observerV = new MutationObserver(changeVideoSource);
  clearInterval(interval.init);
  interval.init = setInterval(() => {
    if (document.querySelector(selector.main)) {
      clearInterval(interval.init);
      init();
    }
  }, 500);
})();