Greasy Fork is available in English.

dアニメストアPlus

dアニメストアに様々な機能を追加します

// ==UserScript==
// @name        dアニメストアPlus
// @namespace   https://github.com/chimaha/dAnimePlus
// @match       https://animestore.docomo.ne.jp/animestore/*
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @version     1.9.3
// @author      chimaha
// @description dアニメストアに様々な機能を追加します
// @license     MIT license
// @icon        https://animestore.docomo.ne.jp/favicon.ico
// @compatible  firefox
// @compatible  chrome
// @supportURL  https://github.com/chimaha/dAnimePlus/issues
// ==/UserScript==

/*! dアニメストアPlus | MIT license | https://github.com/chimaha/dAnimePlus/blob/main/LICENSE */

/*!
 *dアニメストア便利化 (https://greasyfork.org/ja/scripts/414008)
 *Copyright (c) 2020 家守カホウ
 *Released under the MIT license.
 *see https://opensource.org/licenses/MIT
 */

"use strict";

// 解像度表示 + 制作年度表示------------------------------------------------------------------
let qualityCount = 0;

function addResolutionStyle() {
  if (document.getElementById("resolution-style")) {
    return;
  }
  document.head.insertAdjacentHTML("beforeend", '<style id="resolution-style"></style>');
  document.head.lastElementChild.textContent = `
		.quality,
		.production-year {
			position: absolute;
			top: 3px;
			border-radius: 4px;
			padding: 0.5px 4px;
			background-color: rgba(255,255,255,0.8);
			text-decoration: none !important;
		}
		.quality {
			left: 3px;
		}
		.production-year {
			right: 3px;
		}
		.quality > span,
		.production-year > span {
			font-size: 11px;
			font-weight: bold;
			text-decoration: none !important;
		}`;
}

function sort(workids, json) {
  const sorted = [];
  const yearSorted = [];
  for (const workid of workids) {
    const item = json.find((res) => res["id"] == workid);
    sorted.push(item["distribution"]["quality"]);
    yearSorted.push(item["details"]["production_year"]);
  }
  return [sorted, yearSorted];
}

function resolutionBranch(div, quality) {
  switch (quality) {
    case "fhd":
      div("1080p");
      break;
    case "hd":
      div("720p");
      break;
    default:
      div("480p");
  }
}

function qualityAndYear(torf) {
  addResolutionStyle();
  const playerMypage = document.querySelectorAll(".thumbnailContainer > a");
  if (playerMypage.length == 0) {
    return;
  }

  let workIds = [];
  for (let i = 0; i < playerMypage.length; i++) {
    const workId = new URL(document.querySelectorAll(".textContainer[href]")[i]).searchParams.get(
      "workId"
    );
    workIds.push(workId);
  }
  workIds = workIds.slice(qualityCount);
  const fetchAsync = async () => {
    const url =
      "https://animestore.docomo.ne.jp/animestore/rest/v1/works?work_id=" + workIds.join(",");
    const response = await fetch(url);
    const json = await response.json();
    const [sorted, yearSorted] = sort(workIds, json);

    for (let i = 0; i < sorted.length; i++) {
      const quality = sorted[i];
      const year = yearSorted[i];

      function div(quality) {
        let headerquality = "";
        if (torf) {
          if (resolutionbool) {
            headerquality += `
                            <div class="quality">
						    	<span>${quality}</span>
						    </div>
                        `;
          }

          if (addProductionYear) {
            headerquality += `
                            <div class="production-year">
						    	<span>${year}年</span>
						    </div>
                        `;
          }
        } else {
          headerquality = `
						<div class="quality">
							<span>${quality}</span>
						</div>`;
        }
        playerMypage[i + qualityCount].insertAdjacentHTML("beforeend", headerquality);
      }

      resolutionBranch(div, quality);
    }

    qualityCount = qualityCount + sorted.length;
  };

  fetchAsync();
}
// -----------------------------------------------------------------------------------------

// サムネイルとテキストをクリックすると新規タブで開く + mouseover -------------------------------
function addHoverStyle() {
  if (!document.getElementById("mouseover-style")) {
    document.head.insertAdjacentHTML("beforeend", '<style id="mouseover-style"></style>');
    document.head.lastElementChild.textContent = `
			.textContainer:hover {
				cursor: pointer;
			}
			.itemModuleIn:hover .textContainer {
				text-decoration: underline;
			}
			.itemModuleIn:hover .thumbnailContainer > a > .imgWrap16x9 {
				opacity : .6;
			}`;
  }
}

function thumbnailclick() {
  const playerMypage = document.querySelectorAll(".thumbnailContainer > a");
  if (playerMypage.length == 0) {
    return;
  }

  for (let i = 0; i < playerMypage.length; i++) {
    playerMypage[i].removeAttribute("onclick");
    const getHref = document.querySelectorAll(".textContainer[href]")[i];
    const urlGet = new URL(getHref.getAttribute("href"));
    const partId = urlGet.searchParams.get("partId");
    const openUrl = "https://animestore.docomo.ne.jp/animestore/sc_d_pc?partId=" + partId;
    // サムネイルとテキストをクリックすると新規タブで開く
    playerMypage[i].addEventListener("click", () => {
      open(openUrl);
    });
    getHref.addEventListener("click", () => {
      open(openUrl);
    });
  }
  const getHref = document.querySelectorAll(".textContainer[href]");
  for (let i = 0; i < getHref.length; i++) {
    getHref[i].removeAttribute("href");
  }
  addHoverStyle();
}
// -----------------------------------------------------------------------------------------

// 検索結果で常に順番の選択肢を表示
function showOrder() {
  document.head.insertAdjacentHTML("beforeend", '<style id="show-order"></style>');
  document.head.lastElementChild.textContent = `
		.minict_wrapper > span {
			display: none;
		}
		.minict_wrapper > ul {
			display: flex !important;
			border: none;
			border-top: none;
			box-shadow: none;
			width: max-content;
			background: none;
			top: -20px;
			right: -10px;
			opacity: 1 !important;
			user-select: none;
		}
		.minict_wrapper > ul > li {
			padding: 20px 30px;
			border-bottom: none;
		}`;
}

function restCount() {
  const observer = new MutationObserver(() => {
    qualityCount = 0;
  });
  const config = { childList: true };
  observer.observe(document.querySelector(".listHeader > p.headerText"), config);
}

// 解像度を表示選択
let resolutionbool = GM_getValue("menu", true);
let addProductionYear = GM_getValue("addProductionYear", false);
let titlebool = GM_getValue("seekbarTitle", true);
let hideDetailBool = GM_getValue("hideDetail", false);

const path = window.location.pathname.replace("/animestore/", "");
if (path == "mpa_fav_pc" || path == "mpa_hst_pc") {
  // 気になる、視聴履歴
  if (resolutionbool) {
    qualityAndYear(false);
  }
  thumbnailclick();
} else if (path == "mp_viw_pc") {
  // 続きから見る
  const playerMypage = document.querySelectorAll(".thumbnailContainer > a");
  for (let i = 0; i < playerMypage.length; i++) {
    const workId = new URL(
      document.querySelectorAll(".textContainer")[i].getAttribute("href")
    ).searchParams.get("workId");

    // header作成
    const hrefLink = "https://animestore.docomo.ne.jp/animestore/ci_pc?workId=" + workId;
    const title = document.querySelectorAll(".textContainer h2.line1 > span")[i].textContent;
    const id1 = workId.slice(0, 2);
    const id2 = workId.slice(2, 4);
    const id3 = workId.slice(4, 5);
    const imgId =
      "https://cs1.animestore.docomo.ne.jp/anime_kv/img/" +
      id1 +
      "/" +
      id2 +
      "/" +
      id3 +
      "/" +
      workId +
      "_1_3.png";

    const header = `
			<header class="">
				<a href="${hrefLink}">
					<p class="line2"><span class="ui-clamp webkit2LineClamp">${title}</span></p>
					<div class="titleThumbnail">
						<div class="titleThumbnailIn">
							<i class="icon "></i>
							<div class="imgWrap16x9">
								<img class=" verticallyLong lazyloaded" src="${imgId}" alt="パッケージ画像" width="640" height="360">
							</div>
						</div>
					</div>
				</a>
			</header>`;

    document.querySelectorAll(".itemModule > section")[i].insertAdjacentHTML("afterbegin", header);
  }

  if (resolutionbool) {
    qualityAndYear(false);
  }

  thumbnailclick();
} else if (path == "tp_pc") {
  // トップページ
  // 「現在放送中のアニメ」リンク先変更
  let eventStop = false;
  let addOpen = false;
  const observer = new MutationObserver(() => {
    // リンク先変更
    const itemLists = document.querySelectorAll(".itemWrapper > .p-slider__item > a.c-slide");
    if (itemLists.length == 0) {
      return;
    }
    for (const itemList of itemLists) {
      const workId = new URL(itemList.getAttribute("href")).searchParams.get("workId");
      const url = "https://animestore.docomo.ne.jp/animestore/ci_pc?workId=" + workId;
      itemList.setAttribute("href", `${url}`);
    }

    // 新規ウィンドウで開くeventを無効化
    const playerImg = document.querySelectorAll(".thumbnailContainer > a > .imgWrap16x9")[0];
    if (!eventStop && playerImg) {
      playerImg.addEventListener("click", (e) => {
        e.stopPropagation();
      });
      document
        .querySelector(".thumbnailContainer > a > .progress")
        .addEventListener("click", (e) => {
          e.stopPropagation();
        });
      eventStop = true;
      // アイコンのeventが削除できないので、imgWrap16x9の子に移動する
      const iconPlay = document.querySelector(".thumbnailContainer > a > i");
      iconPlay ? playerImg.appendChild(iconPlay) : "";

      // サムネイルをクリックすると新規タブで開く
      const openUrl =
        "https://animestore.docomo.ne.jp/animestore/sc_d_pc?partId=" +
        document.querySelector(".thumbnailContainer > a").getAttribute("data-partid");
      if (!addOpen) {
        document.querySelector(".pageHeader").addEventListener("click", () => {
          open(openUrl);
        });
        // 画像をホバーしても、上のでは動かないので
        playerImg.addEventListener("click", () => {
          open(openUrl);
        });
        addOpen = true;
      }
    }

    // タイトルwidth、ホバー
    if (!document.getElementById("mouseover-style")) {
      document.head.insertAdjacentHTML("beforeend", '<style id="mouseover-style"></style>');
      document.head.lastElementChild.textContent = `
				@media screen and (min-width: 960px) {
					.pageHeader .subTitle {
						margin: 5px 0 0 5px !important;
						width: 100% !important;
					}
					.pageHeader .information,
					.pageHeader .thumbnailContainer {
						float: none !important;
					}
					.pageHeader .pageHeaderIn {
						display: flex !important;
						width: fit-content !important;
					}
					.pageHeader .information {
						max-width: 693px;
						width: fit-content !important;
						margin-left: 15px;
					}
				}
				.pageHeader .btnResume {
					display: none;
				}
				.pageHeader .thumbnailContainer {
					order: 0;
					margin-left: 0 !important;
				}
				.pageHeader .information {
					order: 1;
				}
				.pageHeader .title {
					width: 100% !important;
				}
				.pageHeader .subTitle > p {
					width: 100%;
					transform: none !important;
					transition: none !important;
					overflow: hidden;
					text-overflow: ellipsis;
				}
				.pageHeader:hover {
					cursor: pointer;
				}
				.pageHeader:hover :is(.title, .subTitle > p) {
					text-decoration: underline;
				}
				.pageHeader:hover .imgWrap16x9 {
					opacity : .6;
				}`;
    }

    if (resolutionbool) {
      addResolutionStyle();
      const playerSlider = document.querySelectorAll(
        '.p-slider__item:not(.add-resolution,.isBlack,[data-link^="/animestore/series?seriesId="]) > div > input[data-workid]'
      );
      if (playerSlider.length == 0) {
        return;
      }
      const insertTarget = document.querySelectorAll(
        `.p-slider__item:not(.add-resolution,.isBlack,[data-link^="/animestore/series?seriesId="]) > a.c-slide > .isAnime:not(.isOnAir)`
      );
      const addClassTarget = document.querySelectorAll(
        '.p-slider__item:not(.add-resolution,.isBlack,[data-link^="/animestore/series?seriesId="]):has(> div > input[data-workid])'
      );

      let workIds = [];
      for (let i = 0; i < playerSlider.length; i++) {
        const workId = playerSlider[i].getAttribute("data-workid");
        addClassTarget[i].classList.add("add-resolution");
        workIds.push(workId);
      }

      const fetchAsync = async () => {
        const url =
          "https://animestore.docomo.ne.jp/animestore/rest/v1/works?work_id=" + workIds.join(",");
        const response = await fetch(url);
        const json = await response.json();
        const [sorted, yearSorted] = sort(workIds, json);

        for (let i = 0; i < sorted.length; i++) {
          const quality = sorted[i];
          function div(quality) {
            insertTarget[i].insertAdjacentHTML(
              "afterend",
              `<div class="quality"><span>${quality}</span></div>`
            );
          }
          resolutionBranch(div, quality);
        }
      };
      fetchAsync();
    }
  });
  const config = { childList: true, subtree: true };
  observer.observe(document.body, config);
  setTimeout(function () {
    observer.disconnect();
  }, 2000);
} else if (path == "ci_pc") {
  // 作品ページ
  const playerImges = document.querySelectorAll("section.clearfix > a");

  for (const playerImg of playerImges) {
    const openUrl =
      "https://animestore.docomo.ne.jp/animestore/sc_d_pc" +
      playerImg.getAttribute("href").slice(5);
    // 詳しく見るを開かずに、タブで動画を開く
    playerImg.addEventListener("click", () => {
      open(openUrl);
    });
    playerImg.style.cursor = "pointer";
    playerImg.removeAttribute("href");
  }
} else if (path == "sc_d_pc") {
  // 再生画面
  const video = document.querySelector("video");

  const observerTarget = document.querySelector(".backInfoTxt1");
  const observer = new MutationObserver(() => {
    // 再生ウィンドウ名をアニメ名に変更
    const animeTitle = observerTarget.textContent;
    document.title = animeTitle;

    // シークバーにタイトルと話数を表示
    const episode = document.querySelector(".backInfoTxt2").textContent;

    // 保存した音量に変更
    const getVolume = GM_getValue("volume");
    video.volume = getVolume ? getVolume : "";

    let addElement;
    if (document.getElementById("volumeText") == undefined) {
      addElement = `
			<div id="volumeText" style="display: table; position: relative; width: 25px; right: 5px;">
			    <span style="display: table-cell; color: #a0a09f; font-weight: 600; vertical-align: middle;">
                    ${Math.round(video.volume * 100)}
                </span>
			</div>
        `;
    }

    if (titlebool && document.getElementById("title") == undefined) {
      addElement += `
            <div id="title" style="display: table; margin-left: 20px;">
                <span style="display: table-cell; color: #a0a09f; font-weight: 700; vertical-align: middle;">
                    ${animeTitle} ${episode}
                </span>    
            </div>
        `;
    } else if (titlebool) {
      document.querySelector("#title>span").textContent = `${animeTitle} ${episode}`;
    }

    addElement &&
      document.querySelector(".buttonArea > .volume").insertAdjacentHTML("afterend", addElement);
  });
  const config = { childList: true };
  observer.observe(observerTarget, config);
  observer.disconnect;

  // マウスホイールで音量変更
  window.addEventListener("wheel", (e) => {
    const wheelDelta = Math.sign(e.deltaY);
    let volume = video.volume;
    if (wheelDelta === 1) {
      volume -= 0.02;
    } else if (wheelDelta === -1) {
      volume += 0.02;
    }
    volume = Math.max(0, Math.min(volume, 1));
    video.volume = volume;

    GM_setValue("volume", volume);
    const span = `
            <span style="display: table-cell; color: #a0a09f; font-weight: 600; vertical-align: middle;">
                ${Math.round(video.volume * 100)}
            </span>
        `;

    document.querySelector("#volumeText").innerHTML = span;
  });

  // 音量バーを操作した場合
  let isDragging = false;
  document.querySelector("#volumePopupIn").addEventListener("mousedown", (e) => {
    isDragging = true;
  });
  document.addEventListener("mouseup", () => {
    isDragging = false;
  });
  document.addEventListener("mousemove", () => {
    if (isDragging) {
      GM_setValue("volume", video.volume);
      document.querySelector("#volumeText > span").textContent = Math.round(video.volume * 100);
    }
  });

  // 上下キーで音量変更した場合
  document.addEventListener("keyup", (e) => {
    if (e.key == "ArrowDown" || e.key == "ArrowUp") {
      document.querySelector("#volumeText > span").textContent = Math.round(video.volume * 100);
    }
  });

  // マウスホイールで動画の再生位置を移動
  window.addEventListener("wheel", (e) => {
    const video = document.querySelector("video");
    const wheelDelta = Math.sign(e.deltaX);
    const skipTime = 10;

    if (wheelDelta === 1) {
      video.currentTime -= skipTime;
    } else if (wheelDelta === -1) {
      video.currentTime += skipTime;
    }
  });
} else if (
  path == "sch_pc" ||
  path == "gen_pc" ||
  path == "c_all_pc" ||
  path == "series_pc" ||
  path == "tag_pc"
) {
  // 検索結果、シリーズ、ランキング

  showOrder();
  restCount();
  addHoverStyle();

  // 解像度表示
  if (resolutionbool || addProductionYear) {
    const observer2 = new MutationObserver(() => {
      qualityAndYear(true);
    });
    const config = { childList: true };
    observer2.observe(document.querySelector("#listContainer"), config);
  }
} else if (path == "mpa_cmp_pc") {
  // コンプリート
  // 解像度表示
  if (resolutionbool) {
    qualityAndYear(false);
  }
} else if (path == "CF/mylist_detail" || path == "mpa_shr_pc") {
  // リスト
  // 解像度表示
  if (resolutionbool) {
    const observer = new MutationObserver(() => {
      addResolutionStyle();
      if (document.querySelectorAll(".p-mylistItemList__item > a > .quality")[0]) {
        return;
      }
      const playerMypage = document.querySelectorAll(".p-mylistItemList__item > a");
      let workIds = [];
      for (let i = 0; i < playerMypage.length; i++) {
        const workId = new URL(playerMypage[i].getAttribute("href")).searchParams.get("workId");
        workIds.push(workId);
      }
      const fetchAsync = async () => {
        const url =
          "https://animestore.docomo.ne.jp/animestore/rest/v1/works?work_id=" + workIds.join(",");
        const response = await fetch(url);
        const json = await response.json();
        const [sorted, yearSorted] = sort(workIds, json);

        for (let i = 0; i < sorted.length; i++) {
          const quality = sorted[i];
          function div(quality) {
            const headerquality = `
							<div class="quality">
								<span>${quality}</span>
							</div>`;
            playerMypage[i].insertAdjacentHTML("beforeend", headerquality);
          }
          resolutionBranch(div, quality);
        }
      };
      fetchAsync();
    });
    const config = { childList: true, subtree: true };
    observer.observe(document.querySelector(".p-mylistItemList"), config);
    setTimeout(function () {
      observer.disconnect();
    }, 2000);
  }
}

// 詳しく見るを非表示
if (!hideDetailBool) {
  document.head.insertAdjacentHTML("beforeend", '<style id="hide-detail"></style>');
  document.head.lastElementChild.textContent = `
		.itemModule > section > .detail {
			display: none;
		}`;
}

// 解像度を表示設定
let resolutionText = resolutionbool ? "解像度を表示:ON✔️" : "解像度を表示:OFF❌";
GM_registerMenuCommand(resolutionText, () => {
  if (resolutionbool) {
    GM_setValue("menu", false);
  } else {
    GM_setValue("menu", true);
  }
});

let productionYearText = addProductionYear ? "制作年を表示:ON✔️" : "制作年を表示:OFF❌";
GM_registerMenuCommand(productionYearText, () => {
  if (addProductionYear) {
    GM_setValue("addProductionYear", false);
  } else {
    GM_setValue("addProductionYear", true);
  }
});

// シークバーにタイトルと音量を表示設定
let titleText = titlebool
  ? "シークバーにタイトルを表示:ON✔️"
  : "シークバーにタイトルを表示:OFF❌";
GM_registerMenuCommand(titleText, () => {
  if (titlebool) {
    const showTitle = false;
    GM_setValue("seekbarTitle", showTitle);
  } else {
    const showTitle = true;
    GM_setValue("seekbarTitle", showTitle);
  }
});

// 詳しく見るを非表示
let hideDetailText = hideDetailBool ? "詳しく見るを表示:ON✔️" : "詳しく見るを表示:OFF❌";
GM_registerMenuCommand(hideDetailText, () => {
  if (hideDetailBool) {
    GM_setValue("hideDetail", false);
  } else {
    GM_setValue("hideDetail", true);
  }
});