AutoPlayNextEpisode

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

// ==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();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址