mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-05-10 00:54:15 +08:00
1148 lines
37 KiB
JavaScript
1148 lines
37 KiB
JavaScript
/**
|
||
* Better Genshin Impact JavaScript
|
||
* Bundled with BetterGI CLI (https://www.npmjs.com/package/@bettergi/cli)
|
||
*
|
||
* This file is automatically generated and should not be edited.
|
||
*/
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/workflow.js
|
||
var defaultMaxAttempts = 5;
|
||
var defaultRetryInterval = 1e3;
|
||
var waitForAction = async (condition, retryAction, options) => {
|
||
const { maxAttempts = defaultMaxAttempts, retryInterval = defaultRetryInterval } = options || {};
|
||
for (let i = 0; i < maxAttempts; i++) {
|
||
if (i === 0 && condition())
|
||
return true;
|
||
await retryAction?.();
|
||
await sleep(retryInterval);
|
||
if (condition())
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
var waitForRegionAppear = async (regionProvider, retryAction, options) => {
|
||
return waitForAction(() => {
|
||
const region = regionProvider();
|
||
return region != null && region.isExist();
|
||
}, retryAction, options);
|
||
};
|
||
var waitForRegionDisappear = async (regionProvider, retryAction, options) => {
|
||
return waitForAction(() => {
|
||
const region = regionProvider();
|
||
return !region || !region.isExist();
|
||
}, retryAction, options);
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/asserts.js
|
||
var assertRegionAppearing = async (regionProvider, message, retryAction, options) => {
|
||
const isAppeared = await waitForRegionAppear(regionProvider, retryAction, options);
|
||
if (!isAppeared) {
|
||
throw new Error(message);
|
||
}
|
||
};
|
||
var assertRegionDisappearing = async (regionProvider, message, retryAction, options) => {
|
||
const isDisappeared = await waitForRegionDisappear(regionProvider, retryAction, options);
|
||
if (!isDisappeared) {
|
||
throw new Error(message);
|
||
}
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/exception.js
|
||
var getErrorMessage = (err) => {
|
||
if (err && "message" in err && typeof err.message === "string")
|
||
return err.message;
|
||
return err && typeof err === "object" ? JSON.stringify(err) : "Unknown error";
|
||
};
|
||
var isHostException = (err) => {
|
||
return err && "hostException" in err;
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/mouse.js
|
||
var simulateScroll = async (scrollAmountInClicks, times) => {
|
||
const script = {
|
||
macroEvents: Array(times).fill({ type: 6, mouseX: 0, mouseY: scrollAmountInClicks, time: 0 }),
|
||
info: { name: "", description: "", x: 0, y: 0, width: 1920, height: 1080, recordDpi: 1.5 }
|
||
};
|
||
await keyMouseScript.run(JSON.stringify(script));
|
||
};
|
||
var mouseScrollDown = (height, algorithm = (h) => Math.floor(h / 18)) => {
|
||
return simulateScroll(-120, algorithm(height));
|
||
};
|
||
var mouseScrollDownLines = (lines, lineHeight = 175) => {
|
||
return mouseScrollDown(lines * lineHeight);
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/ocr.js
|
||
var findImageWithinBounds = (image, x, y, w, h, config = {}) => {
|
||
const ir = captureGameRegion();
|
||
try {
|
||
const mat = typeof image === "string" ? file.readImageMatSync(image) : image;
|
||
const ro = RecognitionObject.templateMatch(mat, x, y, w, h);
|
||
if (Object.keys(config).length > 0) {
|
||
Object.assign(ro, config) && ro.initTemplate();
|
||
}
|
||
const region = ir.find(ro);
|
||
return region.isExist() ? region : void 0;
|
||
} catch (err) {
|
||
log.warn(`${err.message || err}`);
|
||
} finally {
|
||
ir.dispose();
|
||
}
|
||
};
|
||
var findFirst = (ir, ro, predicate) => {
|
||
const candidates = ir.findMulti(ro);
|
||
for (let i = 0; i < candidates.count; i++) {
|
||
if (predicate(candidates[i]))
|
||
return candidates[i];
|
||
}
|
||
return void 0;
|
||
};
|
||
var findTextWithinBounds = (text, x, y, w, h, options, config = {}) => {
|
||
const { ignoreCase = true, contains = false } = options || {};
|
||
const searchText = ignoreCase ? text.toLowerCase() : text;
|
||
const ir = captureGameRegion();
|
||
try {
|
||
const ro = RecognitionObject.ocr(x, y, w, h);
|
||
if (Object.keys(config).length > 0) {
|
||
Object.assign(ro, config) && ro.initTemplate();
|
||
}
|
||
return findFirst(ir, ro, (region) => {
|
||
const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
|
||
const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
|
||
return isMatch && region.isExist();
|
||
});
|
||
} catch (err) {
|
||
log.warn(`${err.message || err}`);
|
||
} finally {
|
||
ir.dispose();
|
||
}
|
||
};
|
||
var findTextWithinListView = async (text, listView, matchOptions, retryOptions, config = {}) => {
|
||
const { x, y, w, h, lineHeight, scrollLines = 1, paddingX = 10, paddingY = 10 } = listView;
|
||
const { maxAttempts = 30, retryInterval = 1e3 } = retryOptions || {};
|
||
const findTargetText = () => findTextWithinBounds(text, x, y, w, h, matchOptions, config);
|
||
let lastTextRegion;
|
||
const isReachedBottom = () => {
|
||
const textRegion = findFirst(captureGameRegion(), RecognitionObject.ocr(x, y, w, h), (region) => {
|
||
return region.isExist() && region.text.trim().length > 0;
|
||
});
|
||
if (textRegion) {
|
||
if (lastTextRegion?.text === textRegion.text && Math.abs(textRegion.y - lastTextRegion.y) < lineHeight) {
|
||
return true;
|
||
} else {
|
||
lastTextRegion = textRegion;
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
};
|
||
const isTextFoundOrBottomReached = await waitForAction(() => findTargetText() != void 0 || isReachedBottom(), async () => {
|
||
moveMouseTo(x + w - paddingX, y + paddingY);
|
||
await sleep(50);
|
||
await mouseScrollDownLines(scrollLines, lineHeight);
|
||
}, { maxAttempts, retryInterval });
|
||
return isTextFoundOrBottomReached ? findTargetText() : void 0;
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/misc.js
|
||
var deepMerge = (...objects) => {
|
||
const isPlainObject = (input) => input?.constructor === Object;
|
||
return objects.reduce((result, obj) => {
|
||
return Object.entries(obj).reduce((acc, [key, value]) => {
|
||
const recursive = isPlainObject(acc[key]) && isPlainObject(value);
|
||
acc[key] = recursive ? deepMerge(acc[key], value) : value;
|
||
return acc;
|
||
}, result);
|
||
}, {});
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/time.js
|
||
var getNextDay4AM = () => {
|
||
const now = /* @__PURE__ */ new Date();
|
||
const result = new Date(now);
|
||
result.setHours(4, 0, 0, 0);
|
||
const daysUntilNextDay = now.getHours() < 4 ? 0 : 1;
|
||
result.setDate(now.getDate() + daysUntilNextDay);
|
||
return result;
|
||
};
|
||
var getNextMonday4AM = () => {
|
||
const now = /* @__PURE__ */ new Date();
|
||
const result = new Date(now);
|
||
result.setHours(4, 0, 0, 0);
|
||
const currentDay = now.getDay();
|
||
const daysUntilNextMonday = currentDay === 1 && now.getHours() < 4 ? 0 : 8 - currentDay;
|
||
result.setDate(now.getDate() + daysUntilNextMonday);
|
||
return result;
|
||
};
|
||
var parseDuration = (duration) => {
|
||
return {
|
||
h: Math.floor(duration / 36e5),
|
||
m: Math.floor(duration % 36e5 / 6e4),
|
||
s: Math.floor(duration % 6e4 / 1e3),
|
||
ms: Math.floor(duration % 1e3)
|
||
};
|
||
};
|
||
var formatDurationAsClock = (duration) => {
|
||
return Object.values(parseDuration(duration)).slice(0, 3).map((num) => String(num).padStart(2, "0")).join(":");
|
||
};
|
||
var formatDurationAsReadable = (duration) => {
|
||
return Object.entries(parseDuration(duration)).filter(([, value]) => value > 0).map(([unit, value]) => `${value}${unit}`).join(" ");
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/progress.js
|
||
var ProgressTracker = class {
|
||
total = 0;
|
||
current = 0;
|
||
startTime = Date.now();
|
||
formatter;
|
||
interval;
|
||
lastPrintTime = 0;
|
||
constructor(total, config) {
|
||
const { formatter, interval = 3e3 } = config || {};
|
||
this.total = total;
|
||
this.formatter = formatter || this.defaultFormatter;
|
||
this.interval = interval;
|
||
}
|
||
defaultFormatter = (logger, message, progress) => {
|
||
logger("[🚧 {pct} ⏳ {eta}]: {msg}", progress.formatted.percentage.padStart(6), progress.current > 0 && progress.elapsed > 0 ? progress.formatted.remaining : "--:--:--", message);
|
||
};
|
||
tick(options) {
|
||
const { increment = 1, message, force = false } = options || {};
|
||
this.current = Math.min(this.current + increment, this.total);
|
||
if (message)
|
||
this.print(message, force);
|
||
return this.current === this.total;
|
||
}
|
||
complete(message) {
|
||
this.current = this.total;
|
||
this.print(message, true);
|
||
}
|
||
reset() {
|
||
this.current = 0;
|
||
this.startTime = Date.now();
|
||
this.lastPrintTime = 0;
|
||
}
|
||
print(message, force = false, logger = log.info) {
|
||
if (force || this.shouldPrint()) {
|
||
this.formatter(logger, message, this.getProgress());
|
||
this.printed();
|
||
}
|
||
}
|
||
shouldPrint() {
|
||
return Date.now() - this.lastPrintTime >= this.interval;
|
||
}
|
||
printed() {
|
||
this.lastPrintTime = Date.now();
|
||
}
|
||
getProgress() {
|
||
const percentage = this.current / this.total;
|
||
const elapsed = Date.now() - this.startTime;
|
||
const average = this.current > 0 ? elapsed / this.current : 0;
|
||
const remaining = (this.total - this.current) * average;
|
||
return {
|
||
current: this.current,
|
||
total: this.total,
|
||
percentage,
|
||
elapsed,
|
||
average,
|
||
remaining,
|
||
formatted: {
|
||
percentage: `${(percentage * 100).toFixed(1)}%`,
|
||
elapsed: formatDurationAsReadable(elapsed),
|
||
average: formatDurationAsReadable(average),
|
||
remaining: formatDurationAsClock(remaining)
|
||
}
|
||
};
|
||
}
|
||
};
|
||
|
||
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/store.js
|
||
var useStore = (name) => {
|
||
const filePath = `store/${name}.json`;
|
||
const obj = (() => {
|
||
try {
|
||
const storeFiles = [...file.readPathSync("store")].map((path) => path.replace(/\\/g, "/"));
|
||
if (!storeFiles.includes(filePath))
|
||
throw new Error("File does not exist");
|
||
const text = file.readTextSync(filePath);
|
||
return JSON.parse(text);
|
||
} catch {
|
||
return {};
|
||
}
|
||
})();
|
||
const createProxy = (target, parentPath = []) => {
|
||
if (typeof target !== "object" || target === null) {
|
||
return target;
|
||
}
|
||
return new Proxy(target, {
|
||
get: (target2, key) => {
|
||
const value = Reflect.get(target2, key);
|
||
return typeof value === "object" && value !== null ? createProxy(value, [...parentPath, key]) : value;
|
||
},
|
||
set: (target2, key, value) => {
|
||
const success = Reflect.set(target2, key, value);
|
||
if (success) {
|
||
Promise.resolve().then(() => {
|
||
file.writeTextSync(filePath, JSON.stringify(obj, null, 2));
|
||
});
|
||
}
|
||
return success;
|
||
},
|
||
deleteProperty: (target2, key) => {
|
||
const success = Reflect.deleteProperty(target2, key);
|
||
if (success) {
|
||
Promise.resolve().then(() => {
|
||
file.writeTextSync(filePath, JSON.stringify(obj, null, 2));
|
||
});
|
||
}
|
||
return success;
|
||
}
|
||
});
|
||
};
|
||
return createProxy(obj);
|
||
};
|
||
var useStoreWithDefaults = (name, defaults) => {
|
||
const store2 = useStore(name);
|
||
Object.assign(store2, deepMerge(defaults, store2));
|
||
return store2;
|
||
};
|
||
|
||
// src/config.ts
|
||
//! 用户脚本设置
|
||
var userConfig = {
|
||
//! 每周任务相关设置
|
||
room: settings.room || "20134075027",
|
||
playbacks: (settings.playbacks || "通关回放1.json,通关回放2.json").replace(/,/g, ",").split(",").map((str) => str.trim()).filter(Boolean),
|
||
expPerAttempt: Math.max(1, Number(settings.expPerAttempt || "20")),
|
||
deleteStageSave: settings.deleteStageSave ?? false,
|
||
deleteStageSaveKeyword: settings.deleteStageSaveKeyword || "深渊100层",
|
||
expWeeklyLimit: Math.max(1, Number(settings.expWeeklyLimit || "4000")),
|
||
force: settings.force ?? false,
|
||
thisAttempts: Math.max(0, Number(settings.thisAttempts || "0")),
|
||
//! 每日任务相关设置
|
||
dailyEnabled: settings.dailyEnabled ?? false,
|
||
dailyRooms: (settings.dailyRooms || "24429042323,28644538672").replace(/,/g, ",").split(",").map((str) => str.trim()).filter(Boolean),
|
||
dailyPlaybacks: (settings.dailyPlaybacks || "通关回放1.json,通关回放2.json;60秒按1通关.json").replace(/,/g, ",").replace(/;/g, ";").split(";").map((str) => str.trim()).filter(Boolean).reduce((arr, room) => {
|
||
const files = room.split(",").map((str) => str.trim()).filter(Boolean);
|
||
if (files.length > 0) arr.push(files);
|
||
return arr;
|
||
}, []),
|
||
dailyLimit: Math.max(1, Number(settings.dailyLimit || "1")),
|
||
dailyForce: settings.dailyForce ?? false,
|
||
goToTeyvat: settings.goToTeyvat ?? true
|
||
};
|
||
//! 脚本数据存储
|
||
var store = useStoreWithDefaults("data", {
|
||
weekly: { expGained: 0, attempts: 0 },
|
||
daily: { attempts: 0 },
|
||
nextWeek: getNextMonday4AM().getTime(),
|
||
nextDay: getNextDay4AM().getTime()
|
||
});
|
||
|
||
// src/modules/regions.ts
|
||
//! 通用:查找确认按钮
|
||
var findConfirmBtn = () => {
|
||
return findTextWithinBounds("确认", 480, 720, 960, 145);
|
||
};
|
||
//! 通用:查找标题文字
|
||
var findHeaderTitle = (title, contains) => {
|
||
return findTextWithinBounds(title, 0, 0, 300, 95, { contains });
|
||
};
|
||
//! 通用:查找底部按钮文字
|
||
var findBottomBtnText = (text, contains) => {
|
||
return findTextWithinBounds(text, 0, 980, 1920, 100, { contains });
|
||
};
|
||
//! 通用:查找关闭对话框按钮
|
||
var findCloseDialog = () => {
|
||
const img = "assets/UI_BtnIcon_Close.png";
|
||
const iro = findImageWithinBounds(img, 480, 216, 960, 648, { useMask: true, threshold: 0.75 });
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 通用:点击空白处区域继续位置
|
||
var clickToContinue = () => {
|
||
click(900, 1050);
|
||
};
|
||
//! 查找抽卡按钮(判断处于大世界条件一)
|
||
var findGachaBtn = () => {
|
||
const img = "assets/UI_BtnIcon_Gacha.png";
|
||
const iro = findImageWithinBounds(img, 960, 0, 960, 80, { useMask: true, threshold: 0.75 });
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 查找推荐奇域按钮(判断处于大世界条件二)
|
||
var findBeyondRecommendBtn = () => {
|
||
const img = "assets/UI_BtnIcon_Beyond_Recommend.png";
|
||
const iro = findImageWithinBounds(img, 960, 0, 960, 80, { useMask: true, threshold: 0.75 });
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 查找奇域大厅按钮(判断处于奇域大厅)
|
||
var findBeyondHallBtn = () => {
|
||
const img = "assets/UI_BtnIcon_Beyond_Hall.png";
|
||
const iro = findImageWithinBounds(img, 200, 0, 150, 100, { useMask: true, threshold: 0.75 });
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 房间:查找搜索奇域按钮
|
||
var findAllWonderlandsBtn = () => {
|
||
return findTextWithinBounds("搜索", 1320, 0, 600, 95, { contains: true });
|
||
};
|
||
//! 房间:查找奇域搜索输入框
|
||
var findSearchWonderlandInput = () => {
|
||
return findTextWithinBounds("搜索", 0, 120, 1920, 60, { contains: true });
|
||
};
|
||
//! 房间:查找奇域搜索输入框清除按钮
|
||
var findClearInputBtn = () => {
|
||
return findTextWithinBounds("清除", 0, 120, 1920, 60);
|
||
};
|
||
//! 房间:查找搜索奇域按钮
|
||
var findSearchWonderlandBtn = () => {
|
||
return findTextWithinBounds("搜索", 0, 120, 1920, 60, { contains: true });
|
||
};
|
||
//! 房间:查找搜索过于频繁提示
|
||
var findSearchWonderlandThrottleMsg = () => {
|
||
return findTextWithinBounds("过于频繁", 0, 0, 1920, 300, { contains: true });
|
||
};
|
||
//! 房间:查找第一个奇域搜索结果名称
|
||
var findFirstSearchResultText = () => {
|
||
const ir = captureGameRegion();
|
||
const ro = RecognitionObject.ocr(240, 390, 300, 50);
|
||
return (() => {
|
||
const list = ir.findMulti(ro);
|
||
for (let i = 0; i < list.count; i++) {
|
||
if (list[i] && list[i].isExist()) {
|
||
return list[i].text;
|
||
}
|
||
}
|
||
})();
|
||
};
|
||
//! 房间:点击选择第一个搜索结果位置
|
||
var clickToChooseFirstSearchResult = () => {
|
||
click(330, 365);
|
||
};
|
||
//! 房间:查找进入房间快捷键按钮
|
||
var findEnterRoomShortcut = () => {
|
||
return findTextWithinBounds("房间", 1580, 110, 320, 390, { contains: true });
|
||
};
|
||
//! 房间:查找退出房间按钮
|
||
var findLeaveRoomBtn = () => {
|
||
const img = "assets/UI_Icon_Leave_Right.png";
|
||
const iro = findImageWithinBounds(img, 1570, 0, 350, 100);
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 房间:查找跳转大厅按钮
|
||
var findGoToLobbyBtn = () => {
|
||
return findTextWithinBounds("大厅", 880, 840, 1040, 110, {
|
||
contains: true
|
||
});
|
||
};
|
||
//! 房间:查找创建房间按钮
|
||
var findCreateRoomBtn = () => {
|
||
return findTextWithinBounds("房间", 960, 140, 960, 70, { contains: true });
|
||
};
|
||
//! 房间:点击加入准备区位置
|
||
var clickToPrepare = () => {
|
||
click(770, 275);
|
||
};
|
||
//! 房间:查找加入准备区提示
|
||
var findPrepareMsg = () => {
|
||
return findTextWithinBounds("加入准备", 576, 432, 768, 216, {
|
||
contains: true
|
||
});
|
||
};
|
||
//! 存档:查找奇域收藏
|
||
var findBeyondFavoritesBtn = () => {
|
||
return findTextWithinBounds("收藏", 0, 880, 200, 200, {
|
||
contains: true
|
||
});
|
||
};
|
||
//! 存档:查找管理关卡按钮
|
||
var findManageStagesBtn = () => {
|
||
return findTextWithinBounds("管理", 1320, 0, 600, 95, { contains: true });
|
||
};
|
||
//! 存档:查找编辑关卡存档按钮
|
||
var findEditStageSaveBtn = () => {
|
||
return findTextWithinBounds("管理", 1220, 980, 700, 100);
|
||
};
|
||
//! 存档:查找要删除的存档位置
|
||
var findSaveToDeletePos = (keyword) => findTextWithinListView(
|
||
keyword,
|
||
{
|
||
x: 210,
|
||
y: 250,
|
||
w: 1650,
|
||
h: 710,
|
||
scrollLines: 7,
|
||
lineHeight: 95
|
||
},
|
||
{ contains: true }
|
||
);
|
||
//! 存档:查找局外存档列头
|
||
var findExternalSaveColumnPos = () => {
|
||
return findTextWithinBounds("局外", 55, 190, 1810, 50, { contains: true });
|
||
};
|
||
//! 存档:查找删除局外存档复选框已选中状态
|
||
var findDeleteExternalSaveChecked = (colPos) => {
|
||
const img = "assets/Checkbox_Checked.png";
|
||
const iro = findImageWithinBounds(img, colPos, 250, 290, 710, {
|
||
threshold: 0.6,
|
||
use3Channels: false
|
||
});
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 存档:查找删除关卡存档按钮
|
||
var findDeleteStageSaveBtn = () => {
|
||
return findTextWithinBounds("删除所选", 1220, 980, 700, 100);
|
||
};
|
||
//! 关卡:查找关卡退出按钮
|
||
var findStageEscBtn = () => {
|
||
const img = "assets/UI_Icon_Leave.png";
|
||
const iro = findImageWithinBounds(img, 0, 0, 100, 100, { threshold: 0.75 });
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 关卡:查找中断挑战按钮
|
||
var findExitStageBtn = () => {
|
||
return findTextWithinBounds("中断挑战", 576, 324, 768, 432);
|
||
};
|
||
//! 关卡:查找奇域等级提升页面
|
||
var findSkipLevelUpMsg = () => {
|
||
return findTextWithinBounds("空白处", 610, 950, 700, 60, { contains: true });
|
||
};
|
||
//! 退出:查找返回提瓦特按钮
|
||
var findGotTeyvatBtn = () => {
|
||
return findTextWithinBounds("返回", 1500, 0, 300, 95, { contains: true });
|
||
};
|
||
//! 纪游:查找诸界纪游按钮
|
||
var findBeyondBattlepassBtn = () => {
|
||
const img = "assets/UI_BtnIcon_Beyond_Battlepass.png";
|
||
const iro = findImageWithinBounds(img, 960, 0, 960, 80, { useMask: true, threshold: 0.75 });
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
//! 纪游:查找纪游开屏动画
|
||
var findBeyondBattlepassPopup = () => {
|
||
return findTextWithinBounds("奖励一览", 0, 0, 960, 1080, { contains: true });
|
||
};
|
||
//! 纪游:查找领取奖励按钮
|
||
var findFetchRewardBtn = () => {
|
||
const img = "assets/UI_Img_UGCCultivateReward_FetchHint.png";
|
||
const iro = findImageWithinBounds(img, 1670, 100, 250, 880, { useMask: true, threshold: 0.75 });
|
||
iro?.drawSelf("group_img");
|
||
return iro;
|
||
};
|
||
|
||
// src/modules/lobby.ts
|
||
//! 判断是否处于奇域大厅
|
||
var isInLobby = () => findBeyondHallBtn() !== void 0;
|
||
//! 判断是否处于提瓦特大陆
|
||
var isInTeyvat = () => {
|
||
return findGachaBtn() !== void 0 && findBeyondRecommendBtn() !== void 0;
|
||
};
|
||
//! 从提瓦特前往公共大厅
|
||
//! 退出大厅返回提瓦特大陆
|
||
var exitLobbyToTeyvat = async () => {
|
||
if (!userConfig.goToTeyvat) return;
|
||
if (isInTeyvat()) {
|
||
log.warn("已处于提瓦特大陆,跳过");
|
||
return;
|
||
}
|
||
log.info("打开当前大厅...");
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("大厅", true),
|
||
"打开当前大厅超时",
|
||
() => {
|
||
keyPress("VK_F2");
|
||
},
|
||
{ maxAttempts: 10, retryInterval: 2e3 }
|
||
);
|
||
log.info("返回提瓦特大陆...");
|
||
const done = await waitForAction(
|
||
isInTeyvat,
|
||
() => {
|
||
findGotTeyvatBtn()?.click();
|
||
findConfirmBtn()?.click();
|
||
},
|
||
{ maxAttempts: 120 }
|
||
);
|
||
if (!done) throw new Error("返回提瓦特大陆超时");
|
||
};
|
||
|
||
// src/modules/reawrd.ts
|
||
//! 领取诸界纪游经验
|
||
var fetchBattlepassExp = async () => {
|
||
//! 确保处于大厅内
|
||
if (!isInLobby()) {
|
||
log.warn("不在奇域大厅内,跳过领取诸界纪游经验");
|
||
return;
|
||
}
|
||
if (!findBeyondBattlepassBtn()) {
|
||
log.warn("诸界纪游已结束,跳过领取诸界纪游经验");
|
||
return;
|
||
}
|
||
//! 打开诸界纪游界面
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("纪游", true),
|
||
"打开诸界纪游界面超时",
|
||
() => {
|
||
keyPress("VK_F4");
|
||
//! 关闭纪游开屏动画(如果弹出)
|
||
if (findBeyondBattlepassPopup()) {
|
||
keyPress("VK_ESCAPE");
|
||
}
|
||
},
|
||
{ maxAttempts: 5, retryInterval: 2e3 }
|
||
);
|
||
//! 跳转到任务界面
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("任务", true),
|
||
"打开诸界纪游任务界面超时",
|
||
() => {
|
||
keyPress("VK_E");
|
||
},
|
||
{ maxAttempts: 5, retryInterval: 2e3 }
|
||
);
|
||
//! 点击一键领取
|
||
await assertRegionDisappearing(
|
||
() => findBottomBtnText("领取", true),
|
||
"领取诸界纪游经验超时",
|
||
async () => {
|
||
//! 重复确认,防止误领纪游奖励(部件礼箱会卡流程)而不是经验
|
||
if (findHeaderTitle("任务", true)) {
|
||
findBottomBtnText("领取", true)?.click();
|
||
clickToContinue();
|
||
await sleep(1e3);
|
||
clickToContinue();
|
||
}
|
||
},
|
||
{ maxAttempts: 5, retryInterval: 3e3 }
|
||
);
|
||
await genshin.returnMainUi();
|
||
};
|
||
//! 领取日活奖励
|
||
var fetchCultivateReward = async () => {
|
||
//! 确保处于大厅内
|
||
if (!isInLobby()) {
|
||
log.warn("不在奇域大厅内,跳过领取日活奖励");
|
||
return;
|
||
}
|
||
//! 打开奇趣盛邀
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("盛邀", true),
|
||
"打开任务书超时",
|
||
async () => {
|
||
keyPress("VK_F1");
|
||
await sleep(2e3);
|
||
if (findHeaderTitle("盛邀", true) === void 0) {
|
||
keyPress("VK_E");
|
||
}
|
||
},
|
||
{ maxAttempts: 10, retryInterval: 1e3 }
|
||
);
|
||
//! 仅领取妙思觅索奖励(巧趣醒转奖励里有部件礼箱会卡流程)
|
||
await assertRegionDisappearing(
|
||
findFetchRewardBtn,
|
||
"领取妙思觅索奖励超时",
|
||
async () => {
|
||
const reward = findFetchRewardBtn();
|
||
if (reward) {
|
||
reward.click();
|
||
clickToContinue();
|
||
await sleep(1e3);
|
||
clickToContinue();
|
||
}
|
||
},
|
||
{ maxAttempts: 5, retryInterval: 2e3 }
|
||
);
|
||
await genshin.returnMainUi();
|
||
};
|
||
|
||
// src/modules/room.ts
|
||
var isInRoom = () => findHeaderTitle("房间", true) !== void 0;
|
||
//! 打开人气奇域
|
||
var goToRecommendedWonderlands = async () => {
|
||
log.info("打开人气奇域界面...");
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("人气", true),
|
||
"打开人气奇域界面超时",
|
||
() => {
|
||
keyPress("VK_F6");
|
||
}
|
||
);
|
||
};
|
||
//! 创建并进入奇域房间
|
||
var createRoom = async (room) => {
|
||
await goToRecommendedWonderlands();
|
||
log.info("打开全部奇域界面...");
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("搜索", true),
|
||
"打开全部奇域界面超时",
|
||
() => {
|
||
findAllWonderlandsBtn()?.click();
|
||
}
|
||
);
|
||
await sleep(1500);
|
||
//! 记录搜索前的第一个奇域名称
|
||
let iwnt;
|
||
let wi = 0;
|
||
while (iwnt === void 0) {
|
||
if (wi > 20) break;
|
||
iwnt = findFirstSearchResultText();
|
||
await sleep(500);
|
||
wi += 1;
|
||
}
|
||
if (iwnt === void 0) throw new Error("加载全部奇域列表超时");
|
||
log.info("搜索前的第一个奇域名称: {iwnt}", iwnt);
|
||
log.info("粘贴奇域关卡文本: {room}", room);
|
||
await assertRegionAppearing(findClearInputBtn, "粘贴关卡文本超时", () => {
|
||
const input = findSearchWonderlandInput();
|
||
if (input) {
|
||
input.click();
|
||
inputText(room);
|
||
}
|
||
});
|
||
//! 等待搜索结果变化
|
||
let fswnt;
|
||
log.info("搜索奇域关卡: {room}", room);
|
||
await waitForAction(
|
||
() => {
|
||
if (fswnt === void 0) return false;
|
||
//! 检测搜索过于频繁提示
|
||
if (findSearchWonderlandThrottleMsg()) return true;
|
||
//! 检测搜索结果是否变化
|
||
return fswnt.toLocaleLowerCase().trim() !== iwnt.toLocaleLowerCase().trim();
|
||
},
|
||
async () => {
|
||
const searchBtn = findSearchWonderlandBtn();
|
||
if (searchBtn) {
|
||
searchBtn.click();
|
||
await sleep(200);
|
||
searchBtn.click();
|
||
}
|
||
await sleep(500);
|
||
fswnt = findFirstSearchResultText();
|
||
},
|
||
{ maxAttempts: 30, retryInterval: 200 }
|
||
);
|
||
log.info("打开奇域介绍...");
|
||
await assertRegionAppearing(
|
||
findCreateRoomBtn,
|
||
"打开奇域介绍超时",
|
||
() => {
|
||
const goToLobbyButton = findGoToLobbyBtn();
|
||
if (goToLobbyButton) {
|
||
log.info("当前不在大厅,前往大厅...");
|
||
goToLobbyButton.click();
|
||
} else {
|
||
log.info("选择第一个奇域关卡...");
|
||
clickToChooseFirstSearchResult();
|
||
}
|
||
},
|
||
{ maxAttempts: 60 }
|
||
);
|
||
log.info("创建并进入房间...");
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("房间", true),
|
||
"创建并进入房间超时",
|
||
() => {
|
||
findCreateRoomBtn()?.click();
|
||
},
|
||
{ maxAttempts: 60 }
|
||
);
|
||
};
|
||
//! 进入奇域房间
|
||
var enterRoom = async (room) => {
|
||
const inLobby = isInLobby();
|
||
if (inLobby) {
|
||
const enterButton = findEnterRoomShortcut();
|
||
if (enterButton) {
|
||
log.info("当前已存在房间,进入房间...", room);
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("房间", true),
|
||
"进入房间超时",
|
||
() => {
|
||
keyPress("VK_P");
|
||
}
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
log.info("当前不在房间内,创建房间...", room);
|
||
await createRoom(room);
|
||
};
|
||
//! 离开房间
|
||
var leaveRoom = async () => {
|
||
//! 当前在大厅,且存在房间
|
||
if (isInLobby() && findEnterRoomShortcut() !== void 0 || isInRoom()) {
|
||
log.info("当前存在房间,离开房间...");
|
||
//! 先进入房间
|
||
await assertRegionAppearing(
|
||
() => findHeaderTitle("房间", true),
|
||
"进入房间超时",
|
||
() => {
|
||
keyPress("VK_P");
|
||
}
|
||
);
|
||
//! 离开房间
|
||
await assertRegionAppearing(
|
||
findBeyondHallBtn,
|
||
"离开房间超时",
|
||
async () => {
|
||
findLeaveRoomBtn()?.click();
|
||
await sleep(1e3);
|
||
findConfirmBtn()?.click();
|
||
},
|
||
{ maxAttempts: 5 }
|
||
);
|
||
}
|
||
};
|
||
|
||
// src/modules/stage.ts
|
||
//! 已有的执行通关回放文件列表
|
||
var availablePlaybackFiles = () => {
|
||
return [...file.readPathSync("assets/playbacks")].map((path) => path.replace(/\\/g, "/"));
|
||
};
|
||
var playStage = async (playbacks) => {
|
||
//! 等待进入关卡
|
||
const ok = await waitForAction(
|
||
() => findStageEscBtn() !== void 0 || findBottomBtnText("返回大厅") !== void 0,
|
||
async () => {
|
||
findBottomBtnText("开始游戏")?.click();
|
||
findBottomBtnText("准备", true)?.click();
|
||
//! 判断是否已经加入准备区
|
||
if (findPrepareMsg()) {
|
||
log.info("加入准备区...");
|
||
await assertRegionDisappearing(findPrepareMsg, "等待加入准备区提示消失超时");
|
||
clickToPrepare();
|
||
}
|
||
},
|
||
{ maxAttempts: 60 }
|
||
);
|
||
if (!ok) throw new Error("进入关卡超时");
|
||
//! 直接通关结算的关卡(不会进入关卡)
|
||
if (findBottomBtnText("返回大厅")) {
|
||
await exitStageToLobby();
|
||
return;
|
||
}
|
||
//! 关闭游戏说明对话框
|
||
await assertRegionDisappearing(
|
||
findCloseDialog,
|
||
"关闭游戏说明对话框超时",
|
||
() => {
|
||
findCloseDialog()?.click();
|
||
},
|
||
{ maxAttempts: 10, retryInterval: 500 }
|
||
);
|
||
//! 执行随机通关回放文件
|
||
await execStagePlayback(playbacks);
|
||
await sleep(3e3);
|
||
//! 退出关卡返回大厅
|
||
await exitStageToLobby();
|
||
};
|
||
//! 执行通关回放文件(随机抽取)
|
||
var execStagePlayback = async (playbacks) => {
|
||
const file2 = playbacks[Math.floor(Math.random() * playbacks.length)];
|
||
log.info("执行通关回放文件: {file}", file2);
|
||
await keyMouseScript.runFile(file2);
|
||
};
|
||
//! 退出关卡
|
||
var exitStage = async () => {
|
||
if (findStageEscBtn() === void 0) return;
|
||
log.warn("关卡超时,尝试退出关卡...");
|
||
await assertRegionAppearing(
|
||
findExitStageBtn,
|
||
"等待中断挑战按钮出现超时",
|
||
() => {
|
||
keyPress("VK_ESCAPE");
|
||
},
|
||
{ maxAttempts: 10, retryInterval: 1e3 }
|
||
);
|
||
await assertRegionAppearing(
|
||
findBeyondHallBtn,
|
||
"返回大厅超时",
|
||
async () => {
|
||
//! 点击 “中断挑战” 按钮
|
||
findExitStageBtn()?.click();
|
||
//! 点击底部 “返回大厅” 按钮
|
||
findBottomBtnText("返回大厅")?.click();
|
||
},
|
||
{ maxAttempts: 60 }
|
||
);
|
||
await genshin.returnMainUi();
|
||
};
|
||
//! 退出关卡返回大厅
|
||
var exitStageToLobby = async () => {
|
||
if (isInLobby()) {
|
||
log.warn("已处于奇域大厅,跳过");
|
||
return;
|
||
}
|
||
log.info("退出关卡返回大厅...");
|
||
const done = await waitForAction(
|
||
isInLobby,
|
||
async () => {
|
||
//! 跳过奇域等级提升页面(奇域等级每逢11、21、31、41级时出现加星页面)
|
||
if (findSkipLevelUpMsg()) {
|
||
clickToContinue();
|
||
}
|
||
//! 点击底部 “返回大厅” 按钮
|
||
findBottomBtnText("返回大厅")?.click();
|
||
},
|
||
{ maxAttempts: 60 }
|
||
);
|
||
if (!done) {
|
||
await exitStage();
|
||
throw new Error("退出关卡返回大厅超时");
|
||
}
|
||
};
|
||
|
||
// src/workflows/daily.ts
|
||
var execDailyTask = async () => {
|
||
if (!userConfig.dailyEnabled) {
|
||
log.warn("未启用执行每日通关任务,跳过");
|
||
return;
|
||
}
|
||
//! 确保通关回放文件存在
|
||
if (userConfig.dailyRooms.length !== userConfig.dailyPlaybacks.length) {
|
||
log.warn("每日奇域关卡数量与通关回放文件池数量不匹配,跳过");
|
||
return;
|
||
}
|
||
const files = availablePlaybackFiles();
|
||
const mappings = {};
|
||
for (let i = 0; i < userConfig.dailyRooms.length; i++) {
|
||
const room = userConfig.dailyRooms[i];
|
||
const playbacks = userConfig.dailyPlaybacks[i].map((file2) => `assets/playbacks/${file2}`).filter((path) => files.includes(path));
|
||
if (playbacks.length === 0) {
|
||
log.warn(
|
||
"房间 {room} 未找到任何通关回放文件,请确保已录制回放并拷贝到 assets/playbacks 目录下",
|
||
room
|
||
);
|
||
return;
|
||
}
|
||
mappings[room] = playbacks;
|
||
}
|
||
//! 新的一天开始,重置经验值数据
|
||
if (Date.now() >= store.nextDay) {
|
||
store.daily = { attempts: 0 };
|
||
store.nextDay = getNextDay4AM().getTime();
|
||
}
|
||
//! 检查当日通关次数是否已达上限
|
||
if (store.daily.attempts >= userConfig.dailyLimit) {
|
||
if (userConfig.dailyForce) {
|
||
log.warn("当日通关次数已达上限,强制执行");
|
||
} else {
|
||
log.warn("当日通关次数已达上限,跳过执行");
|
||
return;
|
||
}
|
||
}
|
||
//! 计算需要进行的尝试次数
|
||
let attempts = userConfig.dailyLimit - store.daily.attempts;
|
||
attempts = attempts > 0 ? attempts : 1;
|
||
//! 创建进度追踪器
|
||
const tracker = new ProgressTracker(attempts * userConfig.dailyRooms.length);
|
||
//! 迭代奇域关卡列表
|
||
try {
|
||
for (let i = 0; i < attempts; i++) {
|
||
//! 迭代尝试
|
||
try {
|
||
for (const room of userConfig.dailyRooms) {
|
||
//! 离开当前所在房间(如果存在)
|
||
await leaveRoom();
|
||
tracker.print(`开始当日第 ${store.daily.attempts + 1} 次奇域挑战...`);
|
||
//! 进入房间
|
||
await enterRoom(room);
|
||
//! 游玩关卡
|
||
await playStage(mappings[room]);
|
||
//! 更新进度
|
||
tracker.tick({ increment: 1 });
|
||
}
|
||
} catch (err) {
|
||
//! 发生主机异常(如:任务取消异常等),无法再继续执行
|
||
if (isHostException(err)) throw err;
|
||
//! 发生脚本流程异常,尝试退出关卡(如果在关卡中)
|
||
await exitStage();
|
||
log.error("脚本执行出错: {error}", getErrorMessage(err));
|
||
}
|
||
//! 一轮关卡执行结束,更新数据存储
|
||
store.daily.attempts += 1;
|
||
}
|
||
//! 领取诸界纪游经验
|
||
await fetchBattlepassExp();
|
||
//! 领取日活奖励
|
||
await fetchCultivateReward();
|
||
} catch (err) {
|
||
//! 发生主机异常(如:任务取消异常等),无法再继续执行
|
||
if (isHostException(err)) throw err;
|
||
log.error("脚本执行出错: {error}", getErrorMessage(err));
|
||
}
|
||
await genshin.returnMainUi();
|
||
};
|
||
|
||
// src/modules/save.ts
|
||
//! 进入管理关卡存档界面
|
||
var goToManageStageSave = async () => {
|
||
//! 打开人气奇域
|
||
await goToRecommendedWonderlands();
|
||
//! 打开奇域收藏->管理关卡
|
||
await assertRegionAppearing(
|
||
findEditStageSaveBtn,
|
||
"打开编辑关卡存档按钮超时",
|
||
async () => {
|
||
//! 点击奇域收藏
|
||
findBeyondFavoritesBtn()?.click();
|
||
await sleep(300);
|
||
//! 点击管理关卡
|
||
findManageStagesBtn()?.click();
|
||
await sleep(300);
|
||
},
|
||
{ maxAttempts: 5 }
|
||
);
|
||
};
|
||
//! 删除关卡存档
|
||
var deleteStageSave = async () => {
|
||
if (!userConfig.deleteStageSave || userConfig.deleteStageSaveKeyword.trim() === "") {
|
||
log.info("未启用删除关卡存档,跳过");
|
||
return;
|
||
}
|
||
try {
|
||
//! 进入管理关卡存档界面
|
||
await goToManageStageSave();
|
||
//! 选中要删除的关卡的局外存档
|
||
const stagePos = await findSaveToDeletePos(userConfig.deleteStageSaveKeyword);
|
||
if (stagePos === void 0) {
|
||
log.warn("未找到要删除的关卡存档,跳过");
|
||
return;
|
||
}
|
||
stagePos?.drawSelf("group_text");
|
||
const colPos = findExternalSaveColumnPos();
|
||
if (colPos === void 0) {
|
||
log.warn("无法确定关卡的局外存档列位置,跳过");
|
||
return;
|
||
}
|
||
//! 进入编辑模式
|
||
await assertRegionDisappearing(
|
||
findEditStageSaveBtn,
|
||
"进入编辑模式超时",
|
||
() => {
|
||
keyPress("VK_F");
|
||
},
|
||
{ maxAttempts: 5 }
|
||
);
|
||
//! 计算勾选框位置并点击
|
||
const [cx, cy] = [(colPos.x * 2 + colPos.width) / 2, stagePos.y + 40];
|
||
await assertRegionAppearing(
|
||
() => findDeleteExternalSaveChecked(colPos.x),
|
||
"勾选要删除的局外存档超时",
|
||
() => {
|
||
click(Math.ceil(cx), Math.ceil(cy));
|
||
},
|
||
{ maxAttempts: 5, retryInterval: 1500 }
|
||
);
|
||
//! 点击删除所选按钮
|
||
await assertRegionDisappearing(
|
||
() => findDeleteExternalSaveChecked(colPos.x),
|
||
"删除关卡存档超时",
|
||
async () => {
|
||
//! 特征较为脆弱,多次确认,确保成功删除
|
||
findConfirmBtn()?.click();
|
||
await sleep(500);
|
||
findConfirmBtn()?.click();
|
||
findDeleteStageSaveBtn()?.click();
|
||
await sleep(1e3);
|
||
findConfirmBtn()?.click();
|
||
await sleep(500);
|
||
findConfirmBtn()?.click();
|
||
},
|
||
{
|
||
maxAttempts: 5
|
||
}
|
||
);
|
||
} catch (err) {
|
||
if (isHostException(err)) throw err;
|
||
log.warn("删除关卡存档失败: {error}", getErrorMessage(err));
|
||
} finally {
|
||
//! 返回大厅
|
||
await genshin.returnMainUi();
|
||
}
|
||
};
|
||
|
||
// src/workflows/weekly.ts
|
||
var execWeeklyTask = async () => {
|
||
//! 确保通关回放文件存在
|
||
const files = availablePlaybackFiles();
|
||
const playbacks = userConfig.playbacks.map((file2) => `assets/playbacks/${file2}`).filter((path) => files.includes(path));
|
||
if (playbacks.length === 0) {
|
||
log.warn("未找到任何通关回放文件,请确保已录制回放并拷贝到 assets/playbacks 目录下");
|
||
return;
|
||
}
|
||
//! 新的一周开始,重置经验值数据
|
||
if (Date.now() >= store.nextWeek) {
|
||
store.weekly = { expGained: 0, attempts: 0 };
|
||
store.nextWeek = getNextMonday4AM().getTime();
|
||
}
|
||
//! 检查本周经验值是否已达上限
|
||
if (store.weekly.expGained >= userConfig.expWeeklyLimit) {
|
||
if (userConfig.force) {
|
||
log.warn("本周获取经验值已达上限,强制执行");
|
||
} else {
|
||
log.warn("本周获取经验值已达上限,跳过执行");
|
||
return;
|
||
}
|
||
}
|
||
//! 计算本次本周剩余可获取经验值
|
||
let expRemaining = userConfig.expWeeklyLimit - store.weekly.expGained;
|
||
expRemaining = expRemaining > 0 ? expRemaining : userConfig.expWeeklyLimit;
|
||
//! 计算需要进行的尝试次数
|
||
let attempts = Math.ceil(expRemaining / userConfig.expPerAttempt);
|
||
attempts = userConfig.thisAttempts > 0 ? userConfig.thisAttempts : attempts;
|
||
//! 离开当前所在房间(如果存在)
|
||
await leaveRoom();
|
||
//! 创建进度追踪器
|
||
const tracker = new ProgressTracker(attempts);
|
||
//! 迭代尝试
|
||
try {
|
||
for (let i = 0; i < attempts; i++) {
|
||
tracker.print(`开始本周第 ${store.weekly.attempts + 1} 次奇域挑战...`);
|
||
//! 删除关卡存档
|
||
await deleteStageSave();
|
||
//! 进入房间
|
||
await enterRoom(userConfig.room);
|
||
//! 游玩关卡
|
||
await playStage(playbacks);
|
||
//! 关卡结束,更新数据存储
|
||
store.weekly.attempts += 1;
|
||
store.weekly.expGained += userConfig.expPerAttempt;
|
||
tracker.tick({ increment: 1 });
|
||
//! 本周已获取经验值达到上限,跳出循环
|
||
if (store.weekly.expGained >= userConfig.expWeeklyLimit) {
|
||
if (!userConfig.force) {
|
||
log.warn("本周已获取经验值达到上限,停止执行");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
//! 发生主机异常(如:任务取消异常等),无法再继续执行
|
||
if (isHostException(err)) throw err;
|
||
//! 发生脚本流程异常,尝试退出关卡(如果在关卡中)
|
||
await exitStage();
|
||
log.error("脚本执行出错: {error}", getErrorMessage(err));
|
||
}
|
||
await genshin.returnMainUi();
|
||
};
|
||
|
||
// main.ts
|
||
(async function() {
|
||
//! 初始化游戏环境
|
||
setGameMetrics(1920, 1080, 1.5);
|
||
await genshin.returnMainUi();
|
||
//! 执行每周任务
|
||
await execWeeklyTask();
|
||
//! 执行每日任务
|
||
await execDailyTask();
|
||
//! 返回提瓦特大陆
|
||
await exitLobbyToTeyvat();
|
||
})();
|