- /* eslint-disable no-multi-spaces */
- /* eslint-disable no-useless-call */
- /* eslint-disable userscripts/no-invalid-headers */
-
- // ==UserScript==
- // @name 轻小说文库+
- // @namespace Wenku8+
- // @version 1.7.5
- // @description 轻小说文库全方位体验改善,涵盖阅读、下载、书架、推荐、书评、账号、页面个性化等各种方面,你能想到的这里都有。没有?欢迎提出你的点子。
- // @updateinfo <h3>v1.7.5</h3><ul><li>支持wenku8.cc</li><li>修复书评文字缩放行间距错误地随文字缩放放大缩小问题</li></ul>
- // @author PY-DNG
- // @license GPL-license
- // @icon data:image/vnd.microsoft.icon;base64,AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAD/igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//+KAP//igD//4oA//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw///igD//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//4oA//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw///igD//OfD//znw//858P//OfD//znw//858P//rlB//3HaP/858P//OfD//znw//858P//OfD//znw//858P//OfD//zitv/91Y///dWP//3Vj//9y3X//cdo//3HaP/9x2j//cdo//zitv/858P//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//OfD//znw///ogD//6IA//znw//858P//OfD//znw//9x2j//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6cN//znw//858P//OfD//znw//858P//4oA//znw//858P//OfD//3ZnP//ogD//6IA//+iAP//ogD//sJb//3HaP/83qn//OfD//zeqf//pw3//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+rGv/90IL//OfD//znw//858P//OfD//znw///igD//OfD//znw//858P//cdo//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//OfD//znw//858P//OfD//znw//858P//6IA//+iAP/84rb//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//rQ0//60NP//ogD//6IA//+iAP//ogD//6IA//+iAP/858P//OfD//znw//858P//OfD//znw///ogD//6IA//3Vj//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//4oA//znw//858P//OfD//znw//858P//OfD//6wJ///ogD//6sa//3HaP/92Zz//OfD//znw//858P//OfD//znw//858P//OfD//+iAP//ogD//rQ0//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw///igD//OfD//znw//858P//OfD//zitv/+vk7//6IA//+iAP//ogD//6IA//+iAP/+tDT//OfD//znw//858P//OfD//znw//858P//rAn//+iAP//ogD//ct1//3HaP/+tDT//rQ0//6+Tv/858P//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//ct1//7CW///ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//znw//858P//OfD//znw//858P//4oA//znw//858P//OfD//znw///ogD//6IA//65Qf//ogD//6IA//3HaP/9x2j//6cN//+iAP//ogD//r5O//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//60NP/9x2j//OfD//znw//858P//OfD//znw///igD//OfD//znw//858P//OfD//+iAP//ogD//OfD//65Qf//ogD//6cN//znw//858P//6IA//+iAP/92Zz//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//6IA//+iAP/858P//cdo//+iAP//ogD//dWP//3Vj///pw3//6IA//7CW//858P//rQ0//+iAP/92Zz//OfD//3Ldf/+tDT//OK2//znw//858P//N6p//znw//858P//OfD//znw//858P//4oA//znw//858P//OfD//znw///ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//7CW///ogD//6IA//6+Tv//ogD//6IA//6+Tv/91Y///6IA//+iAP/+vk7//OfD//+iAP//ogD//6IA//znw//858P//OfD//znw///igD//OfD//znw//858P//OfD//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//dWP//+iAP//ogD//6IA//+iAP//ogD//OK2//+rGv//ogD//6IA//3ZnP/858P//6IA//+iAP//qxr//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//sJb//+iAP//pw3//rQ0//+iAP//ogD//OfD//znw//+wlv//6IA//+iAP//ogD//6cN//3ZnP/+vk7//6IA//+iAP/90IL//OfD//60NP//ogD//6IA//znw//858P//OfD//znw//858P//4oA//znw//858P//OfD//znw//84rb//rAn//+iAP//ogD//6IA//+iAP/+sCf//rQ0//+iAP//ogD//6IA//+iAP/9x2j//OfD//65Qf//ogD//rAn//znw//858P//rQ0//+iAP/+uUH//OfD//znw//858P//OfD//znw///igD//OfD//znw//858P//OfD//znw//858P//r5O//60NP//ogD//6IA//+iAP//ogD//6IA//+iAP/9x2j//6IA//+iAP/9x2j//OfD//6+Tv//ogD//6IA//znw//84rb//6IA//+iAP/84rb//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//OfD//znw//858P//OfD//+nDf//ogD//6cN//60NP/+vk7//ct1//znw//+wlv//6IA//+iAP/858P//OK2//+iAP//ogD//dmc//znw///pw3//6IA//6wJ//83qn//OfD//znw//858P//4oA//znw//858P//OfD//znw//858P//OfD//znw//858P//cdo//+iAP//ogD//dmc//znw//858P//OfD//znw///pw3//6IA//+nDf/83qn//dmc//+nDf//ogD//6sa//znw//92Zz//6IA//+iAP/858P//OfD//znw///igD//OfD//znw//858P//OfD//znw//858P//N6p//3HaP/+sCf//6IA//+iAP//ogD//6IA//+iAP//qxr//dmc//3ZnP//pw3//6IA//6+Tv/858P//dmc//+iAP//ogD//OfD//znw//9x2j//sJb//znw//858P//OfD//+KAP/858P//OfD//znw//858P//OfD//znw///ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP/+vk7//OfD//znw//91Y///OK2//znw//858P//dWP//3Vj//9x2j//rlB//+nDf//pw3//OfD//znw//858P//4oA//znw//858P//OfD//znw//858P//OfD//3Ldf//ogD//6IA//+iAP//ogD//6IA//60NP/+tDT//cdo//zitv/+tDT//rQ0//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP/83qn//OfD//znw///igD//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//dmc//+iAP/+sCf//OfD//znw//858P//dWP//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//+iAP//ogD//6IA//znw//858P//OfD//+KAP/858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//cdo//zeqf/858P//OfD//znw//858P//rQ0//60NP/+tDT//rQ0//60NP/9x2j//cdo//3HaP/91Y///dWP//zeqf/858P//OfD//znw//858P//4oA//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw///igD//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//+KAP/858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//4oA//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw//858P//OfD//znw///igD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
- // @match http*://www.wenku8.net/*
- // @match http*://www.wenku8.cc/*
- // @connect wenku8.com
- // @connect wenku8.net
- // @connect greasyfork.org
- // @connect image.kieng.cn
- // @connect sm.ms
- // @connect catbox.moe
- // @connect liumingye.cn
- // @connect p.sda1.dev
- // @connect api.pandaimg.com
- // @connect imagelol.com
- // @connect pic.jitudisk.com
- // @connect cdn.jsdelivr.net
- // @connect cdnjs.cloudflare.com
- // @connect bowercdn.net
- // @connect unpkg.com
- // @connect cdn.bootcdn.net
- // @connect kit.fontawesome.com
- // @grant GM_xmlhttpRequest
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_deleteValue
- // @grant GM_listValues
- // @grant GM_openInTab
- // @grant GM_getResourceText
- // @grant GM_info
- // @grant unsafeWindow
- // @require https://greasyfork.org/scripts/427726-gbk-url-js/code/GBK_URLjs.js?version=953098
- // @require https://greasyfork.org/scripts/431490-greasyforkscriptupdate/code/GreasyForkScriptUpdate.js?version=965063
- // @require https://cdnjs.cloudflare.com/ajax/libs/AlertifyJS/1.13.1/alertify.min.js
- // @require https://unpkg.com/@popperjs/core@2
- // @require https://unpkg.com/tippy.js@6
- // @resource alertify-css https://cdnjs.cloudflare.com/ajax/libs/AlertifyJS/1.13.1/css/alertify.min.css
- // @resource alertify-theme https://cdnjs.cloudflare.com/ajax/libs/AlertifyJS/1.13.1/css/themes/default.min.css
- // @noframes
- // ==/UserScript==
-
- /* 需求记录 [容易(优先级高) ➡️ 困难(优先级低)(我懒,一般而言优先做低难度的)]
- ** [已完成]{BK}书评页提供用户书评搜索
- ** {BK}图片大小(最大)限制
- ** [已完成]{BK}回复区插入@好友
- ** [已完成]全卷/分卷下载:文件重命名为书名,而不是书号
- ** · [已完成]添加单文件下载重命名
- ** {BK}回复区悬浮显示
- ** {热忱}[已完成]修复https引用问题
- ** [已完成]书评打开最后一页
- ** [待完善]书评实时更新
- ** · [待完善]新回复直接添加到当前页面
- ** · 主动回复内容直接添加到当前页面
- ** [待完善]引用回复
- ** · [已完成]引用楼层号和回复内容
- ** · [已完成]仅引用楼层号
- ** [已完成]支持preview版tag搜索
- ** [高优先级]备注功能
- · [待完善]用户备注
- · 小说备注
- · [低优先级]阅读随笔(这真的可能实现吗??)
- ** [待完善]书评帖子收藏
- ** · [已完成]书评页面收藏
- ** · [高优先级]收藏的书评页面可以添加编辑备注
- ** [已完成]每日自动推书
- ** [待完善]{热忱}快速切换账号
- ** · [已完成]为每个账号储存单独的配置
- ** · [待完善]保存账号信息并快速自动切换
- ** [待完善]快速插入图片/表情
- ** · [已完成]直接插入本地图片
- ** · [持续进行]更多图床
- ** · [低优先级]保存常用图片/表情链接
- ** [部分完成]{BK}页面美化
- ** · [已完成]阅读页去除广告
- ** · [已完成]阅读页美化
- ** · [已完成]书评页美化
- ** · …
- ** [高优先级][施工中]脚本储存管理界面
- ** [高优先级][待完善]稍后再读(可以的话,请给我提出改进建议)
- ** {BK}类似ehunter的阅读模式
- ** 改进旧代码:
- ** · 每个page-addon内部要按照功能分模块,执行功能靠调用模块,不能直接写功能代码
- ** · 共性模块要写进脚本全局作用域,可以的话写成构造函数
- ** [低优先级]{RC}书评:@某人时通知他
- ** [待完善]{BK}书评:草稿箱功能
- ** {热忱}{s1h2}提供带文字和插图的epub整合下载
- */
- /* API记录
- ** 阅读API:http://dl.wenku8.com/pack.php?aid=2478&vid=92914
- ** 回帖API:https://www.wenku8.net/modules/article/reviewshow.php?rid=209631&aid=2751
- ** 查人API:https://www.wenku8.net/modules/article/reviewslist.php?keyword=136877
- ** 读书API:https://www.wenku8.net/modules/article/reader.php?aid=2946
- ** 好友API:https://www.wenku8.net/myfriends.php // 好友名称选择器:content.querySelectorAll('tr>td.odd:nth-child(1)')
- ** 登录API:https://www.wenku8.net/login.php?do=submit&jumpurl=http%3A%2F%2Fwww.wenku8.net%2Findex.php
- ** 最新回复:https://www.wenku8.net/modules/article/reviewslist.php?t=1
- ** 检查更新:https://greasyfork.org/zh-CN/scripts/416310/code/script.meta.js
- */
- /* 账号收藏
- ** wenku8高仿号(按照相似度排列):
- ** ** https://www.wenku8.net/userpage.php?uid=912148
- ** ** https://www.wenku8.net/userpage.php?uid=728810
- ** ** https://www.wenku8.net/userpage.php?uid=917768
- ** BK高仿号
- ** ** https://www.wenku8.net/userpage.php?uid=918609
- ** 热忱高仿号
- ** ** https://www.wenku8.net/userpage.php?uid=918764
- ** 隐身鱼高仿号
- ** ** https://www.wenku8.net/userpage.php?uid=918773
- */
- (function FUNC_MAIN() {
- 'use strict';
-
- // Polyfills
- const script_name = '轻小说文库+';
- const script_version = '1.7.4.3';
- const NMonkey_Info = {
- GM_info: {
- script: {
- name: script_name,
- author: 'PY-DNG',
- version: script_version,
- }
- },
- mainFunc: FUNC_MAIN,
- name: 'wenku8_plus',
- requires: [
- // GBK-URL
- {
- name: 'GBK-URL',
- src: 'https://greasyfork.org/scripts/427726-gbk-url-js/code/GBK_URLjs.js?version=953098',
- srcset: [
- 'https://cdn.jsdelivr.net/gh/PYUDNG/CDN@eed1fcf0e901348bc4e752fd483bcb571ebe0408/js/GBK_URL/GBK.js',
- ],
- loaded: () => (typeof $URL === 'object'),
- execmode: 'function'
- },
-
- // GreasyForkScriptUpdate
- {
- name: 'GreasyForkScriptUpdate',
- src: 'https://greasyfork.org/scripts/431490-greasyforkscriptupdate/code/GreasyForkScriptUpdate.js?version=965063',
- srcset: [
- 'https://cdn.jsdelivr.net/gh/PYUDNG/CDN@94fc2bdd313f7bf2af6db5b8699effee8dd0b18d/js/ajax/GreasyForkScriptUpdate.js',
- ],
- loaded: () => (typeof GreasyForkUpdater === 'function'),
- execmode: 'eval'
- },
-
- // Alertify
- {
- name: 'Alertify',
- src: 'https://cdnjs.cloudflare.com/ajax/libs/AlertifyJS/1.13.1/alertify.min.js',
- srcset: [
- 'https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/alertify.min.js',
- 'https://bowercdn.net/c/alertify-js-1.13.1/build/alertify.min.js',
- 'https://cdn.bootcdn.net/ajax/libs/AlertifyJS/1.9.0/alertify.min.js',
- ],
- loaded: () => (typeof alertify === 'object'),
- execmode: 'function'
- },
-
- // FontAwesome
- /*
- {
- src: 'https://kit.fontawesome.com/1288cd6170.js',
- loaded: () => (typeof(FontAwesomeKitConfig) === 'object')
- }
- */
-
- // Tippy.js
- {
- name: 'Tippy.js-Core',
- src: 'https://unpkg.com/@popperjs/core@2',
- loaded: () => (typeof tippy === 'function'),
- execmode: 'function'
- },
- {
- name: 'Tippy.js',
- src: 'https://unpkg.com/tippy.js@6',
- loaded: () => (typeof tippy === 'function'),
- execmode: 'function'
- },
- ],
- resources: [
- // Alertify css
- {
- src: 'https://cdnjs.cloudflare.com/ajax/libs/AlertifyJS/1.13.1/css/alertify.min.css',
- srcset: [
- 'https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/css/alertify.min.css',
- 'https://bowercdn.net/c/alertify-js-1.13.1/build/css/alertify.min.css',
- 'https://cdn.bootcdn.net/ajax/libs/AlertifyJS/1.9.0/css/alertify.min.css',
- ],
- name: 'alertify-css',
- isCss: true
- },
-
- // Alertify theme
- {
- src: 'https://cdnjs.cloudflare.com/ajax/libs/AlertifyJS/1.13.1/css/themes/default.min.css',
- srcset: [
- 'https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/css/themes/default.min.css',
- 'https://bowercdn.net/c/alertify-js-1.13.1/build/css/themes/default.min.css',
- 'https://cdn.bootcdn.net/ajax/libs/AlertifyJS/1.9.0/css/themes/default.min.css',
- ],
- name: 'alertify-theme',
- isCss: true
- },
-
- // tooltip
- /*
- {
- src: 'https://cdn.jsdelivr.net/gh/PYUDNG/css-components@main/build/tooltip/tooltip.css',
- srcset: [
- '',
- ],
- name: 'css-tooltip',
- isCss: true
- },
- */
-
- // FontAwesome
- /*
- {
- src: 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.1/css/all.min.css',
- srcset: [
- 'https://bowercdn.net/c/fontAwesome-6.1.1/css/all.min.css',
- 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css',
- ],
- name: 'css-fontawesome',
- isCss: true
- }
- */
- ]
- };
- const NMonkey_Ready = NMonkey(NMonkey_Info);
- if (!NMonkey_Ready) {return false;}
- polyfill_replaceAll();
-
- // CONSTS
- const NUMBER_MAX_XHR = typeof mbrowser === 'object' ? 1 : 10;
- const NUMBER_LOGSUCCESS_AFTER = NUMBER_MAX_XHR * 2;
- const NUMBER_ELEMENT_LOADING_WAIT_INTERVAL = 500;
-
- const KEY_CM = 'Config-Manager';
- const KEY_CM_VERSION = 'version';
- const VALUE_CM_VERSION = '0.3';
-
- const KEY_DRAFT_DRAFTS = 'comment-drafts';
- const KEY_DRAFT_VERSION = 'version';
- const VALUE_DRAFT_VERSION = '0.2';
-
- const KEY_REVIEW_PREFS = 'comment-preferences';
- const KEY_REVIEW_VERSION = 'version';
- const VALUE_REVIEW_VERSION = '0.9';
-
- const KEY_BOOKCASES = 'book-cases';
- const KEY_BOOKCASE_VERSION = 'version';
- const VALUE_BOOKCASE_VERSION = '0.5';
-
- const KEY_ATRCMMDS = 'auto-recommends';
- const KEY_ATRCMMDS_VERSION = 'version';
- const VALUE_ATRCMMDS_VERSION = '0.2';
-
- const KEY_USRDETAIL = 'user-detail';
- const KEY_USRDETAIL_VERSION = 'version';
- const VALUE_USRDETAIL_VERSION = '0.2';
-
- const KEY_BEAUTIFIER = 'beautifier';
- const KEY_BEAUTIFIER_VERSION = 'version';
- const VALUE_BEAUTIFIER_VERSION = '0.9';
-
- const KEY_REMARKS = 'remarks';
- const KEY_REMARKS_VERSION = 'version';
- const VALUE_REMARKS_VERSION = '0.1';
-
- const KEY_USERGLOBAL = 'user-global-config';
- const KEY_USERGLOBAL_VERSION = 'version';
- const VALUE_USERGLOBAL_VERSION = '0.1';
-
- const VALUE_STR_NULL = 'null';
-
- const URL_NOVELINDEX = `https://${location.host}/book/{I}.htm`;
- const URL_REVIEWSEARCH = `https://${location.host}/modules/article/reviewslist.php?keyword={K}`;
- const URL_REVIEWSHOW = `https://${location.host}/modules/article/reviewshow.php?rid={R}&aid={A}&page={P}`;
- const URL_REVIEWSHOW_1 = `https://${location.host}/modules/article/reviewshow.php?rid={R}`;
- const URL_REVIEWSHOW_2 = `https://${location.host}/modules/article/reviewshow.php?rid={R}&page={P}`;
- const URL_REVIEWSHOW_3 = `https://${location.host}/modules/article/reviewshow.php?rid={R}&aid={A}`;
- const URL_REVIEWSHOW_4 = `https://${location.host}/modules/article/reviewshow.php?rid={R}&page={P}#{Y}`;
- const URL_REVIEWSHOW_5 = `https://${location.host}/modules/article/reviewshow.php?rid={R}&aid={A}&page={P}#{Y}`;
- const URL_USERINFO = `https://${location.host}/userinfo.php?id={K}`;
- const URL_DOWNLOAD1 = `http://${location.host.replace('www.', 'dl.')}/packtxt.php?aid={A}&vid={V}&charset={C}`;
- const URL_DOWNLOAD2 = `http://${location.host.replace('www.', 'dl2.')}/packtxt.php?aid={A}&vid={V}&charset={C}`;
- const URL_DOWNLOAD3 = `http://${location.host.replace('www.', 'dl3.')}/packtxt.php?aid={A}&vid={V}&charset={C}`;
- const URL_PACKSHOW = `https://${location.host}/modules/article/packshow.php?id={A}&type={T}`;
- const URL_BOOKINTRO = `https://${location.host}/book/{A}.htm`;
- const URL_ADDBOOKCASE = `https://${location.host}/modules/article/addbookcase.php?bid={A}`;
- const URL_RECOMMEND = `https://${location.host}/modules/article/uservote.php?id={B}`;
- const URL_TAGSEARCH = `https://${location.host}/modules/article/tags.php?t={TU}`;
- const URL_USRDETAIL = `https://${location.host}/userdetail.php`;
- const URL_USRFRIEND = `https://${location.host}/myfriends.php`;
- const URL_BOOKCASE = `https://${location.host}/modules/article/bookcase.php`;
- const URL_USRLOGIN = `https://${location.host}/login.php?do=submit&jumpurl=http%3A%2F%2F${location.host}%2Findex.php`;
- const URL_USRLOGOFF = `https://${location.host}/logout.php`;
-
- const DATA_XHR_LOGIN = [
- "username={U}",
- "password={P}",
- "usecookie={C}",
- "action=login",
- "submit=%26%23160%3B%B5%C7%26%23160%3B%26%23160%3B%C2%BC%26%23160%3B" // ' 登  录 '
- ].join('&');
- const DATA_IMAGERS = {
- default: 'SDAIDEV',
- /* Imager Model
- _IMAGER_KEY_: {
- available: true,
- name: '_IMAGER_DISPLAY_NAME_',
- tip: '_IMAGER_DISPLAY_TIP_',
- upload: {
- request: {
- url: '_UPLOAD_URL_',
- data: {
- '_FORM_NAME_FOR_FILE_': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json._SUCCESS_KEY_ === '_SUCCESS_VALUE_';},
- geturl: (json)=>{return json._PATH_._SUCCESS_URL_KEY_;},
- getname: (json)=>{return json._PATH_ ? json._PATH_._FILENAME_ : null;},
- getsize: (json)=>{return json._PATH_._SIZE_},
- getpage: (json)=>{return json._PATH_ ? json._PATH_._PAGE_ : null;},
- gethash: (json)=>{return json._PATH_ ? json._PATH_._HASH_ : null;},
- getdelete: (json)=>{return json._PATH_ ? json._PATH_._DELETE_ : null;}
- }
- },
- isImager: true
- },
- */
- LIUMINGYE: {
- available: true,
- name: '刘明野-全能图床',
- tip: '2021-12-04测试可用</br>理论无上传大小限制,实际测试图片过大会上传失败',
- upload: {
- request: {
- url: 'https://tool.liumingye.cn/tuchuang/update.php',
- data: {
- 'file': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 0;},
- geturl: (json)=>{return json.msg;}
- }
- },
- isImager: true
- },
- PANDAIMG: {
- available: true,
- name: '熊猫图床',
- tip: '2022-01-16测试可用</br>单张图片最大5MB',
- upload: {
- request: {
- url: 'https://api.pandaimg.com/upload',
- data: {
- 'file': '$file$',
- 'classifications': '',
- 'day': '0'
- },
- headers: {
- 'usersOrigin': '5edd88d4dfe5d288518c0454d3ccdd2a'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === '200';},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- SDAIDEV: {
- available: true,
- name: '流浪图床',
- tip: '2022-01-09测试可用</br>单张图片最大5MB',
- upload: {
- request: {
- url: 'https://p.sda1.dev/api/v1/upload_external_noform',
- urlargs: {
- 'filename': '$filename$',
- 'ts': '$time$',
- 'rand': '$random$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.success;},
- geturl: (json)=>{return json.data.url;},
- getdelete: (json)=>{return json.data ? json.data.delete_url : null;},
- getsize: (json)=>{return json.data ? json.data.size : null;}
- }
- },
- isImager: true
- },
- JITUDISK: {
- available: true,
- name: '极兔兔床',
- tip: '2022-02-02测试可用',
- upload: {
- request: {
- url: 'https://pic.jitudisk.com/api/upload',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- IMAGELOL: {
- available: false,
- name: '笑果图床',
- tip: '2022-01-17测试可用</br>该图床不支持重复上传同一张图片,请注意</br>单张图片最大2MB',
- upload: {
- request: {
- url: 'https://imagelol.com/json',
- data: {
- 'source': '$file$',
- 'type': 'file',
- 'action': 'upload',
- 'timestamp': '$time$',
- 'auth_token': '4f6fb8d04525bae5a455f4f09e2b09aa750e60c3',
- 'nsfw': '0'
- }
- },
- response: {
- checksuccess: (json)=>{return json.status_code === 200 && json.success && json.success.code === 200;},
- geturl: (json)=>{return json.image.url;},
- getname: (json)=>{return json.image.original_filename;},
- getsize: (json)=>{return json.image.size},
- gethash: (json)=>{return json.image.md5;},
- }
- },
- isImager: true
- },
- /*GEJIBA: {
- available: true,
- name: '老王图床',
- tip: '2022-01-17测试可用</br>单张图片最大10MB</br>PS:此图床审核比较严格',
- upload: {
- request: {
- url: '_UPLOAD_URL_',
- data: {
- '_FORM_NAME_FOR_FILE_': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json._SUCCESS_KEY_ === '_SUCCESS_VALUE_';},
- geturl: (json)=>{return json._PATH_._SUCCESS_URL_KEY_;},
- getname: (json)=>{return json._PATH_ ? json._PATH_._FILENAME_ : null;},
- getsize: (json)=>{return json._PATH_._SIZE_},
- getpage: (json)=>{return json._PATH_ ? json._PATH_._PAGE_ : null;},
- gethash: (json)=>{return json._PATH_ ? json._PATH_._HASH_ : null;},
- getdelete: (json)=>{return json._PATH_ ? json._PATH_._DELETE_ : null;}
- }
- }
- },*/
- KIENG_JD: {
- available: false,
- name: 'KIENG-JD',
- tip: '默认图床</br>个人体验良好,推荐使用',
- upload: {
- request: {
- url: 'https://image.kieng.cn/upload.html?type=jd',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- KIENG_SG: {
- available: false,
- name: 'KIENG-SG',
- upload: {
- request: {
- url: 'https://image.kieng.cn/upload.html?type=sg',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- KIENG_58: {
- available: false,
- name: 'KIENG-58',
- upload: {
- request: {
- url: 'https://image.kieng.cn/upload.html?type=c58',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- KIENG_WY: {
- available: false,
- name: 'KIENG-WY',
- upload: {
- request: {
- url: 'https://image.kieng.cn/upload.html?type=wy',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- KIENG_QQ: {
- available: false,
- name: 'KIENG-QQ',
- upload: {
- request: {
- url: 'https://image.kieng.cn/upload.html?type=qq',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- KIENG_SN: {
- available: false,
- name: 'KIENG-SN',
- upload: {
- request: {
- url: 'https://image.kieng.cn/upload.html?type=sn',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- KIENG_HL: {
- available: false,
- name: 'KIENG-HLX',
- upload: {
- request: {
- url: 'https://image.kieng.cn/upload.html?type=hl',
- data: {
- 'image': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.code === 200;},
- geturl: (json)=>{return json.data.url;},
- getname: (json)=>{return json.data.name;}
- }
- },
- isImager: true
- },
- SMMS: {
- available: true,
- name: 'SM.MS',
- tip: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床',
- warning: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床</br>如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床',
- upload: {
- request: {
- url: 'https://sm.ms/api/v2/upload?inajax=1',
- data: {
- 'smfile': '$file$'
- }
- },
- response: {
- checksuccess: (json)=>{return json.success === true || /^https?:\/\//.test(json.images);},
- geturl: (json)=>{return json.data ? json.data.url : json.images;},
- getname: (json)=>{return json.data ? json.data.filename : null;},
- getpage: (json)=>{return json.data ? json.data.page : null;},
- gethash: (json)=>{return json.data ? json.data.hash : null;},
- getdelete: (json)=>{return json.data ? json.data.delete : null;}
- }
- },
- isImager: true
- },
- CATBOX: {
- available: true,
- name: 'CatBox',
- tip: '注意:此图床访问较不稳定,请谨慎使用此图床',
- warning: '注意:此图床访问较不稳定,请谨慎使用此图床</br>如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床',
- upload: {
- request: {
- url: 'https://catbox.moe/user/api.php',
- responseType: 'text',
- data: {
- 'fileToUpload': '$file$',
- 'reqtype': 'fileupload'
- }
- },
- response: {
- checksuccess: (text)=>{return true;},
- geturl: (text)=>{return text;}
- }
- },
- isImager: true
- }
- };
-
- const FUNC_LATERBOOK_SORTERS = {
- 'addTime_old2new': {
- name: '由旧到新',
- sorter: (a, b) => (a.addTime - b.addTime),
- },
- 'addTime_new2old': {
- name: '由新到旧',
- sorter: (a, b) => (b.addTime - a.addTime),
- },
- 'sort': {
- name: '手动排序',
- sorter: (a, b) => (a.sort - b.sort),
- }
- }
-
- const CLASSNAME_BUTTON = 'plus_btn';
- const CLASSNAME_TEXT = 'plus_text';
- const CLASSNAME_DISABLED = 'plus_disabled';
- const CLASSNAME_BOOKCASE_FORM = 'plus_bcform';
- const CLASSNAME_LIST = 'plus_list';
- const CLASSNAME_LIST_ITEM = 'plus_list_item';
- const CLASSNAME_LIST_BUTTON = 'plus_list_input';
- const CLASSNAME_MODIFIED = 'plus_modified';
-
- const HTML_BOOK_COPY = '<span class="{C}">[复制]</span>'.replace('{C}', CLASSNAME_BUTTON);
- const HTML_BOOK_META = '{K}:{V}<span class="{C}">[复制]</span>'.replace('{C}', CLASSNAME_BUTTON);
- const HTML_BOOK_TAG = '<a class="{C}" href="{U}" target="_blank">{TN}</span>'.replace('{C}', CLASSNAME_BUTTON).replace('{U}', URL_TAGSEARCH);
- const HTML_DOWNLOAD_CONTENER = '<div id="dctn" style=\"margin:0px auto;overflow:hidden;\">\n<fieldset style=\"width:820px;height:35px;margin:0px auto;padding:0px;\">\n<legend><b>《{BOOKNAME}》小说TXT简繁全本下载</b></legend>\n</fieldset>\n</div>';
- const HTML_DOWNLOAD_LINKS_OLD = '<div id="txtfull" style="margin:0px auto;overflow:hidden;"><fieldset style="width:820px;height:35px;margin:0px auto;padding:0px;"><legend><b>《{ORIBOOKNAME}》小说TXT全本下载</b></legend><div style="width:16%; float:left; text-align:center;"><a href="http://dl.wenku8.com/down.php?type=txt&id={BOOKID}">G版原始下载</a></div><div style="width:16%; float:left; text-align:center;"><a href="http://dl.wenku8.com/down.php?type=txt&id={BOOKID}&fname={BOOKNAME}.txt">G版自动重命名</a></div><div style="width:16%; float:left; text-align:center;"><a href="http://dl.wenku8.com/down.php?type=utf8&id={BOOKID}">U版原始下载</a></div><div style="width:16%; float:left; text-align:center;"><a href="http://dl.wenku8.com/down.php?type=utf8&id={BOOKID}&fname={BOOKNAME}">U版自动重命名</a></div><div style="width:16%; float:left; text-align:center;"><a href="http://dl.wenku8.com/down.php?type=big5&id={BOOKID}">繁体原始下载</a></div><div style="width:16%; float:left; text-align:center;"><a href="http://dl.wenku8.com/down.php?type=big5&id={BOOKID}&fname={BOOKNAME}">繁体自动重命名</a></div></fieldset></div>'.replaceAll('{C}', CLASSNAME_BUTTON);
- const HTML_DOWNLOAD_LINKS = `<div style="margin:0px auto;overflow:hidden;"><fieldset style="width:820px;height:35px;margin:0px auto;padding:0px;"><legend><b>《{ORIBOOKNAME}》小说TXT、UMD、JAR电子书下载</b></legend><div style="width:210px; float:left; text-align:center;"><a href="https://${location.host}/modules/article/packshow.php?id={BOOKID}&type=txt{CHARSET}">TXT简繁分卷</a></div><div style="width:210px; float:left; text-align:center;"><a href="https://${location.host}/modules/article/packshow.php?id={BOOKID}&type=txtfull{CHARSET}">TXT简繁全本</a></div><div style="width:210px; float:left; text-align:center;"><a href="https://${location.host}/modules/article/packshow.php?id={BOOKID}&type=umd{CHARSET}">UMD分卷下载</a></div><div style="width:190px; float:left; text-align:center;"><a href="https://${location.host}/modules/article/packshow.php?id={BOOKID}&type=jar{CHARSET}">JAR分卷下载</a></div></fieldset></div>`;
- const HTML_DOWNLOAD_BOARD = '<span class="{C}">阅读与下载限制已解除</br>此功能仅供学习交流,请支持正版<span style="text-align: right;">——{N}</span></span>'.replace('{N}', GM_info.script.name).replace('{C}', CLASSNAME_TEXT);
- const CSS_DOWNLOAD = '.even {display: grid; grid-template-columns: repeat(3, 1fr); text-align: center;} .dlink {text-align: center;}';
- const CSS_PAGE_API = 'body>div {display: flex; align-items: center; justify-content: center;}';
- const CSS_COLOR_BTN_NORMAL = 'rgb(0, 160, 0)', CSS_COLOR_BTN_HOVER = 'rgb(0, 100, 0)', CSS_COLOR_FLOOR_MODIFIED = '#CCCCFF';
- const CSS_COMMON = '.{CT} {color: rgb(30, 100, 220) !important;} .{CB} {color: rgb(0, 160, 0) !important; cursor: pointer !important; user-select: none;} .{CB}:hover {color: rgb(0, 100, 0) !important;} .{CB}:focus {color: rgb(0, 100, 0) !important;} .{CB}.{CD} {color: rgba(150, 150, 150) !important; cursor: not-allowed !important;}'.replaceAll('{CB}', CLASSNAME_BUTTON).replaceAll('{CT}', CLASSNAME_TEXT).replaceAll('{CD}', CLASSNAME_DISABLED)
- + '.{CAT}>ul {list-style: none; text-align: center; padding: 0px; margin: 0px;} .{CAT} {position: absolute; zIndex: 999; backgroundColor: #f5f5f5; float: left; clear: both; height: 180px; overflow-y: auto; overflow-x: visible;} .{CLI} {display: block; list-style: outside none none; margin: 0px; border: 1px solid rgb(204, 204, 204);} .{CLB} {border: 0px; width: 100%; height: 100%; cursor: pointer; padding: 0 0.5em;}'.replaceAll('{CAT}', CLASSNAME_LIST).replaceAll('{CLI}', CLASSNAME_LIST_ITEM).replaceAll('{CLB}', CLASSNAME_LIST_BUTTON)
- + '.tippy-box[data-theme~="wenku_tip"] {background-color: #f0f7ff;color: black;border: 1px solid #a3bee8;}.tippy-box[data-theme~="wenku_tip"][data-placement^="top"]>.tippy-arrow::before {border-top-color: #a3bee8;}.tippy-box[data-theme~="wenku_tip"][data-placement^="left"]>.tippy-arrow::before {border-left-color: #a3bee8;}.tippy-box[data-theme~="wenku_tip"][data-placement^="right"]>.tippy-arrow::before {border-right-color: #a3bee8;}.tippy-box[data-theme~="wenku_tip"][data-placement^="bottom"]>.tippy-arrow::before {border-bottom-color: #a3bee8;}';
- const CSS_COMMONBEAUTIFIER = '.plus_cbty_image {position: fixed;top: 0;left: 0;z-index: -2;}.plus_cbty_cover {position: fixed;top: 0;left: calc((100vw - 960px) / 2);z-Index: -1;background-color: rgba(255,255,255,0.7);width: 960px;height: 100vh;}body {overflow: auto;}body>.main {position: relative;margin-left: 0;margin-right: 0;left: calc((100vw - 960px) / 2);}body.plus_cbty table.grid td, body.plus_cbty .odd, body.plus_cbty .even, body.plus_cbty .blockcontent {background-color: rgba(255,255,255,0) !important;}.textarea, .text {background-color: rgba(255,255,255,0.9);}#headlink{background-color: rgba(255,255,255,0.7);}';
- const CSS_REVIEWSHOW ='body {overflow: auto;background-image: url({BGI});}#content > table > tbody > tr > td {background-color: rgba(255,255,255,0.7) !important;overflow: auto;}body.plus_cbty #content > table > tbody > tr > td {background-color: rgba(255,255,255,0) !important;overflow: auto;}#content {height: 100vh;overflow: auto;}.m_top, .m_head, .main.nav, .m_foot {display: none;}.main {margin-top: 0px;}#content table div[style*="width:100%"]{font-size: calc(1em * {S}/ 100);line-height: 100%;}.jieqiQuote, .jieqiCode, .jieqiNote {font-size: inherit;}.{M}{background-color: {C}}'.replace('{M}', CLASSNAME_MODIFIED).replace('{C}', CSS_COLOR_FLOOR_MODIFIED);
- const CSS_NOVEL = 'html{background-image: url({BGI});}body {width: 100vw;height: 100vh;overflow: overlay;margin: 0px;background-color: rgba(255,255,255,0.7);}#contentmain {overflow-y: auto;height: calc(100vh - {H});max-width: 100%;min-width: 0px;max-width: 100vw;}#adv1, #adtop, #headlink, #footlink, #adbottom {overflow: overlay;min-width: 0px;max-width: 100vw;}#adv900, #adv5 {max-width: 100vw;}';
- const CSS_SIDEPANEL = '#sidepanel-panel {background-color: #00000000;z-index: 4000;}.sidepanel-button {font-size: 1vmin;color: #1E64DC;background-color: #FDFDFD;}.sidepanel-button:hover, .sidepanel-button.low-opacity:hover {opacity: 1;color: #FDFDFD;background-color: #1E64DC;}.sidepanel-button.low-opacity{opacity: 0.4 }.sidepanel-button>i[class^="fa-"] {line-height: 3vmin;width: 3vmin;}.sidepanel-button[class*="tooltip"]:hover::after {font-size: 0.9rem;top: calc((5vmin - 25px) / 2);}.sidepanel-button[class*="tooltip"]:hover::before {top: calc((5vmin - 12px) / 2);}.sidepanel-button.accept-pointer{pointer-events:auto;}';
-
- const ARR_GUI_BOOKCASE_WIDTH = ['3%', '19%', '9%', '25%', '20%', '9%', '5%', '10%'];
-
- const TEXT_TIP_COPY = '点击复制';
- const TEXT_TIP_COPIED = '已复制';
- const TEXT_TIP_SERVERCHANGE = '点击切换线路';
- const TEXT_TIP_API_PACKSHOW_LOADING = '正在初始化下载页面,请稍候...';
- const TEXT_TIP_API_PACKSHOW_LOADED = '初始化下载页面成功';
- const TEXT_TIP_INDEX_LATERREADS = '文库首页显示前六本稍后再读书目</br>您可以在书架页面管理稍后阅读书目和调整书籍顺序';
- const TEXT_TIP_SEARCH_OPTION_TAG = '有关标签搜索</br></br>未完善-开发中…</br>官方尚未正式开放此功能</br>功能预览由[轻小说文库+]提供';
- const TEXT_TIP_REVIEW_BEAUTIFUL = '背景图片可以在"用户面板"中设置</br>您可以从文库首页左侧点击进入用户面板';
- const TEXT_TIP_REVIEW_IMG_INSERTURL = '直接插入网络图片的链接地址';
- const TEXT_TIP_REVIEW_IMG_SELECTIMG = '选择本地图片上传到第三方图床,然后再插入图床提供的图片链接</br>您也可以直接拖拽图片到输入框,或者Ctrl+V直接粘贴您剪贴板里面的图片</br>您可以在用户面板中切换图床</br></br>上传图片请遵守法律以及图床使用规定</br>请不要上传违规图片';
- const TEXT_TIP_IMAGE_FIT = '请选择适合您的屏幕宽高比的图片</br>您选择的图片将会被拉伸以适应屏幕的宽高比,图片宽高比与屏幕宽高比相差过大会导致图片扭曲</br>请避免选择文件大小过大的图片,以防止浏览器卡顿';
- const TEXT_TIP_IMAGER_DEFAULT = '</br></br><span class=\'{CT}\'>{N} 默认图床</span>'.replace('{N}', GM_info.script.name).replace('{CT}', CLASSNAME_TEXT);
- const TEXT_TIP_DOWNLOAD_BBCODE = 'BBCODE格式:</br>即文库评论的代码格式</br>相当于引用楼层时自动填入回复框的内容</br>保存为此格式可以保留排版及多媒体信息';
- const TEXT_TIP_ACCOUNT_NOACCOUNT = '没有储存的账号信息</br>请在登录页面手动登录一次,相关帐号信息就会自动储存</br></br>所有储存的账号信息都自动保存在浏览器的本地存储中';
- const TEXT_ALT_SCRIPT_ERROR_AJAX_FA = 'FontAwesome加载失败(自动重试也失败了),可能会影响一部分脚本界面图标和样式的展示,但基本不会影响功能</br>您可以将此消息<a href="https://greasyfork.org/scripts/416310/feedback" class=\'{CB}\'>反馈给开发者</a>以尝试解决问题'.replace('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_DOWNLOAD_BBCODE_NOCHANGE = '帖子正在下载中,请不要更改此设置!';
- const TEXT_ALT_DOWNLOADFINISH_REVIEW = '{T}({I}) 已下载完毕</br>{N} 已保存';
- const TEXT_ALT_DOWNLOADIMG_CONFIRM_TITLE = '确认下载';
- const TEXT_ALT_DOWNLOADIMG_CONFIRM_MESSAGE = '是否要下载 {N} 的全部插图?';
- const TEXT_ALT_DOWNLOADIMG_CONFIRM_OK = '下载';
- const TEXT_ALT_DOWNLOADIMG_CONFIRM_CANCEL = '取消';
- const TEXT_ALT_DOWNLOADIMG_STATUS_INDEX = '正在获取小说目录...';
- const TEXT_ALT_DOWNLOADIMG_STATUS_LOADING = '正在下载: {CCUR}/{CALL}';
- const TEXT_ALT_DOWNLOADIMG_STATUS_FINISH = '全部插图下载完毕:)';
- const TEXT_ALT_BOOK_AFTERBOOKS_ADDED = '已添加到稍后再读';
- const TEXT_ALT_BOOK_AFTERBOOKS_REMOVED = '已将其从稍后再读中移除';
- const TEXT_ALT_BOOKCASE_AFTERBOOKS_MISSING = '看起来这本书并不在稍后再读的列表里呢</br>是不是已经在其他的标签页里把它从稍后再读中移除了?';
- const TEXT_ALT_BOOKCASE_AFTERBOOKS_V4BUG = '由于历史版本脚本的一个bug,您的<i>稍后再读</i>列表的小说排序被打乱了(非常抱歉)</br>而现在这个bug已经修复,<i>稍后再读</i>列表的小说排序也许需要您重新调整一次</br><span class="{CB}">[我知道了]</span>'.replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_AUTOREFRESH_ON = '页面自动刷新已开启';
- const TEXT_ALT_AUTOREFRESH_OFF = '页面自动刷新已关闭';
- const TEXT_ALT_AUTOREFRESH_NOTLAST = '请先翻到最后一页再开启页面自动刷新</br><span class="{CB}">[点击这里翻到最后一页]</span>'.replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_AUTOREFRESH_WORKING = '正在获取新的回复...';
- const TEXT_ALT_AUTOREFRESH_NOMORE = '木有新的回复';
- const TEXT_ALT_AUTOREFRESH_APPLIED = '发现了新的回复,页面已更新~</br>'.replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_AUTOREFRESH_MODIFIED = '发现已有楼层内容变更,已对其进行了颜色标记</br>点击标记区域即可恢复原来的颜色';
- const TEXT_ALT_BEAUTIFUL_ON = '页面美化已开启</br>您可能需要刷新页面使其生效';
- const TEXT_ALT_BEAUTIFUL_OFF = '页面美化已关闭</br>您可能需要刷新页面使其生效';
- const TEXT_ALT_FAVORITE_LAST_ON = '将在点击收藏的帖子时打开最后一页';
- const TEXT_ALT_FAVORITE_LAST_OFF = '将在点击收藏的帖子时打开第一页';
- const TEXT_ALT_IMAGE_FORMATERROR = '很遗憾,您选择的图片格式无法识别</br>(建议选择jpeg,png)!';
- const TEXT_ALT_IMAGE_UPLOAD_WORKING = '正在上传图片…';
- const TEXT_ALT_IMAGE_DOWNLOAD_WORKING = '正在下载图片…';
- const TEXT_ALT_IMAGE_UPLOAD_SUCCESS = '图片上传成功!</br>文件名: {NAME}</br>URL: {URL}';
- const TEXT_ALT_IMAGE_DOWNLOAD_SUCCESS = '图片下载成功!</br>已经将背景图片 {NAME} 保存在本地';
- const TEXT_ALT_IMAGE_RESPONSE_NONAME = '空(服务器没有返回文件名)';
- const TEXT_ALT_IMAGE_UPLOAD_ERROR = '上传错误!';
- const TEXT_ALT_TEXTSCALE_CHANGED = '字体缩放已保存:{S}%';
- const TEXT_ALT_CONFIG_EXPORTED = '配置文件已导出</br>文件名:{N}';
- const TEXT_ALT_CONFIG_IMPORTED = '配置文件已导入';
- const TEXT_ALT_IMAGER_RESET = '由于{O}已失效,您的图床已自动切换到{N}';
- const TEXT_ALT_IMAGER_NOAVAILBLE = '{O}已失效';
- const TEXT_ALT_META_COPIED = '{M} 已复制';
- const TEXT_ALT_ATRCMMDS_SAVED = '已保存:《{B}》</br>每日自动推荐{N}次</br>每日还可推荐{R}次';
- const TEXT_ALT_ATRCMMDS_INVALID = '未保存:{N}不是非负整数';
- const TEXT_ALT_ATRCMMDS_OVERFLOW = '注意:</br>您的用户信息显示您每天最多推荐{V}票</br>当前您已设置每日推荐合计{C}票</br><span class="{CB}">[单击此处以立即更新您的用户信息]</span>'.replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_ATRCMMDS_AUTO = '已开启自动推书';
- const TEXT_ALT_ATRCMMDS_NOAUTO = '已关闭自动推书';
- const TEXT_ALT_ATRCMMDS_ALL_START = '{S}:正在自动推书...'.replaceAll('{S}', GM_info.script.name);
- const TEXT_ALT_ATRCMMDS_RUNNING = '正在推荐书目:</br>{BN}({BID})';
- const TEXT_ALT_ATRCMMDS_DONE = '推荐完成:</br>{BN}({BID})';
- const TEXT_ALT_ATRCMMDS_ALL_DONE = '全部书目推荐完成:</br>{R}';
- const TEXT_ALT_ATRCMMDS_NOTASK = '木有要推荐的书目╮( ̄▽ ̄)╭';
- const TEXT_ALT_ATRCMMDS_NOTASK_OPENBC = '您还没有设置每日自动推荐的书目╮( ̄▽ ̄)╭</br><span class="{CB}">[点击此处打开书架页面进行设置]</span>'.replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_ATRCMMDS_NOTASK_PLSSET = '请在\'自动推书\'一栏设置每日推荐的书目及推荐次数';
- const TEXT_ALT_ATRCMMDS_MAXRCMMD = '根据您的头衔,您每日一共可以推荐{V}次';
- const TEXT_ALT_USRDTL_REFRESH = '{S}:正在更新用户信息({T})...'.replaceAll('{S}', GM_info.script.name).replaceAll('{T}', getTime());
- const TEXT_ALT_USRDTL_REFRESHED = '{S}:用户信息已更新</br><span class="{CB}">[点此查看详细信息]</span>'.replaceAll('{S}', GM_info.script.name).replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_POLYFILL = '<span class="{CT}">提示:正在使用移动端适配模式</span>'.replaceAll('{CT}', CLASSNAME_TEXT);
- const TEXT_ALT_LASTPAGE_LOADING = '正在获取最后一页,请稍候...';
- const TEXT_ALT_ACCOUNT_SWITCHED = '帐号已切换到 <i>"<span class="{CT}">{N}</span>"</i></br>3s后自动刷新页面</br><span class="{CB}">点击这里取消刷新</span>'.replaceAll('{CT}', CLASSNAME_TEXT).replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_ACCOUNT_WORKING_LOGOFF = '正在退出当前账号...';
- const TEXT_ALT_ACCOUNT_WORKING_LOGIN = '正在登录...';
- const TEXT_ALT_SCRIPT_UPDATE_CHECKING = '正在检查脚本更新...';
- const TEXT_ALT_SCRIPT_UPDATE_GOT = '<div class="{CT}">{SN} 有新版本啦!</br>新版本:{NV}</br>当前版本:{CV}</br><span id="script_update_info" class="{CB}">[点击此处 查看 更新]</span></br><span id="script_update_install" class="{CB}">[点击此处 安装 更新]</span></div>'.replaceAll('{CT}', CLASSNAME_TEXT).replaceAll('{CB}', CLASSNAME_BUTTON);
- const TEXT_ALT_SCRIPT_UPDATE_INFO = '更新信息';
- const TEXT_ALT_SCRIPT_UPDATE_NOINFO = '没有发现更新日志。。';
- const TEXT_ALT_SCRIPT_UPDATE_INSTALL = '安装';
- const TEXT_ALT_SCRIPT_UPDATE_CLOSE = '朕知道了';
- const TEXT_ALT_SCRIPT_UPDATE_NONE = '当前已是最新版本';
- const TEXT_ALT_DETAIL_IMPORTED = '配置导入成功';
- const TEXT_ALT_DETAIL_CONFIG_IMPORT_ERROR_SELECT = '您选择的文件不是配置文件,请检查后再试';
- const TEXT_ALT_DETAIL_CONFIG_IMPORT_ERROR_READ = '配置文件读取出错,请检查是否粘贴了正确的配置文件,以及配置文件是否损坏';
- const TEXT_ALT_DETAIL_MANAGE_NOTFOUND = '该记录已不存在,您是否已经在其他标签页删除它了呢?';
- const TEXT_GUI_API_ADDBOOKCASE_TOBOOKCASE = '进入书架';
- const TEXT_GUI_API_ADDBOOKCASE_REMOVE = '移出本书';
- const TEXT_GUI_API_PACKSHOW_TITLE_LOADING = '初始化下载界面...';
- const TEXT_GUI_API_PACKSHOW_TITLE = '{N} 轻小说TXT分卷下载 - 轻小说文库';
- const TEXT_GUI_UNKNOWN = '未知';
- const TEXT_GUI_DOWNLOAD_THISVOLUME = '下载本卷';
- const TEXT_GUI_DOWNLOAD_THISCHAPTER = '下载本章';
- const TEXT_GUI_NOVEL_FILLING = '</br><span class="{CT}">[轻小说文库+] 正在获取章节内容...</span>'.replaceAll('{CT}', CLASSNAME_TEXT);
- const TEXT_GUI_BOOK_IMAGESDOWNLOAD = '全部插图下载';
- const TEXT_GUI_BOOK_READITLATER = '稍后再读';
- const TEXT_GUI_BOOK_DONTREADLATER = '移出稍后再读';
- const TEXT_GUI_REVIEW_ADDFAVORITE = '收藏本帖:';
- const TEXT_GUI_REVIEW_FAVORADDED = '已收藏 {N}';
- const TEXT_GUI_REVIEW_FAVORDELED = '已从收藏中移除 {N}';
- const TEXT_GUI_REVIEW_BEAUTIFUL = '页面美化:';
- const TEXT_GUI_REVEIW_IMG_INSERTURL = '插入网图链接';
- const TEXT_GUI_REVEIW_IMG_SELECTIMG = '选择本地图片';
- const TEXT_GUI_REVIEW_UNLOCK_WARNING = '<span style="color: red;">仅供测试使用,请勿滥用此功能!</span>';
- const TEXT_GUI_DOWNLOAD_REVIEW = '[下载本帖(共A页)]';
- const TEXT_GUI_DOWNLOADING_REVIEW = '[下载中...(C/A)]';
- const TEXT_GUI_DOWNLOAD_BBCODE = '保存为BBCODE格式:';
- const TEXT_GUI_DOWNLOADFINISH_REVIEW = '[下载完毕]';
- const TEXT_GUI_DOWNLOADALL = '下载全部分卷,请点击右边的按钮:';
- const TEXT_GUI_WAITING = ' 等待中...';
- const TEXT_GUI_DOWNLOADING = ' 下载中...';
- const TEXT_GUI_DOWNLOADED = ' (下载完毕)';
- const TEXT_GUI_NOTHINGHERE = '<span style="color:grey">-Nothing Here-</span>';
- const TEXT_GUI_SDOWNLOAD = '地址三(程序重命名)';
- const TEXT_GUI_SDOWNLOAD_FILENAME = '{NovelName} {VolumeName}.{Extension}';
- const TEXT_GUI_DOWNLOADING_ALL = '下载中...(C/A)';
- const TEXT_GUI_DOWNLOADED_ALL = '下载图片(已完成)';
- const TEXT_GUI_AUTOREFRESH = '自动更新页面:';
- const TEXT_GUI_AUTOREFRESH_PAUSED = '(回复编辑中,暂停刷新)';
- const TEXT_GUI_AUTOSAVE = '(您输入的内容已保存到书评草稿中)';
- const TEXT_GUI_AUTOSAVE_CLEAR = '(草稿为空)';
- const TEXT_GUI_AUTOSAVE_RESTORE = '(已从书评草稿中恢复了您上次编辑的内容)';
- const TEXT_GUI_AREAREPLY_AT = '想用@提到谁?';
- const TEXT_GUI_INDEX_FAVORITES = '收藏的书评';
- const TEXT_GUI_INDEX_STATUS = '{S} 正在运行,版本 {V}。'.replace('{S}', GM_info.script.name).replace('{V}', GM_info.script.version);
- const TEXT_GUI_INDEX_LATERBOOKS = '稍后再读';
- const TEXT_GUI_BOOKCASE_GETTING = '正在搬运书架...(C/A)';
- const TEXT_GUI_BOOKCASE_TOPTITLE = '您的书架可收藏 A 本,已收藏 B 本';
- const TEXT_GUI_BOOKCASE_MOVEBOOK = '移动到 [N]';
- const TEXT_GUI_BOOKCASE_DBLCLICK = '双击/长按我,给我取一个好听的名字吧~';
- const TEXT_GUI_BOOKCASE_WHATNAME = '呜呜呜~会是什么名字呢?';
- const TEXT_GUI_BOOKCASE_ATRCMMD = '自动推书';
- const TEXT_GUI_BOOKCASE_RCMMDAT = '<span>每日自动推书:</span>';
- const TEXT_GUI_BOOKCASE_RCMMDNW = '立即推书';
- const TEXT_GUI_BOOKCASE_RCMMDNW_DONE = '今日推书已完成';
- const TEXT_GUI_BOOKCASE_RCMMDNW_NOTYET = '今日尚未推书';
- const TEXT_GUI_BOOKCASE_RCMMDNW_NOTASK = '您还没有设置自动推书';
- const TEXT_GUI_BOOKCASE_RCMMDNW_CONFIRM = '今天已经推过书了,是否要再推一遍?';
- const TEXT_GUI_SEARCH_OPTION_TAG = '标签(preview)';
- const TEXT_GUI_DETAIL_TITLE_SETTINGS = '脚本设置';
- const TEXT_GUI_DETAIL_TITLE_BGI = '页面美化背景图片';
- const TEXT_GUI_DETAIL_DEFAULT_BGI = '点击选择图片 / 拖拽图片到此处 / Ctrl+V粘贴剪贴板中的图片';
- const TEXT_GUI_DETAIL_BGI = '当前图片:{N}';
- const TEXT_GUI_DETAIL_BGI_WORKING = '处理中...';
- const TEXT_GUI_DETAIL_BGI_UPLOADING = '正在上传: {NAME}';
- const TEXT_GUI_DETAIL_BGI_UPLOADFAILED = '{NAME}(上传失败,已本地保存)';
- const TEXT_GUI_DETAIL_BGI_DOWNLOADING = '正在下载: {NAME}';
- const TEXT_GUI_DETAIL_BGI_UPLOAD = '上传图片到图床以防止卡顿';
- const TEXT_GUI_DETAIL_BGI_LEGAL = '上传图片请遵守法律以及图床使用规定</br>请不要上传违规图片';
- const TEXT_GUI_DETAIL_GUI_IMAGER = '图床选择';
- const TEXT_GUI_DETAIL_GUI_SCALE = '书评字体缩放';
- const TEXT_GUI_DETAIL_BTF_NOVEL = '阅读页面美化';
- const TEXT_GUI_DETAIL_BTF_REVIEW = '书评页面美化';
- const TEXT_GUI_DETAIL_BTF_COMMON = '其他页面美化';
- const TEXT_GUI_DETAIL_FVR_LASTPAGE = '点击收藏的帖子时打开最后一页';
- const TEXT_GUI_DETAIL_VERSION_CURVER = '当前版本';
- const TEXT_GUI_DETAIL_VERSION_CHECKUPDATE = '检查更新';
- const TEXT_GUI_DETAIL_VERSION_CHECK = '点击此处检查更新';
- const TEXT_GUI_DETAIL_CONFIG_EXPORT = '导出所有脚本配置到文件(包含账号密码)';
- const TEXT_GUI_DETAIL_CONFIG_EXPORT_NOPASS = '导出所有脚本配置到文件(不包含账号密码)';
- const TEXT_GUI_DETAIL_EXPORT_CLICK = '点击导出';
- const TEXT_GUI_DETAIL_CONFIG_IMPORT = '从文件导入脚本配置';
- const TEXT_GUI_DETAIL_IMPORT_CLICK = '点击导入 / 拖拽配置文件到此处 / Ctrl+V粘贴剪贴板中的配置文件,并刷新页面';
- const TEXT_GUI_DETAIL_FEEDBACK_TITLE = '提出反馈';
- const TEXT_GUI_DETAIL_FEEDBACK = '点击打开反馈页面';
- const TEXT_GUI_DETAIL_UPDATEINFO_TITLE = '更新日志';
- const TEXT_GUI_DETAIL_UPDATEINFO = '点击去主页查看';
- const TEXT_GUI_DETAIL_CONFIG_MANAGE = '管理存储的信息';
- const TEXT_GUI_DETAIL_CONFIG_MANAGE_EMPTY = '<span style="color:grey;">没有内容</span>';
- const TEXT_GUI_DETAIL_CONFIG_MANAGE_MORE = '<span style="color:grey;">…</span>';
- const TEXT_GUI_DETAIL_MANAGE_CLICK = '点击打开管理页面';
- const TEXT_GUI_DETAIL_MANAGE_HEADER = '脚本储存管理';
- const TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_BTN_OPEN = '打开';
- const TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_BTN_NOTE = '备注';
- const TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_BTN_DELETE = '删除';
- const TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_TIP = '为{TITLE}设置备注: </br>备注将在主页鼠标经过此帖子收藏的链接时悬浮显示';
- const TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_TITLE = '编辑备注';
- const TEXT_GUI_DETAIL_MANAGE_FAV_DELETE_TIP = '确认将{TITLE}移除收藏?';
- const TEXT_GUI_DETAIL_MANAGE_FAV_DELETE_TITLE = '移除收藏';
- const TEXT_GUI_DETAIL_MANAGE_FAV_SAVED = '已保存';
- const TEXT_GUI_DETAIL_MANAGE_FAV_DELETED = '已删除';
- const TEXT_GUI_DETAIL_CONFIG_IMPORT_CONFIRM_SELECT = '是否要将您粘贴的图片({N})中设置为页面美化背景图片?';
- const TEXT_GUI_DETAIL_CONFIG_IMPORT_CONFIRM_PASTE = '是否要从您粘贴的配置文件({N})中导入配置?\n建议先备份您当前的配置,再导入新配置';
- const TEXT_GUI_BLOCK_TITLE_DEFULT = '操作区域';
- const TEXT_GUI_USER_REVIEWSEARCH = '用户书评';
- const TEXT_GUI_USER_USERINFO = '详细资料';
- const TEXT_GUI_USER_USERREMARKEDIT = '编辑备注';
- const TEXT_GUI_USER_USERREMARKSHOW = '用户备注:';
- const TEXT_GUI_USER_USERREMARKEMPTY = '假装这里有个备注';
- const TEXT_GUI_USER_USERREMARKEDIT_TITLE = '编辑备注';
- const TEXT_GUI_USER_USERREMARKEDIT_MSG = '设置 [{N}] 的备注为:';
- const TEXT_GUI_LINK_TOLASTPAGE = '[打开尾页]';
- const TEXT_GUI_ACCOUNT_SWITCH = '切换账号:';
- const TEXT_GUI_ACCOUNT_CONFIRM = '是否要切换到帐号 "{N}"?';
- const TEXT_GUI_ACCOUNT_NOACCOUNT = '(帐号列表为空)';
- const TEXT_GUI_ACCOUNT_NOTLOGGEDIN = '(没有登录信息)';
-
- // Emoji smiles (not used in the script yet)
- const SmList =
- [{text:"/:O",id:"1",alt:"惊讶"}, {text:"/:~",id:"2",alt:"撇嘴"}, {text:"/:*",id:"3",alt:"色色"},
- {text:"/:|",id:"4",alt:"发呆"}, {text:"/8-)",id:"5",alt:"得意"}, {text:"/:LL",id:"6",alt:"流泪"},
- {text:"/:$",id:"7",alt:"害羞"}, {text:"/:X",id:"8",alt:"闭嘴"}, {text:"/:Z",id:"9",alt:"睡觉"},
- {text:"/:`(",id:"10",alt:"大哭"}, {text:"/:-",id:"11",alt:"尴尬"}, {text:"/:@",id:"12",alt:"发怒"},
- {text:"/:P",id:"13",alt:"调皮"}, {text:"/:D",id:"14",alt:"呲牙"}, {text:"/:)",id:"15",alt:"微笑"},
- {text:"/:(",id:"16",alt:"难过"}, {text:"/:+",id:"17",alt:"耍酷"}, {text:"/:#",id:"18",alt:"禁言"},
- {text:"/:Q",id:"19",alt:"抓狂"}, {text:"/:T",id:"20",alt:"呕吐"}]
-
- /* \t
- ┌┬┐┌─┐┏┳┓┏━┓╭─╮
- ├┼┤│┼│┣╋┫┃╋┃│╳│
- └┴┘└─┘┗┻┛┗━┛╰─╯
- ╲╱╭╮
- ╱╲╰╯
- */
- /* **output format: Review Name.txt**
- ** 轻小说文库-帖子 [ID: reviewid]
- ** title
- ** 保存自: reviewlink
- ** 保存时间: savetime
- ** By scriptname Ver. version, author authorname
- **
- ** ──────────────────────────────
- ** [用户: username userid]
- ** 用户名: username
- ** 用户ID: userid
- ** 加入日期: 1970-01-01
- ** 用户链接: userlink
- ** 最早出现: 1楼
- ** ──────────────────────────────
- ** ...
- ** ──────────────────────────────
- ** [#1 2021-04-26 17:53:49] [username userid]
- ** ──────────────────────────────
- ** content - line 1
- ** content - line 2
- ** content - line 3
- ** ──────────────────────────────
- **
- ** ──────────────────────────────
- ** [#2 2021-04-26 19:28:08] [username userid]
- ** ──────────────────────────────
- ** content - line 1
- ** content - line 2
- ** content - line 3
- ** ──────────────────────────────
- **
- ** ...
- **
- **
- ** [THE END]
- */
- const TEXT_SPLIT_LINE_CHAR = '━'; const TEXT_SPLIT_LINE = TEXT_SPLIT_LINE_CHAR.repeat(20)
- const TEXT_OUTPUT_REVIEW_HEAD =
- '轻小说文库-帖子 [ID: {RWID}]\n{RWTT}\n保存自: {RWLK}\n保存时间: {SVTM}\nBy {SCNM} Ver. {VRSN}, author {ATNM}'
- const TEXT_OUTPUT_REVIEW_USER =
- '{LNSPLT}\n[用户: {USERNM} {USERID}]\n用户名: {USERNM}\n用户ID: {USERID}\n加入日期: {USERJT}\n用户链接: {USERLK}\n最早出现: {USERFL}楼\n{LNSPLT}'
- const TEXT_OUTPUT_REVIEW_FLOOR =
- '{LNSPLT}\n[#{RPNUMB} {RPTIME}] [{USERNM} {USERID}]\n{LNSPLT}\n{RPTEXT}\n{LNSPLT}';
- const TEXT_OUTPUT_REVIEW_END = '\n[THE END]';
-
- // Arguments: level=LogLevel.Info, logContent, asObject=false
- // Needs one call "DoLog();" to get it initialized before using it!
- function DoLog() {
- // Global log levels set
- unsafeWindow.LogLevel = {
- None: 0,
- Error: 1,
- Success: 2,
- Warning: 3,
- Info: 4,
- }
- unsafeWindow.LogLevelMap = {};
- unsafeWindow.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
- unsafeWindow.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
- unsafeWindow.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
- unsafeWindow.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
- unsafeWindow.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
- unsafeWindow.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
-
- // Current log level
- DoLog.logLevel = (unsafeWindow ? unsafeWindow.isPY_DNG : window.isPY_DNG) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
-
- // Log counter
- DoLog.logCount === undefined && (DoLog.logCount = 0);
- if (++DoLog.logCount > 512) {
- console.clear();
- DoLog.logCount = 0;
- }
-
- // Get args
- let level, logContent, asObject;
- switch (arguments.length) {
- case 1:
- level = LogLevel.Info;
- logContent = arguments[0];
- asObject = false;
- break;
- case 2:
- level = arguments[0];
- logContent = arguments[1];
- asObject = false;
- break;
- case 3:
- level = arguments[0];
- logContent = arguments[1];
- asObject = arguments[2];
- break;
- default:
- level = LogLevel.Info;
- logContent = 'DoLog initialized.';
- asObject = false;
- break;
- }
-
- // Log when log level permits
- if (level <= DoLog.logLevel) {
- let msg = '%c' + LogLevelMap[level].prefix;
- let subst = LogLevelMap[level].color;
-
- if (asObject) {
- msg += ' %o';
- } else {
- switch(typeof(logContent)) {
- case 'string': msg += ' %s'; break;
- case 'number': msg += ' %d'; break;
- case 'object': msg += ' %o'; break;
- }
- }
-
- console.log(msg, subst, logContent);
- }
- }
- DoLog();
-
- let tipready, CONFIG, TASK, DMode, SPanel, AndAPI
- let API
- main();
-
- // Main
- function main() {
- // Get tab url api part
- API = window.location.href.replace(/https?:\/\/www\.wenku8\.(net|cc)\//, '').replace(/\?.*/, '').replace(/#.*/, '')
- .replace(/^book\/\d+\.html?/, 'book').replace(/novel\/(\d+\/?)+\.html?$/, 'novel')
- .replace(/^novel[\/\d]+index\.html?$/, 'novelindex');
-
- // Common actions
- loadinResourceCSS();
- loadinFontAwesome();
- polyfillAlert();
- tipready = tipcheck();
- tipscroll();
- addStyle(CSS_COMMON);
- GMXHRHook(NUMBER_MAX_XHR);
- CONFIG = new configManager();
- TASK = new taskManager();
- AndAPI = new AndroidAPI();
- //DMode = new Darkmode({autoMatchOsTheme: false});
- formSearch();
- linkReview();
- multiAccount();
- commonBeautify(API);
- SPanel = sideFunctions();
- unsafeWindow.alertify = alertify;
- alertify.set('notifier','position', 'top-right');
-
- if (isAPIPage()) {
- if (!pageAPI(API)) {
- return;
- }
- }
- if (!API) {
- location.href = `https://${location.host}/index.php`;
- return;
- };
- switch (API) {
- // Dwonload page
- case 'modules/article/packshow.php':
- pageDownload();
- break;
- // ReviewList page
- case 'modules/article/reviews.php':
- areaReply();
- break;
- // Review page
- case 'modules/article/reviewshow.php':
- areaReply();
- pageReview();
- break;
- // ReviewEdit page
- case 'modules/article/reviewedit.php':
- areaReply();
- pageReviewedit();
- break;
- // Bookcase page
- case 'modules/article/bookcase.php':
- pageBookcase();
- break;
- // Tags page
- case 'modules/article/tags.php':
- pageTags();
- break;
- // Mylink page
- case 'mylink.php':
- pageMylink();
- break;
- case 'userpage.php':
- pageUser();
- break;
- // Detail page
- case 'userdetail.php':
- pageDetail();
- break;
- // Index page
- case 'index.php':
- pageIndex();
- break;
- // Book page
- // Also: https://www.wenku8.net/modules/article/articleinfo.php?id={ID}&charset=gbk
- case 'modules/article/articleinfo.php':
- case 'book':
- pageBook();
- break;
- // Novel index page
- case 'novelindex':
- pageNovelIndex();
- break;
- // Novel page
- case 'novel':
- pageNovel();
- break;
- // Novel index page & novel page
- case 'modules/article/reader.php':
- chapter_id === '0' ? pageNovelIndex() : pageNovel();
- break;
- // Login page
- case 'login.php':
- pageLogin();
- break;
- // Other pages
- default:
- DoLog(LogLevel.Info, API);
- }
- }
-
- // Autorun tasks
- // use 'new' keyword
- function taskManager() {
- const TM = this;
-
- // UserDetail refresh
- TM.UserDetail = {
- // Refresh userDetail storage everyday
- refresh: function() {
- // Time check: whether recommend has done today
- if (getMyUserDetail().lasttime === getTime('-', false)) {return false;};
- refreshMyUserDetail();
- }
- }
-
- // Auto-recommend
- TM.AutoRecommend = {
-
- // Check if recommend has done
- checkRcmmd: function() {
- const arConfig = CONFIG.AutoRecommend.getConfig();
- return arConfig.lasttime === getTime('-', false);
- },
-
- // Auto recommend main function
- run: function(recommendAnyway=false) {
- let i;
-
- // Get config
- const arConfig = CONFIG.AutoRecommend.getConfig();
-
- // Time check: whether all recommends has done today
- if (TM.AutoRecommend.checkRcmmd() && !recommendAnyway) {return false;};
-
- // Config check: whether we need to auto-recommend
- if (!arConfig.auto && !recommendAnyway) {return false;}
-
- // Config check: whether the recommend list is empty
- if (arConfig.allCount === 0) {
- const altBox = alertify.notify(
- /modules\/article\/bookcase\.php$/.test(location.href) ?
- TEXT_ALT_ATRCMMDS_NOTASK_PLSSET + (getMyUserDetail().userDetail ? '</br>'+TEXT_ALT_ATRCMMDS_MAXRCMMD.replace('{V}', String(getMyUserDetail().userDetail.vote)) : '') :
- TEXT_ALT_ATRCMMDS_NOTASK_OPENBC
- );
- altBox.callback = (isClicked) => {
- isClicked && window.open(URL_BOOKCASE);
- }
- return false;
- };
-
- // Recommend for each
- let recommended = {}, AM = new AsyncManager();
- AM.onfinish = allFinish;
-
- alertify.notify(TEXT_ALT_ATRCMMDS_ALL_START);
- for (const strBookID in arConfig.books) {
- // Only when inherited properties exists must we use hasOwnProperty()
- // here we know there is no inherited properties
- const book = arConfig.books[strBookID]
- const number = book.number;
- const bookID = book.id;
- const bookName = book.name;
-
- // Time check: whether this book's recommend has done today
- if (book.lasttime === getTime('-', false) && !recommendAnyway) {continue;};
-
- // Soft alert
- //alertify.notify(TEXT_ALT_ATRCMMDS_RUNNING.replaceAll('{BN}', bookName).replaceAll('{BID}', strBookID));
-
- // Go work
- for (i = 0; i < number; i++) {
- AM.add();
- getDocument(URL_RECOMMEND.replaceAll('{B}', strBookID), bookFinish,[book, strBookID, bookName]);
- }
-
- // Soft alert
- //alertify.notify(TEXT_ALT_ATRCMMDS_DONE.replaceAll('{BN}', bookName).replaceAll('{BID}', strBookID));
- }
- AM.finishEvent = true;
- return true;
-
- function bookFinish(oDoc, book, strBookID, bookName) {
- // title: "处理成功"
- const statusText = $(oDoc, '.blocktitle').innerText;
- // success: "我们已经记录了本次推荐,感谢您的参与!\n\n您每天拥有 5 次推荐权利,这是您今天第 1 次推荐。"
- // overflow: "\n错误原因:对不起,您今天已经用完了推荐的权利!\n\n您每天可以推荐 20 次。\n\n请 返 回 并修正"
- const returnText = $(oDoc, '.blockcontent').innerText.replace(/\s*\[.+\]\s*$/, '');
-
- // Save book
- book.lasttime = getTime('-', false);
- CONFIG.AutoRecommend.saveConfig(arConfig);
-
- // Log
- DoLog(statusText + '\n' + returnText);
-
- /*
- // Check status
- const success = /我们已经记录了本次推荐,感谢您的参与!\s*您每天拥有\s*(\d+)\s*次推荐权利,这是您今天第\s*(\d+)\s*次推荐。/;
- const overflow = /\s*错误原因:对不起,您今天已经用完了推荐的权利!\s*您每天可以推荐\s*(\d+)\s*次。\s*请\s*返\s*回\s*并修正/;
- */
- const b = recommended[strBookID] = recommended[strBookID] || {name: bookName, strID: strBookID, count: 0};
- b.count++;
- AM.finish();
- }
-
- function allFinish() {
- // Save config
- arConfig.lasttime = getTime('-', false);
- CONFIG.AutoRecommend.saveConfig(arConfig);
-
- // Soft alert
- let text = [];
- for (const strBookID of Object.keys(recommended)) {
- const book = recommended[strBookID];
- text.push('[{BID}]{BN} 推荐了{C}次'.replaceAll('{C}', book.count).replaceAll('{BID}', book.strID).replaceAll('{BN}', book.name));
- }
- alertify.success(TEXT_ALT_ATRCMMDS_ALL_DONE.replaceAll('{R}', text.join('</br>')));
- }
- }
- }
-
- // Config Maintainer
- TM.Cleaner = {
- cleanPageStatus: function() {
- const config = CONFIG.BkReviewPrefs.getConfig();
- const history = config.history;
- let count = 0;
- for (const [rid, his] of Object.entries(history)) {
- if (!his.time || (new Date()).getTime() - his.time > 30*1000) {
- delete history[rid];
- count++;
- }
- }
- CONFIG.BkReviewPrefs.saveConfig(config);
- DoLog(count > 0 ? LogLevel.Success : LogLevel.Info, 'Review page status cleaned ({C})'.replace('{C}', count.toString()));
- },
-
- imagerFix: function() {
- const config = CONFIG.UserGlobalCfg.getConfig();
- const curimager = config.imager;
-
- // If imager does not exist or imager disabled, change it to default
- if (!DATA_IMAGERS[curimager] || !DATA_IMAGERS[curimager].available) {
- DoLog(LogLevel.Warning, 'Current imager unavailable, changing to default.');
- if (curimager !== DATA_IMAGERS.default && DATA_IMAGERS[DATA_IMAGERS.default].available) {
- // Default available
- config.imager = DATA_IMAGERS.default;
- DoLog(LogLevel.Success, 'Changed to default.');
- } else {
- // Default not available
- DoLog(LogLevel.Warning, 'Default imager unavailable, trying to find another imager for use. ')
- for (const [key, imager] of Object.entries(DATA_IMAGERS)) {
- if (imager.available) {
- config.imager = key;
- DoLog(LogLevel.Success, 'Changed to {K}.'.replace('{K}', key));
- break;
- }
- }
-
- if (config.imager === curimager) {
- // OMG, There's NO IMAGER AVAILABLE!!
- DoLog(LogLevel.Error, 'OMG, There\'s NO IMAGER AVAILABLE!!');
- }
- }
-
- CONFIG.UserGlobalCfg.saveConfig(config);
- alertify.warning((config.imager !== curimager ? TEXT_ALT_IMAGER_RESET : TEXT_ALT_IMAGER_NOAVAILBLE).replace('{O}', DATA_IMAGERS[curimager].name).replace('{N}', DATA_IMAGERS[config.imager].name));
- }
- },
- }
-
- // Script
- TM.Script = {
- // Check & Update to latest version of script
- update: function(force=false) {
- // Check for update once a day
- const scriptID = 416310;
- const config = CONFIG.GlobalConfig.getConfig();
- if (!force && config.scriptUpdate.lasttime === getTime('-', false)) {return false;}
-
- const GFU = new GreasyForkUpdater();
- alertify.notify(TEXT_ALT_SCRIPT_UPDATE_CHECKING);
- GFU.checkUpdate(scriptID, GM_info.script.version, function(update, updateurl, metaData) {
- if (update) {
- const box = alertify.notify(TEXT_ALT_SCRIPT_UPDATE_GOT.replaceAll('{SN}', metaData.name).replaceAll('{NV}', metaData.version).replaceAll('{CV}', GM_info.script.version));
- const btnInfo = $(box.element, '#script_update_info');
- const btnInstall = $(box.element, '#script_update_install');
- btnInfo.addEventListener('click', show);
- btnInstall.addEventListener('click', install);
- } else {
- alertify.message(TEXT_ALT_SCRIPT_UPDATE_NONE);
- }
- config.scriptUpdate.lasttime = getTime('-', false);
- CONFIG.GlobalConfig.saveConfig(config);
-
- function install(e) {
- location.href = updateurl;
- }
-
- function show(e) {
- const info = metaData.updateinfo;
- const box = alertify.confirm(info ? info : TEXT_ALT_SCRIPT_UPDATE_NOINFO, install);
- box.setHeader(TEXT_ALT_SCRIPT_UPDATE_INFO);
- box.set('labels', {ok: TEXT_ALT_SCRIPT_UPDATE_INSTALL, cancel: TEXT_ALT_SCRIPT_UPDATE_CLOSE});
- box.set('overflow', true);
- }
- });
-
- return true;
- }
- }
-
- TM.Script.update();
- TM.Cleaner.cleanPageStatus();
- TM.Cleaner.imagerFix();
- TM.UserDetail.refresh();
- TM.AutoRecommend.run();
- }
-
- // Config Manager
- // use 'new' keyword
- function configManager() {
- const CM = this;
- const [getValue, setValue, deleteValue, listValues] = [
- window.getValue ? window.getValue : GM_getValue,
- window.setValue ? window.setValue : GM_setValue,
- window.deleteValue ? window.deleteValue : GM_deleteValue,
- window.listValues ? window.listValues : GM_listValues,
- ]
-
- CM.GlobalConfig = {
- saveConfig: function(config) {
- config ? config[KEY_CM_VERSION] = VALUE_CM_VERSION : function() {};
- setValue(KEY_CM, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {
- users: {},
- scriptUpdate: {
- lasttime: ''
- }
- };
-
- config = func ? func(config) : config;
- save ? CM.GlobalConfig.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = getValue(KEY_CM, null);
- config = config ? config : (init ? CM.GlobalConfig.initConfig(true, init) : CM.GlobalConfig.initConfig());
- return config;
- },
-
- // Review config upgrade (Uses GM_functions)
- upgradeConfig: function() {
- // Get version
- const default_self = {}; default_self[KEY_CM_VERSION] = '0.1'; // v0.1 has no self object
- const self = GM_getValue(KEY_CM, default_self);
- const version = self[KEY_CM_VERSION];
-
- // Upgrade by version
- if (self[KEY_CM_VERSION] === VALUE_CM_VERSION) {DoLog(LogLevel.Info, 'Config Manager self config is in latest version. ');};
- switch(version) {
- case '0.1':
- v01_To_v02();
- v02_To_v03();
- logUpgrade();
- break;
- case '0.2':
- v02_To_v03();
- logUpgrade();
- break;
- }
-
- // Save to global gm_storage
- self[KEY_CM_VERSION] = VALUE_CM_VERSION;
- setValue(KEY_CM, self);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'Config Manager self config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', version).replaceAll('{V2}', VALUE_CM_VERSION));
- }
-
- function v01_To_v02() {
- const props = GM_listValues();
- const userStorage = {};
- for (const prop of props) {
- userStorage[prop] = GM_getValue(prop);
- }
- const userID = getUserID();
- userID ? GM_setValue(userID, userStorage) : GM_setValue('temp', userStorage);
- for (const prop of props) {
- GM_deleteValue(prop);
- }
- }
-
- function v02_To_v03() {
- self.scriptUpdate = self.scriptUpdate ? self.scriptUpdate : {lasttime: ''};
- }
- },
-
- // Redirect global gm_storage to user's storage area (Uses GM_functions)
- // callback(key)
- redirectToUser: function (callback) {
- // Get userID from cookies
- const userID = getUserID();
-
- if (userID) {
- // delete temp data if exist
- GM_deleteValue('temp');
-
- // Save lastUserID
- const config = CM.GlobalConfig.getConfig();
- config.lastUserID = userID;
- CM.GlobalConfig.saveConfig(config);
-
- // Redirect to user storage area
- redirectGMStorage(userID);
- DoLog(LogLevel.Info, 'GM_storage redirected to ' + String(userID));
- } else {
- // Redirect to temp storage area before request finish
- const lastUserID = CM.GlobalConfig.getConfig().lastUserID;
- redirectTemp(lastUserID);
-
- // Request userID
- getMyUserDetail((userDetail)=>{
- const key = userDetail.userDetail.userID;
-
- // Move temp data to user storage area
- redirectGMStorage();
- const tempStorage = GM_getValue('temp');
- GM_setValue(lastUserID ? lastUserID : key, tempStorage);
- GM_deleteValue('temp');
-
- // Save lastUserID
- const config = CM.GlobalConfig.getConfig();
- config.lastUserID = key;
- CM.GlobalConfig.saveConfig(config);
-
- // Redirect to user storage area
- redirectGMStorage(key);
- DoLog(LogLevel.Info, 'GM_storage redirected to ' + String(key));
-
- // callback
- callback ? callback(key) : function() {};
- })
- }
-
- // When userID request not finished, use 'temp' as gm_storage key
- function redirectTemp(lastUserID) {
- if (lastUserID) {
- // Copy config of the user we use last time to 'temp' storage area
- const lastUser = GM_getValue(lastUserID, {});
- GM_setValue('temp', lastUser);
- }
- redirectGMStorage('temp');
- DoLog(LogLevel.Info, 'GM_storage redirected to temp');
- }
- }
- }
-
- CM.GlobalConfig.upgradeConfig();
- CM.GlobalConfig.redirectToUser();
-
- CM.AutoRecommend = {
- saveConfig: function(config) {
- config ? config[KEY_ATRCMMDS_VERSION] = VALUE_ATRCMMDS_VERSION : function() {};
- GM_setValue(KEY_ATRCMMDS, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {};
- config[KEY_ATRCMMDS_VERSION] = VALUE_ATRCMMDS_VERSION;
- config.allCount = 0;
- config.books = {};
- config.auto = true;
-
- config = func ? func(config) : config;
- save ? CM.AutoRecommend.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_ATRCMMDS, null);
- config = config ? config : (init ? CM.AutoRecommend.initConfig(true, init) : CM.AutoRecommend.initConfig());
- return config;
- },
-
- // Auto-recommend config upgrade
- upgradeConfig: function() {
- // Get config
- const config = CM.AutoRecommend.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- switch (config[KEY_ATRCMMDS_VERSION]) {
- case '0.1':
- config.auto = true;
- logUpgrade();
- break;
- case VALUE_ATRCMMDS_VERSION:
- DoLog(LogLevel.Info, 'Auto-recommend config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for Auto-recommend. '.replace('{V}', config[KEY_ATRCMMDS_VERSION]));
- }
-
- // Save to gm_storage
- CM.AutoRecommend.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'Auto-recommend config successfully upgraded From v{V1} to {V2}. '.replaceAll('{V1}', config[KEY_ATRCMMDS_VERSION]).replaceAll('{V2}', VALUE_ATRCMMDS_VERSION));
- }
- }
- }
-
- CM.commentDrafts = {
- saveConfig: function(config) {
- config ? config[KEY_DRAFT_VERSION] = VALUE_DRAFT_VERSION : function() {};
- GM_setValue(KEY_DRAFT_DRAFTS, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {};
-
- config = func ? func(config) : config;
- save ? CM.commentDrafts.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_DRAFT_DRAFTS, null);
- config = config ? config : (init ? CM.commentDrafts.initConfig(true, init) : CM.commentDrafts.initConfig());
- return config;
- },
-
- // Comment-drafts config upgrade
- upgradeConfig: function() {
- // Get config
- let config = CM.commentDrafts.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- switch (config[KEY_DRAFT_VERSION]) {
- case '0.1':
- case undefined:
- v01_To_v02();
- logUpgrade();
- break;
- case VALUE_DRAFT_VERSION:
- DoLog(LogLevel.Info, 'comment-drafts config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for comment-drafts. '.replace('{V}', config[KEY_DRAFT_VERSION]));
- }
-
- // Save to gm_storage
- CM.commentDrafts.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'comment-drafts config successfully upgraded From v{V1} to {V2}. '.replaceAll('{V1}', config[KEY_DRAFT_VERSION]).replaceAll('{V2}', VALUE_DRAFT_VERSION));
- }
-
- function v01_To_v02() {
- // Fix bug caused bookcase's config overwriting comment-drafts' config
- if (config instanceof Array) {
- config = {};
- }
- }
- }
- }
-
- CM.bookcasePrefs = {
- saveConfig: function(config) {
- config ? config[KEY_BOOKCASE_VERSION] = VALUE_BOOKCASE_VERSION : function() {};
- GM_setValue(KEY_BOOKCASES, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {
- bookcases: [],
- laterbooks: {
- sortby: 'addTime_old2new',
- books: {}
- }
- };
-
- config = func ? func(config) : config;
- save ? CM.bookcasePrefs.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_BOOKCASES, null);
- config = config ? config : (init ? CM.bookcasePrefs.initConfig(true, init) : CM.bookcasePrefs.initConfig());
- return config;
- },
-
- // Bookcase config upgrade
- upgradeConfig: function() {
- // Get config
- let config = CM.bookcasePrefs.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- // Original version
- let V = config && config[KEY_BOOKCASE_VERSION] ? config[KEY_BOOKCASE_VERSION] : '0';
-
- switch (V) {
- case '0.1':
- case undefined:
- case '0':
- v01_To_v02();
- v02_To_v03();
- v03_To_v04();
- v04_To_v05();
- logUpgrade();
- break;
- case '0.2':
- v02_To_v03();
- v03_To_v04();
- v04_To_v05();
- logUpgrade();
- break;
- case '0.3':
- v03_To_v04();
- v04_To_v05();
- logUpgrade();
- break;
- case '0.4':
- v04_To_v05();
- logUpgrade();
- break;
- case VALUE_BOOKCASE_VERSION:
- DoLog(LogLevel.Info, 'Bookcase config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for Bookcase. '.replace('{V}', config[KEY_BOOKCASE_VERSION]));
- }
-
- // Save to gm_storage
- CM.bookcasePrefs.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'Bookcase config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', V).replaceAll('{V2}', VALUE_BOOKCASE_VERSION));
- }
-
- function v01_To_v02() {
- // Clear useless key added falsely
- delete config.bbcode;
-
- // Convert array to an object
- if (Array.isArray(config)) {
- const newConfig = {bookcases: []};
- for (let i = 0; i < config.length; i++) {
- newConfig.bookcases[i] = config[i];
- }
- config = newConfig;
- }
- }
-
- function v02_To_v03() {
- // Fix bug caused config.bookcases equals to []
- if (config && config.bookcases && config.bookcases.length === 0) {
- config = CM.bookcasePrefs.initConfig();
- }
- }
-
- function v03_To_v04() {
- if (config.laterbooks) {return false;}
- config.laterbooks = {
- sortby: 'addTime_old2new',
- books: {}
- };
- }
-
- function v04_To_v05() {
- const books = config.laterbooks.books;
- const sorts = [];
- let err = false;
- for (const book of Object.values(books)) {
- if (sorts.includes(book.sort)) {
- err = true;
- break;
- }
- sorts.push(book.sort);
- }
- Math.max.apply(null, sorts) > books.length && (err = true);
- if (err) {
- let i = 0;
- for (const book of Object.values(books)) {
- book.sort = ++i;
- }
- alertify.notify(TEXT_ALT_BOOKCASE_AFTERBOOKS_V4BUG, '', 0);
- }
- }
- }
- }
-
- CM.userDtlePrefs = {
- saveConfig: function(config) {
- config ? config[KEY_USRDETAIL_VERSION] = VALUE_USRDETAIL_VERSION : function() {};
- GM_setValue(KEY_USRDETAIL, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {userDetail: null};
-
- config = func ? func(config) : config;
- save ? CM.userDtlePrefs.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_USRDETAIL, null);
- config = config ? config : (init ? CM.userDtlePrefs.initConfig(true, init) : CM.userDtlePrefs.initConfig());
- return config;
- },
-
- // userDetail config upgrade
- upgradeConfig: function() {
- // Get config
- const config = CM.userDtlePrefs.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- // Original version
- let V = config && config[KEY_BOOKCASE_VERSION] ? config[KEY_BOOKCASE_VERSION] : '0';
-
- switch (V) {
- case '0.1':
- refreshMyUserDetail(logUpgrade);
- break;
- case VALUE_USRDETAIL_VERSION:
- DoLog(LogLevel.Info, 'User-detail config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for User-detail. '.replace('{V}', V));
- }
-
- // Save to gm_storage
- CM.userDtlePrefs.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'User-detail config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', V).replaceAll('{V2}', VALUE_USRDETAIL_VERSION));
- }
- }
- }
-
- CM.BkReviewPrefs = {
- saveConfig: function(config) {
- config ? config[KEY_REVIEW_VERSION] = VALUE_REVIEW_VERSION : function() {};
- GM_setValue(KEY_REVIEW_PREFS, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {
- bbcode: false,
- autoRefresh: false,
- beautiful: true,
- backgroundImage: 'https://img12.360buyimg.com/ddimg/jfs/t1/197476/22/6462/3478996/613227a8E03e8ffc3/99970183ddb9f896.jpg',
- favorites: {
- 228884: {
- name: '文库导航姬',
- href: `https://${location.host}/modules/article/reviewshow.php?rid=228884`,
- tiptitle: '梦想成为书评区大水怪的可以来康康'
- }
- },
- favorlast: false,
- history: {}
- };
-
- config = func ? func(config) : config;
- save ? CM.BkReviewPrefs.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_REVIEW_PREFS, null);
- config = config ? config : (init ? CM.BkReviewPrefs.initConfig(true, init) : CM.BkReviewPrefs.initConfig());
- return config;
- },
-
- // Review config upgrade
- upgradeConfig: function() {
- // Get config
- const config = CM.BkReviewPrefs.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- switch (config[KEY_REVIEW_VERSION]) {
- case '0.1':
- v01_To_v02();
- v02_To_v03();
- v03_To_v04();
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.2':
- v02_To_v03();
- v03_To_v04();
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.3':
- v03_To_v04();
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.4':
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.5':
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.6':
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.7':
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.8':
- v08_To_v09();
- logUpgrade();
- break;
- case VALUE_REVIEW_VERSION:
- DoLog(LogLevel.Info, 'Review config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for Review. '.replace('{V}', config[KEY_REVIEW_VERSION]));
- }
-
- // Save to gm_storage
- CM.BkReviewPrefs.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'Review config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', config[KEY_REVIEW_VERSION]).replaceAll('{V2}', VALUE_REVIEW_VERSION));
- }
-
- function v01_To_v02() {
- config.autoRefresh = false;
- delete config.downloading;
- }
-
- function v02_To_v03() {
- config.favorites = {
- 228884: {
- name: '文库导航姬',
- href: `https://${location.host}/modules/article/reviewshow.php?rid=228884`,
- tiptitle: '梦想成为书评区大水怪的可以来康康'
- }
- }
- }
-
- function v03_To_v04() {
- if (config.favorites) {return;};
- config.favorites = {
- 228884: {
- name: '文库导航姬',
- href: `https://${location.host}/modules/article/reviewshow.php?rid=228884`,
- tiptitle: '梦想成为书评区大水怪的可以来康康'
- }
- };
- }
-
- function v04_To_v05() {
- if (config.history) {return;};
- config.history = {};
- }
-
- function v05_To_v06() {
- if (config.beautiful !== undefined) {return;};
- config.beautiful = true;
- config.backgroundImage = 'https://img12.360buyimg.com/ddimg/jfs/t1/197476/22/6462/3478996/613227a8E03e8ffc3/99970183ddb9f896.jpg';
- }
-
- function v06_To_v07() {
- // Move CM.BkReviewPrefs.upgradeConfig.beautiful to CM.BeautifierCfg
- if (config.beautiful === undefined) {return;};
- const beautifierConfig = {
- reviewshow: {
- beautiful: config.beautiful,
- backgroundImage: config.backgroundImage
- }
- }
- CM.BeautifierCfg.saveConfig(beautifierConfig);
-
- delete config.beautiful;
- delete config.backgroundImage;
- }
-
- function v07_To_v08() {
- // Move CM.BkReviewPrefs.upgradeConfig.beautiful to CM.BeautifierCfg
- if (config.favorlast !== undefined) {return;};
- config.favorlast = false;
- for (const [rid, favorite] of Object.entries(config.favorites)) {
- config.favorites[rid] = {
- name: favorite.name,
- href: favorite.href.replace(/&page=1$/, ''),
- tiptitle: favorite.tiptitle
- };
- }
- }
-
- function v08_To_v09() {
- // Fill all favorite bookreviews' tiptitle using null for those don't have
- config.favorlast = false;
- for (const [rid, favorite] of Object.entries(config.favorites)) {
- !favorite.tiptitle && (favorite.tiptitle = null);
- }
- }
- }
- }
-
- CM.BeautifierCfg = {
- saveConfig: function(config) {
- config ? config[KEY_BEAUTIFIER_VERSION] = VALUE_BEAUTIFIER_VERSION : function() {};
- GM_setValue(KEY_BEAUTIFIER, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {
- upload: false,
- reviewshow: {
- beautiful: true,
- },
- novel: {
- beautiful: true,
- },
- common: {
- beautiful: false,
- },
- backgroundImage: 'https://img12.360buyimg.com/ddimg/jfs/t1/197476/22/6462/3478996/613227a8E03e8ffc3/99970183ddb9f896.jpg',
- bgiName: '默认背景图片 - Pixiv ID: 88913164',
- textScale: 100
- };
-
- config = func ? func(config) : config;
- save ? CM.BeautifierCfg.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_BEAUTIFIER, null);
- config = config ? config : (init ? CM.BeautifierCfg.initConfig(true, init) : CM.BeautifierCfg.initConfig());
- return config;
- },
-
- // Beautifier config upgrade
- upgradeConfig: function() {
- // Get config
- const config = CM.BeautifierCfg.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- switch (config[KEY_BEAUTIFIER_VERSION]) {
- /*case '0.1':
- v01_To_v02();
- break;*/
- case VALUE_BEAUTIFIER_VERSION:
- DoLog(LogLevel.Info, 'Beautifier config is in latest version. ');
- break;
- case '0.1':
- v01_To_v02();
- v02_To_v03();
- v03_To_v04();
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.2':
- v02_To_v03();
- v03_To_v04();
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.3':
- v03_To_v04();
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.4':
- v04_To_v05();
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.5':
- v05_To_v06();
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.6':
- v06_To_v07();
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.7':
- v07_To_v08();
- v08_To_v09();
- logUpgrade();
- break;
- case '0.8':
- v08_To_v09();
- logUpgrade();
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for Beautifier. '.replace('{V}', config[KEY_BEAUTIFIER_VERSION]));
- }
-
- // Save to gm_storage
- CM.BeautifierCfg.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'Beautifier config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', config[KEY_BEAUTIFIER_VERSION]).replaceAll('{V2}', VALUE_BEAUTIFIER_VERSION));
- }
-
- function v01_To_v02() {
- if (config.upload !== undefined) {return false;};
- config.upload = false;
- }
-
- function v02_To_v03() {
- if (config.reviewshow.bgiName !== undefined) {return false;};
- config.reviewshow.bgiName = 'image.jpeg';
- }
-
- function v03_To_v04() {
- if (config.textScale !== undefined) {return false;};
- config.textScale = 100;
- }
-
- function v04_To_v05() {
- if (config.novel !== undefined) {return false;};
- config.novel = {
- beautiful: true
- };
- }
-
- function v05_To_v06() {
- if (!config.textScale) {config.textScale = 100;};
- if (!config.novel) {config.novel = {beautiful: true};};
- }
-
- function v06_To_v07() {
- config.backgroundImage = config.reviewshow.backgroundImage;
- config.bgiName = config.reviewshow.bgiName;
- delete config.reviewshow.backgroundImage;
- delete config.reviewshow.bgiName;
- }
-
- function v07_To_v08() {
- if (config.common) {return false;}
- config.common = {
- beautiful: false
- };
- }
-
- function v08_To_v09() {
- if (config.common) {return false;}
- config.common = {
- beautiful: false
- };
- }
- }
- }
-
- CM.RemarksConfig = {
- saveConfig: function(config) {
- config ? config[KEY_REMARKS_VERSION] = VALUE_REMARKS_VERSION : function() {};
- GM_setValue(KEY_REMARKS, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {
- user: {}
- };
-
- config = func ? func(config) : config;
- save ? CM.RemarksConfig.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_REMARKS, null);
- config = config ? config : (init ? CM.RemarksConfig.initConfig(true, init) : CM.RemarksConfig.initConfig());
- return config;
- },
-
- // Beautifier config upgrade
- upgradeConfig: function() {
- // Get config
- const config = CM.RemarksConfig.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- switch (config[KEY_REMARKS_VERSION]) {
- //case '0.1':
- // v01_To_v02();
- // logUpgrade();
- // break;
- case VALUE_REMARKS_VERSION:
- DoLog(LogLevel.Info, 'RemarksConfig config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for RemarksConfig. '.replace('{V}', config[KEY_REMARKS_VERSION]));
- }
-
- // Save to gm_storage
- CM.RemarksConfig.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'RemarksConfig config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', config[KEY_REMARKS_VERSION]).replaceAll('{V2}', VALUE_REMARKS_VERSION));
- }
-
- //function v#BEFORE_To_v#AFTER() {
- // if (config.#NEWPROP !== undefined) {return false;};
- // config.#NEWPROP = #DEFAULTVALUE;
- //}
- }
- }
-
- CM.UserGlobalCfg = {
- saveConfig: function(config) {
- config ? config[KEY_USERGLOBAL_VERSION] = VALUE_USERGLOBAL_VERSION : function() {};
- GM_setValue(KEY_USERGLOBAL, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {
- imager: DATA_IMAGERS.default
- };
-
- config = func ? func(config) : config;
- save ? CM.UserGlobalCfg.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(KEY_USERGLOBAL, null);
- config = config ? config : (init ? CM.UserGlobalCfg.initConfig(true, init) : CM.UserGlobalCfg.initConfig());
- return config;
- },
-
- // Beautifier config upgrade
- upgradeConfig: function() {
- // Get config
- const config = CM.UserGlobalCfg.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- switch (config[KEY_USERGLOBAL_VERSION]) {
- //case '0.1':
- // v01_To_v02();
- // logUpgrade();
- // break;
- case VALUE_USERGLOBAL_VERSION:
- DoLog(LogLevel.Info, 'UserGlobal config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for UserGlobalCfg. '.replace('{V}', config[KEY_USERGLOBAL_VERSION]));
- }
-
- // Save to gm_storage
- CM.UserGlobalCfg.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'UserGlobal config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', config[KEY_USERGLOBAL_VERSION]).replaceAll('{V2}', VALUE_USERGLOBAL_VERSION));
- }
-
- //function v#BEFORE_To_v#AFTER() {
- // if (config.#NEWPROP !== undefined) {return false;};
- // config.#NEWPROP = #DEFAULTVALUE;
- //}
- }
- }
-
- // New Config Item Template
- /*CM.#NEWCONFIGNAME = {
- saveConfig: function(config) {
- config ? config[#KEY_NEWCONFIG_VERSION] = #VALUE_NEWCONFIG_VERSION : function() {};
- GM_setValue(#KEY_NEWCONFIG, config);
- },
-
- initConfig: function(save=true, func) {
- let config = {
- #key: #value,
- #key: #value
- };
-
- config = func ? func(config) : config;
- save ? CM.#NEWCONFIGNAME.saveConfig(config) : function() {};
- return config;
- },
-
- getConfig: function(init) {
- let config = GM_getValue(#KEY_NEWCONFIG, null);
- config = config ? config : (init ? CM.#NEWCONFIGNAME.initConfig(true, init) : CM.#NEWCONFIGNAME.initConfig());
- return config;
- },
-
- // Beautifier config upgrade
- upgradeConfig: function() {
- // Get config
- const config = CM.#NEWCONFIGNAME.getConfig();
-
- // if not inited
- if (!config) {return;};
-
- switch (config[#KEY_NEWCONFIG_VERSION]) {
- //case '0.1':
- // v01_To_v02();
- // logUpgrade();
- // break;
- case #VALUE_NEWCONFIG_VERSION:
- DoLog(LogLevel.Info, '#NEWCONFIGNAME config is in latest version. ');
- break;
- default:
- DoLog(LogLevel.Error, 'configCheckUpgrade: Invalid config version({V}) for #NEWCONFIGNAME. '.replace('{V}', config[#KEY_NEWCONFIG_VERSION]));
- }
-
- // Save to gm_storage
- CM.#NEWCONFIGNAME.saveConfig(config);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, '#NEWCONFIGNAME config successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', config[#KEY_NEWCONFIG_VERSION]).replaceAll('{V2}', #VALUE_NEWCONFIG_VERSION));
- }
-
- //function v#BEFORE_To_v#AFTER() {
- // if (config.#NEWPROP !== undefined) {return false;};
- // config.#NEWPROP = #DEFAULTVALUE;
- //}
- }
- }*/
-
- CM.AutoRecommend.upgradeConfig();
- CM.commentDrafts.upgradeConfig();
- CM.bookcasePrefs.upgradeConfig();
- CM.userDtlePrefs.upgradeConfig();
- CM.BkReviewPrefs.upgradeConfig();
- CM.BeautifierCfg.upgradeConfig();
- CM.RemarksConfig.upgradeConfig();
- CM.UserGlobalCfg.upgradeConfig();
- //CM.#NEWCONFIGNAME.upgradeConfig();
- }
-
- // Beautifier for all wenku pages
- function commonBeautify(API) {
- // No beautifier on exluded pages
- const excludes = ['novel']
- if (excludes.includes(API)) {return false;}
-
- // No beatifier if user does not want
- if (!CONFIG.BeautifierCfg.getConfig().common.beautiful) {return false;}
-
- const img = $CrE('img');
- img.src = CONFIG.BeautifierCfg.getConfig().backgroundImage;
- img.classList.add('plus_cbty_image');
- document.body.appendChild(img);
-
- const cover = $CrE('div');
- cover.classList.add('plus_cbty_cover');
- document.body.appendChild(cover);
-
- document.body.classList.add('plus_cbty');
- addStyle(CSS_COMMONBEAUTIFIER, 'plus_commonbeautifier')
- return true;
- }
-
- // Book page add-on
- function pageBook() {
- // Resource
- const pageResource = {
- elements: {},
- info: {}
- }
- collectPageResources();
- DoLog(LogLevel.Info, pageResource, true)
-
- // Provide meta info copy
- metaCopy();
-
- // Provide read-later button
- laterReads();
-
- // Provide txtfull download for copyright book
- enableDownload();
-
- // Provide images download
- imagesDownload();
-
- // Provide tag search
- tagOption();
-
- // Ctrl+Enter comment submit
- areaReply();
-
- // Get page resources
- function collectPageResources() {
- collectElements();
- collectInfos();
-
- function collectElements() {
- const elements = pageResource.elements;
- elements.content = $('#content');
- elements.bookMain = $(elements.content, 'div');
- elements.header = $(elements.content, 'div>table');
- elements.titleContainer = $(elements.header, 'table td>span');
- elements.bookName = $(elements.header, 'b');
- elements.recommend = $(elements.content, `a[href^="https://${location.host}/modules/article/uservote.php"]`);
- elements.metaContainer = $(elements.header, 'tr+tr');
- elements.metas = $All(elements.metaContainer, 'td');
- elements.info = $(elements.bookMain, 'div+table');
- elements.cover = $(elements.info, 'img');
- elements.infoText = $(elements.info, 'td+td');
- elements.notice = $All(elements.infoText, 'span.hottext>b');
- elements.tags = elements.notice.length > 1 ? elements.notice[0] : null;
- elements.notice = elements.notice[elements.notice.length-1];
- elements.introduce = $All(elements.infoText, 'span');
- elements.introduce = elements.introduce[elements.introduce.length-1];
- elements.downloadContainer = $(pageResource.elements.bookMain, 'div>fieldset');
- elements.downloadPanel = elements.downloadContainer ? elements.downloadContainer.parentElement : null;
- }
-
- function collectInfos() {
- const info = pageResource.info;
- const elements = pageResource.elements;
- info.bookName = elements.bookName.innerText;
- info.BID = Number(getUrlArgv('id') || location.href.match(/book\/(\d+).htm/)[1]);
- info.metas = []; elements.metas.forEach(function(meta){this.push(getKeyValue(meta.innerText));}, info.metas);
- info.notice = elements.notice.innerText;
- info.tags = elements.tags ? getKeyValue(elements.tags.innerText).VALUE.split(' ') : null;
- info.introduce = elements.introduce.innerText;
- info.cover = elements.cover.src;
- info.dlEnabled = $(elements.content, 'legend>b');
- info.dlEnabled = info.dlEnabled ? info.dlEnabled.innerText : false;
- info.dlEnabled = info.dlEnabled ? (info.dlEnabled.indexOf('TXT') !== -1 && info.dlEnabled.indexOf('UMD') !== -1 && info.dlEnabled.indexOf('JAR') !== -1) : false;
- }
- }
-
- // Copy meta info
- function metaCopy() {
- let tip = TEXT_TIP_COPY;
- for (let i = -1; i < pageResource.elements.metas.length; i++) {
- const meta = i !== -1 ? pageResource.elements.metas[i] : pageResource.elements.bookName;
- const info = i !== -1 ? pageResource.info.metas[i] : pageResource.info.bookName;
- const value = i !== -1 ? info.VALUE : info;
- meta.innerHTML += HTML_BOOK_COPY;
- const copyBtn = $(meta, '.'+CLASSNAME_BUTTON);
- copyBtn.addEventListener('click', function() {
- copyText(value);
- showtip(TEXT_TIP_COPIED);
- alertify.message(TEXT_ALT_META_COPIED.replaceAll('{M}', value));
- });
-
- settip(copyBtn, TEXT_TIP_COPY);
- }
- }
-
- // Add to later-reads
- function laterReads() {
- // Make button
- let btn = installBtn(makeBtn(inAfterbooks() ? 'remove' : 'add'));
-
- // Update book info if in list
- inAfterbooks() && add(false);
-
- function add(alt=true) {
- // Add to config
- const config = CONFIG.bookcasePrefs.getConfig();
- config.laterbooks.books[pageResource.info.BID] = {
- sort: Object.keys(config.laterbooks.books).length + 1,
- addTime: new Date().getTime(),
- name: pageResource.info.bookName,
- aid: pageResource.info.BID,
- metas: pageResource.info.metas,
- tags: pageResource.info.tags,
- introduce: pageResource.info.introduce,
- cover: pageResource.info.cover
- };
- CONFIG.bookcasePrefs.saveConfig(config);
-
- // New button
- removeBtn(btn);
- btn = installBtn(makeBtn('remove'));
-
- // Soft alert
- alt && alertify.success(TEXT_ALT_BOOK_AFTERBOOKS_ADDED);
- }
-
- function remove() {
- // Remove from config
- const config = CONFIG.bookcasePrefs.getConfig();
- const books = config.laterbooks.books;
- const book = books[pageResource.info.BID];
- if (!book) {return false;}
- delete books[pageResource.info.BID];
- Array.prototype.forEach.call(Object.values(books), (b) => (b.sort > book.sort && b.sort--));
- CONFIG.bookcasePrefs.saveConfig(config);
-
- // New button
- removeBtn(btn);
- btn = installBtn(makeBtn('add'));
-
- // Soft alert
- alertify.success(TEXT_ALT_BOOK_AFTERBOOKS_REMOVED);
- }
-
- function makeBtn(type='add') {
- const btn = $CrE('span');
- btn.classList.add(CLASSNAME_BUTTON);
- switch (type) {
- case 'add':
- btn.innerHTML = TEXT_GUI_BOOK_READITLATER;
- btn.addEventListener('click', add);
- break;
- case 'remove':
- btn.innerHTML = TEXT_GUI_BOOK_DONTREADLATER;
- btn.addEventListener('click', remove);
- break;
- }
- return btn;
- }
-
- function installBtn(btn) {
- pageResource.elements.recommend.previousElementSibling.insertAdjacentElement('afterend', btn);
- btn.insertAdjacentText('beforebegin', '[');
- btn.insertAdjacentText('afterend', ']');
- return btn;
- }
-
- function removeBtn(btn) {
- const parent = btn.parentElement;
- for (const node of [btn.previousSibling, btn, btn.nextSibling]) {
- parent.removeChild(node);
- }
- return btn;
- }
-
- function inAfterbooks() {
- return CONFIG.bookcasePrefs.getConfig().laterbooks.books[pageResource.info.BID] ? true : false;
- }
- }
-
- // Download copyright book
- function enableDownload() {
- if (pageResource.info.dlEnabled) {return false;};
-
- // Download panel
- // Create panel
- let div = $CrE('div');
- pageResource.elements.bookMain.appendChild(div);
- div.outerHTML = HTML_DOWNLOAD_LINKS
- .replaceAll('{ORIBOOKNAME}', pageResource.info.bookName)
- .replaceAll('{BOOKID}', String(pageResource.info.BID))
- .replaceAll('{CHARSET}', getUrlArgv('charset') ? '&charset=' + getUrlArgv('charset') : '')
-
- // Use about:blank instead of direct url; aims to aviod unnecessary web requests
- const container = pageResource.elements.downloadContainer = $(pageResource.elements.bookMain, 'div>fieldset');
- div = pageResource.elements.downloadPanel = container.parentElement;
- for (const a of $All(container, 'div>a')) {
- //a.addEventListener('click', openDlPage);
- }
-
- // Notice board
- pageResource.elements.notice.innerHTML = HTML_DOWNLOAD_BOARD
- .replaceAll('{ORIBOOKNAME}', pageResource.info.bookName);
-
- function openDlPage(e) {
- e.preventDefault();
-
- const url = e.target.href;
- const win = window.open(`https://${location.host}/`);
- win.history.replaceState({...win.history.state}, '', url);
- }
- }
-
- // All images downloader
- function imagesDownload() {
- const container = pageResource.elements.downloadContainer;
- const divImage = $CrE('div'), a = $CrE('a');
- divImage.setAttribute('style', 'width:164px; float:left; text-align:center');
- a.href = 'javascript:void(0);';
- a.innerHTML = TEXT_GUI_BOOK_IMAGESDOWNLOAD;
- a.addEventListener('click', confirm);
- divImage.appendChild(a);
- container.appendChild(divImage);
- for (const div of $All(container, 'div')) {
- div.style.width = '164px';
- }
-
- function confirm() {
- const title = TEXT_ALT_DOWNLOADIMG_CONFIRM_TITLE;
- const message = TEXT_ALT_DOWNLOADIMG_CONFIRM_MESSAGE.replace('{N}', pageResource.info.bookName);
- const ok = TEXT_ALT_DOWNLOADIMG_CONFIRM_OK;
- const cancel = TEXT_ALT_DOWNLOADIMG_CONFIRM_CANCEL;
- alertify.confirm(title, message, download, function() {/* oncancel */}).set('labels', {ok: ok, cancel: cancel});
- }
-
- function download() {
- // GUI
- const delay = alertify.get('notifier','delay');
- alertify.set('notifier','delay', 0);
-
- let finished = false, CAll, CCur = 0;
- const AM = new AsyncManager();
- AM.onfinish = downloadFinish;
- const box = alertify.message(TEXT_ALT_DOWNLOADIMG_STATUS_INDEX);
- box.ondismiss = function() {return finished;}
-
- // Start download
- AM.add()
- AndAPI.getNovelIndex({
- aid: pageResource.info.BID,
- lang: 0,
- callback: function(xml) {
- const allChapters = $All(xml, 'chapter');
- const chapters = Array.prototype.filter.call(allChapters, (c) => (c.firstChild.nodeValue.includes('插图')));
- CAll = chapters.length;
- box.setContent(TEXT_ALT_DOWNLOADIMG_STATUS_LOADING.replace('{CCUR}', CCur).replace('{CALL}', CAll));
- for (const chapter of chapters) {
- AM.add();
- getChapter(chapter.getAttribute('cid'), chapter.parentNode);
- }
- AM.finish();
- }
- });
- AM.finishEvent = true;
-
- function getChapter(cid, volume) {
- AndAPI.getNovelContent({
- aid: pageResource.info.BID,
- cid: cid,
- lang: 0,
- callback: getImgs,
- args: [volume]
- });
-
- function getImgs(str, volume) {
- const imgs = str.match(/<!--image-->https?:[^<>]+<!--image-->/g);
- const len = imgs.length.toString().length;
- const CAM = new AsyncManager();
- CAM.onfinish = chapterFinish;
-
- for (let i = 0; i < imgs.length; i++) {
- const img = imgs[i];
- const src = img.match(/<!--image-->(https?:[^<>]+)<!--image-->/)[1];
- const ext = src.match(/\.(\w+)$/) ? src.match(/\.(\w+)$/)[1] : 'jpg';
- const filename = pageResource.info.bookName + '_' + volume.firstChild.nodeValue + ' ' + ['插图', '插圖'][getLang()] + '_' + fillNumber(i+1, len) + '.' + ext;
- CAM.add();
- downloadFile({
- url: src,
- name: filename,
- onload: function() {
- CAM.finish();
- }
- });
- }
- CAM.finishEvent = true;
-
- function chapterFinish() {
- AM.finish();
- box.setContent(TEXT_ALT_DOWNLOADIMG_STATUS_LOADING.replace('{CCUR}', ++CCur).replace('{CALL}', CAll));
- }
- }
- }
-
- function downloadFinish() {
- finished = true;
- alertify.set('notifier','delay', delay);
- box.dismiss();
- alertify.success(TEXT_ALT_DOWNLOADIMG_STATUS_FINISH);
- }
- }
- }
-
- // Download copyright book full txt
- function enableDownload_old() {
- if (pageResource.info.dlEnabled) {return false;};
-
- let div = $CrE('div');
- pageResource.elements.bookMain.appendChild(div);
- div.outerHTML = HTML_DOWNLOAD_LINKS_OLD
- .replaceAll('{ORIBOOKNAME}', pageResource.info.bookName)
- .replaceAll('{BOOKID}', String(pageResource.info.BID))
- .replaceAll('{BOOKNAME}', encodeURIComponent(pageResource.info.bookName));
- div = $('#txtfull');
- pageResource.elements.txtfull = div;
-
- pageResource.elements.notice.innerHTML = HTML_DOWNLOAD_BOARD
- .replaceAll('{ORIBOOKNAME}', pageResource.info.bookName);
- }
-
- // Tag Search
- function tagOption() {
- const tagsEle = pageResource.elements.tags;
- const tags = pageResource.info.tags;
- if (!tags) {return false;}
-
- let html = getKeyValue(tagsEle.innerText).KEY + ':';
- for (const tag of tags) {
- html += HTML_BOOK_TAG.replace('{TU}', $URL.encode(tag)).replace('{TN}', tag) + ' ';
- }
- tagsEle.innerHTML = html;
- }
- }
-
- // Reply area add-on
- function areaReply() {
- /* ## Release title area ## */
- if ($('td > input[name="Submit"]') && !$('#ptitle')) {
- const table = $('form>table');
- const titleText = table.innerHTML.match(/<!--[\s\S]+id="ptitle"[\s\S]+-->/)[0];
- const titleHTML = titleText.replace(/^<!--\s*/, '').replace(/\s*-->$/, '');
- const titleEle = $CrE('tr');
- const caption = $(table, 'caption');
- table.insertBefore(titleEle, caption);
- titleEle.outerHTML = titleHTML;
- }
-
- const commentArea = $('#pcontent'); if (!commentArea) {return false;};
- const commentForm = $(`form[action^="https://${location.host}/modules/article/review"]`);
- const commentSbmt = $('td > input[name="Submit"]');
- const commenttitl = $('#ptitle');
- const commentbttm = commentSbmt.parentElement;
-
- /* ## Ctrl+Enter comment submit ## */
- let btnSbmtValue = commentSbmt.value;
- if (commentSbmt) {
- commentSbmt.value = '发表书评(Ctrl+Enter)';
- commentSbmt.style.padding = '0.3em 0.4em 0.3em 0.4em';
- commentSbmt.style.height= 'auto';
- commentArea.addEventListener('keydown', hotkeyReply);
- commenttitl.addEventListener('keydown', hotkeyReply);
- }
-
- // Enable https protocol for inserted url
- fixHTTPS();
-
- // Provide image upload & insert
- imageplus();
-
- // At user
- atUser();
-
- // Comment auto-save
- // GUI
- const asTip = $CrE('span');
- commentbttm.appendChild(asTip);
-
- // Review-Page: Same rid, same savekey - 'rid123456'
- // Book-Page & Book-Review-List-Page: Same bookid, same savekey - 'bid1234'
- const rid = getUrlArgv({url: commentForm.action, name: 'rid', dealFunc: Number});
- const aid = getUrlArgv({url: commentForm.action, name: 'aid', dealFunc: Number});
- const bid = location.href.match(/\/book\/(\d+).htm/) ? Number(location.href.match(/\/book\/(\d+).htm/)[1]) : 0;
- const key = rid ? 'rid' + String(rid) : 'bid' + String(bid);
- let commentData = CONFIG.commentDrafts.getConfig()[key] || {
- key : key,
- rid : rid,
- aid : aid,
- bid : bid,
- page : getUrlArgv({name: 'rid', dealFunc: Number, defaultValue: 1}),
- time : (new Date()).getTime()
- };
- restoreDraft();
- submitHook();
-
- const events = ['focus', 'blur', 'mousedown', 'keydown', 'keyup', 'change'];
- const eventEles = [commentArea, commenttitl];
- for (const eventEle of eventEles) {
- for (const event of events) {
- eventEle.addEventListener(event, saveDraft);
- }
- }
-
- function saveDraft() {
- const content = commentArea.value;
- const title = commenttitl.value;
-
- if (!content && !title) {
- clearDraft();
- return;
- } else if (commentData.content === content && commentData.title === title) {
- return;
- }
-
- commentData.content = content;
- commentData.title = title;
-
- const allCData = CONFIG.commentDrafts.getConfig();
-
- allCData[commentData.key] = commentData;
- CONFIG.commentDrafts.saveConfig(allCData);
- asTip.innerHTML = TEXT_GUI_AUTOSAVE;
- }
-
- function restoreDraft() {
- const allCData = CONFIG.commentDrafts.getConfig();
- if (!allCData[commentData.key]) {return false;};
- if (!commenttitl.value && !commentArea.value) {
- commentData = allCData[commentData.key];
- commenttitl.value = commentData.title;
- commentArea.value = commentData.content;
- asTip.innerHTML = TEXT_GUI_AUTOSAVE_RESTORE;
- }
- return true;
- }
-
- function clearDraft() {
- const allCData = CONFIG.commentDrafts.getConfig();
- if (!allCData[commentData.key]) {return false;};
- delete allCData[commentData.key];
- CONFIG.commentDrafts.saveConfig(allCData);
- asTip.innerHTML = TEXT_GUI_AUTOSAVE_CLEAR;
- return true;
- }
-
- function hotkeyReply() {
- let keycode = event.keyCode;
- if (keycode === 13 && event.ctrlKey && !event.altKey) {
- // Do not submit directly like this; we need to submit with onsubmit executed
- //commentForm.submit();
- commentSbmt.click();
- }
- }
-
- function fixHTTPS() {
- if (typeof(UBBEditor) === 'undefined') {
- fixHTTPS.wait = fixHTTPS.wait ? fixHTTPS.wait : 0;
- if (++fixHTTPS.wait > 50) {return false;}
- DoLog('fixHTTPS: UBBEditor not loaded, waiting...');
- setTimeout(fixHTTPS, NUMBER_ELEMENT_LOADING_WAIT_INTERVAL);
- return false;
- }
- const eid = 'pcontent';
-
- const menuItemInsertUrl = $(commentForm, '#menuItemInsertUrl');
- const menuItemInsertImage = $(commentForm, '#menuItemInsertImage');
-
- // Wait until menuItemInsertUrl and menuItemInsertImage is loaded
- if (!menuItemInsertUrl || !menuItemInsertImage) {
- DoLog(LogLevel.Info, 'fixHTTPS: element not loaded, waiting...');
- setTimeout(fixHTTPS, NUMBER_ELEMENT_LOADING_WAIT_INTERVAL);
- return false;
- }
-
- // Wait until original onclick function is set
- if (!menuItemInsertUrl.onclick || !menuItemInsertImage.onclick) {
- DoLog(LogLevel.Info, 'fixHTTPS: defult onclick not loaded, waiting...');
- setTimeout(fixHTTPS, NUMBER_ELEMENT_LOADING_WAIT_INTERVAL);
- return false;
- }
-
- menuItemInsertUrl.onclick = function () {
- var url = prompt("请输入超链接地址", "http://");
- if (url != null && url.indexOf("http://") < 0 && url.indexOf("https://") < 0) {
- alert("请输入完整的超链接地址!");
- return;
- }
- if (url != null) {
- if ((document.selection && document.selection.type == "Text") ||
- (window.getSelection &&
- document.getElementById(eid).selectionStart > -1 && document.getElementById(eid).selectionEnd >
- document.getElementById(eid).selectionStart)) {UBBEditor.InsertTag(eid, "url", url,'');}
- else {UBBEditor.InsertTag(eid, "url", url, url);}
- }
- };
-
- menuItemInsertImage.onclick = function () {
- var imgurl = prompt("请输入图片路径", "http://");
- if (imgurl != null && imgurl.indexOf("http://") < 0 && imgurl.indexOf("https://") < 0) {
- alert("请输入完整的图片路径!");
- return;
- }
- if (imgurl != null) {
- UBBEditor.InsertTag(eid, "img", "", imgurl);
- }
- };
-
- return true;
- }
-
- function imageplus() {
- if (typeof(UBBEditor) === 'undefined') {
- DoLog('imageplus: UBBEditor not loaded, waiting...');
- setTimeout(imageplus, NUMBER_ELEMENT_LOADING_WAIT_INTERVAL);
- return false;
- }
-
- // Imager menu
- const menu = $('#UBB_Menu');
- const elmImage = $(commentForm, '#menuItemInsertImage');
- const onclick = elmImage.onclick;
- const imagers = new PlusList({
- id: 'plus_imager',
- list: [
- {value: TEXT_GUI_REVEIW_IMG_INSERTURL, tip: TEXT_TIP_REVIEW_IMG_INSERTURL, onclick: onclick},
- {value: TEXT_GUI_REVEIW_IMG_SELECTIMG, tip: TEXT_TIP_REVIEW_IMG_SELECTIMG, onclick: pickfile}
- ],
- parentElement: menu.parentElement,
- insertBefore: $('#SmileListTable'),
- visible: false,
- onshow: onshow
- });
- elmImage.onclick = (e) => {
- e.stopPropagation();
- imagers.show();
- };
- document.addEventListener('click', imagers.hide);
-
- // drag-drop & copy-paste
- commentArea.addEventListener('paste', pictureGot);
- commentArea.addEventListener('dragenter', destroyEvent);
- commentArea.addEventListener('dragover', destroyEvent);
- commentArea.addEventListener('drop', pictureGot);
-
- function onshow() {
- imagers.div.style.left = String(UBBEditor.GetPosition(elmImage).x) + 'px';
- imagers.div.style.top = String(UBBEditor.GetPosition(elmImage).y + 20) + 'px';
- }
-
- function pickfile() {
- const fileinput = $CrE('input');
- fileinput.type = 'file';
- fileinput.addEventListener('change', pictureGot);
- fileinput.click();
- }
-
- function pictureGot(e) {
- // Get picture file
- const input = e.dataTransfer || e.clipboardData || window.clipboardData || e.target;
- if (!input.files || input.files.length === 0) {return false;};
- const file = input.files[0];
- const mimetype = file.type;
- const name = file.name;
-
- // Pasting an unrecognizable file is not a mistake
- // Maybe the user just wants to paste the filename here
- // Otherwise getting an unrecognizable file is a mistake
- if (!mimetype || mimetype.split('/')[0] !== 'image') {
- if (!e.clipboardData && !window.clipboardData) {
- destroyEvent(e);
- alertify.error(TEXT_ALT_IMAGE_FORMATERROR);
- }
- return false;
- } else {
- destroyEvent(e);
- }
-
- // Insert picture marker
- const marker = '[image_uploading={ID} name={NAME}]'.replace('{ID}', randstr(16, true, commentArea.value)).replace('{NAME}', name);
- insertText(marker);
-
- // Upload
- alertify.notify(TEXT_ALT_IMAGE_UPLOAD_WORKING);
- uploadImage({
- file: file,
- onerror: (e) => {
- alertify.error(TEXT_ALT_IMAGE_UPLOAD_ERROR);
- DoLog(LogLevel.Error, ['Upload error at imageplus>upload:', e]);
- },
- onload: (json) => {
- const name = json.name;
- const url = json.url;
- commentArea.value = commentArea.value.replace(marker, url);
- alertify.success(TEXT_ALT_IMAGE_UPLOAD_SUCCESS.replaceAll('{NAME}', name).replaceAll('{URL}', url));
- }
- });
-
- }
- }
-
- function submitHook() {
- const onsubmit = commentForm.onsubmit;
- commentForm.onsubmit = onsubmitForm;
-
- function onsubmitForm(e) {
- // Cancel submit while content empty
- if (commentArea.value === '' && commenttitl.value === '') {return false;};
-
- // Clear Draft
- clearDraft();
-
- // Restore original submit button value
- if (commentSbmt.value !== btnSbmtValue) {
- commentSbmt.value = btnSbmtValue;
- setTimeout(()=>{commentSbmt.click.call(commentSbmt);}, 0);
- return false;
- }
-
- // Continue submit
- return onsubmit ? onsubmit() : function() {return true;};
- }
- }
-
- function atUser() {
- if (typeof(UBBEditor) === 'undefined') {
- DoLog(LogLevel.Info, 'atUser: UBBEditor not loaded, waiting...');
- setTimeout(atUser, NUMBER_ELEMENT_LOADING_WAIT_INTERVAL);
- return false;
- }
-
- const menu = $('#UBB_Menu');
- const list = new PlusList({
- id: 'plus_AtTable',
- list: [],
- parentElement: menu.parentElement,
- insertBefore: $('#FontSizeTable'),
- visible: false,
- onshow: showlist
- });
- list.onhide = list.clear;
- document.addEventListener('click', list.hide);
-
- const firstBtn = menu.children[0];
- const atBtn = $CrE('input');
- atBtn.type = 'button';
- atBtn.style.backgroundImage = 'none';
- atBtn.value = '@';
- atBtn.title = TEXT_GUI_AREAREPLY_AT;
- atBtn.id = 'plus_At';
- atBtn.classList.add(CLASSNAME_BUTTON);
- atBtn.classList.add('UBB_MenuItem');
- atBtn.addEventListener('click', (e) => {
- e.stopPropagation();
- list.show();
- });
- menu.insertBefore(atBtn, firstBtn);
-
- function showlist(shown) {
- if (shown) {return false;};
- if (typeof(ubb_subdiv) === 'string' && typeof(hideeve) === 'function') {
- hideeve(ubb_subdiv);
- ubb_subdiv = 'plus_AtTable';
- }
- makelist();
- list.ul.focus();
- return true;
- }
-
- function makelist() {
- // Get users
- const allUsers = getAllUsers();
-
- // Make list
- for (const user of allUsers) {
- const item = list.append({
- value: user.userName,
- tip: ()=>{return 'uid: ' + String(user.userID);},
- onclick: btnClick
- });
- item.li.user = user;
- item.button.user = user;
- }
-
- // Style
- list.div.style.left = String(UBBEditor.GetPosition(atBtn).x) + 'px';
- list.div.style.top = String(UBBEditor.GetPosition(atBtn).y + 20) + 'px';
-
- return true;
-
- function getAllUsers() {
- const pageUsers = $All(`#content table strong>a[href^="https://${location.host}/userpage.php"]`);
- const friends = getMyUserDetail().userFriends;
- if (!friends) {
- refreshMyUserDetail(refreshList);
- return false;
- }
-
- // concat to one array
- const allUsers = [];
- for (const pageUser of pageUsers) {
- // Valid check
- if (isNaN(Number(pageUser.href.match(/\?uid=(\d+)/)[1]))) {continue;};
- const user = {
- userName: pageUser.innerText,
- userID: Number(pageUser.href.match(/\?uid=(\d+)/)[1]),
- referred: 0
- }
- if (!userExist(allUsers, user)) {
- const userAsFriend = userExist(friends, user);
- allUsers.push(userAsFriend ? userAsFriend : user);
- }
- }
- for (const friend of friends) {
- if (!userExist(allUsers, friend)) {
- allUsers.push(friend);
- }
- }
-
- // Sort by referred
- allUsers.sort((a,b)=>{return (b.referred?b.referred:0) - (a.referred?a.referred:0);});
-
- return allUsers;
-
- // returns the exist user object found in users, or false if not found
- function userExist(users, user) {
- for (const u of users) {
- if (u.userID === user.userID) {return u;};
- }
- return false;
- }
- }
-
- function btnClick() {
- const btn = this;
- const user = btn.user;
- const name = btn.user.userName;
- const insertValue = '@' + name;
- insertText(insertValue);
-
- // referred increase
- const userDetail = getMyUserDetail();
- const friends = userDetail.userFriends;
- user.referred = user.referred ? user.referred+1 : 1;
- for (let i = 0; i < friends.length; i++) {
- if (friends[i].userID === user.userID) {
- friends[i] = user;
- break;
- }
- }
- CONFIG.userDtlePrefs.saveConfig(userDetail);
- }
- }
- }
-
- function insertText(insertValue) {
- const insertPosition = commentArea.selectionEnd;
- const text = commentArea.value;
- const leftText = text.substr(0, insertPosition);
- const rightText = text.substr(insertPosition);
-
- // if not at the beginning of a line then insert a whitespace before the link
- insertValue = ((leftText.length === 0 || /[ \r\n]$/.test(leftText)) ? '' : ' ') + insertValue;
- // if not at the end of a line then insert a whitespace after the link
- insertValue += (rightText.length === 0 || /^[ \r\n]/.test(rightText)) ? '' : ' ';
-
- commentArea.value = leftText + insertValue + rightText;
- const position = insertPosition + insertValue.length;
- commentForm.scrollIntoView(); commentArea.focus(); commentArea.setSelectionRange(position, position);
- }
- }
-
- // Review link add-on
- function linkReview() {
- // Get all review links and apply add-on functions
- const allRLinks = $All(`td>a[href^="https://${location.host}/modules/article/reviewshow.php?"]`);
- for (const RLink of allRLinks) {
- lastPage(RLink);
- }
-
- // Provide button direct to review last page
-
- // New version. Uses '&page=last' keyword.
- function lastPage(a) {
- const p = a.parentElement;
- const lastpg = $CrE('a');
- const strrid = getUrlArgv({url: a.href, name: 'rid'});
- lastpg.href = URL_REVIEWSHOW_2.replace('{R}', strrid).replace('{P}', 'last');
- lastpg.classList.add(CLASSNAME_BUTTON);
- lastpg.target = '_blank';
- lastpg.innerText = TEXT_GUI_LINK_TOLASTPAGE;
- p.insertBefore(lastpg, a);
- }
- }
-
- // Side functions area
- function sideFunctions() {
- const SPanel = new SidePanel();
- SPanel.usercss = CSS_SIDEPANEL;
- SPanel.create();
- SPanel.setPosition('bottom-right');
-
- commonButtons();
- return SPanel;
-
- function commonButtons() {
- // Button show/hide-all-buttons
- const btnShowHide = SPanel.add({
- faicon: 'fa-solid fa-down-left-and-up-right-to-center',
- className: 'accept-pointer',
- tip: '隐藏面板',
- onclick: (function() {
- let hidden = false;
- return (e) => {
- hidden = !hidden;
- btnShowHide.faicon.className = 'fa-solid ' + (hidden ? 'fa-up-right-and-down-left-from-center' : 'fa-down-left-and-up-right-to-center');
- btnShowHide.classList[hidden ? 'add' : 'remove']('low-opacity');
- btnShowHide.setAttribute('aria-label', (hidden ? '显示面板' : '隐藏面板'));
- SPanel.elements.panel.style.pointerEvents = hidden ? 'none' : 'auto';
- for (const button of SPanel.elements.buttons) {
- if (button === btnShowHide) {continue;}
- //button.style.display = hidden ? 'none' : 'block';
- button.style.pointerEvents = hidden ? 'none' : 'auto';
- button.style.opacity = hidden ? '0' : '1';
- }
- };
- }) ()
- });
-
- // Button scroll-to-bottom
- const btnDown = SPanel.add({
- faicon: 'fa-solid fa-angle-down',
- tip: '转到底部',
- onclick: (e) => {
- const elms = [document.body.parentElement, $('#content'), $('#contentmain')];
-
- for (const elm of elms) {
- elm && elm.scrollTo && elm.scrollTo(elm.scrollLeft, elm.scrollHeight);
- }
- }
- });
-
- // Button scroll-to-top
- const btnUp = SPanel.add({
- faicon: 'fa-solid fa-angle-up',
- tip: '转到顶部',
- onclick: (e) => {
- const elms = [document.body.parentElement, $('#content'), $('#contentmain')];
-
- for (const elm of elms) {
- elm && elm.scrollTo && elm.scrollTo(elm.scrollLeft, 0);
- }
- }
- });
-
- // Darkmode
- /*
- const btnDarkmode = SPanel.add({
- faicon: 'fa-solid ' + (DMode.isActivated() ? 'fa-sun' : 'fa-moon'),
- tip: '明暗切换',
- onclick: (e) => {
- DMode.toggle();
- btnDarkmode.faicon.className = 'fa-solid ' + (DMode.isActivated() ? 'fa-sun' : 'fa-moon');
- }
- });
- */
-
- // Refresh page
- const btnRefresh = SPanel.add({
- faicon: 'fa-solid fa-rotate-right',
- tip: '刷新页面',
- onclick: (e) => {
- reloadPage();
- }
- });
- }
- }
-
- // Reviewedit page add-on
- function pageReviewedit() {
- redirectToCorrectPage();
-
- function redirectToCorrectPage() {
- // Get redirect target rid
- const refreshMeta = $('meta[http-equiv="refresh"]');
- const metaurl = refreshMeta.content.match(/url=(.+)/)[1];
- if (!refreshMeta) {return false;};
- if (getUrlArgv({url: metaurl, name: 'page'})) {return false;};
-
- // Read correct redirect location
- const rid = Number(getUrlArgv({url: metaurl, name: 'rid'}));
- const config = CONFIG.BkReviewPrefs.getConfig();
- const history = config.history;
- const pageHist = history[rid];
- if (!pageHist) {return false;}
- const url = pageHist.href;
-
- // Check if time expired (Expire time: 30 seconds)
- if ((new Date()).getTime() - pageHist.time > 30*1000) {
- // Delete expired record
- delete history[rid];
- CONFIG.BkReviewPrefs.saveConfig(config);
- }
-
- // Redirect link
- $('a').href = url;
-
- // Redirect
- setTimeout(() => {location.href = url;}, 1500);
- }
- }
-
- // Review page add-on
- function pageReview() {
- // Elements
- const main = $('#content');
- const headBars = $All(main, 'tr>td[align]');
-
- // Page Info
- const rid = Number(getUrlArgv('rid'));
- const aid = getUrlArgv('aid') ? Number(getUrlArgv('aid')) : Number($(main, 'td[width]>a').href.match(/(\d+)\.html?$/)[1]);
- const page = Number($('#pagelink strong').innerText);
- const title = $(main, 'th>strong').textContent;
-
- // URL correction
- correctURL();
-
- // Enhancements
- pageStatus();
- downloader();
- sideButtons();
- beautifier();
- floorEnhance();
- autoRefresh();
- addFavorite();
- addUnlock();
-
- function correctURL() {
- (getUrlArgv('page') === 'last' || !getUrlArgv('page')) && setPageUrl(URL_REVIEWSHOW.replace('{A}', aid).replace('{R}', rid).replace('{P}', page));
- }
-
- function sideButtons() {
- // Last page
- SPanel.add({
- faicon: 'fa-solid fa-angles-right',
- tip: '最后一页',
- onclick: (e) => {findclick('#pagelink>.last');}
- });
-
- // Next page
- SPanel.add({
- faicon: 'fa-solid fa-angle-right',
- tip: '下一页',
- onclick: (e) => {findclick('#pagelink>.next');}
- });
-
- // Previous page
- SPanel.add({
- faicon: 'fa-solid fa-angle-left',
- tip: '上一页',
- onclick: (e) => {findclick('#pagelink>.prev');}
- });
-
- // First page
- SPanel.add({
- faicon: 'fa-solid fa-angles-left',
- tip: '第一页',
- onclick: (e) => {findclick('#pagelink>.first');}
- });
-
- function findclick(selector) {return $(selector) && $(selector).click();}
- }
-
- function beautifier() {
- // GUI
- const span = $CrE('span');
- const check = $CrE('input');
- check.type = 'checkbox';
- check.checked = CONFIG.BeautifierCfg.getConfig().reviewshow.beautiful;
- span.innerHTML = TEXT_GUI_REVIEW_BEAUTIFUL;
- span.classList.add(CLASSNAME_BUTTON);
- span.style.marginLeft = '0.5em';
- span.addEventListener('click', toggleBeautiful);
- check.addEventListener('click', toggleBeautiful);
- settip(span, TEXT_TIP_REVIEW_BEAUTIFUL);
- settip(check, TEXT_TIP_REVIEW_BEAUTIFUL);
- headBars[0].appendChild(span);
- headBars[0].appendChild(check);
- CONFIG.BeautifierCfg.getConfig().reviewshow.beautiful && beautiful();
-
- function toggleBeautiful(e) {
- // stop event
- destroyEvent(e);
-
- // Togle & save to config
- const config = CONFIG.BeautifierCfg.getConfig();
- config.reviewshow.beautiful = !config.reviewshow.beautiful;
- CONFIG.BeautifierCfg.saveConfig(config);
-
- setTimeout(() => {check.checked = config.reviewshow.beautiful;}, 0);
- alertify.notify(config.reviewshow.beautiful ? TEXT_ALT_BEAUTIFUL_ON : TEXT_ALT_BEAUTIFUL_OFF);
-
- // beautifier
- config.reviewshow.beautiful ? beautiful() : recover();
- }
-
- function beautiful() {
- const config = CONFIG.BeautifierCfg.getConfig();
- addStyle(CSS_REVIEWSHOW
- .replaceAll('{BGI}', config.backgroundImage)
- .replaceAll('{S}', config.textScale)
- , 'beautifier');
- scaleimgs();
- hookPosition();
-
- function scaleimgs() {
- const imgs = $All('.divimage>img');
- const w = main.clientWidth * 0.8 - 3; // td.width = "80%", .even {padding: 3px;}
- for (const img of imgs) {
- img.width = w;
- }
- }
- }
-
- function recover() {
- addStyle('', 'beautifier');
- restorePosition();
- }
-
- function hookPosition() {
- if (!CONFIG.BeautifierCfg.getConfig().reviewshow.beautiful) {return false;};
- if (typeof(UBBEditor) !== 'object') {
- hookPosition.wait = hookPosition.wait ? hookPosition.wait : 0;
- if (++hookPosition.wait > 50) {return false;}
- DoLog('beautiful/hookPosition: UBBEditor not loaded, waiting...');
- setTimeout(hookPosition, NUMBER_ELEMENT_LOADING_WAIT_INTERVAL);
- return false;
- }
- UBBEditor.GetPosition_BK = UBBEditor.GetPosition;
- UBBEditor.GetPosition = function (obj) {
- var r = new Array();
- r.x = obj.offsetLeft;
- r.y = obj.offsetTop;
- while (obj = obj.offsetParent) {
- if (unsafeWindow.$(obj).getStyle('position') == 'absolute' || unsafeWindow.$(obj).getStyle('position') == 'relative') break;
- r.x += obj.offsetLeft;
- r.y += obj.offsetTop;
- }
- r.x -= main.scrollLeft;
- r.y -= main.scrollTop;
- return r;
- }
- }
-
- function restorePosition() {
- if (typeof(UBBEditor) !== 'object') {return false;};
- if (!UBBEditor.GetPosition_BK) {return false;};
- UBBEditor.GetPosition = UBBEditor.GetPosition_BK;
- }
- }
-
- function pageStatus() {
- window.addEventListener('load', () => {
- // Recover page status
- applyPageStatus();
- // Record the current page status of current review
- setInterval(recordPage, 1000);
- });
- }
-
- // Apply page status sored in history record
- function applyPageStatus() {
- const config = CONFIG.BkReviewPrefs.getConfig();
- const history = config.history;
- const pageHist = history[rid];
-
- // Scroll to the last position
- if (pageHist && pageHist.page === page) {
- // Check if time expired
- if (pageHist.time && (new Date()).getTime() - pageHist.time < 30*1000) {
- // Do not scroll when opening a positioned link(http[s]://.../...#yidxxxxxx)
- if (/#yid\d+$/.test(location.href)) {return;}
- // Scroll
- pageHist.scrollX !== undefined && window.scrollTo(pageHist.scrollX, pageHist.scrollY);
- pageHist.contentsclX !== undefined && main.scrollTo(pageHist.contentsclX, pageHist.contentsclY);
- } else {
- // Delete expired record
- delete history[rid];
- CONFIG.BkReviewPrefs.saveConfig(config);
- }
- }
- }
-
- function recordPage() {
- const config = CONFIG.BkReviewPrefs.getConfig();
- const history = config.history;
-
- // Save page history
- config.history[rid] = {
- rid: rid,
- aid: aid,
- page: page,
- href: URL_REVIEWSHOW.replace('{R}', String(rid)).replace('{A}', String(aid)).replace('{P}', String(page)),
- scrollX: window.pageXOffset,
- scrollY: window.pageYOffset,
- contentsclX: main.scrollLeft,
- contentsclY: main.scrollTop,
- time: (new Date()).getTime()
- }
- CONFIG.BkReviewPrefs.saveConfig(config);
- }
-
- function floorEnhance() {
- const floors = getAllFloors();
- floors.forEach((f)=>(correctFloorLink(f)));
- for (const floor of floors) {
- alinkEdit(floor);
- addQuoteBtn(floor);
- addQueryBtn(floor);
- addRemark(floor);
- alinktofloor(floor.table);
- }
- }
-
- function alinktofloor(parent=main) {
- const floorLinks = $All(main, `a[name][href^="https://${location.host}/modules/article/reviewshow.php"][href*="#yid"]`);
- for (const a of $All(parent, 'a')) {
- if (!a.href.match(/^https?:\/\/www\.wenku8\.(net|cc)\/modules\/article\/reviewshow\.php\?(&?rid=\d+|&?aid=\d+|&?page=\d+){1,4}#yid\d+$/)) {continue;};
- for (const flink of floorLinks) {
- if (isSameReply(a, flink)) {
- // Set scroll target
- a.targetNode = flink;
- while (a.targetNode.nodeName !== 'TABLE') {
- a.targetNode = a.targetNode.parentElement;
- }
-
- // Scroll when clicked
- a.addEventListener('click', (e) => {
- destroyEvent(e);
- e.currentTarget.targetNode.scrollIntoView();
- })
- };
- }
- }
-
- function isSameReply(link1, link2) {
- const url1 = link1.href.toLowerCase().replace('http://', 'https://');
- const url2 = link2.href.toLowerCase().replace('http://', 'https://');
- const rid1 = getUrlArgv({url: url1, name: 'rid', defaultValue: null});
- const yid1 = url1.match(/#yid(\d+)/) ? url1.match(/#yid(\d+)/)[1] : null;
- const rid2 = getUrlArgv({url: url2, name: 'rid', defaultValue: null});
- const yid2 = url2.match(/#yid(\d+)/) ? url2.match(/#yid(\d+)/)[1] : null;
- return rid1 === rid2 && yid1 === yid2;
- }
- }
-
- function alinkEdit(parent=document) {
- const eLinks = $All(`a[href^="https://${location.host}/modules/article/reviewedit.php?yid="]`);
- for (const eLink of eLinks) {
- eLink.addEventListener('click', (e) => {
- // NO e.stopPropagation() here. Just hooks the open action.
- e.preventDefault();
-
- // Open editor dialog
- openDialog(e.target.href + '&ajax_gets=jieqi_contents');
-
- // Show mask if mask not shown
- !document.getElementById("mask") && showMask();
- })
- }
- }
-
- function autoRefresh() {
- let working=false, interval=0;
- const pagelink = $('#pagelink');
- const tdLink = pagelink.parentElement;
- const trContainer = tdLink.parentElement;
- const tdAutoRefresh = $CrE('td');
- const chkAutoRefresh = $CrE('input');
- const txtAutoRefresh = $CrE('span');
- const txtPaused = $CrE('span');
- const ptitle = $('#ptitle');
- const pcontent = $('#pcontent');
- txtAutoRefresh.innerText = TEXT_GUI_AUTOREFRESH;
- txtAutoRefresh.classList.add(CLASSNAME_BUTTON);
- txtAutoRefresh.addEventListener('click', toggleRefresh);
- chkAutoRefresh.addEventListener('click', toggleRefresh);
- chkAutoRefresh.type = 'checkbox';
- chkAutoRefresh.checked = false;
- txtPaused.innerText = '';
- txtPaused.classList.add(CLASSNAME_TEXT);
- txtPaused.style.marginLeft = '0.5em';
- tdAutoRefresh.style.align = 'left';
- tdAutoRefresh.appendChild(txtAutoRefresh);
- tdAutoRefresh.appendChild(chkAutoRefresh);
- tdAutoRefresh.appendChild(txtPaused);
- trContainer.insertBefore(tdAutoRefresh, tdLink);
-
- // Apply config
- CONFIG.BkReviewPrefs.getConfig().autoRefresh ? toggleRefresh() : function() {};
-
- /* No pauses after v1.5.7
- // Show pause
- // Note: Blur event triggers after Focus event was triggered
- for (const editElm of [ptitle, pcontent]) {
- if (!editElm) {continue;};
- editElm.addEventListener('blur', (e) => {
- txtPaused.innerText = '';
- });
- editElm.addEventListener('focus', (e) => {
- txtPaused.innerText = TEXT_GUI_AUTOREFRESH_PAUSED;
- });
- }
- */
-
- function toggleRefresh(e) {
- // stop event
- destroyEvent(e);
-
- // Not in last Page, no auto refresh
- if (!isCurLastPage() && !working) {
- const box = alertify.notify(TEXT_ALT_AUTOREFRESH_NOTLAST);
- box.callback = (isClicked) => {isClicked && (location.href = $('#pagelink>a.last').href);};
- return false;
- }
-
- // toggle
- working = !working;
- working ? interval = setInterval(refresh, 20*1000) : clearInterval(interval);
- working && refresh();
-
- // Save to config
- const review = CONFIG.BkReviewPrefs.getConfig();
- review.autoRefresh = working;
- CONFIG.BkReviewPrefs.saveConfig(review);
-
- setTimeout(() => {chkAutoRefresh.checked = working;}, 0);
- alertify.notify(working ? TEXT_ALT_AUTOREFRESH_ON : TEXT_ALT_AUTOREFRESH_OFF);
- }
-
- function refresh() {
- const box = alertify.notify(TEXT_ALT_AUTOREFRESH_WORKING);
- const url = URL_REVIEWSHOW.replace('{R}', String(rid)).replace('{A}', String(aid)).replace('{P}', 'last');
- getDocument(url, refreshLoaded, url);
-
-
- function refreshLoaded(oDoc, pageurl) {
- // Clost alert box
- box.exist ? box.close.apply(box) : function() {};
-
- // Update all existing floor content (and title)
- const nowfloors = $All('#content>table[class="grid"]');
- const newfloors = $All(oDoc, '#content>table[class="grid"]');
- let i, modified = false;
-
- for (i = 1; i < Math.min(nowfloors.length, newfloors.length); i++) {
- isFloorTable(nowfloors[i]) && isFloorTable(newfloors[i]) && getFloorNumber(nowfloors[i]) === getFloorNumber(newfloors[i]) && updatefloor(i);
- }
- modified && alertify.notify(TEXT_ALT_AUTOREFRESH_MODIFIED);
-
- const newtop = getTopFloorNumber(oDoc);
- const nowtop = getTopFloorNumber(document);
- if (unsafeWindow.isPY_DNG && newtop === 9899) {
- sendReviewReply({rid: rid, title: '测试标题', content: '测试内容'});
- }
- if (newtop > nowtop) {
- const newmain = $(oDoc, '#content');
- const eleLastPage = $(oDoc, '#pagelink a.last');
- const urlLastPage = newmain.url = eleLastPage.href;
- const newpage = Number(getUrlArgv({url: urlLastPage, name: 'page'}));
- const newfloors = getAllFloors(newmain);
- const nowfloors = getAllFloors();
- if (newpage === page) {
- // In same page, append floors
- for (let i = nowfloors.length; i < newfloors.length; i++) {
- const floor = newfloors[i];
- appendfloor(floor);
- }
- } else {
- // In New page, remake floors
- let box = alertify.notify(TEXT_ALT_AUTOREFRESH_APPLIED);
-
- // Remove old floors
- for (const oldfloor of nowfloors) {
- oldfloor.table.parentElement.removeChild(oldfloor.table);
- }
-
- // Append new floors
- for (const newfloor of newfloors) {
- appendfloor(newfloor);
- }
-
- // Remake #pagelink
- $(main, '#pagelink').innerHTML = $(newmain, '#pagelink').innerHTML;
-
- // Reset location.href
- page !== 'last' && setPageUrl(urlLastPage);
-
- return true;
- }
- } else {
- alertify.message(TEXT_ALT_AUTOREFRESH_NOMORE);
- return false;
- }
-
- function updatefloor(i) {
- const nowfloor = nowfloors[i];
- const newfloor = newfloors[i];
- const nowTitle = getEleFloorTitle(nowfloor);
- const newTitle = getEleFloorTitle(newfloor);
- const nowContent = getEleFloorContent(nowfloor);
- const newContent = getEleFloorContent(newfloor);
-
- if (nowTitle.innerHTML !== newTitle.innerHTML) {
- nowTitle.innerHTML = newTitle.innerHTML;
- nowTitle.classList.add(CLASSNAME_MODIFIED);
- nowTitle.addEventListener('click', (e) => {e.currentTarget.classList.remove(CLASSNAME_MODIFIED);});
- modified = true;
- }
- if (getFloorContent(nowContent) !== getFloorContent(newContent)) {
- nowContent.innerHTML = newContent.innerHTML;
- nowContent.classList.add(CLASSNAME_MODIFIED);
- nowContent.addEventListener('click', (e) => {e.currentTarget.classList.remove(CLASSNAME_MODIFIED);});
- modified = true;
- }
- if (modified) {
- alinktofloor(nowfloor);
- }
- }
- }
- }
-
- function isCurLastPage() {
- return $('#pagelink>strong').innerText === $('#pagelink>a.last').innerText;
- }
-
- function getTopFloorNumber(oDoc) {
- const tblfloors = $All(oDoc, '#content>table[class="grid"]');
- for (let i = tblfloors.length-1; i >= 0; i--) {
- const tbllast = tblfloors[i];
- if (isFloorTable(tbllast)) {return getFloorNumber(tbllast);}
- }
-
- return null;
- }
- }
-
- function correctFloorLink(floor) {
- floor.hrefa.href = floor.href;
- }
-
- function addFavorite() {
- // Create GUI
- const spliter = $CrE('span');
- const favorBtn = $CrE('span');
- const favorChk = $CrE('input');
- spliter.style.marginLeft = '1em';
- favorBtn.innerText = TEXT_GUI_REVIEW_ADDFAVORITE;
- favorBtn.classList.add(CLASSNAME_BUTTON);
- favorChk.type = 'checkbox';
- favorChk.checked = CONFIG.BkReviewPrefs.getConfig().favorites.hasOwnProperty(rid);
- favorBtn.addEventListener('click', checkChange);
- favorChk.addEventListener('change', checkChange);
-
- headBars[0].appendChild(spliter);
- headBars[0].appendChild(favorBtn);
- headBars[0].appendChild(favorChk);
-
- function checkChange(e) {
- if (e && e.target === favorChk) {
- destroyEvent(e);
- }
-
- let inFavorites;
- const config = CONFIG.BkReviewPrefs.getConfig();
- if (config.favorites.hasOwnProperty(rid)) {
- delete config.favorites[rid];
- inFavorites = false;
- } else {
- config.favorites[rid] = {
- rid: rid,
- name: title,
- href: URL_REVIEWSHOW_3.replace('{R}', rid).replace('{A}', aid),
- time: (new Date()).getTime(), // time added in version 1.6.7
- tiptitle: null
- };
- inFavorites = true;
- }
- CONFIG.BkReviewPrefs.saveConfig(config);
- setTimeout(() => {favorChk.checked = inFavorites;}, 0);
- alertify.notify((inFavorites ? TEXT_GUI_REVIEW_FAVORADDED : TEXT_GUI_REVIEW_FAVORDELED).replace('{N}', title));
- }
-
- function updateFavorite() {
- const config = CONFIG.BkReviewPrefs.getConfig();
- if (config.favorites.hasOwnProperty(rid)) {
- config.favorites[rid] = {
- rid: rid,
- name: title,
- href: URL_REVIEWSHOW_3.replace('{R}', rid).replace('{A}', aid)
- };
- }
- }
- }
-
- function addQuoteBtn(floor) {
- const table = floor.table;
- const numberEle = $(table, 'td.even div a');
- const attr = numberEle.parentElement;
- const btn = createQuoteBtn(attr);
- const spliter = document.createTextNode(' | ');
- attr.insertBefore(spliter, numberEle);
- attr.insertBefore(btn, spliter);
-
- function createQuoteBtn() {
- // Get content textarea
- const pcontent = $('#pcontent');
- const form = $(`form[action^="https://${location.host}/modules/article/review"]`);
-
- // Create button
- const btn = $CrE('span');
- btn.classList.add(CLASSNAME_BUTTON);
- btn.addEventListener('click', quoteThisFloor);
- btn.innerHTML = '引用';
- const tip_panel = $CrE('div');
- tip_panel.insertAdjacentText('afterbegin', '或者,');
- const btn_qtnum = $CrE('span');
- btn_qtnum.classList.add(CLASSNAME_BUTTON);
- btn_qtnum.addEventListener('click', quoteFloorNum);
- btn_qtnum.innerHTML = '仅引用序号';
- tip_panel.appendChild(btn_qtnum);
- const panel = tippy(btn, {
- content: tip_panel,
- theme: 'wenku_tip',
- placement: 'top',
- interactive: true,
- });
- return btn;
-
- function quoteThisFloor() {
- // In DOM Events, <this> keyword points to the Event Element.
- const numberEle = $(this.parentElement, 'a[name]');
- const numberText = numberEle.innerText;
- const url = URL_REVIEWSHOW_4.replace('{R}', rid).replace('{P}', page).replace('{Y}', numberEle.name);
- const contentEle = $(this.parentElement.parentElement, 'hr+div');
- const content = getFloorContent(contentEle);
- const insertPosition = pcontent.selectionEnd;
- const text = pcontent.value;
- const leftText = text.substr(0, insertPosition);
- const rightText = text.substr(insertPosition);
-
- // Create insert value
- let insertValue = '[url=U]N[/url] [quote]Q[/quote]';
- insertValue = insertValue.replace('U', url).replace('N', numberText).replace('Q', content);
- // if not at the beginning of a line then insert a whitespace before the link
- insertValue = ((leftText.length === 0 || /[ \r\n]$/.test(leftText)) ? '' : ' ') + insertValue;
- // if not at the end of a line then insert a whitespace after the link
- insertValue += (rightText.length === 0 || /^[ \r\n]/.test(rightText)) ? '' : ' ';
-
- pcontent.value = leftText + insertValue + rightText;
- const position = insertPosition + (pcontent.value.length - text.length);
- form.scrollIntoView(); pcontent.focus(); pcontent.setSelectionRange(position, position);
- }
-
- function quoteFloorNum() {
- // In DOM Events, <this> keyword points to the Event Element.
- const numberEle = $(this.parentElement.parentElement.parentElement.parentElement.parentElement, 'a[name]');
- const numberText = numberEle.innerText;
- const url = URL_REVIEWSHOW_4.replace('{R}', rid).replace('{P}', page).replace('{Y}', numberEle.name);
- const contentEle = $(this.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement, 'hr+div');
- const insertPosition = pcontent.selectionEnd;
- const text = pcontent.value;
- const leftText = text.substr(0, insertPosition);
- const rightText = text.substr(insertPosition);
-
- // Create insert value
- let insertValue = '[url=U]N[/url]';
- insertValue = insertValue.replace('U', url).replace('N', numberText);
- // if not at the beginning of a line then insert a whitespace before the link
- insertValue = ((leftText.length === 0 || /[ \r\n]$/.test(leftText)) ? '' : ' ') + insertValue;
- // if not at the end of a line then insert a whitespace after the link
- insertValue += (rightText.length === 0 || /^[ \r\n]/.test(rightText)) ? '' : ' ';
-
- pcontent.value = leftText + insertValue + rightText;
- const position = insertPosition + (pcontent.value.length - text.length);
- form.scrollIntoView(); pcontent.focus(); pcontent.setSelectionRange(position, position);
- }
- }
- }
-
- function addQueryBtn(floor) {
- // Get container div
- const div = floor.leftdiv;
-
- // Create buttons
- const qBtn = $CrE('a'); // Button for query reviews
- const iBtn = $CrE('a'); // Button for query userinfo
- const mBtn = $CrE('a'); // Button for edit user remark
-
- // Get UID
- const user = $(div, 'a');
- const name = user.innerText;
- const UID = Math.floor(user.href.match(/uid=(\d+)/)[1]);
-
- // Create text spliter
- const spliter = document.createTextNode(' | ');
-
- // Config buttons
- qBtn.href = URL_REVIEWSEARCH.replaceAll('{K}', String(UID));
- iBtn.href = URL_USERINFO .replaceAll('{K}', String(UID));
- mBtn.href = 'javascript: void(0);'
- qBtn.target = '_blank';
- iBtn.target = '_blank';
- mBtn.addEventListener('click', editUserRemark.bind(null, UID, name, reloadRemarks));
- qBtn.innerText = TEXT_GUI_USER_REVIEWSEARCH;
- iBtn.innerText = TEXT_GUI_USER_USERINFO;
- mBtn.innerText = TEXT_GUI_USER_USERREMARKEDIT;
-
- // Append to GUI
- div.appendChild($CrE('br'));
- div.appendChild(iBtn);
- div.appendChild(qBtn);
- div.insertBefore(spliter, qBtn);
- div.appendChild($CrE('br'));
- div.appendChild(mBtn);
-
- function reloadRemarks() {
- const floors = getAllFloors();
- floors.forEach((f) => (addRemark(f)));
- }
- }
-
- function addRemark(floor) {
- // Get container div
- const div = floor.leftdiv;
- const strong = $(div, 'strong');
-
- // Get config
- const config = CONFIG.RemarksConfig.getConfig();
- const uid = Math.floor($(div, 'strong>a').href.match(/\?uid=(\d+)/)[1]);
- const user = (config.user[uid] || {});
-
- if ($(div, '.user-remark')) {
- // Edit remark displayer
- const name = $(div, '.user-remark-remark');
- name.innerText = user.remark || TEXT_GUI_USER_USERREMARKEMPTY;
- name.style.color = user.remark ? 'black' : 'grey';
- } else {
- // Add remark displayer
- const container = $CrE('span');
- const br = $CrE('br');
- const name = $CrE('span');
- container.classList.add('user-remark');
- container.classList.add(CLASSNAME_TEXT);
- container.innerText = TEXT_GUI_USER_USERREMARKSHOW;
- name.innerText = user.remark || TEXT_GUI_USER_USERREMARKEMPTY;
- name.style.color = user.remark ? 'black' : 'grey';
- name.classList.add('user-remark-remark');
- container.appendChild(name);
- strong.insertAdjacentElement('afterend', br);
- br.insertAdjacentElement('afterend', container);
- }
- }
-
- // Provide a hidden function to reply overtime book-reviews
- function addUnlock() {
- listen();
-
- function listen() {
- if ($('#pcontent')) {return;}
- const target = $('#content>table>caption+tbody>tr>td:nth-child(2)');
- let count = 0;
- document.addEventListener('click', function hidden_unlocker(e) {
- e.target === target ? count++ : (count = 0);
- count >= 10 && add();
- count >= 10 && document.removeEventListener('click', hidden_unlocker);
- count >= 10 && (target.innerHTML = TEXT_GUI_REVIEW_UNLOCK_WARNING);
- });
- }
-
- function add() {
- const container = $CrE('div');
- $('#content').appendChild(container);
- makeEditor(container, rid, aid);
- }
- }
-
- // Reply without refreshing the document
- function hookReply() {
- const form = $('form[name="frmreview"]');
- const onsubmit = form.onsubmit;
- form.onsubmit = function() {
- const title = $(form, '#ptitle').value;
- const content = $(form, '#pcontent').value;
- (onsubmit ? onsubmit() : true) && sendReviewReply({
- rid: rid, title: title, content: content,
- onload: function(oDoc) {
- // Make floor(s)
- },
- onerror: function(e) {
- DoLog(LogLevel.Error, 'pageReview/hookReply: submit onerror.');
- }
- });
- };
- }
-
- function downloader() {
- // GUI
- const pageCountText = $('#pagelink>.last').href.match(/page=(\d+)/)[1];
- const lefta = $(headBars[0], 'a');
- const lefttext = document.createTextNode('书评回复');
- clearChildnodes(headBars[0]);
- headBars[0].appendChild(lefta);
- headBars[0].appendChild(lefttext);
- headBars[0].width = '45%';
- headBars[1].width = '55%';
-
- const saveBtn = $CrE('span');
- saveBtn.innerText = TEXT_GUI_DOWNLOAD_REVIEW.replaceAll('A', pageCountText);
- saveBtn.classList.add(CLASSNAME_BUTTON);
- saveBtn.addEventListener('click', downloadWholePost);
- headBars[1].appendChild(saveBtn);
-
- const spliter = $CrE('span');
- const bbcdTxt = $CrE('span');
- const bbcdChk = $CrE('input');
- spliter.style.marginLeft = '1em';
- bbcdTxt.innerText = TEXT_GUI_DOWNLOAD_BBCODE;
- bbcdChk.type = 'checkbox';
- bbcdChk.checked = CONFIG.BkReviewPrefs.getConfig().bbcode;
- bbcdTxt.addEventListener('click', bbcodeOnclick);
- bbcdChk.addEventListener('click', bbcodeOnclick);
- settip(bbcdTxt, TEXT_TIP_DOWNLOAD_BBCODE);
- settip(bbcdChk, TEXT_TIP_DOWNLOAD_BBCODE);
- bbcdTxt.classList.add(CLASSNAME_BUTTON);
- headBars[1].appendChild(spliter);
- headBars[1].appendChild(bbcdTxt);
- headBars[1].appendChild(bbcdChk);
-
- function bbcodeOnclick(e) {
- destroyEvent(e);
-
- if (downloadWholePost.working) {
- alertify.warning(TEXT_ALT_DOWNLOAD_BBCODE_NOCHANGE);
- return false;
- }
- const cmConfig = CONFIG.BkReviewPrefs.getConfig();
- cmConfig.bbcode = !cmConfig.bbcode;
- setTimeout(() => {bbcdChk.checked = cmConfig.bbcode;}, 0);
- CONFIG.BkReviewPrefs.saveConfig(cmConfig);
- }
-
- // ## Function: Get data from page document or join it into the given data variable ##
- function getDataFromPage(document, data) {
- let i;
- DoLog(LogLevel.Info, document, true);
-
- // Get Floors; avatars uses for element locating
- const main = $(document, '#content');
- const avatars = $All(main, 'table div img.avatar');
-
- // init data, floors and users if need
- let floors = {}, users = {};
- if (data) {
- floors = data.floors;
- users = data.users;
- } else {
- data = {};
- initData(data, floors, users);
- }
- for (i = 0; i < avatars.length; i++) {
- const floor = newFloor(floors, avatars, i);
- const elements = getFloorElements(floor);
- const reply = getFloorReply(floor);
- const user = getFloorUser(floor);
- appendFloor(floors, floor);
- }
- return data;
-
- function initData(data, floors, users) {
- // data vars
- data.floors = floors; floors.data = data;
- data.users = users; users.data = data;
-
- // review info
- data.link = location.href;
- data.id = getUrlArgv({name: 'rid', dealFunc: Number, defaultValue: 0});
- data.page = getUrlArgv({name: 'page', dealFunc: Number, defaultValue: 1});
- data.title = $(main, 'th strong').innerText;
- return data;
- }
-
- function newFloor(floors, avatars, i) {
- const floor = {};
- floor.avatar = avatars[i];
- floor.floors = floors;
- return floor;
- }
-
- function getFloorElements(floor) {
- const elements = {}; floor.elements = elements;
- elements.avatar = floor.avatar;
- elements.table = elements.avatar.parentElement.parentElement.parentElement.parentElement.parentElement;
- elements.tr = $(elements.table, 'tr');
- elements.tdUser = $(elements.table, 'td.odd');
- elements.tdReply = $(elements.table, 'td.even');
- elements.divUser = $(elements.tdUser, 'div');
- elements.aUser = $(elements.divUser, 'a');
- elements.attr = $(elements.tdReply, 'div a').parentElement;
- elements.time = elements.attr.childNodes[0];
- elements.number = $(elements.attr, 'a[name]');
- elements.title = $(elements.tdReply, 'div>strong');
- elements.content = $(elements.tdReply, 'hr+div');
- return elements;
- }
-
- function getFloorReply(floor) {
- const elements = floor.elements;
- const reply = {}; floor.reply = reply;
- reply.time = elements.time.nodeValue.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0];
- reply.number = Number(elements.number.innerText.match(/\d+/)[0]);
- reply.value = CONFIG.BkReviewPrefs.getConfig().bbcode ? getFloorContent(elements.content, true) : elements.content.innerText;
- reply.title = elements.title.innerText;
- return reply;
- }
-
- function getFloorUser(floor) {
- const elements = floor.elements;
- const user = {}; floor.user = user;
- user.id = elements.aUser.href.match(/uid=(\d+)/)[1];
- user.name = elements.aUser.innerText;
- user.avatar = elements.avatar.src;
- user.link = elements.aUser.href;
- user.jointime = elements.divUser.innerText.match(/\d{4}-\d{2}-\d{2}/)[0];
-
- const data = floor.floors.data; const users = data.users;
- if (!users.hasOwnProperty(user.id)) {
- users[user.id] = user;
- user.floors = [floor];
- } else {
- const uFloors = users[user.id].floors;
- uFloors.push(floor);
- sortUserFloors(uFloors);
- }
- return user;
- }
-
- function sortUserFloors(uFloors) {
- uFloors.sort(function(F1, F2) {
- return F1.reply.number - F2.reply.number;
- })
- }
-
- function appendFloor(floors, floor) {
- floors[floor.reply.number-1] = floor;
- }
- }
-
- // ## Function: Get pages and parse each page to a data, returns data ##
- // callback(data, gotcount, finished) is called when xhr and parsing completed
- function getAllPages(callback) {
- let i, data, gotcount = 0;
- const ridMatcher = /rid=(\d+)/, pageMatcher = /page=(\d+)/;
- const lastpageUrl = $('#pagelink>.last').href;
- const rid = Number(lastpageUrl.match(ridMatcher)[1]);
- const pageCount = Number(lastpageUrl.match(pageMatcher)[1]);
- const curPageNum = location.href.match(pageMatcher) ? Number(location.href.match(pageMatcher)[1]) : 1;
-
- for (i = 1; i <= pageCount; i++) {
- const url = lastpageUrl.replace(pageMatcher, 'page='+String(i));
- getDocument(url, joinPageData, callback);
- }
-
- function joinPageData(pageDocument, callback) {
- data = getDataFromPage(pageDocument, data);
- gotcount++;
-
- // log
- const level = gotcount % NUMBER_LOGSUCCESS_AFTER ? LogLevel.Info : LogLevel.Success;
- DoLog(level, 'got ' + String(gotcount) + ' pages.');
- if (gotcount === pageCount) {
- DoLog(LogLevel.Success, 'All pages xhr and parsing completed.');
- DoLog(LogLevel.Success, data, true);
- }
-
- // callback
- if (callback) {callback(data, gotcount, gotcount === pageCount);};
- }
- }
-
- // Function output
- function joinTXT(data, noSpliter=true) {
- const floors = data.floors; const users = data.users;
-
- // HEAD META DATA
- const saveTime = getTime();
- const head = TEXT_OUTPUT_REVIEW_HEAD
- .replaceAll('{RWID}', data.id).replaceAll('{RWTT}', data.title).replaceAll('{RWLK}', data.link)
- .replaceAll('{SVTM}', saveTime).replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version).replaceAll('{ATNM}', GM_info.script.author);
-
- // join userinfos
- let userText = '';
- for (const [pname, user] of Object.entries(users)) {
- if (!isNumeric(pname)) {continue;};
- userText += TEXT_OUTPUT_REVIEW_USER
- .replaceAll('{LNSPLT}', noSpliter ? '' : TEXT_SPLIT_LINE).replaceAll('{USERNM}', user.name)
- .replaceAll('{USERID}', user.id).replaceAll('{USERJT}', user.jointime)
- .replaceAll('{USERLK}', user.link).replaceAll('{USERFL}', user.floors[0].reply.number);
- userText += '\n'.repeat(2);
- }
-
- // join floors
- let floorText = '';
- for (const [pname, floor] of Object.entries(floors)) {
- if (!isNumeric(pname)) {continue;};
- const avatar = floor.avatar; const elements = floor.elements; const user = floor.user; const reply = floor.reply;
- floorText += TEXT_OUTPUT_REVIEW_FLOOR
- .replaceAll('{LNSPLT}', noSpliter ? '' : TEXT_SPLIT_LINE).replaceAll('{RPNUMB}', String(reply.number))
- .replaceAll('{RPTIME}', reply.time).replaceAll('{USERNM}', user.name)
- .replaceAll('{USERID}', user.id).replaceAll('{RPTEXT}', reply.value);
- floorText += '\n'.repeat(2);
- }
-
- // End
- const foot = TEXT_OUTPUT_REVIEW_END;
-
- // return
- const txt = head + '\n'.repeat(2) + userText + '\n'.repeat(2) + floorText + '\n'.repeat(2) + foot;
- return txt;
- }
-
- // ## Function: Download the whole post ##
- function downloadWholePost() {
- // Continues only if not working
- if (downloadWholePost.working) {return;};
- downloadWholePost.working = true;
- bbcdTxt.classList.add(CLASSNAME_DISABLED);
-
- // GUI
- saveBtn.innerText = TEXT_GUI_DOWNLOADING_REVIEW
- .replaceAll('C', '0').replaceAll('A', pageCountText);
-
- // go work!
- getAllPages(function(data, gotCount, finished) {
- // GUI
- saveBtn.innerText = TEXT_GUI_DOWNLOADING_REVIEW
- .replaceAll('C', String(gotCount)).replaceAll('A', pageCountText);
-
- // Stop here if not completed
- if (!finished) {return;};
-
- // Join text
- const TXT = joinTXT(data);
-
- // Download
- const blob = new Blob([TXT],{type:"text/plain;charset=utf-8"});
- const url = URL.createObjectURL(blob);
- const name = '文库贴 - ' + data.title + ' - ' + data.id.toString() + '.txt';
-
- const a = $CrE('a');
- a.href = url;
- a.download = name;
- a.click();
-
- // GUI
- saveBtn.innerText = TEXT_GUI_DOWNLOADFINISH_REVIEW;
- alertify.success(TEXT_ALT_DOWNLOADFINISH_REVIEW.replaceAll('{T}', data.title).replaceAll('{I}', data.id).replaceAll('{N}', name));
-
- // Work finish
- downloadWholePost.working = false;
- bbcdTxt.classList.remove(CLASSNAME_DISABLED);
- })
- }
- }
-
- // Get all floor object
- /* Contains:
- ** floor.table
- ** floor.tbody
- ** floor.tr
- ** floor.lefttd
- ** floor.righttd
- ** floor.leftdiv
- ** floor.titlediv
- ** floor.titlestrong
- ** floor.metadiv
- ** floor.replydiv
- */
- function getAllFloors(parent=main) {
- const avatars = $All(parent, 'table div img.avatar');
- const floors = [];
- for (const avt of avatars) {
- const floor = {};
- floor.leftdiv = avt.parentElement;
- floor.lefttd = floor.leftdiv.parentElement;
- floor.tr = floor.lefttd.parentElement
- floor.righttd = floor.tr.children[1];
- floor.titlediv = floor.righttd.children[0];
- floor.titlestrong = floor.titlediv.children[0];
- floor.metadiv = floor.righttd.children[1];
- floor.replydiv = floor.righttd.children[3];
- floor.hrefa = $(floor.metadiv, 'a[name]');
- floor.tbody = floor.tr.parentElement;
- floor.table = floor.tbody.parentElement;
- floor.rid = Number(getUrlArgv({url: parent.url || location.href, name: 'rid'}));
- floor.aid = Number($(parent, 'td[width]>a').href.match(/(\d+)\.html?$/)[1]);
- floor.page = Number($(avt.ownerDocument, '#pagelink strong').innerText);
- floor.pagehref = URL_REVIEWSHOW.replace('{R}', floor.rid.toString()).replace('{A}', floor.aid.toString()).replace('{P}', floor.page.toString());
- floor.href = URL_REVIEWSHOW_5.replace('{R}', floor.rid.toString()).replace('{A}', floor.aid.toString()).replace('{P}', floor.page.toString()).replace('{Y}', floor.hrefa.name);
- floors.push(floor);
- }
- return floors;
- }
-
- // Validate a <table> element whether is a floor
- function isFloorTable(tbl) {
- return $(tbl, 'a[href*="#yid"][name^="yid"]') ? true : false;
- }
-
- // Get floor title element (<strong>)
- // Argv: <table> element of the floor
- function getEleFloorTitle(tblfloor) {
- return $(tblfloor, 'td.even>div:first-child>strong'); // or :nth-child(1)
- }
-
- // Get floor content element (<div>)
- // Argv: <table> element of the floor
- function getEleFloorContent(tblfloor) {
- return $(tblfloor, 'td.even>hr+div');
- }
-
- // Get the floor number
- // Argv: <table> element of the floor
- function getFloorNumber(tblfloor) {
- const eleNumber = $(tblfloor, 'a[name^="yid"]');
- return eleNumber ? Number(eleNumber.innerText.match(/\d+/)[0]) : false;
- }
-
- // Get floor content by BBCode format (content only, no title)
- // Argv: <div> content Element
- function getFloorContent(contentEle, original=false) {
- const subNodes = contentEle.childNodes;
- let content = '';
-
- for (const node of subNodes) {
- const type = node.nodeName;
- switch (type) {
- case '#text':
- // Prevent 'Quote:' repeat
- content += node.data.replace(/^\s*Quote:\s*$/, ' ');
- break;
- case 'IMG':
- // wenku8 has forbidden [img] tag for secure reason (preventing CSRF)
- //content += '[img]S[/img]'.replace('S', node.src);
- content += original ? '[img]S[/img]'.replace('S', node.src) : ' S '.replace('S', node.src);
- break;
- case 'A':
- content += '[url=U]T[/url]'.replace('U', node.getAttribute('href')).replace('T', getFloorContent(node));
- break;
- case 'BR':
- // no need to add \n, because \n will be preserved in #text nodes
- //content += '\n';
- break;
- case 'DIV':
- if (node.classList.contains('jieqiQuote')) {
- content += getTagedSubcontent('quote', node);
- } else if (node.classList.contains('jieqiCode')) {
- content += getTagedSubcontent('code', node);
- } else if (node.classList.contains('divimage')) {
- content += getFloorContent(node, original);
- } else {
- content += getFloorContent(node, original);
- }
- break;
- case 'CODE': content += getFloorContent(node, original); break; // Just ignore
- case 'PRE': content += getFloorContent(node, original); break; // Just ignore
- case 'SPAN': content += getFontedSubcontent(node); break; // Size and color
- case 'P': content += getFontedSubcontent(node); break; // Text Align
- case 'B': content += getTagedSubcontent('b', node); break;
- case 'I': content += getTagedSubcontent('i', node); break;
- case 'U': content += getTagedSubcontent('u', node); break;
- case 'DEL': content += getTagedSubcontent('d', node); break;
- default: content += getFloorContent(node, original); break;
- /*
- case 'SPAN':
- subContent = getFloorContent(node);
- size = node.style.fontSize.match(/\d+/) ? node.style.fontSize.match(/\d+/)[0] : '';
- color = node.style.color.match(/rgb\((\d+), ?(\d+), ?(\d+)\)/);
- break;
- */
- }
- }
- return content;
-
- function getTagedSubcontent(tag, node) {
- const subContent = getFloorContent(node, original);
- return '[{T}]{S}[/{T}]'.replaceAll('{T}', tag).replaceAll('{S}', subContent);
- }
-
- function getFontedSubcontent(node) {
- let tag, value;
-
- let strSize = node.style.fontSize.match(/\d+/);
- let strColor = node.style.color;
- let strAlign = node.align;
- strSize = strSize ? strSize[0] : null;
- strColor = strColor ? rgbToHex.apply(null, strColor.match(/\d+/g)) : null;
-
- tag = tag || (strSize ? 'size' : null);
- tag = tag || (strColor ? 'color' : null);
- tag = tag || (strAlign ? 'align' : null);
- value = value || strSize || null;
- value = value || strColor || null;
- value = value || strAlign || null;
-
- const subContent = getFloorContent(node, original);
- if (tag && value) {
- return '[{T}={V}]{S}[/{T}]'.replaceAll('{T}', tag).replaceAll('{V}', value).replaceAll('{S}', subContent);
- } else {
- return subContent;
- }
- }
- }
-
- // Append floor to #content
- function appendfloor(floor) {
- // Append
- const table = floor.table;
- const elmafter = $(main, 'table.grid+table[border]');
- main.insertBefore(table, elmafter);
-
- // Enhances
- correctFloorLink(floor);
- alinkEdit(floor);
- addQuoteBtn(floor);
- addQueryBtn(floor);
- addRemark(floor);
- alinktofloor(floor.table);
- }
- }
-
- // Bookcase page add-on
- function pageBookcase() {
- // Get auto-recommend config
- let arConfig = CONFIG.AutoRecommend.getConfig();
- // Get bookcase lists
- const bookCaseURL = `https://${location.host}/modules/article/bookcase.php?classid={CID}`;
- const content = $('#content');
- const selector = $('[name="classlist"]');
- const options = selector.children;
- // Current bookcase
- const curForm = $(content, '#checkform');
- const curClassid = Number($('[name="clsssid"]').value);
- // Init bookcase config if need
- initPreferences();
- const bookcases = CONFIG.bookcasePrefs.getConfig().bookcases;
- addTopTitle();
- decorateForm(curForm, bookcases[curClassid]);
-
- // gowork
- laterReads();
- showBookcases();
- recommendAllGUI();
-
- function recommendAllGUI() {
- const block = createWenkuBlock({
- type: 'mypage',
- parent: '#left',
- title: TEXT_GUI_BOOKCASE_ATRCMMD,
- items: [
- {innerHTML: arConfig.allCount === 0 ? TEXT_GUI_BOOKCASE_RCMMDNW_NOTASK : (TASK.AutoRecommend.checkRcmmd() ? TEXT_GUI_BOOKCASE_RCMMDNW_DONE : TEXT_GUI_BOOKCASE_RCMMDNW_NOTYET), id: 'arstatus'},
- {innerHTML: TEXT_GUI_BOOKCASE_RCMMDAT, id: 'autorcmmd'},
- {innerHTML: TEXT_GUI_BOOKCASE_RCMMDNW, id: 'rcmmdnow'}
- ]
- });
-
- // Configure buttons
- const ulitm = $(block, '.ulitem');
- const txtst = $(block, '#arstatus');
- const btnAR = $(block, '#autorcmmd');
- const btnRN = $(block, '#rcmmdnow');
- const txtAR = $(block, 'span');
- const checkbox = $CrE('input');
- txtst.classList.add(CLASSNAME_TEXT);
- btnAR.classList.add(CLASSNAME_BUTTON);
- btnRN.classList.add(CLASSNAME_BUTTON);
- checkbox.type = 'checkbox';
- checkbox.checked = arConfig.auto;
- checkbox.addEventListener('click', onclick);
- btnAR.addEventListener('click', onclick);
- btnAR.appendChild(checkbox);
- btnRN.addEventListener('click', rcmmdnow);
-
- function onclick(e) {
- destroyEvent(e);
- arConfig.auto = !arConfig.auto;
- setTimeout(function() {checkbox.checked = arConfig.auto;}, 0);
- CONFIG.AutoRecommend.saveConfig(arConfig);
- alertify.notify(arConfig.auto ? TEXT_ALT_ATRCMMDS_AUTO : TEXT_ALT_ATRCMMDS_NOAUTO);
- }
-
- function rcmmdnow() {
- if (TASK.AutoRecommend.checkRcmmd() && !confirm(TEXT_GUI_BOOKCASE_RCMMDNW_CONFIRM)) {return false;}
- if (arConfig.allCount === 0) {alertify.warning(TEXT_ALT_ATRCMMDS_NOTASK); return false;};
- TASK.AutoRecommend.run(true);
- }
- }
-
- function initPreferences() {
- const config = CONFIG.bookcasePrefs.getConfig();
- if (config.bookcases.length === 0) {
- for (const option of options) {
- config.bookcases.push({
- classid: Number(option.value),
- url: bookCaseURL.replace('{CID}', String(option.value)),
- name: option.innerText
- });
- }
- CONFIG.bookcasePrefs.saveConfig(config);
- }
- }
-
- function addTopTitle() {
- // Clone title bar
- const checkform = $('#checkform') ? $('#checkform') : $('.'+CLASSNAME_BOOKCASE_FORM);
- const oriTitle = $(checkform, 'div.gridtop');
- const topTitle = oriTitle.cloneNode(true);
- content.insertBefore(topTitle, checkform);
-
- // Hide bookcase selector
- const bcSelector = $(topTitle, '[name="classlist"]');
- bcSelector.style.display = 'none';
-
- // Write title text
- const textNode = topTitle.childNodes[0];
- const numMatch = textNode.nodeValue.match(/\d+/g);
- const text = TEXT_GUI_BOOKCASE_TOPTITLE.replace('A', numMatch[0]).replace('B', numMatch[1]);
- textNode.nodeValue = text;
- }
-
- function showBookcases() {
- // GUI
- const topTitle = $(content, 'script+div.gridtop');
- const textNode = topTitle.childNodes[0];
- const oriTitleText = textNode.nodeValue;
- const allCount = bookcases.length;
- let finished = 1;
- textNode.nodeValue = TEXT_GUI_BOOKCASE_GETTING.replace('C', String(finished)).replace('A', String(allCount));
-
- // Get all bookcase pages
- for (const bookcase of bookcases) {
- if (bookcase.classid === curClassid) {continue;};
- getDocument(bookcase.url, appendBookcase, [bookcase]);
- }
-
- function appendBookcase(mDOM, bookcase) {
- const classid = bookcase.classid;
-
- // Get bookcase form and modify it
- const form = $(mDOM, '#checkform');
- form.parentElement.removeChild(form);
-
- // Find the right place to insert it in
- const forms = $All(content, '.'+CLASSNAME_BOOKCASE_FORM);
- for (let i = 0; i < forms.length; i++) {
- const thisForm = forms[i];
- const cid = typeof thisForm.classid === 'number' ? thisForm.classid : curClassid;
- if (cid > classid) {
- content.insertBefore(form, thisForm);
- break;
- }
- }
- if(!form.parentElement) {$('#laterbooks').insertAdjacentElement('beforebegin', form);};
-
- // Decorate
- decorateForm(form, bookcase);
-
- // finished increase
- finished++;
- textNode.nodeValue = finished < allCount ?
- TEXT_GUI_BOOKCASE_GETTING.replace('C', String(finished)).replace('A', String(allCount)) :
- oriTitleText;
- }
- }
-
- function decorateForm(form, bookcase) {
- const classid = bookcase.classid;
- let name = bookcase.name;
-
- // Provide auto-recommand button
- arBtn();
-
- // Modify properties
- form.classList.add(CLASSNAME_BOOKCASE_FORM);
- form.id += String(classid);
- form.classid = classid;
- form.onsubmit = my_check_confirm;
-
- // Hide bookcase selector
- const bcSelector = $(form, '[name="classlist"]');
- bcSelector.style.display = 'none';
-
- // Dblclick Change title
- const titleBar = bcSelector.parentElement;
- titleBar.childNodes[0].nodeValue = name;
- titleBar.addEventListener('dblclick', editName);
- // Longpress Change title for mobile
- let touchTimer;
- titleBar.addEventListener('touchstart', () => {touchTimer = setTimeout(editName, 500);});
- titleBar.addEventListener('touchmove', () => {clearTimeout(touchTimer);});
- titleBar.addEventListener('touchend', () => {clearTimeout(touchTimer);});
- titleBar.addEventListener('mousedown', () => {touchTimer = setTimeout(editName, 500);});
- titleBar.addEventListener('mouseup', () => {clearTimeout(touchTimer);});
-
- // Show tips
- let tip = TEXT_GUI_BOOKCASE_DBLCLICK;
- if (tipready) {
- // tipshow and tiphide is coded inside wenku8 itself, its function is to show a text tip besides the mouse
- titleBar.addEventListener('mouseover', function() {tipshow(tip);});
- titleBar.addEventListener('mouseout' , tiphide);
- } else {
- titleBar.title = tip;
- }
-
- // Change selector names
- renameSelectors(false);
-
- // Replaces the original check_confirm() function
- function my_check_confirm() {
- const checkform = this;
- let checknum = 0;
- for (let i = 0; i < checkform.elements.length; i++){
- if (checkform.elements[i].name == 'checkid[]' && checkform.elements[i].checked == true) checknum++;
- }
- if (checknum === 0){
- alert('请先选择要操作的书目!');
- return false;
- }
- const newclassid = $(checkform, '#newclassid');
- if(newclassid.value == -1){
- if (confirm('确实要将选中书目移出书架么?')) {return true;} else {return false;};
- } else {
- return true;
- }
- }
-
- // Selector name refresh
- function renameSelectors(renameAll) {
- if (renameAll) {
- const forms = $All(content, '.'+CLASSNAME_BOOKCASE_FORM);
- for (const form of forms) {
- renameFormSlctr(form);
- }
- } else {
- renameFormSlctr(form);
- }
-
- function renameFormSlctr(form) {
- const newclassid = $(form, '#newclassid');
- const options = newclassid.children;
- for (let i = 0; i < options.length; i++) {
- const option = options[i];
- const value = Number(option.value);
- const bc = bookcases[value];
- bc ? option.innerText = TEXT_GUI_BOOKCASE_MOVEBOOK.replace('N', bc.name) : function(){};
- }
- }
- }
-
- // Provide <input> GUI to edit bookcase name
- function editName() {
- const nameInput = $CrE('input');
- const form = this;
- tip = TEXT_GUI_BOOKCASE_WHATNAME;
- tipready ? tipshow(tip) : function(){};
-
- titleBar.childNodes[0].nodeValue = '';
- titleBar.appendChild(nameInput);
- nameInput.value = name;
- nameInput.addEventListener('blur', onblur);
- nameInput.addEventListener('keydown', onkeydown)
- nameInput.focus();
- nameInput.setSelectionRange(0, name.length);
-
- function onblur() {
- tip = TEXT_GUI_BOOKCASE_DBLCLICK;
- tipready ? tipobj.innerHTML = tip : function(){};
- const value = nameInput.value.trim();
- if (value) {
- name = value;
- bookcase.name = name;
- CONFIG.bookcasePrefs.saveConfig(bookcases);
- }
- titleBar.childNodes[0].nodeValue = name;
- try {titleBar.removeChild(nameInput)} catch (DOMException) {};
- renameSelectors(true);
- }
-
- function onkeydown(e) {
- if (e.keyCode === 13) {
- e.preventDefault();
- onblur();
- }
- }
- }
-
- // Provide auto-recommend option
- function arBtn() {
- const table = $(form, 'table');
- for (const tr of $All(table, 'tr')) {
- $(tr, '.odd') ? decorateRow(tr) : function() {};
- $(tr, 'th') ? decorateHeader(tr) : function() {};
- $(tr, 'td.foot') ? decorateFooter(tr) : function() {};
- }
-
- // Insert auto-recommend option for given row
- function decorateRow(tr) {
- const eleBookLink = $(tr, 'td:nth-child(2)>a');
- const strBookID = eleBookLink.href.match(/aid=(\d+)/)[1];
- const strBookName = eleBookLink.innerText;
- const newTd = $CrE('td');
- const input = $CrE('input');
- newTd.classList.add('odd');
- input.type = 'number';
- input.inputmode = 'numeric';
- input.style.width = '85%';
- input.value = arConfig.books[strBookID] ? String(arConfig.books[strBookID].number) : '0';
- input.addEventListener('change', onvaluechange);
- input.strBookID = strBookID; input.strBookName = strBookName;
- newTd.appendChild(input); tr.appendChild(newTd);
- }
-
- // Insert a new row for auto-recommend options
- function decorateHeader(tr) {
- const allTh = $All(tr, 'th');
- const width = ARR_GUI_BOOKCASE_WIDTH;
- const newTh = $CrE('th');
- newTh.innerText = TEXT_GUI_BOOKCASE_ATRCMMD;
- newTh.classList.add(CLASSNAME_TEXT);
- tr.appendChild(newTh);
- for (let i = 0; i < allTh.length; i++) {
- const th = allTh[i];
- th.style.width = width[i];
- }
- }
-
- // Fit the width
- function decorateFooter(tr) {
- const td = $(tr, 'td.foot');
- td.colSpan = ARR_GUI_BOOKCASE_WIDTH.length;
- }
-
- // auto-recommend onvaluechange
- function onvaluechange(e) {
- arConfig = CONFIG.AutoRecommend.getConfig();
- const input = e.target;
- const value = input.value;
- const strBookID = input.strBookID;
- const strBookName = input.strBookName;
- const bookID = Number(strBookID);
- const userDetail = getMyUserDetail() ? getMyUserDetail().userDetail : refreshMyUserDetail();
- if (isNumeric(value, true) && Number(value) >= 0) {
- // allCount increase
- const oriNum = arConfig.books[strBookID] ? arConfig.books[strBookID].number : 0;
- const number = Number(value);
- arConfig.allCount += number - oriNum;
-
- // save to config
- number > 0 ? arConfig.books[strBookID] = {number: number, name: strBookName, id: bookID} : delete arConfig.books[strBookID];
- CONFIG.AutoRecommend.saveConfig(arConfig);
-
- // alert
- alertify.notify(
- TEXT_ALT_ATRCMMDS_SAVED
- .replaceAll('{B}', strBookName)
- .replaceAll('{N}', value)
- .replaceAll('{R}', userDetail.vote-arConfig.allCount)
- );
- if (userDetail && arConfig.allCount > userDetail.vote) {
- const alertBox = alertify.warning(
- TEXT_ALT_ATRCMMDS_OVERFLOW
- .replace('{V}', String(userDetail.vote))
- .replace('{C}', String(arConfig.allCount))
- );
- alertBox.callback = function(isClicked) {
- isClicked && refreshMyUserDetail();
- }
- };
- } else {
- // invalid input value, alert
- alertify.error(TEXT_ALT_ATRCMMDS_INVALID.replaceAll('{N}', value));
- }
- }
- }
- }
-
- function laterReads() {
- // Container
- const container = $CrE('div');
- container.id = 'laterbooks';
- content.appendChild(container);
-
- // Title div
- const titlebar = $CrE('div');
- titlebar.classList.add('gridtop');
- titlebar.style.display = 'grid';
- titlebar.style['grid-template-columns'] = '1fr 1fr 1fr';
- container.appendChild(titlebar);
-
- const title = $CrE('span');
- title.innerHTML = '稍后再读';
- title.style['grid-column'] = '2/3';
- titlebar.appendChild(title);
-
- // Sorter select container
- const sortContainer = $CrE('span');
- sortContainer.style['grid-column'] = '3/4';
- sortContainer.style.textAlign = 'right';
- titlebar.appendChild(sortContainer);
-
- // Sorter select
- const sltsort = $CrE('select');
- sltsort.style.width = 'max-content';
- sltsort.addEventListener('change', function() {
- const config = CONFIG.bookcasePrefs.getConfig();
- config.laterbooks.sortby = sltsort.value;
- CONFIG.bookcasePrefs.saveConfig(config);
- showBooks();
- });
- sortContainer.appendChild(sltsort);
-
- // Sorter select options
- const sorttypes = Object.keys(FUNC_LATERBOOK_SORTERS);
- for (const type of sorttypes) {
- const sort = FUNC_LATERBOOK_SORTERS[type];
- const option = $CrE('option');
- option.innerHTML = sort.name;
- option.value = type;
- sltsort.appendChild(option);
- }
- sltsort.selectedIndex = sorttypes.indexOf(CONFIG.bookcasePrefs.getConfig().laterbooks.sortby);
-
- // Body table
- const body = $CrE('table');
- setAttributes(body, {
- 'class': 'grid',
- 'width': '100%',
- 'align': 'center'
- });
- const tbody = $CrE('tbody');
- body.appendChild(tbody);
- container.appendChild(body);
-
- // Header & Rows
- showBooks();
-
- function showBooks() {
- const config = CONFIG.bookcasePrefs.getConfig().laterbooks;
- clearChildnodes(body);
-
- // headers
- const headtr = $CrE('tr');
- headtr.setAttribute('align', 'center');
- const headers = [{
- name: '名称',
- width: '22%'
- },{
- name: '简介',
- width: '60%'
- },{
- name: '操作',
- width: '18%'
- }];
- for (const head of headers) {
- const th = $CrE('th');
- th.innerHTML = head.name;
- th.style.width = head.width;
- headtr.appendChild(th);
- }
- body.appendChild(headtr);
-
- // Book rows
- const books = sortLaterReads(config.books, config.sortby);
-
- for (const book of books) {
- makeRow(book);
- }
-
- function makeRow(book) {
- const config = CONFIG.bookcasePrefs.getConfig().laterbooks;
-
- // row
- const row = $CrE('tr');
-
- // cover & name
- const tdName = $CrE('td');
- tdName.classList.add('odd');
- tdName.style.textAlign = 'center';
- const clink = $CrE('a');
- clink.href = URL_NOVELINDEX.replace('{I}', book.aid);
- clink.target = '_blank';
- tdName.appendChild(clink);
- const cover = $CrE('img');
- cover.src = book.cover;
- cover.style.width = '100px';
- clink.appendChild(cover);
- clink.insertAdjacentHTML('beforeend', '</br>');
- clink.insertAdjacentText('beforeend', book.name);
- row.appendChild(tdName);
-
- // info
- const tdInfo = $CrE('td');
- tdInfo.classList.add('even');
- tdInfo.insertAdjacentHTML('afterbegin', '<span class="hottext">作品Tags:</span></br>');
- for (const tag of book.tags) {
- const a = $CrE('a');
- a.target = '_blank';
- a.href = URL_TAGSEARCH.replace('{TU}', $URL.encode(tag));
- a.classList.add(CLASSNAME_BUTTON);
- a.innerText = tag + ' ';
- tdInfo.appendChild(a);
- }
- tdInfo.insertAdjacentHTML('beforeend', '</br></br><span class="hottext">内容简介:</span></br>');
- tdInfo.insertAdjacentText('beforeend', book.introduce);
- row.appendChild(tdInfo);
-
- // operator
- const tdOprt = $CrE('td');
- tdOprt.classList.add('odd');
- tdOprt.style.textAlign = 'center';
- const btnDel = makeBtn();
- btnDel.innerHTML = '删除';
- btnDel.addEventListener('click', del);
- tdOprt.appendChild(btnDel);
- const btnAbc = makeBtn('a'); // Abc ==> AddBookCase
- btnAbc.innerHTML = '加入书架';
- btnAbc.href = URL_ADDBOOKCASE.replace('{A}', book.aid);
- btnAbc.target = '_blank';
- tdOprt.appendChild(btnAbc);
- if (config.sortby === 'sort') {
- tdOprt.appendChild($CrE('br'));
- const btnUp = makeBtn();
- btnUp.innerHTML = '上移';
- btnUp.addEventListener('click', function () {
- const config = CONFIG.bookcasePrefs.getConfig();
- const books = Object.values(config.laterbooks.books);
- const cur = books.filter((b) => (b.sort === book.sort));
- const previous = books.filter((b) => (b.sort === book.sort-1));
-
- if (cur) {
- if (previous.length > 0) {
- previous[0].sort++;
- cur[0].sort--;
- CONFIG.bookcasePrefs.saveConfig(config);
- showBooks();
- }
- } else {
- alertify.warning(TEXT_ALT_BOOKCASE_AFTERBOOKS_MISSING);
- }
- });
- tdOprt.appendChild(btnUp);
- const btnDown = makeBtn();
- btnDown.innerHTML = '下移';
- btnDown.addEventListener('click', function () {
- const config = CONFIG.bookcasePrefs.getConfig();
- const books = Object.values(config.laterbooks.books);
- const cur = books.filter((b) => (b.sort === book.sort));
- const after = books.filter((b) => (b.sort === book.sort+1));
-
- if (cur) {
- if (after.length > 0) {
- after[0].sort--;
- cur[0].sort++;
- CONFIG.bookcasePrefs.saveConfig(config);
- showBooks();
- }
- } else {
- alertify.warning(TEXT_ALT_BOOKCASE_AFTERBOOKS_MISSING);
- }
- });
- tdOprt.appendChild(btnDown);
- }
- row.appendChild(tdOprt);
-
- body.appendChild(row);
-
- function del() {
- const config = CONFIG.bookcasePrefs.getConfig();
- const books = config.laterbooks.books;
- const bk = books[book.aid];
- if (!bk) {
- body.removeChild(row);
- return false;
- }
- delete config.laterbooks.books[book.aid];
- Array.prototype.forEach.call(Object.values(books), (b) => (b.sort > bk.sort && b.sort--));
- CONFIG.bookcasePrefs.saveConfig(config);
- body.removeChild(row);
- }
-
- function makeBtn(tagName='span') {
- const btn = $CrE(tagName);
- btn.classList.add(CLASSNAME_BUTTON);
- btn.style.margin = '0 1em';
- return btn;
- }
- }
- }
- }
-
- // Set attributes to an element
- function setAttributes(elm, attributes) {
- for (const [name, attr] of Object.entries(attributes)) {
- elm.setAttribute(name, attr);
- }
- }
- }
-
- // Novel ads remover
- function removeTopAds() {
- const ads = []; $All('div>script+script+a').forEach(function(a) {ads.push(a.parentElement);});
- for (const ad of ads) {
- ad.parentElement.removeChild(ad);
- }
- }
-
- // Novel index page add-on
- function pageNovelIndex() {
- removeTopAds();
- //downloader();
-
- function downloader() {
- AndAPI.getNovelIndex({
- aid: unsafeWindow.article_id,
- lang: 0,
- callback: indexGot
- });
-
- function indexGot(xml) {
- const volumes = $All(xml, 'volume');
- const vtitles = $All('.vcss');
- if (volumes.length !== vtitles.length) {return false;}
-
- for (let i = 0; i < volumes.length; i++) {
- const volume = volumes[i];
- const vtitle = vtitles[i];
- const vname = volume.childNodes[0].nodeValue;
-
- // Title element
- const elmTitle = $CrE('span');
- elmTitle.innerText = vname;
-
- // Spliter element
- const elmSpliter = $CrE('span');
- elmSpliter.style.margin = '0 0.5em';
-
- // Download button
- const elmDlBtn = $CrE('span');
- elmDlBtn.classList.add(CLASSNAME_BUTTON);
- elmDlBtn.innerHTML = TEXT_GUI_DOWNLOAD_THISVOLUME;
- elmDlBtn.addEventListener('click', function() {
- // getAttribute returns string rather than number,
- // but downloadVolume accepts both string and number as vid
- downloadVolume(volume.getAttribute('vid'), vname, ['utf-8', 'big5'][getLang()]);
- });
-
- clearChildnodes(vtitle);
- vtitle.appendChild(elmTitle);
- vtitle.appendChild(elmSpliter);
- vtitle.appendChild(elmDlBtn);
- }
- }
-
- function downloadVolume(vid, vname, charset='utf-8') {
- const url = URL_DOWNLOAD1.replace('{A}', unsafeWindow.article_id).replace('{V}', vid).replace('{C}', charset);
- downloadFile({
- url: url,
- name: TEXT_GUI_SDOWNLOAD_FILENAME
- .replace('{NovelName}', $('#title').innerText)
- .replace('{VolumeName}', vname)
- .replace('{Extension}', 'txt')
- });
- }
- }
- }
-
- // Novel page add-on
- function pageNovel() {
- const CSM = new ConfigSetManager();
- CSM.install();
- const pageResource = {elements: {}, infos: {}, download: {}};
- collectPageResources();
-
- // Remove ads
- removeTopAds();
-
- // Side-Panel buttons
- sideButtons();
-
- // Provide download GUI
- downloadGUI();
-
- // Prevent URL.revokeObjectURL in script 轻小说文库下载
- revokeObjectURLHOOK();
-
- // Font changer
- fontChanger();
-
- // More font-sizes
- moreFontSizes();
-
- // Fill content if need
- fillContent();
-
- // Beautifier page
- beautifier();
-
- function collectPageResources() {
- collectElements();
- collectInfos();
- initDownload();
-
- function collectElements() {
- const elements = pageResource.elements;
- elements.title = $('#title');
- elements.images = $All('.imagecontent');
- elements.rightButtonDiv = $('#linkright');
- elements.rightNodes = elements.rightButtonDiv.childNodes;
- elements.rightBlank = elements.rightNodes[elements.rightNodes.length-1];
- elements.content = $('#content');
- elements.contentmain = $('#contentmain');
- elements.spliterDemo = document.createTextNode(' | ');
- }
- function collectInfos() {
- const elements = pageResource.elements;
- const infos = pageResource.infos;
- infos.title = elements.title.innerText;
- infos.isImagePage = elements.images.length > 0;
- infos.content = infos.isImagePage ? null : elements.content.innerText;
- }
- function initDownload() {
- const elements = pageResource.elements;
- const download = pageResource.download;
- download.running = false;
- download.finished = 0;
- download.all = elements.images.length;
- download.error = 0;
- }
- }
-
- // Prevent URL.revokeObjectURL in script 轻小说文库下载
- function revokeObjectURLHOOK() {
- const Ori_revokeObjectURL = URL.revokeObjectURL;
- URL.revokeObjectURL = function(arg) {
- if (typeof(arg) === 'string' && arg.substr(0, 5) === 'blob:') {return false;};
- return Ori_revokeObjectURL(arg);
- }
- }
-
- // Side-Panel buttons
- function sideButtons() {
- // Download
- SPanel.add({
- faicon: 'fa-solid fa-download',
- tip: TEXT_GUI_DOWNLOAD_THISCHAPTER,
- onclick: dlNovel
- });
-
- // Next page
- SPanel.add({
- faicon: 'fa-solid fa-angle-right',
- tip: '下一页',
- onclick: (e) => {$('#foottext>a:nth-child(4)').click();}
- });
-
- // Previous page
- SPanel.add({
- faicon: 'fa-solid fa-angle-left',
- tip: '上一页',
- onclick: (e) => {$('#foottext>a:nth-child(3)').click();}
- });
- }
-
- // Provide download GUI
- function downloadGUI() {
- const elements = pageResource.elements;
- const infos = pageResource.infos;
-
- // Create donwload button
- const dlBtn = elements.downloadBtn = $CrE('span');
- dlBtn.classList.add(CLASSNAME_BUTTON);
- dlBtn.addEventListener('click', dlNovel);
- dlBtn.innerText = TEXT_GUI_DOWNLOAD_THISCHAPTER;
-
- // Create spliter
- const spliter = elements.spliterDemo.cloneNode();
-
- // Append to rightButtonDiv
- elements.rightButtonDiv.style.width = '550px';
- elements.rightButtonDiv.insertBefore(spliter, elements.rightBlank);
- elements.rightButtonDiv.insertBefore(dlBtn, elements.rightBlank);
- }
-
- // Page beautifier
- function beautifier() {
- CONFIG.BeautifierCfg.getConfig().novel.beautiful && beautiful();
-
- function beautiful() {
- const config = CONFIG.BeautifierCfg.getConfig();
- const usedHeight = getRestHeight();
-
- addStyle(CSS_NOVEL
- .replaceAll('{BGI}', config.backgroundImage)
- .replaceAll('{S}', config.textScale)
- .replaceAll('{H}', usedHeight), 'beautifier'
- );
-
- unsafeWindow.stopScroll = beautiful_stopScroll;
- document.onmousedown = beautiful_stopScroll;
- unsafeWindow.scrolling = beautiful_scrolling;
-
- // Get rest height without #contentmain
- function getRestHeight() {
- let usedHeight = 0;
- ['adv1', 'adtop', 'headlink', 'footlink', 'adbottom'].forEach((id) => {
- const node = $('#'+id);
- if (node instanceof Element && node.id !== 'contentmain') {
- const cs = getComputedStyle(node);
- ['height', 'marginTop', 'marginBottom', 'paddingTop', 'paddingBottom', 'borderTop', 'borderBottom'].forEach((style) => {
- const reg = cs[style].match(/([\.\d]+)px/);
- reg && (usedHeight += Number(reg[1]));
- });
- };
- });
- usedHeight = usedHeight.toString() + 'px';
- return usedHeight;
- }
-
- // Mouse dblclick scroll with beautifier applied
- function beautiful_scrolling() {
- let contentmain = pageResource.elements.contentmain;
- let currentpos = contentmain.scrollTop || 0;
- contentmain.scrollTo(0, ++currentpos);
- let nowpos = contentmain.scrollTop || 0;
- pageResource.elements.content.style.userSelect = 'none';
- currentpos != nowpos && beautiful_stopScroll();
- }
-
- function beautiful_stopScroll() {
- pageResource.elements.content.style.userSelect = '';
- unsafeWindow.clearInterval(timer);
- }
- }
- }
-
- // Provide font changer
- function fontChanger() {
- // Button
- const bcolor = $('#bcolor');
- const txtfont = $CrE('select');
- txtfont.id = 'txtfont';
- txtfont.addEventListener('change', applyFont);
- bcolor.insertAdjacentElement('afterend', txtfont);
- bcolor.insertAdjacentText('afterend', '\t\t\t 字体选择');
-
- // Provided fonts
- const FONTS = [{"name":"默认","value":"unset"}, {"name":"微软雅黑","value":"Microsoft YaHei"},{"name":"黑体","value":"SimHei"},{"name":"微软正黑体","value":"Microsoft JhengHei"},{"name":"宋体","value":"SimSun"},{"name":"仿宋","value":"FangSong"},{"name":"新宋体","value":"NSimSun"},{"name":"细明体","value":"MingLiU"},{"name":"新细明体","value":"PMingLiU"},{"name":"楷体","value":"KaiTi"},{"name":"标楷体","value":"DFKai-SB"}]
- for (const font of FONTS) {
- const option = $CrE('option');
- option.innerText = font.name;
- option.value = font.value;
- txtfont.appendChild(option);
- }
-
- // Function
- CSM.ConfigSets.txtfont = {
- save: () => (setCookies('txtfont', txtfont[txtfont.selectedIndex].value)),
- load: () => {
- const tmpstr = ReadCookies("txtfont");
- if (tmpstr != "") {
- for (let i = 0; i < txtfont.length; i++) {
- if (txtfont.options[i].value == tmpstr) {
- txtfont.selectedIndex = i;
- break;
- }
- }
- }
- applyFont();
- }
- };
-
- // Load saved font
- CSM.ConfigSets.txtfont.load();
-
- function applyFont() {
- $('#content').style['font-family'] = txtfont[txtfont.selectedIndex].value;
- }
- }
-
- // Provide more font-sizes
- function moreFontSizes() {
- const select = $('#fonttype');
- const savebtn = $('#saveset');
- const sizes = [
- {
- name: '更小',
- size: '10px'
- },
- {
- name: '更大',
- size: '28px'
- },
- {
- name: '很大',
- size: '32px'
- },
- {
- name: '超大',
- size: '36px'
- },
- {
- name: '极大',
- size: '40px'
- },
- {
- name: '过大',
- size: '44px'
- },
- ];
-
- for (const size of sizes) {
- const option = $CrE('option');
- option.innerHTML = size.name;
- option.value = size.size;
-
- // Insert with sorting
- for (const opt of select.children) {
- const sizeNum1 = getSizeNum(opt.value);
- const sizeNum2 = getSizeNum(option.value);
- if (isNaN(sizeNum1) || isNaN(sizeNum2)) {continue;} // Code shouldn't be here in normal cases
- if (sizeNum1 > sizeNum2) {
- select.insertBefore(option, opt);
- break;
- }
- }
- option.parentElement !== select && select.appendChild(option);
- }
-
- // Load saved fonttype
- CSM.ConfigSets.fonttype.load();
-
- function getSizeNum(size) {
- return Number(size.match(/(\d+)px/)[1]);
- }
- }
-
- // Provide content using AndroidAPI
- function fillContent() {
- // Check whether needs filling
- if ($('#contentmain>span')) {
- if ($('#contentmain>span').innerText.trim() !== 'null') {
- return false;
- }
- } else {return false;}
-
- // prepare
- const content = pageResource.elements.content;
- content.innerHTML = TEXT_GUI_NOVEL_FILLING;
- const charset = (function() {
- const match = document.cookie.match(/(; *)?jieqiUserCharset=(.+?)( *;|$)/);
- return match && match[2] && match[2].toLowerCase() === 'big5' ? 1 : 0;
- }) ();
-
- // Get content xml
- AndAPI.getNovelContent({
- aid: unsafeWindow.article_id,
- cid: unsafeWindow.chapter_id,
- lang: charset,
- callback: function(text) {
- const imgModel = '<div class="divimage"><a href="{U}" target="_blank"><img src="{U}" border="0" class="imagecontent"></a></div>';
-
- // Trim whitespaces
- text = text.trim();
-
- // Get images like <!--image-->http://pic.wenku8.com/pictures/0/716/24406/11588.jpg<!--image-->
- const imgUrls = text.match(/<!--image-->[^<>]+?<!--image-->/g) || [];
-
- // Parse <img> for every image url
- let html = '';
- for (const url of imgUrls) {
- const index = text.indexOf(url);
- const src = htmlEncode(url.match(/<!--image-->([^<>]+?)<!--image-->/)[1]);
- html += htmlEncode(text.substring(0, index)).replaceAll('\r\n', '\n').replaceAll('\r', '\n').replaceAll('\n', '</br>');
- html += imgModel.replaceAll('{U}', src);
- text = text.substring(index + url.length);
- }
- html += htmlEncode(text);
-
- // Set content
- pageResource.elements.content.innerHTML = html;
-
- // Reset pageResource-image if need
- pageResource.infos.isImagePage = imgUrls.length > 0;
- pageResource.elements.images = $All('.imagecontent');
- pageResource.download.all = pageResource.elements.images.length;
- }
- })
-
- return true;
- }
-
- // Download button onclick
- function dlNovel() {
- pageResource.infos.isImagePage ? dlNovelImages() : dlNovelText();
- }
-
- // Download Images
- function dlNovelImages() {
- const elements = pageResource.elements;
- const infos = pageResource.infos;
- const download = pageResource.download;
-
- if (download.running) {return false;};
- download.running = true; download.finished = 0; download.error = 0;
- updateDownloadStatus();
-
- const lenNumber = String(elements.images.length).length;
- for (let i = 0; i < elements.images.length; i++) {
- const img = elements.images[i];
- const name = infos.title + '_' + fillNumber(i+1, lenNumber) + '.jpg';
- GM_xmlhttpRequest({
- url: img.src,
- responseType: 'blob',
- onloadstart: function() {
- DoLog(LogLevel.Info, '[' + String(i) + ']downloading novel image from ' + img.src);
- },
- onload: function(e) {
- DoLog(LogLevel.Info, '[' + String(i) + ']image got: ' + img.src);
-
- const image = new Image();
- image.onload = function() {
- const url = toImageFormatURL(image, 1);
- DoLog(LogLevel.Info, '[' + String(i) + ']image transformed: ' + img.src);
-
- const a = $CrE('a');
- a.href = url;
- a.download = name;
- a.click();
-
- download.finished++;
- updateDownloadStatus();
- // Code below seems can work, but actually it doesn't work well and somtimes some file cannot be saved
- // The reason is still unknown, but from what I know I can tell that mistakes happend in GM_xmlhttpRequest
- // Error stack: GM_xmlhttpRequest.onload ===> image.onload ===> downloadFile ===> GM_xmlhttpRequest =X=> .onload
- // This Error will also stuck the GMXHRHook.ongoingList
- /*downloadFile({
- url: url,
- name: name,
- onload: function() {
- download.finished++;
- DoLog(LogLevel.Info, '[' + String(i) + ']file saved: ' + name);
- alert('[' + String(i) + ']file saved: ' + name);
- updateDownloadStatus();
- },
- onerror: function() {
- alert('downloadfile error! url = ' + String(url) + ', i = ' + String(i));
- }
- })*/
- }
- image.onerror = function() {
- throw new Error('image load error! image.src = ' + String(image.src) + ', i = ' + String(i));
- }
- image.src = URL.createObjectURL(e.response);
- },
- onerror: function(e) {
- // Error dealing need...
- DoLog(LogLevel.Error, '[' + String(i) + ']image fetch error: ' + img.src);
- download.error++;
- }
- })
- }
-
- function updateDownloadStatus() {
- elements.downloadBtn.innerText = TEXT_GUI_DOWNLOADING_ALL.replaceAll('C', String(download.finished)).replaceAll('A', String(download.all));
- if (download.finished === download.all) {
- DoLog(LogLevel.Success, 'All images got.');
- elements.downloadBtn.innerText = TEXT_GUI_DOWNLOADED_ALL;
- download.running = false;
- }
- }
- }
-
- // Download Text
- function dlNovelText() {
- const infos = pageResource.infos;
- const name = infos.title + '.txt';
- const text = infos.content.replaceAll(/[\r\n]+/g, '\r\n');
- downloadText(text, name);
- }
-
- // Image format changing function
- // image: <img> or Image(); format: 1 for jpeg, 2 for png, 3 for webp
- function toImageFormatURL(image, format) {
- if (typeof(format) === 'number') {format = ['image/jpeg', 'image/png', 'image/webp'][format-1]}
- const cvs = $CrE('canvas');
- cvs.width = image.width;
- cvs.height = image.height;
- const ctx = cvs.getContext('2d');
- ctx.drawImage(image, 0, 0);
- return cvs.toDataURL(format);
- }
-
- function ConfigSetManager() {
- const CSM = this;
- /*const setCookies = unsafeWindow.setCookies,
- ReadCookies = unsafeWindow.ReadCookies,
- bcolor = unsafeWindow.bcolor,
- txtcolor = unsafeWindow.txtcolor,
- fonttype = unsafeWindow.fonttype,
- scrollspeed = unsafeWindow.scrollspeed,
- setSpeed = unsafeWindow.setSpeed,
- contentobj = unsafeWindow.contentobj;*/
-
- CSM.ConfigSets = {
- 'bcolor': {
- save: () => (setCookies("bcolor", bcolor.options[bcolor.selectedIndex].value)),
- load: () => {
- const tmpstr = ReadCookies("bcolor");
- bcolor.selectedIndex = 0;
- if (tmpstr != "") {
- for (let i = 0; i < bcolor.length; i++) {
- if (bcolor.options[i].value == tmpstr) {
- bcolor.selectedIndex = i;
- break;
- }
- }
- }
- document.bgColor = bcolor.options[bcolor.selectedIndex].value;
- }
- },
- 'txtcolor': {
- save: () => (setCookies("txtcolor", txtcolor.options[txtcolor.selectedIndex].value)),
- load: () => {
- const tmpstr = ReadCookies("txtcolor");
- txtcolor.selectedIndex = 0;
- if (tmpstr != "") {
- for (let i = 0; i < txtcolor.length; i++) {
- if (txtcolor.options[i].value == tmpstr) {
- txtcolor.selectedIndex = i;
- break;
- }
- }
- }
- $('#content').style.color = txtcolor.options[txtcolor.selectedIndex].value;
- }
- },
- 'fonttype': {
- save: () => (setCookies("fonttype", fonttype.options[fonttype.selectedIndex].value)),
- load: () => {
- const tmpstr = ReadCookies("fonttype");
- fonttype.selectedIndex = 2;
- if (tmpstr != "") {
- for (let i = 0; i < fonttype.length; i++) {
- if (fonttype.options[i].value == tmpstr) {
- fonttype.selectedIndex = i;
- break;
- }
- }
- }
- $('#content').style.fontSize = fonttype.options[fonttype.selectedIndex].value;
- }
- },
- 'scrollspeed': {
- save: () => (setCookies("scrollspeed", scrollspeed.value)),
- load: () => {
- const tmpstr = ReadCookies("scrollspeed");
- if (tmpstr == '') {tmpstr = 5;}
- scrollspeed.value = tmpstr;
- setSpeed();
- }
- }
- };
-
- CSM.saveSet = function() {
- for (const [name, set] of Object.entries(CSM.ConfigSets)) {
- set.save();
- }
- };
-
- CSM.loadSet = function() {
- for (const [name, set] of Object.entries(CSM.ConfigSets)) {
- set.load();
- }
- };
-
- CSM.install = function() {
- Object.defineProperty(unsafeWindow, 'saveSet', {
- configurable: false,
- enumerable: true,
- value: CSM.saveSet,
- writable: false
- });
- Object.defineProperty(unsafeWindow, 'loadSet', {
- configurable: false,
- enumerable: true,
- value: CSM.loadSet,
- writable: false
- });
- };
- }
- }
-
- // Search form add-on
- function formSearch() {
- const searchForm = $('form[name="articlesearch"]');
- if (!searchForm) {return false;};
- const typeSelect = $(searchForm, '#searchtype');
- const searchText = $(searchForm, '#searchkey');
- const searchSbmt = $(searchForm, 'input[class="button"][type="submit"]');
-
- let optionTags;
- provideTagOption();
- onsubmitHOOK();
-
- function provideTagOption() {
- optionTags = $CrE('option');
- optionTags.value = VALUE_STR_NULL;
- optionTags.innerText = TEXT_GUI_SEARCH_OPTION_TAG;
- typeSelect.appendChild(optionTags);
-
- if (tipready) {
- // tipshow and tiphide is coded inside wenku8 itself, its function is to show a text tip besides the mouse
- typeSelect.addEventListener('mouseover', show);
- searchSbmt.addEventListener('mouseover', show);
- typeSelect.addEventListener('mouseout' , tiphide);
- searchSbmt.addEventListener('mouseout' , tiphide);
- } else {
- typeSelect.title = TEXT_TIP_SEARCH_OPTION_TAG;
- searchSbmt.title = TEXT_TIP_SEARCH_OPTION_TAG;
- }
-
- function show() {
- optionTags.selected ? tipshow(TEXT_TIP_SEARCH_OPTION_TAG) : function() {};
- }
- }
- function onsubmitHOOK() {
- const onsbmt = searchForm.onsubmit;
- searchForm.onsubmit = function() {
- if (optionTags.selected) {
- // DON'T USE window.open()!
- // Wenku8 has no window.open used in its own scripts, so do not use it in userscript either.
- // It might cause security problems.
- //window.open('https://www.wenku8.net/modules/article/tags.php?t=' + $URL.encode(searchText.value));
- if (typeof($URL) === 'undefined' ) {
- $URLError();
- return true;
- } else {
- GM_openInTab(URL_TAGSEARCH.replace('{TU}', $URL.encode(searchText.value)), {
- active: true, insert: true, setParent: true, incognito: false
- });
- return false;
- }
- }
- }
-
- function $URLError() {
- DoLog(LogLevel.Error, '$URL(from gbk.js) is not loaded.');
- DoLog(LogLevel.Warning, 'Search as plain text instead.');
-
- // Search as plain text instead
- for (const node of typeSelect.childNodes) {
- node.selected = (node.tagName === 'OPTION' && node.value === 'articlename') ? true : false;
- }
- }
- }
- }
-
- // Tags page add-on
- function pageTags() {
- }
-
- // Mylink page add-on
- function pageMylink() {
- // Get elements
- const main = $('#content');
- const tbllink = $('#content>table');
-
- linkEnhance();
-
- function fixEdit(link) {
- const aedit = link.aedit;
- aedit.setAttribute('onclick', "editlink({ULID},'{NAME}','{HREF}','{INFO}')".replace('{ULID}', deal(link.ulid)).replace('{NAME}', deal(link.name)).replace('{HREF}', deal(link.href)).replace('{INFO}', deal(link.info)));
-
- function deal(str) {
- return str.replaceAll("'", "\\'");
- }
- }
-
- function linkEnhance() {
- const links = getAllLinks();
- for (const link of links) {
- fixEdit(link);
- }
- }
-
- function getAllLinks() {
- const links = [];
- const trs = $All(tbllink, 'tbody>tr+tr');
- for (const tr of trs) {
- const link = {};
-
- // All <td>
- link.tdlink = tr.children[0];
- link.tdinfo = tr.children[1];
- link.tdtime = tr.children[2];
- link.tdoprt = tr.children[3];
-
- // Inside <td>
- link.alink = link.tdlink.children[0];
- link.aedit = link.tdoprt.children[0];
- link.apos = link.tdoprt.children[1];
- link.adel = link.tdoprt.children[2];
-
- // Infos
- link.href = link.alink.href;
- link.ulid = getUrlArgv({url: link.apos.href, name: 'ulid'});
- link.name = link.alink.innerText;
- link.info = link.tdinfo.innerText;
- link.time = link.tdtime.innerText;
- link.purl = link.apos.href;
-
- links.push(link);
- }
-
- return links;
- }
- }
-
- // User page add-on
- function pageUser() {
- const UID = Number(getUrlArgv('uid'));
-
- // Provide review search option
- reviewButton();
-
- // Review search option
- function reviewButton() {
- // clone button and container div
- const oriContainer = $All('.blockcontent .userinfo')[0].parentElement;
- const container = oriContainer.cloneNode(true);
- const button = $(container, 'a');
- button.innerText = TEXT_GUI_USER_REVIEWSEARCH;
- button.href = URL_REVIEWSEARCH.replaceAll('{K}', String(UID));
- oriContainer.parentElement.appendChild(container);
- }
- }
-
- // Detail page add-on
- function pageDetail() {
- // Get elements
- const content = $('#content');
- const tbody = $(content, 'table>tbody');
-
- insertSettings();
-
- // Insert Settings GUI
- function insertSettings() {
- let elements = GUI();
-
- function GUI() {
- const review = CONFIG.BkReviewPrefs.getConfig();
- const settings = [
- [{html: TEXT_GUI_DETAIL_TITLE_SETTINGS, colSpan: 3, class: 'foot'}],
- [{html: TEXT_GUI_DETAIL_TITLE_BGI}, {colSpan: 2, key: 'bgimage', tiptitle: TEXT_TIP_IMAGE_FIT}],
- [{html: TEXT_GUI_DETAIL_BGI_UPLOAD}, {colSpan: 2, key: 'bgupload'}],
- [{html: TEXT_GUI_DETAIL_GUI_IMAGER}, {colSpan: 2, key: 'imager'}],
- [{html: TEXT_GUI_DETAIL_GUI_SCALE}, {colSpan: 2, key: 'scalectnr'}],
- [{html: TEXT_GUI_DETAIL_BTF_NOVEL}, {colSpan: 2, key: 'btfnvlctnr'}],
- [{html: TEXT_GUI_DETAIL_BTF_REVIEW}, {colSpan: 2, key: 'btfrvwctnr'}],
- [{html: TEXT_GUI_DETAIL_BTF_COMMON}, {colSpan: 2, key: 'btfcmnctnr'}],
- [{html: TEXT_GUI_DETAIL_FVR_LASTPAGE}, {colSpan: 2, key: 'favoropen'}],
- [{html: TEXT_GUI_DETAIL_VERSION_CURVER}, {colSpan: 2, key: 'curversion'}],
- [{html: TEXT_GUI_DETAIL_VERSION_CHECKUPDATE}, {colSpan: 2, key: 'updatecheck'}],
- [{html: TEXT_GUI_DETAIL_FEEDBACK_TITLE, colSpan: 1, key: 'feedbackttle'}, {html: TEXT_GUI_DETAIL_FEEDBACK, colSpan: 2, key: 'feedback'}],
- [{html: TEXT_GUI_DETAIL_UPDATEINFO_TITLE, colSpan: 1, key: 'feedbackttle'}, {html: TEXT_GUI_DETAIL_UPDATEINFO, colSpan: 2, key: 'updateinfo'}],
- [{html: TEXT_GUI_DETAIL_CONFIG_EXPORT}, {html: TEXT_GUI_DETAIL_EXPORT_CLICK, colSpan: 2, key: 'exportcfg'}],
- [{html: TEXT_GUI_DETAIL_CONFIG_EXPORT_NOPASS}, {html: TEXT_GUI_DETAIL_EXPORT_CLICK, colSpan: 2, key: 'exportcfgnp'}],
- [{html: TEXT_GUI_DETAIL_CONFIG_IMPORT, colSpan: 1, key: 'importcfgttle'}, {html: TEXT_GUI_DETAIL_IMPORT_CLICK, colSpan: 2, key: 'importcfg'}],
- [{html: TEXT_GUI_DETAIL_CONFIG_MANAGE, colSpan: 1, key: 'managecfgttle'}, {html: TEXT_GUI_DETAIL_MANAGE_CLICK, colSpan: 2, key: 'managecfg'}],
- //[{html: TEXT_GUI_DETAIL_XXXXXX_XXXXXX, colSpan: 1, key: 'xxxxxxxx'}, {html: TEXT_GUI_DETAIL_XXXXXX_XXXXXX, colSpan: 2, key: 'xxxxxxxx'}],
- ]
- const elements = createTableGUI(settings);
- const tdBgi = elements.bgimage;
- const imageinput = elements.imageinput = $CrE('input');
- const bgioprt = elements.bgioprt = $CrE('span');
- const bgiupld = elements.bgupload;
- const ckbgiup = elements.ckbgiup = $CrE('input');
- ckbgiup.type = 'checkbox';
- ckbgiup.checked = CONFIG.BeautifierCfg.getConfig().upload;
- ckbgiup.addEventListener('change', uploadChange);
- settip(ckbgiup, TEXT_GUI_DETAIL_BGI_LEGAL);
- bgiupld.appendChild(ckbgiup);
- imageinput.type = 'file';
- imageinput.style.display = 'none';
- imageinput.addEventListener('change', pictureGot);
- bgioprt.innerHTML = TEXT_GUI_DETAIL_DEFAULT_BGI + '</br>' + TEXT_GUI_DETAIL_BGI.replace('{N}', CONFIG.BeautifierCfg.getConfig().bgiName);
- bgioprt.style.color = 'grey';
- settip(bgioprt, TEXT_TIP_IMAGE_FIT);
- tdBgi.addEventListener("dragenter", destroyEvent);
- tdBgi.addEventListener("dragover", destroyEvent);
- tdBgi.addEventListener('drop', pictureGot);
- tdBgi.style.textAlign = 'center';
- tdBgi.addEventListener('click', ()=>{elements.imageinput.click();});
- tdBgi.appendChild(imageinput);
- tdBgi.appendChild(bgioprt);
-
- // Imager
- const curimager = CONFIG.UserGlobalCfg.getConfig().imager;
- elements.imager.style.padding = '0px 0.5em';
- for (const [key, imager] of Object.entries(DATA_IMAGERS)) {
- if (typeof(imager) !== 'object' || !imager.isImager) {continue;}
-
- const span = $CrE('span');
- const radio = $CrE('input');
- const text = $CrE('span');
- radio.type = 'radio';
- radio.value = '';
- radio.id = 'imager_'+key;
- radio.imagerkey = key;
- radio.name = 'imagerselect';
- radio.style.cursor = 'pointer';
- radio.addEventListener('change', imagerChange);
- radio.disabled = !imager.available;
- text.innerText = imager.name + (imager.available ? '' : '(已失效)');
- text.style.marginRight = '1em';
- text.style.cursor = 'pointer';
- text.addEventListener('click', function() {radio.click();});
- span.style.display = 'inline-block';
- span.appendChild(radio);
- span.appendChild(text);
- if (imager.tip) {
- let tip = imager.tip;
- DATA_IMAGERS.default === key && (tip += TEXT_TIP_IMAGER_DEFAULT);
- !imager.available && (tip = '<del>{T}</del></br>已失效'.replace('{T}', tip));
- settip(radio, tip);
- settip(text, tip);
- //settip(span, imager.tip);
- }
- elements.imager.appendChild(span);
- }
- $(elements.imager, '#imager_'+curimager).checked = true;
-
- // Text scale
- const textScale = CONFIG.BeautifierCfg.getConfig().textScale;
- const scalectnr = elements.scalectnr;
- const elmscale = elements.scale = $CrE('input');
- elmscale.type = 'number';
- elmscale.id = 'textScale';
- elmscale.value = textScale;
- elmscale.addEventListener('change', scaleChange);
- elmscale.addEventListener('keydown', (e) => {e.keyCode === 13 && scaleChange();});
- scalectnr.appendChild(elmscale);
- scalectnr.appendChild(document.createTextNode('%'));
-
- // Beautifier
- const btfnvlctnr = elements.btfnvlctnr;
- const btfrvwctnr = elements.btfrvwctnr;
- const btfcmnctnr = elements.btfcmnctnr;
- const ckbtfnvl = elements.ckbtfnvl = $CrE('input');
- const ckbtfrvw = elements.ckbtfrvw = $CrE('input');
- const ckbtfcmn = elements.ckbtfcmn = $CrE('input');
- ckbtfnvl.type = ckbtfrvw.type = ckbtfcmn.type = 'checkbox';
- ckbtfnvl.page = 'novel';
- ckbtfrvw.page = 'reviewshow';
- ckbtfcmn.page = 'common';
- ckbtfnvl.checked = CONFIG.BeautifierCfg.getConfig().novel.beautiful;
- ckbtfrvw.checked = CONFIG.BeautifierCfg.getConfig().reviewshow.beautiful;
- ckbtfcmn.checked = CONFIG.BeautifierCfg.getConfig().common.beautiful;
- ckbtfnvl.addEventListener('change', beautifulChange);
- ckbtfrvw.addEventListener('change', beautifulChange);
- ckbtfcmn.addEventListener('change', beautifulChange);
- btfnvlctnr.appendChild(ckbtfnvl);
- btfrvwctnr.appendChild(ckbtfrvw);
- btfcmnctnr.appendChild(ckbtfcmn);
-
- // Favorite open
- const favoropen = elements.favoropen;
- const favorlast = elements.favorlast = $CrE('input');
- favorlast.type = 'checkbox';
- favorlast.checked = CONFIG.BkReviewPrefs.getConfig().favorlast;
- favorlast.addEventListener('change', favorlastChange);
- favoropen.appendChild(favorlast);
-
- // Version control
- const curversion = elements.curversion;
- const updatecheck = elements.updatecheck;
- const versiondisplay = $CrE('span');
- versiondisplay.innerText = 'v' + GM_info.script.version;
- updatecheck.innerText = TEXT_GUI_DETAIL_VERSION_CHECK;
- updatecheck.style.color = 'grey';
- updatecheck.style.textAlign = 'center';
- updatecheck.addEventListener('click', updateOnclick);
- curversion.appendChild(versiondisplay);
-
- // Feedback
- const feedback = elements.feedback;
- feedback.style.color = 'grey';
- feedback.style.textAlign = 'center';
- feedback.addEventListener('click', function() {
- window.open('https://greasyfork.org/scripts/416310/feedback');
- });
-
- // Update info
- const updateinfo = elements.updateinfo;
- updateinfo.style.color = 'grey';
- updateinfo.style.textAlign = 'center';
- updateinfo.addEventListener('click', function() {
- window.open('https://greasyfork.org/scripts/416310#updateinfo');
- })
-
- // Config export/import
- const exportcfg = elements.exportcfg;
- const exportcfgnp = elements.exportcfgnp;
- const importcfg = elements.importcfg;
- const configinput = elements.configinput = $CrE('input');
- configinput.type = 'file';
- configinput.style.display = 'none';
- importcfg.style.color = exportcfgnp.style.color = exportcfg.style.color = 'grey';
- importcfg.style.textAlign = exportcfgnp.style.textAlign = exportcfg.style.textAlign = 'center';
- exportcfg.addEventListener('click', ()=>{exportConfig(false);});
- exportcfgnp.addEventListener('click', ()=>{exportConfig(true);});
- importcfg.addEventListener('click', () => {configinput.click()});
- configinput.addEventListener('change', configfileGot);
- importcfg.addEventListener("dragenter", destroyEvent);
- importcfg.addEventListener("dragover", destroyEvent);
- importcfg.addEventListener('drop', configfileGot);
- //importcfg.appendChild(configinput);
-
- // Config management
- const managecfg = elements.managecfg;
- managecfg.style.color = 'grey';
- managecfg.style.textAlign = 'center';
- managecfg.addEventListener('click', openManagePanel);
-
- // Paste event
- window.addEventListener('paste', filePasted);
-
- return elements;
- }
-
- function filePasted(e) {
- const input = e.dataTransfer || e.clipboardData || window.clipboardData || e.target;
- if (!input.files || input.files.length === 0) {return false;};
-
- for (const file of input.files) {
- switch (file.type) {
- case 'image/bmp':
- case 'image/gif':
- case 'image/vnd.microsoft.icon':
- case 'image/jpeg':
- case 'image/png':
- case 'image/svg+xml':
- case 'image/tiff':
- case 'image/webp':
- confirm(TEXT_GUI_DETAIL_CONFIG_IMPORT_CONFIRM_SELECT.replace('{N}', file.name)) && pictureGot(e);
- break;
- case '': {
- const splited = file.name.split('.');
- const ext = splited[splited.length-1];
- switch (ext) {
- case 'wkp':
- confirm(TEXT_GUI_DETAIL_CONFIG_IMPORT_CONFIRM_PASTE.replace('{N}', file.name)) && configfileGot(e);
- }
- }
- }
- }
- }
-
- function pictureGot(e) {
- e.preventDefault();
-
- // Get file
- const input = e.dataTransfer || e.clipboardData || window.clipboardData || e.target;
- if (!input.files || input.files.length === 0) {return false;};
- const fileObj = input.files[0];
- const mimetype = fileObj.type;
- const name = fileObj.name;
-
- // Create a new file input
- elements.bgimage.removeChild(elements.imageinput);
- const imageinput = elements.imageinput = $CrE('input');
- imageinput.type = 'file';
- imageinput.style.display = 'none';
- imageinput.addEventListener('change', pictureGot);
- elements.bgimage.appendChild(imageinput);
-
- if (!mimetype || mimetype.split('/')[0] !== 'image') {
- alertify.error(TEXT_ALT_IMAGE_FORMATERROR);
- return false;
- }
- elements.bgioprt.innerHTML = TEXT_GUI_DETAIL_BGI_WORKING;
-
- // Get object url
- const objurl = URL.createObjectURL(fileObj);
-
- // Get image url(format base64)
- getImageUrl(objurl, true, true, (url) => {
- if (!url) {return false;};
-
- // Save to config
- const config = CONFIG.BeautifierCfg.getConfig();
- config.backgroundImage = url;
- config.bgiName = name;
- CONFIG.BeautifierCfg.saveConfig(config);
- elements.bgioprt.innerHTML = name;
- URL.revokeObjectURL(objurl);
-
- // Upload if need
- if (config.upload) {
- alertify.notify(TEXT_ALT_IMAGE_UPLOAD_WORKING);
- elements.bgioprt.innerHTML = TEXT_GUI_DETAIL_BGI_UPLOADING.replace('{NAME}', name);
- const file = dataURLtoFile(url, name);
- uploadImage({
- file: file,
- onerror: (e) => {
- alertify.error(TEXT_ALT_IMAGE_UPLOAD_ERROR);
- DoLog(LogLevel.Error, ['Upload error at pictureGot:', e]);
- elements.bgioprt.innerHTML = TEXT_GUI_DETAIL_BGI_UPLOADFAILED.replace('{NAME}', name);
- const config = CONFIG.BeautifierCfg.getConfig();
- config.upload = elements.ckbgiup.checked = /^https?:\/\//.test(config.reveiwshow.backgroundImage);
- CONFIG.BeautifierCfg.saveConfig(config);
- },
- onload: (json) => {
- const config = CONFIG.BeautifierCfg.getConfig();
- config.backgroundImage = json.url;
- CONFIG.BeautifierCfg.saveConfig(config);
- elements.bgioprt.innerHTML = TEXT_GUI_DETAIL_DEFAULT_BGI + '</br>' + TEXT_GUI_DETAIL_BGI.replace('{N}', CONFIG.BeautifierCfg.getConfig().bgiName);
- alertify.success(TEXT_ALT_IMAGE_UPLOAD_SUCCESS.replace('{NAME}', json.name).replace('{URL}', json.url));
- }
- })
- }
- });
- }
-
- function uploadChange(e) {
- e.preventDefault();
-
- const config = CONFIG.BeautifierCfg.getConfig();
- config.upload = !config.upload;
- CONFIG.BeautifierCfg.saveConfig(config);
- const name = config.bgiName ? config.bgiName : 'image.jpeg';
-
- if (config.upload) {
- // Upload
- const url = config.backgroundImage;
- if (!/^https?:\/\//.test(url)) {
- alertify.notify(TEXT_ALT_IMAGE_UPLOAD_WORKING);
- elements.bgioprt.innerHTML = TEXT_GUI_DETAIL_BGI_UPLOADING.replace('{NAME}', name);
- const file = dataURLtoFile(url, name);
- uploadImage({
- file: file,
- onerror: (e) => {
- alertify.error(TEXT_ALT_IMAGE_UPLOAD_ERROR);
- DoLog(LogLevel.Error, ['Upload error at uploadChange:', e]);
- elements.bgioprt.innerHTML = TEXT_GUI_DETAIL_BGI_UPLOADFAILED.replace('{NAME}', name);
- const config = CONFIG.BeautifierCfg.getConfig();
- config.upload = elements.ckbgiup.checked = /^https?:\/\//.test(config.backgroundImage);
- CONFIG.BeautifierCfg.saveConfig(config);
- },
- onload: (json) => {
- const config = CONFIG.BeautifierCfg.getConfig();
- config.backgroundImage = json.url;
- config.bgiName = elements.bgioprt.innerHTML = json.name;
- CONFIG.BeautifierCfg.saveConfig(config);
- alertify.success(TEXT_ALT_IMAGE_UPLOAD_SUCCESS.replace('{NAME}', json.name).replace('{URL}', json.url));
- }
- });
- }
- } else {
- // Download
- const url = config.backgroundImage;
- if (/^https?:\/\//.test(url)) {
- alertify.notify(TEXT_ALT_IMAGE_DOWNLOAD_WORKING);
- elements.bgioprt.innerHTML = TEXT_GUI_DETAIL_BGI_DOWNLOADING.replace('{NAME}', name);
- getImageUrl(url, true, true, (dataurl) => {
- if (!dataurl) {
- const config = CONFIG.BeautifierCfg.getConfig();
- config.upload = elements.ckbgiup.checked = /^https?:\/\//.test(config.backgroundImage);
- CONFIG.BeautifierCfg.saveConfig(config);
- return false;
- };
-
- // Save to config
- const config = CONFIG.BeautifierCfg.getConfig();
- config.backgroundImage = dataurl;
- CONFIG.BeautifierCfg.saveConfig(config);
- alertify.success(TEXT_ALT_IMAGE_DOWNLOAD_SUCCESS.replace('{NAME}', name));
- elements.bgioprt.innerHTML = name;
- });
- }
- }
-
- setTimeout(()=>{elements.ckbgiup.checked = config.upload;}, 0);
- }
-
- function imagerChange(e) {
- e.stopPropagation();
- const radio = e.target;
- if (radio.checked) {
- const imager = DATA_IMAGERS[radio.imagerkey];
- const config = CONFIG.UserGlobalCfg.getConfig();
- config.imager = radio.imagerkey;
- CONFIG.UserGlobalCfg.saveConfig(config);
- alertify.message('图床已切换到{NAME}'.replace('{NAME}', imager.name));
- imager.warning && alertify.warning(imager.warning);
- }
- }
-
- function scaleChange(e) {
- e.stopPropagation();
- const config = CONFIG.BeautifierCfg.getConfig();
- config.textScale = e.target.value;
- CONFIG.BeautifierCfg.saveConfig(config);
- alertify.message(TEXT_ALT_TEXTSCALE_CHANGED.replaceAll('{S}', config.textScale));
- }
-
- function beautifulChange(e) {
- e.stopPropagation();
- const checkbox = e.target;
- const config = CONFIG.BeautifierCfg.getConfig();
- config[checkbox.page].beautiful = checkbox.checked;
- CONFIG.BeautifierCfg.saveConfig(config);
- alertify.message(checkbox.checked ? TEXT_ALT_BEAUTIFUL_ON : TEXT_ALT_BEAUTIFUL_OFF);
- }
-
- function favorlastChange(e) {
- e.stopPropagation();
- const checkbox = e.target;
- const config = CONFIG.BkReviewPrefs.getConfig();
- config.favorlast = checkbox.checked;
- CONFIG.BkReviewPrefs.saveConfig(config);
- alertify.message(checkbox.checked ? TEXT_ALT_FAVORITE_LAST_ON : TEXT_ALT_FAVORITE_LAST_OFF);
- }
-
- function updateOnclick(e) {
- TASK.Script.update(true);
- }
-
- function configfileGot(e) {
- e.preventDefault();
-
- // Get file
- const input = e.dataTransfer || e.clipboardData || window.clipboardData || e.target;
- if (!input.files || input.files.length === 0) {return false;};
- const fileObj = input.files[0];
- const splitedname = fileObj.name.split('.');
- const ext = splitedname[splitedname.length-1].toLowerCase();
- if (ext !== 'wkp') {
- alertify.error(TEXT_ALT_DETAIL_CONFIG_IMPORT_ERROR_SELECT);
- DoLog(LogLevel.Warning, 'pageDetail.insertSettings.GUI.configfileGot: userinput error.')
- return false;
- }
-
- // Read config from file
- try {
- const FR = new FileReader();
- FR.onload = fileOnload;
- FR.readAsText(fileObj);
- } catch(e) {
- fileError(e);
- }
-
- function fileOnload(e) {
- try {
- // Get json
- const json = JSON.parse(e.target.result);
-
- // Import
- importConfig(json);
-
- alertify.success(TEXT_ALT_DETAIL_IMPORTED);
- } catch(err) {
- fileError(err);
- }
- }
-
- function fileError(e) {
- DoLog(LogLevel.Error, ['pageDetail.insertSettings.GUI.configfileGot:', e]);
- alertify.error(TEXT_ALT_DETAIL_CONFIG_IMPORT_ERROR_READ);
- }
- }
-
- function openManagePanel(e) {
- const settings = {
- id: 'ConfigPanel'
- };
- const SetPanel = new SettingPanel(settings);
- const tblAccount = SetPanel.tables[0];
-
- account();
- drafts();
- review_favorites();
- pending_tip();
-
- SetPanel.usercss += '.settingpanel-block.sp-center {text-align: center;} .settingpanel-block{overflow-wrap: anywhere;}';
-
- function account() {
- const userConfig = CONFIG.GlobalConfig.getConfig();
- const users = userConfig.users ? userConfig.users : {};
-
- // Create table
- const table = new SetPanel.PanelTable({
- rows: [{
- blocks: [{
- className: 'sp-center',
- innerHTML: '账号管理',
- colSpan: 3
- }]
- },{
- blocks: [{
- className: 'sp-center',
- innerHTML: '用户名'
- },{
- className: 'sp-center',
- innerHTML: '密码'
- },{
- className: 'sp-center',
- innerHTML: '操作'
- }]
- }]
- });
- SetPanel.appendTable(table);
-
- for (const [name, user] of Object.entries(users)) {
- // Get account
- const username = user.username;
- const password = user.password;
-
- // Row
- const row = new SetPanel.PanelRow();
- table.appendRow(row);
-
- // Block username
- const block_username = new SetPanel.PanelBlock({
- className: 'sp-center',
- innerHTML: username
- });
-
- // Block password
- const spanpswd = $CrE('span');;
- spanpswd.innerHTML = '*'.repeat(password.length);
- const block_password = new SetPanel.PanelBlock({
- className: 'sp-center',
- children: [spanpswd]
- });
-
- // Block operator
- const btndel = _createBtn('删除', make_del_callback(row, username));
- const elmshow = $CrE('span'); elmshow.innerHTML = '查看';
- const btnshow = _createBtn(elmshow, make_show_callback(elmshow, spanpswd, password));
- const block_operator = new SetPanel.PanelBlock({
- className: 'sp-center',
- children: [btnshow, btndel]
- });
-
- // Append row to SettingPanel
- row.appendBlock(block_username).appendBlock(block_password).appendBlock(block_operator);
- }
-
- function make_del_callback(row, username) {
- return function(e) {
- const userConfig = CONFIG.GlobalConfig.getConfig();
- delete userConfig.users[username];
- CONFIG.GlobalConfig.saveConfig(userConfig);
- row.remove();
- }
- }
-
- function make_show_callback(btn, span, password) {
- let show = false;
- let timeout;
- return function toggle(e) {
- show = !show;
- span.innerHTML = show ? password : '*'.repeat(password.length);
- btn.innerHTML = show ? '隐藏' : '查看';
- }
- }
- }
-
- function drafts() {
- // Get config
- const allCData = CONFIG.commentDrafts.getConfig();
-
- // Create table
- const table = new SetPanel.PanelTable({
- rows: [{
- blocks: [{
- className: 'sp-center',
- innerHTML: '书评草稿管理',
- colSpan: 3
- }]
- },{
- blocks: [{
- className: 'sp-center',
- innerHTML: '标题'
- },{
- className: 'sp-center',
- innerHTML: '内容'
- },{
- className: 'sp-center',
- innerHTML: '操作'
- }]
- }]
- });
- SetPanel.appendTable(table);
-
- // Append rows
- for (const [propkey, commentData] of Object.entries(allCData)) {
- if (propkey === KEY_DRAFT_VERSION) {continue;}
- const title = commentData.title;
- const content = commentData.content;
- const key = commentData.key;
-
- // Row
- const row = new SetPanel.PanelRow();
- table.appendRow(row);
-
- // Block title
- const span_title = $CrE('span');
- span_title.innerHTML = _decorate(title);
- const block_title = new SetPanel.PanelBlock({className: 'draft-title sp-center', children: [span_title]});
-
- // Block content
- const span_content = $CrE('span');
- span_content.innerHTML = _decorate(content);
- const block_content = new SetPanel.PanelBlock({className: 'draft-content', children: [span_content]});
-
- // Block operator
- const elmshow = $CrE('span'); elmshow.innerHTML = '展开';
- const btnshow = _createBtn(elmshow, make_show_callback(elmshow, key, row, span_title, span_content));
- //const btnedit = _createBtn('编辑', make_edit_callback(key, row));
- const btnopen = _createBtn('打开', make_open_callback(key));
- const btndel = _createBtn('删除', make_del_callback(key, row));
- const block_operator = new SetPanel.PanelBlock({className: 'draft-operator sp-center', children: [btnshow, btnopen, btndel]});
-
- // Append to row
- row.appendBlock(block_title).appendBlock(block_content).appendBlock(block_operator);
- }
-
- // Append css
- SetPanel.usercss += '.settingpanel-block.draft-title {width: 20%;} .settingpanel-block.draft-content {width: 50%;} .settingpanel-block.draft-operator {width: 30%}';
-
- function make_show_callback(btn, key, row, span_title, span_content) {
- let show = false;
- return function() {
- const allCData = CONFIG.commentDrafts.getConfig();
- const data = allCData[key];
- if (!data) {
- alertify.warning(TEXT_ALT_DETAIL_MANAGE_NOTFOUND);
- row.remove();
- return false;
- }
- show = !show;
- btn.innerHTML = show ? '收起' : '展开';
- span_title.innerHTML = _decorate(show ? {text: data.title, length: -1} : data.title);
- span_content.innerHTML = _decorate(show ? {text: data.content, length: -1} : data.content);
- };
- }
-
- function make_edit_callback(key, row) {
- return function() {
- // Get data
- const allCData = CONFIG.commentDrafts.getConfig();
- const data = allCData[key];
- if (!data) {
- alertify.warning(TEXT_ALT_DETAIL_MANAGE_NOTFOUND);
- row.remove();
- return false;
- }
-
- // Create box gui
- const box = alertify.alert();
- const container = box.elements.content;
- makeEditor(container, data.rid.toString());
- const form = $(container, 'form');
- const ptitle = $(container, '#ptitle');
- const pcontent = $(container, '#pcontent');
- ptitle.value = data.title;
- pcontent.value = data.content;
- box.setting({
- maximizable: false,
- resizable: true
- });
- box.resizeTo('80%', '60%');
- box.show();
- };
- }
-
- function make_open_callback(key, row) {
- return function() {
- const allCData = CONFIG.commentDrafts.getConfig();
- const data = allCData[key];
- if (!data) {
- alertify.warning(TEXT_ALT_DETAIL_MANAGE_NOTFOUND);
- row.remove();
- return false;
- }
- const url = data.rid ? URL_REVIEWSHOW_1.replace('{R}', data.rid.toString()) : URL_NOVELINDEX.replace('{I}', data.bid.toString());
- window.open(url);
- }
- }
-
- function make_del_callback(key, row) {
- return function() {
- const allCData = CONFIG.commentDrafts.getConfig();
- delete allCData[key];
- CONFIG.commentDrafts.saveConfig(allCData);
- row.remove();
- };
- }
- }
-
- function review_favorites() {
- // Get config
- const config = CONFIG.BkReviewPrefs.getConfig();
- const favs = config.favorites;
-
- // Create table
- const table = new SetPanel.PanelTable({
- rows: [{
- blocks: [{
- className: 'sp-center',
- innerHTML: '书评收藏管理',
- colSpan: 3
- }]
- },{
- blocks: [{
- className: 'sp-center',
- innerHTML: '主题'
- },{
- className: 'sp-center',
- innerHTML: '备注'
- },{
- className: 'sp-center',
- innerHTML: '操作'
- }]
- }]
- });
- SetPanel.appendTable(table);
-
- // Append rows
- for (const [rid, fav] of Object.entries(favs)) {
- // Row
- const row = new SetPanel.PanelRow();
- table.appendRow(row);
-
- // Title block
- const span_title = $CrE('span');
- span_title.innerHTML = _decorate({text: fav.name, length: 0});
- const block_title = new SetPanel.PanelBlock({className: 'fav-title sp-center', children: [span_title]});
-
- // Note block
- const span_note = $CrE('span');
- span_note.innerHTML = _decorate({text: fav.tiptitle, length: 0});
- const block_note = new SetPanel.PanelBlock({className: 'fav-note sp-center', children: [span_note]});
-
- // Operator block
- const btn_open = _makeBtn({
- tagName: 'a',
- innerHTML: TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_BTN_OPEN,
- props: {
- href: fav.href + (config.favorlast ? '&page=last' : ''),
- target: '_blank'
- }
- });
- const btn_edit = _makeBtn({
- innerHTML: TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_BTN_NOTE,
- onclick: edit.bind(null, fav, row)
- });
- const btn_delete = _makeBtn({
- innerHTML: TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_BTN_DELETE,
- onclick: del.bind(null, rid, row)
- });
- const block_oprt = new SetPanel.PanelBlock({className: 'fav-operator sp-center', children: [btn_open, btn_edit, btn_delete]});
-
- // Append to row
- row.appendBlock(block_title).appendBlock(block_note).appendBlock(block_oprt);
- }
-
- // Append css
- SetPanel.usercss += '.settingpanel-block.fav-title {width: 35%;} .settingpanel-block.fav-note {width: 35%;} .settingpanel-block.fav-operator {width: 30%}';
-
- function edit(fav, row) {
- alertify.prompt(TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_TITLE, TEXT_GUI_DETAIL_MANAGE_FAV_NOTE_TIP.replace('{TITLE}', fav.name), fav.tiptitle || '', onok, function() {});
-
- function onok(e, value) {
- // Save empty value as null
- value === value || null;
- fav.tiptitle = value;
- CONFIG.BkReviewPrefs.saveConfig(config);
- row.blocks[1].element.firstChild.innerHTML = _decorate({text: value, length: 0});
- alertify.success(TEXT_GUI_DETAIL_MANAGE_FAV_SAVED);
- }
- }
-
- function del(rid, row) {
- alertify.confirm(TEXT_GUI_DETAIL_MANAGE_FAV_DELETE_TITLE, TEXT_GUI_DETAIL_MANAGE_FAV_DELETE_TIP.replace('{TITLE}', favs[rid].name), onok, function() {});
-
- function onok() {
- delete favs[rid];
- CONFIG.BkReviewPrefs.saveConfig(config);
- row.remove();
- alertify.success(TEXT_GUI_DETAIL_MANAGE_FAV_DELETED);
- }
- }
- }
-
- function pending_tip() {
- const span = $CrE('span');
- span.innerHTML = '*其他管理项尚在开发中,请耐心等待';
- span.classList.add(CLASSNAME_TEXT);
- SetPanel.element.appendChild(span);
- }
-
- function _createBtn(htmlorbtn, onclick) {
- const innerHTML = typeof htmlorbtn === 'string' ? htmlorbtn : htmlorbtn.innerHTML;
- const btn = htmlorbtn instanceof HTMLElement ? htmlorbtn : $CrE('span');
- !btn.classList.contains(CLASSNAME_BUTTON) && btn.classList.add(CLASSNAME_BUTTON);
- btn.innerHTML = innerHTML;
- btn.style.margin = '0px 0.5em';
- onclick && btn.addEventListener('click', onclick);
- return btn;
- }
-
- function _makeBtn(details) {
- // Create element
- const elm = $CrE(details.tagName || 'span');
-
- // Write innerHTML
- copyProp(details, elm, 'innerHTML');
-
- // Write other properties
- details.props && copyProps(details.props, elm, Object.keys(details.props));
-
- // Make onclick
- const onclick = details.onclick || (details.onclickMaker ? details.onclickMaker.apply(null, details.onclickArgs || []) : null);
-
- // Create button
- const btn = _createBtn(elm, onclick);
-
- // Custom classes
- details.classes && details.classes.forEach(function(c) {!btn.classList.contains(c) && btn.classList.add(c);});
-
- return btn;
- }
-
- // details: 'string' or {text: '', length: 16}
- function _decorate(details) {
- // Get Args
- details = !details ? '' : details;
- details = typeof details === 'string' ? {text: details} : details;
- const text = details.text || '';
- const length = typeof details.length === 'number' ? (details.length > 0 ? details.length : Infinity) : 16;
-
- const len = length > 0 ? length : 9999999999999;
- const overflow = (text.length - len) > length;
- const cut = overflow ? text.substr(0, len) : text;
- const encoded = htmlEncode(cut).replaceAll('\n', '</br>');
- const filled = text.length === 0 ? TEXT_GUI_DETAIL_CONFIG_MANAGE_EMPTY : (overflow ? encoded + TEXT_GUI_DETAIL_CONFIG_MANAGE_MORE : encoded);
- return filled;
- }
- }
- }
-
- function createTableGUI(lines) {
- const elements = {};
- for (const line of lines) {
- const tr = $CrE('tr');
- for (const item of line) {
- const td = $CrE('td');
- item.html && (td.innerHTML = item.html);
- item.colSpan && (td.colSpan = item.colSpan);
- item.class && (td.className = item.class);
- item.id && (td.id = item.id);
- item.tiptitle && settip(td, item.tiptitle);
- item.key && (elements[item.key] = td);
- td.style.padding = '3px';
- tr.appendChild(td);
- }
- tbody.appendChild(tr);
- }
- return elements;
-
- function ElementObject(element) {
- const p = new Proxy(element, {
- get: function(elm, id, receiver) {
- return elm[id] || $(elm, '#'+id);
- }
- });
-
- return p;
- }
- }
- }
-
- // Index page add-on
- function pageIndex() {
- insertStatus();
- showFavorites();
- showLaterReads();
-
- // Insert usersript inserted tip
- function insertStatus() {
- const blockcontent = $('#centers>.block:nth-child(1)>.blockcontent');
- blockcontent.appendChild($CrE('br'));
- const textNode = $CrE('span');
- textNode.innerText = TEXT_GUI_INDEX_STATUS;
- textNode.classList.add(CLASSNAME_TEXT);
- blockcontent.appendChild(textNode);
- }
-
- // Show favorite reviews
- function showFavorites() {
- const links = [];
- const config = CONFIG.BkReviewPrefs.getConfig();
-
- for (const [rid, favorite] of Object.entries(config.favorites)) {
- const href = favorite.href + (config.favorlast ? '&page=last' : '');
- const tiptitle = favorite.tiptitle ? favorite.tiptitle : href;
- const innerHTML = favorite.name.substr(0, 12) // prevent overflow
- links.push({
- innerHTML: innerHTML,
- tiptitle: tiptitle,
- href: href
- });
- }
-
- const block = createWenkuBlock({
- type: 'toplist',
- parent: '#left',
- title: TEXT_GUI_INDEX_FAVORITES,
- items: links
- });
- }
-
- // Show top-6 read-later books
- function showLaterReads() {
- const config = CONFIG.bookcasePrefs.getConfig().laterbooks;
- const books = sortLaterReads(config.books, config.sortby).filter((e,i,a)=>(i<6));
- const items = books.map(function(book, i) {
- return {
- href: URL_NOVELINDEX.replace('{I}', book.aid),
- src: book.cover,
- tiptitle: book.name,
- text: book.name
- }
- });
- const block = createWenkuBlock({
- type: 'imagelist',
- parent: '#centers',
- title: TEXT_GUI_INDEX_LATERBOOKS,
- items: items
- });
- settip($(block, '.blocktitle'), TEXT_TIP_INDEX_LATERREADS);
- }
- }
-
- // Download page add-on
- function pageDownload() {
- let i;
- let dlCount = 0; // number of active download tasks
- let dlAllRunning = false; // whether there is downloadAll running
-
- // Get novel info
- const novelInfo = {}; collectNovelInfo();
- const myDlBtns = [];
-
- // Donwload GUI
- downloadGUI();
-
- // Server GUI
- serverGUI();
-
- /* ******************* Code ******************* */
- function collectNovelInfo() {
- novelInfo.novelName = $('html body div.main div#centerm div#content table.grid caption a').innerText;
- novelInfo.displays = getAllNameEles();
- novelInfo.volumeNames = getAllNames();
- novelInfo.type = getUrlArgv('type');
- novelInfo.ext = novelInfo.type !== 'txtfull' ? novelInfo.type : 'txt';
- }
-
- // Donwload GUI
- function downloadGUI() {
- switch (novelInfo.type) {
- case 'txt':
- downloadGUI_txt();
- break;
- case 'txtfull':
- downloadGUI_txtfull();
- break;
- case 'umd':
- downloadGUI_umd();
- break;
- case 'jar':
- downloadGUI_jar();
- break;
- default:
- DoLog(LogLevel.Warning, 'pageDownload.downloadGUI: Unknown download type');
- }
- }
-
- // Donwload GUI for txt
- function downloadGUI_txt() {
- // Only txt is really separated by volumes
- if (novelInfo.type !== 'txt') {return false;};
-
- // define vars
- let i;
-
- const tbody = $('table>tbody');
- const header = $(tbody, 'th').parentElement;
- const thead = $(header, 'th');
-
- // Append new th
- const newHead = thead.cloneNode(true);
- newHead.innerText = TEXT_GUI_SDOWNLOAD;
- thead.width = '40%';
- header.appendChild(newHead);
-
- // Append new td
- const trs = $All(tbody, 'tr');
- for (i = 1; i < trs.length; i++) { /* i = 1 to trs.length-1: skip header */
- const index = i-1;
- const tr = trs[i];
- const newTd = $(tr, 'td.even').cloneNode(true);
- const links = $All(newTd, 'a');
- for (const a of links) {
- a.classList.add(CLASSNAME_BUTTON);
- a.info = {
- description: 'volume download button',
- name: novelInfo.volumeNames[index],
- filename: TEXT_GUI_SDOWNLOAD_FILENAME
- .replace('{NovelName}', novelInfo.novelName)
- .replace('{VolumeName}', novelInfo.volumeNames[index])
- .replace('{Extension}', novelInfo.ext),
- index: index,
- display: novelInfo.displays[index]
- }
- a.onclick = downloadOnclick;
- myDlBtns.push(a);
- }
- tr.appendChild(newTd);
- }
-
- // Append new tr, provide batch download
- const newTr = trs[trs.length-1].cloneNode(true);
- const newTds = $All(newTr, 'td');
- newTds[0].innerText = TEXT_GUI_DOWNLOADALL;
- //clearChildnodes(newTds[1]); clearChildnodes(newTds[2]);
- newTds[1].innerHTML = newTds[2].innerHTML = TEXT_GUI_NOTHINGHERE;
- tbody.insertBefore(newTr, tbody.children[1]);
-
- const allBtns = $All(newTds[3], 'a');
- for (i = 0; i < allBtns.length; i++) {
- const a = allBtns[i];
- a.href = 'javascript:void(0);';
- a.info = {
- description: 'download all button',
- index: i
- }
- a.onclick = downloadAllOnclick;
- }
-
- // Download button onclick
- function downloadOnclick() {
- const a = this;
- a.info.display.innerText = a.info.name + TEXT_GUI_WAITING;
- downloadFile({
- url: a.href,
- name: a.info.filename,
- onloadstart: function(e) {
- a.info.display.innerText = a.info.name + TEXT_GUI_DOWNLOADING;
- },
- onload: function(e) {
- a.info.display.innerText = a.info.name + TEXT_GUI_DOWNLOADED;
- }
- });
- return false;
- }
-
- // DownloadAll button onclick
- function downloadAllOnclick() {
- const a = this;
- const index = (a.info.index+1)%3;
- for (let i = 0; i < myDlBtns.length; i++) {
- if ((i+1)%3 !== index) {continue;};
- const btn = myDlBtns[i];
- btn.click();
- }
- return false;
- }
- }
-
- // Donwload GUI for txtfull
- function downloadGUI_txtfull() {
- const container = $('#content>table tr>td:nth-child(3)');
- const links = arrfilter(container.children, (e,i)=>([1,3,5].includes(i)));
- const TEXTS = ['简体(G)', '简体(U)', '繁体(U)'];
- const elms = [];
-
- elms.push($CrE('br'));
- elms.push(document.createTextNode('程序重命名('));
- for (let i = 0; i < links.length; i++) {
- const a = links[i];
- const btn = $CrE('a');
- btn.href = a.previousElementSibling.href;
- btn.download = novelInfo.novelName + '.txt';
- btn.innerHTML = TEXTS[i];
- btn.classList.add(CLASSNAME_BUTTON);
- btn.addEventListener('click', downloadFromA);
- elms.push(btn);
- i+1 < links.length && elms.push(a.previousSibling.cloneNode());
- }
- elms.push(document.createTextNode(')'));
-
- for (const elm of elms) {
- container.appendChild(elm);
- }
- }
-
- // Donwload GUI for umd
- function downloadGUI_umd() {
- const container = $('#content>table tr>td:nth-child(5)');
- const a = container.firstChild;
- const btn = $CrE('a');
- btn.href = a.href;
- btn.download = novelInfo.novelName + '.umd';
- btn.innerHTML = '重命名下载';
- btn.classList.add(CLASSNAME_BUTTON);
- btn.addEventListener('click', downloadFromA);
- a.insertAdjacentElement('afterend', btn);
- a.insertAdjacentElement('afterend', $CrE('br'));
- }
-
- // Donwload GUI for jar
- function downloadGUI_jar() {
- const container = $('#content>table tr>td:nth-child(5)');
- const links = arrfilter(container.children, ()=>(true));
- const TEXTS = ['重命名JAR', '重命名JAD'];
- const EXTS = ['.jar', '.jad'];
- const elms = [];
-
- elms.push($CrE('br'));
- for (let i = 0; i < links.length; i++) {
- const a = links[i];
- const btn = $CrE('a');
- btn.href = a.href;
- btn.download = novelInfo.novelName + EXTS[i];
- btn.innerHTML = TEXTS[i];
- btn.classList.add(CLASSNAME_BUTTON);
- btn.addEventListener('click', downloadFromA);
- elms.push(btn);
- i+1 < links.length && elms.push(a.nextSibling.cloneNode());
- }
-
- for (const elm of elms) {
- container.appendChild(elm);
- }
-
- $('#content>table tr>th:nth-child(4)').setAttribute('width', '47%');
- $('#content>table tr>th:nth-child(5)').setAttribute('width', '20%');
- }
-
- function downloadFromA(e) {
- e.preventDefault();
-
- const btn = e.target;
- const url = btn.href;
-
- downloadFile({
- url: url,
- name: btn.download
- });
- }
-
- // Get all name display elements
- function getAllNameEles() {
- return $All('.grid tbody tr .odd');
- }
-
- // Get all names
- function getAllNames() {
- const all = getAllNameEles()
- const names = [];
- for (let i = 0; i < all.length; i++) {
- names[i] = all[i].innerText;
- }
- return names;
- }
-
- // Server GUI
- function serverGUI() {
- let servers = $All('#content>b');
- let serverEles = [];
- for (i = 0; i < servers.length; i++) {
- if (servers[i].innerText.includes('wenku8.com')) {
- serverEles.push(servers[i]);
- }
- }
- for (i = 0; i < serverEles.length; i++) {
- serverEles[i].classList.add(CLASSNAME_BUTTON);
- serverEles[i].addEventListener('click', function () {
- changeAllServers(this.innerText);
- });
- settip(serverEles[i], TEXT_TIP_SERVERCHANGE);
- }
- }
-
- // Change all server elements
- function changeAllServers(server) {
- let i;
- const allA = $All('.even a');
- for (i = 0; i < allA.length; i++) {
- changeServer(server, allA[i]);
- }
- }
-
- // Change server for an element
- function changeServer(server, element) {
- if (!element.href) {return false;};
- element.href = element.href.replace(/\/\/dl\d?\.wenku8\.com\//g, '//' + server + '/');
- }
-
- // Array.prototype.filter
- function arrfilter(arr, callback) {
- return Array.prototype.filter.call(arr, callback);
- }
- }
-
- // Login page add-on
- function pageLogin() {
- const form = $('form[name="frmlogin"]');
- if (!form) {return false;}
- const eleUsername = $(form, 'input.text[name="username"]');
- const elePassword = $(form, 'input.text[name="password"]')
-
- catchAccount();
-
- // Save account info
- function catchAccount() {
- form.addEventListener('submit', () => {
- const config = CONFIG.GlobalConfig.getConfig();
- const username = eleUsername.value;
- const password = elePassword.value;
- config.users = config.users ? config.users : {};
- config.users[username] = {
- username: username,
- password: password
- }
- CONFIG.GlobalConfig.saveConfig(config);
- });
- }
- }
-
- // Account fast switching
- function multiAccount() {
- if (!$('.fl')) {return false;};
- GUI();
-
- function GUI() {
- // Add switch select
- const eleTopLeft = $('.fl');
- const eletext = $CrE('span');
- const sltSwitch = $CrE('select');
- eletext.innerText = TEXT_GUI_ACCOUNT_SWITCH;
- eletext.classList.add(CLASSNAME_TEXT);
- eletext.style.marginLeft = '0.5em';
- eleTopLeft.appendChild(eletext);
- eleTopLeft.appendChild(sltSwitch);
-
- // Not logged in, create and select an empty option
- // Select current user's option
- if (!getUserName()) {
- appendOption(TEXT_GUI_ACCOUNT_NOTLOGGEDIN, '').selected = true;
- };
-
- // Add select options
- const userConfig = CONFIG.GlobalConfig.getConfig();
- const users = userConfig.users ? userConfig.users : {};
- const names = Object.keys(users);
- if (names.length === 0) {
- appendOption(TEXT_GUI_ACCOUNT_NOACCOUNT, '');
- settip(sltSwitch, TEXT_TIP_ACCOUNT_NOACCOUNT);
- }
- for (const username of names) {
- appendOption(username, username)
- }
-
- // Select current user's option
- if (getUserName()) {selectCurUser();};
-
- // onchange: switch account
- sltSwitch.addEventListener('change', (e) => {
- const select = e.target;
- if (!select.value || !confirm(TEXT_GUI_ACCOUNT_CONFIRM.replace('{N}', select.value))) {
- selectCurUser();
- destroyEvent(e);
- return;
- }
-
- switchAccount(select.value);
- });
-
- function appendOption(text, value) {
- const option = $CrE('option');
- option.innerText = text;
- option.value = value;
- sltSwitch.appendChild(option);
- return option;
- }
-
- function selectCurUser() {
- for (const option of $All(sltSwitch, 'option')) {
- option.selected = getUserName().toLowerCase() === option.value.toLowerCase();
- }
- }
- }
-
- function switchAccount(username) {
- // Logout
- alertify.notify(TEXT_ALT_ACCOUNT_WORKING_LOGOFF);
- GM_xmlhttpRequest({
- method: 'GET',
- url: URL_USRLOGOFF,
- onload: function(response) {
- // Login
- alertify.notify(TEXT_ALT_ACCOUNT_WORKING_LOGIN);
- const account = CONFIG.GlobalConfig.getConfig().users[username];
- const data = DATA_XHR_LOGIN
- .replace('{U}', $URL.encode(account.username))
- .replace('{P}', $URL.encode(account.password))
- .replace('{C}', $URL.encode('315360000')) // Expire time: 1 year
- GM_xmlhttpRequest({
- method: 'POST',
- url: URL_USRLOGIN,
- data: data,
- headers: {
- "Content-Type": "application/x-www-form-urlencoded"
- },
- onload: function() {
- let box = alertify.success(TEXT_ALT_ACCOUNT_SWITCHED.replace('{N}', username));
- redirectGMStorage(getUserID());
- DoLog(LogLevel.Info, 'GM_storage redirected to ' + String(getUserID()));
- const timeout = setTimeout(()=>{location.href=location.href;}, 3000);
- box.callback = (isClicked) => {
- isClicked && clearTimeout(timeout);
- };
- }
- })
- }
- })
- }
- }
-
- // API page and its sub pages add-on
- function pageAPI(API) {
- addStyle(CSS_PAGE_API, 'plus_api_css');
- //logAPI();
-
- let result;
- switch(API) {
- case 'modules/article/addbookcase.php':
- result = pageAddbookcase();
- break;
- case 'modules/article/packshow.php':
- result = pagePackshow();
- break;
- default:
- result = logAPI();
- }
-
- return result;
-
- function logAPI() {
- DoLog('This is wenku API page.');
- DoLog('API is: [' + API + ']');
- DoLog('There is nothing to do. Quiting...');
- }
-
- function pageAddbookcase() {
-
- // Append link to bookcase page
- addBottomButton({
- href: `https://${location.host}/modules/article/bookcase.php`,
- innerHTML: TEXT_GUI_API_ADDBOOKCASE_TOBOOKCASE
- });
-
- // Append link to remove from bookcase (not finished)
- /*addBottomButton({
- href: `https://${location.host}/modules/article/bookcase.php?delid=` + getUrlArgv('bid'),
- innerHTML: TEXT_GUI_API_ADDBOOKCASE_REMOVE,
- onclick: function() {
- confirm('确实要将本书移出书架么?')
- }
- });*/
- }
-
- function pagePackshow() {
- // Load packshow page
- loadPage();
-
- // Packshow page loader
- function loadPage() {
- // Data
- const language = getLang();
- const aid = getUrlArgv('id');
- const type = getUrlArgv('type');
-
- if (!['txt', 'txtfull', 'umd', 'jar'].includes(type)) {
- return false;
- }
-
- // Hide api box
- const apiBox = $('body>div:nth-child(1)');
- apiBox.style.display = 'none';
-
- // Disable api css
- addStyle('', 'plus_api_css');
-
- // AsyncManager
- const resource = {xmlIndex: null, xmlInfo: null, oDoc: null};
- const AM = new AsyncManager();
- AM.onfinish = fetchFinish;
-
- // Show soft alert
- alertify.message(TEXT_TIP_API_PACKSHOW_LOADING);
-
- // Set Title
- document.title = TEXT_GUI_API_PACKSHOW_TITLE_LOADING;
-
- // Load model page
- const bgImage = $('body>.plus_cbty_image');
- AM.add();
- getDocument(URL_PACKSHOW.replace('{A}', "1").replace('{T}', type), function(oDoc) {
- resource.oDoc = oDoc;
-
- // Insert body elements
- const nodes = Array.prototype.map.call(oDoc.body.childNodes, (elm) => (elm));
- for (const node of nodes) {
- document.body.insertBefore(node, bgImage);
- }
-
- // Insert css link and scripts
- const links = Array.prototype.map.call($All(oDoc, 'link[rel="stylesheet"][href]'), (elm) => (elm));
- const olinks = Array.prototype.map.call($All('link[rel="stylesheet"][href]'), (elm) => (elm));
- for (const link of links) {
- if (!link.href.startsWith('http')) {continue;}
- for (const olink of Array.prototype.filter.call(olinks, (l) => (l.href === link.href))) {olink.parentElement.removeChild(olink);}
- document.head.appendChild(link);
- }
- const scripts = Array.prototype.map.call($All(oDoc, 'script[src]'), (elm) => (elm));
- for (const script of scripts) {
- if (!script.src.startsWith('http')) {continue;}
- if (Array.prototype.filter.call($All('script[src]'), (s) => (s.src === script.src)).length > 0) {continue;}
- document.head.appendChild(script);
- }
-
- // Fix all <a>.href
- Array.from($All('a')).forEach((a) => {
- if (/https?:\/\/www\.wenku8\.(net|cc)\/modules\/article\/packshow.php\??/.test(a.href)) {
- a.href = a.href.replace(/([\?&])id=\d+/, '$1id='+aid)
- }
- });
-
- AM.finish();
- });
-
- // Load novel index
- AM.add();
- AndAPI.getNovelIndex({
- aid: aid,
- lang: language,
- callback: function(xml) {
- resource.xmlIndex = xml;
- AM.finish();
- }
- });
- AM.add();
- AndAPI.getNovelShortInfo({
- aid: aid,
- lang: language,
- callback: function(xml) {
- resource.xmlInfo = xml;
- AM.finish();
- }
- });
-
- AM.finishEvent = true;
-
- function fetchFinish() {
- // Resources
- const xmlIndex = resource.xmlIndex;
- const xmlInfo = resource.xmlInfo;
- const oDoc = resource.oDoc;
-
- // Elements
- const content = $('#content');
- const table = $(content, 'table');
- const tbody = $(table, 'tbody');
-
- // Data
- const name = $(xmlInfo, 'data[name="Title"]').childNodes[0].nodeValue;
- const lastupdate = $(xmlInfo, 'data[name="LastUpdate"]').getAttribute('value');
- const aBook = $(table, 'caption>a:first-child');
- const charsets = ['gbk', 'utf-8', 'big5', 'gbk', 'utf-8', 'big5'];
- const innerTexts = ['简体(G)', '简体(U)', '繁体(U)', '简体(G)', '简体(U)', '繁体(U)'];
-
- // Set Title
- document.title = TEXT_GUI_API_PACKSHOW_TITLE.replace('{N}', name);
-
- // Set book
- aBook.innerText = name;
- aBook.href = URL_BOOKINTRO.replace('{A}', aid);
-
- // Load book index
- loadIndex();
-
- // Soft alert
- alertify.success(TEXT_TIP_API_PACKSHOW_LOADED);
-
- // Enter common download page enhance
- pageDownload();
-
- // Book index loader
- function loadIndex() {
- switch (type) {
- case 'txt':
- loadIndex_txt();
- break;
- case 'txtfull':
- loadIndex_txtfull();
- break;
- case 'umd':
- loadIndex_umd();
- break;
- case 'jar':
- loadIndex_jar();
- break;
- }
- }
-
- // Book index loader for type txt
- function loadIndex_txt() {
- // Clear tbody trs
- for (const tr of $All(table, 'tr+tr')) {
- tbody.removeChild(tr);
- }
-
- // Make new trs
- for (const volume of $All(xmlIndex, 'volume')) {
- const tr = makeTr(volume);
- tbody.appendChild(tr);
- }
-
- function makeTr(volume) {
- const tr = $CrE('tr');
- const [tdName, td1, td2] = [$CrE('td'), $CrE('td'), $CrE('td')];
- const a = Array(6);
- const vid = volume.getAttribute('vid');
- const vname = volume.childNodes[0].nodeValue;
-
- // init tds
- tdName.classList.add('odd');
- td1.classList.add('even');
- td2.classList.add('even');
- td1.align = td2.align = 'center';
-
- // Set volume name
- tdName.innerText = vname;
-
- // Make <a> links
- for (let i = 0; i < a.length; i++) {
- a[i] = $CrE('a');
- a[i].target = '_blank';
- a[i].href = 'http://dl.wenku8.com/packtxt.php?aid=' + aid +
- '&vid=' + vid +
- (i >= 3 ? '&aname=' + $URL.encode(name) : '') +
- (i >= 3 ? '&vname=' + $URL.encode(vname) : '') +
- '&charset=' + charsets[i];
- a[i].innerText = innerTexts[i];
- (i < 3 ? td1 : td2).appendChild(a[i]);
- }
-
- // Insert whitespace textnode
- for (const i of [1, 2, 4, 5]) {
- (i < 3 ? td1 : td2).insertBefore(document.createTextNode('\n'), a[i]);
- }
-
- tr.appendChild(tdName);
- tr.appendChild(td1);
- tr.appendChild(td2);
-
- return tr;
- }
- }
-
- // Book index loader for type txtfull
- function loadIndex_txtfull() {
- const tr = $(tbody, 'tr+tr');
- const tds = Array.prototype.map.call(tr.children, (elm) => (elm));
-
- tds[0].innerText = lastupdate;
- tds[1].innerText = TEXT_GUI_UNKNOWN;
- for (const a of $All(tds[2], 'a')) {
- a.href = a.href.replace(/id=\d+/, 'id='+aid).replace(/fname=[^&]+/, 'fname='+$URL.encode(name));
- }
- }
-
- // Book index loader for type umd
- function loadIndex_umd() {
- const tr = $(tbody, 'tr+tr');
- const tds = toArray(tr.children);
-
- tds[0].innerText = tds[1].innerText = TEXT_GUI_UNKNOWN;
- tds[2].innerText = lastupdate;
- tds[3].innerText = $(xmlIndex, 'volume:first-child').childNodes[0].nodeValue + '—' + $(xmlIndex, 'volume:last-child').childNodes[0].nodeValue;
- const as = [].concat(toArray($All(tds[4], 'a'))).concat(toArray($All(table, 'caption>a+a')));
- for (const a of as) {
- a.href = a.href.replace(/id=\d+/, 'id='+aid);
- }
- }
-
- // Book index loader for type jar
- function loadIndex_jar() {
- // Currently type jar is the same as type umd
- loadIndex_umd();
- }
-
- function toArray(_arr) {
- return Array.prototype.map.call(_arr, (elm) => (elm));
- }
- }
- }
- }
-
- // Add a bottom-styled botton into bottom line, to the first place
- function addBottomButton(details) {
- const aClose = $('a[href="javascript:window.close()"]');
- const bottom = aClose.parentElement;
- const a = $CrE('a');
- const t1 = document.createTextNode('[');
- const t2 = document.createTextNode(']');
- const blank = $CrE('span');
- blank.innerHTML = ' ';
- blank.style.width = '0.5em';
- a.href = details.href;
- a.innerHTML = details.innerHTML;
- a.onclick = details.onclick;
- [blank, t2, a, t1].forEach((elm) => {bottom.insertBefore(elm, bottom.childNodes[0]);});
- }
- }
-
- // Check if current page is an wenku API page ('处理成功', '出现错误!')
- function isAPIPage() {
- // API page has just one .block div and one close-page button
- const block = $All('.block');
- const close = $All('a[href="javascript:window.close()"]');
- return block.length === 1 && close.length === 1;
- }
-
- // Basic functions
- // querySelector
- function $() {
- switch(arguments.length) {
- case 2:
- return arguments[0].querySelector(arguments[1]);
- break;
- default:
- return document.querySelector(arguments[0]);
- }
- }
- // querySelectorAll
- function $All() {
- switch(arguments.length) {
- case 2:
- return arguments[0].querySelectorAll(arguments[1]);
- break;
- default:
- return document.querySelectorAll(arguments[0]);
- }
- }
- // createElement
- function $CrE() {
- switch(arguments.length) {
- case 2:
- return arguments[0].createElement(arguments[1]);
- break;
- default:
- return document.createElement(arguments[0]);
- }
- }
- // Object1[prop] ==> Object2[prop]
- function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);}
- function copyProps(obj1, obj2, props) {props.forEach((prop) => (copyProp(obj1, obj2, prop)));}
-
- // Display an alertify prompt for editing user-remark
- function editUserRemark(uid, name, callback) {
- const config = CONFIG.RemarksConfig.getConfig();
- const user = config.user[uid] || {uid: uid, name: name, remark: ''};
-
- // Update name
- user.name = name;
- CONFIG.RemarksConfig.saveConfig(config);
-
- // Display dialog
- alertify.prompt(TEXT_GUI_USER_USERREMARKEDIT_TITLE, TEXT_GUI_USER_USERREMARKEDIT_MSG.replace('{N}', user.name), user.remark, onChange, onCancel);
-
- function onChange(evt, value) {
- const config = CONFIG.RemarksConfig.getConfig();
- if (value) {
- const user = config.user[uid] || {uid: uid, name: name, remark: ''};
- user.remark = value;
- config.user[uid] = user;
- } else {
- delete config.user[uid]
- }
- CONFIG.RemarksConfig.saveConfig(config);
-
- callback(value);
- }
-
- function onCancel() {}
- }
-
- // Send reply for bookreview
- // Arg: {rid, title, content, onload:(oDoc)=>{}, onerror:()=>{}}
- function sendReviewReply(detail) {
- if (typeof($URL) !== 'object') {
- DoLog(LogLevel.Error, 'sendReviewReply: $URL not found.');
- return false;
- }
- const data = '&ptitle=' + $URL.encode(detail.title) + '&pcontent=' + $URL.encode(detail.content);
- const url = `https://${location.host}/modules/article/reviewshow.php?rid=` + detail.rid.toString();
- GM_xmlhttpRequest({
- method: 'POST',
- url: url,
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
- data: data,
- responseType: 'blob',
- onload: function (response) {
- if (!detail.onload) {return false;}
- parseDocument(response.response, detail.onload);
- },
- onerror: function (e) {
- detail.onerror && detail.onerror(e);
- }
- });
- }
-
- // Android API set
- function AndroidAPI() {
- const AA = this;
- const DParser = new DOMParser();
-
- const encode = AA.encode = function(str) {
- return '&appver=1.13&request=' + btoa(str) + '&timetoken=' + (new Date().getTime());
- };
-
- const request = AA.request = function(details) {
- const url = details.url;
- const type = details.type || 'text';
- const callback = details.callback || function() {};
- const args = details.args || [];
- GM_xmlhttpRequest({
- method: 'POST',
- url: 'http://app.wenku8.com/android.php',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 7.1.2; unknown Build/NZH54D)'
- },
- data: encode(url),
- onload: function(e) {
- let result;
- switch (type) {
- case 'xml':
- result = DParser.parseFromString(e.responseText, 'text/xml');
- break;
- case 'text':
- result = e.responseText;
- break;
- }
- callback.apply(null, [result].concat(args));
- },
- onerror: function(e) {
- DoLog(LogLevel.Error, 'AndroidAPI.request Error while requesting "' + url + '"');
- DoLog(LogLevel.Error, e);
- }
- });
- };
-
- // aid, lang, callback, args
- AA.getNovelShortInfo = function(details) {
- const aid = details.aid;
- const lang = details.lang;
- const callback = details.callback || function() {};
- const args = details.args || [];
- const url = 'action=book&do=info&aid=' + aid + '&t=' + lang;
- request({
- url: url,
- callback: callback,
- args: args,
- type: 'xml'
- });
- }
-
- // aid, lang, callback, args
- AA.getNovelIndex = function(details) {
- const aid = details.aid;
- const lang = details.lang;
- const callback = details.callback || function() {};
- const args = details.args || [];
- const url = 'action=book&do=list&aid=' + aid + '&t=' + lang;
- request({
- url: url,
- callback: callback,
- args: args,
- type: 'xml'
- });
- };
-
- // aid, cid, lang, callback, args
- AA.getNovelContent = function(details) {
- const aid = details.aid;
- const cid = details.cid;
- const lang = details.lang;
- const callback = details.callback || function() {};
- const args = details.args || [];
- const url = 'action=book&do=text&aid=' + aid + '&cid=' + cid + '&t=' + lang;
- request({
- url: url,
- callback: callback,
- args: args,
- type: 'text'
- });
- };
- }
-
- // Create reply-area with enhanced UBBEditor
- function makeEditor(parent, rid, aid) {
- parent.innerHTML = `<form name="frmreview" method="post" action="https://${location.host}/modules/article/reviewshow.php?rid={RID}"><table class="grid" width="100%" align="center"><tbody><tr><td class="odd" width="25%">标题</td><td class="even"><input type="text" class="text" name="ptitle" id="ptitle" size="60" maxlength="60" value="" /></td></tr></tbody><caption>回复书评:</caption><tbody><tr><td class="odd" width="25%">内容(每帖+1积分)</td><td class="even"><textarea class="textarea" name="pcontent" id="pcontent" cols="60" rows="12"></textarea></td></tr><tr><td class="odd" width="25%"> </td><td class="even"><input type="submit" name="Submit" class="button" value="发表书评(Ctrl+Enter)" style="padding: 0.3em 0.4em; height: auto;" /><span></span></td></tr></tbody></table></form>`.replace('{RID}', rid).replace('{AID}', aid);
- const script = $CrE('script');
- script.innerHTML = `loadJs("https://${location.host}/scripts/ubbeditor_gbk.js", function(){UBBEditor.Create("pcontent");});`;
- $(parent, '#pcontent').parentElement.appendChild(script);
- areaReply();
- }
-
- // getMyUserDetail with soft alerts
- function refreshMyUserDetail(callback, args=[]) {
- alertify.notify(TEXT_ALT_USRDTL_REFRESH);
- getMyUserDetail(function() {
- const alertBox = alertify.success(TEXT_ALT_USRDTL_REFRESHED);
-
- // rewrite onclick function from copying to showing details
- alertBox.callback = function(isClicked) {
- isClicked && alertify.message(altMyUserDetail()/*JSON.stringify(getMyUserDetail())*/);
- }
-
- // callback if exist
- callback ? callback.apply(args) : function() {};
- })
- }
-
- // Get my user info detail
- // if no argument provided, this function will just read userdetail from gm_storage
- // otherwise, the function will make a http request to get the latest userdetail
- // if no argument provided and no gm_storage record, then will just return false
- // if not logged in, return false
- // if callback is not a function, then will just request&store but not callback
- function getMyUserDetail(callback, args=[]) {
- if (getUserID() === null) {
- return false;
- }
- if (callback) {
- requestWeb();
- return true;
- } else {
- const storage = CONFIG.userDtlePrefs.getConfig();
- if (!storage.userDetail && !storage.userFriends) {
- DoLog(LogLevel.Warning, 'Attempt to read userDetail from gm_storage but no record found');
- return false;
- };
- const userDetail = storage;
- return userDetail;
- }
-
- function requestWeb() {
- const lastStorage = CONFIG ? CONFIG.userDtlePrefs.getConfig() : undefined;
- let restXHR = 2;
- let storage = {};
-
- // Request userDetail
- getDocument(URL_USRDETAIL, detailLoaded)
-
- // Request userFriends
- getDocument(URL_USRFRIEND, friendLoaded)
-
- function detailLoaded(oDoc) {
- const content = $(oDoc, '#content');
- storage.userDetail = {
- userID: Number($(content, 'tr:nth-child(1)>.even').innerText), // '用户ID'
- userLink: $(content, 'tr:nth-child(2)>.even').innerText, // '推广链接'
- userName: $(content, 'tr:nth-child(3)>.even').innerText, // '用户名'
- displayName: $(content, 'tr:nth-child(4)>.even').innerText, // '用户昵称'
- userType: $(content, 'tr:nth-child(5)>.even').innerText, // '等级'
- userGrade: $(content, 'tr:nth-child(6)>.even').innerText, // '头衔'
- gender: $(content, 'tr:nth-child(7)>.even').innerText, // '性别'
- email: $(content, 'tr:nth-child(8)>.even').innerText, // 'Email'
- qq: $(content, 'tr:nth-child(9)>.even').innerText, // 'QQ'
- msn: $(content, 'tr:nth-child(10)>.even').innerText, // 'MSN'
- site: $(content, 'tr:nth-child(11)>.even').innerText, // '网站'
- signupDate: $(content, 'tr:nth-child(13)>.even').innerText, // '注册日期'
- contibute: $(content, 'tr:nth-child(14)>.even').innerText, // '贡献值'
- exp: $(content, 'tr:nth-child(15)>.even').innerText, // '经验值'
- credit: $(content, 'tr:nth-child(16)>.even').innerText, // '现有积分'
- friends: $(content, 'tr:nth-child(17)>.even').innerText, // '最多好友数'
- mailbox: $(content, 'tr:nth-child(18)>.even').innerText, // '信箱最多消息数'
- bookcase: $(content, 'tr:nth-child(19)>.even').innerText, // '书架最大收藏量'
- vote: $(content, 'tr:nth-child(20)>.even').innerText, // '每天允许推荐次数'
- sign: $(content, 'tr:nth-child(22)>.even').innerText, // '用户签名'
- intoduction: $(content, 'tr:nth-child(23)>.even').innerText, // '个人简介'
- userImage: $(content, 'tr>td>img').src // '头像'
- }
- loaded();
- }
-
- function friendLoaded(oDoc) {
- const content = $(oDoc, '#content');
- const trs = $All(content, 'tr');
- const friends = [];
- const lastFriends = lastStorage ? lastStorage.userFriends : undefined;
-
- for (let i = 1; i < trs.length; i++) {
- getFriends(trs[i]);
- }
- storage.userFriends = friends;
- loaded();
-
- function getFriends(tr) {
- // Check if userID exist
- if (isNaN(Number($(tr.children[2], 'a').href.match(/\?uid=(\d+)/)[1]))) {return false;};
-
- // Collect information
- let friend = {
- userID: Number($(tr.children[2], 'a').href.match(/\?uid=(\d+)/)[1]),
- userName: tr.children[0].innerText,
- signupDate: tr.children[1].innerText
- }
- friend = fillLocalInfo(friend)
- friends.push(friend);
- }
-
- function fillLocalInfo(friend) {
- if (!lastFriends) {return friend;};
- for (const f of lastFriends) {
- if (f.userID === friend.userID) {
- for (const [key, value] of Object.entries(f)) {
- if (friend.hasOwnProperty(key)) {continue;};
- friend[key] = value;
- }
- break;
- }
- }
- return friend;
- }
- }
-
- function loaded() {
- restXHR--;
- if (restXHR === 0) {
- // Save to gm_storage
- if (CONFIG) {
- storage.lasttime = getTime('-', false);
- CONFIG.userDtlePrefs.saveConfig(storage);
- }
-
- // Callback
- typeof(callback) === 'function' ? callback.apply(null, [storage].concat(args)) : function() {};
- }
- }
- }
- }
-
- // Show userdetail in an alertify alertbox
- function altMyUserDetail() {
- const json = getMyUserDetail();
- alertify.message(JSON.stringify(getMyUserDetail()));
- }
-
- function exportConfig(noPass=false) {
- // Get config
- const config = {};
- const getValue = window.getValue ? window.getValue : GM_getValue;
- const listValues = window.listValues ? window.listValues : GM_listValues;
- for (const key of listValues()) {
- config[key] = getValue(key);
- }
-
- // Remove username and password if required
- noPass && (config[KEY_CM].users = {});
-
- // Download
- const text = JSON.stringify(config);
- const name = '轻小说文库+_配置文件({P})_v{V}_{T}.wkp'.replace('{P}', noPass ? '无账号密码' : '含账号密码').replace('{V}', GM_info.script.version).replace('{T}', getTime());
- downloadText(text, name);
- alertify.success(TEXT_ALT_CONFIG_EXPORTED.replace('{N}', name));
- }
-
- function importConfig(json) {
- // Redirect
- redirectGMStorage();
-
- // Preserve users
- const users = GM_getValue('Config-Manager').users;
-
- // Delete json
- for (const [key, value] of GM_listValues()) {
- GM_deleteValue(key, value);
- }
-
- // Set json
- for (const [key, value] of Object.entries(json)) {
- GM_setValue(key, value);
- }
-
- // Preserve users
- const config = GM_getValue('Config-Manager', {});
- if (!config.users) {config.users = {}}
- for (const [name, user] of Object.entries(users)) {
- config.users[name] = user;
- }
- GM_setValue('Config-Manager', config);
-
- // Reload
- location.reload();
- }
-
- function sortLaterReads(books, sortby) {
- const sorter = FUNC_LATERBOOK_SORTERS[sortby].sorter;
- return Object.values(books).sort(sorter);
- }
-
- function getUserID() {
- const match = $URL.decode(document.cookie).match(/jieqiUserId=(\d+)/);
- const id = match && match[1] ? Number(match[1]) : null;
- return isNaN(id) ? null : id;
- }
-
- function getUserName() {
- const match = $URL.decode(document.cookie).match(/jieqiUserName=([^, ;]+)/);
- const name = match ? match[1] : null;
- return name;
- }
-
- // Reload page without re-sending form data, and keeps reviewshow-page
- function reloadPage() {
- const url = /^https?:\/\/www\.wenku8\.(net|cc)\/modules\/article\/reviewshow\.php/.test(location.href) ? URL_REVIEWSHOW_2.replace('{R}', getUrlArgv('rid')).replace('{P}', $('#pagelink>strong').innerText) : location.href;
- location.href = url;
- }
-
- // Check if tipobj is ready, if not, then make it
- function tipcheck() {
- DoLog(LogLevel.Info, 'checking tipobj...');
- if (typeof(tipobj) === 'object' && tipobj !== null) {
- DoLog(LogLevel.Info, 'tipobj ready...');
- return true;
- } else {
- DoLog(LogLevel.Warning, 'tipobj not ready');
- if (typeof(tipinit) === 'function') {
- DoLog(LogLevel.Success, 'tipinit executed');
- tipinit();
- return true;
- } else {
- DoLog(LogLevel.Error, 'tipinit not found');
- return false;
- }
- }
- }
-
- // New tipobj movement method. Makes sure the tipobj stay close with the mouse.
- function tipscroll() {
- if (!tipready) {return false;}
-
- DoLog('tipscroll executed. ')
- tipobj.style.position = 'fixed';
- window.addEventListener('mousemove', tipmoveplus)
- return true;
-
- function tipmoveplus(e) {
- tipobj.style.left = e.clientX + tipx + 'px';
- tipobj.style.top = e.clientY + tipy + 'px';
- }
- }
-
- // show & hide tip when mouse in & out. accepts tip as a string or a function that returns the tip string
- function settip(elm, tip) {
- typeof(tip) === 'string' && (elm.tiptitle = tip);
- typeof(tip) === 'function' && (elm.tipgetter = tip);
- elm.removeEventListener('mouseover', showtip);
- elm.removeEventListener('mouseout', hidetip);
- elm.addEventListener('mouseover', showtip);
- elm.addEventListener('mouseout', hidetip);
- }
-
- function showtip(e) {
- if (e && e.currentTarget && (e.currentTarget.tiptitle || e.currentTarget.tipgetter)) {
- const tip = e.currentTarget.tiptitle || e.currentTarget.tipgetter();
- if (tipready) {
- tipshow(tip);
- e.currentTarget.title && e.currentTarget.removeAttribute('title');
- } else {
- e.currentTarget.title = e.currentTarget.tiptitle;
- }
- } else if (typeof(e) === 'string') {
- tipready && tipshow(e);
- }
- }
-
- function hidetip() {
- tipready && tiphide();
- }
-
- // Side-located control panel
- // Requirements: FontAwesome, tooltip.css(from https://github.com/felipefialho/css-components/blob/main/build/tooltip/tooltip.css)
- // Use 'new' keyword
- function SidePanel() {
- // Public SP
- const SP = this;
- const elms = SP.elements = {};
-
- // Private _SP
- // keys start with '_' shouldn't be modified
- const _SP = {
- _id: {
- css: 'sidepanel-style',
- usercss: 'sidepanel-style-user',
- panel: 'sidepanel-panel'
- },
- _class: {
- button: 'sidepanel-button'
- },
- _css: '#sidepanel-panel {position: fixed; background-color: #00000000; padding: 0.5vmin; line-height: 3.5vmin; height: auto; display: flex; transition-duration: 0.3s; z-index: 9999999999;} #sidepanel-panel.right {right: 3vmin;} #sidepanel-panel.bottom {bottom: 3vmin; flex-direction: column-reverse;} #sidepanel-panel.left {left: 3vmin;} #sidepanel-panel.top {top: 3vmin; flex-direction: column;} .sidepanel-button {padding: 1vmin; margin: 0.5vmin; font-size: 3.5vmin; border-radius: 10%; text-align: center; color: #00000088; background-color: #FFFFFF88; box-shadow:3px 3px 2px #00000022; user-select: none; transition-duration: inherit;} .sidepanel-button:hover {color: #FFFFFFDD; background-color: #000000DD;}',
- _directions: ['left', 'right', 'top', 'bottom']
- };
-
- Object.defineProperty(SP, 'css', {
- configurable: false,
- enumerable: true,
- get: () => (_SP.css),
- set: (css) => {
- _SP.css = css;
- spAddStyle(css, _SP._id.css);
- }
- });
- Object.defineProperty(SP, 'usercss', {
- configurable: false,
- enumerable: true,
- get: () => (_SP.usercss),
- set: (usercss) => {
- _SP.usercss = usercss;
- spAddStyle(usercss, _SP._id.usercss);
- }
- });
- SP.css = _SP._css;
-
- SP.create = function() {
- // Create panel
- const panel = elms.panel = document.createElement('div');
- panel.id = _SP._id.panel;
- SP.setPosition('bottom-right');
- document.body.appendChild(panel);
-
- // Prepare buttons
- elms.buttons = [];
- }
-
- // Insert a button to given index
- // details = {index, text, faicon, id, tip, className, onclick, listeners}, all optional
- // listeners = [..[..args]]. [..args] will be applied as button.addEventListener's args
- // faicon = 'fa-icon-name-classname fa-icon-style-classname', this arg stands for a FontAwesome icon to be inserted inside the botton
- // Returns the button(HTMLDivElement), including button.faicon(HTMLElement/HTMLSpanElement in firefox, <i>) if faicon is set
- SP.insert = function(details) {
- const index = details.index;
- const text = details.text;
- const faicon = details.faicon;
- const id = details.id;
- const tip = details.tip;
- const className = details.className;
- const onclick = details.onclick;
- const listeners = details.listeners || [];
-
- const button = document.createElement('div');
- text && (button.innerHTML = text);
- id && (button.id = id);
- tip && setTooltip(button, tip); //settip(button, tip);
- className && (button.className = className);
- onclick && (button.onclick = onclick);
- if (faicon) {
- const i = document.createElement('i');
- i.className = faicon;
- button.faicon = i;
- button.appendChild(i);
- }
- for (const listener of listeners) {
- button.addEventListener.apply(button, listener);
- }
- button.classList.add(_SP._class.button);
-
- elms.buttons = insertItem(elms.buttons, button, index);
- index < elms.buttons.length ? elms.panel.insertBefore(button, elms.panel.children[index]) : elms.panel.appendChild(button);
- return button;
- }
-
- // Append a button
- SP.add = function(details) {
- details.index = elms.buttons.length;
- return SP.insert(details);
- }
-
- // Remove a button
- SP.remove = function(arg) {
- let index, elm;
- if (arg instanceof HTMLElement) {
- elm = arg;
- index = elms.buttons.indexOf(elm);
- } else if (typeof(arg) === 'number') {
- index = arg;
- elm = elms.buttons[index];
- } else if (typeof(arg) === 'string') {
- elm = $(elms.panel, arg);
- index = elms.buttons.indexOf(elm);
- }
-
- elms.buttons = delItem(elms.buttons, index);
- elm.parentElement.removeChild(elm);
- }
-
- // Sets the display position by texts like 'right-bottom'
- SP.setPosition = function(pos) {
- const poses = _SP.direction = pos.split('-');
- const avails = _SP._directions;
-
- // Available check
- if (poses.length !== 2) {return false;}
- for (const p of poses) {
- if (!avails.includes(p)) {return false;}
- }
-
- // remove all others
- for (const p of avails) {
- elms.panel.classList.remove(p);
- }
-
- // add new pos
- for (const p of poses) {
- elms.panel.classList.add(p);
- }
-
- // Change tooltips' direction
- elms.buttons && elms.buttons.forEach(function(button) {
- if (button.getAttribute('role') === 'tooltip') {
- setTooltipDirection(button)
- }
- });
- }
-
- // Gets the current display position
- SP.getPosition = function() {
- return _SP.direction.join('-');
- }
-
- // Append a style text to document(<head>) with a <style> element
- // Replaces existing id-specificed <style>s
- function spAddStyle(css, id) {
- const style = document.createElement("style");
- id && (style.id = id);
- style.textContent = css;
- for (const elm of $All('#'+id)) {
- elm.parentElement && elm.parentElement.removeChild(elm);
- }
- document.head.appendChild(style);
- }
-
- // Set a tooltip to the element
- function setTooltip(elm, text, direction='auto') {
- elm.tooltip = tippy(elm, {
- content: text,
- arrow: true,
- hideOnClick: false
- });
-
- // Old version, uses tooltip.css
- /*
- elm.setAttribute('role', 'tooltip');
- elm.setAttribute('aria-label', text);
- */
-
- setTooltipDirection(elm, direction);
- }
-
- function setTooltipDirection(elm, direction='auto') {
- direction === 'auto' && (direction = _SP.direction.includes('left') ? 'right' : 'left');
- if (!_SP._directions.includes(direction)) {throw new Error('setTooltip: invalid direction');}
-
- // Tippy direction
- if (!elm.tooltip) {
- DoLog(LogLevel.Error, 'SidePanel.setTooltipDirection: Given elm has no tippy instance(elm.tooltip)');
- throw new Error('SidePanel.setTooltipDirection: Given elm has no tippy instance(elm.tooltip)');
- }
- elm.tooltip.setProps({
- placement: direction
- });
-
- // Old version, uses tooltip.css
- /*
- for (const dirct of _SP._directions) {
- elm.classList.remove('tooltip-'+dirct);
- }
- elm.classList.add('tooltip-'+direction);
- */
- }
-
- // Del an item from an array using its index. Returns the array but can NOT modify the original array directly!!
- function delItem(arr, index) {
- arr = arr.slice(0, index).concat(arr.slice(index+1));
- return arr;
- }
-
- // Insert an item into an array using given index. Returns the array but can NOT modify the original array directly!!
- function insertItem(arr, item, index) {
- arr = arr.slice(0, index).concat(item).concat(arr.slice(index));
- return arr;
- }
- }
-
- // Create a list gui like reviewshow.php##FontSizeTable
- // list = {display: '', id: '', parentElement: <*>, insertBefore: <*>, list: [{value: '', onclick: Function, tip: ''/Function}, ...], visible: bool, onshow: Function(bool shown), onhide: Function(bool hidden)}
- // structure: {div: <div>, ul: <ul>, list: [{li: <li>, button: <input>}, ...], visible: list.visible, show: Function, hide: Function, append: Function({...}), remove: Function(index), clear: Function, onshow: list.onshow, onhide: list.onhide}
- // Use 'new' keyword
- function PlusList(list) {
- const PL = this;
-
- // Make list
- const div = PL.div = document.createElement('div');
- const ul = PL.ul = document.createElement('ul');
- div.classList.add(CLASSNAME_LIST);
- div.appendChild(ul);
- list.display && (div.style.display = list.display);
- list.id && (div.id = list.id);
- list.parentElement && list.parentElement.insertBefore(div, list.insertBefore ? list.insertBefore : null);
-
- PL.list = [];
- for (const item of list.list) {
- appendItem(item);
- }
-
- // Attach properties
- let onshow = list.onshow ? list.onshow : function() {};
- let onhide = list.onhide ? list.onhide : function() {};
- let visible = list.visible;
- PL.create = createItem;
- PL.append = appendItem;
- PL.insert = insertItem;
- PL.remove = removeItem;
- PL.clear = removeAll;
- PL.show = showList;
- PL.hide = hideList;
- Object.defineProperty(PL, 'onshow', {
- get: function() {return onshow;},
- set: function(func) {
- onshow = func ? func : function() {};
- },
- configurable: false,
- enumerable: true
- });
- Object.defineProperty(PL, 'onhide', {
- get: function() {return onhide;},
- set: function(func) {
- onhide = func ? func : function() {};
- },
- configurable: false,
- enumerable: true
- });
- Object.defineProperty(PL, 'visible', {
- get: function() {return visible;},
- set: function(bool) {
- if (typeof(bool) !== 'boolean') {return false;};
- visible = bool;
- bool ? showList() : hideList();
- },
- configurable: false,
- enumerable: true
- });
- Object.defineProperty(PL, 'maxheight', {
- get: function() {return maxheight;},
- set: function(num) {
- if (typeof(num) !== 'number') {return false;};
- maxheight = num;
- },
- configurable: false,
- enumerable: true
- });
-
- // Apply configurations
- div.style.display = list.visible === true ? '' : 'none';
-
- // Functions
- function appendItem(item) {
- const listitem = createItem(item);
- ul.appendChild(listitem.li);
- PL.list.push(listitem);
- return listitem;
- }
-
- function insertItem(item, index, insertByNode=false) {
- const listitem = createItem(item);
- const children = insertByNode ? ul.childNodes : ul.children;
- const elmafter = children[index];
- ul.insertBefore(item.li, elmafter);
- inserttoarr(PL.list, listitem, index);
- }
-
- function createItem(item) {
- const listitem = {
- remove: () => {removeItem(listitem);},
- li: document.createElement('li'),
- button: document.createElement('input')
- };
- const li = listitem.li;
- const btn = listitem.button;
- btn.type = 'button';
- btn.classList.add(CLASSNAME_LIST_BUTTON);
- li.classList.add(CLASSNAME_LIST_ITEM);
- item.value && (btn.value = item.value);
- item.onclick && btn.addEventListener('click', item.onclick);
- item.tip && settip(li, item.tip);
- item.tip && settip(btn, item.tip);
- li.appendChild(btn);
- return listitem;
- }
-
- function removeItem(itemorindex) {
- // Get index
- let index;
- if (typeof(itemorindex) === 'number') {
- index = itemorindex;
- } else if (typeof(itemorindex) === 'object') {
- index = PL.list.indexOf(itemorindex);
- } else {
- return false;
- }
- if (index < 0 || index >= PL.list.length) {
- return false;
- }
-
- // Remove
- const li = PL.list[index];
- ul.removeChild(li.li);
- delfromarr(PL.list, index);
- return li;
- }
-
- function removeAll() {
- const length = PL.list.length;
- for (let i = 0; i < length; i++) {
- removeItem(0);
- }
- }
-
- function showList() {
- if (visible) {return false;};
- onshow(false);
- div.style.display = '';
- onshow(true);
- visible = true;
- }
-
- function hideList() {
- if (!visible) {return false;};
- onhide(false);
- div.style.display = 'none';
- hidetip();
- onhide(true);
- visible = false;
- }
-
- // Support functions
- // Del an item from an array by provided index, returns the deleted item. MODIFIES the original array directly!!
- function delfromarr(arr, delIndex) {
- if (delIndex < 0 || delIndex > arr.length-1) {
- return false;
- }
- const deleted = arr[delIndex];
- for (let i = delIndex; i < arr.length-1; i++) {
- arr[i] = arr[i+1];
- }
- arr.pop();
- return deleted;
- }
-
- // Insert an item to an array by its provided index, returns the item itself. MODIFIES the original array directly!!
- function inserttoarr(arr, item, index) {
- if (index < 0 || index > arr.length-1) {
- return false;
- }
- for (let i = arr.length; i > index; i--) {
- arr[i] = arr[i-1];
- }
- arr[index] = item;
- return item;
- }
- }
-
- // A table-based setting panel using alertify-js
- // Requires: alertify-js
- // Use 'new' keyword
- // Usage:
- /*
- var panel = new SettingPanel({
- className: '',
- id: '',
- name: '',
- tables: [
- {
- className: '',
- id: '',
- name: '',
- rows: [
- {
- className: '',
- id: '',
- name: '',
- blocks: [
- {
- innerHTML / innerText: ''
- colSpan: 1,
- rowSpan: 1,
- className: '',
- id: '',
- name: '',
- children: [HTMLElement, ...]
- },
- ...
- ]
- },
- ...
- ]
- },
- ...
- ]
- });
- */
- function SettingPanel(details={}) {
- const SP = this;
- SP.insertTable = insertTable;
- SP.appendTable = appendTable;
- SP.removeTable = removeTable;
- SP.remove = remove;
- SP.PanelTable = PanelTable;
- SP.PanelRow = PanelRow;
- SP.PanelBlock = PanelBlock;
-
- // <div> element
- const elm = $C('div');
- copyProps(details, elm, ['id', 'name', 'className']);
- elm.classList.add('settingpanel-container');
-
- // Configure object
- let css='', usercss='';
- SP.element = elm;
- SP.elements = {};
- SP.children = {};
- SP.tables = [];
- SP.length = 0;
- details.id !== undefined && (SP.elements[details.id] = elm);
- copyProps(details, SP, ['id', 'name']);
- Object.defineProperty(SP, 'css', {
- configurable: false,
- enumerable: true,
- get: function() {
- return css;
- },
- set: function(_css) {
- addStyle(_css, 'settingpanel-css');
- css = _css;
- }
- });
- Object.defineProperty(SP, 'usercss', {
- configurable: false,
- enumerable: true,
- get: function() {
- return usercss;
- },
- set: function(_usercss) {
- addStyle(_usercss, 'settingpanel-usercss');
- usercss = _usercss;
- }
- });
- SP.css = '.settingpanel-table {border-spacing: 0px; border-collapse: collapse; width: 100%; margin: 2em 0;} .settingpanel-block {border: 1px solid; text-align: center; vertical-align: middle; padding: 3px; text-align: left;}'
-
- // Create tables
- if (details.tables) {
- for (const table of details.tables) {
- if (table instanceof PanelTable) {
- appendTable(table);
- } else {
- appendTable(new PanelTable(table));
- }
- }
- }
-
- // Make alerity box
- const box = SP.alertifyBox = alertify.alert();
- clearChildNodes(box.elements.content);
- box.elements.content.appendChild(elm);
- box.elements.content.style.overflow = 'auto';
- box.setHeader(TEXT_GUI_DETAIL_MANAGE_HEADER);
- box.setting({
- maximizable: true,
- overflow: true
- });
- box.show();
-
- // Insert a Panel-Row
- // Returns Panel object
- function insertTable(table, index) {
- // Insert table
- !(table instanceof PanelTable) && (table = new PanelTable(table));
- index < SP.length ? elm.insertBefore(table.element, elm.children[index]) : elm.appendChild(table.element);
- insertItem(SP.tables, table, index);
- table.id !== undefined && (SP.children[table.id] = table);
- SP.length++;
-
- // Set parent
- table.parent = SP;
-
- // Inherit elements
- for (const [id, subelm] of Object.entries(table.elements)) {
- SP.elements[id] = subelm;
- }
-
- // Inherit children
- for (const [id, child] of Object.entries(table.children)) {
- SP.children[id] = child;
- }
- return SP;
- }
-
- // Append a Panel-Row
- // Returns Panel object
- function appendTable(table) {
- return insertTable(table, SP.length);
- }
-
- // Remove a Panel-Row
- // Returns Panel object
- function removeTable(index) {
- const table = SP.tables[index];
- SP.element.removeChild(table.element);
- removeItem(SP.rows, index);
- return SP;
- }
-
- // Remove itself from parentElement
- // Returns Panel object
- function remove() {
- SP.element.parentElement && SP.parentElement.removeChild(SP.element);
- return SP;
- }
-
- // Panel-Table object
- // Use 'new' keyword
- function PanelTable(details={}) {
- const PT = this;
- PT.insertRow = insertRow;
- PT.appendRow = appendRow;
- PT.removeRow = removeRow;
- PT.remove = remove
-
- // <table> element
- const elm = $C('table');
- copyProps(details, elm, ['id', 'name', 'className']);
- elm.classList.add('settingpanel-table');
-
- // Configure
- PT.element = elm;
- PT.elements = {};
- PT.children = {};
- PT.rows = [];
- PT.length = 0;
- details.id !== undefined && (PT.elements[details.id] = elm);
- copyProps(details, PT, ['id', 'name']);
-
- // Append rows
- if (details.rows) {
- for (const row of details.rows) {
- if (row instanceof PanelRow) {
- insertRow(row);
- } else {
- insertRow(new PanelRow(row));
- }
- }
- }
-
- // Insert a Panel-Row
- // Returns Panel-Table object
- function insertRow(row, index) {
- // Insert row
- !(row instanceof PanelRow) && (row = new PanelRow(row));
- index < PT.length ? elm.insertBefore(row.element, elm.children[index]) : elm.appendChild(row.element);
- insertItem(PT.rows, row, index);
- row.id !== undefined && (PT.children[row.id] = row);
- PT.length++;
-
- // Set parent
- row.parent = PT;
-
- // Inherit elements
- for (const [id, subelm] of Object.entries(row.elements)) {
- PT.elements[id] = subelm;
- }
-
- // Inherit children
- for (const [id, child] of Object.entries(row.children)) {
- PT.children[id] = child;
- }
- return PT;
- }
-
- // Append a Panel-Row
- // Returns Panel-Table object
- function appendRow(row) {
- return insertRow(row, PT.length);
- }
-
- // Remove a Panel-Row
- // Returns Panel-Table object
- function removeRow(index) {
- const row = PT.rows[index];
- PT.element.removeChild(row.element);
- removeItem(PT.rows, index);
- return PT;
- }
-
- // Remove itself from parentElement
- // Returns Panel-Table object
- function remove() {
- PT.parent instanceof SettingPanel && PT.parent.removeTable(PT.tables.indexOf(PT));
- return PT;
- }
- }
-
- // Panel-Row object
- // Use 'new' keyword
- function PanelRow(details={}) {
- const PR = this;
- PR.insertBlock = insertBlock;
- PR.appendBlock = appendBlock;
- PR.removeBlock = removeBlock;
- PR.remove = remove;
-
- // <tr> element
- const elm = $C('tr');
- copyProps(details, elm, ['id', 'name', 'className']);
- elm.classList.add('settingpanel-row');
-
- // Configure object
- PR.element = elm;
- PR.elements = {};
- PR.children = {};
- PR.blocks = [];
- PR.length = 0;
- details.id !== undefined && (PR.elements[details.id] = elm);
- copyProps(details, PR, ['id', 'name']);
-
- // Append blocks
- if (details.blocks) {
- for (const block of details.blocks) {
- if (block instanceof PanelBlock) {
- appendBlock(block);
- } else {
- appendBlock(new PanelBlock(block));
- }
- }
- }
-
- // Insert a Panel-Block
- // Returns Panel-Row object
- function insertBlock(block, index) {
- // Insert block
- !(block instanceof PanelBlock) && (block = new PanelBlock(block));
- index < PR.length ? elm.insertBefore(block.element, elm.children[index]) : elm.appendChild(block.element);
- insertItem(PR.blocks, block, index);
- block.id !== undefined && (PR.children[block.id] = block);
- PR.length++;
-
- // Set parent
- block.parent = PR;
-
- // Inherit elements
- for (const [id, subelm] of Object.entries(block.elements)) {
- PR.elements[id] = subelm;
- }
-
- // Inherit children
- for (const [id, child] of Object.entries(block.children)) {
- PR.children[id] = child;
- }
- return PR;
- };
-
- // Append a Panel-Block
- // Returns Panel-Row object
- function appendBlock(block) {
- return insertBlock(block, PR.length);
- }
-
- // Remove a Panel-Block
- // Returns Panel-Row object
- function removeBlock(index) {
- const block = PR.blocks[index];
- PR.element.removeChild(block.element);
- removeItem(PR.blocks, index);
- return PR;
- }
-
- // Remove itself from parent
- // Returns Panel-Row object
- function remove() {
- PR.parent instanceof PanelTable && PR.parent.removeRow(PR.parent.rows.indexOf(PR));
- return PR;
- }
- }
-
- // Panel-Block object
- // Use 'new' keyword
- function PanelBlock(details={}) {
- const PB = this;
- PB.remove = remove;
-
- // <td> element
- const elm = $C('td');
- copyProps(details, elm, ['innerText', 'innerHTML', 'colSpan', 'rowSpan', 'id', 'name', 'className']);
- elm.classList.add('settingpanel-block');
-
- // Configure object
- PB.element = elm;
- PB.elements = {};
- PB.children = {};
- details.id !== undefined && (PB.elements[details.id] = elm);
- copyProps(details, PB, ['id', 'name']);
-
- // Append to parent if need
- details.parent instanceof PanelRow && (PB.parent = details.parent.appendBlock(PB));
-
- // Append child elements if exist
- if (details.children) {
- for (const child of details.children) {
- elm.appendChild(child);
- }
- }
-
- // Remove itself from parent
- // Returns Panel-Block object
- function remove() {
- PB.parent instanceof PanelRow && PB.parent.removeBlock(PB.parent.blocks.indexOf(PB));
- return PB;
- }
- }
-
- function $(e) {return document.querySelector(e);}
- function $C(e) {return document.createElement(e);}
- function $R(e) {return $(e) && $(e).parentElement.removeChild($(e));}
- function clearChildNodes(elm) {for (const el of elm.childNodes) {elm.removeChild(el);}}
- function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);}
- function copyProps(obj1, obj2, props) {props.forEach((prop) => (copyProp(obj1, obj2, prop)));}
- function insertItem(arr, item, index) {
- for (let i = arr.length; i > index ; i--) {
- arr[i] = arr[i-1];
- }
- arr[index] = item;
- return arr;
- }
- function removeItem(arr, index) {
- for (let i = index; i < arr.length-1; i++) {
- arr[i] = arr[i+1];
- }
- delete arr[arr.length-1];
- return arr;
- }
- function addStyle(css, id) {
- $R('#'+id);
- const style = $C('style');
- style.innerHTML = css;
- style.id = id;
- document.head.appendChild(style);
- return style
- }
- }
-
- // Create a left .block operatingArea
- // options = {type: '', ...opts}
- // Supported type: 'mypage', 'toplist'
- function createWenkuBlock(details) {
- // Args
- //title=TEXT_GUI_BLOCK_TITLE_DEFULT, append=false, options
- const title = details.title || TEXT_GUI_BLOCK_TITLE_DEFULT;
- const parent = ({'string': $(details.parent), 'object': details.parent})[typeof details.parent];
- const type = details.type ? details.type.toLowerCase() : null;
- const items = details.items;
- const options = details.options;
-
- // Standard block
- const stdBlock = makeStandardBlock();
- const block = stdBlock.block;
- const blocktitle = stdBlock.blocktitle;
- const blockcontent = stdBlock.blockcontent;
-
- blocktitle.innerHTML = title;
- makeContent();
- parent && parent.appendChild(block);
-
- return block;
-
- // Create a standard block structure
- function makeStandardBlock() {
- const block = $CrE('div'); block.classList.add('block');
- const blocktitle = $CrE('div'); blocktitle.classList.add('blocktitle');
- const blockcontent = $CrE('div'); blockcontent.classList.add('blockcontent');
- block.appendChild(blocktitle); block.appendChild(blockcontent);
- return {block: block, blocktitle: blocktitle, blockcontent: blockcontent};
- }
-
- function makeContent() {
- switch (type) {
- case 'mypage': typeMypage(); break;
- case 'toplist': typeToplist(); break;
- case 'imagelist': typeImglist(); break;
- case 'element': typeElement(); break;
- default: DoLog(LogLevel.Error, 'createWenkuBlock: Invalid block type');
- }
- }
-
- // Links such as https://www.wenku8.net/userdetail.php
- function typeMypage() {
- const ul = $CrE('ul');
- ul.classList.add('ulitem');
- for (const link of details.items) {
- const li = $CrE('li');
- const a = $CrE('a');
- a.href = link.href ? link.href : 'javascript: void(0);';
- link.href && (a.target = '_blank');
- link.tiptitle && settip(a, link.tiptitle);
- a.innerHTML = link.innerHTML;
- a.id = link.id ? link.id : '';
- li.appendChild(a);
- ul.appendChild(li);
- }
- blockcontent.appendChild(ul);
- }
-
- // Links such as top-books-list inside #right in index page
- // links = [...{href: '', innerHTML: '', tiptitle: '', id: ''}]
- function typeToplist() {
- const ul = $CrE('ul');
- ul.classList.add('ultop');
- for (const link of details.items) {
- const li = $CrE('li');
- const a = $CrE('a');
- a.href = link.href ? link.href : 'javascript: void(0);';
- link.href && (a.target = '_blank');
- link.tiptitle && settip(a, link.tiptitle);
- a.innerHTML = link.innerHTML;
- a.id = link.id ? link.id : '';
- li.appendChild(a);
- ul.appendChild(li);
- }
- blockcontent.appendChild(ul);
- }
-
- // Links with images like center blocks in index page
- function typeImglist() {
- const container = $CrE('div');
- container.style.height = '155px';
-
- for (const item of items) {
- const div = $CrE('div');
- div.setAttribute('style', 'float: left;text-align:center;width: 95px; height:155px;overflow:hidden;');
-
- const a = $CrE('a');
- a.href = item.href;
- a.target = '_blank';
- item.tiptitle && settip(a, item.tiptitle);
-
- const img = $CrE('img');
- img.src = item.src;
- setAttributes(img, {
- 'border': '0',
- 'width': '90',
- 'height': '127'
- });
- a.appendChild(img);
-
- const br = $CrE('br');
-
- const a2 = $CrE('a');
- a2.href = item.href;
- a2.target = '_blank';
- a2.innerHTML = item.text;
-
- div.appendChild(a);
- div.appendChild(br);
- div.appendChild(a2);
- container.appendChild(div);
- }
-
- blockcontent.appendChild(container);
- }
-
- // Just append given elements into block content
- function typeElement() {
- const elms = Array.isArray(items) ? items : [items];
- for (const elm of elms) {
- blockcontent.appendChild(elm);
- }
- }
-
- // Set attributes to an element
- function setAttributes(elm, attributes) {
- for (const [name, attr] of Object.entries(attributes)) {
- elm.setAttribute(name, attr);
- }
- }
- }
-
- // Get a review's last page url
- function getLatestReviewPageUrl(rid, callback, args=[]) {
- const reviewUrl = `https://${location.host}/modules/article/reviewshow.php?rid=` + String(rid);
- getDocument(reviewUrl, firstPage, args);
-
- function firstPage(oDoc, ...args) {
- const url = $(oDoc, '#pagelink>a.last').href;
- args = [url].concat(args);
- callback.apply(null, args);
- };
- };
-
- // Upload image to KIENG images
- // details: {file: File, onload: Function, onerror: Function, type: 'sm.ms/jd/sg/tt/...'}
- function uploadImage(details) {
- const file = details.file;
- const onload = details.onload ? details.onload : function() {};
- const onerror = details.onerror ? details.onerror : uploadError;
- const type = details.type ? details.type : CONFIG.UserGlobalCfg.getConfig().imager;
- if (!DATA_IMAGERS.hasOwnProperty(type) || !DATA_IMAGERS[type].available) {
- onerror();
- return false;
- }
- const imager = DATA_IMAGERS[type];
- const upload = imager.upload;
- const request = upload.request;
- const response = upload.response;
-
- // Construct request url
- let url = request.url;
- if (request.urlargs) {
- const args = request.urlargs;
- const makearg = (key, value) => ('{K}={V}'.replace('{K}', key).replace('{V}', value));
- const replacers = {
- '$filename$': () => (encodeURIComponent(file.name)),
- '$random$': () => (Math.random().toString()),
- '$time$': () => ((new Date()).getTime().toString())
- };
- for (let [key, value] of Object.entries(args)) {
- url += url.includes('?') ? '&' : '?';
- for (const [str, replacer] of Object.entries(replacers)) {
- while (value !== null && value.includes(str)) {
- const val = replacer(key);
- value = (val !== null) ? value.replace(str, val) : null;
- }
- }
- (value !== null) && (url += makearg(key, value));
- }
- }
-
- // Construst request body
- let data;
- if (request.data) {
- data = new FormData();
- const replacers = {
- '$file$': (key) => ((data.append(key, file), null)),
- '$random$': () => (Math.random().toString()),
- '$time$': () => ((new Date()).getTime().toString())
- };
-
- for (let [key, value] of Object.entries(request.data)) {
- for (const [str, replacer] of Object.entries(replacers)) {
- while (value !== null && value.includes(str)) {
- const val = replacer(key);
- value = (val !== null) ? value.replace(str, val) : null;
- }
- }
- (value !== null) && data.append(key, value);
- }
- } else {
- data = file;
- }
-
- // headers
- const headers = request.headers || {};
-
- GM_xmlhttpRequest({
- method: 'POST',
- url: url,
- timeout: 15 * 1000,
- data: data,
- headers: headers,
- responseType: request.responseType ? request.responseType : 'json',
- onerror: onerror,
- ontimeout: onerror,
- onabort: onerror,
- onload: (e) => {
- const json = e.response;
- const success = e.status === 200 && response.checksuccess(json);
- if (success) {
- const url = response.geturl(json);
- const name = response.getname ? (response.getname(json) ? response.getname(json) : TEXT_ALT_IMAGE_RESPONSE_NONAME) : TEXT_ALT_IMAGE_RESPONSE_NONAME
- onload({
- url: url,
- name: name,
- });
- } else {
- onerror(json);
- return;
- }
- }
- })
- /* Common xhr version. Cannot bypass CORS.
- const re = new XMLHttpRequest();
- re.open('POST', request.url, true);
- re.timeout = 15 * 1000;
- re.onerror = re.ontimeout = re.onabort = uploadError;
- re.responseType = request.responseType ? request.responseType : 'json';
- re.onload = (e) => {
- const json = re.response;
- const success = response.checksuccess(json)
- if (success) {
- onload({
- url: response.geturl(json),
- name: response.getname ? response.getname(json) : TEXT_ALT_IMAGE_RESPONSE_NONAME,
- });
- } else {
- uploadError(json);
- return;
- }
- }
- re.send(data);*/
-
- function uploadError(json) {
- alertify.error(TEXT_ALT_IMAGE_UPLOAD_ERROR);
- DoLog(LogLevel.Error, [TEXT_ALT_IMAGE_UPLOAD_ERROR, json]);
- }
- }
-
- // Wait until a variable loaded, and call callback
- function waitUntilLoaded(varnames, callback, args=[]) {
- if (!varnames) {callback.apply(null, args)}
- if (!Array.isArray(varnames)) {varnames = [varnames];}
-
- const AM = new AsyncManager();
- AM.onfinish = function() {
- callback.apply(null, args);
- };
- for (const varname of varnames) {
- AM.add();
- makeWaitFunc(varname, AM)();
- }
- AM.finishEvent = true;
-
- function makeWaitFunc(varname, AM) {
- return function wait() {
- if (typeof(getvar(varname)) === 'undefined') {
- setTimeout(wait, NUMBER_ELEMENT_LOADING_WAIT_INTERVAL);
- return false;
- }
- AM.finish();
- };
- }
- }
-
- // Remove all childnodes from an element
- function clearChildnodes(element) {
- const cns = []
- for (const cn of element.childNodes) {
- cns.push(cn);
- }
- for (const cn of cns) {
- element.removeChild(cn);
- }
- }
-
- // Change location.href without reloading using history.pushState/replaceState
- function setPageUrl(url, push=false) {
- return history[push ? 'pushState' : 'replaceState']({modified: true, ...history.state}, '', url);
- }
-
- // Just stopPropagation and preventDefault
- function destroyEvent(e) {
- if (!e) {return false;};
- if (!e instanceof Event) {return false;};
- e.stopPropagation();
- e.preventDefault();
- }
-
- // eval() function with security check that only allows to get variable values, but don't allow executing js.
- function getvar(varname) {
- const unsafe_chars = ['(', ')', '+', '-', '*', '/', '&', '|', '[', ']', '=', '^', '%', '!', '.', '<', '>', '\\', '"', '\''];
- for (const char of unsafe_chars) {
- if (varname.includes(char)) {throw new Error('Function getvar(varname) called with insecure string "{V}"'.replaceAll('V', varname.replaceAll('"', '\\"')))}
- }
-
- return eval(varname);
- }
-
- // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR
- // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting)
- // (If the request is invalid, such as url === '', will return false and will NOT make this request)
- // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event
- // Requires: function delItem(){...} & function uniqueIDMaker(){...}
- function GMXHRHook(maxXHR=5) {
- const GM_XHR = GM_xmlhttpRequest;
- const getID = uniqueIDMaker();
- let todoList = [], ongoingList = [];
- GM_xmlhttpRequest = safeGMxhr;
-
- function safeGMxhr() {
- // Get an id for this request, arrange a request object for it.
- const id = getID();
- const request = {id: id, args: arguments, aborter: null};
-
- // Deal onload function first
- dealEndingEvents(request);
-
- /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES!
- // Stop invalid requests
- if (!validCheck(request)) {
- return false;
- }
- */
-
- // Judge if we could start the request now or later?
- todoList.push(request);
- checkXHR();
- return makeAbortFunc(id);
-
- // Decrease activeXHRCount while GM_XHR onload;
- function dealEndingEvents(request) {
- const e = request.args[0];
-
- // onload event
- const oriOnload = e.onload;
- e.onload = function() {
- reqFinish(request.id);
- checkXHR();
- oriOnload ? oriOnload.apply(null, arguments) : function() {};
- }
-
- // onerror event
- const oriOnerror = e.onerror;
- e.onerror = function() {
- reqFinish(request.id);
- checkXHR();
- oriOnerror ? oriOnerror.apply(null, arguments) : function() {};
- }
-
- // ontimeout event
- const oriOntimeout = e.ontimeout;
- e.ontimeout = function() {
- reqFinish(request.id);
- checkXHR();
- oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {};
- }
-
- // onabort event
- const oriOnabort = e.onabort;
- e.onabort = function() {
- reqFinish(request.id);
- checkXHR();
- oriOnabort ? oriOnabort.apply(null, arguments) : function() {};
- }
- }
-
- // Check if the request is invalid
- function validCheck(request) {
- const e = request.args[0];
-
- if (!e.url) {
- return false;
- }
-
- return true;
- }
-
- // Call a XHR from todoList and push the request object to ongoingList if called
- function checkXHR() {
- if (ongoingList.length >= maxXHR) {return false;};
- if (todoList.length === 0) {return false;};
- const req = todoList.shift();
- const reqArgs = req.args;
- const aborter = GM_XHR.apply(null, reqArgs);
- req.aborter = aborter;
- ongoingList.push(req);
- return req;
- }
-
- // Make a function that aborts a certain request
- function makeAbortFunc(id) {
- return function() {
- let i;
-
- // Check if the request haven't been called
- for (i = 0; i < todoList.length; i++) {
- const req = todoList[i];
- if (req.id === id) {
- // found this request: haven't been called
- delItem(todoList, i);
- return true;
- }
- }
-
- // Check if the request is running now
- for (i = 0; i < ongoingList.length; i++) {
- const req = todoList[i];
- if (req.id === id) {
- // found this request: running now
- req.aborter();
- reqFinish(id);
- checkXHR();
- }
- }
-
- // Oh no, this request is already finished...
- return false;
- }
- }
-
- // Remove a certain request from ongoingList
- function reqFinish(id) {
- let i;
- for (i = 0; i < ongoingList.length; i++) {
- const req = ongoingList[i];
- if (req.id === id) {
- ongoingList = delItem(ongoingList, i);
- return true;
- }
- }
- return false;
- }
- }
- }
-
- // Redirect GM_storage API
- // Each key points to a different storage area
- // Original GM_functions will be backuped in window object
- // PS: No worry for GM_functions leaking, because Tempermonkey's Sandboxing
- function redirectGMStorage(key) {
- // Recover if redirected before
- GM_setValue = typeof(window.setValue) === 'function' ? window.setValue : GM_setValue;
- GM_getValue = typeof(window.getValue) === 'function' ? window.getValue : GM_getValue;
- GM_listValues = typeof(window.listValues) === 'function' ? window.listValues : GM_listValues;
- GM_deleteValue = typeof(window.deleteValue) === 'function' ? window.deleteValue : GM_deleteValue;
-
- // Stop if no key
- if (!key) {return;};
-
- // Save original GM_functions
- window.setValue = typeof(GM_setValue) === 'function' ? GM_setValue : function() {};
- window.getValue = typeof(GM_getValue) === 'function' ? GM_getValue : function() {};
- window.listValues = typeof(GM_listValues) === 'function' ? GM_listValues : function() {};
- window.deleteValue = typeof(GM_deleteValue) === 'function' ? GM_deleteValue : function() {};
-
- // Redirect GM_functions
- typeof(GM_setValue) === 'function' ? GM_setValue = RD_GM_setValue : function() {};
- typeof(GM_getValue) === 'function' ? GM_getValue = RD_GM_getValue : function() {};
- typeof(GM_listValues) === 'function' ? GM_listValues = RD_GM_listValues : function() {};
- typeof(GM_deleteValue) === 'function' ? GM_deleteValue = RD_GM_deleteValue : function() {};
-
- // Get global storage
- //const storage = getStorage();
-
- function getStorage() {
- return window.getValue(key, {});
- }
-
- function saveStorage(storage) {
- return window.setValue(key, storage);
- }
-
- function RD_GM_setValue(key, value) {
- const storage = getStorage();
- storage[key] = value;
- saveStorage(storage);
- }
-
- function RD_GM_getValue(key, defaultValue) {
- const storage = getStorage();
- return storage[key] || defaultValue;
- }
-
- function RD_GM_listValues() {
- const storage = getStorage();
- return Object.keys(storage);
- }
-
- function RD_GM_deleteValue(key) {
- const storage = getStorage();
- delete storage[key];
- saveStorage(storage);
- }
- }
-
- // Aim to separate big data from config, to boost up the speed of config reading.
- // FAILED. NEVER USE THESE CODES. NEVER DO THESE THINGS AGAIN. FUCK MYSELF ME STUPID.
- // NOOOOOOOO!!!!!!! WHY ARE YOU DICKHEAD STILL THINGKING ABOUT THIS SHIT??????? NEVER EVER THINK ABOUT THIS FUCKING UNACHIEVABLE FUNCTION AGAIN!!!!!!
- // See https://www.wenku8.net/modules/article/reviewshow.php?rid=244568&aid=1973&page=202#yid930393 if you still want to try, you'll pay for that.
- function GMBigData(maxsize=1024) {
- const BD = this;
- BD.maxsize = maxsize;
- BD.keyPrefix = 'GM_BIGDATA:' + btoa(encodeURIComponent(GM_info.script.name + (GM_info.script.namespace || '')));
-
- BD.hook = function() {
- hookget();
- hookset();
- }
-
- BD.unhook = function() {
- if (!BD.GM_getValue || !BD.GM_setValue) {
- throw TypeError('GMBigData: BD.GM_getValue or BD.GM_setValue missing');
- }
- GM_getValue = BD.GM_getValue;
- GM_setValue = BD.GM_setValue;
- }
-
- function hookget() {
- const oGet = BD.GM_getValue = GM_getValue;
- GM_getValue = function(name, defaultValue) {
- return decodeValue(oGet(name, defaultValue));
- }
-
- function decodeValue(value) {
- return (({
- 'string': decodeString,
- 'object': value !== null ? decodeObject : null
- })[typeof value] || ((v) => (v)))(value);
-
- function decodeString(str) {
- return (isDatakey(str) && keyExists(str)) ? localStorage.getItem(str) : str;
- }
-
- function decodeObject(obj) {
- return new Proxy(obj, {
- get: function(target, property, receiver) {
- return decodeValue(target[property]);
- }
- });
- }
- }
- }
-
- function hookset() {
- const oSet = BD.GM_setValue = GM_setValue;
- GM_setValue = function(name, value) {
- const encoded = encodeValue(value);
- clearUnusedBigData(encoded);
- return oSet(name, encoded);
- }
-
- function encodeValue(value) {
- return (({
- 'string': encodeString,
- 'object': value !== null ? encodeObject : value
- })[typeof value] || ((v) => (v)))(value);
-
- function encodeString(str) {
- if (getDataSize(str) <= BD.maxsize) {
- return str;
- } else {
- const key = generateKey();
- localStorage.setItem(key, str);
- return key;
- }
- }
-
- function encodeObject(obj) {
- return new Proxy(obj, {
- get: function(target, property, receiver) {
- return encodeValue(target[property]);
- }
- });
- }
- }
-
- function clearUnusedBigData(data) {
- const usingKeys = getAllUsingKeys(data);
- for (const key of Object.keys(localStorage)) {
- if (isDatakey(key) && !usingKeys.includes(key)) {
- localStorage.removeItem(key);
- }
- }
-
- function getAllUsingKeys(data) {
- const usingKeys = [];
- (({
- 'string': checkString,
- 'object': data !== null ? getAllUsingKeys : null
- })[typeof data] || function() {})();
- return usingKeys;
-
- function checkString(str) {
- isDatakey(str) && keyExists(str) && usingKeys.push(str);
- }
- }
- }
- }
-
- // Datakey generator
- function generateKey(length=16) {
- let datakey = newKey();
- while (keyExists(datakey)) {
- datakey = newKey();
- }
- return datakey;
-
- function newKey() {
- return BD.keyPrefix + ',' + randstr(length);
- }
- }
-
- // Check whether a datakey already exists
- function keyExists(datakey) {
- return Object.keys(localStorage).includes(datakey);
- }
-
- // Check whether the value is a datakey
- function isDatakey(value) {
- return typeof value === 'string' && value.startsWith(BD.keyPrefix);
- }
-
- // Get the size of data
- function getDataSize(data) {
- return (new Blob([data])).size;
- }
- }
-
- // Download and parse a url page into a html document(dom).
- // when xhr onload: callback.apply([dom, args])
- function getDocument(url, callback, args=[]) {
- GM_xmlhttpRequest({
- method : 'GET',
- url : url,
- responseType : 'blob',
- timeout : 15 * 1000,
- onloadstart : function() {
- DoLog(LogLevel.Info, 'getting document, url=\'' + url + '\'');
- },
- onload : function(response) {
- const htmlblob = response.response;
- parseDocument(htmlblob, callback, args);
- },
- onerror : reqerror,
- ontimeout : reqerror
- });
-
- function reqerror(e) {
- DoLog(LogLevel.Error, 'getDocument: Request Error');
- DoLog(LogLevel.Error, e);
- throw new Error('getDocument: Request Error')
- }
- }
-
- function parseDocument(htmlblob, callback, args=[]) {
- const reader = new FileReader();
- reader.onload = function(e) {
- const htmlText = reader.result;
- const dom = new DOMParser().parseFromString(htmlText, 'text/html');
- args = [dom].concat(args);
- callback.apply(null, args);
- //callback(dom, htmlText);
- }
- const charset = ['GBK', 'BIG5'][getLang()];
- reader.readAsText(htmlblob, charset);
- }
-
- // Get a base64-formatted url of an image
- // When image load error occurs, callback will be called without any argument
- function getImageUrl(src, fitx, fity, callback, args=[]) {
- const image = new Image();
- image.setAttribute("crossOrigin",'anonymous');
- image.onload = convert;
- image.onerror = image.onabort = callback;
- image.src = src;
-
- function convert() {
- const cvs = $CrE('canvas');
- const ctx = cvs.getContext('2d');
-
- let width, height;
- if (fitx && fity) {
- width = window.innerWidth;
- height = window.innerHeight;
- } else if (fitx) {
- width = window.innerWidth;
- height = (width / image.width) * image.height;
- } else if (fity) {
- height = window.innerHeight;
- width = (height / image.height) * image.width;
- } else {
- width = image.width;
- height = image.height;
- }
- cvs.width = width;
- cvs.height = height;
- ctx.drawImage(image, 0, 0, width, height);
- try {
- callback.apply(null, [cvs.toDataURL()].concat(args));
- } catch (e) {
- DoLog(LogLevel.Error, ['Error at getImageUrl.convert()', e]);
- callback();
- }
- }
- }
-
- // Convert a 'data:image/jpeg;base64,57af8b....' to a Blob object
- function b64toBlob(dataURI) {
- const mime = dataURI.match(/data:(.+?);/)[1];
- const byteString = atob(dataURI.split(',')[1]);
- const ab = new ArrayBuffer(byteString.length);
- const ia = new Uint8Array(ab);
-
- for (let i = 0; i < byteString.length; i++) {
- ia[i] = byteString.charCodeAt(i);
- }
- return new Blob([ab], {type: mime});
- }
-
- //将base64转换为文件
- function dataURLtoFile(dataurl, filename) {
- var arr = dataurl.split(','),
- mime = arr[0].match(/:(.*?);/)[1],
- bstr = atob(arr[1]),
- n = bstr.length,
- u8arr = new Uint8Array(n);
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n);
- }
- return new File([u8arr], filename, {
- type: mime
- });
- }
-
- // Save dataURL to file
- function saveFile(dataURL, filename) {
- const a = $CrE('a');
- a.href = dataURL;
- a.download = filename;
- a.click();
- }
-
- // File download function
- // details looks like the detail of GM_xmlhttpRequest
- // onload function will be called after file saved to disk
- function downloadFile(details) {
- if (!details.url || !details.name) {return false;};
-
- // Configure request object
- const requestObj = {
- url: details.url,
- responseType: 'blob',
- onload: function(e) {
- // Save file
- const url = URL.createObjectURL(e.response);
- saveFile(URL.createObjectURL(e.response), details.name);
- URL.revokeObjectURL(url);
-
- // onload callback
- details.onload ? details.onload(e) : function() {};
- }
- }
- if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
- if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
- if (details.onerror ) {requestObj.onerror = details.onerror;};
- if (details.onabort ) {requestObj.onabort = details.onabort;};
- if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
- if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
-
- // Send request
- GM_xmlhttpRequest(requestObj);
- }
-
- // Save text to textfile
- function downloadText(text, name) {
- if (!text || !name) {return false;};
-
- // Get blob url
- const blob = new Blob([text],{type:"text/plain;charset=utf-8"});
- const url = URL.createObjectURL(blob);
-
- // Create <a> and download
- const a = $CrE('a');
- a.href = url;
- a.download = name;
- a.click();
- }
-
- function requestText(url, callback, args=[]) {
- GM_xmlhttpRequest({
- method: 'GET',
- url: url,
- responseType: 'text',
- onload: function(response) {
- const text = response.responseText;
- const argvs = [text].concat(args);
- callback.apply(null, argvs);
- }
- })
- }
-
- // Get a url argument from lacation.href
- // also recieve a function to deal the matched string
- // returns defaultValue if name not found
- // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name'
- function getUrlArgv(details) {
- typeof(details) === 'string' && (details = {name: details});
- typeof(details) === 'undefined' && (details = {});
- if (!details.name) {return null;};
-
- const url = details.url ? details.url : location.href;
- const name = details.name ? details.name : '';
- const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;});
- const defaultValue = details.defaultValue ? details.defaultValue : null;
- const matcher = new RegExp('[\\?&]' + name + '=([^&#]+)');
- const result = url.match(matcher);
- const argv = result ? dealFunc(result[1]) : defaultValue;
-
- return argv;
- }
-
- // Get language: 0 for simplyfied chinese and others, 1 for traditional chinese
- function getLang() {
- const match = document.cookie.match(/(; *)?jieqiUserCharset=(.+?)( *;|$)/);
- const nvgtLang = ({'zh-CN': 0, 'zh-TW': 1})[navigator.language] || 0;
- return match && match[2] ? (match[2].toLowerCase() === 'big5' ? 1 : 0) : nvgtLang;
- }
-
- // Get a time text like 1970-01-01 00:00:00
- // if dateSpliter provided false, there will be no date part. The same for timeSpliter.
- function getTime(dateSpliter='-', timeSpliter=':') {
- const d = new Date();
- let fulltime = ''
- fulltime += dateSpliter ? fillNumber(d.getFullYear(), 4) + dateSpliter + fillNumber((d.getMonth() + 1), 2) + dateSpliter + fillNumber(d.getDate(), 2) : '';
- fulltime += dateSpliter && timeSpliter ? ' ' : '';
- fulltime += timeSpliter ? fillNumber(d.getHours(), 2) + timeSpliter + fillNumber(d.getMinutes(), 2) + timeSpliter + fillNumber(d.getSeconds(), 2) : '';
- return fulltime;
- }
-
- // Get key-value object from text like 'key: value'/'key:value'/' key : value '
- // returns: {key: value, KEY: key, VALUE: value}
- function getKeyValue(text, delimiters=[':', ':', ',', '︰']) {
- // Modify from https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error#examples
- // Create a new object, that prototypally inherits from the Error constructor.
- function SplitError(message) {
- this.name = 'SplitError';
- this.message = message || 'SplitError Message';
- this.stack = (new Error()).stack;
- }
- SplitError.prototype = Object.create(Error.prototype);
- SplitError.prototype.constructor = SplitError;
-
- if (!text) {return [];};
-
- const result = {};
- let key, value;
- for (let i = 0; i < text.length; i++) {
- const char = text.charAt(i);
- for (const delimiter of delimiters) {
- if (delimiter === char) {
- if (!key && !value) {
- key = text.substr(0, i).trim();
- value = text.substr(i+1).trim();
- result[key] = value;
- result.KEY = key;
- result.VALUE = value;
- } else {
- throw new SplitError('Mutiple Delimiter in Text');
- }
- }
- }
- }
-
- return result;
- }
-
- function htmlEncode(text) {
- const span = $CrE('div');
- span.innerText = text;
- return span.innerHTML;
- }
-
- // Convert rgb color(e.g. 51,51,153) to hex color(e.g. '333399')
- function rgbToHex(r, g, b) {return fillNumber(((r << 16) | (g << 8) | b).toString(16), 6);}
-
- // Fill number text to certain length with '0'
- function fillNumber(number, length) {
- let str = String(number);
- for (let i = str.length; i < length; i++) {
- str = '0' + str;
- }
- return str;
- }
-
- // Judge whether the str is a number
- function isNumeric(str, disableFloat=false) {
- const result = Number(str);
- return !isNaN(result) && str !== '' && (!disableFloat || result===Math.floor(result));
- }
-
- // Del a item from an array using its index. Returns the array but can NOT modify the original array directly!!
- function delItem(arr, delIndex) {
- arr = arr.slice(0, delIndex).concat(arr.slice(delIndex+1));
- return arr;
- }
-
- // Clone(deep) an object variable
- // Returns the new object
- function deepclone(obj) {
- if (obj === null) return null;
- if (typeof(obj) !== 'object') return obj;
- if (obj.constructor === Date) return new Date(obj);
- if (obj.constructor === RegExp) return new RegExp(obj);
- var newObj = new obj.constructor(); //保持继承的原型
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- const val = obj[key];
- newObj[key] = typeof val === 'object' ? deepclone(val) : val;
- }
- }
- return newObj;
- }
-
- // Makes a function that returns a unique ID number each time
- function uniqueIDMaker() {
- let id = 0;
- return makeID;
- function makeID() {
- id++;
- return id;
- }
- }
-
- // Returns a random string
- function randstr(length=16, cases=true, aviod=[]) {
- const all = 'abcdefghijklmnopqrstuvwxyz0123456789' + (cases ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' : '');
- while (true) {
- let str = '';
- for (let i = 0; i < length; i++) {
- str += all.charAt(randint(0, all.length-1));
- }
- if (!aviod.includes(str)) {return str;};
- }
- }
-
- function randint(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
-
- function AsyncManager() {
- const AM = this;
-
- // Ongoing xhr count
- this.taskCount = 0;
-
- // Whether generate finish events
- let finishEvent = false;
- Object.defineProperty(this, 'finishEvent', {
- configurable: true,
- enumerable: true,
- get: () => (finishEvent),
- set: (b) => {
- finishEvent = b;
- b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
- }
- });
-
- // Add one task
- this.add = () => (++AM.taskCount);
-
- // Finish one task
- this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
- }
-
- function loadinResourceCSS() {
- for (const res of NMonkey_Info.resources) {
- if (res.isCss) {
- const css = GM_getResourceText(res.name);
- css && addStyle(css);
- }
- }
- }
-
- function loadinFontAwesome() {
- // https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.1/css/all.min.css
- const url = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css';
- const alts = [
- 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.1/css/all.min.css',
- 'https://bowercdn.net/c/fontAwesome-6.1.1/css/all.min.css',
- ];
- let i = -1;
-
- const link = $CrE('link');
- link.href = url;
- link.rel = 'stylesheet';
- link.onerror = function() {
- i++;
- if (i < alts.length) {
- link.href = alts[i];
- } else {
- alertify.error(TEXT_ALT_SCRIPT_ERROR_AJAX_FA);
- }
- }
-
- document.head.appendChild(link);
- }
-
- // NMonkey By PY-DNG, 2021.07.18 - 2022.02.18, License GPL-3
- // NMonkey: Provides GM_Polyfills and make your userscript compatible with non-script-manager environment
- // Description:
- /*
- Simulates a script-manager environment("NMonkey Environment") for non-script-manager browser, load @require & @resource, provides some GM_functions(listed below), and also compatible with script-manager environment.
- Provides GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_openInTab, GM_setClipboard, GM_getResourceText, GM_getResourceURL, GM_addStyle, GM_addElement, GM_log, unsafeWindow(object), GM_info(object)
- Also provides an object called GM_POLYFILLED which has the following properties that shows you which GM_functions are actually polyfilled.
- Returns true if polyfilled is environment is ready, false for not. Don't worry, just follow the usage below.
- */
- // Note: DO NOT DEFINE GM-FUNCTION-NAMES IN YOUR CODE. DO NOT DEFINE GM_POLYFILLED AS WELL.
- // Note: NMonkey is an advanced version of GM_PolyFill (and BypassXB), it includes more functions than GM_PolyFill, and provides better stability and compatibility. Do NOT use NMonkey and GM_PolyFill (and BypassXB) together in one script.
- // Usage:
- /*
- // ==UserScript==
- // @name xxx
- // @namespace xxx
- // @version 1.0
- // ...
- // @require https://.../xxx.js
- // @require ...
- // ...
- // @resource https://.../xxx
- // @resource ...
- // ...
- // ==/UserScript==
-
- // Use a closure to wrap your code. Make sure you have it a name.
- (function YOUR_MAIN_FUNCTION() {
- 'use strict';
- // Strict mode is optional. You can use strict mode or not as you want.
- // Polyfill first. Do NOT do anything before Polyfill.
- var NMonkey_Ready = NMonkey({
- mainFunc: YOUR_MAIN_FUNCTION,
- name: "script-storage-key, aims to separate different scripts' storage area. Use your script's @namespace value if you don't how to fill this field.",
- requires: [
- {
- name: "", // Optional, used to display loading error messages if anything went wrong while loading this item
- src: "https://.../xxx.js",
- loaded: function() {return boolean_value_shows_whether_this_js_has_already_loaded;}
- execmode: "'eval' for eval code in current scope or 'function' for Function(code)() in global scope or 'script' for inserting a <script> element to document.head"
- },
- ...
- ],
- resources: [
- {
- src: "https://.../xxx"
- name: "@resource name. Will try to get it from @resource using this name before fetch it from src",
- },
- ...
- ],
- GM_info: {
- // You can get GM_info object, if you provide this argument(and there is no GM_info provided by the script-manager).
- // You can provide any object here, what you provide will be what you get.
- // Additionally, two property of NMonkey itself will be attached to GM_info if polyfilled:
- // {
- // scriptHandler: "NMonkey"
- // version: "NMonkey's version, it should look like '0.1'"
- // }
- // The following is just an example.
- script: {
- name: 'my first userscript for non-scriptmanager browsers!',
- description: 'this script works well both in my PC and my mobile!',
- version: '1.0',
- released: true,
- version_num: 1,
- authors: ['Johnson', 'Leecy', 'War Mars']
- update_history: {
- '0.9': 'First beta version',
- '1.0': 'Finally released!'
- }
- }
- surprise: 'if you check GM_info.surprise and you will read this!'
- // And property "scriptHandler" & "version" will be attached here
- }
- });
- if (!NMonkey_Ready) {
- // Stop executing of polyfilled environment not ready.
- // Don't worry, during polyfill progress YOUR_MAIN_FUNCTION will be called twice, and on the second call the polyfilled environment will be ready.
- return;
- }
-
- // Your code here...
- // Make sure your code is written after NMonkey be called
- if
- // ...
-
- // Just place NMonkey function code here
- function NMonkey(details) {
- ...
- }
- }) ();
-
- // Oh you want to write something here? Fine. But code you write here cannot get into the simulated script-manager-environment.
- */
- function NMonkey(details) {
- // Constances
- const CONST = {
- Text: {
- Require_Load_Failed: '动态加载依赖js库失败(自动重试也都失败了),请刷新页面后再试:(\n一共尝试了{I}个备用加载源\n加载项目:{N}',
- Resource_Load_Failed: '动态加载依赖resource资源失败(自动重试也都失败了),请刷新页面后再试:(\n一共尝试了{I}个备用加载源\n加载项目:{N}',
- UnkownItem: '未知项目',
- }
- };
-
- // Init DoLog
- DoLog();
-
- // Get argument
- const mainFunc = details.mainFunc;
- const name = details.name || 'default';
- const requires = details.requires || [];
- const resources = details.resources || [];
- details.GM_info = details.GM_info || {};
- details.GM_info.scriptHandler = 'NMonkey';
- details.GM_info.version = '1.0';
-
- // Run in variable-name-polifilled environment
- if (InNPEnvironment()) {
- // Already in polifilled environment === polyfill has alredy done, just return
- return true;
- }
-
- // Polyfill functions and data
- const GM_POLYFILL_KEY_STORAGE = 'GM_STORAGE_POLYFILL';
- let GM_POLYFILL_storage;
- const Supports = {
- GetStorage: function() {
- let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
- gstorage = gstorage ? JSON.parse(gstorage) : {};
- let storage = gstorage[name] ? gstorage[name] : {};
- return storage;
- },
-
- SaveStorage: function() {
- let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
- gstorage = gstorage ? JSON.parse(gstorage) : {};
- gstorage[name] = GM_POLYFILL_storage;
- localStorage.setItem(GM_POLYFILL_KEY_STORAGE, JSON.stringify(gstorage));
- },
- };
- const Provides = {
- // GM_setValue
- GM_setValue: function(name, value) {
- GM_POLYFILL_storage = Supports.GetStorage();
- name = String(name);
- GM_POLYFILL_storage[name] = value;
- Supports.SaveStorage();
- },
-
- // GM_getValue
- GM_getValue: function(name, defaultValue) {
- GM_POLYFILL_storage = Supports.GetStorage();
- name = String(name);
- if (GM_POLYFILL_storage.hasOwnProperty(name)) {
- return GM_POLYFILL_storage[name];
- } else {
- return defaultValue;
- }
- },
-
- // GM_deleteValue
- GM_deleteValue: function(name) {
- GM_POLYFILL_storage = Supports.GetStorage();
- name = String(name);
- if (GM_POLYFILL_storage.hasOwnProperty(name)) {
- delete GM_POLYFILL_storage[name];
- Supports.SaveStorage();
- }
- },
-
- // GM_listValues
- GM_listValues: function() {
- GM_POLYFILL_storage = Supports.GetStorage();
- return Object.keys(GM_POLYFILL_storage);
- },
-
- // unsafeWindow
- unsafeWindow: window,
-
- // GM_xmlhttpRequest
- // not supported properties of details: synchronous binary nocache revalidate context fetch
- // not supported properties of response(onload arguments[0]): finalUrl
- // ---!IMPORTANT!--- DOES NOT SUPPORT CROSS-ORIGIN REQUESTS!!!!! ---!IMPORTANT!---
- // details.synchronous is not supported as Tampermonkey
- GM_xmlhttpRequest: function(details) {
- const xhr = new XMLHttpRequest();
-
- // open request
- const openArgs = [details.method, details.url, true];
- if (details.user && details.password) {
- openArgs.push(details.user);
- openArgs.push(details.password);
- }
- xhr.open.apply(xhr, openArgs);
-
- // set headers
- if (details.headers) {
- for (const key of Object.keys(details.headers)) {
- xhr.setRequestHeader(key, details.headers[key]);
- }
- }
- details.cookie ? xhr.setRequestHeader('cookie', details.cookie) : function () {};
- details.anonymous ? xhr.setRequestHeader('cookie', '') : function () {};
-
- // properties
- xhr.timeout = details.timeout;
- xhr.responseType = details.responseType;
- details.overrideMimeType ? xhr.overrideMimeType(details.overrideMimeType) : function () {};
-
- // events
- xhr.onabort = details.onabort;
- xhr.onerror = details.onerror;
- xhr.onloadstart = details.onloadstart;
- xhr.onprogress = details.onprogress;
- xhr.onreadystatechange = details.onreadystatechange;
- xhr.ontimeout = details.ontimeout;
- xhr.onload = function (e) {
- const response = {
- readyState: xhr.readyState,
- status: xhr.status,
- statusText: xhr.statusText,
- responseHeaders: xhr.getAllResponseHeaders(),
- response: xhr.response
- };
- (details.responseType === '' || details.responseType === 'text') ? (response.responseText = xhr.responseText) : function () {};
- (details.responseType === '' || details.responseType === 'document') ? (response.responseXML = xhr.responseXML) : function () {};
- details.onload(response);
- }
-
- // send request
- details.data ? xhr.send(details.data) : xhr.send();
-
- return {
- abort: xhr.abort
- };
- },
-
- // NOTE: options(arg2) is NOT SUPPORTED! if provided, then will just be skipped.
- GM_openInTab: function(url) {
- window.open(url);
- },
-
- // NOTE: needs to be called in an event handler function, and info(arg2) is NOT SUPPORTED!
- GM_setClipboard: function(text) {
- // Create a new textarea for copying
- const newInput = document.createElement('textarea');
- document.body.appendChild(newInput);
- newInput.value = text;
- newInput.select();
- document.execCommand('copy');
- document.body.removeChild(newInput);
- },
-
- GM_getResourceText: function(name) {
- const _get = typeof(GM_getResourceText) === 'function' ? GM_getResourceText : () => (null);
- let text = _get(name);
- if (text) {return text;}
- for (const resource of resources) {
- if (resource.name === name) {
- return resource.content ? resource.content : null;
- }
- }
- return null;
- },
-
- GM_getResourceURL: function(name) {
- const _get = typeof(GM_getResourceURL) === 'function' ? GM_getResourceURL : () => (null);
- let url = _get(name);
- if (url) {return url;}
- for (const resource of resources) {
- if (resource.name === name) {
- return resource.src ? btoa(resource.src) : null;
- }
- }
- return null;
- },
-
- GM_addStyle: function(css) {
- const style = document.createElement('style');
- style.innerHTML = css;
- document.head.appendChild(style);
- },
-
- GM_addElement: function() {
- let parent_node, tag_name, attributes;
- const head_elements = ['title', 'base', 'link', 'style', 'meta', 'script', 'noscript'/*, 'template'*/];
- if (arguments.length === 2) {
- tag_name = arguments[0];
- attributes = arguments[1];
- parent_node = head_elements.includes(tag_name.toLowerCase()) ? document.head : document.body;
- } else if (arguments.length === 3) {
- parent_node = arguments[0];
- tag_name = arguments[1];
- attributes = arguments[2];
- }
- const element = document.createElement(tag_name);
- for (const [prop, value] of Object.entries(attributes)) {
- element[prop] = value;
- }
- parent_node.appendChild(element);
- },
-
- GM_log: function() {
- const args = [];
- for (let i = 0; i < arguments.length; i++) {
- args[i] = arguments[i];
- }
- console.log.apply(null, args);
- },
-
- GM_info: details.GM_info,
-
- GM: {info: details.GM_info}
- };
- const _GM_POLYFILLED = Provides.GM_POLYFILLED = {};
- for (const pname of Object.keys(Provides)) {
- _GM_POLYFILLED[pname] = true;
- }
-
- // Not in polifilled environment, then polyfill functions and create & move into the environment
- // Bypass xbrowser's useless GM_functions
- bypassXB();
-
- // Create & move into polifilled environment
- ExecInNPEnv();
-
- return false;
-
- // Bypass xbrowser's useless GM_functions
- function bypassXB() {
- if (typeof(mbrowser) === 'object' || (typeof(GM_info) === 'object' && GM_info.scriptHandler === 'XMonkey')) {
- // Useless functions in XMonkey 1.0
- const GM_funcs = [
- 'unsafeWindow',
- 'GM_getValue',
- 'GM_setValue',
- 'GM_listValues',
- 'GM_deleteValue',
- //'GM_xmlhttpRequest',
- ];
- for (const GM_func of GM_funcs) {
- window[GM_func] = undefined;
- eval('typeof({F}) === "function" && ({F} = Provides.{F});'.replaceAll('{F}', GM_func));
- }
- // Delete dirty data saved by these stupid functions before
- for (let i = 0; i < localStorage.length; i++) {
- const key = localStorage.key(i);
- const value = localStorage.getItem(key);
- value === '[object Object]' && localStorage.removeItem(key);
- }
- }
- }
-
- // Check if already in name-predefined environment
- // I think there won't be anyone else wants to use this fxxking variable name...
- function InNPEnvironment() {
- return (typeof(GM_POLYFILLED) === 'object' && GM_POLYFILLED !== null && GM_POLYFILLED !== window.GM_POLYFILLED) ? true : false;
- }
-
- function ExecInNPEnv() {
- const NG = new NameGenerator();
-
- // Init names
- const tnames = ['context', 'fapply', 'CDATA', 'uneval', 'define', 'module', 'exports', 'window', 'globalThis', 'console', 'cloneInto', 'exportFunction', 'createObjectIn', 'GM', 'GM_info'];
- const pnames = Object.keys(Provides);
- const fnames = tnames.slice();
- const argvlist = [];
- const argvs = [];
-
- // Add provides
- for (const pname of pnames) {
- !fnames.includes(pname) && fnames.push(pname);
- }
-
- // Add grants
- if (typeof(GM_info) === 'object' && GM_info.script && GM_info.script.grant) {
- for (const gname of GM_info.script.grant) {
- !fnames.includes(gname) && fnames.push(gname);
- }
- }
-
- // Make name code
- for (let i = 0; i < fnames.length; i++) {
- const fname = fnames[i];
- const exist = eval('typeof ' + fname + ' !== "undefined"') && fname !== 'GM_POLYFILLED';
- argvlist[i] = exist ? fname : (Provides.hasOwnProperty(fname) ? 'Provides.'+fname : '');
- argvs[i] = exist ? eval(fname) : (Provides.hasOwnProperty(fname) ? Provides[name] : undefined);
- pnames.includes(fname) && (_GM_POLYFILLED[fname] = !exist);
- }
-
- // Load all @require and @resource
- loadRequires(requires, resources, function(requires, resources) {
- // Join requirecode
- let requirecode = '';
- for (const require of requires) {
- const mode = require.execmode ? require.execmode : 'eval';
- const content = require.content;
- if (!content) {continue;}
- switch(mode) {
- case 'eval':
- requirecode += content + '\n';
- break;
- case 'function': {
- const func = Function.apply(null, fnames.concat(content));
- func.apply(null, argvs);
- break;
- }
- case 'script': {
- const s = document.createElement('script');
- s.innerHTML = content;
- document.head.appendChild(s);
- break;
- }
- }
- }
-
- // Make final code & eval
- const varnames = ['NG', 'tnames', 'pnames', 'fnames', 'argvist', 'argvs', 'code', 'finalcode', 'wrapper', 'ExecInNPEnv', 'GM_POLYFILL_KEY_STORAGE', 'GM_POLYFILL_storage', 'InNPEnvironment', 'NameGenerator', 'LocalCDN', 'loadRequires', 'requestText', 'Provides', 'Supports', 'bypassXB', 'details', 'mainFunc', 'name', 'requires', 'resources', '_GM_POLYFILLED', 'CONST', 'NMonkey', 'polyfill_status'];
- const code = requirecode + 'let ' + varnames.join(', ') + ';\n(' + mainFunc.toString() + ') ();';
- const wrapper = Function.apply(null, fnames.concat(code));
- const finalcode = '(' + wrapper.toString() + ').apply(this, [' + argvlist.join(', ') + ']);';
- eval(finalcode);
- });
-
- function NameGenerator() {
- const NG = this;
- const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
- let index = [0];
-
- NG.generate = function() {
- const chars = [];
- indexIncrease();
- for (let i = 0; i < index.length; i++) {
- chars[i] = letters.charAt(index[i]);
- }
- return chars.join('');
- }
-
- NG.randtext = function(len=32) {
- const chars = [];
- for (let i = 0; i < len; i++) {
- chars[i] = letters[randint(0, letter.length-1)];
- }
- return chars.join('');
- }
-
- function indexIncrease(i=0) {
- index[i] === undefined && (index[i] = -1);
- ++index[i] >= letters.length && (index[i] = 0, indexIncrease(i+1));
- }
-
- function randint(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
- }
- }
-
- // Load all @require and @resource for non-GM/TM environments (such as Alook javascript extension)
- // Requirements: function AsyncManager(){...}, function LocalCDN(){...}
- function loadRequires(requires, resoures, callback, args=[]) {
- // LocalCDN
- const LCDN = new LocalCDN();
-
- // AsyncManager
- const AM = new AsyncManager();
- AM.onfinish = function() {
- callback.apply(null, [requires, resoures].concat(args));
- }
-
- // Load js
- for (const js of requires) {
- !js.loaded() && loadinJs(js);
- }
-
- // Load resource
- for (const resource of resoures) {
- loadinResource(resource);
- }
-
- AM.finishEvent = true;
-
- function loadinJs(js) {
- AM.add();
-
- const srclist = js.srcset ? LCDN.sort(js.srcset).srclist : [];
- let i = -1;
- LCDN.get(js.src, onload, [], onfail);
-
- function onload(content) {
- js.content = content;
- AM.finish();
- }
-
- function onfail() {
- i++;
- if (i < srclist.length) {
- LCDN.get(srclist[i], onload, [], onfail);
- } else {
- alert(CONST.Text.Require_Load_Failed.replace('{I}', i.toString()).replace('{N}', js.name ? js.name : CONST.Text.UnkownItem));
- }
- }
- }
-
- function loadinResource(resource) {
- let content;
- if (typeof GM_getResourceText === 'function' && (content = GM_getResourceText(resource.name))) {
- resource.content = content;
- } else {
- AM.add();
-
- let i = -1;
- LCDN.get(resource.src, onload, [], onfail);
-
- function onload(content) {
- resource.content = content;
- AM.finish();
- }
-
- function onfail(content) {
- i++;
- if (resource.srcset && i < resource.srcset.length) {
- LCDN.get(resource.srcset[i], onload, [], onfail);
- } else {
- debugger;
- alert(CONST.Text.Resource_Load_Failed.replace('{I}', i.toString()).replace('{N}', js.name ? js.name : CONST.Text.UnkownItem));
- }
- }
- }
- }
- }
-
- // Loads web resources and saves them to GM-storage
- // Tries to load web resources from GM-storage in subsequent calls
- // Updates resources every $(this.expire) hours, or use $(this.refresh) function to update all resources instantly
- // Dependencies: GM_getValue(), GM_setValue(), requestText(), AsyncManager(), KEY_LOCALCDN
- function LocalCDN() {
- const LC = this;
- const _GM_getValue = typeof(GM_getValue) === 'function' ? GM_getValue : Provides.GM_getValue;
- const _GM_setValue = typeof(GM_setValue) === 'function' ? GM_setValue : Provides.GM_setValue;
-
- const KEY_LOCALCDN = 'LOCAL-CDN';
- const KEY_LOCALCDN_VERSION = 'version';
- const VALUE_LOCALCDN_VERSION = '0.3';
-
- // Default expire time (by hour)
- LC.expire = 72;
-
- // Try to get resource content from loaclCDN first, if failed/timeout, request from web && save to LocalCDN
- // Accepts callback only: onload & onfail(optional)
- // Returns true if got from LocalCDN, false if got from web
- LC.get = function(url, onload, args=[], onfail=function(){}) {
- const CDN = _GM_getValue(KEY_LOCALCDN, {});
- const resource = CDN[url];
- const time = (new Date()).getTime();
-
- if (resource && resource.content !== null && !expired(time, resource.time)) {
- onload.apply(null, [resource.content].concat(args));
- return true;
- } else {
- LC.request(url, _onload, [], onfail);
- return false;
- }
-
- function _onload(content) {
- onload.apply(null, [content].concat(args));
- }
- }
-
- // Generate resource obj and set to CDN[url]
- // Returns resource obj
- // Provide content means load success, provide null as content means load failed
- LC.set = function(url, content) {
- const CDN = _GM_getValue(KEY_LOCALCDN, {});
- const time = (new Date()).getTime();
- const resource = {
- url: url,
- time: time,
- content: content,
- success: content !== null ? (CDN[url] ? CDN[url].success + 1 : 1) : (CDN[url] ? CDN[url].success : 0),
- fail: content === null ? (CDN[url] ? CDN[url].fail + 1 : 1) : (CDN[url] ? CDN[url].fail : 0),
- };
- CDN[url] = resource;
- _GM_setValue(KEY_LOCALCDN, CDN);
- return resource;
- }
-
- // Delete one resource from LocalCDN
- LC.delete = function(url) {
- const CDN = _GM_getValue(KEY_LOCALCDN, {});
- if (!CDN[url]) {
- return false;
- } else {
- delete CDN[url];
- _GM_setValue(KEY_LOCALCDN, CDN);
- return true;
- }
- }
-
- // Delete all resources in LocalCDN
- LC.clear = function() {
- _GM_setValue(KEY_LOCALCDN, {});
- upgradeConfig();
- }
-
- // List all resource saved in LocalCDN
- LC.list = function() {
- const CDN = _GM_getValue(KEY_LOCALCDN, {});
- const urls = LC.listurls();
- return LC.listurls().map((url) => (CDN[url]));
- }
-
- // List all resource's url saved in LocalCDN
- LC.listurls = function() {
- return Object.keys(_GM_getValue(KEY_LOCALCDN, {})).filter((url) => (url !== KEY_LOCALCDN_VERSION));
- }
-
- // Request content from web and save it to CDN[url]
- // Accepts callbacks only: onload & onfail(optional)
- LC.request = function(url, onload, args=[], onfail=function(){}) {
- const CDN = _GM_getValue(KEY_LOCALCDN, {});
- requestText(url, _onload, [], _onfail);
-
- function _onload(content) {
- LC.set(url, content);
- onload.apply(null, [content].concat(args));
- }
-
- function _onfail() {
- LC.set(url, null);
- onfail();
- }
- }
-
- // Re-request all resources in CDN instantly, ignoring LC.expire
- LC.refresh = function(callback, args=[]) {
- const urls = LC.listurls();
-
- const AM = new AsyncManager();
- AM.onfinish = function() {
- callback.apply(null, [].concat(args))
- };
-
- for (const url of urls) {
- AM.add();
- LC.request(url, function() {
- AM.finish();
- });
- }
-
- AM.finishEvent = true;
- }
-
- // Sort src && srcset, to get a best request sorting
- LC.sort = function(srcset) {
- const CDN = _GM_getValue(KEY_LOCALCDN, {});
- const result = {srclist: [], lists: []};
- const lists = result.lists;
- const srclist = result.srclist;
- const suc_rec = lists[0] = []; // Recent successes take second (not expired yet)
- const suc_old = lists[1] = []; // Old successes take third
- const fails = lists[2] = []; // Fails & unused take the last place
- const time = (new Date()).getTime();
-
- // Make lists
- for (const s of srcset) {
- const resource = CDN[s];
- if (resource && resource.content !== null) {
- if (!expired(resource.time, time)) {
- suc_rec.push(s);
- } else {
- suc_old.push(s);
- }
- } else {
- fails.push(s);
- }
- }
-
- // Sort lists
- // Recently successed: Choose most recent ones
- suc_rec.sort((res1, res2) => (res2.time - res1.time));
- // Successed long ago or failed: Sort by success rate & tried time
- [suc_old, fails].forEach((arr) => (arr.sort(sorting)));
-
- // Push all resources into seclist
- [suc_rec, suc_old, fails].forEach((arr) => (arr.forEach((res) => (srclist.push(res)))));
-
- DoLog(['LocalCDN: sorted', result]);
- return result;
-
- function sorting(res1, res2) {
- const sucRate1 = (res1.success+1) / (res1.fail+1);
- const sucRate2 = (res2.success+1) / (res2.fail+1);
-
- if (sucRate1 !== sucRate2) {
- // Success rate: high to low
- return sucRate2 - sucRate1;
- } else {
- // Tried time: less to more
- // Less tried time means newer added source
- return (res1.success+res1.fail) - (res2.success+res2.fail);
- }
- }
- }
-
- function upgradeConfig() {
- const CDN = _GM_getValue(KEY_LOCALCDN, {});
- switch(CDN[KEY_LOCALCDN_VERSION]) {
- case undefined:
- init();
- break;
- case '0.1':
- v01_To_v02();
- logUpgrade();
- break;
- case '0.2':
- v01_To_v02();
- v02_To_v03();
- logUpgrade();
- break;
- case VALUE_LOCALCDN_VERSION:
- DoLog('LocalCDN is in latest version.');
- break;
- default:
- DoLog(LogLevel.Error, 'LocalCDN.upgradeConfig: Invalid config version({V}) for LocalCDN. '.replace('{V}', CDN[KEY_LOCALCDN_VERSION]));
- }
- CDN[KEY_LOCALCDN_VERSION] = VALUE_LOCALCDN_VERSION;
- _GM_setValue(KEY_LOCALCDN, CDN);
-
- function logUpgrade() {
- DoLog(LogLevel.Success, 'LocalCDN successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', CDN[KEY_LOCALCDN_VERSION]).replaceAll('{V2}', VALUE_LOCALCDN_VERSION));
- }
-
- function init() {
- // Nothing to do here
- }
-
- function v01_To_v02() {
- const urls = LC.listurls();
- for (const url of urls) {
- if (url === KEY_LOCALCDN_VERSION) {continue;}
- CDN[url] = {
- url: url,
- time: 0,
- content: CDN[url]
- };
- }
- }
-
- function v02_To_v03() {
- const urls = LC.listurls();
- for (const url of urls) {
- CDN[url].success = CDN[url].fail = 0;
- }
- }
- }
-
- function clearExpired() {
- const resources = LC.list();
- const time = (new Date()).getTime();
-
- for (const resource of resources) {
- expired(resource.time, time) && LC.delete(resource.url);
- }
- }
-
- function expired(t1, t2) {
- return (t1 - t2) > (LC.expire * 60 * 60 * 1000);
- }
-
- upgradeConfig();
- clearExpired();
- }
-
- function requestText(url, callback, args=[], onfail=function(){}) {
- const req = typeof(GM_xmlhttpRequest) === 'function' ? GM_xmlhttpRequest : Provides.GM_xmlhttpRequest;
- req({
- method: 'GET',
- url: url,
- responseType: 'text',
- timeout: 45*1000,
- onload: function(response) {
- const text = response.responseText;
- const argvs = [text].concat(args);
- callback.apply(null, argvs);
- },
- onerror: onfail,
- ontimeout: onfail,
- onabort: onfail,
- })
- }
-
- function AsyncManager() {
- const AM = this;
-
- // Ongoing xhr count
- this.taskCount = 0;
-
- // Whether generate finish events
- let finishEvent = false;
- Object.defineProperty(this, 'finishEvent', {
- configurable: true,
- enumerable: true,
- get: () => (finishEvent),
- set: (b) => {
- finishEvent = b;
- b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
- }
- });
-
- // Add one task
- this.add = () => (++AM.taskCount);
-
- // Finish one task
- this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
- }
-
- // Arguments: level=LogLevel.Info, logContent, asObject=false
- // Needs one call "DoLog();" to get it initialized before using it!
- function DoLog() {
- const win = typeof(unsafeWindow) !== 'undefined' ? unsafeWindow : window;
-
- // Global log levels set
- win.LogLevel = {
- None: 0,
- Error: 1,
- Success: 2,
- Warning: 3,
- Info: 4,
- }
- win.LogLevelMap = {};
- win.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
- win.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
- win.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
- win.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
- win.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
- win.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
-
- // Current log level
- DoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
-
- // Log counter
- DoLog.logCount === undefined && (DoLog.logCount = 0);
- if (++DoLog.logCount > 512) {
- console.clear();
- DoLog.logCount = 0;
- }
-
- // Get args
- let level, logContent, asObject;
- switch (arguments.length) {
- case 1:
- level = LogLevel.Info;
- logContent = arguments[0];
- asObject = false;
- break;
- case 2:
- level = arguments[0];
- logContent = arguments[1];
- asObject = false;
- break;
- case 3:
- level = arguments[0];
- logContent = arguments[1];
- asObject = arguments[2];
- break;
- default:
- level = LogLevel.Info;
- logContent = 'DoLog initialized.';
- asObject = false;
- break;
- }
-
- // Log when log level permits
- if (level <= DoLog.logLevel) {
- let msg = '%c' + LogLevelMap[level].prefix;
- let subst = LogLevelMap[level].color;
-
- if (asObject) {
- msg += ' %o';
- } else {
- switch(typeof(logContent)) {
- case 'string': msg += ' %s'; break;
- case 'number': msg += ' %d'; break;
- case 'object': msg += ' %o'; break;
- }
- }
-
- console.log(msg, subst, logContent);
- }
- }
- }
-
- // Polyfill alert
- function polyfillAlert() {
- if (typeof(GM_POLYFILLED) !== 'object') {return false;}
- if (GM_POLYFILLED.GM_setValue) {
- alertify.notify(TEXT_ALT_POLYFILL);
- }
- }
-
- // Polyfill String.prototype.replaceAll
- // replaceValue does NOT support regexp match groups($1, $2, etc.)
- function polyfill_replaceAll() {
- String.prototype.replaceAll = String.prototype.replaceAll ? String.prototype.replaceAll : PF_replaceAll;
-
- function PF_replaceAll(searchValue, replaceValue) {
- const str = String(this);
-
- if (searchValue instanceof RegExp) {
- const global = RegExp(searchValue, 'g');
- if (/\$/.test(replaceValue)) {console.error('Error: Polyfilled String.protopype.replaceAll does support regexp groups');};
- return str.replace(global, replaceValue);
- } else {
- return str.split(searchValue).join(replaceValue);
- }
- }
- }
-
- // Append a style text to document(<head>) with a <style> element
- function addStyle(css, id) {
- const style = $CrE("style");
- id && (style.id = id);
- style.textContent = css;
- for (const elm of $All('#'+id)) {
- elm.parentElement && elm.parentElement.removeChild(elm);
- }
- document.head.appendChild(style);
- }
-
- // Copy text to clipboard (needs to be called in an user event)
- function copyText(text) {
- // Create a new textarea for copying
- const newInput = $CrE('textarea');
- document.body.appendChild(newInput);
- newInput.value = text;
- newInput.select();
- document.execCommand('copy');
- document.body.removeChild(newInput);
- }
- })();