// ==UserScript==
// @name         ☄️拷贝漫画增强☄️
// @namespace    http://tampermonkey.net/
// @version      12.3
// @description  拷贝漫画去广告🚫、加速访问🚀、并排布局📖、图片高度自适应↕️、辅助翻页↔️、页码显示⏱、侧边目录栏📑、暗夜模式🌙、章节评论💬
// @author       Byaidu
// @match        *://*.copymanga.com/*
// @match        *://*.copymanga.org/*
// @match        *://*.copymanga.net/*
// @match        *://*.copymanga.info/*
// @match        *://*.copymanga.site/*
// @match        *://*.copymanga.tv/*
// @match        *://*.mangacopy.com/*
// @match        *://*.copy20.com/*
// @match        *://*.2025copy.com/*
// @match        *://copymanga.com/*
// @match        *://copymanga.org/*
// @match        *://copymanga.net/*
// @match        *://copymanga.info/*
// @match        *://copymanga.site/*
// @match        *://copymanga.tv/*
// @match        *://mangacopy.com/*
// @match        *://copy20.com/*
// @match        *://2025copy.com/*
// @license      GNU General Public License v3.0 or later
// @resource     element_css https://unpkg.com/element-ui@2.15.13/lib/theme-chalk/index.css
// @resource     animate_css https://unpkg.com/animate.css@4.1.1/animate.min.css
// @require      https://unpkg.com/vue@2.7.14/dist/vue.min.js
// @require      https://unpkg.com/element-ui@2.15.13/lib/index.js
// @require      https://unpkg.com/axios@1.3.6/dist/axios.min.js
// @require      https://unpkg.com/store.js@1.0.4/store.js
// @require      https://unpkg.com/jquery@3.6.4/dist/jquery.min.js
// @require      https://unpkg.com/jszip@3.1.5/dist/jszip.min.js
// @require      https://unpkg.com/file-saver@2.0.5/dist/FileSaver.min.js
// @require      https://unpkg.com/crypto-js@4.1.1/crypto-js.js
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// @downloadURL https://update.greasyfork.icu/scripts/421371/%E2%98%84%EF%B8%8F%E6%8B%B7%E8%B4%9D%E6%BC%AB%E7%94%BB%E5%A2%9E%E5%BC%BA%E2%98%84%EF%B8%8F.user.js
// @updateURL https://update.greasyfork.icu/scripts/421371/%E2%98%84%EF%B8%8F%E6%8B%B7%E8%B4%9D%E6%BC%AB%E7%94%BB%E5%A2%9E%E5%BC%BA%E2%98%84%EF%B8%8F.meta.js
// ==/UserScript==
var largeMode = 1;
var enableDownload = 0;
var getPictureMode = 1;
// retry
axios.interceptors.response.use(undefined, (err) => {
    return new Promise((resolve) => { setTimeout(() => { resolve() }, 1000) }).then(() => axios(err.config));
});
function route() {
    if (document.getElementsByClassName('ban').length) banPage();
    else if (/^\/comic\/.*\/.*$/.test(location.pathname)) comicPage(1);
    else if (/^\/comic\/[^\/]*$/.test(location.pathname)) tablePage(1);
    else if (/^\/$/.test(location.pathname)) homePage();
    else if (/^\/h5\/details\/comic\/[^\/]*$/.test(location.pathname)) tablePage(0);
    else if (/^\/h5\/comicContent\/.*$/.test(location.pathname)) comicPage(0);
}
route();
if (/^\/h5.*$/.test(location.pathname)) {
    let previousUrl = location.href;
    const observer = new MutationObserver(function (mutations) {
        if (location.href !== previousUrl) {
            previousUrl = location.href;
            route();
        }
    });
    const config = { subtree: true, childList: true };
    observer.observe(document, config);
}
async function loadCSS() {
    var element_css, animate_css;
    if (typeof (GM_getResourceText) == 'undefined') {
        await axios.get('https://unpkg.com/element-ui@2.15.0/lib/theme-chalk/index.css')
            .then(function (response) {
                element_css = response.data;
            })
        await axios.get('https://unpkg.com/animate.css@4.1.1/animate.min.css')
            .then(function (response) {
                animate_css = response.data;
            })
    } else {
        element_css = GM_getResourceText("element_css");
        animate_css = GM_getResourceText("animate_css");
    }
    GM_addStyle(element_css);
    GM_addStyle(animate_css);
}
function banPage() {
    window.stop();
    document.querySelectorAll('main')[0].innerHTML = `

来自 ☄️拷贝漫画增强☄️ 的消息:
请安装 User-Agent Switcher and Manager 插件并切换浏览器 UA
`
}
function homePage() {
    GM_addStyle('.header-jum {display:none;}');
}
function makeRequest(options) {
    const {url, isPC, headers = {}, params = {}} = options;
    if (isPC) {
        // axios
        return axios.get(url, {headers, params});
    } else {
        const urlParams = new URLSearchParams(params);
        const fullUrl = `${url}?${urlParams.toString()}`;
        headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0';
        // gm绕过ua
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                url: fullUrl,
                headers: headers,
                responseType:'json',
                onload: function(response) {
                    if (response.response === undefined){
                        resolve({data:response.responseText});
                    } else {
                       resolve({data:response.response});
                    }
                },
                onerror: function(error) {
                    reject(error);
                }
            });
        });
    }
}
async function getAesName(url, isPC, fileName) {
    let aesKeyName;
    const request = {
        url: url,
        isPC: isPC
    };
    const regexName = /\[.*?\]\['parse'\]\((.*?)\)/;
    const response = await makeRequest(request);
    const parser = new DOMParser();
    const doc = parser.parseFromString(response.data, "text/html");
    const scripts = doc.querySelectorAll('script');
    if (fileName !== undefined){
        for (const script of scripts) {
            if (script.src.includes(fileName)) {
                const request = {
                    url: script.src,
                    isPC: isPC
                };
                const response = await makeRequest(request);
                const scriptText = response.responseText;
                const evalRegex = /^\s*eval\(\s*(.+)\s*\)\s*$/s;
                const match = scriptText.match(evalRegex);
                const innerCode = match[1];
                const executeCode = new Function('return ' + innerCode);
                const result = executeCode();
                const matchName = result.match(regexName);
                if (matchName && matchName[1]) {
                    aesKeyName = matchName[1];
                    break;
                }
            }
        }
    }
    return [aesKeyName,doc];
}
async function getAesKey(url, isPC, defaultName, fileName) {
    const result = await getAesName(url, isPC, fileName);
    let aesKeyValue = [];
    let aesKeyName = result[0];
    const doc = result[1];
    const scripts = doc.querySelectorAll('script');
    for (let i = 0; i < defaultName.length; i++) {
        if (result[0] === undefined){
            aesKeyName = defaultName[i];
        }
        for (const script of scripts) {
            if (script.textContent.includes(aesKeyName)) {
                const regexValue = new RegExp(`var\\s+${aesKeyName}\\s*=\\s*['"]?([^'";\\s]+)['"]?`);
                const matchValue = script.textContent.match(regexValue);
                if (matchValue && matchValue[1]) {
                    aesKeyValue.push(matchValue[1]);
                    break;
                }
            }
        }
    }
    return [aesKeyValue, doc];
}
async function apiChapters(comic,isPC) {
    const aesKeyName = 'ccz';
    const headers = {'dnts': '2'};
    const request = {
        url: window.location.origin + '/comicdetail/' + comic + '/chapters',
        isPC: isPC,
        headers:headers
    };
    const results = await Promise.all([
        getAesKey(
            window.location.origin + '/comic/' + comic,
            isPC,
            [aesKeyName]
            ),
        makeRequest(request)
    ]);
    let iv = typeof results[1].data === "string"
            ? JSON.parse(results[1].data).results.substring(0, 16)
            : results[1].data.results.substring(0, 16),
        cipher = typeof results[1].data === "string"
            ? JSON.parse(results[1].data).results.substring(16)
            : results[1].data.results.substring(16),
        result = JSON.parse(CryptoJS.AES.decrypt(
            CryptoJS.enc.Base64.stringify(
                CryptoJS.enc.Hex.parse(cipher)
            ),
            CryptoJS.enc.Utf8.parse(results[0][0]),
            {
                'iv': CryptoJS.enc.Utf8.parse(iv),
                'mode': CryptoJS.mode.CBC,
                'padding': CryptoJS.pad.Pkcs7
            }
        ).toString(CryptoJS.enc.Utf8));
    let type_map = new Map();
    result.build.type.forEach((v, index) => {
        type_map.set(v.id, v.name);
    });
    result.groups.default.chapters.forEach((v, index) => {
        v.index = index;
        let type_name = type_map.get(v.type);
        v.name = "【" + type_name + "】" + v.name;
        v.type_name = type_name;
    });
    return result;
}
function tablePage(isPC) {
    loadCSS();
    var collect, save,
        comic,
        content_comic = [],
        app;
    if (isPC)
        comic = window.location.pathname.split('/')[2];
    else
        comic = window.location.pathname.split('/')[4];
    $(() => {
        if (enableDownload) {
          GM_addStyle('.comicParticulars-botton:nth-of-type(4) {background: lightskyblue;}');
          if (isPC)
              collect = document.getElementsByClassName('collect')[0];
          else
              collect = document.getElementsByTagName('button')[2];
          save = collect.cloneNode();
          save.innerHTML = '批量下载';
          save.onclick = saveComic;
          collect.after(save);
          var app_html = document.createElement("div");
          app_html.innerHTML = `
  
    下载范围:
    
      
      
    
    至
    
      
      
    
  
  `
          collect.after(app_html);
          if (isPC)
              document.getElementById('app_save').setAttribute('style', 'margin-top:18px;');
          else
              document.getElementsByClassName('detailsTextContentItem')[0].setAttribute('style', 'flex-wrap:wrap;');
          app = new Vue({
              el: '#app_save',
              data: {
                  content_comic: [],
                  begin: '',
                  end: '',
              },
          })
          GM_addStyle('.el-input__suffix {display:none !important;}');
          apiChapters(comic,isPC)
              .then(function (response) {
                  content_comic = response.groups.default.chapters;
                  app.content_comic = content_comic;
                  app.begin = content_comic.at(0).index;
                  app.end = content_comic.at(-1).index;
              }).catch(function (error) {
                  save.innerHTML = '下载失败';
              })
        }
        cookieStore.get('token')
            .then(function (token) {
                if (token) {
                    axios.get('https://api.mangacopy.com/api/v3/comic2/query/' + comic, {
                        headers: {
                            'authorization': 'Token ' + token.value
                        }
                    }).then(function (response) {
                        if (response.data.results.browse != null) {
                            var read = document.getElementsByClassName('comicParticulars-botton')[0];
                            read.innerHTML = response.data.results.browse.chapter_name;
                            read.href = window.location.origin + '/comic/' + comic + '/chapter/' + response.data.results.browse.chapter_uuid;
                            GM_addStyle('.comicParticulars-botton {max-width:80px; overflow: hidden;text-overflow: ellipsis; white-space: nowrap;}');
                        }
                    });
                }
            })
    })
    let sleep = function (time) {
        return new Promise((resolve) => {
            setTimeout(resolve, time)
        })
    }
    async function saveComic() {
        let zip = new JSZip();
        let task_cnt = 0;
        let date_created_chapter_map = new Map();
        let comic_name = null;
        for (let idx = app.begin; idx <= app.end; idx++) {
            let c = content_comic[idx];
            task_cnt++;
            save.innerHTML = task_cnt + '/' + (app.end - app.begin + 1);
            await axios.get('https://api.mangacopy.com/api/v3/comic/' + comic + '/chapter/' + c.id)
                .then(async function (response) {
                    let task_chapter = [];
                    var chpt_index = Number(response.data.results.chapter.index) + 1
                    let dir_chpt_name = response.data.results.chapter.name
                    if (c.type_name !== undefined) {
                        dir_chpt_name = "【" + chpt_index + "】【" + c.type_name + "】" + dir_chpt_name;
                    }
                    let dir_comic = zip.folder(response.data.results.comic.name)
                    let content = response.data.results.chapter.contents,
                        size = content.length,
                        dict = {};
                    comic_name = response.data.results.comic.name;
                    let date_created_chapter = new Date(response.data.results.chapter.datetime_created);
                    if (idx != app.begin && date_created_chapter.getTime() <= date_created_chapter_map.get(idx - 1).getTime()) {
                        date_created_chapter = new Date(date_created_chapter_map.get(idx - 1).getTime() + 2000);
                    }
                    date_created_chapter_map.set(idx, date_created_chapter);
                    dir_comic.file(dir_chpt_name, null, {
                        dir: true,
                        date: date_created_chapter,
                    });
                    let dir_chpt = dir_comic.folder(dir_chpt_name);
                    for (let i = 0; i < size; i++) {
                        (() => {
                            let self = i;
                            let img_url = content[i].url;
                            if (largeMode) img_url = img_url.replace('c800x.jpg', 'c1500x.jpg');
                            task_chapter.push(axios.get(img_url, { responseType: 'arraybuffer' })
                                .then(function (response) {
                                    let date_created_file = new Date(date_created_chapter.getTime() + 2000 * self);
                                    dir_chpt.file((self + 1) + '.jpg', response.data, { date: date_created_file });
                                }).catch(function (error) {
                                    save.innerHTML = '下载失败';
                                }))
                        })()
                    }
                    await axios.all(task_chapter);
                }).catch(function (error) {
                    save.innerHTML = '下载失败';
                })
            await sleep(1000);
        }
        zip.generateAsync({ type: "blob" }, function (metadata) {
            save.innerHTML = metadata.percent.toFixed(0) + '%';
        }).then(function (blob) {
            saveAs(blob, comic_name + ".zip");
            save.innerHTML = '下载完成';
        })
    }
}
async function comicPage(isPC) {
    // 停止加载原生网页
    window.stop();
    // 解析 URL
    if (isPC) {
        comic = window.location.pathname.split('/')[2];
        chapter = window.location.pathname.split('/')[4];
    } else {
        comic = window.location.pathname.split('/')[3];
        chapter = window.location.pathname.split('/')[4];
    }
    // 加载 HTML
    document.querySelectorAll('html')[0].innerHTML = `
  
    
    
    
      
        - 
          
        
 
      
    
    
      发表
    
    
    
      
      {{message_scroll}}
      
      {{message_page}}
      
      {{message_skip}}
      
      {{message_switch}}
      
      {{message_full}}
      
      {{message_home}}
      {{message_count}}
     
  
 
  
`;
    loadCSS();
    // 加载 LocalStorage
    let dark = store.get('dark');
    let skip = store.get('skip');
    let page = store.get('page');
    let scroll = store.get('scroll');
    if (dark == undefined)
        dark = true;
    if (skip == undefined)
        skip = true;
    if (page == undefined)
        page = true;
    if (scroll == undefined)
        scroll = false;
    if (dark) {
        document.body.classList.add('dark');
    }
    if (skip) {
        document.body.classList.add('skip');
    }
    if (page) {
        document.body.classList.add('page');
    }
    if (scroll) {
        document.body.classList.add('scroll');
    }
    // 加载 Vue
    var app = new Vue({
        el: '#app',
        data: {
            drawer: false,
            isClickable: false,
            size: '100%',
            modal: false,
            direction: 'ltr',
            sidebar_data: [], // 章节数据源
            comic_data: [],   // 图片数据源
            comment_data: [], // 评论数据源
            comment_input: '',
            is_input: 0,
            cur_lock: 0,
            cur_id: 0,
            cur_ch: 0,
            dark: dark,
            page: page,
            skip: skip,
            scroll: scroll,
            show: 0,
            full: 0,
        },
        computed: {
            message_home: function () {
                return '⬅️返回目录';
            },
            message_full: function () {
                return this.full ? '↩️退出全屏' : '↕️进入全屏';
            },
            message_switch: function () {
                return this.dark ? '☀️日间模式' : '🌙夜间模式';
            },
            message_page: function () {
                return this.page ? '1️⃣单页排布' : '2️⃣双页排布';
            },
            message_skip: function () {
                return this.skip ? '📑添加空页' : '📄移除空页';
            },
            message_scroll: function () {
                return this.scroll ? '⏬纵向滚动' : '⏪横向滚动';
            },
            message_count: function () {
                return (this.skip ? (this.cur_id <= 1 ? this.cur_id : this.cur_id - 1) : this.cur_id) + '/' + (this.comic_data.length + 1 - this.skip);
            }
        },
        methods: {
            handleSelect(key) {
                location.href = this.sidebar_data[key].href;
            },
            handleOpen() {
                setTimeout(() => {
                    let sidebar = document.getElementsByClassName('el-drawer__body')[0],
                        ch_list = sidebar.children[0].children;
                    sidebar.scrollTop = ch_list[Math.max(app.cur_ch - 2, 0)].offsetTop;
                }, 0);
                setTimeout(() => {
                    this.isClickable = true;
                }, 10);
            },
            switch_home: function () {
                location.href = window.location.origin + '/comic/' + comic;
            },
            switch_full: function () {
                this.full = !this.full;
                if (this.full) {
                    document.documentElement.requestFullscreen();
                } else {
                    document.exitFullscreen();
                }
            },
            switch_night: function () {
                this.dark = !this.dark;
                store.set('dark', this.dark);
                document.body.classList.toggle('dark');
            },
            switch_skip: function () {
                this.skip = !this.skip;
                store.set('skip', this.skip);
                document.body.classList.toggle('skip');
            },
            switch_page: function () {
                this.page = !this.page;
                store.set('page', this.page);
                document.body.classList.toggle('page');
            },
            switch_scroll: function () {
                this.scroll = !this.scroll;
                store.set('scroll', this.scroll);
                document.body.classList.toggle('scroll');
            },
            send_comment: async function () {
                let token = await cookieStore.get('token');
                await axios.post('https://api.mangacopy.com/api/v3/member/roast', 'chapter_id=' + chapter + '&roast=' + this.comment_input + '&_update=true', {
                    headers: {
                        'authorization': 'Token ' + token.value
                    }
                }).then(function (response) {
                    app.comment_input = response.data.message;
                });
                await this.load_comment();
            },
            load_comment: async function () {
                await axios.get('https://api.mangacopy.com/api/v3/roasts?chapter_id=' + chapter + '&limit=100&offset=0&_update=true')
                    .then(function (response) {
                        app.comment_data = response.data.results.list;
                    })
            },
            prev_chapter: function () {
                location.href = app.sidebar_data[app.cur_ch - 1].href;
            },
            next_chapter: function () {
                location.href = app.sidebar_data[app.cur_ch + 1].href;
            },
        }
    });
    //接口出错通知
    function showErrorNotification() {
        GM_addStyle(`
            .tamper-mask {
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                background: rgba(0, 0, 0, 0.5);
                backdrop-filter: blur(5px);
                -webkit-backdrop-filter: blur(5px);
                z-index: 99998;
                pointer-events: auto;
              }
            .tamper-notification {
              position: fixed;
              top: 100px;
              left: 50%;
              transform: translateX(-50%) translateY(0);
              padding: 15px 20px;
              background: #333;
              color: white;
              border-radius: 4px;
              z-index: 99999;
              font-size: 16px;
            }
        `);
        const mask = document.createElement('div');
        mask.className = 'tamper-mask';
        document.body.appendChild(mask);
        const notification = document.createElement('div');
        notification.className = 'tamper-notification';
        notification.innerHTML = '请更换IP或等待一小时后再试。
如果依然出现此提示,请联系作者更新脚本!';
        document.body.appendChild(notification);
    }
    //通过图片List
    async function getImageOld() {
        const headers = {'version': '2025.08.08', 'platform': '1'};
        const request = {
            url: 'https://api.2025copy.com/api/v3/comic/' + comic + '/chapter/' + chapter,
            isPC: isPC,
            headers: headers
        };
        const response = await makeRequest(request);
        if (response.data.code == 210) {
            showErrorNotification();
            return;
        }
        document.title = response.data.results.comic.name + ' - ' + response.data.results.chapter.name;
        const content = response.data.results.chapter.contents;
        return content;
    }
    //通过加密数据
    async function getImageNew() {
        const contentName = 'contentKey';
        const aesKeyName = 'cct';
        const results = await getAesKey(
            window.location.origin + '/comic/' + comic + '/chapter/' + chapter,
            isPC,
            [contentName, aesKeyName]
            );
        if (results[0].length != 2) {
            throw new Error();
            return;
        }
        const doc = results[1];
        const scripts = doc.querySelectorAll('script');
        const parts = doc.title.split(" - ")
        document.title = parts[0] + ' - ' + parts[1];
        const contentValue = results[0][0];
        const aesKeyValue = results[0][1];
        let iv = contentValue.substring(0, 16),
            cipher = contentValue.substring(16),
            content = JSON.parse(CryptoJS.AES.decrypt(
                CryptoJS.enc.Base64.stringify(
                    CryptoJS.enc.Hex.parse(cipher)
                ),
                CryptoJS.enc.Utf8.parse(aesKeyValue),
                {
                    'iv': CryptoJS.enc.Utf8.parse(iv),
                    'mode': CryptoJS.mode.CBC,
                    'padding': CryptoJS.pad.Pkcs7
                }
            ).toString(CryptoJS.enc.Utf8));
        return content;
    }
    // 加载图片
    async function loadImg() {
        if (getPictureMode) {
            var content;
            try {
                content = await getImageNew();
            } catch (error) {
                content = await getImageOld();
            }
        } else {
            content = await getImageOld();
        }
        const matrix = document.getElementById('matrix'),
              size = content.length,
              dict = {};
        for (let i = 0; i < size; i++) {
            let img_url = content[i].url;
            if (largeMode) img_url = img_url.replace('c800x.jpg', 'c1500x.jpg');
            app.comic_data.push({
                url: img_url
            })
        }
        // TODO
        setTimeout(() => {
            let $blank = $('.inner_img:eq(0)').clone();
            $blank.addClass('blank');
            $blank.css('filter', 'brightness(0) invert(1)');
            $('#matrix').prepend($blank);
        }, 0);
    }
    loadImg();
    // 加载章节
    apiChapters(comic,isPC)
        .then(function (response) {
            // var content = response.groups.default.chapters;
            var content = Object.values(response.groups).flatMap(obj => obj.chapters);
            content.forEach((i,index) => {
                if (location.href.indexOf(i.id) >= 0) {
                    app.cur_ch = index;
                    GM_addStyle('.el-menu>li:nth-child(' + (index + 1) + '){background:rgba(255,165,0,.5) !important}');
                }
                app.sidebar_data.push({
                    title: i.name,
                    href: window.location.origin + '/comic/' + comic + '/chapter/' + i.id
                })
            })
        })
    // 加载评论
    app.load_comment();
    //上下方向键滚动页面,左右方向键切换章节
    function scrollUp() {
        let img_list = document.querySelectorAll('.inner_img'),
            first_img = img_list[app.skip ? 1 : 0],
            last_img = img_list[img_list.length - 1];
        if (app.cur_id == 0) return;
        var id = img_list.length + 1;
        for (var i = (app.skip ? 1 : 0) + 1; i <= Math.min(app.cur_id, img_list.length); i++) {
            if (((app.cur_lock && app.cur_id >= 1 && app.cur_id <= img_list.length) ? img_list[app.cur_id - 1].offsetTop : pageYOffset) < img_list[i - 1].offsetTop + img_list[i - 1].offsetHeight + 5) {
                id = i;
                break;
            }
        }
        if (((app.cur_lock && app.cur_id >= 1 && app.cur_id <= img_list.length) ? img_list[app.cur_id - 1].offsetTop : pageYOffset) < first_img.offsetTop + 5) {
            id = 0;
        }
        app.cur_lock++;
        app.cur_id = id;
        setTimeout(function () { app.cur_lock--; }, 500);
        // TODO
        $("html").stop();
        if (id == 0) {
            $("html").animate({ scrollTop: 0 }, 500);
        } else {
            $("html").animate({ scrollTop: img_list[id - 1].offsetTop }, 500);
        }
    }
    function scrollDown() {
        let img_list = document.querySelectorAll('.inner_img'),
            first_img = img_list[app.skip ? 1 : 0],
            last_img = img_list[img_list.length - 1];
        if (app.cur_id == img_list.length + 1) return;
        var id = img_list.length + 1;
        for (var i = Math.max(app.cur_id, (app.skip ? 1 : 0) + 1); i <= img_list.length; i++) {
            if (((app.cur_lock && app.cur_id >= 1 && app.cur_id <= img_list.length) ? img_list[app.cur_id - 1].offsetTop : pageYOffset) < img_list[i - 1].offsetTop - 5) {
                id = i;
                break;
            }
        }
        app.cur_lock++;
        app.cur_id = id;
        setTimeout(function () { app.cur_lock--; }, 500);
        // TODO
        $("html").stop();
        if (id == img_list.length + 1) {
            $("html").animate({ scrollTop: last_img.offsetTop + last_img.offsetHeight }, 500);
        } else {
            $("html").animate({ scrollTop: img_list[id - 1].offsetTop }, 500);
        }
    }
    document.getElementById('matrix').onclick = function (event) {
        if (event.clientY > $(window).height() / 2) {
            if (app.page) scrollDown();
        } else {
            if (app.page) scrollUp();
        }
    }
    document.body.onkeydown = function (event) {
        if (!app.is_input) {
            if (app.scroll) {
                switch(event.keyCode) {
                case 38:
                    event.preventDefault()
                    app.prev_chapter();
                    break;
                case 40:
                    event.preventDefault()
                    app.next_chapter();
                    break;
                }
            } else {
                switch(event.keyCode) {
                case 38:
                    if (app.page) scrollUp();
                    break;
                case 40:
                    if (app.page) scrollDown();
                    break;
                case 37:
                    app.prev_chapter();
                    break;
                case 39:
                    app.next_chapter();
                    break;
                }
            }
            if (event.keyCode == 13) {
                app.switch_full();
            } else if (event.keyCode == 8) {
                location.href = window.location.origin + '/comic/' + comic;
            }
        }
    }
    // 加载当前页码
    function getID() {
        let cur_id = 0,
            img_list = document.querySelectorAll('.inner_img'),
            first_img = img_list[app.skip ? 1 : 0],
            last_img = img_list[img_list.length - 1];
        if (img_list.length > 0) {
            if (app.scroll) {
                let progress = matrix.scrollLeft / (matrix.scrollWidth - matrix.clientWidth);
                progress = Math.max(0, Math.min(1, Math.abs(progress)));
                let referenceX = matrix.clientWidth - matrix.clientWidth * progress + matrix.offsetLeft;
                referenceX = Math.round(referenceX);
                for (let i = 0; i < img_list.length; i++) {
                    const imgOffsetLeft = Math.round(img_list[i].getBoundingClientRect().left);
                    const imgPosition = [imgOffsetLeft - 1, imgOffsetLeft + img_list[i].clientWidth +1];
                    if (imgPosition[1] > referenceX && referenceX > imgPosition[0]) {
                        cur_id = i + 1;
                        break;
                    }
                }
            } else {
                for (let i = 0; i < img_list.length; i++) {
                    if (pageYOffset > img_list[i].offsetTop - 5 && pageYOffset < img_list[i].offsetTop + img_list[i].offsetHeight - 5 && cur_id == 0) {
                        cur_id = i + 1;
                        break;
                    }
                }
                if (pageYOffset > last_img.offsetTop + last_img.offsetHeight - 5)
                    cur_id = img_list.length + 1;
            }
            if (app.cur_lock == 0) app.cur_id = cur_id;
        }
    }
    setInterval(getID, 100);
    window.addEventListener('mousewheel', getID);
    window.addEventListener('error', event => {
        let target = event.target || event.srcElement;
        let isElementTarget = target instanceof HTMLImageElement;
        if (!isElementTarget) return false;
        let url = target.src;
        setTimeout(() => {
            target.src = url;
        }, Math.floor(Math.random() * 2000) + 1);
    }, true);
}