/* eslint-disable no-multi-spaces */ /* eslint-disable no-useless-call */ // ==UserScript== // @name 轻小说文库+ // @namespace Wenku8+ // @version 1.6.2.3 // @description TXT分卷批量下载,版权限制小说TXT简繁全本下载,书名/作者名双击复制,Ctrl+Enter快捷键发表书评,单章节下载,小说JPEG插图下载,下载线路点击切换,书评直接打开最后一页,书评帖子全贴下载保存,书评帖子回复功能增强,书评帖子自动刷新,书评帖子收藏,书架功能增强,修复文库若干自身bug,轻小说标签搜索,用户书评搜索,简单的阅读页和目录页美化,每日自动推书,帐号快速切换 // @author PY-DNG // @license GPL // @match http*://www.wenku8.net/* // @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 // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_openInTab // @grant GM_info // @grant unsafeWindow // @require https://greasyfork.org/scripts/429557-gmrequirechecker/code/GMRequireChecker.js?version=951692 // @require https://cdn.jsdelivr.net/gh/PYUDNG/CDN@eed1fcf0e901348bc4e752fd483bcb571ebe0408/js/GBK_URL/GBK.js // @require https://cdn.jsdelivr.net/gh/PYUDNG/CDN@058b97a4c86980fa3de3d9ee9bc9f2f787e11c84/js/gui/elegant%20alert.js // @require https://cdn.jsdelivr.net/gh/PYUDNG/CDN@94fc2bdd313f7bf2af6db5b8699effee8dd0b18d/js/ajax/GreasyForkScriptUpdate.js // @noframes // @downloadURL none // ==/UserScript== /* 需求记录 [容易(优先级高) ➡️ 困难(优先级低)] ** [已完成]{BK}书评页提供用户书评搜索 ** {BK}图片大小(最大)限制 ** [已完成]{BK}回复区插入@好友 ** {jack158}[部分完成]全卷/分卷下载:文件重命名为书名,而不是书号 ** · [已完成分卷&Book页]添加单文件下载重命名 ** {BK}回复区悬浮显示 ** {热忱}[已完成]修复https引用问题 ** [已完成]书评打开最后一页 ** [待完善]书评实时更新 ** · [待完善]新回复直接添加到当前页面 ** · 主动回复内容直接添加到当前页面 ** [已完成]引用回复 ** [已完成]支持preview版tag搜索 ** [待完善]书评帖子收藏 ** [已完成]每日自动推书 ** [待完善]{热忱}快速切换账号 ** · [已完成]为每个账号储存单独的配置 ** · [待完善]保存账号信息并快速自动切换 ** [待完善]快速插入图片/表情 ** · [待完善]直接插入本地图片 ** · 更多图床 ** · 保存常用图片/表情链接 ** {BK}类似ehunter的阅读模式 ** 改进旧代码: ** · 每个page-addon内部要按照功能分模块,执行功能靠调用模块,不能直接写功能代码 ** · 共性模块要写进脚本全局作用域,可以的话写成构造函数 ** {热忱}书评:@某人时通知他 ** {BK}[部分完成]页面美化 ** · [已完成]阅读页去除广告 ** · [待完善]阅读页美化 ** · [待完善]书评页美化 ** · … ** {热忱}提供带文字和插图的epub整合下载 ** {BK}[待完善]书评:草稿箱功能 */ (function() { 'use strict'; // Polyfills polyfill_replaceAll(); bypassXB(); const GM_POLYFILLED = GM_PolyFill('wenku8_plus'); polyfill_GM_info('1.6.2.3'); // CONSTS const NUMBER_MAX_XHR = 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_LOCALCDN = 'LOCAL-CDN'; const KEY_LOCALCDN_VERSION = 'version'; const VALUE_LOCALCDN_VERSION = '0.1'; 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.8'; const KEY_BOOKCASES = 'book-cases'; const KEY_BOOKCASE_VERSION = 'version'; const VALUE_BOOKCASE_VERSION = '0.3'; 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.5'; const KEY_USERGLOBAL = 'user-global-config'; const KEY_USERGLOBAL_VERSION = 'version'; const VALUE_USERGLOBAL_VERSION = '0.1'; const VALUE_STR_NULL = 'null'; const URL_REVIEWSEARCH = 'https://www.wenku8.net/modules/article/reviewslist.php?keyword={K}'; const URL_REVIEWSHOW = 'https://www.wenku8.net/modules/article/reviewshow.php?rid={R}&aid={A}&page={P}'; const URL_REVIEWSHOW_1 = 'https://www.wenku8.net/modules/article/reviewshow.php?rid={R}'; const URL_REVIEWSHOW_2 = 'https://www.wenku8.net/modules/article/reviewshow.php?rid={R}&page={P}'; const URL_REVIEWSHOW_3 = 'https://www.wenku8.net/modules/article/reviewshow.php?rid={R}&aid={A}'; const URL_USERINFO = 'https://www.wenku8.net/userinfo.php?id={K}'; const URL_DOWNLOAD1 = 'http://dl.wenku8.com/packtxt.php?aid={A}&vid={V}&charset={C}'; const URL_DOWNLOAD2 = 'http://dl2.wenku8.com/packtxt.php?aid={A}&vid={V}&charset={C}'; const URL_DOWNLOAD3 = 'http://dl3.wenku8.com/packtxt.php?aid={A}&vid={V}&charset={C}'; const URL_RECOMMEND = 'https://www.wenku8.net/modules/article/uservote.php?id={B}'; const URL_TAGSEARCH = 'https://www.wenku8.net/modules/article/tags.php?t={TU}'; const URL_USRDETAIL = 'https://www.wenku8.net/userdetail.php'; const URL_USRFRIEND = 'https://www.wenku8.net/myfriends.php'; const URL_BOOKCASE = 'https://www.wenku8.net/modules/article/bookcase.php'; const URL_USRLOGIN = 'https://www.wenku8.net/login.php?do=submit&jumpurl=http%3A%2F%2Fwww.wenku8.net%2Findex.php'; const URL_USRLOGOFF = 'https://www.wenku8.net/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 = { LIUMINGYE: { available: true, name: '刘明野-全能图床', tip: '2021-12-04测试可用', upload: { request: { url: 'https://tool.liumingye.cn/tuchuang/update.php', name: { file: 'file' } }, response: { checksuccess: (json)=>{return json.code === 0;}, geturl: (json)=>{return json.msg;} } } }, SDAIDEV: { available: true, name: '流浪图床', tip: '2022-01-09测试可用', 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;} } } }, KIENG_JD: { available: false, name: 'KIENG-JD', tip: '默认图床
个人体验良好,推荐使用

已失效', upload: { request: { url: 'https://image.kieng.cn/upload.html?type=jd', name: { file: 'image' } }, response: { checksuccess: (json)=>{return json.code === 200;}, geturl: (json)=>{return json.data.url;}, getname: (json)=>{return json.data.name;} } } }, KIENG_SG: { available: false, name: 'KIENG-SG', upload: { request: { url: 'https://image.kieng.cn/upload.html?type=sg', name: { file: 'image' } }, response: { checksuccess: (json)=>{return json.code === 200;}, geturl: (json)=>{return json.data.url;}, getname: (json)=>{return json.data.name;} } } }, KIENG_58: { available: false, name: 'KIENG-58', upload: { request: { url: 'https://image.kieng.cn/upload.html?type=c58', name: { file: 'image' } }, response: { checksuccess: (json)=>{return json.code === 200;}, geturl: (json)=>{return json.data.url;}, getname: (json)=>{return json.data.name;} } } }, KIENG_WY: { available: false, name: 'KIENG-WY', upload: { request: { url: 'https://image.kieng.cn/upload.html?type=wy', name: { file: 'image' } }, response: { checksuccess: (json)=>{return json.code === 200;}, geturl: (json)=>{return json.data.url;}, getname: (json)=>{return json.data.name;} } } }, KIENG_QQ: { available: false, name: 'KIENG-QQ', upload: { request: { url: 'https://image.kieng.cn/upload.html?type=qq', name: { file: 'image' } }, response: { checksuccess: (json)=>{return json.code === 200;}, geturl: (json)=>{return json.data.url;}, getname: (json)=>{return json.data.name;} } } }, KIENG_SN: { available: false, name: 'KIENG-SN', upload: { request: { url: 'https://image.kieng.cn/upload.html?type=sn', name: { file: 'image' } }, response: { checksuccess: (json)=>{return json.code === 200;}, geturl: (json)=>{return json.data.url;}, getname: (json)=>{return json.data.name;} } } }, KIENG_HL: { available: false, name: 'KIENG-HLX', upload: { request: { url: 'https://image.kieng.cn/upload.html?type=hl', name: { file: 'image' } }, response: { checksuccess: (json)=>{return json.code === 200;}, geturl: (json)=>{return json.data.url;}, getname: (json)=>{return json.data.name;} } } }, SMMS: { available: true, name: 'SM.MS', tip: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床', warning: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床
如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床', upload: { request: { url: 'https://sm.ms/api/v2/upload?inajax=1', name: { file: 'smfile' } }, 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;} } } }, CATBOX: { available: true, name: 'CatBox', tip: '注意:此图床访问较不稳定,请谨慎使用此图床', warning: '注意:此图床访问较不稳定,请谨慎使用此图床
如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床', upload: { request: { url: 'https://catbox.moe/user/api.php', responseType: 'text', name: { file: 'fileToUpload', reqtype: 'fileupload' } }, response: { checksuccess: (text)=>{return true;}, geturl: (text)=>{return text;} } } } }; 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 = '[复制]'.replace('{C}', CLASSNAME_BUTTON); const HTML_BOOK_META = '{K}:{V}[复制]'.replace('{C}', CLASSNAME_BUTTON); const HTML_BOOK_TAG = '{TN}'.replace('{C}', CLASSNAME_BUTTON).replace('{U}', URL_TAGSEARCH); const HTML_DOWNLOAD_CONTENER = '
\n
\n《{BOOKNAME}》小说TXT简繁全本下载\n
\n
'; const HTML_DOWNLOAD_LINKS = '
《{ORIBOOKNAME}》小说TXT全本下载
G版原始下载
G版自动重命名
U版原始下载
U版自动重命名
繁体原始下载
繁体自动重命名
'.replaceAll('{C}', CLASSNAME_BUTTON); const HTML_DOWNLOAD_BOARD = '[轻小说文库+] 为您提供《{ORIBOOKNAME}》的TXT简繁全本下载!
由此产生的一切法律及其他问题均由脚本用户承担
—— PY-DNG
'.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;} .{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;} .{CLI} {display: block; list-style: outside none none; margin: 0px; width: 100%; border: 1px solid rgb(204, 204, 204);} .{CLB} {border: 0px; width: 100%; height: 100%; cursor: pointer;}'.replaceAll('{CAT}', CLASSNAME_LIST).replaceAll('{CLI}', CLASSNAME_LIST_ITEM).replaceAll('{CLB}', CLASSNAME_LIST_BUTTON); const CSS_REVIEWSHOW = `body { overflow: auto; background-image: url({BGI}); } #content table, table.grid td { background-color: rgba(255,255,255,0.7) !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%"], .jieqiQuote, .jieqiCode, .jieqiNote{ font-size: calc(1em * {S} / 100); line-height: calc(120% * {S} / 100); } .{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: overlay; 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; } /* #content { font-size: calc(1em * {S} / 100) !important; } #title { font-size: calc(1em * {S} / 100); }*/`; 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_SEARCH_OPTION_TAG = '有关标签搜索

未完善-开发中…
官方尚未正式开放此功能
功能预览由[轻小说文库+]提供'; const TEXT_TIP_REVIEW_BEAUTIFUL = '背景图片可以在"用户面板"中设置
您可以从文库首页左侧点击进入用户面板'; const TEXT_TIP_REVIEW_IMG_INSERTURL = '直接插入网络图片的链接地址'; const TEXT_TIP_REVIEW_IMG_SELECTIMG = '选择本地图片上传到第三方图床,然后再插入图床提供的图片链接
您也可以直接拖拽图片到输入框,或者Ctrl+V直接粘贴您剪贴板里面的图片
您可以在用户面板中切换图床

上传图片请遵守法律以及图床使用规定
请不要上传违规图片'; const TEXT_TIP_IMAGE_FIT = '请选择适合您的屏幕宽高比的图片
您选择的图片将会被拉伸以适应屏幕的宽高比,图片宽高比与屏幕宽高比相差过大会导致图片扭曲
请避免选择文件大小过大的图片,以防止浏览器卡顿'; const TEXT_TIP_DOWNLOAD_BBCODE = 'BBCODE格式:
即文库评论的代码格式
相当于引用楼层时自动填入回复框的内容
保存为此格式可以保留排版及多媒体信息'; const TEXT_TIP_ACCOUNT_NOACCOUNT = '没有储存的账号信息
请在登录页面手动登录一次,相关帐号信息就会自动储存

所有储存的账号信息都自动保存在浏览器的本地存储中'; const TEXT_ALT_DOWNLOAD_BBCODE_NOCHANGE = '帖子正在下载中,请不要更改此设置!'; const TEXT_ALT_DOWNLOADFINISH_REVIEW = '{T}({I}) 已下载完毕
{N} 已保存'; const TEXT_ALT_AUTOREFRESH_ON = '页面自动刷新已开启'; const TEXT_ALT_AUTOREFRESH_OFF = '页面自动刷新已关闭'; const TEXT_ALT_AUTOREFRESH_NOTLAST = '请先翻到最后一页再开启页面自动刷新
[点击这里翻到最后一页]'.replaceAll('{CB}', CLASSNAME_BUTTON); const TEXT_ALT_AUTOREFRESH_WORKING = '正在获取新的回复...'; const TEXT_ALT_AUTOREFRESH_NOMORE = '木有新的回复'; const TEXT_ALT_AUTOREFRESH_APPLIED = '发现了新的回复,页面已更新~
'.replaceAll('{CB}', CLASSNAME_BUTTON); const TEXT_ALT_AUTOREFRESH_MODIFIED = '发现已有楼层内容变更,已对其进行了颜色标记
点击标记区域即可恢复原来的颜色'; const TEXT_ALT_BEAUTIFUL_ON = '页面美化已开启'; const TEXT_ALT_BEAUTIFUL_OFF = '页面美化已关闭'; const TEXT_ALT_FAVORITE_LAST_ON = '将在点击收藏的帖子时打开最后一页'; const TEXT_ALT_FAVORITE_LAST_OFF = '将在点击收藏的帖子时打开第一页'; const TEXT_ALT_IMAGE_FORMATERROR = '很遗憾,您选择的图片格式无法识别
(建议选择jpeg,png)!'; const TEXT_ALT_IMAGE_UPLOAD_WORKING = '正在上传图片…'; const TEXT_ALT_IMAGE_DOWNLOAD_WORKING = '正在下载图片…'; const TEXT_ALT_IMAGE_UPLOAD_SUCCESS = '图片上传成功!
文件名: {NAME}
URL: {URL}'; const TEXT_ALT_IMAGE_DOWNLOAD_SUCCESS = '图片下载成功!
已经将背景图片 {NAME} 保存在本地'; const TEXT_ALT_IMAGE_RESPONSE_NONAME = '空(服务器没有返回文件名)'; const TEXT_ALT_IMAGE_UPLOAD_ERROR = '上传错误!'; const TEXT_ALT_TEXTSCALE_CHANGED = '字体缩放已保存:{S}%'; const TEXT_ALT_CONFIG_EXPORTED = '配置文件已导出
文件名:{N}'; const TEXT_ALT_CONFIG_IMPORTED = '配置文件已导入'; const TEXT_ALT_META_COPIED = '{M} 已复制'; const TEXT_ALT_ATRCMMDS_SAVED = '已保存:《{B}》
每日自动推荐{N}次
每日还可推荐{R}次'; const TEXT_ALT_ATRCMMDS_INVALID = '未保存:{N}不是非负整数'; const TEXT_ALT_ATRCMMDS_OVERFLOW = '注意:
您的用户信息显示您每天最多推荐{V}票
当前您已设置每日推荐合计{C}票
[单击此处以立即更新您的用户信息]'.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 = '正在推荐书目:
{BN}({BID})'; const TEXT_ALT_ATRCMMDS_DONE = '推荐完成:
{BN}({BID})'; const TEXT_ALT_ATRCMMDS_ALL_DONE = '全部书目推荐完成:
{R}'; const TEXT_ALT_ATRCMMDS_NOTASK = '木有要推荐的书目╮( ̄▽ ̄)╭'; const TEXT_ALT_ATRCMMDS_NOTASK_OPENBC = '您还没有设置每日自动推荐的书目╮( ̄▽ ̄)╭
[点击此处打开书架页面进行设置]'.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}:用户信息已更新
[点此查看详细信息]'.replaceAll('{S}', GM_info.script.name).replaceAll('{CB}', CLASSNAME_BUTTON); const TEXT_ALT_POLYFILL = '提示:正在使用移动端适配模式'.replaceAll('{CT}', CLASSNAME_TEXT); const TEXT_ALT_LASTPAGE_LOADING = '正在获取最后一页,请稍候...'; const TEXT_ALT_ACCOUNT_SWITCHED = '帐号已切换到 "{N}"
3s后自动刷新页面
点击这里取消刷新'.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 = '
{SN} 有新版本啦!
新版本:{NV}
当前版本:{CV}
[点击此处安装更新]
'.replaceAll('{CT}', CLASSNAME_TEXT).replaceAll('{CB}', CLASSNAME_BUTTON); 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_GUI_REQUIRE_FAILED = '动态加载依赖js库失败(自动重试也都失败了),请刷新页面后再试:('; const TEXT_GUI_API_ADDBOOKCASE_TOBOOKCASE = '进入书架'; const TEXT_GUI_API_ADDBOOKCASE_REMOVE = '移出本书'; const TEXT_GUI_DOWNLOAD_IMAGE = '下载图片'; const TEXT_GUI_DOWNLOAD_TEXT = '下载本章'; 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_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 = '-Nothing Here-'; const TEXT_GUI_SDOWNLOAD = '地址三(程序重命名)'; 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} 正在运行。'.replace('{S}', GM_info.script.name); 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 = '每日自动推书:'; 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_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 = '上传图片请遵守法律以及图床使用规定
请不要上传违规图片'; 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_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_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_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 window.LogLevel = { None: 0, Error: 1, Success: 2, Warning: 3, Info: 4, } window.LogLevelMap = {}; window.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'} window.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'} window.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'} window.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'} window.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'} window.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 let API loadRequires(main); // Main function main() { // Common actions polyfillAlert(); tipready = tipcheck(); tipscroll(); addStyle(CSS_COMMON); GMXHRHook(NUMBER_MAX_XHR); CONFIG = new configManager(); TASK = new taskManager(); formSearch(); linkReview(); multiAccount(); // Get tab url api part API = window.location.href.replace(/https?:\/\/www\.wenku8\.net\//, '').replace(/\?.*/, '') .replace(/^book\/\d+\.html?/, 'book').replace(/novel\/(\d+\/?)+\.html?$/, 'novel') .replace(/^novel[\/\d]+index\.html?$/, 'novelindex'); if (isAPIPage()) { pageAPI(API); return; }; if (!API) { location.href = 'https://www.wenku8.net/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; 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 'book': pageBook(); break; // Novel index page case 'novelindex': pageNovelIndex(); break; // Novel page case 'novel': 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 = new ElegantAlertBox( /modules\/article\/bookcase\.php$/.test(location.href) ? TEXT_ALT_ATRCMMDS_NOTASK_PLSSET + (getMyUserDetail().userDetail ? '
'+TEXT_ALT_ATRCMMDS_MAXRCMMD.replace('{V}', String(getMyUserDetail().userDetail.vote)) : '') : TEXT_ALT_ATRCMMDS_NOTASK_OPENBC ); altBox.elm.onclick = (e) => { window.open(URL_BOOKCASE); } return false; }; // Recommend for each let recommended = '', count = 0, allCount = 0; new ElegantAlertBox(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 //new ElegantAlertBox(TEXT_ALT_ATRCMMDS_RUNNING.replaceAll('{BN}', bookName).replaceAll('{BID}', strBookID)); // Go work for (i = 0; i < number; i++) { allCount++; getDocument(URL_RECOMMEND.replaceAll('{B}', strBookID), function(oDoc) { // title: "处理成功" const statusText = oDoc.querySelector('.blocktitle').innerText; // content: "我们已经记录了本次推荐,感谢您的参与!\n\n您每天拥有 5 次推荐权利,这是您今天第 1 次推荐。" const returnText = oDoc.querySelector('.blockcontent').innerText.replace(/\s*\[.+\]\s*$/, ''); // Save count++; book.lasttime = getTime('-', false); count === allCount ? arConfig.lasttime = getTime('-', false) : function() {}; CONFIG.AutoRecommend.saveConfig(arConfig); // GUI recommended += '
{BID} - {BN}'.replaceAll('{BN}', bookName).replaceAll('{BID}', strBookID); count === allCount ? new ElegantAlertBox(TEXT_ALT_ATRCMMDS_ALL_DONE.replaceAll('{R}', recommended)) : function() {}; // Log DoLog(LogLevel.Info, statusText); DoLog(LogLevel.Info, returnText); }) } // Soft alert //new ElegantAlertBox(TEXT_ALT_ATRCMMDS_DONE.replaceAll('{BN}', bookName).replaceAll('{BID}', strBookID)); } return true; } } // Cleaner 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())); } } // 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(); new ElegantAlertBox(TEXT_ALT_SCRIPT_UPDATE_CHECKING); GFU.checkUpdate(scriptID, GM_info.script.version, function(update, updateurl, metaData) { if (update) { const box = new ElegantAlertBox(TEXT_ALT_SCRIPT_UPDATE_GOT.replaceAll('{SN}', metaData.name).replaceAll('{NV}', metaData.version).replaceAll('{CV}', GM_info.script.version)); box.elm.onclick = function() { box.close.call(box); location.href = updateurl; } } else { new ElegantAlertBox(TEXT_ALT_SCRIPT_UPDATE_NONE); } config.scriptUpdate.lasttime = getTime('-', false); CONFIG.GlobalConfig.saveConfig(config); }); return true; } } TM.Script.update(); TM.Cleaner.cleanPageStatus(); 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 = null; 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(); logUpgrade(); break; case '0.2': v01_To_v02(); v02_To_v03(); 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 (config instanceof Array) { 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(); } } } } 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://www.wenku8.net/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(); logUpgrade(); break; case '0.2': v02_To_v03(); v03_To_v04(); v04_To_v05(); v05_To_v06(); v06_To_v07(); v07_To_v08(); logUpgrade(); break; case '0.3': v03_To_v04(); v04_To_v05(); v05_To_v06(); v06_To_v07(); v07_To_v08(); logUpgrade(); break; case '0.4': v04_To_v05(); v05_To_v06(); v06_To_v07(); v07_To_v08(); logUpgrade(); break; case '0.5': v05_To_v06(); v06_To_v07(); v07_To_v08(); logUpgrade(); break; case '0.6': v06_To_v07(); v07_To_v08(); logUpgrade(); break; case '0.7': v07_To_v08(); 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://www.wenku8.net/modules/article/reviewshow.php?rid=228884', tiptitle: '梦想成为书评区大水怪的可以来康康' } } } function v03_To_v04() { if (config.favorites) {return;}; config.favorites = { 228884: { name: '文库导航姬', href: 'https://www.wenku8.net/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 }; } } } } 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, backgroundImage: 'https://img12.360buyimg.com/ddimg/jfs/t1/197476/22/6462/3478996/613227a8E03e8ffc3/99970183ddb9f896.jpg' }, novel: { beautiful: true, }, 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(); 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; 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 }; } } } 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: 'KIENG_JD' }; 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.UserGlobalCfg.upgradeConfig(); //CM.#NEWCONFIGNAME.upgradeConfig(); } // Book page add-on function pageBook() { // Resource const pageResource = { elements: {}, info: {} } collectPageResources(); DoLog(LogLevel.Info, pageResource, true) // Provide meta info copy metaCopy(); // Provide txtfull download for copyright book enableDownload(); // Provide tag search tagOption(); // Ctrl+Enter comment submit areaReply(); // Get page resources function collectPageResources() { collectElements(); collectInfos(); function collectElements() { const elements = pageResource.elements; elements.content = document.querySelector('#content'); elements.bookMain = elements.content.querySelector('div'); elements.header = elements.content.querySelector('div>table'); elements.bookName = elements.header.querySelector('b'); elements.metaContainer = elements.header.querySelector('tr+tr'); elements.metas = elements.metaContainer.querySelectorAll('td'); elements.info = elements.bookMain.querySelector('div+table'); elements.infoText = elements.info.querySelector('td+td'); elements.notice = elements.infoText.querySelectorAll('span.hottext>b'); elements.tags = elements.notice.length > 1 ? elements.notice[0] : null; elements.notice = elements.notice[elements.notice.length-1]; elements.introduce = elements.infoText.querySelectorAll('span'); elements.introduce = elements.introduce[elements.introduce.length-1]; } function collectInfos() { const info = pageResource.info; const elements = pageResource.elements; info.bookName = elements.bookName.innerText; info.BID = Number(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.dlEnabled = elements.content.querySelector('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.querySelector('.'+CLASSNAME_BUTTON); copyBtn.addEventListener('click', function() { copyText(value); showtip(TEXT_TIP_COPIED); const alertBox = new ElegantAlertBox(TEXT_ALT_META_COPIED.replaceAll('{M}', value)); alertBox.elm.onclick = function() { // Prevent copying box content alertBox.close.call(alertBox); } }); settip(copyBtn, TEXT_TIP_COPY); } } // Download copyright book function enableDownload() { if (pageResource.info.dlEnabled) {return false;}; let div = document.createElement('div'); pageResource.elements.bookMain.appendChild(div); div.outerHTML = HTML_DOWNLOAD_LINKS .replaceAll('{ORIBOOKNAME}', pageResource.info.bookName) .replaceAll('{BOOKID}', String(pageResource.info.BID)) .replaceAll('{BOOKNAME}', encodeURIComponent(pageResource.info.bookName)); div = document.querySelector('#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 (document.querySelector('td > input[name="Submit"]') && !document.querySelector('#ptitle')) { const table = document.querySelector('form>table'); const titleText = table.innerHTML.match(//)[0]; const titleHTML = titleText.replace(/^$/, ''); const titleEle = document.createElement('tr'); const caption = table.querySelector('caption'); table.insertBefore(titleEle, caption); titleEle.outerHTML = titleHTML; } const commentArea = document.querySelector('#pcontent'); if (!commentArea) {return false;}; const commentForm = document.querySelector('form[action^="https://www.wenku8.net/modules/article/review"]'); const commentSbmt = document.querySelector('td > input[name="Submit"]'); const commenttitl = document.querySelector('#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 = document.createElement('span'); commentbttm.appendChild(asTip); // Review-Page: Same rid, same savekey - 'rid123456' // Book-Page & Book-Review-List-Page: Same bookid, same savekey - 'bid1234' let commentData = { rid : getUrlArgv({name: 'rid', dealFunc: Number}), aid : getUrlArgv({name: 'aid', dealFunc: Number}), bid : location.href.match(/\/book\/(\d+).htm/) ? Number(location.href.match(/\/book\/(\d+).htm/)[1]) : 0, page : getUrlArgv({name: 'rid', dealFunc: Number, defaultValue: 1}) } commentData.key = commentData.rid ? 'rid' + String(commentData.rid) : 'bid' + String(commentData.bid); 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.querySelector('#menuItemInsertUrl'); const menuItemInsertImage = commentForm.querySelector('#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 = document.querySelector('#UBB_Menu'); const elmImage = commentForm.querySelector('#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: document.querySelector('#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 = document.createElement('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); new ElegantAlertBox(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 new ElegantAlertBox(TEXT_ALT_IMAGE_UPLOAD_WORKING); uploadImage({ file: file, onerror: (e) => { new ElegantAlertBox(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); new ElegantAlertBox(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 = document.querySelector('#UBB_Menu'); const list = new PlusList({ id: 'plus_AtTable', list: [], parentElement: menu.parentElement, insertBefore: document.querySelector('#FontSizeTable'), visible: false, onshow: showlist }); list.onhide = list.clear; document.addEventListener('click', list.hide); const firstBtn = menu.children[0]; const atBtn = document.createElement('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); /*const div = document.createElement('div'); const ul = document.createElement('ul'); div.classList.add(CLASSNAME_LIST); div.appendChild(ul); div.style.display = 'none'; div.id = 'plus_AtTable'; document.addEventListener('click', hideList); menu.parentElement.insertBefore(div, document.querySelector('#FontSizeTable')); function toggleDisplay() { div.style.display === 'none' ? showList() : hideList(); } function hideList() { div.style.display = 'none'; } function showList(e) { if (typeof(ubb_subdiv) === 'string' && typeof(hideeve) === 'function') { hideeve(ubb_subdiv); ubb_subdiv = 'plus_AtTable'; } destroyEvent(e) div.style.display = ''; refreshList(); ul.focus(); }*/ 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 refreshList() { const pageUsers = document.querySelectorAll('#content table strong>a[href^="https://www.wenku8.net/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);}); // Make
  • let maxNameLength = 0; clearChildnodes(ul); for (const user of allUsers) { makeList(user); } // Style ul.style.width = String(maxNameLength+1) + 'em'; div.style.left = String(UBBEditor.GetPosition(atBtn).x) + 'px'; div.style.top = String(UBBEditor.GetPosition(atBtn).y + 20) + 'px'; return true; // 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 makeList(user) { // the same style as FontSizeTable const li = document.createElement('li'); const btn = document.createElement('input'); btn.type = 'button'; btn.value = user.userName; btn.user = user; btn.classList.add(CLASSNAME_LIST_BUTTON); btn.addEventListener('click', btnClick); settip(btn, 'uid: ' + String(user.userID)); li.classList.add(CLASSNAME_LIST_ITEM); li.appendChild(btn); ul.appendChild(li); maxNameLength = Math.max(maxNameLength, user.userName.length); return li; } function btnClick() { const btn = this; const user = btn.user; const name = btn.user.userName; const insertPosition = commentArea.selectionEnd; const text = commentArea.value; const leftText = text.substr(0, insertPosition); const rightText = text.substr(insertPosition); let insertValue = '@' + name; // 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); // 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 makelist() { // Get users const allUsers = getAllUsers(); // Make list let maxNameLength = 0; 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; maxNameLength = Math.max(maxNameLength, user.userName.length); } // Style list.ul.style.width = String(maxNameLength+2) + 'em'; 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 = document.querySelectorAll('#content table strong>a[href^="https://www.wenku8.net/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 = document.querySelectorAll('td>a[href^="https://www.wenku8.net/modules/article/reviewshow.php?"]'); for (const RLink of allRLinks) { lastPage(RLink); } // Provide button direct to review last page /*// Old version. Uses xhr to get last page. function lastPage(a) { const p = a.parentElement; const btn = document.createElement('span'); const rid = Number(a.href.match(/\?[.+&]?rid=(\d+)/)[1]); if (isNaN(rid)) {return false;} else {btn.rid = rid;}; btn.addEventListener('click', gotoLastPage); btn.classList.add(CLASSNAME_BUTTON); btn.innerText = TEXT_GUI_LINK_TOLASTPAGE; btn.url = null; p.insertBefore(btn, a); function gotoLastPage() { const btn = this; const rid = btn.rid; if (btn.url) { window.open(btn.url); } else { const box = new ElegantAlertBox(TEXT_ALT_LASTPAGE_LOADING); getLatestReviewPageUrl(rid, (url)=>{ if (box.exist) {box.close.call(box);}; btn.url = url; window.open(btn.url); }); } } }*/ // New version. Uses '&page=last' keyword. function lastPage(a) { const p = a.parentElement; const lastpg = document.createElement('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); } } // Reviewedit page add-on function pageReviewedit() { redirectToCorrectPage(); function redirectToCorrectPage() { // Get redirect target rid const refreshMeta = document.querySelector('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 document.querySelector('a').href = url; // Redirect setTimeout(() => {location.href = url;}, 1500); } } // Review page add-on function pageReview() { // Page Info const main = document.querySelector('#content'); const rid = Number(getUrlArgv('rid')); const aid = getUrlArgv('aid') ? Number(getUrlArgv('aid')) : Number(main.querySelector('td[width]>a').href.match(/(\d+)\.html?$/)[1]); const page = Number(document.querySelector('#pagelink strong').innerText); const title = main.querySelector('th>strong').textContent; window.addEventListener('load', () => { // Recover page status applyPageStatus(); // Record the current page status of current review setInterval(recordPage, 1000); }); // GUI const pageCountText = document.querySelector('#pagelink>.last').href.match(/page=(\d+)/)[1]; const headBars = main.querySelectorAll('tr>td[align]'); const lefta = headBars[0].querySelector('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 = document.createElement('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 = document.createElement('span'); const bbcdTxt = document.createElement('span'); const bbcdChk = document.createElement('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); beautifier(); floorEnhance(); autoRefresh(); addFavorite(); function beautifier() { // GUI const span = document.createElement('span'); const check = document.createElement('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); new ElegantAlertBox(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.reviewshow.backgroundImage) .replaceAll('{S}', config.textScale) , 'beautifier'); hookPosition(); } 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 ($(obj).getStyle('position') == 'absolute' || $(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; } } // 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) { // 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(); for (const floor of floors) { alinkEdit(floor); addQuoteBtn(floor); addQueryBtn(floor); alinktofloor(floor.table); } } function alinktofloor(parent=main) { const floorLinks = parent.querySelectorAll('a[name][href^="#yid"]'); for (const a of main.querySelectorAll('a')) { if (!a.href.match(/^https?:\/\/www\.wenku8\.net\/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 = document.querySelectorAll('a[href^="https://www.wenku8.net/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(); // Use wenku8's openDialog openDialog(e.target.href + '&ajax_gets=jieqi_contents', false); // Show mask if mask not shown !document.getElementById("mask") && showMask(); }) } } function autoRefresh() { let working=false, interval=0; const pagelink = document.querySelector('#pagelink'); const tdLink = pagelink.parentElement; const trContainer = tdLink.parentElement; const tdAutoRefresh = document.createElement('td'); const chkAutoRefresh = document.createElement('input'); const txtAutoRefresh = document.createElement('span'); const txtPaused = document.createElement('span'); const ptitle = document.querySelector('#ptitle'); const pcontent = document.querySelector('#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 = new ElegantAlertBox(TEXT_ALT_AUTOREFRESH_NOTLAST); box.elm.onclick = () => {location.href = document.querySelector('#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); new ElegantAlertBox(working ? TEXT_ALT_AUTOREFRESH_ON : TEXT_ALT_AUTOREFRESH_OFF); } function refresh() { const box = new ElegantAlertBox(TEXT_ALT_AUTOREFRESH_WORKING); // Get last page, but uses the 'last' keyword in url args /*getDocument(location.href, (oDoc)=>{ const eleCurPage = oDoc.querySelector('#pagelink strong'); const eleLastPage = oDoc.querySelector('#pagelink a.last'); const urlLastPage = eleLastPage.href; if (eleCurPage.innerText !== eleLastPage.innerText) { getDocument(urlLastPage, refreshLoaded); } else { refreshLoaded(oDoc); } })*/ const url = URL_REVIEWSHOW.replace('{R}', String(rid)).replace('{A}', String(aid)).replace('{P}', 'last'); getDocument(url, refreshLoaded); function refreshLoaded(oDoc) { // Clost alert box box.exist ? box.close.apply(box) : function() {}; // Update all existing floor content (and title) const nowfloors = document.querySelectorAll('#content>table[class="grid"]'); const newfloors = oDoc .querySelectorAll('#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 && new ElegantAlertBox(TEXT_ALT_AUTOREFRESH_MODIFIED); const newtop = getTopFloorNumber(oDoc); const nowtop = getTopFloorNumber(document); /*if (newtop === 9999) { sendReviewReply({rid: rid, title: '诶嘿', content: '又到10000楼咯~'}); }*/ if (newtop > nowtop) { const eleLastPage = oDoc.querySelector('#pagelink a.last'); const urlLastPage = eleLastPage.href; const newmain = oDoc.querySelector('#content'); const newfloors = getAllFloors(newmain); const nowfloors = getAllFloors(); if (getUrlArgv({url: urlLastPage, name: page}) === page.toString()) { // 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 = new ElegantAlertBox(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.querySelector('#pagelink').innerHTML = newmain.querySelector('#pagelink').innerHTML; return true; } } else { new ElegantAlertBox(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; } } } } function isCurLastPage() { return document.querySelector('#pagelink>strong').innerText === document.querySelector('#pagelink>a.last').innerText; } function getTopFloorNumber(oDoc) { const tblfloors = oDoc.querySelectorAll('#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 addFavorite() { // Create GUI const spliter = document.createElement('span'); const favorBtn = document.createElement('span'); const favorChk = document.createElement('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) }; inFavorites = true; } CONFIG.BkReviewPrefs.saveConfig(config); setTimeout(() => {favorChk.checked = inFavorites;}, 0); new ElegantAlertBox((inFavorites ? TEXT_GUI_REVIEW_FAVORADDED : TEXT_GUI_REVIEW_FAVORDELED).replace('{N}', title)); } } function addQuoteBtn(floor) { const table = floor.table; const numberEle = table.querySelector('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 = document.querySelector('#pcontent'); const form = document.querySelector('form[action^="https://www.wenku8.net/modules/article/review"]'); // Create button const btn = document.createElement('span'); btn.classList.add(CLASSNAME_BUTTON); btn.addEventListener('click', quoteThisFloor); btn.innerHTML = '引用'; return btn; function quoteThisFloor() { // In DOM Events, keyword points to the Event Element. const numberEle = this.parentElement.querySelector('a[name]'); const numberText = numberEle.innerText; const url = numberEle.href; const contentEle = this.parentElement.parentElement.querySelector('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 addQueryBtn(floor) { // Get container div const div = floor.leftdiv; // Create buttons const qBtn = document.createElement('a'); // Button for query reviews const iBtn = document.createElement('a'); // Button for query userinfo // Get UID const user = div.querySelector('a'); const UID = Number(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)); qBtn.target = '_blank'; iBtn.target = '_blank'; qBtn.innerText = TEXT_GUI_USER_REVIEWSEARCH; iBtn.innerText = TEXT_GUI_USER_USERINFO; // Append to GUI div.appendChild(document.createElement('br')); div.appendChild(iBtn); div.appendChild(qBtn); div.insertBefore(spliter, qBtn); } // Reply without refreshing the document function hookReply() { const form = document.querySelector('form[name="frmreview"]'); const onsubmit = form.onsubmit; form.onsubmit = function() { const title = form.querySelector('#ptitle').value; const content = form.querySelector('#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 bbcodeOnclick(e) { destroyEvent(e); if (downloadWholePost.working) { new ElegantAlertBox(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.querySelector('#content'); const avatars = main.querySelectorAll('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.querySelector('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.querySelector('tr'); elements.tdUser = elements.table.querySelector('td.odd'); elements.tdReply = elements.table.querySelector('td.even'); elements.divUser = elements.tdUser.querySelector('div'); elements.aUser = elements.divUser.querySelector('a'); elements.attr = elements.tdReply.querySelector('div a').parentElement; elements.time = elements.attr.childNodes[0]; elements.number = elements.attr.querySelector('a[name]'); elements.title = elements.tdReply.querySelector('div>strong'); elements.content = elements.tdReply.querySelector('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) : 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 = document.querySelector('#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 = '文库贴 - ' + String(data.id) + '.txt'; const a = document.createElement('a'); a.href = url; a.download = name; a.click(); // GUI saveBtn.innerText = TEXT_GUI_DOWNLOADFINISH_REVIEW; new ElegantAlertBox(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 = parent.querySelectorAll('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.tbody = floor.tr.parentElement; floor.table = floor.tbody.parentElement; floors.push(floor); } return floors; } // Validate a element whether is a floor function isFloorTable(tbl) { return tbl.querySelector('a[href^="#yid"][name^="yid"]') ? true : false; } // Get floor title element () // Argv:
    element of the floor function getEleFloorTitle(tblfloor) { return tblfloor.querySelector('td.even>div:first-child>strong'); // or :nth-child(1) } // Get floor content element (
    ) // Argv:
    element of the floor function getEleFloorContent(tblfloor) { return tblfloor.querySelector('td.even>hr+div'); } // Get the floor number // Argv:
    element of the floor function getFloorNumber(tblfloor) { const eleNumber = tblfloor.querySelector('a[name^="yid"]'); return eleNumber ? Number(eleNumber.innerText.match(/\d+/)[0]) : false; } // Get floor content by BBCode format (content only, no title) // Argv:
    content Element function getFloorContent(contentEle) { 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 += ' S '.replace('S', node.src); break; case 'A': content += '[url=U]T[/url]'.replace('U', node.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); } else { content += getFloorContent(node); } break; case 'CODE': content += getFloorContent(node); break; // Just ignore case 'PRE': content += getFloorContent(node); 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); 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); 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); 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.querySelector('table.grid+table[border]'); main.insertBefore(table, elmafter); // Enhances alinkEdit(floor); addQuoteBtn(floor); addQueryBtn(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://www.wenku8.net/modules/article/bookcase.php?classid={CID}'; const content = document.querySelector('#content'); const selector = document.querySelector('[name="classlist"]'); const options = selector.children; // Current bookcase const curForm = content.querySelector('#checkform'); const curClassid = Number(document.querySelector('[name="clsssid"]').value); const bookcases = CONFIG.bookcasePrefs.getConfig(initPreferences).bookcases; addTopTitle(); decorateForm(curForm, bookcases[curClassid]); // gowork showBookcases(); recommendAllGUI(); function recommendAllGUI() { const block = createLeftBlock(TEXT_GUI_BOOKCASE_ATRCMMD, true, { type: 'mypage', links: [ {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.querySelector('.ulitem'); const txtst = block.querySelector('#arstatus'); const btnAR = block.querySelector('#autorcmmd'); const btnRN = block.querySelector('#rcmmdnow'); const txtAR = btnAR.querySelector('span'); const checkbox = document.createElement('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); new ElegantAlertBox(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) {new ElegantAlertBox(TEXT_ALT_ATRCMMDS_NOTASK); return false;}; TASK.AutoRecommend.run(true); } } function initPreferences() { const lists = []; for (const option of options) { lists.push({ classid: Number(option.value), url: bookCaseURL.replace('{CID}', String(option.value)), name: option.innerText }) } return {bookcases: lists}; } function addTopTitle() { // Clone title bar const checkform = document.querySelector('#checkform') ? document.querySelector('#checkform') : document.querySelector('.'+CLASSNAME_BOOKCASE_FORM); const oriTitle = checkform.querySelector('div.gridtop'); const topTitle = oriTitle.cloneNode(true); content.insertBefore(topTitle, checkform); // Hide bookcase selector const bcSelector = topTitle.querySelector('[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.querySelector('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.querySelector('#checkform'); form.parentElement.removeChild(form); // Find the right place to insert it in const forms = content.querySelectorAll('.'+CLASSNAME_BOOKCASE_FORM); for (let i = 0; i < forms.length; i++) { const thisForm = forms[i]; const cid = thisForm.classid ? thisForm.classid : curClassid; if (cid > classid) { content.insertBefore(form, thisForm); break; } } if(!form.parentElement) {content.appendChild(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.querySelector('[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.querySelector('#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 = content.querySelectorAll('.'+CLASSNAME_BOOKCASE_FORM); for (const form of forms) { renameFormSlctr(form); } } else { renameFormSlctr(form); } function renameFormSlctr(form) { const newclassid = form.querySelector('#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 GUI to edit bookcase name function editName() { const nameInput = document.createElement('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.querySelector('table'); for (const tr of table.querySelectorAll('tr')) { tr.querySelector('.odd') ? decorateRow(tr) : function() {}; tr.querySelector('th') ? decorateHeader(tr) : function() {}; tr.querySelector('td.foot') ? decorateFooter(tr) : function() {}; } // Insert auto-recommend option for given row function decorateRow(tr) { const eleBookLink = tr.querySelector('td:nth-child(2)>a'); const strBookID = eleBookLink.href.match(/aid=(\d+)/)[1]; const strBookName = eleBookLink.innerText; const newTd = document.createElement('td'); const input = document.createElement('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 = tr.querySelectorAll('th'); const width = ARR_GUI_BOOKCASE_WIDTH; const newTh = document.createElement('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.querySelector('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 new ElegantAlertBox( TEXT_ALT_ATRCMMDS_SAVED .replaceAll('{B}', strBookName) .replaceAll('{N}', value) .replaceAll('{R}', userDetail.vote-arConfig.allCount) ); if (userDetail && arConfig.allCount > userDetail.vote) { const alertBox = new ElegantAlertBox( TEXT_ALT_ATRCMMDS_OVERFLOW .replace('{V}', String(userDetail.vote)) .replace('{C}', String(arConfig.allCount)) ); alertBox.elm.onclick = function() { alertBox.close.call(alertBox); refreshMyUserDetail(); } }; } else { // invalid input value, alert new ElegantAlertBox(TEXT_ALT_ATRCMMDS_INVALID.replaceAll('{N}', value)); } } } } } // Novel ads remover function removeTopAds() { const ads = []; document.querySelectorAll('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(); } // Novel page add-on function pageNovel() { const pageResource = {elements: {}, infos: {}, download: {}}; collectPageResources(); DoLog(LogLevel.Info, pageResource, true) // Remove ads removeTopAds(); // Provide download GUI downloadGUI(); // Prevent URL.revokeObjectURL in script 轻小说文库下载 revokeObjectURLHOOK(); // Beautifier page beautifier(); function collectPageResources() { collectElements(); collectInfos(); initDownload(); function collectElements() { const elements = pageResource.elements; elements.title = document.querySelector('#title'); elements.images = document.querySelectorAll('.imagecontent'); elements.rightButtonDiv = document.querySelector('#linkright'); elements.rightNodes = elements.rightButtonDiv.childNodes; elements.rightBlank = elements.rightNodes[elements.rightNodes.length-1]; elements.content = document.querySelector('#content'); elements.contentmain = document.querySelector('#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); } } // Provide download GUI function downloadGUI() { const elements = pageResource.elements; const infos = pageResource.infos; const download = pageResource.download; // Create donwload button const dlBtn = elements.downloadBtn = document.createElement('span'); dlBtn.classList.add(CLASSNAME_BUTTON); dlBtn.addEventListener('click', infos.isImagePage ? dlNovelImages : dlNovelText); dlBtn.innerText = infos.isImagePage ? TEXT_GUI_DOWNLOAD_IMAGE : TEXT_GUI_DOWNLOAD_TEXT; // 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); function dlNovelImages() { 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 = document.createElement('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() { alert('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; } } } function dlNovelText() { const name = infos.title + '.txt'; const text = infos.content.replaceAll(/[\r\n]+/g, '\r\n'); downloadText(text, name); } } // 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.reviewshow.backgroundImage) .replaceAll('{S}', config.textScale) .replaceAll('{H}', usedHeight), 'beautifier' ); // Get rest height without #contentmain function getRestHeight() { let usedHeight = 0; ['adv1', 'adtop', 'headlink', 'footlink', 'adbottom'].forEach((id) => { const node = document.querySelector('#'+id); if (node instanceof Element && node.id !== 'contentmain') { const cs = getComputedStyle(node); ['height', 'marginTop', 'marginBottom', 'paddingTop', 'paddingBottom', 'borderTop', 'borderBottom'].forEach((style) => { usedHeight += Number(cs[style].match(/([\.\d]+)px/)[1]); }); }; }); usedHeight = usedHeight.toString() + 'px'; return usedHeight; } } } // Image format changing function // image: 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 = document.createElement('canvas'); cvs.width = image.width; cvs.height = image.height; const ctx = cvs.getContext('2d'); ctx.drawImage(image, 0, 0); return cvs.toDataURL(format); } } // Search form add-on function formSearch() { const searchForm = document.querySelector('form[name="articlesearch"]'); if (!searchForm) {return false;}; const typeSelect = searchForm.querySelector('#searchtype'); const searchText = searchForm.querySelector('#searchkey'); const searchSbmt = searchForm.querySelector('input[class="button"][type="submit"]'); let optionTags; provideTagOption(); onsubmitHOOK(); function provideTagOption() { optionTags = document.createElement('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() { } // 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 = document.querySelectorAll('.blockcontent .userinfo')[0].parentElement; const container = oriContainer.cloneNode(true); const button = container.querySelector('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 = document.querySelector('#content'); const tbody = content.querySelector('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_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_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'}] ] const elements = createTableGUI(settings); const tdBgi = elements.bgimage; const imageinput = elements.imageinput = document.createElement('input'); const bgioprt = elements.bgioprt = document.createElement('span'); const bgiupld = elements.bgupload; const ckbgiup = elements.ckbgiup = document.createElement('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; 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)) { const span = document.createElement('span'); const radio = document.createElement('input'); const text = document.createElement('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) { settip(radio, imager.tip); settip(text, imager.tip); //settip(span, imager.tip); } elements.imager.appendChild(span); } elements.imager.querySelector('#imager_'+curimager).checked = true; // Text scale const textScale = CONFIG.BeautifierCfg.getConfig().textScale; const scalectnr = elements.scalectnr; const elmscale = elements.scale = document.createElement('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 ckbtfnvl = elements.ckbtfnvl = document.createElement('input'); const ckbtfrvw = elements.ckbtfrvw = document.createElement('input'); ckbtfnvl.type = ckbtfrvw.type = 'checkbox'; ckbtfnvl.page = 'novel'; ckbtfrvw.page = 'reviewshow'; ckbtfnvl.checked = CONFIG.BeautifierCfg.getConfig().novel.beautiful; ckbtfrvw.checked = CONFIG.BeautifierCfg.getConfig().reviewshow.beautiful; ckbtfnvl.addEventListener('change', beautifulChange); ckbtfrvw.addEventListener('change', beautifulChange); btfnvlctnr.appendChild(ckbtfnvl); btfrvwctnr.appendChild(ckbtfrvw); // Favorite open const favoropen = elements.favoropen; const favorlast = elements.favorlast = document.createElement('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 = document.createElement('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); // Config export/import const exportcfg = elements.exportcfg; const exportcfgnp = elements.exportcfgnp; const importcfg = elements.importcfg; const configinput = elements.configinput = document.createElement('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); 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); // 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 = document.createElement('input'); imageinput.type = 'file'; imageinput.style.display = 'none'; imageinput.addEventListener('change', pictureGot); elements.bgimage.appendChild(imageinput); if (!mimetype || mimetype.split('/')[0] !== 'image') { new ElegantAlertBox(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.reviewshow.backgroundImage = url; config.reviewshow.bgiName = name; CONFIG.BeautifierCfg.saveConfig(config); elements.bgioprt.innerHTML = name; URL.revokeObjectURL(objurl); // Upload if need if (config.upload) { new ElegantAlertBox(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) => { new ElegantAlertBox(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.reviewshow.backgroundImage = json.url; CONFIG.BeautifierCfg.saveConfig(config); elements.bgioprt.innerHTML = name; new ElegantAlertBox(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.reviewshow.bgiName ? config.reviewshow.bgiName : 'image.jpeg'; if (config.upload) { // Upload const url = config.reviewshow.backgroundImage; if (!/^https?:\/\//.test(url)) { new ElegantAlertBox(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) => { new ElegantAlertBox(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.reviewshow.backgroundImage); CONFIG.BeautifierCfg.saveConfig(config); }, onload: (json) => { const config = CONFIG.BeautifierCfg.getConfig(); config.reviewshow.backgroundImage = json.url; config.reviewshow.bgiName = elements.bgioprt.innerHTML = json.name; CONFIG.BeautifierCfg.saveConfig(config); new ElegantAlertBox(TEXT_ALT_IMAGE_UPLOAD_SUCCESS.replace('{NAME}', json.name).replace('{URL}', json.url)); } }); } } else { // Download const url = config.reviewshow.backgroundImage; if (/^https?:\/\//.test(url)) { new ElegantAlertBox(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.reviewshow.backgroundImage); CONFIG.BeautifierCfg.saveConfig(config); return false; }; // Save to config const config = CONFIG.BeautifierCfg.getConfig(); config.reviewshow.backgroundImage = dataurl; CONFIG.BeautifierCfg.saveConfig(config); new ElegantAlertBox(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); new ElegantAlertBox('图床已切换到{NAME}'.replace('{NAME}', imager.name)); imager.warning && new ElegantAlertBox(imager.warning); } } function scaleChange(e) { e.stopPropagation(); const config = CONFIG.BeautifierCfg.getConfig(); config.textScale = e.target.value; CONFIG.BeautifierCfg.saveConfig(config); new ElegantAlertBox(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); new ElegantAlertBox(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); new ElegantAlertBox(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') { new ElegantAlertBox(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); new ElegantAlertBox(TEXT_ALT_DETAIL_IMPORTED); } catch(err) { fileError(err); } } function fileError(e) { DoLog(LogLevel.Error, ['pageDetail.insertSettings.GUI.configfileGot:', e]); new ElegantAlertBox(TEXT_ALT_DETAIL_CONFIG_IMPORT_ERROR_READ); } } } function createTableGUI(lines) { const elements = {}; for (const line of lines) { const tr = document.createElement('tr'); for (const item of line) { const td = document.createElement('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; } } // Index page add-on function pageIndex() { insertStatus(); showFavorites(); // 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 = createLeftBlock(TEXT_GUI_INDEX_FAVORITES, true, { type: 'toplist', links: links }) } // Insert usersript inserted tip function insertStatus() { const blockcontent = document.querySelector('#centers>.block:nth-child(1)>.blockcontent'); blockcontent.appendChild(document.createElement('br')); const textNode = document.createElement('span'); textNode.innerText = TEXT_GUI_INDEX_STATUS; textNode.classList.add(CLASSNAME_TEXT); blockcontent.appendChild(textNode); } } // 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 = document.querySelector('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() { // Only txt is really separated by volumes if (novelInfo.type !== 'txt') {return false;}; // define vars let i; const tbody = document.querySelector('table>tbody'); const header = tbody.querySelector('th').parentElement; const thead = header.querySelector('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 = tbody.querySelectorAll('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.querySelector('td.even').cloneNode(true); const links = newTd.querySelectorAll('a'); for (const a of links) { a.classList.add(CLASSNAME_BUTTON); a.info = { description: 'volume download button', name: novelInfo.volumeNames[index], filename: '{NovelName} {VolumeName}.{Extension}' .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 = newTr.querySelectorAll('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 = newTds[3].querySelectorAll('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; } // Get all name display elements function getAllNameEles() { return document.querySelectorAll('.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 = document.querySelectorAll('#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 = document.querySelectorAll('.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 + '/'); } } // Login page add-on function pageLogin() { const form = document.querySelector('form[name="frmlogin"]'); if (!form) {return false;} const eleUsername = form.querySelector('input.text[name="username"]'); const elePassword = form.querySelector('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 (!document.querySelector('.fl')) {return false;}; GUI(); function GUI() { // Add switch select const eleTopLeft = document.querySelector('.fl'); const eletext = document.createElement('span'); const sltSwitch = document.createElement('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 = document.createElement('option'); option.innerText = text; option.value = value; sltSwitch.appendChild(option); return option; } function selectCurUser() { for (const option of sltSwitch.querySelectorAll('option')) { option.selected = getUserName().toLowerCase() === option.value.toLowerCase(); } } } function switchAccount(username) { // Logout new ElegantAlertBox(TEXT_ALT_ACCOUNT_WORKING_LOGOFF); GM_xmlhttpRequest({ method: 'GET', url: URL_USRLOGOFF, onload: function(response) { // Login new ElegantAlertBox(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 = new ElegantAlertBox(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.elm.onclick = () => { clearTimeout(timeout); box.close.call(box); }; } }) } }) } } // API page and its sub pages add-on function pageAPI(API) { addStyle(CSS_PAGE_API); //logAPI(); switch(API) { case 'modules/article/addbookcase.php': pageAddbookcase(); break; default: logAPI(); } 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://www.wenku8.net/modules/article/bookcase.php', innerHTML: TEXT_GUI_API_ADDBOOKCASE_TOBOOKCASE }); // Append link to remove from bookcase (not finished) /*addBottomButton({ href: 'https://www.wenku8.net/modules/article/bookcase.php?delid=' + getUrlArgv('bid'), innerHTML: TEXT_GUI_API_ADDBOOKCASE_REMOVE, onclick: function() { confirm('确实要将本书移出书架么?') } });*/ } // Add a bottom-styled botton into bottom line, to the first place function addBottomButton(details) { const aClose = document.querySelector('a[href="javascript:window.close()"]'); const bottom = aClose.parentElement; const a = document.createElement('a'); const t1 = document.createTextNode('['); const t2 = document.createTextNode(']'); const blank = document.createElement('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 = document.querySelectorAll('.block'); const close = document.querySelectorAll('a[href="javascript:window.close()"]'); return block.length === 1 && close.length === 1; } // 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://www.wenku8.net/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); } }); } // getMyUserDetail with soft alerts function refreshMyUserDetail(callback, args=[]) { new ElegantAlertBox(TEXT_ALT_USRDTL_REFRESH); getMyUserDetail(function() { const alertBox = new ElegantAlertBox(TEXT_ALT_USRDTL_REFRESHED); // rewrite onclick function from copying to showing details alertBox.elm.onclick = function() { alertBox.close.call(alertBox); new ElegantAlertBox(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 callback is not a function, then will just request&store but not callback function getMyUserDetail(callback, args=[]) { 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.querySelector('#content'); storage.userDetail = { userID: Number(content.querySelector('tr:nth-child(1)>.even').innerText), // '用户ID' userLink: content.querySelector('tr:nth-child(2)>.even').innerText, // '推广链接' userName: content.querySelector('tr:nth-child(3)>.even').innerText, // '用户名' displayName: content.querySelector('tr:nth-child(4)>.even').innerText, // '用户昵称' userType: content.querySelector('tr:nth-child(5)>.even').innerText, // '等级' userGrade: content.querySelector('tr:nth-child(6)>.even').innerText, // '头衔' gender: content.querySelector('tr:nth-child(7)>.even').innerText, // '性别' email: content.querySelector('tr:nth-child(8)>.even').innerText, // 'Email' qq: content.querySelector('tr:nth-child(9)>.even').innerText, // 'QQ' msn: content.querySelector('tr:nth-child(10)>.even').innerText, // 'MSN' site: content.querySelector('tr:nth-child(11)>.even').innerText, // '网站' signupDate: content.querySelector('tr:nth-child(13)>.even').innerText, // '注册日期' contibute: content.querySelector('tr:nth-child(14)>.even').innerText, // '贡献值' exp: content.querySelector('tr:nth-child(15)>.even').innerText, // '经验值' credit: content.querySelector('tr:nth-child(16)>.even').innerText, // '现有积分' friends: content.querySelector('tr:nth-child(17)>.even').innerText, // '最多好友数' mailbox: content.querySelector('tr:nth-child(18)>.even').innerText, // '信箱最多消息数' bookcase: content.querySelector('tr:nth-child(19)>.even').innerText, // '书架最大收藏量' vote: content.querySelector('tr:nth-child(20)>.even').innerText, // '每天允许推荐次数' sign: content.querySelector('tr:nth-child(22)>.even').innerText, // '用户签名' intoduction: content.querySelector('tr:nth-child(23)>.even').innerText, // '个人简介' userImage: content.querySelector('tr>td>img').src // '头像' } loaded(); } function friendLoaded(oDoc) { const content = oDoc.querySelector('#content'); const trs = content.querySelectorAll('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].querySelector('a').href.match(/\?uid=(\d+)/)[1]))) {return false;}; // Collect information let friend = { userID: Number(tr.children[2].querySelector('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() {}; } } } } 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 = '轻小说文库+_配置文件_v{V}_{T}.wkp'.replace('{V}', GM_info.script.version).replace('{T}', getTime()); downloadText(text, name); new ElegantAlertBox(TEXT_ALT_CONFIG_EXPORTED.replace('{N}', name)); } function importConfig(json) { // Redirect redirectGMStorage(); // 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); } // Reload location.reload(); } 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; } // 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'; document.onmousemove = unsafeWindow.tipmove = function tipmoveplus(e) { tipobj.style.left = e.clientX + tipx + 'px'; tipobj.style.top = e.clientY + tipy + 'px'; } return true; } // 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.target && (e.target.tiptitle || e.target.tipgetter)) { const tip = e.target.tiptitle || e.target.tipgetter(); if (tipready) { tipshow(tip); e.target.title && e.target.removeAttribute('title'); } else { e.target.title = e.target.tiptitle; } } else if (typeof(e) === 'string') { tipready && tipshow(e); } } function hidetip() { tipready && tiphide(); } // 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:
    , ul: