AutoPlayNextEpisode

自動撥放下一集&紀錄觀看的動畫集數

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AutoPlayNextEpisode
// @version      1.0.2
// @description  自動撥放下一集&紀錄觀看的動畫集數
// @author       Jay.Huang
// @match        https://v.myself-bbs.com/player/*
// @match        https://myself-bbs.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=myself-bbs.com
// @grant        none
// @license      MIT
// @namespace https://github.com/2jo4u4/MySelfRecorder.git
// ==/UserScript==
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
const storeKeyWord = "recorder";
const storeFavorite = "favorite";
const favoriteIconHref = "https://cdn-icons-png.flaticon.com/512/2107/2107845.png";
const favoriteAddIconHref = "https://cdn-icons-png.flaticon.com/512/2001/2001314.png";
const favoriteRemoveIconHref = "https://cdn-icons-png.flaticon.com/512/2001/2001316.png";
const notFoundCoverImg = "https://cdn-icons-png.flaticon.com/512/7214/7214281.png";
const clearLogImg = "https://cdn-icons-png.flaticon.com/512/3602/3602056.png";
const PIPmode = true

var FavoriteBtnStatus;
(function (FavoriteBtnStatus) {
    FavoriteBtnStatus[FavoriteBtnStatus["\u672A\u52A0\u5165\u6700\u611B"] = 0] = "\u672A\u52A0\u5165\u6700\u611B";
    FavoriteBtnStatus[FavoriteBtnStatus["\u5DF2\u52A0\u5165\u6700\u611B"] = 1] = "\u5DF2\u52A0\u5165\u6700\u611B";
})(FavoriteBtnStatus || (FavoriteBtnStatus = {}));
function dalay() {
    return __awaiter(this, arguments, void 0, function* (timeout = 500) {
        return new Promise(resolve => {
            setTimeout(resolve, timeout);
        });
    });
}
class VideoPlayManager {
    constructor() {
        // https://v.myself-bbs.com/player/AgADjw0AAmr7uVQ?totalEpisode=11&0=AgADjw0AAmr7uVQ&1=AgADuQ8AAhENCFU
        this.url = new URL(window.location.href);
        this.totalEpisode = Number(this.url.searchParams.get("totalEpisode") ?? "NaN");
        this.currEpisode = Number(this.url.searchParams.get("currEpisode") ?? "NaN");
        if(!isNaN(this.totalEpisode)) {
            this.from = this.url.searchParams.get('from') ?? null
            this.addCrtlBtn();
            document.body.onload = () => {
                this.getVideoPlay().then(videoEl => {
                    if (videoEl) {
                        console.log(videoEl, { PIPmode, requestPictureInPicture: Boolean(videoEl.requestPictureInPicture) })
                        if(PIPmode && videoEl.requestPictureInPicture) {
                            videoEl.addEventListener("play", ()=>{
                                videoEl.requestPictureInPicture().then(()=>{
                                    console.log("auto open pip mode");
                                })
                            })
                        }
                        videoEl.addEventListener("ended", () => {
                            this.changeEpisode(true);
                        });
                    }
                });
            }
        }
    }
    changeEpisode(next = true){
        const targetNumber = next ? this.currEpisode + 1 : this.currEpisode - 1
        const videoUrl = this.url.searchParams.get(targetNumber.toString()) ?? null
        if(videoUrl !== null) {
            window.location.href = this.getNextURL(videoUrl, targetNumber);
        } else {
            if(next) {
                alert("沒有下一集了,將返回列表。");
                 if(this.from) {
                     window.location.href = this.from
                 }
            }
            else {
                alert("找不到上一集。");
            }
        }
    }
    addCrtlBtn(){
        const prevBtn = document.createElement("div");
        prevBtn.onclick = () => {
            this.changeEpisode(false);
        }

        const nextBtn = document.createElement("div");
        nextBtn.onclick = () => {
            this.changeEpisode(true);
        }

        function addStyle(btn, type = "left"){
            btn.style.position = "fixed";
            btn.style.zIndex = "998";
            btn.style.top = "50%";
            btn.style[type] = "0px";
            btn.style.width = "24px";
            btn.style.height = "64px";
            btn.style.borderRadius = "12px";
            btn.style.border = "1px black solid";
            btn.style.backgroundColor = "white";
            btn.style.opacity = "0.3";
            btn.style.transition = "opacity 0.3s"
            btn.onmouseenter = () => {
                btn.style.opacity = "1";
            }
            btn.onmouseleave = () => {
                btn.style.opacity = "0.3";
            }
        }

        addStyle(prevBtn, "left");
        addStyle(nextBtn, "right");
        document.body.append(prevBtn, nextBtn)
    }
    getVideoPlay() {
        return __awaiter(this, arguments, void 0, function* (times = 5) {
            let video;
            let retry = 0;
            while (!Boolean(video) && retry <= times) {
                video = document.querySelector("video");
                yield dalay(1000);
                retry += 1;
            }
            return video;
        });
    }
    getNextURL(videoUrl, episodeNumber) {
        const nextUrl = new URL(videoUrl)
        nextUrl.search = this.url.search
        nextUrl.searchParams.set("currEpisode", episodeNumber)
        return nextUrl.toString();
    }
}
class AnimeManager {
    constructor() {
        this.favoriteList = Tools.getFavorite();
        this.createPositionEl();
        this.renderFavoriteListUI();
        // thread-47934-1-1.html
        if (/^\/thread/.test(window.location.pathname)) {
            this.page = "episode";
            this.animeCode = window.location.pathname.split("-")[1];
            this.recordList = Tools.getRecorder();
            this.episodeUrls = [];
            this.getElement().then(main => {
                if (main) {
                    this.mainEl = main;
                    this.animeName = this.getAnimeName();
                    this.anchorEls = Array.from(main.getElementsByClassName("various"));
                    this.anchorEls.forEach((tagA, index) => {
                        const url = this.getEpisodeUrlByElement(tagA);
                        this.enhanceAnchorEl(tagA, index);
                        if (url) {
                            this.episodeUrls.push(url);
                            this.addAutoBtnEachEpisode(tagA, url);
                        }
                    });
                    this.rerenderAnchorElHighight("render");
                    this.renderEpisodeUI();
                    this.renderCtrlFavoriteUI();
                }
                else {
                   console.warn("找不到動畫集數資訊");
                }
            });
        }
        else {
            this.page = "overview";
        }
        document.body.append(this.positionEl);
    }
    get currAnimeRecord() {
        return this.recordList[this.animeCode];
    }
    renderEpisodeUI() {
        const animeCode = this.animeCode;
        const container = document.querySelector(".fr.vodlist_index").children[0];
        container.style.position = "relative";
        // 添加清除按鈕
        container.appendChild(UIComponent.cleanWatchLogBtn(() => {
            const recorder = Tools.getRecorder();
            Tools.setRecorder(Object.assign(Object.assign({}, recorder), { [animeCode]: [] }));
            this.rerenderAnchorElHighight("clearAll");
        }));
    }
    // 添加觀看紀錄的高亮提示
    rerenderAnchorElHighight(type) {
        if (type === "clearAll") {
            this.anchorEls.forEach(el => {
                el.parentElement.parentElement.parentElement.style.backgroundColor = "unset";
            });
        }
        else if (type === "render" && this.currAnimeRecord && this.currAnimeRecord.length > 1) {
            const [recently, ...log] = this.currAnimeRecord;
            this.anchorEls[recently].parentElement.parentElement.parentElement.style.backgroundColor = "#ff000080";
            log.forEach(episodeIndex => {
                this.anchorEls[episodeIndex].parentElement.parentElement.parentElement.style.backgroundColor = "#ff000030";
            });
        }
    }
    getEpisodeUrlByElement(el) {
        return el.dataset.href || null;
    }
    addAutoBtnEachEpisode(el, url) {
        const span = document.createElement("span");
        span.innerText = "自動接續下集";
        span.style.cursor = "pointer";
        span.style.marginLeft = "4px";
        span.onclick = () => {
            this.gotoPlayPage(url);
        };
        el.parentElement.appendChild(span);
    }
    gotoPlayPage(targetUrl) {
        const totalEpisode = `totalEpisode=${this.episodeUrls.length}`;
        let keyValue = ""
        let currEpisode = 0
        this.episodeUrls.forEach((url, index) => {
            const episode = index + 1;
            if(targetUrl === url) {
                currEpisode = episode
            }
            keyValue += `&${episode}=${url}`;
        });
        const from = window.location.href;
        const url = `${targetUrl}?currEpisode=${currEpisode}&${totalEpisode}${keyValue}&from=${from}`;
        window.location.href = url;
    }
    getElement() {
        return __awaiter(this, arguments, void 0, function* (times = 5) {
            let main;
            let retry = 0;
            while (main === undefined && retry <= times) {
                main = document.getElementsByClassName("main_list")[0];
                yield dalay();
                retry += 1;
            }
            return main;
        });
    }
    enhanceAnchorEl(el, index) {
        const animeCode = this.animeCode;
        el.onclick = () => {
            const recorder = Tools.getRecorder();
            const newNumberList = Array.from(new Set(recorder[animeCode] ? [index, ...recorder[animeCode]] : [index]));
            Tools.setRecorder(Object.assign(Object.assign({}, recorder), { [animeCode]: newNumberList }));
            this.recordList = Tools.getRecorder();
            this.rerenderAnchorElHighight("render");
        };
    }
    getAnimeName() {
        var _a;
        const block = (((_a = document.querySelector("#pt .z")) === null || _a === void 0 ? void 0 : _a.lastElementChild) || null);
        if (block) {
            return block.innerText.replace(/【(\S|\s|0-9)*/, "");
        }
        else {
            return "";
        }
    }
    /** 建立主畫面定位按鈕元素 */
    createPositionEl() {
        // 定位主畫面的按鈕
        this.positionEl = document.createElement("div");
        this.positionEl.id = "positionEl";
        this.positionEl.style.position = "fixed";
        this.positionEl.style.display = "flex";
        this.positionEl.style.flexDirection = "row";
        this.positionEl.style.top = "20px";
        this.positionEl.style.left = "20px";
    }
    /** 我的最愛列表 */
    renderFavoriteListUI() {
        let favoritListFlag = false;
        // 父元素
        const container = document.createElement("div");
        container.id = "container";
        container.style.display = "flex";
        container.style.flexDirection = "row";
        container.style.marginRight = "4px";
        // 用於打開最愛列表的按鈕
        const favorite_btn = document.createElement("img");
        this.ctrlBtnStyle(favorite_btn);
        favorite_btn.src = favoriteIconHref;
        favorite_btn.title = "打開/關閉最愛列表";
        // 被打開的列表
        const favorite_list = document.createElement("div");
        favorite_list.style.maxHeight = "50vh";
        favorite_list.style.display = favoritListFlag ? "flex" : "none";
        favorite_list.style.flexDirection = "column";
        favorite_list.style.marginTop = "12px";
        favorite_list.style.padding = "12px";
        favorite_list.style.backdropFilter = "blur(20px)";
        favorite_list.style.borderRadius = "12px";
        favorite_list.style.overflow = "auto";
        favorite_list.style.position = "absolute";
        favorite_list.style.width = "max-content";
        favorite_list.style.top = "48px";
        // 用於顯示開動畫的 Cover 圖定位
        const coverImage = document.createElement("div");
        coverImage.style.marginTop = "12px";
        coverImage.style.position = "relative";
        const span = document.createElement("span");
        span.innerText = "暫無最愛";
        span.style.color = "darkorange";
        favorite_btn.onclick = () => {
            favoritListFlag = !favoritListFlag;
            favorite_list.style.display = favoritListFlag ? "flex" : "none";
            span.style.display = this.favoriteList.length === 0 ? "block" : "none";
        };
        this.favoriteList.forEach((item, index) => {
            const card = this.favoriteAnimeItem(coverImage, item, index !== 0 ? 12 : 0);
            favorite_list.append(card);
        });
        favorite_list.append(span);
        container.append(favorite_btn, coverImage, favorite_list);
        this.positionEl.append(container);
    }
    favoriteAnimeItem(coverImagePosition, v, marginTop = 0) {
        const { animecode, image, name, href } = v;
        const card = document.createElement("div");
        card.id = animecode;
        card.style.marginTop = `${marginTop}px`;
        card.style.maxWidth = "300px";
        const coverImage = document.createElement("img");
        coverImage.src = image;
        coverImage.style.position = "absolute";
        coverImage.style.top = "0";
        coverImage.style.left = "310px";
        coverImage.style.border = "6px solid white";
        coverImage.style.borderRadius = "12px";
        coverImage.style.boxShadow = "4px 6px 8px 6px #60606073";
        const link = document.createElement("a");
        link.href = href;
        link.style.color = "#fff";
        link.style.textShadow = "#000 0.1em 0.1em 0.2em";
        link.innerText = name;
        link.style.display = "flex";
        link.style.flexDirection = "column";
        link.onmouseenter = () => {
            coverImagePosition.append(coverImage);
        };
        link.onmouseleave = () => {
            coverImage.remove();
        };
        card.append(link);
        return card;
    }
    /** 加入最愛 / 移除最愛 */
    renderCtrlFavoriteUI() {
        let index = this.favoriteList.findIndex(({ animecode }) => animecode === this.animeCode);
        let isFavorite = index !== -1;
        const coverPicture = document.querySelector(".info_con .info_img_box img");
        const info = {
            name: this.animeName,
            image: (coverPicture === null || coverPicture === void 0 ? void 0 : coverPicture.src) || notFoundCoverImg,
            href: window.location.pathname,
            animecode: this.animeCode,
        };
        const ctrl_btn = document.createElement("img");
        this.ctrlBtnStyle(ctrl_btn);
        if (isFavorite) {
            ctrl_btn.src = favoriteRemoveIconHref;
            ctrl_btn.title = "移除最愛";
        }
        else {
            ctrl_btn.src = favoriteAddIconHref;
            ctrl_btn.title = "加入最愛";
        }
        ctrl_btn.addEventListener("click", () => {
            if (isFavorite) {
                // remove
                this.favoriteList.splice(index, 1);
                Tools.setFavorite(this.favoriteList);
                ctrl_btn.title = "加入最愛";
                ctrl_btn.src = favoriteAddIconHref;
            }
            else {
                // add
                this.favoriteList = [...this.favoriteList, info];
                Tools.setFavorite(this.favoriteList);
                ctrl_btn.title = "移除最愛";
                ctrl_btn.src = favoriteRemoveIconHref;
            }
            this.rerenderFavoriteListUI();
            isFavorite = !isFavorite;
        });
        this.positionEl.append(ctrl_btn);
    }
    rerenderFavoriteListUI() {
        const listContainer = this.positionEl.children[0];
        const ctrlBtn = this.positionEl.children[1];
        listContainer.remove();
        ctrlBtn.remove();
        this.renderFavoriteListUI();
        this.positionEl.append(ctrlBtn);
    }
    /** 主畫面顯示的按鈕樣式 */
    ctrlBtnStyle(el) {
        el.style.width = "24px";
        el.style.height = "24px";
        el.style.padding = "12px";
        el.style.borderRadius = "12px";
        el.style.backdropFilter = "blur(20px)";
        el.style.cursor = "pointer";
        el.style.border = "3px solid rgb(130, 130, 130)";
    }
}
class Tools {
    static setFavorite(data) {
        window.localStorage.setItem(storeFavorite, JSON.stringify(data));
    }
    static getFavorite() {
        const str = window.localStorage.getItem(storeFavorite) || "[]";
        return JSON.parse(str);
    }
    static setRecorder(data) {
        window.localStorage.setItem(storeKeyWord, JSON.stringify(data));
    }
    static getRecorder() {
        const str = window.localStorage.getItem(storeKeyWord) || "{}";
        return JSON.parse(str);
    }
}
class NoticeUIChange {
}
class UIComponent {
    static cleanWatchLogBtn(clickCB) {
        const clearBtn = document.createElement("img");
        clearBtn.src = clearLogImg;
        clearBtn.style.position = "absolute";
        clearBtn.style.top = "6px";
        clearBtn.style.right = "6px";
        clearBtn.style.cursor = "pointer";
        clearBtn.style.width = "24px";
        clearBtn.style.height = "24px";
        clearBtn.alt = "清除觀看紀錄";
        clearBtn.title = "清除觀看紀錄";
        clearBtn.onclick = clickCB;
        // clearBtn.onclick = function () {
        //   const recorder = Tools.getRecorder();
        //   Tools.setRecorder({ ...recorder, [animeCode]: [] });
        //   mark([]);
        // };
        return clearBtn;
    }
    static ctrlFavoriteBtn(initType) {
        const btn = document.createElement("img");
        let isAlreadyAdd = initType === FavoriteBtnStatus.已加入最愛;
        const changeIcon_Remove = () => {
            btn.src = favoriteRemoveIconHref;
            btn.title = "移除最愛";
        };
        const changeIcon_Add = () => {
            btn.src = favoriteAddIconHref;
            btn.title = "加入最愛";
        };
        btn.style.marginRight = "4px";
        btn.style.width = "24px";
        btn.style.height = "24px";
        btn.style.padding = "12px";
        btn.style.borderRadius = "12px";
        btn.style.backdropFilter = "blur(20px)";
        btn.style.cursor = "pointer";
        btn.style.border = "3px solid rgb(130, 130, 130)";
        if (isAlreadyAdd) {
            changeIcon_Remove();
        }
        else {
            changeIcon_Add();
        }
        btn.addEventListener("click", () => {
            isAlreadyAdd = !isAlreadyAdd;
            if (isAlreadyAdd) {
                changeIcon_Remove();
            }
            else {
                changeIcon_Add();
            }
        });
    }
}
(function () {
    "use strict";
    const isVideoPlay = window.location.origin === "https://v.myself-bbs.com";
    if (isVideoPlay) {
        new VideoPlayManager();
    }
    else {
        new AnimeManager();
    }
})();