rebuild release branch

This commit is contained in:
huiyadanli
2026-05-29 12:49:22 +00:00
committed by github-actions[bot]
commit 03c6f46b5e
14030 changed files with 1590555 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
# 原神兑换码自动兑换工具使用说明
## 准备工作
若你兑换的是自己通过某些渠道获得的专属兑换码(如联动套餐),
请在`BetterGI\User\JsScript\AutoCode\codes.txt`中按照`兑换码,截止时间`的格式填入兑换码(格式务必参考已有内容,且使用英文逗号)
若你兑换的是公共渠道的兑换码(如前瞻直播、周年庆福利兑换码等) ,那恭喜你,不需要任何准备工作!直接点击即用!
## 使用BGI提供的兑换码列表
可在设置中选择`使用BGI提供的兑换码列表`,这将通过网络实时获取最新的前瞻兑换码,并记录你的兑换历史,不需要更新此脚本也能长久使用!
> 此功能需要同时在调度器中右键脚本,点击`修改通用配置`,启用`JS Http权限`,否则将无法运行。
## 用户名设置(多用户使用)
添加脚本进调度器后右键调度器内js脚本点击 `修改js脚本自定义设置` ,在其中设置用户名
### 用户名规则:
```json
支持中文、英文、数字
长度 1-20 个字符
如未设置或格式错误,将使用默认用户名 "default"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -0,0 +1,2 @@
魔女尼可的小小谜题,2026.07.01 23:59:59

256
repo/js/AutoCode/main.js Normal file
View File

@@ -0,0 +1,256 @@
import {ocrUID} from "./utils/uid";
let username = settings.username || "default";
let use_bgi_code_source = settings.use_bgi_code_source || false;
async function* getCodeFromFile() {
const content = file.readTextSync("codes.txt");
const codes = content.split("\n");
for (let i = 0; i < codes.length; i++) {
const line = codes[i].trim();
if (!line) continue;
// 找到最后一个英文逗号
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;
yield [code, deadline];
}
}
const BGI_CODE_UPDATETIME_URL = "https://cnb.cool/bettergi/genshin-redeem-code/-/git/raw/main/update_time.txt"
const BGI_CODE_LIST_URL = "https://cnb.cool/bettergi/genshin-redeem-code/-/git/raw/main/codes.json"
async function* getCodeFromBGI() {
// const updateTimeResp = await fetch(BGI_CODE_UPDATETIME_URL);
// if (!updateTimeResp.ok) {
// log.error(`获取BGI兑换码更新时间失败: ${updateTimeResp.status} ${updateTimeResp.statusText}`);
// return;
// }
// const updateTime = (await updateTimeResp.text()).trim(); // YYYYMMDD
// const updateTimeDate = new Date(`${updateTime.slice(0, 4)}-${updateTime.slice(4, 6)}-${updateTime.slice(6, 8)}`);
const listResp = await http.request('GET', BGI_CODE_LIST_URL, null, null);
log.debug(`BGI兑换码列表请求返回: ${JSON.stringify(listResp)}`);
if (listResp.status_code != 200) {
log.error(`获取BGI兑换码列表失败: 服务器返回状态${listResp.status} ${listResp.statusText}`);
return;
}
const fixed = listResp.body
.replace(/,\s*]/g, ']')
.replace(/,\s*}/g, '}');
const listJson = JSON.parse(fixed);
if (!Array.isArray(listJson)) {
log.error(`BGI兑换码列表格式错误`);
return;
}
const codeTitles = listJson.map(item => item.title);
log.info(`BGI兑换码列表获取成功${codeTitles.length} 组兑换码: ${codeTitles.join(", ")}`);
for (const item of listJson) {
const valid = item.valid.trim(); // YYYY-MM-DD
const validDate = new Date(valid);
for (const code of item.codes) {
if (!code) continue;
// 有效期格式不明,暂时先不返回
yield [code, null];
}
}
}
async function* getCodeList() {
if (!use_bgi_code_source) {
yield *getCodeFromFile();
} else {
yield *getCodeFromBGI();
}
}
async function openCodeUI() {
keyPress("ESCAPE");
await sleep(2000);
const settingsRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/settings.png"));
const ro1 = captureGameRegion();
const settingsRes = ro1.find(settingsRo);
ro1.dispose();
if (settingsRes.isExist()) settingsRes.click();
await sleep(2000);
const accountRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/account.png"));
const ro2 = captureGameRegion();
const accountRes = ro2.find(accountRo);
ro2.dispose();
if (accountRes.isExist()) accountRes.click();
await sleep(500);
const goToRedeemRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/go_to_redeem.png"));
const ro3 = captureGameRegion();
const goToRedeemRes = ro3.find(goToRedeemRo);
ro3.dispose();
if (goToRedeemRes.isExist()) goToRedeemRes.click();
await sleep(500);
}
(async function () {
setGameMetrics(1920, 1080, 1);
// 1. 返回主界面
await genshin.returnMainUi();
await sleep(1000);
// 2. 检验设置用户名
async function getUsername() {
const uid = await genshin.uid()
if (uid === 0) {
username = username.trim();
// 只允许 中文 / 英文 / 数字,长度 1~20
if (!username || !/^[\u4e00-\u9fa5A-Za-z0-9]{1,20}$/.test(username)) {
log.error(`用户名${username}违规暂时使用默认用户名请查看readme后修改`)
username = "default";
}
} else {
username = `${uid}`;
}
return username;
}
username =await 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-5. 读取 code 并打开兑换界面进行兑换
try {
let count = 0;
let uiOpened = false;
for await (const [code, deadline] of getCodeList()) {
// 跳过已兑换的
if (redeemedCodes.has(code)) {
count++;
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;
}
// 打开兑换界面
if (!uiOpened) {
await openCodeUI();
uiOpened = true;
}
// 输入兑换码
const inputCodeRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/input_code.png"));
const ro4 = captureGameRegion();
const inputCodeRes = ro4.find(inputCodeRo);
ro4.dispose();
if (inputCodeRes.isExist()) inputCodeRes.click();
await sleep(300);
await inputText(code);
await sleep(500);
// 点击兑换按钮
const redeemRo = RecognitionObject.TemplateMatch(file.readImageMatSync("assets/redeem.png"));
const ro5 = captureGameRegion();
const redeemRes = ro5.find(redeemRo);
ro5.dispose();
if (redeemRes.isExist()) redeemRes.click();
await sleep(1500);
// 检测各种状态
const ro6 = captureGameRegion();
const invalidRes = ro6.find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/invalid.png")));
ro6.dispose();
if (invalidRes.isExist()) log.info(`兑换码【${code}】无效`);
const ro7 = captureGameRegion();
const usedRes = ro7.find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/used.png")));
ro7.dispose();
if (usedRes.isExist()) {
// 写入记录
const writeOk = file.writeTextSync(recordPath, code + "\n", true);
if (writeOk) {
log.info(`兑换码【${code}】已使用`);
redeemedCodes.add(code);
}
}
const ro8 = captureGameRegion();
const expiredRes = ro8.find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/expired.png")));
ro8.dispose();
if (expiredRes.isExist()) {
// 写入记录
const writeOk = file.writeTextSync(recordPath, code + "\n", true);
if (writeOk) {
log.info(`兑换码【${code}】已过期`);
redeemedCodes.add(code);
}
}
const ro9 = captureGameRegion();
const notopenRes = ro9.find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/not_open.png")));
ro9.dispose();
if (notopenRes.isExist()) log.info(`兑换码【${code}】未开启`);
const ro10 = captureGameRegion();
const confirmRes = ro10.find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/confirm.png")));
ro10.dispose();
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);
}
}
// 清除输入
const ro11 = captureGameRegion();
const clearRes = ro11.find(RecognitionObject.TemplateMatch(file.readImageMatSync("assets/clear.png")));
ro11.dispose();
if (clearRes.isExist()) clearRes.click();
await sleep(4000);
}
if (count > 0) {
log.info(`检测到${count}个兑换码已兑换过,跳过`);
}
} catch (error) {
log.error(`读取兑换码文件失败: ${error}`);
}
// 6. 返回主界面
await genshin.returnMainUi();
})();

View File

@@ -0,0 +1,37 @@
{
"manifest_version": 1,
"name": "自动使用兑换码",
"version": "6.6.1",
"bgi_version": "0.61.0",
"description": "仅支持国服",
"authors": [
{
"name": "Tool_tingsu",
"links": "https://github.com/Tooltingsu"
},
{
"name": "躁动的氨气",
"links": "https://github.com/zaodonganqi"
},
{
"name": "NyaMisty",
"links": "https://github.com/NyaMisty"
},
{
"name": "云端客",
"links": "https://github.com/Kirito520Asuna"
}
],
"settings_ui": "settings.json",
"main": "main.js",
"saved_files": [
"record/*.txt"
],
"http_allowed_urls": [
"https://cnb.cool/bettergi/genshin-redeem-code/*"
]
}

View File

@@ -0,0 +1,13 @@
[
{
"name": "username",
"type": "input-text",
"label": "账户名称\n用于多账户运行时区分不同账户",
"default": "默认账户"
},
{
"name": "use_bgi_code_source",
"type": "checkbox",
"label": "使用BGI提供的兑换码列表"
}
]