├── LICENSE ├── README.md └── SPXXKLP.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Cinder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SPXXKLP 2 | SPXXKLP is a Minecraft.net & X.com blog-to-BBCode converter based on SPXX User Script™️,adapted to klpbbs.com in order to make Minecraft news translation easier. 3 | 4 | SPXXKLP 是一个基于 SPXX User Script™️ 的 Minecraft.net 、 feedback.minecraft.net 、 help.minecraft net 及 X.com 博文至 BBCode 转换器,进行了一系列修复、完善与调整以便适应 klpbbs.com 的版块规定,令快讯翻译更便捷。 5 | 6 | SPXXKLP is licensed under MIT. 7 | 8 | SPXXKLP 采用 MIT 协议进行授权。 9 | 10 | # About SPXX User Script™️ 11 | SPXX is a project which rewrites SPX,focusing on assisting news translaters. 12 | 13 | SPXX 是一个对 SPX 进行重写以便辅助快讯译者的项目。 14 | 15 | The SPXX User Script adds a "Copy BBCode" button to Minecraft.net, feedback.minecraft.net and help.minecraft.net articles and Tweets, which sets the [BBCode](https://en.m.wikipedia.org/wiki/BBCode) representation of this blog article to your clipboard. 16 | 17 | SPXX 用户脚本为 Minecraft.net, feedback.minecraft.net 和 help.minecraft.net 文章及 Tweets 添加了“Copy BBCode”按钮,可将博文以 [BBCode](https://en.m.wikipedia.org/wiki/BBCode) 形式拷贝至您的剪贴板。 18 | 19 | SPXX project is licensed under [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/) Deed. 20 | 21 | SPXX 项目使用 CC0-1.0 协议进行授权。 22 | 23 | # Contributers 24 | SPXXKLP is written by Cinder0601 based on SPXX User Script v2.4.14 and SPX. 25 | 26 | SPXXKLP 由 Cinder0601 基于 SPXX 用户脚本 v2.4.14 及 SPX 进行改写。 27 | 28 | Got help from [Redstone-D](https://github.com/Redstone-D) during development. 29 | 30 | 在开发中得到了 [Redstone-D](https://github.com/Redstone-D) 的帮助。 31 | 32 | # Related 33 | SPXX Project:https://github.com/SPXFellow/spxx 34 | 35 | SPXX 项目地址:https://github.com/SPXFellow/spxx 36 | 37 | Sincere thanks to SPGoding and [SPXFellow](https://github.com/SPXFellow) for their tremendous contribution on SPXX™️,you ARE eternal newslords. 38 | 39 | 衷心感谢 SPGoding 及 [SPXFellow](https://github.com/SPXFellow) 对 SPXX™️ 开发的巨大贡献,你们是永远的纽斯罗德。 40 | -------------------------------------------------------------------------------- /SPXXKLP.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name SPXXKLP 3 | // @description Minecraft.net & X.com blog article to BBCode converter, adapted to KLPBBS 4 | // @namespace npmjs.com/package/@spxxklp/userscript 5 | // @author Cinder & SPGoding & SPX Fellow 6 | // @connect * 7 | // @connect feedback.minecraft.com 8 | // @connect help.minecraft.net 9 | // @connect raw.githubusercontent.com 10 | // @homepage https://github.com/cinder0601/SPXXKLP 11 | // @match https://www.minecraft.net/en-us/article/* 12 | // @match https://www.minecraft.net/zh-hans/article/* 13 | // @match https://x.com/*/status/* 14 | // @match https://feedback.minecraft.net/hc/en-us/articles/* 15 | // @match https://help.minecraft.net/hc/en-us/articles/* 16 | // @require https://fastly.jsdelivr.net/gh/sizzlemctwizzle/GM_config@2207c5c1322ebb56e401f03c2e581719f909762a/gm_config.js 17 | // @icon https://www.minecraft.net/etc.clientlibs/minecraft/clientlibs/main/resources/favicon.ico 18 | // @version 3.2.5 19 | // @grant GM_getValue 20 | // @grant GM_setValue 21 | // @grant GM_setClipboard 22 | // @grant GM_xmlhttpRequest 23 | // @grant GM_registerMenuCommand 24 | // @license MIT 25 | // @downloadURL https://update.greasyfork.org/scripts/491477/SPXXKLP.user.js 26 | // @updateURL https://update.greasyfork.org/scripts/491477/SPXXKLP.meta.js 27 | // ==/UserScript== 28 | (function () { 29 | "use strict"; 30 | 31 | var GM_config = new GM_configStruct(); 32 | GM_config.init({ 33 | id: "spxxklp", 34 | title: "SPXXKLP 用户脚本", 35 | fields: { 36 | translator: { 37 | label: "译者名", 38 | type: "text", 39 | default: "<默认译者>", 40 | }, 41 | bugSource: { 42 | label: "选择翻译源", 43 | type: "select", 44 | options: ["Github", "自定义"], 45 | default: "Github", 46 | }, 47 | bugCenterTranslation: { 48 | label: "漏洞翻译源", 49 | type: "text", 50 | default: 51 | "https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/crowdin/zh-CN/zh_CN.json", 52 | }, 53 | bugCenterTranslator: { 54 | label: "漏洞译者源", 55 | type: "text", 56 | default: 57 | "https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/translator.json", 58 | }, 59 | bugCenterColor: { 60 | label: "漏洞颜色源", 61 | type: "text", 62 | default: 63 | "https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/color.json", 64 | }, 65 | }, 66 | }); 67 | GM_registerMenuCommand("编辑配置", () => GM_config.open()); 68 | const src = GM_config.get("bugSource"); 69 | let tr = ""; 70 | let tor = ""; 71 | let c = ""; 72 | 73 | if (src == "Github") { 74 | console.log("[SPXXKLP] 正在使用 Github 漏洞中心"); 75 | tr = 76 | "https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/crowdin/zh-CN/zh_CN.json"; 77 | tor = 78 | "https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/translator.json"; 79 | c = 80 | "https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/color.json"; 81 | } else if (src == "自定义") { 82 | console.log("[SPXXKLP] 正在使用自定义漏洞中心"); 83 | tr = GM_config.get("bugCenterTranslation"); 84 | tor = GM_config.get("bugCenterTranslator"); 85 | c = GM_config.get("bugCenterColor"); 86 | } 87 | 88 | const config = { 89 | translator: GM_config.get("translator"), 90 | bugCenter: { 91 | translation: tr, 92 | translator: tor, 93 | color: c, 94 | }, 95 | }; 96 | 97 | var version = "3.2.5"; 98 | 99 | function getVersionType(url) { 100 | const lowerUrl = url.toLowerCase(); 101 | if (lowerUrl.includes("snapshot")) { 102 | return VersionType.Snapshot; 103 | } else if (lowerUrl.includes("pre-release")) { 104 | return VersionType.PreRelease; 105 | } else if (lowerUrl.includes("release-candidate")) { 106 | return VersionType.ReleaseCandidate; 107 | } else if ( 108 | lowerUrl.includes("minecraft-java-edition") && 109 | !lowerUrl.includes("snapshot") 110 | ) { 111 | return VersionType.Release; 112 | } else if ( 113 | lowerUrl.includes("minecraft-preview") || 114 | lowerUrl.includes("minecraft-beta-preview") || 115 | lowerUrl.includes("minecraft-beta") 116 | ) { 117 | return VersionType.BedrockBeta; 118 | } else if (lowerUrl.includes("bedrock")) { 119 | return VersionType.BedrockRelease; 120 | } else { 121 | return VersionType.Normal; 122 | } 123 | } 124 | 125 | const bugsCenter = config.bugCenter.translation; 126 | const bugsTranslatorsTable = config.bugCenter.translator; 127 | const translatorColorTable = config.bugCenter.color; 128 | const spxxklpVersion = version; 129 | const url1 = window.location.href; 130 | 131 | function getReleaseVersionCode(url) { 132 | const lowerUrl = url.toLowerCase(); 133 | if (lowerUrl.includes("pre-release")) { 134 | const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-release/; 135 | const match = lowerUrl.match(versionRegex); 136 | if (match && match.length >= 3) { 137 | const Version1 = match[1]; 138 | const Version2 = match[2]; 139 | const Version3 = match[3]; 140 | if (Version3 == "") { 141 | const formattedVersion = `${Version1}.${Version2}`; 142 | return formattedVersion; 143 | } else { 144 | const formattedVersion = `${Version1}.${Version2}.${Version3}`; 145 | return formattedVersion; 146 | } 147 | } 148 | } else if (lowerUrl.includes("release-candidate")) { 149 | const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-candidate/; 150 | const match = lowerUrl.match(versionRegex); 151 | if (match && match.length >= 3) { 152 | const Version1 = match[1]; 153 | const Version2 = match[2]; 154 | const Version3 = match[3]; 155 | if (Version3 == "") { 156 | const formattedVersion = `${Version1}.${Version2}`; 157 | return formattedVersion; 158 | } else { 159 | const formattedVersion = `${Version1}.${Version2}.${Version3}`; 160 | return formattedVersion; 161 | } 162 | } 163 | } 164 | } 165 | 166 | function getVersionCode(url) { 167 | const lowerUrl = url.toLowerCase(); 168 | if (lowerUrl.includes("snapshot")) { 169 | const versionRegex = /-([0-9a-zA-Z]+)$/; 170 | const match = url.match(versionRegex); 171 | if (match && match[1]) { 172 | return match[1]; 173 | } 174 | } else if (lowerUrl.includes("pre-release")) { 175 | const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-release-(\d+)/; 176 | const match = lowerUrl.match(versionRegex); 177 | if (match && match.length >= 4) { 178 | const Version1 = match[1]; 179 | const Version2 = match[2]; 180 | const Version3 = match[3]; 181 | const Version4 = match[5]; 182 | if (Version3 == "") { 183 | const formattedVersion = `${Version1}.${Version2}-pre${Version4}`; 184 | return formattedVersion; 185 | } else { 186 | const formattedVersion = `${Version1}.${Version2}.${Version3}-pre${Version4}`; 187 | return formattedVersion; 188 | } 189 | } 190 | } else if (lowerUrl.includes("release-candidate")) { 191 | const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-candidate-(\d+)/; 192 | const match = lowerUrl.match(versionRegex); 193 | if (match && match.length >= 4) { 194 | const Version1 = match[1]; 195 | const Version2 = match[2]; 196 | const Version3 = match[3]; 197 | const Version4 = match[5]; 198 | if (Version3 == "") { 199 | const formattedVersion = `${Version1}.${Version2}-rc${Version4}`; 200 | return formattedVersion; 201 | } else { 202 | const formattedVersion = `${Version1}.${Version2}.${Version3}-rc${Version4}`; 203 | return formattedVersion; 204 | } 205 | } 206 | } else if (lowerUrl.includes("minecraft-beta-preview")) { 207 | const versionRegex = /-beta-preview-(\d+)-(\d+)-(\d+)-(\d+)/; 208 | const match = lowerUrl.match(versionRegex); 209 | if (match && match.length >= 5) { 210 | const Version1 = match[1]; 211 | const Version2 = match[2]; 212 | const Version3 = match[3]; 213 | const Version4 = match[4]; 214 | const formattedVersion = `${Version1}.${Version2}.${Version3}.${Version4}`; 215 | return formattedVersion; 216 | } 217 | } else if ( 218 | lowerUrl.includes("minecraft-preview") && 219 | !lowerUrl.includes("beta") 220 | ) { 221 | const versionRegex = /-preview-(\d+)-(\d+)-(\d+)-(\d+)/; 222 | const match = lowerUrl.match(versionRegex); 223 | if (match && match.length >= 5) { 224 | const Version1 = match[1]; 225 | const Version2 = match[2]; 226 | const Version3 = match[3]; 227 | const Version4 = match[4]; 228 | const formattedVersion = `${Version1}.${Version2}.${Version3}.${Version4}`; 229 | return formattedVersion; 230 | } 231 | } else if ( 232 | lowerUrl.includes("minecraft-beta") && 233 | !lowerUrl.includes("preview") 234 | ) { 235 | const versionRegex = /-beta-(\d+)-(\d+)-(\d+)-(\d+)/; 236 | const match = lowerUrl.match(versionRegex); 237 | if (match && match.length >= 5) { 238 | const Version1 = match[1]; 239 | const Version2 = match[2]; 240 | const Version3 = match[3]; 241 | const Version4 = match[4]; 242 | const formattedVersion = `${Version1}.${Version2}.${Version3}.${Version4}`; 243 | return formattedVersion; 244 | } 245 | } 246 | } 247 | 248 | function getVersionCount(url) { 249 | const lowerUrl = url.toLowerCase(); 250 | if (lowerUrl.includes("pre-release")) { 251 | const versionRegex = /-release-(\d+)/; 252 | const match = lowerUrl.match(versionRegex); 253 | if (match && match[1]) { 254 | return match[1]; 255 | } 256 | } else if (lowerUrl.includes("release-candidate")) { 257 | const versionRegex = /-candidate-(\d+)/; 258 | const match = lowerUrl.match(versionRegex); 259 | if (match && match[1]) { 260 | return match[1]; 261 | } 262 | } 263 | } 264 | 265 | const link = document.createElement("link"); 266 | link.rel = "stylesheet"; 267 | link.href = 268 | "https://www.minecraft.net/etc.clientlibs/minecraftnet/clientlibs/clientlib-site/resources/fonts/MinecraftTen.woff"; 269 | document.head.appendChild(link); 270 | 271 | let releaseversioncode = getReleaseVersionCode(url1); 272 | let versioncode = getVersionCode(url1); 273 | let versioncount = getVersionCount(url1); 274 | function getHeader(articleType, type) { 275 | if (articleType.toLowerCase() !== "news") { 276 | return `[color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 277 | } 278 | 279 | switch (type) { 280 | case VersionType.Snapshot: 281 | return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size] 282 | [color=#388e3c][size=5]|[/size][/color][size=4][b]每周快照[/b]是 Minecraft Java 版的测试机制,主要用于下一个正式版的特性预览。[/size] 283 | [color=#f44336][size=5]|[/size][/color][size=4]然而,[b]每周快照[/b]主要用于新特性展示,通常存在大量漏洞。因此对于普通玩家建议仅做[color=Red][b]测试尝鲜[/b][/color]用。在快照中打开存档前请务必[color=Red][b]进行备份[/b][/color]。[b]适用于正式版的 Mod 不兼容快照,且大多数 Mod 都不对每周快照提供支持[/b]。 [/size] 284 | [color=#f44336][size=5]|[/size][/color][size=4]Minecraft Java 版 <正式版版本号> 仍未发布,${versioncode} 为其第 <计数> 个快照。[/size] 285 | [color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 286 | 287 | case VersionType.PreRelease: 288 | return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size] 289 | [color=#388e3c][size=5]|[/size][/color][size=4][b]预发布版[/b]是 Minecraft Java 版的测试机制,如果该版本作为正式版发布,那么预发布版的游戏文件将与启动器推送的正式版完全相同。[/size] 290 | [color=#f44336][size=5]|[/size][/color][size=4]然而,预发布版主要用于服主和 Mod 制作者的预先体验,如果发现重大漏洞,该预发布版会被新的预发布版代替。因此建议普通玩家[color=Red]持观望态度[/color]。 [/size] 291 | [color=#f44336][size=5]|[/size][/color][size=4]Minecraft Java 版 ${releaseversioncode} 仍未发布,${versioncode} 为其第 ${versioncount} 个预发布版。[/size] 292 | [color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 293 | 294 | case VersionType.ReleaseCandidate: 295 | return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指运行在 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size] 296 | [color=#388e3c][size=5]|[/size][/color][size=4][b]候选版[/b]是 Minecraft Java 版正式版的候选版本,如果发现重大漏洞,该候选版会被新的候选版代替。如果一切正常,该版本将会作为正式版发布。[/size] 297 | [color=#f44336][size=5]|[/size][/color][size=4]候选版已可供普通玩家进行抢鲜体验,但仍需当心可能存在的漏洞。[/size] 298 | [color=#f44336][size=5]|[/size][/color][size=4]Minecraft Java 版 ${releaseversioncode} 仍未发布,${versioncode} 为其第 ${versioncount} 个候选版。[/size] 299 | [color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 300 | 301 | case VersionType.Release: 302 | return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指运行在 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size] 303 | [color=#f44336][size=5]|[/size][/color][size=4][b]正式版[/b]是 Minecraft Java 版经过一段时间的预览版测试后得到的稳定版本,也是众多纹理、Mod 与服务器插件会逐渐跟进的版本。官方启动器也会第一时间进行推送。 [/size] 304 | [color=#f44336][size=5]|[/size][/color][size=4]建议玩家与服主关注其相关服务端、Mod 与插件的更新,迎接新的正式版吧!专注于单人原版游戏的玩家可立即更新,多人游戏玩家请关注您所在服务器的通知。[/size] 305 | [color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 306 | 307 | case VersionType.BedrockRelease: 308 | return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft 基岩版[/b]是指运行在移动平台(Android、iOS)、Windows 10/11、主机(Xbox One、Switch、PlayStation 4/5)上,使用「基岩引擎」(C++语言)开发的 Minecraft 版本。[/size] 309 | [color=#f44336][size=5]|[/size][/color][size=4][b]正式版[/b]是 Minecraft 基岩版经过一段时间的测试版测试之后得到的稳定版本,也是众多纹理、附加包和 Realms 会逐渐跟进的版本。与此同时 Google Play、Microsoft Store 等官方软件商店也会推送此次更新。 [/size] 310 | [color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 311 | 312 | case VersionType.BedrockBeta: 313 | return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft 基岩版[/b]是指运行在移动平台(Android、iOS)、Windows 10/11、主机(Xbox One、Switch、PlayStation 4/5)上,使用「基岩引擎」(C++语言)开发的 Minecraft 版本。[/size] 314 | [color=#388e3c][size=5]|[/size][/color][size=4][b]测试版[/b]是 Minecraft 基岩版的测试机制,主要用于下一个正式版的特性预览。[/size] 315 | [color=#f44336][size=5]|[/size][/color][size=4][b]然而,测试版主要用于新特性展示,通常存在大量漏洞。因此对于普通玩家建议仅做测试尝鲜用。使用测试版打开存档前请务必备份。适用于正式版的领域服务器与测试版不兼容。[/b] [/size] 316 | [color=#f44336][size=5]|[/size][/color][size=4]如果在测试版中遇到旧版存档无法使用的问题,测试版将允许你将存档上传以供开发团队查找问题。[/size] 317 | [color=#f44336][size=5]|[/size][/color][size=4]Minecraft 基岩版 <正式版版本号> 仍未发布,Beta & Preview ${versioncode} 为其第 <计数> 个测试版。[/size] 318 | [color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 319 | 320 | case VersionType.Normal: 321 | default: 322 | return `[color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n`; 323 | } 324 | } 325 | function getFooter(articleType, type) { 326 | const time = new Date(); 327 | 328 | function padTime(time) { 329 | return time.toString().padStart(2, "0"); 330 | } 331 | 332 | function toHoursAndMinutes(totalMinutes) { 333 | const m = Math.abs(totalMinutes); 334 | const minutes = m % 60; 335 | const hours = Math.floor(m / 60); 336 | return `${totalMinutes < 0 ? "+" : "-"}${padTime(hours)}${padTime( 337 | minutes 338 | )}`; 339 | } 340 | 341 | const poweredBy = `\n[align=center][size=1][color=Silver]Powered by SPXXKLP ${spxxklpVersion} with love 342 | Converted at ${time.getFullYear()}-${ 343 | padTime(time.getMonth() + 1) // why +1 javascript 344 | }-${padTime(time.getDate())} ${padTime(time.getHours())}:${padTime( 345 | time.getMinutes() 346 | )} ${toHoursAndMinutes(time.getTimezoneOffset())}[/color][/size][/align]`; 347 | 348 | /*Same contents,change if necessary.**/ 349 | 350 | switch (type) { 351 | case VersionType.Snapshot: 352 | return `\n${poweredBy}\n[hr][color=#388e3c][size=5]|[/size][/color][size=4][b]想了解更多游戏资讯?[/b][/size][list][*][size=3][url=https://klpbbs.com/forum-2-1.html][color=#388e3c][u]苦力怕论坛 - 游戏资讯版块[/u][/color][/url][/size][/list]`; 353 | 354 | case VersionType.PreRelease: 355 | return `\n${poweredBy}\n[hr][color=#388e3c][size=5]|[/size][/color][size=4][b]想了解更多游戏资讯?[/b][/size][list][*][size=3][url=https://klpbbs.com/forum-2-1.html][color=#388e3c][u]苦力怕论坛 - 游戏资讯版块[/u][/color][/url][/size][/list]`; 356 | 357 | case VersionType.ReleaseCandidate: 358 | return `\n${poweredBy}\n[hr][color=#388e3c][size=5]|[/size][/color][size=4][b]想了解更多游戏资讯?[/b][/size][list][*][size=3][url=https://klpbbs.com/forum-2-1.html][color=#388e3c][u]苦力怕论坛 - 游戏资讯版块[/u][/color][/url][/size][/list]`; 359 | 360 | case VersionType.Release: 361 | return `\n${poweredBy}\n[hr][color=#388e3c][size=5]|[/size][/color][size=4][b]想了解更多游戏资讯?[/b][/size][list][*][size=3][url=https://klpbbs.com/forum-2-1.html][color=#388e3c][u]苦力怕论坛 - 游戏资讯版块[/u][/color][/url][/size][/list]`; 362 | 363 | case VersionType.BedrockRelease: 364 | return `\n${poweredBy}\n[hr][color=#388e3c][size=5]|[/size][/color][size=4][b]想了解更多游戏资讯?[/b][/size][list][*][size=3][url=https://klpbbs.com/forum-2-1.html][color=#388e3c][u]苦力怕论坛 - 游戏资讯版块[/u][/color][/url][/size][/list]`; 365 | 366 | case VersionType.BedrockBeta: 367 | return `\n${poweredBy}\n[hr][color=#388e3c][size=5]|[/size][/color][size=4][b]想了解更多游戏资讯?[/b][/size][list][*][size=3][url=https://klpbbs.com/forum-2-1.html][color=#388e3c][u]苦力怕论坛 - 游戏资讯版块[/u][/color][/url][/size][/list]`; 368 | 369 | case VersionType.Normal: 370 | return `\n${poweredBy}\n[hr][color=#388e3c][size=5]|[/size][/color][size=4][b]想了解更多游戏资讯?[/b][/size][list][*][size=3][url=https://klpbbs.com/forum-2-1.html][color=#388e3c][u]苦力怕论坛 - 游戏资讯版块[/u][/color][/url][/size][/list]`; 371 | } 372 | } 373 | let VersionType; 374 | 375 | (function (VersionType) { 376 | VersionType[(VersionType["Snapshot"] = 1)] = "Snapshot"; 377 | VersionType[(VersionType["PreRelease"] = 2)] = "PreRelease"; 378 | VersionType[(VersionType["ReleaseCandidate"] = 3)] = "ReleaseCandidate"; 379 | VersionType[(VersionType["Release"] = 4)] = "Release"; 380 | VersionType[(VersionType["Normal"] = 5)] = "Normal"; 381 | VersionType[(VersionType["BedrockBeta"] = 6)] = "BedrockBeta"; 382 | VersionType[(VersionType["BedrockRelease"] = 7)] = "BedrockRelease"; 383 | })(VersionType || (VersionType = {})); 384 | 385 | const translators = { 386 | headings: (input, ctx) => { 387 | return translator(input, ctx, [ 388 | [/Block of the Week: /gi, "本周方块:"], 389 | [/Taking Inventory: /gi, "背包盘点:"], 390 | [/Around the Block: /gi, "群系漫游:"], 391 | [/A Minecraft Java Snapshot/gi, "Minecraft Java版 快照"], 392 | [/A Minecraft Java Pre-Release/gi, "Minecraft Java版 预发布版"], 393 | [/A Minecraft Java Release Candidate/gi, "Minecraft Java版 候选版本"], 394 | [/Minecraft Beta (?:-|——) (.*?) \((.*?)\)/gi, "Minecraft 基岩版 Beta $1($2)"], 395 | [/Minecraft Beta & Preview - (.*?)/g, "Minecraft 基岩版 Beta & Preview $1"], 396 | [/Minecraft (?:-|——) (.*?) \(Bedrock\)/gi, "Minecraft 基岩版 $1"], 397 | [/Minecraft (?:-|——) (.*?) \((.*?) Only\)/gi, "Minecraft 基岩版 $1(仅$2)"], 398 | [/Minecraft (?:-|——) (.*?) \((.*?)\)/gi, "Minecraft 基岩版 $1(仅$2)"], 399 | [/Marketplace/gi, "市场"], 400 | [/Data-Driven/gi, "数据驱动"], 401 | [/Graphical/gi, "图像"], 402 | [/Vanilla /gi, "原版"], 403 | [/Player/gi, "玩家"], 404 | [/Experimental /gi, "实验性"], 405 | [/Mobs/gi, "生物"], 406 | [/Features and Bug Fixes/gi, "特性和漏洞修复"], 407 | [/ADVANCEMENTS/gi, "进度"], 408 | [/Accessibility/gi, "辅助功能"], 409 | [/Gameplay/gi, "玩法"], 410 | [/Items/gi, "物品"], 411 | [/Blocks/gi, "方块"], 412 | [/User Interface/gi, "用户界面"], 413 | [/Commands/gi, "命令"], 414 | [/Known Issues/gi, "已知问题"], 415 | [/Character Creator/gi, "角色创建器"], 416 | [/ Components/gi, "组件"], 417 | [/General/gi, "通用"], 418 | [/Technical Experimental Updates/gi, "实验性技术性更新"], 419 | [/Gametest Framework/gi, "Gametest 框架"], 420 | [/Gametest Framework (experimental)/gi, "Gametest 框架(实验性)"], 421 | [/Minecraft Snapshot /gi, "Minecraft 快照 "], 422 | [/ Pre-Release /gi, "-pre"], 423 | [/ Release Candidate /gi, "-rc"], 424 | [/Get the Release Candidate/gi, "获取预发布版本"], 425 | [/Get the Release/gi, "获取正式版"], 426 | [/Get the Pre-Release/gi, "获取候选版本"], 427 | [/Get the Snapshot/gi, "获取快照版本"], 428 | [/New Features in ([^\r\n]+)/gi, "$1 的新增特性"], 429 | [/Technical changes in ([^\r\n]+)/gi, "$1 的技术性修改"], 430 | [/Changes in ([^\r\n]+)/gi, "$1 的修改内容"], 431 | [/Fixed bugs in ([^\r\n]+)/gi, "$1 修复的漏洞"], 432 | [/STABILITY AND PERFORMANCE/gi, "性能与稳定性"], 433 | [/FEATURES AND BUG FIXES/gi, "特性和漏洞修复"], 434 | [/LOOT/gi, "战利品"], 435 | [/PARITY/gi, "趋同"], 436 | [/Components/gi, "组件"], 437 | [/ADD-ONS AND SCRIPT ENGINE/gi, "附加包和脚本引擎"], 438 | [/DRESSING ROOM/gi, "更衣室"], 439 | [/Item/gi, "物品"], 440 | [/CHANGES/gi, "改动"], 441 | [/SOUNDS/gi, "音效"], 442 | [/DATA PACK VERSION/gi, "数据包版本"], 443 | [/PREDICATES/gi, "谓词"], 444 | [/ PREDICATE/gi, "谓词"], 445 | [/EFFECT/gi, "效果"], 446 | [/COMMAND/gi, "命令"], 447 | [/ATTRIBUTE/gi, "属性"], 448 | [/BLOCK/gi, "方块"], 449 | [/ENTITY/gi, "实体"], 450 | [/ENCHANTMENTS/gi, "附魔"], 451 | [/ TAGS/gi, "标签"], 452 | [/TAGS/gi, "标签"], 453 | [/TYPE/gi, "类型"], 454 | [/MUSIC/gi, "音乐"], 455 | [/GAME TIPS/gi, "游戏提示"], 456 | [/NEW FEATURES/gi, "新特性"], 457 | [/NEW /gi, "新的"], 458 | [/USER INTERFACE/gi, "用户界面"], 459 | [/EDITOR/gi, "编辑器"], 460 | [/FIXES/gi, "修复"], 461 | [/IMPROVEMENTS/gi, "改进"], 462 | [/RESOURCE PACK VERSION/gi, "资源包版本"], 463 | [/SHADERS/gi, "着色器"], 464 | [/PARTICLES/gi, "粒子效果"], 465 | [/TOUCH CONTROLS/gi, "触控"], 466 | [/TECHNICAL UPDATES/gi, "技术性更新"], 467 | [/ TABLES/gi, "表"], 468 | [/PROJECTILES/gi, "弹射物"], 469 | [/STRUCTURES/gi, "结构"], 470 | [/ENTITIES/gi, "实体"], 471 | [/FUNCTIONS/gi, "函数"], 472 | ]); 473 | }, 474 | imgCredits: (input, ctx) => { 475 | return translator(input, ctx, [ 476 | [/Image credit:/gi, "图片来源:"], 477 | [/CC BY-NC-ND/gi, "知识共享 署名-非商业性使用-禁止演绎"], 478 | [/CC BY-NC-SA/gi, "知识共享 署名-非商业性使用-相同方式共享"], 479 | [/CC BY-NC/gi, "知识共享 署名-非商业性使用"], 480 | [/CC BY-ND/gi, "知识共享 署名-禁止演绎"], 481 | [/CC BY-SA/gi, "知识共享 署名-相同方式共享"], 482 | [/CC BY/gi, "知识共享 署名"], 483 | [/Public Domain/gi, "公有领域"], 484 | ]); 485 | }, 486 | punctuation: (input, ctx) => { 487 | return translator( 488 | input, 489 | ctx, 490 | [ 491 | [/\[i]/gi, "[font=楷体]"], 492 | [/\[\/i]/g, "[/font]"], 493 | ...(ctx.disablePunctuationConverter 494 | ? [] 495 | : [ 496 | [/,( |$)/g, ","], 497 | [/!( |$)/g, "!"], 498 | [/\.\.\.( |$)/g, "…"], 499 | [/\.( |$)/g, "。"], 500 | [/\?( |$)/g, "?"], 501 | [/( |^)-( |$)/g, " —— "], 502 | ]), 503 | ], 504 | (input) => { 505 | return quoteTreatment(input, [["“", "”", /"/]]); 506 | } 507 | ); 508 | }, 509 | code: (input, ctx) => { 510 | return quoteTreatment(input, [ 511 | [ 512 | '[backcolor=#f1edec][color=Silver][font=SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace][/font][/color][/backcolor]', 513 | "`", 514 | /`/, 515 | ], 516 | ]); 517 | }, 518 | }; 519 | function translate(input, ctx, type) { 520 | if (typeof type === "string") { 521 | type = [type]; 522 | } 523 | 524 | for (const t of type) { 525 | input = translators[t](input, ctx); 526 | } 527 | 528 | return input; 529 | } 530 | 531 | function quoteTreatment(input, quoteArrays) { 532 | for (const quoteArray of quoteArrays) { 533 | const split = input.split(quoteArray[2]); 534 | input = ""; 535 | 536 | for (let i = 0; i < split.length - 1; i++) { 537 | const element = split[i]; 538 | input += element + quoteArray[i % 2]; 539 | } 540 | 541 | input += split[split.length - 1]; 542 | } 543 | 544 | return input; 545 | } 546 | 547 | function translator(input, ctx, mappings, treatment = (input) => input) { 548 | // REPLACE!!!!1 549 | for (const mapping of mappings) { 550 | input = input.replace(mapping[0], mapping[1]); 551 | } 552 | 553 | treatment(input, ctx); 554 | return input; 555 | } 556 | 557 | const converters = { 558 | /** 559 | * Converts a ChildNode to a BBCode string according to the type of the node. 560 | */ 561 | convert: async (node, ctx) => { 562 | if (node.classList?.contains("spxxklp-userscript-ignored")) { 563 | return ""; 564 | } // Listing all possible elements in the document 565 | 566 | switch (node.nodeName) { 567 | case "A": 568 | return converters.a(node, ctx); 569 | 570 | case "B": 571 | case "STRONG": 572 | return converters.strong(node, ctx); 573 | 574 | case "BLOCKQUOTE": 575 | return converters.blockquote(node, ctx); 576 | 577 | case "BR": 578 | return converters.br(); 579 | 580 | case "CITE": 581 | return converters.cite(node, ctx); 582 | 583 | case "CODE": 584 | return converters.code(node, ctx); 585 | 586 | case "DIV": 587 | case "SECTION": 588 | return converters.div(node, ctx); 589 | 590 | case "DD": 591 | return converters.dd(node, ctx); 592 | 593 | case "DL": 594 | return converters.dl(node, ctx); 595 | 596 | case "DT": 597 | return converters.dt(); 598 | 599 | case "EM": 600 | return converters.em(node, ctx); 601 | 602 | case "H1": 603 | return converters.h1(node, ctx); 604 | 605 | case "H2": 606 | return converters.h2(node, ctx); 607 | 608 | case "H3": 609 | return converters.h3(node, ctx); 610 | 611 | case "H4": 612 | return converters.h4(node, ctx); 613 | 614 | case "I": 615 | return converters.i(node, ctx); 616 | 617 | case "IMG": 618 | return converters.img(node); 619 | 620 | case "LI": 621 | return converters.li(node, ctx); 622 | 623 | case "OL": 624 | return converters.ol(node, ctx); 625 | 626 | case "P": 627 | return converters.p(node, ctx); 628 | 629 | case "PICTURE": 630 | return converters.picture(node, ctx); 631 | 632 | case "PRE": 633 | return converters.pre(node, ctx); 634 | 635 | case "SPAN": 636 | return converters.span(node, ctx); 637 | 638 | case "TABLE": 639 | return converters.table(node, ctx); 640 | 641 | case "TBODY": 642 | return converters.tbody(node, ctx); 643 | 644 | case "TH": 645 | case "TD": 646 | return converters.td(node, ctx); 647 | 648 | case "TR": 649 | return converters.tr(node, ctx); 650 | 651 | case "UL": 652 | return converters.ul(node, ctx); 653 | 654 | case "#text": 655 | if (node) { 656 | if (ctx.multiLineCode) { 657 | return node.textContent ? node.textContent : ""; 658 | } else 659 | return node.textContent 660 | .replace(/[\n\r\t]+/g, "") 661 | .replace(/\s{2,}/g, ""); 662 | } else { 663 | return ""; 664 | } 665 | 666 | case "H5": 667 | return converters.h5(node, ctx); 668 | 669 | case "BUTTON": 670 | case "NAV": 671 | case "svg": 672 | case "SCRIPT": 673 | if (node) { 674 | return node.textContent ? node.textContent : ""; 675 | } else { 676 | return ""; 677 | } 678 | case "FIGURE": 679 | return converters.figure(node, ctx); 680 | 681 | default: 682 | console.warn(`Unknown type: '${node.nodeName}'.`); 683 | 684 | if (node) { 685 | return node.textContent ? node.textContent : ""; 686 | } else { 687 | return ""; 688 | } 689 | } 690 | }, 691 | 692 | /** 693 | * Convert child nodes of an HTMLElement to a BBCode string. 694 | */ 695 | recurse: async (ele, ctx) => { 696 | let ans = ""; 697 | 698 | if (!ele) { 699 | return ans; 700 | } 701 | 702 | for (const child of Array.from(ele.childNodes)) { 703 | ans += await converters.convert(child, ctx); 704 | } 705 | 706 | return ans; 707 | }, 708 | a: async (anchor, ctx) => { 709 | const url = resolveUrl(anchor.href); 710 | let ans; 711 | 712 | if (url) { 713 | ans = `[url=${url}][color=#388d40][u]${await converters.recurse( 714 | anchor, 715 | ctx 716 | )}[/u][/color][/url]`; 717 | } else { 718 | ans = await converters.recurse(anchor, ctx); 719 | } 720 | 721 | return ans; 722 | }, 723 | blockquote: async (ele, ctx) => { 724 | const prefix = ""; 725 | const suffix = ""; 726 | const ans = `${prefix}${await converters.recurse(ele, ctx)}${suffix}`; 727 | return ans; 728 | }, 729 | br: async () => { 730 | const ans = "\n"; 731 | return ans; 732 | }, 733 | cite: async (ele, ctx) => { 734 | const prefix = "—— "; 735 | const suffix = ""; 736 | const ans = `${prefix}${await converters.recurse(ele, ctx)}${suffix}`; 737 | return ans; 738 | }, 739 | code: async (ele, ctx) => { 740 | if (!ele || !await converters.recurse(ele, { 741 | ...ctx, 742 | disablePunctuationConverter: true, 743 | })) { 744 | return ''; 745 | } 746 | const prefix = ctx.multiLineCode 747 | ? "[code]" 748 | : "[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]"; 749 | const suffix = ctx.multiLineCode 750 | ? "[/code]" 751 | : "[/font][/color][/backcolor]"; 752 | const ans = `${prefix}${await converters.recurse(ele, { 753 | ...ctx, 754 | disablePunctuationConverter: true, 755 | })}${suffix}`; 756 | return ans; 757 | }, 758 | div: async (ele, ctx) => { 759 | let ans = await converters.recurse(ele, ctx); 760 | 761 | if (ele.classList.contains("text-center")) { 762 | ans = `[align=center]${ans}[/align]\n`; 763 | } else if (ele.classList.contains("article-image-carousel")) { 764 | /*const prefix = `[/indent][/indent][album]\n`; 765 | const suffix = `\n[/album][indent][indent]\n`;*/ 766 | 767 | const slides = []; 768 | const findSlides = async (ele) => { 769 | if (ele.classList.contains("slick-cloned")) { 770 | return; 771 | } 772 | 773 | if ( 774 | ele.nodeName === "IMG" && 775 | ele.classList.contains("article-image-carousel__image") 776 | ) { 777 | slides.push([resolveUrl(ele.src), " "]); 778 | } else if ( 779 | ele.nodeName === "DIV" && 780 | ele.classList.contains("article-image-carousel__caption") 781 | ) { 782 | if (slides.length > 0) { 783 | slides[slides.length - 1][1] = `[b]${await converters.recurse( 784 | ele, 785 | ctx 786 | )}[/b]`; 787 | } 788 | } else { 789 | for (const child of Array.from(ele.childNodes)) { 790 | if (child.nodeName === "DIV" || child.nodeName === "IMG") { 791 | await findSlides(child); 792 | } 793 | } 794 | } 795 | }; 796 | 797 | await findSlides(ele); 798 | 799 | if (slides.length > 0) { 800 | ans = `[align=center]${slides 801 | .map(([url, caption]) => `[img]${url}[/img]\n${caption}`) 802 | .join("\n")}[/align]\n`; 803 | } else { 804 | ans = ""; 805 | } 806 | } else if (ele.classList.contains("video")) { 807 | ans = "\n[align=center]<无法获取的视频,如有可用视频源,请在此处插入>\n<对于B站视频,可使用 [bilibili] 代码>[/align]\n"; 808 | } else if ( 809 | ele.classList.contains("quote") || 810 | ele.classList.contains("attributed-quote") 811 | ) { 812 | ans = `\n[quote]\n${ans}\n[/quote]\n`; 813 | } else if (ele.classList.contains("article-social")) { 814 | ans = ""; 815 | } else if (ele.classList.contains("modal")) { 816 | ans = ""; 817 | } 818 | 819 | return ans; 820 | }, 821 | dt: async () => { 822 | // const ans = `${converters.rescure(ele)}:` 823 | // return ans 824 | return ""; 825 | }, 826 | dl: async (ele, ctx) => { 827 | const ans = `\n\n${await converters.recurse( 828 | ele, 829 | ctx 830 | )}\n【本文排版借助了:[url=https://github.com/cinder0601/SPXXKLP][color=#388d40][u]SPXXKLP[/u][/color][/url] 用户脚本 v${spxxklpVersion}】\n\n`; 831 | return ans; 832 | }, 833 | dd: async (ele, ctx) => { 834 | let ans = ""; 835 | 836 | if (ele.classList.contains("pubDate")) { 837 | // Published: 838 | // `pubDate` is like '2019-03-08T10:00:00.876+0000'. 839 | const date = ele.attributes.getNamedItem("data-value"); 840 | 841 | if (date) { 842 | ans = `[b]【${ctx.translator} 译自[url=${ 843 | ctx.url 844 | }][color=#388d40][u]官网 ${date.value.slice( 845 | 0, 846 | 4 847 | )} 年 ${date.value.slice(5, 7)} 月 ${date.value.slice( 848 | 8, 849 | 10 850 | )} 日发布的 ${ctx.title}[/u][/color][/url];原作者 ${ 851 | ctx.author 852 | }】[/b]`; 853 | } else { 854 | ans = `[b]【${ctx.translator} 译自[url=${ctx.url}][color=#388d40][u]官网 * 年 * 月 * 日发布的 ${ctx.title}[/u][/color][/url]】[/b]`; 855 | } 856 | } else { 857 | // Written by: 858 | ctx.author = await converters.recurse(ele, ctx); 859 | } 860 | 861 | return ans; 862 | }, 863 | em: async (ele, ctx) => { 864 | const ans = `[i]${await converters.recurse(ele, ctx)}[/i]`; 865 | return ans; 866 | }, 867 | h1: async (ele, ctx) => { 868 | const prefix = "[size=6][b]"; 869 | const suffix = "[/b][/size]"; 870 | const processNode = async (node) => { 871 | if (node.nodeType === Node.ELEMENT_NODE) { 872 | return await converters[node.tagName.toLowerCase()](node, ctx); 873 | } else if (node.nodeType === Node.TEXT_NODE) { 874 | return node.nodeValue; 875 | } 876 | }; 877 | const rawInnerArray = await Promise.all( 878 | Array.from(ele.childNodes).map(processNode) 879 | ); 880 | const rawInner = rawInnerArray.join(""); 881 | const inner = makeUppercaseHeader(rawInner); 882 | const ans = `${prefix}[color=Silver]${usingSilver(inner).replace( 883 | /[\n\r]+/g, 884 | " " 885 | )}[/color]${suffix}\n${prefix}${translate(`${inner}`, ctx, [ 886 | "headings", 887 | "punctuation", 888 | ]).replace(/[\n\r]+/g, " ")}${suffix}\n\n`; 889 | return ans; 890 | }, 891 | h2: async (ele, ctx) => { 892 | if (isBlocklisted(ele.textContent)) return ""; 893 | const prefix = "[size=5][b]"; 894 | const suffix = "[/b][/size]"; 895 | const processNode = async (node) => { 896 | if (node.nodeType === Node.ELEMENT_NODE) { 897 | return await converters[node.tagName.toLowerCase()](node, ctx); 898 | } else if (node.nodeType === Node.TEXT_NODE) { 899 | return node.nodeValue; 900 | } 901 | }; 902 | const rawInnerArray = await Promise.all( 903 | Array.from(ele.childNodes).map(processNode) 904 | ); 905 | const rawInner = rawInnerArray.join(""); 906 | const inner = makeUppercaseHeader(rawInner); 907 | const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace( 908 | /[\n\r]+/g, 909 | " " 910 | )}[/color]${suffix}\n${prefix}${translate(`${inner}`, ctx, [ 911 | "headings", 912 | "punctuation", 913 | ]).replace(/[\n\r]+/g, " ")}${suffix}\n\n`; 914 | return ans; 915 | }, 916 | h3: async (ele, ctx) => { 917 | const prefix = "[size=4][b]"; 918 | const suffix = "[/b][/size]"; 919 | const processNode = async (node) => { 920 | if (node.nodeType === Node.ELEMENT_NODE) { 921 | return await converters[node.tagName.toLowerCase()](node, ctx); 922 | } else if (node.nodeType === Node.TEXT_NODE) { 923 | return node.nodeValue; 924 | } 925 | }; 926 | const rawInnerArray = await Promise.all( 927 | Array.from(ele.childNodes).map(processNode) 928 | ); 929 | const rawInner = rawInnerArray.join(""); 930 | const inner = makeUppercaseHeader(rawInner); 931 | const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace( 932 | /[\n\r]+/g, 933 | " " 934 | )}[/color]${suffix}\n${prefix}${translate(`${inner}`, ctx, [ 935 | "headings", 936 | "punctuation", 937 | ]).replace(/[\n\r]+/g, " ")}${suffix}\n\n`; 938 | return ans; 939 | }, 940 | h4: async (ele, ctx) => { 941 | const prefix = "[size=3][b]"; 942 | const suffix = "[/b][/size]"; 943 | const processNode = async (node) => { 944 | if (node.nodeType === Node.ELEMENT_NODE) { 945 | return await converters[node.tagName.toLowerCase()](node, ctx); 946 | } else if (node.nodeType === Node.TEXT_NODE) { 947 | return node.nodeValue; 948 | } 949 | }; 950 | const rawInnerArray = await Promise.all( 951 | Array.from(ele.childNodes).map(processNode) 952 | ); 953 | const rawInner = rawInnerArray.join(""); 954 | const inner = makeUppercaseHeader(rawInner); 955 | const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace( 956 | /[\n\r]+/g, 957 | " " 958 | )}[/color]${suffix}\n${prefix}${inner}${suffix}\n\n`; 959 | return ans; 960 | }, 961 | h5: async (ele, ctx) => { 962 | const prefix = "[size=2][b]"; 963 | const suffix = "[/b][/size]"; 964 | const processNode = async (node) => { 965 | if (node.nodeType === Node.ELEMENT_NODE) { 966 | return await converters[node.tagName.toLowerCase()](node, ctx); 967 | } else if (node.nodeType === Node.TEXT_NODE) { 968 | return node.nodeValue; 969 | } 970 | }; 971 | const rawInnerArray = await Promise.all( 972 | Array.from(ele.childNodes).map(processNode) 973 | ); 974 | const rawInner = rawInnerArray.join(""); 975 | const inner = makeUppercaseHeader(rawInner); 976 | const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace( 977 | /[\n\r]+/g, 978 | " " 979 | )}[/color]${suffix}\n${prefix}${inner}${suffix}\n\n`; 980 | return ans; 981 | }, 982 | i: async (ele, ctx) => { 983 | const ans = `[i]${await converters.recurse(ele, ctx)}[/i]`; 984 | return ans; 985 | }, 986 | img: async (img) => { 987 | let host = location.host; 988 | 989 | let w; 990 | let h; 991 | 992 | if (img.classList.contains("attributed-quote__image")) { 993 | // for in-quote avatar image 994 | h = 92; 995 | w = 53; 996 | } else if (img.classList.contains("mr-3")) { 997 | // for attributor avatar image 998 | h = 121; 999 | w = 82; 1000 | } 1001 | 1002 | const prefix = w && h ? `[img=${w},${h}]` : "[img]"; 1003 | const imgUrl = resolveUrl(img.src); 1004 | if (imgUrl === "") return ""; // in case of empty image 1005 | let ans = `[align=center]${prefix}${imgUrl}[/img][/align]\n`; //Left aligning is too ugly. 1006 | return ans; 1007 | }, 1008 | li: async (ele, ctx) => { 1009 | let ans; 1010 | let nestedList = false; 1011 | 1012 | for (const child of ele.childNodes) { 1013 | if (child.nodeName === "OL" || child.nodeName === "UL") { 1014 | nestedList = true; 1015 | } 1016 | } 1017 | 1018 | if (nestedList) { 1019 | // Nested lists. 1020 | let theParagragh = ""; 1021 | let theList = ""; 1022 | let addingList = false; 1023 | 1024 | for (let i = 0; i < ele.childNodes.length - 1; i++) { 1025 | let nodeName = ele.childNodes[i].nodeName; 1026 | 1027 | if (nodeName === "OL" || nodeName === "UL") { 1028 | addingList = true; 1029 | } 1030 | 1031 | if (!addingList) { 1032 | const paragraghNode = await converters.convert(ele.childNodes[i], { 1033 | ...ctx, 1034 | inList: true, 1035 | }); 1036 | theParagragh = `${theParagragh}${paragraghNode}`; 1037 | } else { 1038 | const listNode = await converters.convert(ele.childNodes[i], { 1039 | ...ctx, 1040 | inList: true, 1041 | }); 1042 | theList = `${theList}${listNode}`; 1043 | } 1044 | } 1045 | 1046 | ans = `[*][color=Silver]${usingSilver( 1047 | theParagragh 1048 | )}[/color]\n[*]${translate( 1049 | translateBugs(theParagragh, ctx), 1050 | ctx, 1051 | "code" 1052 | )}\n${theList}`; 1053 | } else if (isBlocklisted(ele.textContent)) { 1054 | return ""; 1055 | } else { 1056 | const inner = await converters.recurse(ele, { ...ctx, inList: true }); 1057 | ans = `[*][color=Silver]${usingSilver(inner)}[/color]\n[*]${translate( 1058 | translateBugs(inner, ctx), 1059 | ctx, 1060 | "code" 1061 | )}\n`; 1062 | } 1063 | 1064 | return ans; 1065 | }, 1066 | ol: async (ele, ctx) => { 1067 | const inner = await converters.recurse(ele, ctx); 1068 | const ans = `[list=1]\n${inner}[/list]\n`; 1069 | return ans; 1070 | }, 1071 | p: async (ele, ctx) => { 1072 | const processNode = async (node) => { 1073 | if (node.nodeType === Node.ELEMENT_NODE) { 1074 | const converter = converters[node.tagName.toLowerCase()]; 1075 | if (converter) { 1076 | return await converter(node, ctx); 1077 | } 1078 | } else if (node.nodeType === Node.TEXT_NODE) { 1079 | return node.nodeValue; 1080 | } 1081 | }; 1082 | 1083 | let inner = await converters.recurse(ele, ctx); 1084 | inner = inner ? inner.trim() : ""; 1085 | 1086 | let ans; 1087 | 1088 | if (inner === "") { 1089 | return ""; 1090 | } 1091 | 1092 | if (ele.style.textAlign === "center") { 1093 | ans = `[align=center][size=2][color=Silver]${usingSilver( 1094 | inner 1095 | )}[/color][/size]\n${translate(inner, ctx, [ 1096 | "punctuation", 1097 | "imgCredits", 1098 | ])}[/align]\n`; 1099 | } else if (ele.classList.contains("lead")) { 1100 | ans = `[b][size=2][color=Silver]${inner}[/color][/size][/b]\n[size=4][b]${translate( 1101 | inner, 1102 | ctx, 1103 | "headings" 1104 | )}[/b][/size]\n`; 1105 | } else if ( 1106 | ele.querySelector("strong") !== null && 1107 | ele.querySelector("strong").textContent === "Posted:" 1108 | ) { 1109 | return ""; 1110 | } else if (isBlocklisted(inner)) { 1111 | return ""; 1112 | } else if (ele.innerHTML.trim() === " ") { 1113 | return ""; 1114 | } else if ( 1115 | /\s{0,}/.test(inner) && 1116 | ele.querySelectorAll("img").length === 1 1117 | ) { 1118 | return inner; 1119 | } else { 1120 | if (ctx.inList) { 1121 | ans = inner; 1122 | } else { 1123 | ans = `[size=2][color=Silver]${usingSilver( 1124 | inner 1125 | )}[/color][/size]\n${translate(inner, ctx, [ 1126 | "punctuation", 1127 | "imgCredits", 1128 | ])}\n\n`; 1129 | } 1130 | } 1131 | 1132 | return ans; 1133 | }, 1134 | picture: async (ele, ctx) => { 1135 | const ans = await converters.recurse(ele, ctx); 1136 | return ans; 1137 | }, 1138 | figure: async (ele, ctx) => { 1139 | const ans = await converters.recurse(ele, ctx); 1140 | return ans; 1141 | }, 1142 | pre: async (ele, ctx) => { 1143 | const ans = await converters.recurse(ele, { 1144 | ...ctx, 1145 | multiLineCode: true, 1146 | }); 1147 | return ans; 1148 | }, 1149 | span: async (ele, ctx) => { 1150 | const ans = await converters.recurse(ele, ctx); 1151 | 1152 | if (ele.classList.contains("MC_Effect_TextHighlightA")) { 1153 | // Special for MC_Effect_TextHighlightA element. 1154 | const textContent = await converters.recurse(ele, ctx); 1155 | const prefix = 1156 | "[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]"; 1157 | const suffix = "[/font][/color][/backcolor]"; 1158 | return `${prefix}${textContent}${suffix}`; 1159 | } else if (ele.classList.contains("MC_Effect_TextHighlightB")) { 1160 | // Special for MC_Effect_TextHighlightB element. 1161 | const textContent = await converters.recurse(ele, ctx); 1162 | const prefix = 1163 | "[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]"; 1164 | const suffix = "[/font][/color][/backcolor]"; 1165 | return `${prefix}${textContent}${suffix}`; 1166 | } else if (ele.classList.contains("bedrock-server")) { 1167 | // Inline code. 1168 | const prefix = 1169 | "[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]"; 1170 | const suffix = "[/font][/color][/backcolor]"; 1171 | return `${prefix}${await converters.recurse(ele, { 1172 | ...ctx, 1173 | disablePunctuationConverter: true, 1174 | })}${suffix}`; 1175 | } else if (ele.classList.contains("strikethrough")) { 1176 | // Strikethrough text. 1177 | const prefix = "[s]"; 1178 | const suffix = "[/s]"; 1179 | return `${prefix}${ans}${suffix}`; 1180 | } else if ( 1181 | ele.childElementCount === 1 && 1182 | ele.firstElementChild.nodeName === "IMG" 1183 | ) { 1184 | // Image. 1185 | const img = ele.firstElementChild; 1186 | return await converters.img(img); 1187 | } 1188 | 1189 | return ans; 1190 | }, 1191 | strong: async (ele, ctx) => { 1192 | const ans = `[b]${await converters.recurse(ele, ctx)}[/b]`; 1193 | return ans; 1194 | }, 1195 | table: async (ele, ctx) => { 1196 | const ans = `\n[table]\n${await converters.recurse(ele, ctx)}[/table]\n`; 1197 | return ans; 1198 | }, 1199 | tbody: async (ele, ctx) => { 1200 | const ans = await converters.recurse(ele, ctx); 1201 | return ans; 1202 | }, 1203 | td: async (ele, ctx) => { 1204 | const ans = `[td]${await converters.recurse(ele, ctx)}[/td]`; 1205 | return ans; 1206 | }, 1207 | tr: async (ele, ctx) => { 1208 | const ans = `[tr]${await converters.recurse(ele, ctx)}[/tr]\n`; 1209 | return ans; 1210 | }, 1211 | ul: async (ele, ctx) => { 1212 | const inner = await converters.recurse(ele, ctx); 1213 | const ans = `[list]\n${inner}[/list]\n`; 1214 | return ans; 1215 | }, 1216 | }; 1217 | /** 1218 | * Resolve relative URLs. 1219 | */ 1220 | 1221 | function resolveUrl(url) { 1222 | if (url[0] === "/") { 1223 | return `https://${location.host}${url}`; 1224 | } else { 1225 | return url; 1226 | } 1227 | } 1228 | function usingSilver(text) { 1229 | return text.replace(/#388d40/g, "Silver").replace(/#7824c5/g, "Silver"); 1230 | } 1231 | function makeUppercaseHeader(header) { 1232 | let retStr = ""; 1233 | let idx = 0; 1234 | let bracket = 0; 1235 | 1236 | for (let i = 0; i < header.length; i++) { 1237 | if (header[i] == "[") { 1238 | if (bracket == 0) { 1239 | retStr = retStr.concat(header.substring(idx, i).toUpperCase()); 1240 | idx = i; 1241 | } 1242 | 1243 | bracket++; 1244 | } else if (header[i] == "]") { 1245 | if (bracket <= 1) { 1246 | retStr = retStr.concat(header.substring(idx, i + 1)); 1247 | idx = i + 1; 1248 | } 1249 | 1250 | bracket = Math.max(0, bracket - 1); 1251 | } 1252 | } 1253 | 1254 | if (bracket > 0) { 1255 | console.error("bracket not closed!"); 1256 | retStr = retStr.concat(header.substring(idx, header.length)); 1257 | } else { 1258 | retStr = retStr.concat( 1259 | header.substring(idx, header.length).toUpperCase() 1260 | ); 1261 | } 1262 | 1263 | return retStr; 1264 | } 1265 | /** 1266 | * Get bugs from BugCenter. 1267 | * Guangyao and GitHub source are down, so I deleted them. 1268 | */ 1269 | 1270 | async function getBugs() { 1271 | return new Promise((rs, rj) => { 1272 | GM_xmlhttpRequest({ 1273 | method: "GET", 1274 | url: bugsCenter, 1275 | fetch: true, 1276 | nocache: true, 1277 | timeout: 7_000, 1278 | onload: (r) => { 1279 | try { 1280 | rs(JSON.parse(r.responseText)); 1281 | } catch (e) { 1282 | rj(e); 1283 | } 1284 | }, 1285 | onabort: () => rj(new Error("Aborted")), 1286 | onerror: (e) => rj(e), 1287 | ontimeout: () => rj(new Error("Time out")), 1288 | }); 1289 | }); 1290 | } 1291 | async function getBugsTranslators() { 1292 | return new Promise((rs, rj) => { 1293 | GM_xmlhttpRequest({ 1294 | method: "GET", 1295 | url: bugsTranslatorsTable, 1296 | fetch: true, 1297 | nocache: true, 1298 | timeout: 7_000, 1299 | onload: (r) => { 1300 | try { 1301 | rs(JSON.parse(r.responseText)); 1302 | } catch (e) { 1303 | rj(e); 1304 | } 1305 | }, 1306 | onabort: () => rj(new Error("Aborted")), 1307 | onerror: (e) => rj(e), 1308 | ontimeout: () => rj(new Error("Time out")), 1309 | }); 1310 | }); 1311 | } 1312 | async function getTranslatorColor() { 1313 | return new Promise((rs, rj) => { 1314 | GM_xmlhttpRequest({ 1315 | method: "GET", 1316 | url: translatorColorTable, 1317 | fetch: true, 1318 | nocache: true, 1319 | timeout: 7_000, 1320 | onload: (r) => { 1321 | try { 1322 | rs(JSON.parse(r.responseText)); 1323 | } catch (e) { 1324 | rj(e); 1325 | } 1326 | }, 1327 | onabort: () => rj(new Error("Aborted")), 1328 | onerror: (e) => rj(e), 1329 | ontimeout: () => rj(new Error("Time out")), 1330 | }); 1331 | }); 1332 | } 1333 | 1334 | function markdownToBbcode(value) { 1335 | return value.replace( 1336 | /`([^`]+)`/g, 1337 | "[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]$1[/font][/color][/backcolor]" 1338 | ); 1339 | } 1340 | /** 1341 | * Replace untranslated bugs. 1342 | */ 1343 | 1344 | function translateBugs(str, ctx) { 1345 | if ( 1346 | str.startsWith("[url=https://bugs.mojang.com/browse/MC-") && 1347 | ctx.bugs != null // nullish 1348 | ) { 1349 | const id = str.slice(36, str.indexOf("]")); 1350 | const data = ctx.bugs[id]; 1351 | 1352 | if (data) { 1353 | let bugColor = "#388d40"; 1354 | 1355 | if (ctx.bugsTranslators[id]) { 1356 | const bugTranslator = ctx.bugsTranslators[id]; 1357 | 1358 | if (ctx.translatorColor[bugTranslator]) { 1359 | bugColor = ctx.translatorColor[bugTranslator]; 1360 | } 1361 | } 1362 | 1363 | const bbcode = markdownToBbcode(data); 1364 | return `[url=https://bugs.mojang.com/browse/${id}][b][color=${bugColor}]${id}[/color][/b][/url]- ${bbcode}`; 1365 | } else { 1366 | return str; 1367 | } 1368 | } else { 1369 | return str; 1370 | } 1371 | } 1372 | /** 1373 | * KLPBBS does NOT support [album], the shouldUseAlbum function is removed temporarily. 1374 | */ 1375 | 1376 | /*function shouldUseAlbum(slides) { 1377 | return slides.length > 1 && slides.every(([_, caption]) => caption === ' ') 1378 | ; 1379 | }*/ 1380 | 1381 | function isBlocklisted(text) { 1382 | const blocklist = [ 1383 | "Information on the Minecraft Preview and Beta:", 1384 | "While the version numbers between Preview and Beta are different, there is no difference in game content", 1385 | "These work-in-progress versions can be unstable and may not be representative of final version quality", 1386 | "Minecraft Preview is available on Xbox, Windows 10/11, and iOS devices. More information can be found at aka.ms/PreviewFAQ", 1387 | "The beta is available on Android (Google Play). To join or leave the beta, see aka.ms/JoinMCBeta for detailed instructions", 1388 | ]; 1389 | return blocklist 1390 | .map((i) => { 1391 | return i.replace(/\p{General_Category=Space_Separator}*/, ""); 1392 | }) 1393 | .some((block) => 1394 | text 1395 | .trim() 1396 | .trim() 1397 | .replace(/\p{General_Category=Space_Separator}*/, "") 1398 | .includes(block) 1399 | ); 1400 | } 1401 | 1402 | async function minecraftNet() { 1403 | const url = document.location.toString(); 1404 | 1405 | if (url.match(/^https:\/\/www\.minecraft\.net\/(?:[a-z-]+)\/article\//)) { 1406 | const authorContainer = document.querySelector( 1407 | ".MC_articleHeroA_attribution_author" 1408 | ); 1409 | const dateElement = authorContainer.querySelector("dd:nth-child(4)"); // 获取发布日期的 dd 元素 1410 | 1411 | const button = document.createElement("button"); 1412 | button.classList.add("spxxklp-userscript-ignored"); 1413 | button.innerText = "复制 BBCode (KLPBBS)"; 1414 | // 按钮样式设置 1415 | button.style.backgroundColor = "#3C8527"; 1416 | button.style.color = "#FFFFFF"; 1417 | button.style.border = "none"; 1418 | button.style.padding = "10px 20px"; 1419 | button.style.borderRadius = "5px"; 1420 | button.style.fontSize = "16px"; 1421 | button.style.cursor = "pointer"; 1422 | button.style.transition = "background-color 0.3s ease"; 1423 | button.style.fontFamily = "MinecraftTen, sans-serif"; 1424 | 1425 | button.style.width = "140px"; 1426 | button.style.height = "70px"; 1427 | button.style.textAlign = "center"; 1428 | button.style.marginLeft = "auto"; 1429 | 1430 | button.onmouseover = () => { 1431 | button.style.backgroundColor = "#52A535"; 1432 | }; 1433 | button.onmouseout = () => { 1434 | button.style.backgroundColor = "#3C8527"; 1435 | }; 1436 | button.onclick = async () => { 1437 | button.innerText = "处理中..."; 1438 | const bbcode = await convertMCArticleToBBCode(document, url); 1439 | GM_setClipboard(bbcode, { 1440 | type: "text", 1441 | mimetype: "text/plain", 1442 | }); 1443 | button.innerText = "已复制!"; 1444 | setTimeout(() => (button.innerText = "复制 BBCode (KLPBBS)"), 5000); 1445 | }; 1446 | const container = document.createElement("div"); 1447 | container.id = "spxxklp-buttons"; 1448 | container.style.display = "flex"; 1449 | container.style.flexDirection = "column"; 1450 | container.style.alignItems = "flex-end"; 1451 | container.style.width = "100%"; 1452 | container.style.padding = "10px"; 1453 | container.style.boxSizing = "border-box"; 1454 | container.append(button); 1455 | // 将按钮插入到日期下方 1456 | dateElement.insertAdjacentElement("afterend", button); 1457 | } 1458 | } 1459 | 1460 | async function convertMCArticleToBBCode( 1461 | html, 1462 | articleUrl, 1463 | translator = config.translator 1464 | ) { 1465 | const articleType = getArticleType(html); 1466 | const versionType = getVersionType(articleUrl); 1467 | let bugs; 1468 | 1469 | try { 1470 | bugs = await getBugs(); 1471 | } catch (e) { 1472 | bugs = {}; 1473 | console.error("[convertMCArticleToBBCode#getBugs]", e); 1474 | } 1475 | 1476 | let bugsTranslators; 1477 | 1478 | try { 1479 | bugsTranslators = await getBugsTranslators(); 1480 | } catch (e) { 1481 | bugsTranslators = {}; 1482 | console.error("[convertMCArticleToBBCode#getBugs]", e); 1483 | } 1484 | 1485 | let translatorColor; 1486 | 1487 | try { 1488 | translatorColor = await getTranslatorColor(); 1489 | } catch (e) { 1490 | translatorColor = {}; 1491 | console.error("[convertMCArticleToBBCode#getBugs]", e); 1492 | } 1493 | 1494 | const header = getHeader(articleType, versionType); 1495 | const heroImage = getHeroImage(html, articleType); 1496 | const maintitle = await getMainTitle(html); 1497 | const subtitle = await getSubTitle(html); 1498 | let content = await getContent(html, { 1499 | bugs, 1500 | bugsTranslators, 1501 | translatorColor, 1502 | title: html.title.split(" | ").slice(0, -1).join(" | "), 1503 | date: null, 1504 | translator, 1505 | url: articleUrl, 1506 | }); 1507 | const footer = getFooter(articleType, versionType); 1508 | const author = await getAuthor(html); 1509 | const ans = `${header}${heroImage}\n[align=center][color=silver][size=6][b]${maintitle}[/b][/size][/color][/align]\n[align=center][size=6][b]${maintitle}[/b][/size][/align]\n[align=center][color=silver][size=2]${subtitle}[/size][/color][/align]\n[align=center][size=2]${subtitle}[/size][/align]\n\n${content}[b]${author}\n\n${footer}`; 1510 | return ans; 1511 | } 1512 | /** 1513 | * Returns the type of the article. 1514 | */ 1515 | 1516 | function getArticleType(html) { 1517 | try { 1518 | const type = 1519 | html.getElementsByClassName("MC_articleHeroA_category")?.[0] 1520 | ?.textContent ?? ""; 1521 | return type.toUpperCase(); 1522 | } catch (e) { 1523 | console.error("[getArticleType]", e); 1524 | } 1525 | 1526 | return "INSIDER"; 1527 | } 1528 | 1529 | /** 1530 | * Get the hero image (head image) of an article as the form of a BBCode string. 1531 | * @param html An HTML Document. 1532 | */ 1533 | 1534 | function getHeroImage(html, articleType) { 1535 | const category = articleType 1536 | ? `\n[backcolor=Black][color=White][font="Noto Sans",sans-serif][b]${articleType}[/b][/font][/color][/backcolor][/align]` 1537 | : ""; 1538 | const img = html.getElementsByClassName("article-head__image")[0]; 1539 | 1540 | if (!img) { 1541 | return `\n[align=center]${category}[/align]\n`; 1542 | } 1543 | 1544 | const src = img.src; 1545 | const ans = `[align=center][img=1200,513]${resolveUrl( 1546 | src 1547 | )}[/img]\n${category}[/align]\n`; 1548 | return ans; 1549 | } 1550 | /** 1551 | * Get the content of an article as the form of a BBCode string. 1552 | * @param html An HTML Document. 1553 | */ 1554 | 1555 | async function getSubTitle(html) { 1556 | let con = html.getElementsByClassName( 1557 | "MC_articleHeroA_header_container" 1558 | )[0]; 1559 | let subtitle = con.getElementsByClassName( 1560 | "MC_articleHeroA_header_subheadline" 1561 | )[0].innerText; 1562 | return subtitle; 1563 | } 1564 | async function getMainTitle(html) { 1565 | let con = html.getElementsByClassName( 1566 | "MC_articleHeroA_header_container" 1567 | )[0]; 1568 | let maintitle = con.getElementsByClassName("MC_Heading_1")[0].innerText; 1569 | return maintitle; 1570 | } 1571 | 1572 | async function getAuthor(html, translator = config.translator) { 1573 | try { 1574 | let rawauthor = html.getElementsByClassName("MC_articleHeroA_attribution_author")[0]; 1575 | if (!rawauthor) { 1576 | console.warn("Author attribution element not found"); 1577 | return "Unknown Author"; 1578 | } 1579 | 1580 | let authorImgUrl = ""; 1581 | let authorImg = rawauthor.getElementsByTagName("img")[0]; 1582 | if (authorImg && authorImg.src) { 1583 | authorImgUrl = authorImg.src; 1584 | } 1585 | 1586 | let authorName = "Unknown"; 1587 | let authorNameElement = rawauthor.getElementsByTagName("dd")[0]; 1588 | if (authorNameElement) { 1589 | authorName = authorNameElement.innerText; 1590 | } 1591 | 1592 | let publishDate = "Unknown Date"; 1593 | let publishDateElement = rawauthor.getElementsByTagName("dd")[1]; 1594 | if (publishDateElement) { 1595 | publishDate = publishDateElement.innerText; 1596 | } 1597 | 1598 | let [a, b, c] = publishDate.split("/"); 1599 | let year, month, day; 1600 | if (a > 12) { 1601 | year = a; 1602 | month = b; 1603 | day = c; 1604 | } else { 1605 | year = "20" + c; 1606 | month = a; 1607 | day = b; 1608 | } 1609 | let url = window.location.href; 1610 | let title = await getMainTitle(html); 1611 | 1612 | let ans = `\n${authorImgUrl ? `[float=left][img]${authorImgUrl}[/img][/float]\n\n\n` : ''}【${translator} 译自[url=${url}][color=#388d40][u]${authorName} ${year} 年 ${month} 月 ${day} 日发布的 ${title}[/u][/color][/url]】[/b]\n【本文排版借助了:[url=https://github.com/cinder0601/SPXXKLP][color=#388d40][u]SPXXKLP[/u][/color][/url] 用户脚本 v${spxxklpVersion}】`; 1613 | return ans; 1614 | } catch (error) { 1615 | console.error("Error in getAuthor function:", error); 1616 | return "Error retrieving author information"; 1617 | } 1618 | } 1619 | 1620 | async function getContent(html, ctx) { 1621 | let results = []; 1622 | let elements = document.querySelectorAll( 1623 | ".MC_articleGridA_container.MC_articleGridA_grid, .MC_Carousel_track_slide.MC_Theme_Vanilla.MC_Carousel_track_slide__active, .MC_Carousel_track_slide.MC_Theme_Vanilla:not(.MC_Carousel_track_slide__copy), .MC_Carousel_track_slide.MC_Theme_Legends.MC_Carousel_track_slide__active, .MC_Carousel_track_slide.MC_Theme_Legends:not(.MC_Carousel_track_slide__copy)" 1624 | ); 1625 | let container = document.createElement("div"); 1626 | 1627 | let seenElements = new Set(); 1628 | 1629 | Array.from(elements).forEach((element) => { 1630 | let identifier = element.querySelector("img")?.src || element.innerHTML; 1631 | if (!seenElements.has(identifier)) { 1632 | seenElements.add(identifier); 1633 | if (element.classList.contains("MC_Carousel_track_slide")) { 1634 | let mediaDiv = element.querySelector(".MC_Carousel_track_slide_media"); 1635 | if (mediaDiv) { 1636 | let clonedDiv = document.createElement("div"); 1637 | clonedDiv.appendChild(mediaDiv.cloneNode(true)); 1638 | container.appendChild(clonedDiv); 1639 | } 1640 | } else { 1641 | container.appendChild(element.cloneNode(true)); 1642 | } 1643 | } 1644 | }); 1645 | 1646 | let containerElements = Array.from(container.children); 1647 | 1648 | for (let i = 0; i < containerElements.length; i++) { 1649 | let rootDiv = containerElements[i]; 1650 | 1651 | let rootDivHTML = rootDiv.outerHTML.replace( 1652 | /(?: |\s)*<\/h[1-5]>/g, 1653 | "" 1654 | ); 1655 | rootDiv = document.createElement("div"); 1656 | rootDiv.innerHTML = rootDivHTML; 1657 | 1658 | let spanElements = rootDiv.querySelectorAll("span"); 1659 | spanElements.forEach((spanElement) => { 1660 | spanElement.innerHTML = spanElement.innerHTML.replace(/\n/g, " "); 1661 | }); 1662 | 1663 | let ans = await converters.recurse(rootDiv, ctx); 1664 | ans = ans 1665 | .replace(/([a-zA-Z0-9\-._])(\[[A-Za-z])/g, "$1 $2") 1666 | .replace(/(\[\/[^\]]+?])([a-zA-Z0-9\-._])/g, "$1 $2"); 1667 | results.push(ans); 1668 | } 1669 | 1670 | return results.join("\n\n"); 1671 | } 1672 | 1673 | function getZendesk(controlDOM, titleSlice, contentClass, versionType) { 1674 | const button = document.createElement("a"); 1675 | button.classList.add("spxxklp-userscript-ignored", "navLink"); 1676 | button.innerText = "复制 BBCode (KLPBBS)"; 1677 | // 按钮样式设置 1678 | button.style.backgroundColor = "#3C8527"; 1679 | button.style.color = "#FFFFFF"; 1680 | button.style.border = "none"; 1681 | button.style.padding = "5px 10px"; 1682 | button.style.borderRadius = "5px"; 1683 | button.style.fontSize = "15px"; 1684 | button.style.cursor = "pointer"; 1685 | button.style.transition = "background-color 0.3s ease"; 1686 | 1687 | button.style.width = "120px"; 1688 | button.style.height = "50px"; 1689 | button.style.textAlign = "center"; 1690 | button.style.marginLeft = "auto"; 1691 | 1692 | button.onmouseover = () => { 1693 | button.style.backgroundColor = "#52A535"; 1694 | }; 1695 | button.onmouseout = () => { 1696 | button.style.backgroundColor = "#3C8527"; 1697 | }; 1698 | button.onclick = async () => { 1699 | button.innerText = "处理中..."; 1700 | const bbcode = await convertZendeskArticleToBBCode( 1701 | document, 1702 | location.href, 1703 | config.translator, 1704 | titleSlice, 1705 | contentClass, 1706 | versionType 1707 | ); 1708 | GM_setClipboard(bbcode, { 1709 | type: "text", 1710 | mimetype: "text/plain", 1711 | }); 1712 | button.innerText = "已复制!"; 1713 | setTimeout(() => (button.innerText = "复制 BBCode (KLPBBS)"), 5_000); 1714 | }; 1715 | const container = document.createElement("div"); 1716 | container.id = "spxxklp-buttons"; 1717 | container.style.display = "flex"; 1718 | container.style.flexDirection = "column"; 1719 | container.style.alignItems = "flex-end"; 1720 | container.style.width = "100%"; 1721 | container.style.padding = "10px"; 1722 | container.style.boxSizing = "border-box"; 1723 | container.append(button); 1724 | 1725 | controlDOM(button); 1726 | } 1727 | 1728 | async function converthelpElementsToBBCode(elements, ctx) { 1729 | let bbcode = ""; 1730 | const seenImages = new Set(); 1731 | 1732 | for (let element of elements) { 1733 | try { 1734 | let converted = await converters.recurse(element, ctx); 1735 | let imgTags = converted.match(/\[img](.*?)\[\/img]/g); 1736 | if (imgTags) { 1737 | for (let imgTag of imgTags) { 1738 | let imgUrl = imgTag.match(/\[img](.*?)\[\/img]/)[1]; 1739 | if (seenImages.has(imgUrl)) { 1740 | converted = converted.replace(imgTag, ""); 1741 | } else { 1742 | seenImages.add(imgUrl); 1743 | } 1744 | } 1745 | } 1746 | 1747 | bbcode += converted + "\n"; 1748 | } catch (error) { 1749 | console.error("Error converting content to BBCode:", error); 1750 | } 1751 | } 1752 | return bbcode; 1753 | } 1754 | 1755 | function getHelpContent(controlDOM) { 1756 | const ctx = { 1757 | multiLineCode: false, 1758 | disablePunctuationConverter: false, 1759 | translator: config.translator, 1760 | url: window.location.href, 1761 | inList: false, 1762 | }; 1763 | const heading = document.getElementsByClassName("article-page-heading"); 1764 | const content = document.getElementsByClassName("article-page-body"); 1765 | 1766 | const button = document.createElement("a"); 1767 | button.classList.add("spxxklp-userscript-ignored", "navLink"); 1768 | button.innerText = "复制 BBCode (KLPBBS)"; 1769 | // 按钮样式设置 1770 | button.style.backgroundColor = "#3C8527"; 1771 | button.style.color = "#FFFFFF"; 1772 | button.style.border = "none"; 1773 | button.style.padding = "5px 10px"; 1774 | button.style.borderRadius = "5px"; 1775 | button.style.fontSize = "15px"; 1776 | button.style.cursor = "pointer"; 1777 | button.style.transition = "background-color 0.3s ease"; 1778 | 1779 | button.style.width = "120px"; 1780 | button.style.height = "50px"; 1781 | button.style.textAlign = "center"; 1782 | button.style.marginLeft = "auto"; 1783 | 1784 | button.onmouseover = () => { 1785 | button.style.backgroundColor = "#52A535"; 1786 | }; 1787 | button.onmouseout = () => { 1788 | button.style.backgroundColor = "#3C8527"; 1789 | }; 1790 | button.onclick = async () => { 1791 | button.innerText = "处理中..."; 1792 | let bbcode = await converthelpElementsToBBCode(heading, ctx); 1793 | let title = bbcode; 1794 | title = title.replace(/\n/g, ""); 1795 | bbcode = `[color=#388e3c][size=5]|[/size][/color][size=4]本文内容按照 [/size][url=https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans][size=4][color=#2e8b57][u]CC BY-NC-SA 4.0[/u][/color][/size][/url][size=4] 协议进行授权,[b]转载本帖时须注明[color=#ff0000]原作者[/color]以及[color=#ff0000]本帖地址[/color][/b]。[/size][hr]\n[size=6][b][color=silver]${bbcode}[/color][/b][/size][size=6][b]${bbcode}[/b][/size]\n`; 1796 | bbcode += await converthelpElementsToBBCode(content, ctx); 1797 | bbcode += `[b]【${ctx.translator} 译自[url=${ctx.url}][color=#388d40][u] help.minecraft.net 上的 ${title}[/u][/color][/url]】[/b]\n【本文排版借助了:[url=https://github.com/cinder0601/SPXXKLP][color=#388d40][u]SPXXKLP[/u][/color][/url] 用户脚本 v${version}】\n`; 1798 | bbcode += getFooter("INSIDER", VersionType.Normal); 1799 | GM_setClipboard(bbcode, { 1800 | type: "text", 1801 | mimetype: "text/plain", 1802 | }); 1803 | button.innerText = "已复制!"; 1804 | setTimeout(() => (button.innerText = "复制 BBCode (KLPBBS)"), 5000); 1805 | }; 1806 | const container = document.createElement("div"); 1807 | container.id = "spxxklp-buttons"; 1808 | container.style.display = "flex"; 1809 | container.style.flexDirection = "column"; 1810 | container.style.alignItems = "flex-end"; 1811 | container.style.width = "100%"; 1812 | container.style.padding = "10px"; 1813 | container.style.boxSizing = "border-box"; 1814 | container.append(button); 1815 | 1816 | controlDOM(button); 1817 | } 1818 | 1819 | async function convertZendeskArticleToBBCode( 1820 | html, 1821 | articleUrl, 1822 | translator = config.translator, 1823 | titleSlice, 1824 | contentClass, 1825 | versionType 1826 | ) { 1827 | const title = html.title.slice(0, html.title.lastIndexOf(titleSlice)); 1828 | const ctx = { 1829 | bugs: {}, 1830 | title: title, 1831 | date: null, 1832 | translator, 1833 | url: articleUrl, 1834 | }; 1835 | const content = await getZendeskContent(html, ctx, contentClass); 1836 | const posted = await getZendeskDate(location.href); 1837 | const header = versionType ? getHeader("news", versionType) : ""; 1838 | const footer = versionType ? getFooter("news", versionType) : ""; 1839 | const ans = `${header}[align=center][size=6][b][color=Silver]${title}[/color][/b][/size] 1840 | ${translate( 1841 | `[size=6][b]${title}[/b][/size]`, 1842 | ctx, 1843 | "headings" 1844 | )}[/align]\n\n${content}\n 1845 | [b]【${ctx.translator} 译自[url=${ctx.url}][color=#388d40][u]${ 1846 | ctx.url.match(/https:\/\/(.*?)\//)[1] 1847 | } ${posted.year} 年 ${posted.month} 月 ${posted.day} 日发布的 ${ 1848 | ctx.title 1849 | }[/u][/color][/url]】[/b] 1850 | 【本文排版借助了:[url=https://github.com/cinder0601/SPXXKLP][color=#388d40][u]SPXXKLP[/u][/color][/url] 用户脚本 v${spxxklpVersion}】\n\n${footer}`; 1851 | return ans; 1852 | } 1853 | 1854 | async function getZendeskContent(html, ctx, contentClass) { 1855 | const rootSection = html.getElementsByClassName(contentClass)[0]; // Yep, this is the only difference. 1856 | 1857 | let ans = await converters.recurse(rootSection, ctx); // Add spaces between texts and '[x'. 1858 | 1859 | ans = ans.replace(/([a-zA-Z0-9\-._])(\[[A-Za-z])/g, "$1 $2"); // Add spaces between '[/x]' and texts. 1860 | 1861 | ans = ans.replace(/(\[\/[^\]]+?])([a-zA-Z0-9\-._])/g, "$1 $2"); 1862 | return ans; 1863 | } 1864 | 1865 | async function getZendeskDate(url) { 1866 | const req = new Promise((rs, rj) => { 1867 | GM_xmlhttpRequest({ 1868 | method: "GET", 1869 | url: 1870 | "/api/v2/help_center/en-us/articles/" + 1871 | url.match(/\/articles\/(\d+)/)[1], 1872 | fetch: true, 1873 | nocache: true, 1874 | timeout: 7_000, 1875 | onload: (r) => { 1876 | try { 1877 | rs(r.responseText); 1878 | } catch (e) { 1879 | rj(e); 1880 | } 1881 | }, 1882 | onabort: () => rj(new Error("Aborted")), 1883 | onerror: (e) => rj(e), 1884 | ontimeout: () => rj(new Error("Time out")), 1885 | }); 1886 | }); 1887 | let res; 1888 | await req.then((value) => { 1889 | const rsp = JSON.parse(value); 1890 | res = new Date(rsp.article.created_at); 1891 | }); 1892 | let year, month, day; 1893 | if (res.getFullYear() > 12) { 1894 | year = res.getFullYear(); 1895 | month = res.getMonth() + 1; 1896 | day = res.getDate(); 1897 | } else { 1898 | year = "20" + res.getDate(); 1899 | month = res.getFullYear(); 1900 | day = res.getMonth() + 1; 1901 | } 1902 | return { 1903 | year, 1904 | month, 1905 | day, 1906 | }; 1907 | } 1908 | 1909 | function feedback() { 1910 | let url = window.location.href; // 获取当前页面的URL 1911 | let versionType = getVersionType(url); // 调用getVersionType函数确定versionType 1912 | 1913 | getZendesk( 1914 | (button) => { 1915 | document.querySelector(".topNavbar nav").append(button); 1916 | }, 1917 | " – Minecraft Feedback", 1918 | "article-info", 1919 | versionType 1920 | ); 1921 | } 1922 | 1923 | function help() { 1924 | getHelpContent((button) => { 1925 | document.querySelector(".mc-globalbanner").append(button); 1926 | }); 1927 | } 1928 | function twitter() { 1929 | const ProfilePictures = new Map([ 1930 | ["Mojang", "https://s2.loli.net/2024/05/27/tMZKe4BmldgDv2P.jpg"], 1931 | ["MojangSupport", "https://s2.loli.net/2024/05/27/tMZKe4BmldgDv2P.jpg"], 1932 | ["MojangStatus", "https://s2.loli.net/2024/05/27/tMZKe4BmldgDv2P.jpg"], 1933 | ["Minecraft", "https://s2.loli.net/2024/05/27/6QsES9CwgKMLv7I.jpg"], 1934 | ["henrikkniberg", "https://s2.loli.net/2024/05/27/h2KGZEBks4XMTFq.png"], 1935 | ["_LadyAgnes", "https://s2.loli.net/2024/05/27/ZoJsth1i8randl9.png"], 1936 | ["kingbdogz", "https://s2.loli.net/2024/05/27/IH7aVTepiDXBZb2.png"], 1937 | ["JasperBoerstra", "https://s2.loli.net/2024/05/27/Jh2doD1eG7A56TR.png"], 1938 | ["adrian_ivl", "https://s2.loli.net/2024/05/27/itAL8hsGqk67cxS.png"], 1939 | ["slicedlime", "https://s2.loli.net/2024/05/27/DY6gQscuX8HiA9e.jpg"], 1940 | ["Cojomax99", "https://s2.loli.net/2024/05/27/DxeZ3rINFgTildA.png"], 1941 | ["Mojang_Ined", "https://s2.loli.net/2024/05/27/dzX3pYy8TSa7uJt.png"], 1942 | ["SeargeDP", "https://s2.loli.net/2024/05/27/nf7EKltTYXLgsxN.png"], 1943 | ["Dinnerbone", "https://s2.loli.net/2024/05/27/Q4ebCE29vFwPmxn.png"], 1944 | ["Marc_IRL", "https://s2.loli.net/2024/05/27/bcW5zXfQ84r9IO6.png"], 1945 | ["Mega_Spud", "https://s2.loli.net/2024/05/27/TZwzsJBRhnLyuFx.png"], 1946 | ["CornerHardMC", "https://s2.loli.net/2024/05/28/o4wLCvuRGi9Yxh7.png"], 1947 | ["MinecraftWikiEN", "https://s2.loli.net/2024/06/23/fn1SeWpdmit86lQ.png"], 1948 | ]); //More pictures can be added manually. 1949 | function getTweetMetadata() { 1950 | const tweetMetadata = { 1951 | date: "", 1952 | source: "", 1953 | text: "", 1954 | rawtext: "", 1955 | tweetLink: "", 1956 | urls: "", 1957 | userName: "", 1958 | userTag: "", 1959 | lang: "", 1960 | }; 1961 | const url = window.location.href; 1962 | const regex = /https:\/\/x\.com\/([^/]+)\/status\/\d+/; 1963 | const match = url.match(regex); 1964 | tweetMetadata.userTag = match[1]; 1965 | let posterNameContent = []; 1966 | const posterNameElements = document 1967 | .querySelector('div[data-testid="User-Name"] a span') 1968 | .querySelectorAll("span, img[alt]"); 1969 | for (const element of posterNameElements) { 1970 | if (element.tagName.toLowerCase() === "span") { 1971 | posterNameContent.push(element.textContent); 1972 | } else if (element.tagName.toLowerCase() === "img" && element.alt) { 1973 | posterNameContent.push(element.alt); 1974 | } 1975 | } 1976 | tweetMetadata.userName = posterNameContent.join(""); 1977 | let texts = []; 1978 | let rawTexts = []; 1979 | 1980 | const articleDivs = document 1981 | .querySelector("article div[lang]") 1982 | .querySelectorAll("a, span, img[alt]"); 1983 | 1984 | for (const element of articleDivs) { 1985 | let textContent = ""; 1986 | let rawContent = ""; 1987 | 1988 | if (element.tagName.toLowerCase() === "a") { 1989 | const url = element.href; 1990 | let linkText = element.textContent.trim(); 1991 | const span = element.querySelector("span"); 1992 | if (span) { 1993 | let spanContent = span.textContent.trim(); 1994 | linkText = linkText.replace(spanContent, "").trim(); 1995 | } 1996 | textContent = `[url=${url}][color=#00bfff][u]${linkText}[/u][/color][/url]`; 1997 | rawContent = linkText; 1998 | } else if (element.tagName.toLowerCase() === "span") { 1999 | if ( 2000 | !element.closest("a") && 2001 | element.querySelectorAll("a").length === 0 2002 | ) { 2003 | textContent = element.innerHTML; 2004 | rawContent = element.textContent; 2005 | } 2006 | } else if (element.tagName.toLowerCase() === "img" && element.alt) { 2007 | textContent = element.alt; 2008 | rawContent = element.alt; 2009 | } 2010 | if (textContent.trim()) { 2011 | texts.push(textContent); 2012 | rawTexts.push(rawContent.replace(/(.*?)<\/a>/g, "$1")); 2013 | } 2014 | } 2015 | tweetMetadata.text = texts.join(""); 2016 | tweetMetadata.rawtext = rawTexts.join(""); 2017 | //I have tried my best but failed, if it still returns 'http://' or 'https://', please add the link manually. 2018 | tweetMetadata.lang = document 2019 | .querySelector("article div[lang]") 2020 | .getAttribute("lang"); 2021 | tweetMetadata.date = document.querySelector("time").innerHTML; 2022 | tweetMetadata.source = document.querySelector( 2023 | 'article a[role="link"] span' 2024 | ).innerText; 2025 | tweetMetadata.tweetLink = window.location.href; 2026 | 2027 | return tweetMetadata; 2028 | } 2029 | 2030 | function getTweetBbcode(tweet, mode) { 2031 | const attributeColor = "#5B7083"; 2032 | const backgroundColor = mode === "dark" ? "#000000" : "#FFFFFF"; 2033 | const foregroundColor = mode === "dark" ? "#D9D9D9" : "#0F1419"; 2034 | const dateString = `${tweet.date} · ${tweet.source} · SPXXKLP v${spxxklpVersion} · 转载请注明原作者及本帖地址`; 2035 | const content = tweet.text; 2036 | const content1 = tweet.rawtext; 2037 | 2038 | return `[align=center][table=560,${backgroundColor}] 2039 | [tr][td][indent][font=-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif] 2040 | [float=left][img=44,44]${ 2041 | ProfilePictures.get(tweet.userTag) || "<不支持的头像,请手动添加图片链接>" 2042 | }[/img][/float][size=15px][b][color=${foregroundColor}]${ 2043 | tweet.userName 2044 | }[/color][/b] 2045 | [color=${attributeColor}]@${tweet.userTag}[/color][/size] 2046 | 2047 | [color=Silver][size=23px]${content1}[/color][/size] 2048 | 2049 | [size=15px][color=${attributeColor}]由 ${GM_config.get("translator")} 翻译自${ 2050 | tweet.lang.startsWith("en") ? "英语" : ` ${tweet.lang}` 2051 | }[/color][/size] 2052 | 2053 | [color=${foregroundColor}][size=23px]${content}[/size] 2054 | [/size][/color][/indent][align=center]<如有配图,请在此处添加>[/align] 2055 | [indent][size=15px][url=${ 2056 | tweet.tweetLink 2057 | }][color=${attributeColor}][u]${dateString}[/u][/color][/url][/size][/indent][/td][/tr] 2058 | [/table][/align]`; 2059 | } 2060 | 2061 | function x() { 2062 | console.info("[SPXXKLP] Activated"); 2063 | 2064 | const buttonLight = document.createElement("button"); 2065 | buttonLight.style.backgroundColor = "rgb(255, 255, 255)"; 2066 | buttonLight.style.color = "#000000"; 2067 | buttonLight.style.border = "none"; 2068 | buttonLight.style.padding = "5px 10px"; 2069 | buttonLight.style.borderRadius = "5px"; 2070 | buttonLight.style.fontSize = "15px"; 2071 | buttonLight.style.cursor = "pointer"; 2072 | buttonLight.style.transition = "background-color 0.3s ease"; 2073 | buttonLight.style.width = "180px"; 2074 | buttonLight.style.height = "50px"; 2075 | buttonLight.style.textAlign = "center"; 2076 | buttonLight.style.marginLeft = "auto"; 2077 | 2078 | buttonLight.onmouseover = () => { 2079 | buttonLight.style.backgroundColor = "rgb(223, 223, 223)"; 2080 | }; 2081 | buttonLight.onmouseout = () => { 2082 | buttonLight.style.backgroundColor = "rgb(255, 255, 255)"; 2083 | }; 2084 | buttonLight.innerText = "复制 BBCode (KLPBBS)(浅色)"; 2085 | buttonLight.onclick = async () => { 2086 | buttonLight.innerText = "处理中..."; 2087 | try { 2088 | const bbcode = getTweetBbcode(getTweetMetadata(), "light"); 2089 | GM_setClipboard(bbcode, { type: "text", mimetype: "text/plain" }); 2090 | buttonLight.innerText = "已复制!"; 2091 | } catch (error) { 2092 | console.error("Error processing BBCode (Light):", error); 2093 | buttonLight.innerText = "错误!"; 2094 | } 2095 | setTimeout(() => (buttonLight.innerText = "复制 BBCode (KLPBBS)(浅色)"), 5000); 2096 | }; 2097 | 2098 | const buttonDark = document.createElement("button"); 2099 | buttonDark.style.backgroundColor = "rgb(32, 32, 32)"; 2100 | buttonDark.style.color = "#FFFFFF"; 2101 | buttonDark.style.border = "none"; 2102 | buttonDark.style.padding = "5px 10px"; 2103 | buttonDark.style.borderRadius = "5px"; 2104 | buttonDark.style.fontSize = "15px"; 2105 | buttonDark.style.cursor = "pointer"; 2106 | buttonDark.style.transition = "background-color 0.3s ease"; 2107 | buttonDark.style.width = "180px"; 2108 | buttonDark.style.height = "50px"; 2109 | buttonDark.style.textAlign = "center"; 2110 | buttonDark.style.marginLeft = "auto"; 2111 | 2112 | buttonDark.onmouseover = () => { 2113 | buttonDark.style.backgroundColor = "rgb(42, 42, 42)"; 2114 | }; 2115 | buttonDark.onmouseout = () => { 2116 | buttonDark.style.backgroundColor = "rgb(32, 32, 32)"; 2117 | }; 2118 | buttonDark.innerText = "复制 BBCode (KLPBBS)(深色)"; 2119 | buttonDark.onclick = async () => { 2120 | buttonDark.innerText = "处理中..."; 2121 | try { 2122 | const bbcode = getTweetBbcode(getTweetMetadata(), "dark"); 2123 | GM_setClipboard(bbcode, { type: "text", mimetype: "text/plain" }); 2124 | buttonDark.innerText = "已复制!"; 2125 | } catch (error) { 2126 | console.error("Error processing BBCode (Dark):", error); 2127 | buttonDark.innerText = "错误!"; 2128 | } 2129 | setTimeout(() => (buttonDark.innerText = "复制 BBCode (KLPBBS)(深色)"), 5000); 2130 | }; 2131 | 2132 | const checkLoaded = setInterval(() => { 2133 | const targetDiv = document.querySelector("article div[lang]"); 2134 | if (targetDiv && !document.querySelector("#spxxklp-buttons")) { 2135 | const container = document.createElement("div"); 2136 | container.id = "spxxklp-buttons"; 2137 | container.style.display = "flex"; 2138 | container.style.flexDirection = "column"; 2139 | container.style.alignItems = "flex-end"; 2140 | container.style.width = "100%"; 2141 | container.style.padding = "10px"; 2142 | container.style.boxSizing = "border-box"; 2143 | container.append(buttonLight); 2144 | container.append(buttonDark); 2145 | targetDiv.parentElement.append(container); 2146 | clearInterval(checkLoaded); 2147 | } 2148 | }, 300); 2149 | } 2150 | 2151 | x(); 2152 | } 2153 | switch (location.host) { 2154 | case "www.minecraft.net": //Fuck minecraft.net what the heck are you doing. 2155 | minecraftNet(); 2156 | break; 2157 | 2158 | case "x.com": 2159 | twitter(); 2160 | break; 2161 | 2162 | case "feedback.minecraft.net": 2163 | feedback(); 2164 | break; 2165 | 2166 | case "help.minecraft.net": 2167 | help(); 2168 | break; 2169 | } 2170 | })(); 2171 | //# sourceMappingURL=bundle.user.js.map 2172 | --------------------------------------------------------------------------------