优化自动兑换码 (#2024)

* 优化自动兑换码

* 优化自动兑换码

* 修改readme
This commit is contained in:
躁动的氨气
2025-09-28 20:10:26 +08:00
committed by GitHub
parent 904475dc12
commit 02ff2fba78
5 changed files with 132 additions and 59 deletions

View File

@@ -1,3 +1,18 @@
兑换码+截止时间存储地址codes.txt
格式 兑换码,xxxx.xx.xx xx:xx:xx
仅支持国服
# 原神兑换码自动兑换工具使用说明
## 准备工作
若你兑换的是自己通过某些渠道获得的专属兑换码(如联动套餐),
请在`BetterGI\User\JsScript\AutoCode\codes.txt`中按照`兑换码,截止时间`的格式填入兑换码(格式务必参考已有内容,且使用英文逗号)
若你兑换的是公共渠道的兑换码(如前瞻直播、周年庆福利兑换码等) ,那恭喜你,不需要任何准备工作!直接点击即用!
## 用户名设置(多用户使用)
添加脚本进调度器后右键调度器内js脚本点击 `修改js脚本自定义设置` ,在其中设置用户名
### 用户名规则:
```json
支持中文、英文、数字
长度 1-20 个字符
如未设置或格式错误,将使用默认用户名 "default"

View File

@@ -4,3 +4,4 @@ VYKGJWZHY2AN,2025.10.20 00:00:00
CY2H2XHYZKCS,2025.10.20 00:00:00
NG3ZJXHYG3CW,2025.10.20 00:00:00
NG2Z3EHYYKVJ,2025.10.20 00:00:00
原神5周年快乐,2025.11.1 00:00:00

View File

@@ -1,117 +1,158 @@
let username = settings.useename || "default";
(async function () {
setGameMetrics(1920, 1080, 1);
// 1. 返回主界面等待1秒
setGameMetrics(1920, 1080, 1);
// 1. 返回主界面
await genshin.returnMainUi();
await sleep(1000);
// 2. 通过keyPress点按esc键(VK_ESCAPE)等待2秒。ocr识别设置图片并点击等待2秒。识别账户图片并点击等待0.5秒识别前往兑换图片并点击等待0.5秒
// 2. 检验设置用户名
function getUsername() {
username = username.trim();
// 只允许 中文 / 英文 / 数字,长度 1~20
if (!username || !/^[\u4e00-\u9fa5A-Za-z0-9]{1,20}$/.test(username)) {
log.error(`用户名${username}违规暂时使用默认用户名请查看readme后修改`)
username = "default";
}
return username;
}
username = getUsername();
const recordPath = `record/record_${username}.txt`;
// 3. 读取已兑换记录
let redeemedCodes = new Set();
try {
const recordContent = file.readTextSync(recordPath);
if (recordContent) {
redeemedCodes = new Set(recordContent.split("\n").map(l => l.trim()).filter(Boolean));
}
} catch (e) {
log.warn(`未找到 ${recordPath},稍后会自动创建`);
}
// 4. 打开兑换界面
keyPress("ESCAPE");
await sleep(2000);
const settingsRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/settings.png"));
const settingsRes = captureGameRegion().find(settingsRo);
if (settingsRes.isExist()) {
settingsRes.click();
}
if (settingsRes.isExist()) settingsRes.click();
await sleep(2000);
const accountRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/account.png"));
const accountRes = captureGameRegion().find(accountRo);
if (accountRes.isExist()) {
accountRes.click();
}
if (accountRes.isExist()) accountRes.click();
await sleep(500);
const goToRedeemRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/go_to_redeem.png"));
const goToRedeemRes = captureGameRegion().find(goToRedeemRo);
if (goToRedeemRes.isExist()) {
goToRedeemRes.click();
}
if (goToRedeemRes.isExist()) goToRedeemRes.click();
await sleep(500);
// 3. 新建一个txt用于存储兑换码及截止时间之间换行区分格式为【兑换码截止时间】
// 5. 读取 codes.txt 进行兑换
try {
const content = file.readTextSync("codes.txt");
const codes = content.split('\n');
const codes = content.split("\n");
for (let i = 0; i < codes.length; i++) {
const codeInfo = codes[i].split(',');
const code = codeInfo[0];
const deadline = codeInfo[1];
const line = codes[i].trim();
if (!line) continue;
// a. 获取当前时间【xxxx.xx.xx xx:xx:xx】(年月日时分秒),与截止时间进行对比
// 找到最后一个英文逗号
const lastCommaIndex = line.lastIndexOf(',');
let code, deadline;
if (lastCommaIndex === -1) {
// 没有逗号则整个当作code
code = line;
deadline = '';
} else {
code = line.slice(0, lastCommaIndex).trim();
deadline = line.slice(lastCommaIndex + 1).trim();
}
if (!code) continue;
// 跳过已兑换的
if (redeemedCodes.has(code)) {
log.info(`检测到${redeemedCodes.size}个兑换码已兑换过,跳过`);
continue;
}
// 时间检查
const now = new Date();
const currentTime = now.getFullYear() + '.' + String(now.getMonth() + 1).padStart(2, '0') + '.' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0');
if (currentTime > deadline) {
log.info(`兑换码【${code}】已超过截止时间,跳过`);
continue;
}
// b. 识别输入兑换码图片并点击
// 输入兑换码
const inputCodeRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/input_code.png"));
const inputCodeRes = captureGameRegion().find(inputCodeRo);
if (inputCodeRes.isExist()) {
inputCodeRes.click();
}
if (inputCodeRes.isExist()) inputCodeRes.click();
await sleep(300);
// c. 通过虚拟键代码依次keyPress键入兑换码的每一个字符
await inputText(code);
await sleep(500);
await sleep(500);
// d. 输入完毕后识别兑换图片并点击等待1.5秒
// 点击兑换按钮
const redeemRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/redeem.png"));
const redeemRes = captureGameRegion().find(redeemRo);
if (redeemRes.isExist()) {
redeemRes.click();
}
if (redeemRes.isExist()) redeemRes.click();
await sleep(1500);
// e. 识别无效图片、已使用图片、过期图片、确认图片、未开启图片
const invalidRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/invalid.png"));
const invalidRes = captureGameRegion().find(invalidRo);
if (invalidRes.isExist()) {
log.info(`兑换码【${code}】无效`);
}
// 检测各种状态
const invalidRes = captureGameRegion().find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/invalid.png")));
if (invalidRes.isExist()) log.info(`兑换码【${code}】无效`);
const usedRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/used.png"));
const usedRes = captureGameRegion().find(usedRo);
const usedRes = captureGameRegion().find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/used.png")));
if (usedRes.isExist()) {
log.info(`兑换码【${code}】已使用`);
// 写入记录
const writeOk = file.writeTextSync(recordPath, code + "\n", true);
if (writeOk) {
log.info(`兑换码【${code}】已使用`);
redeemedCodes.add(code);
}
}
const expiredRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/expired.png"));
const expiredRes = captureGameRegion().find(expiredRo);
const expiredRes = captureGameRegion().find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/expired.png")));
if (expiredRes.isExist()) {
log.info(`兑换码【${code}】已过期`);
// 写入记录
const writeOk = file.writeTextSync(recordPath, code + "\n", true);
if (writeOk) {
log.info(`兑换码【${code}】已过期`);
redeemedCodes.add(code);
}
}
const notopenRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/not_open.png"));
const notopenRes = captureGameRegion().find(notopenRo);
if (notopenRes.isExist()) {
log.info(`兑换码【${code}】未开启`);
}
const notopenRes = captureGameRegion().find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/not_open.png")));
if (notopenRes.isExist()) log.info(`兑换码【${code}】未开启`);
const confirmRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/confirm.png"));
const confirmRes = captureGameRegion().find(confirmRo);
const confirmRes = captureGameRegion().find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/confirm.png")));
if (confirmRes.isExist()) {
log.info(`兑换码【${code}】成功兑换`);
confirmRes.click();
// 写入记录
const writeOk = file.writeTextSync(recordPath, code + "\n", true);
if (writeOk) {
log.info(`已记录兑换码【${code}】到 ${recordPath}`);
redeemedCodes.add(code);
}
}
// f. 识别清除图片并点击,若未识别到则不做处理
const clearRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/clear.png"));
const clearRes = captureGameRegion().find(clearRo);
if (clearRes.isExist()) {
clearRes.click();
}
// 清除输入
const clearRes = captureGameRegion().find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/clear.png")));
if (clearRes.isExist()) clearRes.click();
await sleep(4000);
}
} catch (error) {
log.error(`读取兑换码文件失败: ${error}`);
}
// 4. 所有兑换码兑换完成后返回主界面
// 6. 返回主界面
await genshin.returnMainUi();
})();

View File

@@ -8,8 +8,16 @@
{
"name": "Tool_tingsu",
"links": "https://github.com/Tooltingsu"
},
{
"name": "躁动的氨气",
"links": "https://github.com/zaodonganqi"
}
],
"main": "main.js"
"settings_ui": "settings.json",
"main": "main.js",
"saved_files": [
"record/*.txt"
]
}

View File

@@ -0,0 +1,8 @@
[
{
"name": "username",
"type": "input-text",
"label": "账户名称\n用于多账户运行时区分不同账户",
"default": "默认账户"
}
]