From e410e24cb728e44ab5a4cad901f76f09b88850d8 Mon Sep 17 00:00:00 2001 From: pythonSmall-Q Date: Fri, 24 Apr 2026 20:36:38 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=9A=90=E7=A7=81?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E3=80=81=E5=84=BF=E7=AB=A5=E4=BF=9D=E6=8A=A4?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E5=92=8C=E6=9C=8D=E5=8A=A1=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=EF=BC=8C=E5=B9=B6=E6=9B=B4=E6=96=B0=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E5=A4=87=E6=A1=88=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- child-protection.html | 36 ++++++++++++++++++++++++++++++++++++ index.html | 11 ++++++++++- privacy.html | 36 ++++++++++++++++++++++++++++++++++++ terms.html | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 child-protection.html create mode 100644 privacy.html create mode 100644 terms.html diff --git a/child-protection.html b/child-protection.html new file mode 100644 index 00000000..f2278440 --- /dev/null +++ b/child-protection.html @@ -0,0 +1,36 @@ + + + + + + 儿童保护协议 - 小明的OJ增强脚本 + + + + +
+

儿童保护协议

+

生效日期:2026-04-24

+ +

一、适用范围

+

本协议适用于未满十四周岁儿童相关信息处理活动,以及监护人对儿童使用服务的管理与监督。

+ +

二、监护人责任

+

监护人应指导并监督儿童合理使用网络服务,避免泄露隐私信息,防止沉迷与不当互动。

+ +

三、儿童信息保护

+

我们坚持最小必要原则处理信息,不会在无正当理由情况下收集与服务无关的儿童个人信息。

+ +

四、风险防护措施

+

我们将持续优化内容展示、交互提醒和异常行为识别机制,降低儿童接触不适宜内容和网络风险的可能性。

+ +

五、监护人权利

+

监护人有权查阅、更正或删除儿童相关信息,并可通过反馈渠道提出限制处理、停止服务等申请。

+ +

六、联系我们

+

如您对儿童信息保护有疑问,请通过项目公开反馈渠道联系我们,我们会及时处理并反馈结果。

+ +

返回首页

+
+ + diff --git a/index.html b/index.html index 522de611..f62c8fe7 100644 --- a/index.html +++ b/index.html @@ -968,7 +968,16 @@

+
+
我们的网站在萌国备案啦!我们的备案号是:萌ICP备20240425号
+ +
-
我们的网站在萌国备案啦!我们的备案号是:萌ICP备20240425号
diff --git a/privacy.html b/privacy.html new file mode 100644 index 00000000..3f4aa5f9 --- /dev/null +++ b/privacy.html @@ -0,0 +1,36 @@ + + + + + + 隐私协议 - 小明的OJ增强脚本 + + + + +
+

隐私协议

+

生效日期:2026-04-24

+ +

一、我们收集的信息

+

为提供基础功能,我们可能会处理您在使用脚本时主动提供的数据,包括账号标识、消息内容、页面偏好设置、日志与故障排查信息。

+ +

二、信息使用目的

+

我们仅在实现脚本功能、改进产品体验、保障服务安全与处理用户反馈的必要范围内使用信息,不会将您的信息用于与服务无关的用途。

+ +

三、信息存储与保护

+

我们采取合理的技术与管理措施保护数据安全,尽量减少未授权访问、披露、篡改或丢失风险。数据仅在达到处理目的所需期限内保存。

+ +

四、信息共享与披露

+

除法律法规另有要求,或经您明确同意外,我们不会向无关第三方出售、出租或共享您的个人信息。

+ +

五、您的权利

+

您有权访问、更正、删除相关信息,也可以通过项目反馈渠道申请注销相关服务能力。我们会在合理期限内处理您的请求。

+ +

六、协议更新

+

本协议可能根据功能调整而更新。更新后将通过官网页面公示,继续使用服务即视为您已阅读并同意更新内容。

+ +

返回首页

+
+ + diff --git a/terms.html b/terms.html new file mode 100644 index 00000000..12b52040 --- /dev/null +++ b/terms.html @@ -0,0 +1,36 @@ + + + + + + 服务协议 - 小明的OJ增强脚本 + + + + +
+

服务协议

+

生效日期:2026-04-24

+ +

一、协议说明

+

本协议用于规范您使用小明的OJ增强脚本及相关网页服务的行为。您访问或使用服务即表示同意本协议条款。

+ +

二、服务内容

+

服务包含用户脚本功能、页面增强能力以及相关支持页面。我们会在不降低整体安全性的前提下持续迭代功能。

+ +

三、用户义务

+

您应遵守适用法律法规,不得利用服务从事违法违规活动,不得干扰平台正常运行或侵犯他人合法权益。

+ +

四、知识产权

+

本项目代码及文档受开源协议及相关法律保护。您在使用、修改或分发时需遵守对应许可证条款。

+ +

五、免责与限制

+

在法律允许范围内,服务按“现状”提供。因网络、第三方平台变更、不可抗力或用户自身操作导致的损失,我们不承担超出法律要求的责任。

+ +

六、协议变更与终止

+

我们有权根据业务调整更新协议。若您不同意更新内容,可停止使用服务。您违反本协议时,我们可采取限制措施。

+ +

返回首页

+
+ + From 9abf256c4a40dadfebff4cd842256b65fc9ecfc7 Mon Sep 17 00:00:00 2001 From: pythonSmall-Q Date: Fri, 24 Apr 2026 20:54:05 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E4=B8=AD=E5=BF=83=E7=99=BB=E5=BD=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=B9=B6=E6=9B=B4=E6=96=B0=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=92=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- index.html | 3 ++ messages.html | 114 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index f62c8fe7..831cf471 100644 --- a/index.html +++ b/index.html @@ -55,6 +55,9 @@ + diff --git a/messages.html b/messages.html index ea7e2484..06b43e2e 100644 --- a/messages.html +++ b/messages.html @@ -73,6 +73,9 @@

登录

+ @@ -216,6 +219,22 @@
第二步:在 XMOJ 上点击书签
+ + + @@ -377,6 +396,11 @@ const SCROLL_THRESHOLD = 80; const MAX_IMAGE_BYTES = 5 * 1024 * 1024; const PREVIEW_LEN = 60; +const OAUTH_AUTHORIZE_URL = 'https://sso.xmoj-bbs.me/authorize'; +const OAUTH_CLIENT_ID = 'app_0f4cbbddfa42c0c4576cba8f7598d0a2'; +const OAUTH_SCOPE = 'openid profile email xmoj_profile'; +const OAUTH_REDIRECT_URI = 'https://xmoj-bbs.me/messages.html'; +const STORAGE_OAUTH_STATE = 'xmoj-msg-oauth-state'; // ── State ────────────────────────────────────────────────────────────────── let currentUser = null; // { username, phpsessid } @@ -931,6 +955,7 @@ }); document.getElementById('tab-bookmarklet').style.display = tab === 'bookmarklet' ? '' : 'none'; document.getElementById('tab-manual').style.display = tab === 'manual' ? '' : 'none'; + document.getElementById('tab-sso').style.display = tab === 'sso' ? '' : 'none'; } document.querySelectorAll('#loginTabs .nav-link').forEach(function(btn) { @@ -939,6 +964,89 @@ }); }); +function generateUuidV4() { + if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID(); + if (window.crypto && window.crypto.getRandomValues) { + var bytes = new Uint8Array(16); + window.crypto.getRandomValues(bytes); + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + var hex = Array.from(bytes).map(function(b) { return b.toString(16).padStart(2, '0'); }).join(''); + return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20)].join('-'); + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0; + var v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +function startSsoLogin() { + var state = generateUuidV4() + '.' + generateUuidV4(); + localStorage.setItem(STORAGE_OAUTH_STATE, state); + var params = new URLSearchParams({ + response_type: 'code', + client_id: OAUTH_CLIENT_ID, + redirect_uri: OAUTH_REDIRECT_URI, + scope: OAUTH_SCOPE, + state: state + }); + location.href = OAUTH_AUTHORIZE_URL + '?' + params.toString(); +} + +async function exchangeSsoCode(code, state) { + var res = await fetch(API_BASE + 'ExchangeSSOCode', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + Code: code, + State: state, + RedirectURI: OAUTH_REDIRECT_URI, + ClientID: OAUTH_CLIENT_ID + }) + }); + if (!res.ok) throw new Error('HTTP ' + res.status); + return res.json(); +} + +async function handleSsoCallback() { + var url = new URL(location.href); + var code = url.searchParams.get('code'); + var state = url.searchParams.get('state'); + if (!code) return false; + + var expectedState = localStorage.getItem(STORAGE_OAUTH_STATE); + if (!state || !expectedState || state !== expectedState) { + showToast('统一认证登录失败:state 校验失败', 'danger'); + url.searchParams.delete('code'); + url.searchParams.delete('state'); + history.replaceState(null, '', url.pathname + (url.searchParams.toString() ? '?' + url.searchParams.toString() : '')); + return false; + } + + try { + var result = await exchangeSsoCode(code, state); + if (!result || !result.Success || !result.Data) { + throw new Error((result && result.Message) || '后端未返回有效登录数据'); + } + if (!result.Data.Username || !result.Data.SessionID) { + throw new Error('返回数据缺少 Username 或 SessionID'); + } + + localStorage.removeItem(STORAGE_OAUTH_STATE); + saveSession(result.Data.Username, result.Data.SessionID); + url.searchParams.delete('code'); + url.searchParams.delete('state'); + history.replaceState(null, '', url.pathname + (url.searchParams.toString() ? '?' + url.searchParams.toString() : '')); + showToast('统一认证登录成功', 'success'); + onLoggedIn(); + return true; + } catch (err) { + showToast('统一认证登录失败:' + err.message, 'danger'); + return false; + } +} + // ── Event Wiring ─────────────────────────────────────────────────────────── document.getElementById('btn-manual-login').addEventListener('click', function() { var username = document.getElementById('input-username').value.trim(); @@ -948,6 +1056,8 @@ onLoggedIn(); }); +document.getElementById('btn-sso-login').addEventListener('click', startSsoLogin); + document.getElementById('btn-logout').addEventListener('click', logout); document.getElementById('btn-refresh-list').addEventListener('click', loadMailList); @@ -1034,7 +1144,7 @@ }); // ── Boot ─────────────────────────────────────────────────────────────────── -(function init() { +(async function init() { initTheme(); initBookmarklet(); @@ -1050,6 +1160,8 @@ return; } + if (await handleSsoCallback()) return; + if (loadSession()) { onLoggedIn(); } else { From f6b0dcc97f4dc9963357fba5f913228fa1038a7c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 May 2026 18:18:07 +0800 Subject: [PATCH 3/3] Switch all backend endpoints to api.xmoj-script.uk (#980) * switch endpoint to api.xmoj-script.uk for everything Agent-Logs-Url: https://github.com/XMOJ-Script-dev/XMOJ-Script/sessions/3a87c529-2f63-4617-8be5-5b9f3c33f751 Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> * Update ASSET_BASE URL to use assets subdomain Signed-off-by: Shan Wenxiao * 3.4.6 * Update version info to 3.4.6 * Update image upload URLs to new asset location Signed-off-by: Shan Wenxiao * Update time and description of 3.4.6 * Update ServerURL for script debugging Signed-off-by: Shan Wenxiao * Update time and description of 3.4.6 * Update SSO button for development version Signed-off-by: Shan Wenxiao * Update time and description of 3.4.6 * Remove badge from messages link in navigation Signed-off-by: Shan Wenxiao * Update time and description of 3.4.6 --------- Signed-off-by: Shan Wenxiao Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> Co-authored-by: Shan Wenxiao Co-authored-by: github-actions[bot] --- Update.json | 11 +++++++++++ XMOJ.user.js | 21 +++++++++++---------- index.html | 4 +--- messages.html | 7 +++---- package.json | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Update.json b/Update.json index 03af9550..e5371722 100644 --- a/Update.json +++ b/Update.json @@ -3574,6 +3574,17 @@ } ], "Notes": "Fix loginpage.php loop." + }, + "3.4.6": { + "UpdateDate": 1777708421566, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 980, + "Description": "Switch all backend endpoints to api.xmoj-script.uk" + } + ], + "Notes": "No release notes were provided for this release." } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 57d59748..e9d5874f 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.4.5 +// @version 3.4.6 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen @@ -21,10 +21,11 @@ // @grant GM_setValue // @grant GM_getValue // @grant GM_cookie -// @homepage https://www.xmoj-bbs.me/ -// @supportURL https://support.xmoj-bbs.me/form/8050213e-c806-4680-b414-0d1c48263677 +// @homepage https://www.xmoj-script.uk/ +// @supportURL https://github.com/XMOJ-Script-dev/XMOJ-Script/issues // @connect api.xmoj-bbs.tech // @connect api.xmoj-bbs.me +// @connect api.xmoj-script.uk // @connect challenges.cloudflare.com // @connect cppinsights.io // @connect cdnjs.cloudflare.com @@ -505,7 +506,7 @@ let RequestAPI = (Action, Data, CallBack) => { } GM_xmlhttpRequest({ method: "POST", - url: (UtilityEnabled("SuperDebug") ? "http://127.0.0.1:8787/" : "https://api.xmoj-bbs.me/") + Action, + url: (UtilityEnabled("SuperDebug") ? "http://127.0.0.1:8787/" : "https://api.xmoj-script.uk/") + Action, headers: { "Content-Type": "application/json", "Cache-Control": "no-cache", @@ -643,7 +644,7 @@ function ConnectNotificationSocket() { return; } - let wsUrl = (UtilityEnabled("SuperDebug") ? "ws://127.0.0.1:8787" : "wss://api.xmoj-bbs.me") + "/ws/notifications?SessionID=" + Session; + let wsUrl = (UtilityEnabled("SuperDebug") ? "ws://127.0.0.1:8787" : "wss://api.xmoj-script.uk") + "/ws/notifications?SessionID=" + Session; if (UtilityEnabled("DebugMode")) { console.log("WebSocket: Connecting to", wsUrl); @@ -922,7 +923,7 @@ if (UtilityEnabled("AutoLogin") && document.querySelector("body > a:nth-child(1) } let SearchParams = new URLSearchParams(location.search); -let ServerURL = (UtilityEnabled("DebugMode") ? "https://ghpages.xmoj-bbs.me/" : "https://www.xmoj-bbs.me") +let ServerURL = (UtilityEnabled("DebugMode") ? "https://ghpages.xmoj-script.uk/" : "https://www.xmoj-script.uk") if (document.querySelector("#profile") === null && !logined) { location.href = "https://www.xmoj.tech/loginpage.php"; } @@ -5026,7 +5027,7 @@ int main() "Image": Reader.result }, (ResponseData) => { if (ResponseData.Success) { - Content.value = Before + `![](https://assets.xmoj-bbs.me/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; + Content.value = Before + `![](https://assets.xmoj-script.uk/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; Content.dispatchEvent(new Event("input")); } else { Content.value = Before + `![上传失败!` + ResponseData.Message + `]()` + After; @@ -5282,7 +5283,7 @@ int main() "Image": Reader.result }, (ResponseData) => { if (ResponseData.Success) { - ContentElement.value = Before + `![](https://assets.xmoj-bbs.me/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; + ContentElement.value = Before + `![](https://assets.xmoj-script.uk/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; ContentElement.dispatchEvent(new Event("input")); } else { ContentElement.value = Before + `![上传失败!]()` + After; @@ -5455,7 +5456,7 @@ int main() "Image": Reader.result }, (ResponseData) => { if (ResponseData.Success) { - ContentElement.value = Before + `![](https://assets.xmoj-bbs.me/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; + ContentElement.value = Before + `![](https://assets.xmoj-script.uk/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; ContentElement.dispatchEvent(new Event("input")); } else { ContentElement.value = Before + `![上传失败!]()` + After; @@ -5713,7 +5714,7 @@ int main() "Image": Reader.result }, (ResponseData) => { if (ResponseData.Success) { - ContentEditor.value = Before + `![](https://assets.xmoj-bbs.me/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; + ContentEditor.value = Before + `![](https://assets.xmoj-script.uk/GetImage?ImageID=${ResponseData.Data.ImageID})` + After; ContentEditor.dispatchEvent(new Event("input")); } else { ContentEditor.value = Before + `![上传失败!]()` + After; diff --git a/index.html b/index.html index 831cf471..fda3f205 100644 --- a/index.html +++ b/index.html @@ -48,9 +48,7 @@ 关于 @@ -386,8 +386,8 @@ 'use strict'; // ── Constants ────────────────────────────────────────────────────────────── -const API_BASE = 'https://api.xmoj-bbs.me/'; -const ASSET_BASE = 'https://assets.xmoj-bbs.me/GetImage?ImageID='; +const API_BASE = 'https://api.xmoj-script.uk/'; +const ASSET_BASE = 'https://assets.xmoj-script.uk/GetImage?ImageID='; const XMOJ_BASE = 'https://www.xmoj.tech'; const WEBUI_VERSION = 'webui-1.0.0'; const STORAGE_USER = 'xmoj-msg-username'; @@ -991,7 +991,6 @@ scope: OAUTH_SCOPE, state: state }); - location.href = OAUTH_AUTHORIZE_URL + '?' + params.toString(); } async function exchangeSsoCode(code, state) { diff --git a/package.json b/package.json index e2238633..a2e6b736 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.4.5", + "version": "3.4.6", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": {