js:提瓦特扫描仪初版 (#1263)

This commit is contained in:
mno
2025-07-03 20:45:25 +08:00
committed by GitHub
parent 5853008f54
commit 312d126432
114 changed files with 4681 additions and 0 deletions

View File

@@ -0,0 +1,963 @@
// 主函数
(async function () {
await fakeLog("提瓦特扫描仪", true, true, 0);
//去七天神像切换到可战斗配队
await genshin.tpToStatueOfTheSeven();
await switchPartyIfNeeded(settings.partyName);
//文件处理
const pathFolder = "pathing";
const paths = await readFolder(pathFolder, true);
for (let i = 0; i < 30; i++) {
if (await isCompassFree(1)) {
keyPress("Z");
break;
} else {
log.warn(`${i + 1}次尝试未找到罗盘图标`);
}
if (i === 29) {
log.warn("请装备罗盘后再启动本js");
return;
}
await sleep(1000);
}
if (!isValidAccountName(settings.accountName || "默认账户")) {
return;
}
const recordFilePath = `records/${settings.accountName || "默认账户"}.txt`;
// 初始化结果对象
const result = {};
if (!doFileExist(recordFilePath)) {
log.warn("对应记录文件不存在,将创建空白文件");
} else {
// 读取文件内容
const content = await file.readText(recordFilePath);
// 按行分割内容
const lines = content.split('\n');
// 当前国家
let currentCountry = null;
// 遍历每一行
for (const line of lines) {
// 去除行首行尾的空格
const trimmedLine = line.trim();
// 如果行以国家名称结尾(冒号),更新当前国家
if (trimmedLine.endsWith("")) {
currentCountry = trimmedLine.slice(0, -1).trim();
result[currentCountry] = {};
continue;
}
// 如果行以“-”开头,表示路线信息
if (trimmedLine.startsWith("-")) {
// 提取路线名称和宝藏信息
const [routeInfo, treasureInfo] = trimmedLine.slice(1).split("");
const routeName = routeInfo.trim();
const treasures = treasureInfo.split("").map(t => t.trim());
// 将路线信息添加到当前国家
if (currentCountry) {
result[currentCountry][routeName] = treasures;
}
}
}
}
// 创建一个对象来存储每条路线的文件路径
const routes = {};
// 遍历 paths将文件按路线分类
for (const file of paths) {
// 获取文件所在的路线文件夹路径
const folderPathArray = file.folderPathArray;
// 确保路径数组长度足够
if (folderPathArray.length < 2) {
log.debug(`文件路径 ${file.fullPath} 的层级不足,跳过该文件`);
continue;
}
const routeFolder = folderPathArray[folderPathArray.length - 1]; // 倒数第一个元素是路线文件夹
const countryFolder = folderPathArray[folderPathArray.length - 2]; // 倒数第二个元素是国家文件夹
// 如果国家文件夹不存在于 routes 对象中,则初始化
if (!routes[countryFolder]) {
routes[countryFolder] = {};
}
// 如果路线文件夹不存在于国家文件夹中,则初始化
if (!routes[countryFolder][routeFolder]) {
routes[countryFolder][routeFolder] = [];
}
// 将文件的完整路径添加到对应的路线文件夹中
routes[countryFolder][routeFolder].push(file.fullPath);
log.debug(`找到文件${file.fullPath},在${countryFolder}${routeFolder}`);
}
let currentElement = "";
if (settings.operationMode != "导航至宝藏地点") {
log.info("开启扫描模式");
// 遍历 routes 对象
for (const country in routes) {
// 使用 switch 语句判断是否跳过该国家
// 获取当前国家的所有路线
const routesForCountry = routes[country];
// 检查 result 中是否存在多余的路线,并移除
if (result[country]) {
const resultRoutes = Object.keys(result[country]);
for (const resultRoute of resultRoutes) {
if (!routesForCountry[resultRoute]) {
// 如果 result 中的路线在 routes 中不存在,则移除该路线
delete result[country][resultRoute];
log.warn(`移除路线: ${resultRoute},因为该路线在 routes 中不存在`);
}
}
}
switch (country) {
case "蒙德":
if (settings.disableMondstadt) {
log.info(`跳过国家: ${country}`);
continue;
}
break;
case "璃月":
if (settings.disableLiyue) {
log.info(`跳过国家: ${country}`);
continue;
}
break;
case "稻妻":
if (settings.disableInazuma) {
log.info(`跳过国家: ${country}`);
continue;
}
break;
case "须弥":
if (settings.disableSumeru) {
log.info(`跳过国家: ${country}`);
continue;
}
break;
case "枫丹":
if (settings.disableFontaine) {
log.info(`跳过国家: ${country}`);
continue;
}
break;
case "纳塔":
if (settings.disableNatlan) {
log.info(`跳过国家: ${country}`);
continue;
}
break;
default:
log.warn(`跳过未知国家: ${country}`);
continue;
}
log.info(`开始处理 ${country}的路线`);
for (const route in routes[country]) {
// 获取当前路线下的文件数量
const fileCount = routes[country][route].length;
// 检查 result 中是否存在该路线
if (result[country] && result[country][route]) {
const treasureResults = result[country][route];
const allNotExist = treasureResults.every(item => item === "不存在宝藏");
// 如果所有项都是“不存在宝藏”且文件数量与项目数量一致,则跳过该路线
if (allNotExist && fileCount === treasureResults.length) {
log.info(`跳过路线: ${route},所有点位都已确认不存在宝藏`);
continue;
}
}
// 如果 result 中不存在该路线,或者项目数量与文件数量不一致
if (!result[country] || !result[country][route] || fileCount !== result[country][route].length) {
// 在 result 中新增或修改该路线,项目改为文件数量的“未知”
if (!result[country]) {
result[country] = {};
}
result[country][route] = Array(fileCount).fill("未知");
}
log.info(`开始处理路线: ${route}`);
let targetElement = null; // 初始化 targetElement
// 检查字符串是否以“主”结尾
if (route.endsWith("主")) {
// 获取“主”字前的一个字
targetElement = route[route.length - 2]; // 获取倒数第二个字符
log.info(`该路线需要${targetElement}系主角`);
if (targetElement != currentElement) {
currentElement = targetElement;
await switchTravelerElement(targetElement);
}
}
await fakeLog(`${route}.json`, false, true, 0);
let index = 0; // 添加一个计数器
let xyCheck = false;
for (const pathingPath of routes[country][route]) {
// 判断是否处于限制运行时间
if (await isTimeRestricted(settings.timeRule)) {
return;
}
log.info(`地图追踪:${pathingPath}`);
await pathingScript.runFile(pathingPath);
try {
await sleep(10);
} catch (error) {
log.error(`终止任务: ${error}`);
return;
}
// 获取 JSON 文件中的最后一个点位坐标及 description
const jsonINfo = await checkJson(pathingPath);
// 获取当前在小地图上的位置坐标,确保调用之前在主界面
const miniMapPosition = await genshin.getPositionFromMap();
log.info(`当前小地图坐标: X=${miniMapPosition.X}, Y=${miniMapPosition.Y}`);
// 比较坐标
const diffX = Math.abs(jsonINfo.lastX - miniMapPosition.X);
const diffY = Math.abs(jsonINfo.lastY - miniMapPosition.Y);
if (diffX + diffY >= 5 && jsonINfo.description !== "终点处小地图无效") {
log.error(`坐标偏差过大,地图追踪可能异常。偏差: X=${diffX}, Y=${diffY}`);
xyCheck = true;
continue; //跳出该路线
} else {
log.info(`坐标偏差在允许范围内。偏差: X=${diffX}, Y=${diffY}`);
}
const compassResult = await checkTreasure();
// 根据 compassResult 更新 result 中对应项的信息
if (compassResult === "罗盘状态异常,可能在战斗中") {
result[country][route][index] = "未知";
log.error("出错啦,请检查路线");
} else if (compassResult === "未发现宝藏或宝藏相关线索") {
result[country][route][index] = "不存在宝藏";
log.info("这里的宝箱都找齐啦");
} else if (compassResult === "发现宝藏或宝藏相关线索") {
result[country][route][index] = "存在宝藏";
log.warn("快看,是野生的宝箱");
} else {
result[country][route][index] = "未知";
}
// 更新记录文件
await writeDataToFile(result, recordFilePath);
index++; // 更新计数器
}
await fakeLog(`${route}.json`, false, false, 0);
}
}
} else {
log.info("开启导航模式将带您前往扫描到宝箱的地点请到达后使用快捷键暂停bgi找齐后继续运行前往下一个地点");
// 遍历 result 对象
for (const country in result) {
for (const country in result) {
if (Object.keys(result[country]).length === 0) {
log.warn(`国家 ${country} 不包含任何路线,将从 result 中删除`);
delete result[country];
}
}
//更新记录文件
await writeDataToFile(result, recordFilePath);
for (const route in result[country]) {
const treasures = result[country][route];
// 检查 routes 中是否存在对应的国家和路线
if (!routes[country] || !routes[country][route]) {
log.warn(`路线 ${route}不存在,从 result 中删除`);
delete result[country][route]; // 从 result 中删除对应的路线
for (const country in result) {
if (Object.keys(result[country]).length === 0) {
log.warn(`国家 ${country} 不包含任何路线,将从 result 中删除`);
delete result[country];
}
}
//更新记录文件
await writeDataToFile(result, recordFilePath);
continue;
}
const filesForRoute = routes[country][route]; // 获取该路线下的所有文件路径
for (let i = 0; i < treasures.length; i++) {
if (treasures[i] === "存在宝藏") {
// 检查字符串是否以“主”结尾
if (route.endsWith("主")) {
// 获取“主”字前的一个字
targetElement = route[route.length - 2]; // 获取倒数第二个字符
log.info(`该路线需要${targetElement}系主角`);
if (targetElement != currentElement) {
currentElement = targetElement;
await switchTravelerElement(targetElement);
}
}
// 遍历前 i + 1 个地图追踪文件
log.info(`路线${route}${i + 1}个点位存在宝藏`);
for (let j = 0; j <= i; j++) {
if (j < filesForRoute.length) {
const filePath = filesForRoute[j];
await pathingScript.runFile(filePath);
try {
await sleep(10);
} catch (error) {
log.error(`终止任务: ${error}`);
return;
}
} else {
log.error("路线数量错误,请检查文件");
return;
}
}
keyPress("Z");
result[country][route][i] = "未知";
await writeDataToFile(result, recordFilePath);
log.info("到啦野生的宝箱在召唤你请在10秒内按下快捷键暂停bgi运行找齐该点位宝箱后继续运行");
await pathingScript.runFile("assets/waitFor10Seconds.json");
log.info("已更新记录中对应项目为未知,继续运行");
}
}
}
}
log.info("所有存在宝藏的位置都处理了")
}
await fakeLog("提瓦特扫描仪", true, false, 2333);
})();
/**
* 切换主角元素
*/
async function switchTravelerElement(element) {
const fullPath = `assets/switchElement/${element}.json`;
//导航至对应的七天神像
await pathingScript.runFile(fullPath);
//点击与某元素共鸣
log.info(`旅行者正在汲取${element}元素力`);
for (let i = 0; i < 5; i++) {
await click(1400, 675);
await sleep(500);
}
//传送离开防止影响后续
await genshin.returnMainUi();
await genshin.tpToStatueOfTheSeven();
}
/**
* 检查罗盘是否处于冷却状态
* @returns {Promise<boolean>}
*/
async function isCompassFree(totalAttempts = 3) {
const iconPaths = [
{ path: "assets/icon/Anemo.png", name: "风" },
{ path: "assets/icon/Geo.png", name: "岩" },
{ path: "assets/icon/Electro.png", name: "雷" },
{ path: "assets/icon/Dendro.png", name: "草" },
{ path: "assets/icon/Hydro.png", name: "水" },
{ path: "assets/icon/Pyro.png", name: "火" },
//{ path: "assets/icon/Cryo.png", name: "冰" }
]; // 定义寻宝罗盘图标的路径及名称
for (let i = 0; i < totalAttempts; i++) {
// 后续每次暂停 100 毫秒
if (i > 0) {
await sleep(100);
}
// 遍历所有图标路径
for (const icon of iconPaths) {
// 使用图像识别方法查找图标
const iconList = captureGameRegion().findMulti(
RecognitionObject.TemplateMatch(file.ReadImageMatSync(icon.path))
);
// 判断是否找到图标
if (iconList && iconList.count > 0) {
log.debug(`${icon.name}之寻宝罗盘已刷新`);
return true; // 任意一个图标存在则返回 true
} else {
}
}
}
log.debug(`寻宝罗盘未刷新`);
return false; // 所有图标都不存在则返回 false
}
/**
* 检查罗盘是否处于冷却状态
* @returns {Promise<boolean>}
*/
async function isCompassFree(totalAttempts = 3) {
const iconPaths = [
{ path: "assets/icon/Anemo.png", name: "风" },
{ path: "assets/icon/Geo.png", name: "岩" },
{ path: "assets/icon/Electro.png", name: "雷" },
{ path: "assets/icon/Dendro.png", name: "草" },
{ path: "assets/icon/Hydro.png", name: "水" },
{ path: "assets/icon/Pyro.png", name: "火" },
//{ path: "assets/icon/Cryo.png", name: "冰" }
]; // 定义寻宝罗盘图标的路径及名称
for (let i = 0; i < totalAttempts; i++) {
// 后续每次暂停 100 毫秒
if (i > 0) {
await sleep(100);
}
// 遍历所有图标路径
for (const icon of iconPaths) {
// 使用图像识别方法查找图标
const iconList = captureGameRegion().findMulti(
RecognitionObject.TemplateMatch(file.ReadImageMatSync(icon.path))
);
// 判断是否找到图标
if (iconList && iconList.count > 0) {
log.debug(`${icon.name}之寻宝罗盘已刷新`);
return true; // 任意一个图标存在则返回 true
} else {
}
}
}
log.debug(`寻宝罗盘未刷新`);
return false; // 所有图标都不存在则返回 false
}
/**
* 检查罗盘是否处于不可用状态
* @returns {Promise<boolean>}
*/
async function isCompassLocked(totalAttempts = 3) {
const iconPaths = [
{ path: "assets/icon/Anemo_off.png", name: "风" },
{ path: "assets/icon/Geo_off.png", name: "岩" },
{ path: "assets/icon/Electro_off.png", name: "雷" },
{ path: "assets/icon/Dendro_off.png", name: "草" },
{ path: "assets/icon/Hydro_off.png", name: "水" },
{ path: "assets/icon/Pyro_off.png", name: "火" },
//{ path: "assets/icon/Cryo_off.png", name: "冰" }
]; // 定义寻宝罗盘图标的路径及名称
for (let i = 0; i < totalAttempts; i++) {
// 后续每次暂停 100 毫秒
if (i > 0) {
await sleep(100);
}
// 遍历所有图标路径
for (const icon of iconPaths) {
// 使用图像识别方法查找图标
const iconList = captureGameRegion().findMulti(
RecognitionObject.TemplateMatch(file.ReadImageMatSync(icon.path))
);
// 判断是否找到图标
if (iconList && iconList.count > 0) {
log.debug(`${icon.name}之寻宝罗盘处于锁定状态`);
return true; // 任意一个图标存在则返回 true
} else {
}
}
}
log.debug(`寻宝罗盘不处于锁定状态`);
return false; // 所有图标都不存在则返回 false
}
/**
* 检查 chest.png 或 flag.png 是否存在
* @param {number} [totalAttempts=3] - 尝试次数,默认为 3 次
* @returns {Promise<boolean>} - 如果 chest.png 或 flag.png 存在,则返回 true否则返回 false
*/
async function doTreasureExist(totalAttempts = 3) {
const iconPaths = [
{ path: "assets/icon/chest.png", name: "宝箱" },
{ path: "assets/icon/flag.png", name: "旗帜" }
]; // 定义图标路径及名称
for (let i = 0; i < totalAttempts; i++) {
// 后续每次暂停 100 毫秒
if (i > 0) {
await sleep(100);
}
// 遍历所有图标路径
for (const icon of iconPaths) {
// 使用图像识别方法查找图标
const iconList = captureGameRegion().findMulti(
RecognitionObject.TemplateMatch(file.ReadImageMatSync(icon.path))
);
// 判断是否找到图标
if (iconList && iconList.count > 0) {
log.debug(`${icon.name}图标存在`);
return true; // 任意一个图标存在则返回 true
}
}
}
log.debug(`未找到任何图标`);
return false; // 所有图标都不存在则返回 false
}
/**
* 检查宝藏状态
* @returns {Promise<string>} 返回检查结果的描述
*/
async function checkTreasure() {
let checkTime = 0;
let compassFree = false;
let offCount = 0;
let WaitStartTime = Date.now(); // 记录检查开始时间
while (checkTime < 31000) {
// 检查罗盘状态
const startTime = Date.now(); // 记录调用结束时间
compassFree = await isCompassFree(1);
let compassOff = await isCompassLocked(1);
const endTime = Date.now(); // 记录调用结束时间
checkTime = endTime - WaitStartTime; // 计算实际调用时间
if (compassOff) {
offCount++; //累计失败次数
}
if (compassFree) {
break; // 如果返回 true则跳出循环
}
if (offCount > 2) {
break;
}
//确保每轮循环时间不少于3秒
await sleep(startTime + 3000 - endTime)
}
if (!compassFree) {
//31秒后罗盘仍然不处于刷新状态或三次以上处于锁定状态
return "罗盘状态异常,可能在战斗中";
}
// 按下 Z 键两次
keyPress("Z");
await sleep(500);
keyPress("Z");
await sleep(500);
checkTime = 0;
let compassOff = false;
offCount = 0;
WaitStartTime = Date.now(); // 记录检查开始时间
while (checkTime < 10000) {
// 检查罗盘状态
const startTime = Date.now(); // 记录调用结束时间
compassFree = await isCompassFree(1);
compassOff = await isCompassLocked(1);
const endTime = Date.now(); // 记录调用结束时间
checkTime = endTime - WaitStartTime; // 计算实际调用时间
if (compassOff) {
offCount++; //累计失败次数
}
if (compassFree) {
break; // 如果返回 true则跳出循环
}
if (offCount > 0) {
break;
}
//确保每轮循环时间不少于3秒
await sleep(startTime + 3000 - endTime)
}
if (compassFree) {
//罗盘刷新了说明此处没有宝箱
return "未发现宝藏或宝藏相关线索";
} else if (checkTime >= 10000) {
//罗盘10秒内都没有刷新说明此处有宝箱
return "发现宝藏或宝藏相关线索";
} else {
//识别到罗盘处于锁定状态
return "罗盘状态异常,可能在战斗中";
}
}
//切换队伍
async function switchPartyIfNeeded(partyName) {
if (!partyName) {
await genshin.returnMainUi();
return;
}
try {
log.info("正在尝试切换至" + partyName);
if (!await genshin.switchParty(partyName)) {
log.info("切换队伍失败,前往七天神像重试");
await genshin.tpToStatueOfTheSeven();
await genshin.switchParty(partyName);
}
} catch {
log.error("队伍切换失败,可能处于联机模式或其他不可切换状态");
notification.error(`队伍切换失败,可能处于联机模式或其他不可切换状态`);
await genshin.returnMainUi();
}
}
// 定义 readFolder 函数
/*
该函数可以实现输入要处理的文件夹路径后,将其中所有文件/仅json文件按照原顺序存储在一个对象中具体使用参考主函数
*/
async function readFolder(folderPath, onlyJson) {
log.info(`开始读取文件夹:${folderPath}`);
// 新增一个堆栈,初始时包含 folderPath
const folderStack = [folderPath];
// 新增一个数组,用于存储文件信息对象
const files = [];
// 当堆栈不为空时,继续处理
while (folderStack.length > 0) {
// 从堆栈中弹出一个路径
const currentPath = folderStack.pop();
// 读取当前路径下的所有文件和子文件夹路径
const filesInSubFolder = file.ReadPathSync(currentPath);
// 临时数组,用于存储子文件夹路径
const subFolders = [];
for (const filePath of filesInSubFolder) {
if (file.IsFolder(filePath)) {
// 如果是文件夹,先存储到临时数组中
subFolders.push(filePath);
} else {
// 如果是文件,根据 onlyJson 判断是否存储
if (onlyJson) {
if (filePath.endsWith(".json")) {
const fileName = filePath.split('\\').pop(); // 提取文件名
const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组
files.push({
fullPath: filePath,
fileName: fileName,
folderPathArray: folderPathArray
});
//log.info(`找到 JSON 文件:${filePath}`);
}
} else {
const fileName = filePath.split('\\').pop(); // 提取文件名
const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组
files.push({
fullPath: filePath,
fileName: fileName,
folderPathArray: folderPathArray
});
//log.info(`找到文件:${filePath}`);
}
}
}
// 将临时数组中的子文件夹路径按原顺序压入堆栈
folderStack.push(...subFolders.reverse()); // 反转子文件夹路径
}
return files;
}
/**
* 检查当前时间是否处于限制时间内或即将进入限制时间
* @param {string} timeRule - 时间规则字符串,格式如 "4, 4-6, 10-12"
* @param {number} [threshold=10] - 接近限制时间的阈值(分钟)
* @returns {Promise<boolean>} - 如果处于限制时间内或即将进入限制时间,则返回 true否则返回 false
*/
async function isTimeRestricted(timeRule, threshold = 10) {
// 如果输入的时间规则为 undefined 或空字符串,视为不进行时间处理,返回 false
if (timeRule === undefined || timeRule === "") {
return false;
}
// 初始化 0-23 小时为可用状态
const hours = Array(24).fill(false);
// 解析时间规则
const rules = timeRule.split('').map(rule => rule.trim());
// 校验输入的字符串是否符合规则
for (const rule of rules) {
if (rule.includes('-')) {
// 处理时间段,如 "4-6"
const [startHour, endHour] = rule.split('-').map(Number);
if (isNaN(startHour) || isNaN(endHour) || startHour < 0 || startHour >= 24 || endHour <= startHour || endHour > 24) {
// 如果时间段格式不正确或超出范围,则报错并返回 true
log.error("时间填写不符合规则,请检查");
return true;
}
for (let i = startHour; i < endHour; i++) {
hours[i] = true; // 标记为不可用
}
} else {
// 处理单个时间点,如 "4"
const hour = Number(rule);
if (isNaN(hour) || hour < 0 || hour >= 24) {
// 如果时间点格式不正确或超出范围,则报错并返回 true
log.error("时间填写不符合规则,请检查");
return true;
}
hours[hour] = true; // 标记为不可用
}
}
// 获取当前时间
const now = new Date();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
log.info(`当前小时数为${currentHour},分钟数为${currentMinute}`);
// 检查当前时间是否处于限制时间内
if (hours[currentHour]) {
log.warn("处于限制时间内");
return true; // 当前时间处于限制时间内
}
// 检查当前时间是否即将进入限制时间
for (let i = 0; i < 24; i++) {
if (hours[i]) {
const nextHour = i;
const timeUntilNextHour = (nextHour - currentHour - 1) * 60 + (60 - currentMinute);
if (timeUntilNextHour > 0 && timeUntilNextHour <= threshold) {
// 如果距离下一个限制时间小于等于阈值,则等待到限制时间开始
log.warn("接近限制时间,开始等待");
await sleep(timeUntilNextHour * 60 * 1000);
return true;
}
}
}
log.info("不处于限制时间");
return false; // 当前时间不在限制时间内
}
/**
* 检查账户名是否合法
* @param {string} accountName - 账户名
* @returns {boolean} - 如果账户名合法,返回 true否则返回 false
*/
function isValidAccountName(accountName) {
// Windows文件名非法字符列表
const illegalCharacters = /[\\/:*?"<>|]/;
// Windows保留设备名称列表
const reservedNames = [
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
];
// 检查accountName是否为空字符串
if (accountName === "") {
log.error(`账户名 "${accountName}" 不合法,为空字符串。`);
log.error(`请使用合法的名称`);
return false;
}
// 检查accountName是否以空格开头
else if (accountName.startsWith(" ")) {
log.error(`账户名 "${accountName}" 不合法,以空格开头。`);
log.error(`请使用合法的名称`);
return false;
}
// 检查accountName是否以空格结尾
else if (accountName.endsWith(" ")) {
log.error(`账户名 "${accountName}" 不合法,以空格结尾。`);
log.error(`请使用合法的名称`);
return false;
}
// 检查accountName是否包含非法字符
else if (illegalCharacters.test(accountName)) {
log.error(`账户名 "${accountName}" 不合法,包含非法字符。`);
log.error(`请使用合法的名称`);
return false;
}
// 检查accountName是否是保留设备名称
else if (reservedNames.includes(accountName.toUpperCase())) {
log.error(`账户名 "${accountName}" 不合法,是保留设备名称。`);
log.error(`请使用合法的名称`);
return false;
}
// 检查accountName长度是否超过255字符
else if (accountName.length > 255) {
log.error(`账户名 "${accountName}" 不合法,账户名过长。`);
log.error(`请使用合法的名称`);
return false;
}
else {
log.info(`账户名 "${accountName}" 合法。`);
return true;
}
}
/**
* 将对象内容写入文件
* @param {Object} data - 要写入的对象
* @returns {Promise<void>}
*/
async function writeDataToFile(data, recordFilePath) {
// 初始化内容字符串
let content = "";
// 遍历对象,将内容逐行转化为文字
for (const country in data) {
content += `${country}\n`;
for (const route in data[country]) {
// 每条路线的最后一项之后没有逗号
const treasures = data[country][route].join('').replace(/$/, '');
content += `-${route}${treasures}\n`;
}
}
// 异步写入文件
await file.writeText(recordFilePath, content);
}
// fakeLog 函数,使用方法:将本函数放在主函数前,调用时请务必使用await否则可能出现v8白框报错
//在js开头处伪造该js结束运行的日志信息如 await fakeLog("js脚本", true, true, 0);
//在js结尾处伪造该js开始运行的日志信息如 await fakeLog("js脚本", true, false, 2333);
//duration项目仅在伪造结束信息时有效且无实际作用可以任意填写当你需要在日志中输出特定值时才需要单位为毫秒
//在调用地图追踪前伪造该地图追踪开始运行的日志信息,如 await fakeLog(`地图追踪.json`, false, true, 0);
//在调用地图追踪后伪造该地图追踪结束运行的日志信息,如 await fakeLog(`地图追踪.json`, false, false, 0);
//如此便可以在js运行过程中伪造地图追踪的日志信息可以在日志分析等中查看
async function fakeLog(name, isJs, isStart, duration) {
await sleep(10);
const currentTime = Date.now();
// 参数检查
if (typeof name !== 'string') {
log.error("参数 'name' 必须是字符串类型!");
return;
}
if (typeof isJs !== 'boolean') {
log.error("参数 'isJs' 必须是布尔型!");
return;
}
if (typeof isStart !== 'boolean') {
log.error("参数 'isStart' 必须是布尔型!");
return;
}
if (typeof currentTime !== 'number' || !Number.isInteger(currentTime)) {
log.error("参数 'currentTime' 必须是整数!");
return;
}
if (typeof duration !== 'number' || !Number.isInteger(duration)) {
log.error("参数 'duration' 必须是整数!");
return;
}
// 将 currentTime 转换为 Date 对象并格式化为 HH:mm:ss.sss
const date = new Date(currentTime);
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
const formattedTime = `${hours}:${minutes}:${seconds}.${milliseconds}`;
// 将 duration 转换为分钟和秒,并保留三位小数
const durationInSeconds = duration / 1000; // 转换为秒
const durationMinutes = Math.floor(durationInSeconds / 60);
const durationSeconds = (durationInSeconds % 60).toFixed(3); // 保留三位小数
// 使用四个独立的 if 语句处理四种情况
if (isJs && isStart) {
// 处理 isJs = true 且 isStart = true 的情况
const logMessage = `正在伪造js开始的日志记录\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`------------------------------\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`→ 开始执行JS脚本: "${name}"`;
log.debug(logMessage);
}
if (isJs && !isStart) {
// 处理 isJs = true 且 isStart = false 的情况
const logMessage = `正在伪造js结束的日志记录\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}${durationSeconds}\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`------------------------------`;
log.debug(logMessage);
}
if (!isJs && isStart) {
// 处理 isJs = false 且 isStart = true 的情况
const logMessage = `正在伪造地图追踪开始的日志记录\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`------------------------------\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`→ 开始执行地图追踪任务: "${name}"`;
log.debug(logMessage);
}
if (!isJs && !isStart) {
// 处理 isJs = false 且 isStart = false 的情况
const logMessage = `正在伪造地图追踪结束的日志记录\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}${durationSeconds}\n\n` +
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
`------------------------------`;
log.debug(logMessage);
}
}
/**
* 检查指定文件是否存在
* @param {string} filePath - 文件的完整路径
* @returns {boolean} - 如果文件存在,返回 true否则返回 false
*/
function doFileExist(filePath) {
// 提取文件夹路径和文件名
const folderPath = filePath.substring(0, filePath.lastIndexOf('/') + 1); // 获取文件夹路径
log.info(`目标文件夹路径:${folderPath}`)
const fileName = filePath.substring(filePath.lastIndexOf('/') + 1); // 获取文件名
log.info(`目标文件名:${fileName}`)
// 读取子文件夹中的所有文件路径
const filesInSubFolder = file.ReadPathSync(folderPath);
// 检查指定文件是否存在
for (const currentFilePath of filesInSubFolder) {
const currentFileName = currentFilePath.substring(currentFilePath.lastIndexOf('\\') + 1); // 提取文件名
if (currentFileName === fileName) {
return true; // 文件存在
}
}
return false; // 文件不存在
}
/**
* 读取并解析 JSON 文件
* @param {string} filePath - JSON 文件路径
* @returns {Promise<Object>} - 包含最后一个点位的 x 和 y 坐标,以及 description 字段
*/
async function checkJson(filePath) {
try {
// 使用 file.readText() 读取文件内容
const content = await file.readText(filePath);
// 解析 JSON 数据
const parsedData = JSON.parse(content);
// 获取最后一个点位的 x 和 y 坐标
const lastPosition = parsedData.positions[parsedData.positions.length - 1];
const lastX = lastPosition.x;
const lastY = lastPosition.y;
// 获取 description 字段
const description = parsedData.info.description;
// 返回结果对象
return {
lastX: lastX,
lastY: lastY,
description: description
};
} catch (error) {
log.error('读取或解析文件时发生错误:', error);
}
}