Skip to content

Commit b51498f

Browse files
authored
Merge pull request #7 from AdjieC/dev
feat: 优化脚本稳定性并增加中文注释
2 parents d9c54f9 + c712fe9 commit b51498f

File tree

1 file changed

+139
-45
lines changed

1 file changed

+139
-45
lines changed

renew.user.js

Lines changed: 139 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// ==UserScript==
22
// @name Extend VPS Expiration
3+
// @name:zh-CN Xserver VPS 自动续期脚本
34
// @namespace http://tampermonkey.net/
4-
// @version 2025-07-17
5-
// @description Auto Renew Free VPS Expiration
5+
// @version 2025-07-21
6+
// @description Automatically renews the expiration date of free Xserver VPS.
7+
// @description:zh-CN 自动为 Xserver 的免费 VPS 续期。
68
// @author You
79
// @match https://secure.xserver.ne.jp/xapanel*/xvps*
810
// @icon https://www.google.com/s2/favicons?sz=64&domain=xserver.ne.jp
@@ -13,48 +15,140 @@
1315
// @supportURL https://github.com/GitHub30/extend-vps-exp
1416
// ==/UserScript==
1517

16-
// Usage:
17-
// 1. Please bookmark https://secure.xserver.ne.jp/xapanel/login/xvps/
18-
// 2. Open the URL everyday
19-
// 3. (option) Save email and password on Login page
20-
21-
// Login
22-
if (location.pathname.startsWith('/xapanel/login/xvps')) {
23-
const memberid = GM_getValue('memberid')
24-
const user_password = GM_getValue('user_password')
25-
if (memberid && user_password && !document.querySelector('.errorMessage')) {
26-
unsafeWindow.memberid.value = memberid
27-
unsafeWindow.user_password.value = user_password
28-
unsafeWindow.loginFunc()
18+
/*
19+
* =================================================================================================
20+
* 使用说明 (Usage Instructions)
21+
* =================================================================================================
22+
* 1. 请将登录页面设为浏览器书签: https://secure.xserver.ne.jp/xapanel/login/xvps/
23+
* (Bookmark the login page)
24+
*
25+
* 2. 每天访问一次该书签。
26+
* (Visit the bookmark once every day.)
27+
*
28+
* 3. (可选) 首次访问时,在登录页面输入您的邮箱和密码,脚本会自动保存。之后访问将自动填充和登录。
29+
* (Optional) On your first visit, enter your email and password on the login page.
30+
* The script will save them automatically for future auto-login.
31+
*
32+
* =================================================================================================
33+
* 工作流程 (Workflow)
34+
* =================================================================================================
35+
* 1. 登录页面: 自动填充已保存的凭据并提交。
36+
* (Login Page: Auto-fills saved credentials and submits.)
37+
*
38+
* 2. VPS管理主页: 检查免费VPS的到期日期。如果明天到期,则跳转到续期页面。
39+
* (VPS Dashboard: Checks the expiration date. If it expires tomorrow, it navigates to the renewal page.)
40+
*
41+
* 3. 续期申请页: 自动点击“确认”按钮,进入验证码页面。
42+
* (Renewal Page: Clicks the confirmation button to proceed to the CAPTCHA page.)
43+
*
44+
* 4. 验证码页:
45+
* a. 提取验证码图片。
46+
* b. 发送到外部API服务进行识别。
47+
* c. 自动填充识别结果。
48+
* d. 监听 Cloudflare Turnstile (一种人机验证) 的令牌生成,一旦生成,立即提交表单。
49+
* (CAPTCHA Page: Extracts the CAPTCHA image, sends it to a recognition service,
50+
* fills the result, and submits the form once the Cloudflare Turnstile token is ready.)
51+
* =================================================================================================
52+
*/
53+
54+
(function() {
55+
'use strict';
56+
57+
/**
58+
* @description 登录页面逻辑:自动填充并保存用户凭据。
59+
*/
60+
if (location.pathname.startsWith('/xapanel/login/xvps')) {
61+
const memberid = GM_getValue('memberid');
62+
const user_password = GM_getValue('user_password');
63+
64+
// 如果存在已保存的凭据且页面没有显示错误消息,则自动填充并登录
65+
if (memberid && user_password && !document.querySelector('.errorMessage')) {
66+
unsafeWindow.memberid.value = memberid;
67+
unsafeWindow.user_password.value = user_password;
68+
// 调用页面自带的登录函数
69+
unsafeWindow.loginFunc();
70+
}
71+
72+
// 监听登录表单的提交事件,以便保存用户输入的凭据
73+
// 确保 jQuery 已加载
74+
if (typeof $ !== 'undefined') {
75+
$('#login_area').on('submit', () => {
76+
GM_setValue('memberid', unsafeWindow.memberid.value);
77+
GM_setValue('user_password', unsafeWindow.user_password.value);
78+
});
79+
}
80+
}
81+
82+
/**
83+
* @description VPS 管理主页逻辑:检查到期时间并跳转。
84+
*/
85+
if (location.pathname.startsWith('/xapanel/xvps/index')) {
86+
// 计算明天的日期,格式为 YYYY-MM-DD (瑞典时区格式)
87+
const tomorrow = new Date(Date.now() + 86400000).toLocaleDateString('sv');
88+
const expireDate = document.querySelector('tr:has(.freeServerIco) .contract__term')?.textContent;
89+
90+
// 如果到期日是明天,则准备续期
91+
if (expireDate === tomorrow) {
92+
const href = document.querySelector('tr:has(.freeServerIco) a[href^="/xapanel/xvps/server/detail?id="]').href;
93+
// 跳转到续期页面
94+
location.href = href.replace('detail?id', 'freevps/extend/index?id_vps');
95+
}
2996
}
30-
// eslint-disable-next-line no-undef
31-
$('#login_area').on('submit', () => {
32-
GM_setValue('memberid', unsafeWindow.memberid.value)
33-
GM_setValue('user_password', unsafeWindow.user_password.value)
34-
})
35-
}
36-
37-
// Check expiration date
38-
if (location.pathname.startsWith('/xapanel/xvps/index')) {
39-
const tomorrow = new Date(Date.now() + 864e5).toLocaleDateString('sv')
40-
const expireDate = document.querySelector('tr:has(.freeServerIco) .contract__term')?.textContent
41-
if (expireDate === tomorrow) {
42-
const href = document.querySelector('tr:has(.freeServerIco) a[href^="/xapanel/xvps/server/detail?id="]').href
43-
location = href.replace('detail?id', 'freevps/extend/index?id_vps')
97+
98+
/**
99+
* @description 续期申请页面逻辑:自动点击确认按钮。
100+
*/
101+
if (location.pathname.startsWith('/xapanel/xvps/server/freevps/extend/index')) {
102+
const extendButton = document.querySelector('[formaction="/xapanel/xvps/server/freevps/extend/conf"]');
103+
if (extendButton) {
104+
extendButton.click();
105+
}
106+
}
107+
108+
/**
109+
* @description 验证码页面逻辑:识别并提交验证码。
110+
* 使用 IIFE (立即调用的函数表达式) 来创建异步上下文,以便使用 await。
111+
*/
112+
if ((location.pathname.startsWith('/xapanel/xvps/server/freevps/extend/conf') || location.pathname.startsWith('/xapanel/xvps/server/freevps/extend/do')) && unsafeWindow.submit_button) {
113+
(async function() {
114+
try {
115+
const img = document.querySelector('img[src^="data:"]');
116+
if (!img) {
117+
console.log('CAPTCHA image not found.');
118+
return;
119+
}
120+
121+
const body = img.src;
122+
// 调用外部API来识别验证码图片
123+
const code = await fetch('https://captcha-120546510085.asia-northeast1.run.app', { method: 'POST', body }).then(r => r.text());
124+
125+
const input = document.querySelector('[placeholder="上の画像の数字を入力"]');
126+
if (input) {
127+
input.value = code;
128+
}
129+
130+
// 处理 Cloudflare Turnstile 人机验证
131+
const cf = document.querySelector('.cf-turnstile [name=cf-turnstile-response]');
132+
if (cf) {
133+
// 如果令牌已经存在,直接点击提交
134+
if (cf.value) {
135+
unsafeWindow.submit_button.click();
136+
return;
137+
}
138+
// 如果令牌尚不存在,则使用 MutationObserver 监听其值的变化
139+
// 一旦 cf-turnstile-response 的 value 被填充,就立即点击提交按钮
140+
new MutationObserver((mutationsList, observer) => {
141+
for(const mutation of mutationsList) {
142+
if (mutation.type === 'attributes' && mutation.attributeName === 'value' && cf.value) {
143+
unsafeWindow.submit_button.click();
144+
observer.disconnect(); // 任务完成,停止监听
145+
}
146+
}
147+
}).observe(cf, { attributes: true, attributeFilter: ['value'] });
148+
}
149+
} catch (error) {
150+
console.error('Error solving CAPTCHA:', error);
151+
}
152+
})();
44153
}
45-
}
46-
47-
// Extend expiration date
48-
if (location.pathname.startsWith('/xapanel/xvps/server/freevps/extend/index')) {
49-
document.querySelector('[formaction="/xapanel/xvps/server/freevps/extend/conf"]').click()
50-
}
51-
52-
// Solve CAPTCHA
53-
if ((location.pathname.startsWith('/xapanel/xvps/server/freevps/extend/conf') || location.pathname.startsWith('/xapanel/xvps/server/freevps/extend/do')) && unsafeWindow.submit_button) {
54-
const body = document.querySelector('img[src^="data:"]').src
55-
const code = await fetch('https://captcha-120546510085.asia-northeast1.run.app', { method: 'POST', body }).then(r => r.text())
56-
document.querySelector('[placeholder="上の画像の数字を入力"]').value = code
57-
const cf = document.querySelector('.cf-turnstile [name=cf-turnstile-response]')
58-
if (cf.value) unsafeWindow.submit_button.click()
59-
new MutationObserver(() => unsafeWindow.submit_button.click()).observe(cf, { attributes: true, attributeFilter: ['value'] })
60-
}
154+
})();

0 commit comments

Comments
 (0)