Greasy Fork

Greasy Fork is available in English.

copymanga-自动存储浏览记录

自动存储拷贝漫画的浏览记录,以防拷贝卷记录跑路。

当前为 2024-04-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         copymanga-自动存储浏览记录
// @namespace    http://tampermonkey.net/
// @description  自动存储拷贝漫画的浏览记录,以防拷贝卷记录跑路。
// @version      1.3.1
// @author       Y_jun
// @license      MIT
// @icon         https://hi77-overseas.mangafuna.xyz/static/free.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_xmlhttpRequest
// @match        *://*.copymanga.com/*
// @match        *://*.copymanga.org/*
// @match        *://*.copymanga.net/*
// @match        *://*.copymanga.info/*
// @match        *://*.copymanga.site/*
// @match        *://*.copymanga.tv/*
// @match        *://*.mangacopy.com/*
// @match        *://copymanga.com/*
// @match        *://copymanga.org/*
// @match        *://copymanga.net/*
// @match        *://copymanga.info/*
// @match        *://copymanga.site/*
// @match        *://copymanga.tv/*
// @match        *://mangacopy.com/*
// @run-at       document-start
// ==/UserScript==


let token;

function sleep(time) {
    return new Promise((resolve) => setTimeout(resolve, time));
}

function completeDate(value) {
    return value < 10 ? "0" + value : value;
}

function getNowFormatTime() {
    let nowDate = new Date();
    let colon = ":";
    let char = "-";
    let day = nowDate.getDate();
    let month = nowDate.getMonth() + 1;//注意月份需要+1
    let year = nowDate.getFullYear();
    let h = nowDate.getHours();
    let m = nowDate.getMinutes();
    let s = nowDate.getSeconds();
    //补全0,并拼接
    return year + char + completeDate(month) + char + completeDate(day) + " " + completeDate(h) + colon + completeDate(m) + colon + completeDate(s);
}

function addNotice() {
    let button = document.createElement('button');
    button.id = 'save-button';
    button.style.marginLeft = '20px';
    button.textContent = '开始保存完整记录';
    button.onclick = () => {
        button.className = 'allow-save-liulan';
    }

    let notice = document.createElement('span');
    notice.id = 'save-liulan';
    notice.style.marginLeft = '20px';
    notice.textContent = '尚未保存浏览记录';
    let collectActionArea = document.querySelector('.collectAction');

    collectActionArea.appendChild(button)
    collectActionArea.appendChild(notice);
}

function editNotice(text) {
    let notice = document.getElementById('save-liulan');
    notice.textContent = text;
}

function getPopularNum(popularStr) {
    if (popularStr.indexOf('W') > -1) {
        return Number(popularStr.substring(0, popularStr.length - 1)) * 10000;
    }
    if (popularStr.indexOf('K') > -1) {
        return Number(popularStr.substring(0, popularStr.length - 1)) * 1000;
    }
    return Number(popularStr);
}

function deleteAllValues() {
    const keys = GM_listValues();
    keys.forEach(key => {
        GM_deleteValue(key);
    })
}

async function getLiulanList() {
    while (!document.querySelector('.allow-save-liulan')) {
        await sleep(2000);
    }
    editNotice('正在删除旧记录……');
    deleteAllValues();
    let offset = 0;
    let limit = 25;
    let lastIndex = 1;
    const datetime = getNowFormatTime();
    let totalStr = document.querySelector('.demonstration').innerText;
    let total = Number(totalStr.substring(3, totalStr.length - 2));
    while (offset < total) {
        editNotice('保存浏览记录中,进度:' + Math.round(offset / total * 10000) / 100 + "%");
        GM_xmlhttpRequest({
            method: "get",
            url: `${window.location.origin}/api/kb/web/browses?limit=${limit}&offset=${offset}&free_type=1`,
            data: "",
            headers: {
                "Content-Type": "application/json",
                "Authorization": token,
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.160 Safari/537.36"
            },
            onload: res => {
                if (res.status === 200) {
                    const response = JSON.parse(res.response);
                    if (response.code === 200) {
                        const results = response.results;
                        const mangaList = results.list;
                        mangaList.forEach((manga) => {
                            const authors = [];
                            if (Array.isArray(manga.comic.author)) {
                                const authorList = manga.comic.author;
                                authorList.forEach((author) => {
                                    authors.push(author.name);
                                })
                            }
                            const mangaObj = {
                                "name": manga.comic.name,
                                "uuid": manga.comic.uuid,
                                "path": manga.comic.path_word,
                                "lastRead": manga.last_chapter_name,
                                "lastUuid": manga.last_chapter_id,
                                "lastIndex": lastIndex,
                                "lastTime": datetime,
                                "latestChapter": manga.comic.last_chapter_name,
                                "latestTime": manga.comic.datetime_updated,
                                "popular": manga.comic.popular,
                                "authors": authors
                            }
                            lastIndex++;
                            GM_setValue(manga.comic.path_word, mangaObj);
                        });
                    } else {
                        editNotice('保存浏览记录出错');
                        console.log('code不为200:\n' + res);
                        total = -1;
                    }
                } else {
                    editNotice('保存浏览记录出错');
                    console.log('status不为200:\n' + res);
                    total = -1;
                }
            },
            onerror: () => {
                editNotice('保存浏览记录出错');
                console.log('读取浏览记录失败');
                total = -1;
            }
        });
        offset += limit;
        await sleep(2000);
    }
    editNotice('保存完毕');
}
function getLastRead(path, count = 1) {
    if (document.querySelector('.table-default') === null) {
        if (count <= 50) {
            const args = Array.from(arguments).slice(0, arguments.length);
            args.push(count + 1);
            setTimeout(getLastRead, 200, ...args);
        }
        return;
    }
    const name = document.querySelector('h6').textContent;
    const updateArr = document.querySelector('.table-default-right').textContent.split('更新');
    const latestChapter = updateArr[1].substring(3);
    const updateTime = updateArr[2].substring(3);
    const authors = document.querySelectorAll('.comicParticulars-right-txt')[1].innerHTML.match(/>[^<]+<\/a>/g);
    for (let i = 0; i < authors.length; i++) {
        const author = authors[i];
        authors[i] = author.substring(1, author.length - 4);
    }
    const popularStr = document.querySelectorAll('.comicParticulars-right-txt')[2].innerText;
    const popular = getPopularNum(popularStr);
    GM_xmlhttpRequest({
        method: "get",
        url: `${window.location.origin}/api/v3/comic2/${path}/query?platform=1&_update=true`,
        data: "",
        headers: {
            "Content-Type": "application/json",
            "Authorization": token,
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.160 Safari/537.36"
        },
        onload: res => {
            if (res.status === 200) {
                const response = JSON.parse(res.response);
                if (response.code === 200) {
                    const results = response.results;
                    if (results.browse) {
                        const mangaObj = {
                            "name": name,
                            "uuid": results.browse.comic_uuid,
                            "path": path,
                            "lastRead": results.browse.chapter_name,
                            "lastUuid": results.browse.chapter_uuid,
                            "lastIndex": 0,
                            "lastTime": getNowFormatTime(),
                            "latestChapter": latestChapter,
                            "latestTime": updateTime,
                            "popular": popular,
                            "authors": authors
                        }
                        GM_setValue(path, mangaObj);
                    }
                } else {
                    console.log('code不为200:\n' + res);
                }
            } else {
                console.log('status不为200:\n' + res);
            }
        },
        onerror: () => {
            console.log('读取最近阅读失败');
        }
    });
}

function setCurrentRead(path, lastUuid, count = 1) {
    let storageManga = GM_getValue(path);
    if (!storageManga) return;
    if (document.querySelector('h4.header') === null) {
        if (count <= 50) {
            const args = Array.from(arguments).slice(0, arguments.length);
            args.push(count + 1);
            setTimeout(getLastRead, 200, ...args);
        }
        return;
    }
    let StrArr = document.querySelector('h4.header').innerText.split('/');
    storageManga.lastRead = StrArr[1];
    storageManga.lastUuid = lastUuid;
    storageManga.lastTime = getNowFormatTime();
    GM_setValue(path, storageManga);
}

window.onload = () => {
    token = 'Token ' + document.cookie.split('; ').find((cookie) => cookie.startsWith('token='))?.replace('token=', '');
    const pathArr = window.location.pathname.split('/');
    if (window.location.pathname === '/web/person/liulan') {
        addNotice();
        getLiulanList();
    } else if (pathArr.length === 3 && pathArr[1] === 'comic') {
        document.addEventListener('visibilitychange', () => {
            getLastRead(pathArr[2].replace('#', ''));
        });
    } else if (pathArr.length === 5 && pathArr[3] === 'chapter') {
        setCurrentRead(pathArr[2], pathArr[4]);
    }
}

setInterval(() => {
    const pathArr = window.location.pathname.split('/');
    if (pathArr.length < 4 || pathArr[2] !== 'person') {
        return;
    }
    const barClass = document.querySelector('.el-menu').querySelectorAll('li')[1].className;
    if (barClass.indexOf('is-active') < 0) {
        return;
    }
    const main = document.querySelector('.man_');
    Array.from(main.children).forEach((child, index) => {
        if (child.className.indexOf('is-injected') < 0) {
            child.style.position = 'relative';
            const path = child.firstChild.href.split('/')[4];
            const storageJson = GM_getValue(path, null);
            let lastRead;
            let lastUuid;
            if (storageJson) {
                lastRead = storageJson.lastRead;
                lastUuid = storageJson.lastUuid;
            }
            const lastP = child.querySelector(`[id='${path}']`);
            if (lastP) child.removeChild(lastP);
            const p = document.createElement('p');
            p.id = path;
            p.innerHTML = lastUuid ? `<a href="/comic/${path}/chapter/${lastUuid}" target='_blank'>上次阅读:  ${lastRead}</a>` : '还没看过';
            p.style.width = '100%';
            p.style.position = 'absolute';
            p.style.bottom = '10px';
            child.appendChild(p);
            child.classList.add('is-injected');
        }
    })
}, 1000);