// ==UserScript==
// @name SPXXKLP
// @description Minecraft.net & X.com blog article to BBCode converter, adapted to KLPBBS
// @namespace npmjs.com/package/@spxxklp/userscript
// @author Cinder & SPGoding & SPX Fellow
// @connect *
// @connect feedback.minecraft.com
// @connect help.minecraft.net
// @connect raw.githubusercontent.com
// @homepage https://github.com/cinder0601/SPXXKLP
// @match https://www.minecraft.net/en-us/article/*
// @match https://www.minecraft.net/zh-hans/article/*
// @match https://x.com/*/status/*
// @match https://feedback.minecraft.net/hc/en-us/articles/*
// @match https://help.minecraft.net/hc/en-us/articles/*
// @require https://fastly.jsdelivr.net/gh/sizzlemctwizzle/GM_config@2207c5c1322ebb56e401f03c2e581719f909762a/gm_config.js
// @icon https://www.minecraft.net/etc.clientlibs/minecraft/clientlibs/main/resources/favicon.ico
// @version 3.2.5
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
(function () {
"use strict";
var GM_config = new GM_configStruct();
GM_config.init({
id: "spxxklp",
title: "SPXXKLP 用户脚本",
fields: {
translator: {
label: "译者名",
type: "text",
default: "<默认译者>",
},
bugSource: {
label: "选择翻译源",
type: "select",
options: ["Github", "自定义"],
default: "Github",
},
bugCenterTranslation: {
label: "漏洞翻译源",
type: "text",
default:
"https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/crowdin/zh-CN/zh_CN.json",
},
bugCenterTranslator: {
label: "漏洞译者源",
type: "text",
default:
"https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/translator.json",
},
bugCenterColor: {
label: "漏洞颜色源",
type: "text",
default:
"https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/color.json",
},
},
});
GM_registerMenuCommand("编辑配置", () => GM_config.open());
const src = GM_config.get("bugSource");
let tr = "";
let tor = "";
let c = "";
if (src == "Github") {
console.log("[SPXXKLP] 正在使用 Github 漏洞中心");
tr =
"https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/crowdin/zh-CN/zh_CN.json";
tor =
"https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/translator.json";
c =
"https://raw.githubusercontent.com/SPXFellow/spxx-translation-database/master/color.json";
} else if (src == "自定义") {
console.log("[SPXXKLP] 正在使用自定义漏洞中心");
tr = GM_config.get("bugCenterTranslation");
tor = GM_config.get("bugCenterTranslator");
c = GM_config.get("bugCenterColor");
}
const config = {
translator: GM_config.get("translator"),
bugCenter: {
translation: tr,
translator: tor,
color: c,
},
};
var version = "3.2.5";
function getVersionType(url) {
const lowerUrl = url.toLowerCase();
if (lowerUrl.includes("snapshot")) {
return VersionType.Snapshot;
} else if (lowerUrl.includes("pre-release")) {
return VersionType.PreRelease;
} else if (lowerUrl.includes("release-candidate")) {
return VersionType.ReleaseCandidate;
} else if (
lowerUrl.includes("minecraft-java-edition") &&
!lowerUrl.includes("snapshot")
) {
return VersionType.Release;
} else if (
lowerUrl.includes("minecraft-preview") ||
lowerUrl.includes("minecraft-beta-preview") ||
lowerUrl.includes("minecraft-beta")
) {
return VersionType.BedrockBeta;
} else if (lowerUrl.includes("bedrock")) {
return VersionType.BedrockRelease;
} else {
return VersionType.Normal;
}
}
const bugsCenter = config.bugCenter.translation;
const bugsTranslatorsTable = config.bugCenter.translator;
const translatorColorTable = config.bugCenter.color;
const spxxklpVersion = version;
const url1 = window.location.href;
function getReleaseVersionCode(url) {
const lowerUrl = url.toLowerCase();
if (lowerUrl.includes("pre-release")) {
const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-release/;
const match = lowerUrl.match(versionRegex);
if (match && match.length >= 3) {
const Version1 = match[1];
const Version2 = match[2];
const Version3 = match[3];
if (Version3 == "") {
const formattedVersion = `${Version1}.${Version2}`;
return formattedVersion;
} else {
const formattedVersion = `${Version1}.${Version2}.${Version3}`;
return formattedVersion;
}
}
} else if (lowerUrl.includes("release-candidate")) {
const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-candidate/;
const match = lowerUrl.match(versionRegex);
if (match && match.length >= 3) {
const Version1 = match[1];
const Version2 = match[2];
const Version3 = match[3];
if (Version3 == "") {
const formattedVersion = `${Version1}.${Version2}`;
return formattedVersion;
} else {
const formattedVersion = `${Version1}.${Version2}.${Version3}`;
return formattedVersion;
}
}
}
}
function getVersionCode(url) {
const lowerUrl = url.toLowerCase();
if (lowerUrl.includes("snapshot")) {
const versionRegex = /-([0-9a-zA-Z]+)$/;
const match = url.match(versionRegex);
if (match && match[1]) {
return match[1];
}
} else if (lowerUrl.includes("pre-release")) {
const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-release-(\d+)/;
const match = lowerUrl.match(versionRegex);
if (match && match.length >= 4) {
const Version1 = match[1];
const Version2 = match[2];
const Version3 = match[3];
const Version4 = match[5];
if (Version3 == "") {
const formattedVersion = `${Version1}.${Version2}-pre${Version4}`;
return formattedVersion;
} else {
const formattedVersion = `${Version1}.${Version2}.${Version3}-pre${Version4}`;
return formattedVersion;
}
}
} else if (lowerUrl.includes("release-candidate")) {
const versionRegex = /(\d+)-(\d+)-(\d*)(.+)-candidate-(\d+)/;
const match = lowerUrl.match(versionRegex);
if (match && match.length >= 4) {
const Version1 = match[1];
const Version2 = match[2];
const Version3 = match[3];
const Version4 = match[5];
if (Version3 == "") {
const formattedVersion = `${Version1}.${Version2}-rc${Version4}`;
return formattedVersion;
} else {
const formattedVersion = `${Version1}.${Version2}.${Version3}-rc${Version4}`;
return formattedVersion;
}
}
} else if (lowerUrl.includes("minecraft-beta-preview")) {
const versionRegex = /-beta-preview-(\d+)-(\d+)-(\d+)-(\d+)/;
const match = lowerUrl.match(versionRegex);
if (match && match.length >= 5) {
const Version1 = match[1];
const Version2 = match[2];
const Version3 = match[3];
const Version4 = match[4];
const formattedVersion = `${Version1}.${Version2}.${Version3}.${Version4}`;
return formattedVersion;
}
} else if (
lowerUrl.includes("minecraft-preview") &&
!lowerUrl.includes("beta")
) {
const versionRegex = /-preview-(\d+)-(\d+)-(\d+)-(\d+)/;
const match = lowerUrl.match(versionRegex);
if (match && match.length >= 5) {
const Version1 = match[1];
const Version2 = match[2];
const Version3 = match[3];
const Version4 = match[4];
const formattedVersion = `${Version1}.${Version2}.${Version3}.${Version4}`;
return formattedVersion;
}
} else if (
lowerUrl.includes("minecraft-beta") &&
!lowerUrl.includes("preview")
) {
const versionRegex = /-beta-(\d+)-(\d+)-(\d+)-(\d+)/;
const match = lowerUrl.match(versionRegex);
if (match && match.length >= 5) {
const Version1 = match[1];
const Version2 = match[2];
const Version3 = match[3];
const Version4 = match[4];
const formattedVersion = `${Version1}.${Version2}.${Version3}.${Version4}`;
return formattedVersion;
}
}
}
function getVersionCount(url) {
const lowerUrl = url.toLowerCase();
if (lowerUrl.includes("pre-release")) {
const versionRegex = /-release-(\d+)/;
const match = lowerUrl.match(versionRegex);
if (match && match[1]) {
return match[1];
}
} else if (lowerUrl.includes("release-candidate")) {
const versionRegex = /-candidate-(\d+)/;
const match = lowerUrl.match(versionRegex);
if (match && match[1]) {
return match[1];
}
}
}
const link = document.createElement("link");
link.rel = "stylesheet";
link.href =
"https://www.minecraft.net/etc.clientlibs/minecraftnet/clientlibs/clientlib-site/resources/fonts/MinecraftTen.woff";
document.head.appendChild(link);
let releaseversioncode = getReleaseVersionCode(url1);
let versioncode = getVersionCode(url1);
let versioncount = getVersionCount(url1);
function getHeader(articleType, type) {
if (articleType.toLowerCase() !== "news") {
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`;
}
switch (type) {
case VersionType.Snapshot:
return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size]
[color=#388e3c][size=5]|[/size][/color][size=4][b]每周快照[/b]是 Minecraft Java 版的测试机制,主要用于下一个正式版的特性预览。[/size]
[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]
[color=#f44336][size=5]|[/size][/color][size=4]Minecraft Java 版 <正式版版本号> 仍未发布,${versioncode} 为其第 <计数> 个快照。[/size]
[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`;
case VersionType.PreRelease:
return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size]
[color=#388e3c][size=5]|[/size][/color][size=4][b]预发布版[/b]是 Minecraft Java 版的测试机制,如果该版本作为正式版发布,那么预发布版的游戏文件将与启动器推送的正式版完全相同。[/size]
[color=#f44336][size=5]|[/size][/color][size=4]然而,预发布版主要用于服主和 Mod 制作者的预先体验,如果发现重大漏洞,该预发布版会被新的预发布版代替。因此建议普通玩家[color=Red]持观望态度[/color]。 [/size]
[color=#f44336][size=5]|[/size][/color][size=4]Minecraft Java 版 ${releaseversioncode} 仍未发布,${versioncode} 为其第 ${versioncount} 个预发布版。[/size]
[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`;
case VersionType.ReleaseCandidate:
return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指运行在 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size]
[color=#388e3c][size=5]|[/size][/color][size=4][b]候选版[/b]是 Minecraft Java 版正式版的候选版本,如果发现重大漏洞,该候选版会被新的候选版代替。如果一切正常,该版本将会作为正式版发布。[/size]
[color=#f44336][size=5]|[/size][/color][size=4]候选版已可供普通玩家进行抢鲜体验,但仍需当心可能存在的漏洞。[/size]
[color=#f44336][size=5]|[/size][/color][size=4]Minecraft Java 版 ${releaseversioncode} 仍未发布,${versioncode} 为其第 ${versioncount} 个候选版。[/size]
[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`;
case VersionType.Release:
return `[color=#388e3c][size=5]|[/size][/color][size=4][b]Minecraft Java 版[/b]是指运行在 Windows、Mac OS 与 Linux 平台上,使用 Java 语言开发的 Minecraft 版本。[/size]
[color=#f44336][size=5]|[/size][/color][size=4][b]正式版[/b]是 Minecraft Java 版经过一段时间的预览版测试后得到的稳定版本,也是众多纹理、Mod 与服务器插件会逐渐跟进的版本。官方启动器也会第一时间进行推送。 [/size]
[color=#f44336][size=5]|[/size][/color][size=4]建议玩家与服主关注其相关服务端、Mod 与插件的更新,迎接新的正式版吧!专注于单人原版游戏的玩家可立即更新,多人游戏玩家请关注您所在服务器的通知。[/size]
[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`;
case VersionType.BedrockRelease:
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]
[color=#f44336][size=5]|[/size][/color][size=4][b]正式版[/b]是 Minecraft 基岩版经过一段时间的测试版测试之后得到的稳定版本,也是众多纹理、附加包和 Realms 会逐渐跟进的版本。与此同时 Google Play、Microsoft Store 等官方软件商店也会推送此次更新。 [/size]
[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`;
case VersionType.BedrockBeta:
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]
[color=#388e3c][size=5]|[/size][/color][size=4][b]测试版[/b]是 Minecraft 基岩版的测试机制,主要用于下一个正式版的特性预览。[/size]
[color=#f44336][size=5]|[/size][/color][size=4][b]然而,测试版主要用于新特性展示,通常存在大量漏洞。因此对于普通玩家建议仅做测试尝鲜用。使用测试版打开存档前请务必备份。适用于正式版的领域服务器与测试版不兼容。[/b] [/size]
[color=#f44336][size=5]|[/size][/color][size=4]如果在测试版中遇到旧版存档无法使用的问题,测试版将允许你将存档上传以供开发团队查找问题。[/size]
[color=#f44336][size=5]|[/size][/color][size=4]Minecraft 基岩版 <正式版版本号> 仍未发布,Beta & Preview ${versioncode} 为其第 <计数> 个测试版。[/size]
[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`;
case VersionType.Normal:
default:
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`;
}
}
function getFooter(articleType, type) {
const time = new Date();
function padTime(time) {
return time.toString().padStart(2, "0");
}
function toHoursAndMinutes(totalMinutes) {
const m = Math.abs(totalMinutes);
const minutes = m % 60;
const hours = Math.floor(m / 60);
return `${totalMinutes < 0 ? "+" : "-"}${padTime(hours)}${padTime(
minutes
)}`;
}
const poweredBy = `\n[align=center][size=1][color=Silver]Powered by SPXXKLP ${spxxklpVersion} with love
Converted at ${time.getFullYear()}-${
padTime(time.getMonth() + 1) // why +1 javascript
}-${padTime(time.getDate())} ${padTime(time.getHours())}:${padTime(
time.getMinutes()
)} ${toHoursAndMinutes(time.getTimezoneOffset())}[/color][/size][/align]`;
/*Same contents,change if necessary.**/
switch (type) {
case VersionType.Snapshot:
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]`;
case VersionType.PreRelease:
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]`;
case VersionType.ReleaseCandidate:
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]`;
case VersionType.Release:
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]`;
case VersionType.BedrockRelease:
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]`;
case VersionType.BedrockBeta:
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]`;
case VersionType.Normal:
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]`;
}
}
let VersionType;
(function (VersionType) {
VersionType[(VersionType["Snapshot"] = 1)] = "Snapshot";
VersionType[(VersionType["PreRelease"] = 2)] = "PreRelease";
VersionType[(VersionType["ReleaseCandidate"] = 3)] = "ReleaseCandidate";
VersionType[(VersionType["Release"] = 4)] = "Release";
VersionType[(VersionType["Normal"] = 5)] = "Normal";
VersionType[(VersionType["BedrockBeta"] = 6)] = "BedrockBeta";
VersionType[(VersionType["BedrockRelease"] = 7)] = "BedrockRelease";
})(VersionType || (VersionType = {}));
const translators = {
headings: (input, ctx) => {
return translator(input, ctx, [
[/Block of the Week: /gi, "本周方块:"],
[/Taking Inventory: /gi, "背包盘点:"],
[/Around the Block: /gi, "群系漫游:"],
[/A Minecraft Java Snapshot/gi, "Minecraft Java版 快照"],
[/A Minecraft Java Pre-Release/gi, "Minecraft Java版 预发布版"],
[/A Minecraft Java Release Candidate/gi, "Minecraft Java版 候选版本"],
[/Minecraft Beta (?:-|——) (.*?) \((.*?)\)/gi, "Minecraft 基岩版 Beta $1($2)"],
[/Minecraft Beta & Preview - (.*?)/g, "Minecraft 基岩版 Beta & Preview $1"],
[/Minecraft (?:-|——) (.*?) \(Bedrock\)/gi, "Minecraft 基岩版 $1"],
[/Minecraft (?:-|——) (.*?) \((.*?) Only\)/gi, "Minecraft 基岩版 $1(仅$2)"],
[/Minecraft (?:-|——) (.*?) \((.*?)\)/gi, "Minecraft 基岩版 $1(仅$2)"],
[/Marketplace/gi, "市场"],
[/Data-Driven/gi, "数据驱动"],
[/Graphical/gi, "图像"],
[/Vanilla /gi, "原版"],
[/Player/gi, "玩家"],
[/Experimental /gi, "实验性"],
[/Mobs/gi, "生物"],
[/Features and Bug Fixes/gi, "特性和漏洞修复"],
[/ADVANCEMENTS/gi, "进度"],
[/Accessibility/gi, "辅助功能"],
[/Gameplay/gi, "玩法"],
[/Items/gi, "物品"],
[/Blocks/gi, "方块"],
[/User Interface/gi, "用户界面"],
[/Commands/gi, "命令"],
[/Known Issues/gi, "已知问题"],
[/Character Creator/gi, "角色创建器"],
[/ Components/gi, "组件"],
[/General/gi, "通用"],
[/Technical Experimental Updates/gi, "实验性技术性更新"],
[/Gametest Framework/gi, "Gametest 框架"],
[/Gametest Framework (experimental)/gi, "Gametest 框架(实验性)"],
[/Minecraft Snapshot /gi, "Minecraft 快照 "],
[/ Pre-Release /gi, "-pre"],
[/ Release Candidate /gi, "-rc"],
[/Get the Release Candidate/gi, "获取预发布版本"],
[/Get the Release/gi, "获取正式版"],
[/Get the Pre-Release/gi, "获取候选版本"],
[/Get the Snapshot/gi, "获取快照版本"],
[/New Features in ([^\r\n]+)/gi, "$1 的新增特性"],
[/Technical changes in ([^\r\n]+)/gi, "$1 的技术性修改"],
[/Changes in ([^\r\n]+)/gi, "$1 的修改内容"],
[/Fixed bugs in ([^\r\n]+)/gi, "$1 修复的漏洞"],
[/STABILITY AND PERFORMANCE/gi, "性能与稳定性"],
[/FEATURES AND BUG FIXES/gi, "特性和漏洞修复"],
[/LOOT/gi, "战利品"],
[/PARITY/gi, "趋同"],
[/Components/gi, "组件"],
[/ADD-ONS AND SCRIPT ENGINE/gi, "附加包和脚本引擎"],
[/DRESSING ROOM/gi, "更衣室"],
[/Item/gi, "物品"],
[/CHANGES/gi, "改动"],
[/SOUNDS/gi, "音效"],
[/DATA PACK VERSION/gi, "数据包版本"],
[/PREDICATES/gi, "谓词"],
[/ PREDICATE/gi, "谓词"],
[/EFFECT/gi, "效果"],
[/COMMAND/gi, "命令"],
[/ATTRIBUTE/gi, "属性"],
[/BLOCK/gi, "方块"],
[/ENTITY/gi, "实体"],
[/ENCHANTMENTS/gi, "附魔"],
[/ TAGS/gi, "标签"],
[/TAGS/gi, "标签"],
[/TYPE/gi, "类型"],
[/MUSIC/gi, "音乐"],
[/GAME TIPS/gi, "游戏提示"],
[/NEW FEATURES/gi, "新特性"],
[/NEW /gi, "新的"],
[/USER INTERFACE/gi, "用户界面"],
[/EDITOR/gi, "编辑器"],
[/FIXES/gi, "修复"],
[/IMPROVEMENTS/gi, "改进"],
[/RESOURCE PACK VERSION/gi, "资源包版本"],
[/SHADERS/gi, "着色器"],
[/PARTICLES/gi, "粒子效果"],
[/TOUCH CONTROLS/gi, "触控"],
[/TECHNICAL UPDATES/gi, "技术性更新"],
[/ TABLES/gi, "表"],
[/PROJECTILES/gi, "弹射物"],
[/STRUCTURES/gi, "结构"],
[/ENTITIES/gi, "实体"],
[/FUNCTIONS/gi, "函数"],
]);
},
imgCredits: (input, ctx) => {
return translator(input, ctx, [
[/Image credit:/gi, "图片来源:"],
[/CC BY-NC-ND/gi, "知识共享 署名-非商业性使用-禁止演绎"],
[/CC BY-NC-SA/gi, "知识共享 署名-非商业性使用-相同方式共享"],
[/CC BY-NC/gi, "知识共享 署名-非商业性使用"],
[/CC BY-ND/gi, "知识共享 署名-禁止演绎"],
[/CC BY-SA/gi, "知识共享 署名-相同方式共享"],
[/CC BY/gi, "知识共享 署名"],
[/Public Domain/gi, "公有领域"],
]);
},
punctuation: (input, ctx) => {
return translator(
input,
ctx,
[
[/\[i]/gi, "[font=楷体]"],
[/\[\/i]/g, "[/font]"],
...(ctx.disablePunctuationConverter
? []
: [
[/,( |$)/g, ","],
[/!( |$)/g, "!"],
[/\.\.\.( |$)/g, "…"],
[/\.( |$)/g, "。"],
[/\?( |$)/g, "?"],
[/( |^)-( |$)/g, " —— "],
]),
],
(input) => {
return quoteTreatment(input, [["“", "”", /"/]]);
}
);
},
code: (input, ctx) => {
return quoteTreatment(input, [
[
'[backcolor=#f1edec][color=Silver][font=SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace][/font][/color][/backcolor]',
"`",
/`/,
],
]);
},
};
function translate(input, ctx, type) {
if (typeof type === "string") {
type = [type];
}
for (const t of type) {
input = translators[t](input, ctx);
}
return input;
}
function quoteTreatment(input, quoteArrays) {
for (const quoteArray of quoteArrays) {
const split = input.split(quoteArray[2]);
input = "";
for (let i = 0; i < split.length - 1; i++) {
const element = split[i];
input += element + quoteArray[i % 2];
}
input += split[split.length - 1];
}
return input;
}
function translator(input, ctx, mappings, treatment = (input) => input) {
// REPLACE!!!!1
for (const mapping of mappings) {
input = input.replace(mapping[0], mapping[1]);
}
treatment(input, ctx);
return input;
}
const converters = {
/**
* Converts a ChildNode to a BBCode string according to the type of the node.
*/
convert: async (node, ctx) => {
if (node.classList?.contains("spxxklp-userscript-ignored")) {
return "";
} // Listing all possible elements in the document
switch (node.nodeName) {
case "A":
return converters.a(node, ctx);
case "B":
case "STRONG":
return converters.strong(node, ctx);
case "BLOCKQUOTE":
return converters.blockquote(node, ctx);
case "BR":
return converters.br();
case "CITE":
return converters.cite(node, ctx);
case "CODE":
return converters.code(node, ctx);
case "DIV":
case "SECTION":
return converters.div(node, ctx);
case "DD":
return converters.dd(node, ctx);
case "DL":
return converters.dl(node, ctx);
case "DT":
return converters.dt();
case "EM":
return converters.em(node, ctx);
case "H1":
return converters.h1(node, ctx);
case "H2":
return converters.h2(node, ctx);
case "H3":
return converters.h3(node, ctx);
case "H4":
return converters.h4(node, ctx);
case "I":
return converters.i(node, ctx);
case "IMG":
return converters.img(node);
case "LI":
return converters.li(node, ctx);
case "OL":
return converters.ol(node, ctx);
case "P":
return converters.p(node, ctx);
case "PICTURE":
return converters.picture(node, ctx);
case "PRE":
return converters.pre(node, ctx);
case "SPAN":
return converters.span(node, ctx);
case "TABLE":
return converters.table(node, ctx);
case "TBODY":
return converters.tbody(node, ctx);
case "TH":
case "TD":
return converters.td(node, ctx);
case "TR":
return converters.tr(node, ctx);
case "UL":
return converters.ul(node, ctx);
case "#text":
if (node) {
if (ctx.multiLineCode) {
return node.textContent ? node.textContent : "";
} else
return node.textContent
.replace(/[\n\r\t]+/g, "")
.replace(/\s{2,}/g, "");
} else {
return "";
}
case "H5":
return converters.h5(node, ctx);
case "BUTTON":
case "NAV":
case "svg":
case "SCRIPT":
if (node) {
return node.textContent ? node.textContent : "";
} else {
return "";
}
case "FIGURE":
return converters.figure(node, ctx);
default:
console.warn(`Unknown type: '${node.nodeName}'.`);
if (node) {
return node.textContent ? node.textContent : "";
} else {
return "";
}
}
},
/**
* Convert child nodes of an HTMLElement to a BBCode string.
*/
recurse: async (ele, ctx) => {
let ans = "";
if (!ele) {
return ans;
}
for (const child of Array.from(ele.childNodes)) {
ans += await converters.convert(child, ctx);
}
return ans;
},
a: async (anchor, ctx) => {
const url = resolveUrl(anchor.href);
let ans;
if (url) {
ans = `[url=${url}][color=#388d40][u]${await converters.recurse(
anchor,
ctx
)}[/u][/color][/url]`;
} else {
ans = await converters.recurse(anchor, ctx);
}
return ans;
},
blockquote: async (ele, ctx) => {
const prefix = "";
const suffix = "";
const ans = `${prefix}${await converters.recurse(ele, ctx)}${suffix}`;
return ans;
},
br: async () => {
const ans = "\n";
return ans;
},
cite: async (ele, ctx) => {
const prefix = "—— ";
const suffix = "";
const ans = `${prefix}${await converters.recurse(ele, ctx)}${suffix}`;
return ans;
},
code: async (ele, ctx) => {
if (!ele || !await converters.recurse(ele, {
...ctx,
disablePunctuationConverter: true,
})) {
return '';
}
const prefix = ctx.multiLineCode
? "[code]"
: "[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]";
const suffix = ctx.multiLineCode
? "[/code]"
: "[/font][/color][/backcolor]";
const ans = `${prefix}${await converters.recurse(ele, {
...ctx,
disablePunctuationConverter: true,
})}${suffix}`;
return ans;
},
div: async (ele, ctx) => {
let ans = await converters.recurse(ele, ctx);
if (ele.classList.contains("text-center")) {
ans = `[align=center]${ans}[/align]\n`;
} else if (ele.classList.contains("article-image-carousel")) {
/*const prefix = `[/indent][/indent][album]\n`;
const suffix = `\n[/album][indent][indent]\n`;*/
const slides = [];
const findSlides = async (ele) => {
if (ele.classList.contains("slick-cloned")) {
return;
}
if (
ele.nodeName === "IMG" &&
ele.classList.contains("article-image-carousel__image")
) {
slides.push([resolveUrl(ele.src), " "]);
} else if (
ele.nodeName === "DIV" &&
ele.classList.contains("article-image-carousel__caption")
) {
if (slides.length > 0) {
slides[slides.length - 1][1] = `[b]${await converters.recurse(
ele,
ctx
)}[/b]`;
}
} else {
for (const child of Array.from(ele.childNodes)) {
if (child.nodeName === "DIV" || child.nodeName === "IMG") {
await findSlides(child);
}
}
}
};
await findSlides(ele);
/*if (shouldUseAlbum(slides)) {
ans = `${prefix}${slides.map(([url, caption]) => `[aimg=${url}]${caption}[/aimg]`).join('\n')}${suffix}`;
} else */ if (slides.length > 0) {
ans = `[align=center]${slides
.map(([url, caption]) => `[img]${url}[/img]\n${caption}`)
.join("\n")}[/align]\n`;
} else {
ans = "";
}
} else if (ele.classList.contains("video")) {
// Video.
ans =
"\n[align=center]<无法获取的视频,如有可用视频源,请在此处插入>\n<对于B站视频,可使用 [bilibili] 代码>[/align]\n";
} else if (
ele.classList.contains("quote") ||
ele.classList.contains("attributed-quote")
) {
ans = `\n[quote]\n${ans}\n[/quote]\n`;
} else if (ele.classList.contains("article-social")) {
// End of the content.
ans = "";
} else if (ele.classList.contains("modal")) {
// Unknown useless content
ans = "";
}
return ans;
},
dt: async () => {
// const ans = `${converters.rescure(ele)}:`
// return ans
return "";
},
dl: async (ele, ctx) => {
const ans = `\n\n${await converters.recurse(
ele,
ctx
)}\n【本文排版借助了:[url=https://github.com/cinder0601/SPXXKLP][color=#388d40][u]SPXXKLP[/u][/color][/url] 用户脚本 v${spxxklpVersion}】\n\n`;
return ans;
},
dd: async (ele, ctx) => {
let ans = "";
if (ele.classList.contains("pubDate")) {
// Published:
// `pubDate` is like '2019-03-08T10:00:00.876+0000'.
const date = ele.attributes.getNamedItem("data-value");
if (date) {
ans = `[b]【${ctx.translator} 译自[url=${
ctx.url
}][color=#388d40][u]官网 ${date.value.slice(
0,
4
)} 年 ${date.value.slice(5, 7)} 月 ${date.value.slice(
8,
10
)} 日发布的 ${ctx.title}[/u][/color][/url];原作者 ${
ctx.author
}】[/b]`;
} else {
ans = `[b]【${ctx.translator} 译自[url=${ctx.url}][color=#388d40][u]官网 * 年 * 月 * 日发布的 ${ctx.title}[/u][/color][/url]】[/b]`;
}
} else {
// Written by:
ctx.author = await converters.recurse(ele, ctx);
}
return ans;
},
em: async (ele, ctx) => {
const ans = `[i]${await converters.recurse(ele, ctx)}[/i]`;
return ans;
},
h1: async (ele, ctx) => {
const prefix = "[size=6][b]";
const suffix = "[/b][/size]";
const processNode = async (node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
return await converters[node.tagName.toLowerCase()](node, ctx);
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue;
}
};
const rawInnerArray = await Promise.all(
Array.from(ele.childNodes).map(processNode)
);
const rawInner = rawInnerArray.join("");
const inner = makeUppercaseHeader(rawInner);
const ans = `${prefix}[color=Silver]${usingSilver(inner).replace(
/[\n\r]+/g,
" "
)}[/color]${suffix}\n${prefix}${translate(`${inner}`, ctx, [
"headings",
"punctuation",
]).replace(/[\n\r]+/g, " ")}${suffix}\n\n`;
return ans;
},
h2: async (ele, ctx) => {
if (isBlocklisted(ele.textContent)) return "";
const prefix = "[size=5][b]";
const suffix = "[/b][/size]";
const processNode = async (node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
return await converters[node.tagName.toLowerCase()](node, ctx);
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue;
}
};
const rawInnerArray = await Promise.all(
Array.from(ele.childNodes).map(processNode)
);
const rawInner = rawInnerArray.join("");
const inner = makeUppercaseHeader(rawInner);
const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace(
/[\n\r]+/g,
" "
)}[/color]${suffix}\n${prefix}${translate(`${inner}`, ctx, [
"headings",
"punctuation",
]).replace(/[\n\r]+/g, " ")}${suffix}\n\n`;
return ans;
},
h3: async (ele, ctx) => {
const prefix = "[size=4][b]";
const suffix = "[/b][/size]";
const processNode = async (node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
return await converters[node.tagName.toLowerCase()](node, ctx);
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue;
}
};
const rawInnerArray = await Promise.all(
Array.from(ele.childNodes).map(processNode)
);
const rawInner = rawInnerArray.join("");
const inner = makeUppercaseHeader(rawInner);
const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace(
/[\n\r]+/g,
" "
)}[/color]${suffix}\n${prefix}${translate(`${inner}`, ctx, [
"headings",
"punctuation",
]).replace(/[\n\r]+/g, " ")}${suffix}\n\n`;
return ans;
},
h4: async (ele, ctx) => {
const prefix = "[size=3][b]";
const suffix = "[/b][/size]";
const processNode = async (node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
return await converters[node.tagName.toLowerCase()](node, ctx);
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue;
}
};
const rawInnerArray = await Promise.all(
Array.from(ele.childNodes).map(processNode)
);
const rawInner = rawInnerArray.join("");
const inner = makeUppercaseHeader(rawInner);
const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace(
/[\n\r]+/g,
" "
)}[/color]${suffix}\n${prefix}${inner}${suffix}\n\n`;
return ans;
},
h5: async (ele, ctx) => {
const prefix = "[size=2][b]";
const suffix = "[/b][/size]";
const processNode = async (node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
return await converters[node.tagName.toLowerCase()](node, ctx);
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue;
}
};
const rawInnerArray = await Promise.all(
Array.from(ele.childNodes).map(processNode)
);
const rawInner = rawInnerArray.join("");
const inner = makeUppercaseHeader(rawInner);
const ans = `\n${prefix}[color=Silver]${usingSilver(inner).replace(
/[\n\r]+/g,
" "
)}[/color]${suffix}\n${prefix}${inner}${suffix}\n\n`;
return ans;
},
i: async (ele, ctx) => {
const ans = `[i]${await converters.recurse(ele, ctx)}[/i]`;
return ans;
},
img: async (img) => {
let host = location.host;
let w;
let h;
if (img.classList.contains("attributed-quote__image")) {
// for in-quote avatar image
h = 92;
w = 53;
} else if (img.classList.contains("mr-3")) {
// for attributor avatar image
h = 121;
w = 82;
}
const prefix = w && h ? `[img=${w},${h}]` : "[img]";
const imgUrl = resolveUrl(img.src);
if (imgUrl === "") return ""; // in case of empty image
let ans;
if (host == "www.minecraft.net") {
ans = `[align=center]${prefix}${imgUrl}[/img][/align]\n`; //Left aligning is too ugly.
} else {
ans = `[align=center]${prefix}${imgUrl}[/img][/align]\n`;
}
return ans;
},
li: async (ele, ctx) => {
let ans;
let nestedList = false;
for (const child of ele.childNodes) {
if (child.nodeName === "OL" || child.nodeName === "UL") {
nestedList = true;
}
}
if (nestedList) {
// Nested lists.
let theParagragh = "";
let theList = "";
let addingList = false;
for (let i = 0; i < ele.childNodes.length - 1; i++) {
let nodeName = ele.childNodes[i].nodeName;
if (nodeName === "OL" || nodeName === "UL") {
addingList = true;
}
if (!addingList) {
const paragraghNode = await converters.convert(ele.childNodes[i], {
...ctx,
inList: true,
});
theParagragh = `${theParagragh}${paragraghNode}`;
} else {
const listNode = await converters.convert(ele.childNodes[i], {
...ctx,
inList: true,
});
theList = `${theList}${listNode}`;
}
}
ans = `[*][color=Silver]${usingSilver(
theParagragh
)}[/color]\n[*]${translate(
translateBugs(theParagragh, ctx),
ctx,
"code"
)}\n${theList}`;
} else if (isBlocklisted(ele.textContent)) {
return "";
} else {
const inner = await converters.recurse(ele, { ...ctx, inList: true });
ans = `[*][color=Silver]${usingSilver(inner)}[/color]\n[*]${translate(
translateBugs(inner, ctx),
ctx,
"code"
)}\n`;
}
return ans;
},
ol: async (ele, ctx) => {
const inner = await converters.recurse(ele, ctx);
const ans = `[list=1]\n${inner}[/list]\n`;
return ans;
},
p: async (ele, ctx) => {
const processNode = async (node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const converter = converters[node.tagName.toLowerCase()];
if (converter) {
return await converter(node, ctx);
}
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue;
}
};
let inner = await converters.recurse(ele, ctx);
inner = inner ? inner.trim() : "";
let ans;
if (inner === "") {
return "";
}
if (ele.style.textAlign === "center") {
ans = `[align=center][size=2][color=Silver]${usingSilver(
inner
)}[/color][/size]\n${translate(inner, ctx, [
"punctuation",
"imgCredits",
])}[/align]\n`;
} else if (ele.classList.contains("lead")) {
ans = `[b][size=2][color=Silver]${inner}[/color][/size][/b]\n[size=4][b]${translate(
inner,
ctx,
"headings"
)}[/b][/size]\n`;
} else if (
ele.querySelector("strong") !== null &&
ele.querySelector("strong").textContent === "Posted:"
) {
return "";
} else if (isBlocklisted(inner)) {
return "";
} else if (ele.innerHTML.trim() === " ") {
return "";
} else if (
/\s{0,}/.test(inner) &&
ele.querySelectorAll("img").length === 1
) {
return inner;
} else {
if (ctx.inList) {
ans = inner;
} else {
ans = `[size=2][color=Silver]${usingSilver(
inner
)}[/color][/size]\n${translate(inner, ctx, [
"punctuation",
"imgCredits",
])}\n\n`;
}
}
return ans;
},
picture: async (ele, ctx) => {
const ans = await converters.recurse(ele, ctx);
return ans;
},
figure: async (ele, ctx) => {
const ans = await converters.recurse(ele, ctx);
return ans;
},
pre: async (ele, ctx) => {
const ans = await converters.recurse(ele, {
...ctx,
multiLineCode: true,
});
return ans;
},
span: async (ele, ctx) => {
const ans = await converters.recurse(ele, ctx);
if (ele.classList.contains("MC_Effect_TextHighlightA")) {
// Special for MC_Effect_TextHighlightA element.
const textContent = await converters.recurse(ele, ctx);
const prefix =
"[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]";
const suffix = "[/font][/color][/backcolor]";
return `${prefix}${textContent}${suffix}`;
} else if (ele.classList.contains("MC_Effect_TextHighlightB")) {
// Special for MC_Effect_TextHighlightB element.
const textContent = await converters.recurse(ele, ctx);
const prefix =
"[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]";
const suffix = "[/font][/color][/backcolor]";
return `${prefix}${textContent}${suffix}`;
} else if (ele.classList.contains("bedrock-server")) {
// Inline code.
const prefix =
"[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]";
const suffix = "[/font][/color][/backcolor]";
return `${prefix}${await converters.recurse(ele, {
...ctx,
disablePunctuationConverter: true,
})}${suffix}`;
} else if (ele.classList.contains("strikethrough")) {
// Strikethrough text.
const prefix = "[s]";
const suffix = "[/s]";
return `${prefix}${ans}${suffix}`;
} else if (
ele.childElementCount === 1 &&
ele.firstElementChild.nodeName === "IMG"
) {
// Image.
const img = ele.firstElementChild;
return await converters.img(img);
}
return ans;
},
strong: async (ele, ctx) => {
const ans = `[b]${await converters.recurse(ele, ctx)}[/b]`;
return ans;
},
table: async (ele, ctx) => {
const ans = `\n[table]\n${await converters.recurse(ele, ctx)}[/table]\n`;
return ans;
},
tbody: async (ele, ctx) => {
const ans = await converters.recurse(ele, ctx);
return ans;
},
td: async (ele, ctx) => {
const ans = `[td]${await converters.recurse(ele, ctx)}[/td]`;
return ans;
},
tr: async (ele, ctx) => {
const ans = `[tr]${await converters.recurse(ele, ctx)}[/tr]\n`;
return ans;
},
ul: async (ele, ctx) => {
const inner = await converters.recurse(ele, ctx);
const ans = `[list]\n${inner}[/list]\n`;
return ans;
},
};
/**
* Resolve relative URLs.
*/
function resolveUrl(url) {
if (url[0] === "/") {
return `https://${location.host}${url}`;
} else {
return url;
}
}
function usingSilver(text) {
return text.replace(/#388d40/g, "Silver").replace(/#7824c5/g, "Silver");
}
function makeUppercaseHeader(header) {
let retStr = "";
let idx = 0;
let bracket = 0;
for (let i = 0; i < header.length; i++) {
if (header[i] == "[") {
if (bracket == 0) {
retStr = retStr.concat(header.substring(idx, i).toUpperCase());
idx = i;
}
bracket++;
} else if (header[i] == "]") {
if (bracket <= 1) {
retStr = retStr.concat(header.substring(idx, i + 1));
idx = i + 1;
}
bracket = Math.max(0, bracket - 1);
}
}
if (bracket > 0) {
console.error("bracket not closed!");
retStr = retStr.concat(header.substring(idx, header.length));
} else {
retStr = retStr.concat(
header.substring(idx, header.length).toUpperCase()
);
}
return retStr;
}
/**
* Get bugs from BugCenter.
* Guangyao and GitHub source are down, so I deleted them.
*/
async function getBugs() {
return new Promise((rs, rj) => {
GM_xmlhttpRequest({
method: "GET",
url: bugsCenter,
fetch: true,
nocache: true,
timeout: 7_000,
onload: (r) => {
try {
rs(JSON.parse(r.responseText));
} catch (e) {
rj(e);
}
},
onabort: () => rj(new Error("Aborted")),
onerror: (e) => rj(e),
ontimeout: () => rj(new Error("Time out")),
});
});
}
async function getBugsTranslators() {
return new Promise((rs, rj) => {
GM_xmlhttpRequest({
method: "GET",
url: bugsTranslatorsTable,
fetch: true,
nocache: true,
timeout: 7_000,
onload: (r) => {
try {
rs(JSON.parse(r.responseText));
} catch (e) {
rj(e);
}
},
onabort: () => rj(new Error("Aborted")),
onerror: (e) => rj(e),
ontimeout: () => rj(new Error("Time out")),
});
});
}
async function getTranslatorColor() {
return new Promise((rs, rj) => {
GM_xmlhttpRequest({
method: "GET",
url: translatorColorTable,
fetch: true,
nocache: true,
timeout: 7_000,
onload: (r) => {
try {
rs(JSON.parse(r.responseText));
} catch (e) {
rj(e);
}
},
onabort: () => rj(new Error("Aborted")),
onerror: (e) => rj(e),
ontimeout: () => rj(new Error("Time out")),
});
});
}
function markdownToBbcode(value) {
return value.replace(
/`([^`]+)`/g,
"[backcolor=#f1edec][color=#7824c5][font=SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace]$1[/font][/color][/backcolor]"
);
}
/**
* Replace untranslated bugs.
*/
function translateBugs(str, ctx) {
if (
str.startsWith("[url=https://bugs.mojang.com/browse/MC-") &&
ctx.bugs != null // nullish
) {
const id = str.slice(36, str.indexOf("]"));
const data = ctx.bugs[id];
if (data) {
let bugColor = "#388d40";
if (ctx.bugsTranslators[id]) {
const bugTranslator = ctx.bugsTranslators[id];
if (ctx.translatorColor[bugTranslator]) {
bugColor = ctx.translatorColor[bugTranslator];
}
}
const bbcode = markdownToBbcode(data);
return `[url=https://bugs.mojang.com/browse/${id}][b][color=${bugColor}]${id}[/color][/b][/url]- ${bbcode}`;
} else {
return str;
}
} else {
return str;
}
}
/**
* KLPBBS does NOT support [album], the shouldUseAlbum function is removed temporarily.
*/
/*function shouldUseAlbum(slides) {
return slides.length > 1 && slides.every(([_, caption]) => caption === ' ')
;
}*/
function isBlocklisted(text) {
const blocklist = [
"Information on the Minecraft Preview and Beta:",
"While the version numbers between Preview and Beta are different, there is no difference in game content",
"These work-in-progress versions can be unstable and may not be representative of final version quality",
"Minecraft Preview is available on Xbox, Windows 10/11, and iOS devices. More information can be found at aka.ms/PreviewFAQ",
"The beta is available on Android (Google Play). To join or leave the beta, see aka.ms/JoinMCBeta for detailed instructions",
];
return blocklist
.map((i) => {
return i.replace(/\p{General_Category=Space_Separator}*/, "");
})
.some((block) =>
text
.trim()
.trim()
.replace(/\p{General_Category=Space_Separator}*/, "")
.includes(block)
);
}
async function minecraftNet() {
const url = document.location.toString();
if (url.match(/^https:\/\/www\.minecraft\.net\/(?:[a-z-]+)\/article\//)) {
const authorContainer = document.querySelector(
".MC_articleHeroA_attribution_author"
);
const dateElement = authorContainer.querySelector("dd:nth-child(4)"); // 获取发布日期的 dd 元素
const button = document.createElement("button");
button.classList.add("spxxklp-userscript-ignored");
button.innerText = "复制 BBCode (KLPBBS)";
// 按钮样式设置
button.style.backgroundColor = "#3C8527";
button.style.color = "#FFFFFF";
button.style.border = "none";
button.style.padding = "10px 20px";
button.style.borderRadius = "5px";
button.style.fontSize = "16px";
button.style.cursor = "pointer";
button.style.transition = "background-color 0.3s ease";
button.style.fontFamily = "MinecraftTen, sans-serif";
button.style.width = "140px";
button.style.height = "70px";
button.style.textAlign = "center";
button.style.marginLeft = "auto";
button.onmouseover = () => {
button.style.backgroundColor = "#52A535";
};
button.onmouseout = () => {
button.style.backgroundColor = "#3C8527";
};
button.onclick = async () => {
button.innerText = "处理中...";
const bbcode = await convertMCArticleToBBCode(document, url);
GM_setClipboard(bbcode, {
type: "text",
mimetype: "text/plain",
});
button.innerText = "已复制!";
setTimeout(() => (button.innerText = "复制 BBCode (KLPBBS)"), 5000);
};
const container = document.createElement("div");
container.id = "spxxklp-buttons";
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.alignItems = "flex-end";
container.style.width = "100%";
container.style.padding = "10px";
container.style.boxSizing = "border-box";
container.append(button);
// 将按钮插入到日期下方
dateElement.insertAdjacentElement("afterend", button);
}
}
async function convertMCArticleToBBCode(
html,
articleUrl,
translator = config.translator
) {
const articleType = getArticleType(html);
const versionType = getVersionType(articleUrl);
let bugs;
try {
bugs = await getBugs();
} catch (e) {
bugs = {};
console.error("[convertMCArticleToBBCode#getBugs]", e);
}
let bugsTranslators;
try {
bugsTranslators = await getBugsTranslators();
} catch (e) {
bugsTranslators = {};
console.error("[convertMCArticleToBBCode#getBugs]", e);
}
let translatorColor;
try {
translatorColor = await getTranslatorColor();
} catch (e) {
translatorColor = {};
console.error("[convertMCArticleToBBCode#getBugs]", e);
}
const header = getHeader(articleType, versionType);
const heroImage = getHeroImage(html, articleType);
const maintitle = await getMainTitle(html);
const subtitle = await getSubTitle(html);
let content = await getContent(html, {
bugs,
bugsTranslators,
translatorColor,
title: html.title.split(" | ").slice(0, -1).join(" | "),
date: null,
translator,
url: articleUrl,
});
const footer = getFooter(articleType, versionType);
const author = await getAuthor(html);
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}`;
return ans;
}
/**
* Returns the type of the article.
*/
function getArticleType(html) {
try {
const type =
html.getElementsByClassName("MC_articleHeroA_category")?.[0]
?.textContent ?? "";
return type.toUpperCase();
} catch (e) {
console.error("[getArticleType]", e);
}
return "INSIDER";
}
/**
* Get the hero image (head image) of an article as the form of a BBCode string.
* @param html An HTML Document.
*/
function getHeroImage(html, articleType) {
const category = articleType
? `\n[backcolor=Black][color=White][font="Noto Sans",sans-serif][b]${articleType}[/b][/font][/color][/backcolor][/align]`
: "";
const img = html.getElementsByClassName("article-head__image")[0];
if (!img) {
return `\n[align=center]${category}[/align]\n`;
}
const src = img.src;
const ans = `[align=center][img=1200,513]${resolveUrl(
src
)}[/img]\n${category}[/align]\n`;
return ans;
}
/**
* Get the content of an article as the form of a BBCode string.
* @param html An HTML Document.
*/
async function getSubTitle(html) {
let con = html.getElementsByClassName(
"MC_articleHeroA_header_container"
)[0];
let subtitle = con.getElementsByClassName(
"MC_articleHeroA_header_subheadline"
)[0].innerText;
return subtitle;
}
async function getMainTitle(html) {
let con = html.getElementsByClassName(
"MC_articleHeroA_header_container"
)[0];
let maintitle = con.getElementsByClassName("MC_Heading_1")[0].innerText;
return maintitle;
}
async function getAuthor(html, translator = config.translator) {
try {
let rawauthor = html.getElementsByClassName("MC_articleHeroA_attribution_author")[0];
if (!rawauthor) {
console.warn("Author attribution element not found");
return "Unknown Author";
}
let authorImgUrl = "";
let authorImg = rawauthor.getElementsByTagName("img")[0];
if (authorImg && authorImg.src) {
authorImgUrl = authorImg.src;
}
let authorName = "Unknown";
let authorNameElement = rawauthor.getElementsByTagName("dd")[0];
if (authorNameElement) {
authorName = authorNameElement.innerText;
}
let publishDate = "Unknown Date";
let publishDateElement = rawauthor.getElementsByTagName("dd")[1];
if (publishDateElement) {
publishDate = publishDateElement.innerText;
}
let [a, b, c] = publishDate.split("/");
let year, month, day;
if (a > 12) {
year = a;
month = b;
day = c;
} else {
year = "20" + c;
month = a;
day = b;
}
let url = window.location.href;
let title = await getMainTitle(html);
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}】`;
return ans;
} catch (error) {
console.error("Error in getAuthor function:", error);
return "Error retrieving author information";
}
}
async function getContent(html, ctx) {
let results = [];
let elements = document.querySelectorAll(
".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)"
);
let container = document.createElement("div");
let seenElements = new Set();
Array.from(elements).forEach((element) => {
let identifier = element.outerHTML;
if (!seenElements.has(identifier)) {
seenElements.add(identifier);
container.appendChild(element.cloneNode(true)); // cloneNode(true) 深拷贝元素
}
});
let containerElements = Array.from(container.children);
for (let i = 0; i < containerElements.length; i++) {
let rootDiv = containerElements[i];
let rootDivHTML = rootDiv.outerHTML.replace(
/<h[1-5]>(?: |\s)*<\/h[1-5]>/g,
""
);
rootDiv = document.createElement("div");
rootDiv.innerHTML = rootDivHTML;
let spanElements = rootDiv.querySelectorAll("span");
spanElements.forEach((spanElement) => {
spanElement.innerHTML = spanElement.innerHTML.replace(/\n/g, " ");
});
let ans = await converters.recurse(rootDiv, ctx);
ans = ans
.replace(/([a-zA-Z0-9\-._])(\[[A-Za-z])/g, "$1 $2")
.replace(/(\[\/[^\]]+?])([a-zA-Z0-9\-._])/g, "$1 $2");
results.push(ans);
}
return results.join("\n\n");
}
function getZendesk(controlDOM, titleSlice, contentClass, versionType) {
const button = document.createElement("a");
button.classList.add("spxxklp-userscript-ignored", "navLink");
button.innerText = "复制 BBCode (KLPBBS)";
// 按钮样式设置
button.style.backgroundColor = "#3C8527";
button.style.color = "#FFFFFF";
button.style.border = "none";
button.style.padding = "5px 10px";
button.style.borderRadius = "5px";
button.style.fontSize = "15px";
button.style.cursor = "pointer";
button.style.transition = "background-color 0.3s ease";
button.style.width = "120px";
button.style.height = "50px";
button.style.textAlign = "center";
button.style.marginLeft = "auto";
button.onmouseover = () => {
button.style.backgroundColor = "#52A535";
};
button.onmouseout = () => {
button.style.backgroundColor = "#3C8527";
};
button.onclick = async () => {
button.innerText = "处理中...";
const bbcode = await convertZendeskArticleToBBCode(
document,
location.href,
config.translator,
titleSlice,
contentClass,
versionType
);
GM_setClipboard(bbcode, {
type: "text",
mimetype: "text/plain",
});
button.innerText = "已复制!";
setTimeout(() => (button.innerText = "复制 BBCode (KLPBBS)"), 5_000);
};
const container = document.createElement("div");
container.id = "spxxklp-buttons";
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.alignItems = "flex-end";
container.style.width = "100%";
container.style.padding = "10px";
container.style.boxSizing = "border-box";
container.append(button);
controlDOM(button);
}
async function converthelpElementsToBBCode(elements, ctx) {
let bbcode = "";
const seenImages = new Set();
for (let element of elements) {
try {
let converted = await converters.recurse(element, ctx);
let imgTags = converted.match(/\[img](.*?)\[\/img]/g);
if (imgTags) {
for (let imgTag of imgTags) {
let imgUrl = imgTag.match(/\[img](.*?)\[\/img]/)[1];
if (seenImages.has(imgUrl)) {
converted = converted.replace(imgTag, "");
} else {
seenImages.add(imgUrl);
}
}
}
bbcode += converted + "\n";
} catch (error) {
console.error("Error converting content to BBCode:", error);
}
}
return bbcode;
}
function getHelpContent(controlDOM) {
const ctx = {
multiLineCode: false,
disablePunctuationConverter: false,
translator: config.translator,
url: window.location.href,
inList: false,
};
const heading = document.getElementsByClassName("article-page-heading");
const content = document.getElementsByClassName("article-page-body");
const button = document.createElement("a");
button.classList.add("spxxklp-userscript-ignored", "navLink");
button.innerText = "复制 BBCode (KLPBBS)";
// 按钮样式设置
button.style.backgroundColor = "#3C8527";
button.style.color = "#FFFFFF";
button.style.border = "none";
button.style.padding = "5px 10px";
button.style.borderRadius = "5px";
button.style.fontSize = "15px";
button.style.cursor = "pointer";
button.style.transition = "background-color 0.3s ease";
button.style.width = "120px";
button.style.height = "50px";
button.style.textAlign = "center";
button.style.marginLeft = "auto";
button.onmouseover = () => {
button.style.backgroundColor = "#52A535";
};
button.onmouseout = () => {
button.style.backgroundColor = "#3C8527";
};
button.onclick = async () => {
button.innerText = "处理中...";
let bbcode = await converthelpElementsToBBCode(heading, ctx);
let title = bbcode;
title = title.replace(/\n/g, "");
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`;
bbcode += await converthelpElementsToBBCode(content, ctx);
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`;
bbcode += getFooter("INSIDER", VersionType.Normal);
GM_setClipboard(bbcode, {
type: "text",
mimetype: "text/plain",
});
button.innerText = "已复制!";
setTimeout(() => (button.innerText = "复制 BBCode (KLPBBS)"), 5000);
};
const container = document.createElement("div");
container.id = "spxxklp-buttons";
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.alignItems = "flex-end";
container.style.width = "100%";
container.style.padding = "10px";
container.style.boxSizing = "border-box";
container.append(button);
controlDOM(button);
}
async function convertZendeskArticleToBBCode(
html,
articleUrl,
translator = config.translator,
titleSlice,
contentClass,
versionType
) {
const title = html.title.slice(0, html.title.lastIndexOf(titleSlice));
const ctx = {
bugs: {},
title: title,
date: null,
translator,
url: articleUrl,
};
const content = await getZendeskContent(html, ctx, contentClass);
const posted = await getZendeskDate(location.href);
const header = versionType ? getHeader("news", versionType) : "";
const footer = versionType ? getFooter("news", versionType) : "";
const ans = `${header}[align=center][size=6][b][color=Silver]${title}[/color][/b][/size]
${translate(
`[size=6][b]${title}[/b][/size]`,
ctx,
"headings"
)}[/align]\n\n${content}\n
[b]【${ctx.translator} 译自[url=${ctx.url}][color=#388d40][u]${
ctx.url.match(/https:\/\/(.*?)\//)[1]
} ${posted.year} 年 ${posted.month} 月 ${posted.day} 日发布的 ${
ctx.title
}[/u][/color][/url]】[/b]
【本文排版借助了:[url=https://github.com/cinder0601/SPXXKLP][color=#388d40][u]SPXXKLP[/u][/color][/url] 用户脚本 v${spxxklpVersion}】\n\n${footer}`;
return ans;
}
async function getZendeskContent(html, ctx, contentClass) {
const rootSection = html.getElementsByClassName(contentClass)[0]; // Yep, this is the only difference.
let ans = await converters.recurse(rootSection, ctx); // Add spaces between texts and '[x'.
ans = ans.replace(/([a-zA-Z0-9\-._])(\[[A-Za-z])/g, "$1 $2"); // Add spaces between '[/x]' and texts.
ans = ans.replace(/(\[\/[^\]]+?])([a-zA-Z0-9\-._])/g, "$1 $2");
return ans;
}
async function getZendeskDate(url) {
const req = new Promise((rs, rj) => {
GM_xmlhttpRequest({
method: "GET",
url:
"/api/v2/help_center/en-us/articles/" +
url.match(/\/articles\/(\d+)/)[1],
fetch: true,
nocache: true,
timeout: 7_000,
onload: (r) => {
try {
rs(r.responseText);
} catch (e) {
rj(e);
}
},
onabort: () => rj(new Error("Aborted")),
onerror: (e) => rj(e),
ontimeout: () => rj(new Error("Time out")),
});
});
let res;
await req.then((value) => {
const rsp = JSON.parse(value);
res = new Date(rsp.article.created_at);
});
let year, month, day;
if (res.getFullYear() > 12) {
year = res.getFullYear();
month = res.getMonth() + 1;
day = res.getDate();
} else {
year = "20" + res.getDate();
month = res.getFullYear();
day = res.getMonth() + 1;
}
return {
year,
month,
day,
};
}
function feedback() {
let url = window.location.href; // 获取当前页面的URL
let versionType = getVersionType(url); // 调用getVersionType函数确定versionType
getZendesk(
(button) => {
document.querySelector(".topNavbar nav").append(button);
},
" – Minecraft Feedback",
"article-info",
versionType
);
}
function help() {
getHelpContent((button) => {
document.querySelector(".mc-globalbanner").append(button);
});
}
function twitter() {
const ProfilePictures = new Map([
["Mojang", "https://s2.loli.net/2024/05/27/tMZKe4BmldgDv2P.jpg"],
["MojangSupport", "https://s2.loli.net/2024/05/27/tMZKe4BmldgDv2P.jpg"],
["MojangStatus", "https://s2.loli.net/2024/05/27/tMZKe4BmldgDv2P.jpg"],
["Minecraft", "https://s2.loli.net/2024/05/27/6QsES9CwgKMLv7I.jpg"],
["henrikkniberg", "https://s2.loli.net/2024/05/27/h2KGZEBks4XMTFq.png"],
["_LadyAgnes", "https://s2.loli.net/2024/05/27/ZoJsth1i8randl9.png"],
["kingbdogz", "https://s2.loli.net/2024/05/27/IH7aVTepiDXBZb2.png"],
["JasperBoerstra", "https://s2.loli.net/2024/05/27/Jh2doD1eG7A56TR.png"],
["adrian_ivl", "https://s2.loli.net/2024/05/27/itAL8hsGqk67cxS.png"],
["slicedlime", "https://s2.loli.net/2024/05/27/DY6gQscuX8HiA9e.jpg"],
["Cojomax99", "https://s2.loli.net/2024/05/27/DxeZ3rINFgTildA.png"],
["Mojang_Ined", "https://s2.loli.net/2024/05/27/dzX3pYy8TSa7uJt.png"],
["SeargeDP", "https://s2.loli.net/2024/05/27/nf7EKltTYXLgsxN.png"],
["Dinnerbone", "https://s2.loli.net/2024/05/27/Q4ebCE29vFwPmxn.png"],
["Marc_IRL", "https://s2.loli.net/2024/05/27/bcW5zXfQ84r9IO6.png"],
["Mega_Spud", "https://s2.loli.net/2024/05/27/TZwzsJBRhnLyuFx.png"],
["CornerHardMC", "https://s2.loli.net/2024/05/28/o4wLCvuRGi9Yxh7.png"],
["MinecraftWikiEN", "https://s2.loli.net/2024/06/23/fn1SeWpdmit86lQ.png"],
]); //More pictures can be added manually.
function getTweetMetadata() {
const tweetMetadata = {
date: "",
source: "",
text: "",
rawtext: "",
tweetLink: "",
urls: "",
userName: "",
userTag: "",
lang: "",
};
const url = window.location.href;
const regex = /https:\/\/x\.com\/([^/]+)\/status\/\d+/;
const match = url.match(regex);
tweetMetadata.userTag = match[1];
let posterNameContent = [];
const posterNameElements = document
.querySelector('div[data-testid="User-Name"] a span')
.querySelectorAll("span, img[alt]");
for (const element of posterNameElements) {
if (element.tagName.toLowerCase() === "span") {
posterNameContent.push(element.textContent);
} else if (element.tagName.toLowerCase() === "img" && element.alt) {
posterNameContent.push(element.alt);
}
}
tweetMetadata.userName = posterNameContent.join("");
let texts = [];
let rawTexts = [];
const articleDivs = document
.querySelector("article div[lang]")
.querySelectorAll("a, span, img[alt]");
for (const element of articleDivs) {
let textContent = "";
let rawContent = "";
if (element.tagName.toLowerCase() === "a") {
const url = element.href;
let linkText = element.textContent.trim();
const span = element.querySelector("span");
if (span) {
let spanContent = span.textContent.trim();
linkText = linkText.replace(spanContent, "").trim();
}
textContent = `[url=${url}][color=#00bfff][u]${linkText}[/u][/color][/url]`;
rawContent = linkText;
} else if (element.tagName.toLowerCase() === "span") {
if (
!element.closest("a") &&
element.querySelectorAll("a").length === 0
) {
textContent = element.innerHTML;
rawContent = element.textContent;
}
} else if (element.tagName.toLowerCase() === "img" && element.alt) {
textContent = element.alt;
rawContent = element.alt;
}
if (textContent.trim()) {
texts.push(textContent);
rawTexts.push(rawContent.replace(/<a.*?>(.*?)<\/a>/g, "$1"));
}
}
tweetMetadata.text = texts.join("");
tweetMetadata.rawtext = rawTexts.join("");
//I have tried my best but failed, if it still returns 'http://' or 'https://', please add the link manually.
tweetMetadata.lang = document
.querySelector("article div[lang]")
.getAttribute("lang");
tweetMetadata.date = document.querySelector("time").innerHTML;
tweetMetadata.source = document.querySelector(
'article a[role="link"] span'
).innerText;
tweetMetadata.tweetLink = window.location.href;
return tweetMetadata;
}
function getTweetBbcode(tweet, mode) {
const attributeColor = "#5B7083";
const backgroundColor = mode === "dark" ? "#000000" : "#FFFFFF";
const foregroundColor = mode === "dark" ? "#D9D9D9" : "#0F1419";
const dateString = `${tweet.date} · ${tweet.source} · SPXXKLP v${spxxklpVersion} · 转载请注明原作者及本帖地址`;
const content = tweet.text;
const content1 = tweet.rawtext;
return `[align=center][table=560,${backgroundColor}]
[tr][td][indent][font=-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif]
[float=left][img=44,44]${
ProfilePictures.get(tweet.userTag) || "<不支持的头像,请手动添加图片链接>"
}[/img][/float][size=15px][b][color=${foregroundColor}]${
tweet.userName
}[/color][/b]
[color=${attributeColor}]@${tweet.userTag}[/color][/size]
[color=Silver][size=23px]${content1}[/color][/size]
[size=15px][color=${attributeColor}]由 ${GM_config.get("translator")} 翻译自${
tweet.lang.startsWith("en") ? "英语" : ` ${tweet.lang}`
}[/color][/size]
[color=${foregroundColor}][size=23px]${content}[/size]
[/size][/color][/indent][align=center]<如有配图,请在此处添加>[/align]
[indent][size=15px][url=${
tweet.tweetLink
}][color=${attributeColor}][u]${dateString}[/u][/color][/url][/size][/indent][/td][/tr]
[/table][/align]`;
}
function x() {
console.info("[SPXXKLP] Activated");
const buttonLight = document.createElement("button");
buttonLight.style.backgroundColor = "rgb(255, 255, 255)";
buttonLight.style.color = "#000000";
buttonLight.style.border = "none";
buttonLight.style.padding = "5px 10px";
buttonLight.style.borderRadius = "5px";
buttonLight.style.fontSize = "15px";
buttonLight.style.cursor = "pointer";
buttonLight.style.transition = "background-color 0.3s ease";
buttonLight.style.width = "180px";
buttonLight.style.height = "50px";
buttonLight.style.textAlign = "center";
buttonLight.style.marginLeft = "auto";
buttonLight.onmouseover = () => {
buttonLight.style.backgroundColor = "rgb(223, 223, 223)";
};
buttonLight.onmouseout = () => {
buttonLight.style.backgroundColor = "rgb(255, 255, 255)";
};
buttonLight.innerText = "复制 BBCode (KLPBBS)(浅色)";
buttonLight.onclick = async () => {
buttonLight.innerText = "处理中...";
try {
const bbcode = getTweetBbcode(getTweetMetadata(), "light");
GM_setClipboard(bbcode, { type: "text", mimetype: "text/plain" });
buttonLight.innerText = "已复制!";
} catch (error) {
console.error("Error processing BBCode (Light):", error);
buttonLight.innerText = "错误!";
}
setTimeout(() => (buttonLight.innerText = "复制 BBCode (KLPBBS)(浅色)"), 5000);
};
const buttonDark = document.createElement("button");
buttonDark.style.backgroundColor = "rgb(32, 32, 32)";
buttonDark.style.color = "#FFFFFF";
buttonDark.style.border = "none";
buttonDark.style.padding = "5px 10px";
buttonDark.style.borderRadius = "5px";
buttonDark.style.fontSize = "15px";
buttonDark.style.cursor = "pointer";
buttonDark.style.transition = "background-color 0.3s ease";
buttonDark.style.width = "180px";
buttonDark.style.height = "50px";
buttonDark.style.textAlign = "center";
buttonDark.style.marginLeft = "auto";
buttonDark.onmouseover = () => {
buttonDark.style.backgroundColor = "rgb(42, 42, 42)";
};
buttonDark.onmouseout = () => {
buttonDark.style.backgroundColor = "rgb(32, 32, 32)";
};
buttonDark.innerText = "复制 BBCode (KLPBBS)(深色)";
buttonDark.onclick = async () => {
buttonDark.innerText = "处理中...";
try {
const bbcode = getTweetBbcode(getTweetMetadata(), "dark");
GM_setClipboard(bbcode, { type: "text", mimetype: "text/plain" });
buttonDark.innerText = "已复制!";
} catch (error) {
console.error("Error processing BBCode (Dark):", error);
buttonDark.innerText = "错误!";
}
setTimeout(() => (buttonDark.innerText = "复制 BBCode (KLPBBS)(深色)"), 5000);
};
const checkLoaded = setInterval(() => {
const targetDiv = document.querySelector("article div[lang]");
if (targetDiv && !document.querySelector("#spxxklp-buttons")) {
const container = document.createElement("div");
container.id = "spxxklp-buttons";
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.alignItems = "flex-end";
container.style.width = "100%";
container.style.padding = "10px";
container.style.boxSizing = "border-box";
container.append(buttonLight);
container.append(buttonDark);
targetDiv.parentElement.append(container);
clearInterval(checkLoaded);
}
}, 300);
}
x();
}
switch (location.host) {
case "www.minecraft.net": //Fuck minecraft.net what the heck are you doing.
minecraftNet();
break;
case "x.com":
twitter();
break;
case "feedback.minecraft.net":
feedback();
break;
case "help.minecraft.net":
help();
break;
}
})();
//# sourceMappingURL=bundle.user.js.map