Files
TeyvatGuide/src/pages/User/Scripts.vue
2026-01-02 00:11:56 +08:00

364 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<v-app-bar>
<template #prepend>
<div class="us-top-title">
<img alt="icon" src="/source/UI/toolbox.webp" />
<span>实用脚本</span>
<v-select
v-model="curAccount"
:disabled="runScript || runAll"
:hide-details="true"
:items="accounts"
class="us-top-select"
density="compact"
item-title="uid"
label="账号"
variant="outlined"
>
<template #selection="{ item }">
<div class="select-main">
<img :src="item.raw.brief.avatar" alt="icon" />
<div class="content">
<span>{{ item.raw.brief.nickname }}</span>
<span>UID:{{ item.raw.uid }}</span>
</div>
</div>
</template>
<template #item="{ props, item }">
<div class="select-item" v-bind="props">
<img :src="item.raw.brief.avatar" alt="icon" />
<div class="content">
<span>{{ item.raw.brief.nickname }}</span>
<span>UID:{{ item.raw.uid }}</span>
</div>
<div class="append">
<v-icon v-if="item.raw.uid === uid" color="green" title="当前登录账号">
mdi-account-check
</v-icon>
<v-icon
v-else
icon="mdi-account-convert"
size="small"
title="切换用户"
@click="loadAccount(item.raw.uid)"
/>
</div>
</div>
</template>
</v-select>
<v-btn :loading="runAll" class="run-all-btn" variant="elevated" @click="tryExecAll()">
一键执行
</v-btn>
</div>
</template>
<template #append>
<v-btn class="us-test-btn" title="点击验证" variant="elevated" @click="tryCkVerify()">
打卡测试
</v-btn>
</template>
</v-app-bar>
<div class="us-page-container">
<!-- 左侧脚本列表 -->
<div class="us-scripts">
<TusMission ref="mission" v-model="runScript" />
<TusSign ref="sign" v-model="runScript" />
</div>
<!-- 右侧脚本输出 -->
<TusOutput />
</div>
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TusMission from "@comp/userScripts/tus-mission.vue";
import TusOutput from "@comp/userScripts/tus-output.vue";
import TusSign from "@comp/userScripts/tus-sign.vue";
import apiHubReq from "@req/apiHubReq.js";
import miscReq from "@req/miscReq.js";
import painterReq from "@req/painterReq.js";
import TSUserAccount from "@Sqlm/userAccount.js";
import useUserStore from "@store/user.js";
import { exit } from "@tauri-apps/plugin-process";
import TGNotify from "@utils/TGNotify.js";
import { storeToRefs } from "pinia";
import { onBeforeMount, onMounted, ref, shallowRef, useTemplateRef } from "vue";
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
const { uid, briefInfo, cookie, account } = storeToRefs(useUserStore());
// 路由参数
const autoRun = ref<boolean>(false);
const exitAfter = ref<boolean>(false);
const skipGeetest = ref<boolean>(false);
const targetUids = shallowRef<Array<string>>([]);
const accounts = shallowRef<Array<TGApp.App.Account.User>>([]);
const curAccount = shallowRef<TGApp.App.Account.User>();
const runScript = ref<boolean>(false);
const runAll = ref<boolean>(false);
const missionEl = useTemplateRef("mission");
const signEl = useTemplateRef("sign");
onBeforeMount(async () => {
if (Object.keys(route.query).length > 0) {
exitAfter.value = route.query.exit === "true";
skipGeetest.value = route.query.skip === "true";
autoRun.value = route.query.auto === "true";
if (route.query.uids) {
if (Array.isArray(route.query.uids)) {
targetUids.value = <Array<string>>route.query.uids;
} else {
targetUids.value = [route.query.uids];
}
}
await router.replace({ path: route.path, query: {} });
}
});
onMounted(async () => {
accounts.value = await TSUserAccount.account.getAllAccount();
targetUids.value = targetUids.value.filter((u) => accounts.value.some((a) => a.uid === u));
curAccount.value = accounts.value.find((i) => i.uid === uid.value);
await showLoading.end();
if (autoRun.value) await tryAutoRun();
});
async function tryAutoRun(): Promise<void> {
let uids: Array<string> = accounts.value.map((u) => u.uid);
if (targetUids.value.length === 0) {
showSnackbar.warn("未接收到合法UID列表默认全部执行");
} else {
uids = targetUids.value;
}
const startTime = Date.now();
for (const uid of uids) {
await loadAccount(uid);
await tryExecAll();
}
if (exitAfter.value) {
showSnackbar.success("任务执行完成,即将自动退出");
const costTime = Date.now() - startTime;
await TGNotify.normal("自动脚本执行完成", `耗时${Math.floor(costTime / 1000)}s`);
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
await exit();
}
}
async function loadAccount(ac: string): Promise<void> {
if (uid.value && ac === uid.value) {
showSnackbar.warn("该账户已经登录,无需切换");
return;
}
const accountGet = await TSUserAccount.account.getAccount(ac);
if (!accountGet) {
showSnackbar.warn(`未找到${uid}的账号信息,请重新登录`);
return;
}
uid.value = ac;
briefInfo.value = accountGet.brief;
cookie.value = accountGet.cookie;
const gameAccount = await TSUserAccount.game.getCurAccount(ac);
if (!gameAccount) {
showSnackbar.warn(`未找到${uid.value}的游戏账号信息,请尝试刷新`);
return;
}
account.value = gameAccount;
showSnackbar.success(`成功切换到用户${uid.value}`);
}
async function tryCkVerify(): Promise<void> {
if (!cookie.value) {
showSnackbar.warn("当前账号未登录,请先登录");
return;
}
const check = await showDialog.check("确定验证?", "将通过执行米社社区打卡以验证ck有效性");
if (!check) {
showSnackbar.cancel("已取消验证");
return;
}
const ck = {
stoken: cookie.value.stoken,
stuid: cookie.value.stuid,
mid: cookie.value.mid,
};
let flag = false;
let challenge: string | undefined = undefined;
while (!flag) {
await showLoading.start("正在验证CK有效性");
await painterReq.forum.recent(26, 2, 1, undefined, 20, ck);
const resp = await apiHubReq.sign(ck, 2, challenge);
await showLoading.update(`[${resp.retcode}] ${resp.message}`);
if (resp.retcode === -100) {
await showLoading.end();
break;
} else if (resp.retcode === 1034) {
await showLoading.end();
const cGet = await miscReq.challenge(ck);
if (cGet !== false) challenge = cGet;
else break;
} else {
flag = true;
await showLoading.end();
}
}
if (!flag) showSnackbar.error("CK验证失败");
else showSnackbar.success("CK验证成功");
}
async function tryExecAll(): Promise<void> {
if (!cookie.value) {
showSnackbar.warn("当前账号未登录,请先登录");
return;
}
if (!curAccount.value) {
showSnackbar.warn("当前账号未选择,请先选择账号");
return;
}
if (runScript.value || runAll.value) {
showSnackbar.warn("脚本正在执行,请稍后");
return;
}
runAll.value = true;
await missionEl.value?.tryAuto(skipGeetest.value);
await signEl.value?.tryAuto(skipGeetest.value);
runAll.value = false;
}
</script>
<style lang="scss" scoped>
.us-top-title {
display: flex;
align-items: center;
justify-content: center;
margin-left: 12px;
column-gap: 8px;
img {
width: 32px;
height: 32px;
}
span {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
}
.us-top-select {
width: 250px;
max-width: 250px;
}
.select-main {
position: relative;
display: flex;
height: 24px;
align-items: center;
justify-content: center;
column-gap: 4px;
img {
width: 24px;
height: 24px;
}
.content {
position: relative;
display: flex;
flex-direction: column;
:first-child {
font-family: var(--font-title);
font-size: 12px;
}
:last-child {
font-size: 10px;
}
}
}
.select-item {
position: relative;
display: flex;
width: 100%;
box-sizing: border-box;
align-items: center;
justify-content: flex-start;
padding: 8px;
column-gap: 4px;
img {
width: 24px;
height: 24px;
}
.content {
position: relative;
display: flex;
flex-direction: column;
:first-child {
font-family: var(--font-title);
font-size: 12px;
}
:last-child {
font-size: 10px;
}
}
.append {
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
}
}
.top-hint {
position: relative;
padding: 8px;
color: var(--tgc-pink-1);
cursor: pointer;
font-family: var(--font-title);
font-size: 20px;
}
.us-page-container {
display: flex;
height: calc(100vh - 100px);
align-items: flex-start;
justify-content: center;
column-gap: 12px;
}
.us-scripts {
position: relative;
display: flex;
width: 100%;
max-height: 100%;
box-sizing: border-box;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-right: 8px;
overflow-y: auto;
row-gap: 8px;
}
.run-all-btn,
.us-test-btn {
background: var(--tgc-btn-1);
color: var(--btn-text);
}
.us-test-btn {
margin-right: 12px;
}
</style>