From ddd295565bd976f27c5519696c7a059a9d2364bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=A1=E5=A4=8D=E4=B9=A0?= <2353956224@qq.com> Date: Tue, 28 Oct 2025 19:41:05 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=B5=8B=E8=AF=95=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/account.js | 4 + src/api/chat.js | 5 + src/api/ios.js | 15 + src/assets/wifi.png | Bin 0 -> 6299 bytes src/components/HostListManagerDialog.vue | 2 +- src/components/MessageDialogd.vue | 4 + src/components/MultiLineInputDialog.vue | 42 +- src/components/translationDialog.vue | 538 +++++++++++++++++++++++ src/stores/storage.js | 6 +- src/utils/axios.js | 4 +- src/views/HomeView.vue | 4 +- src/views/VideoStream.vue | 493 ++++++++++++++++----- vue.config.js | 3 +- 13 files changed, 996 insertions(+), 124 deletions(-) create mode 100644 src/assets/wifi.png create mode 100644 src/components/translationDialog.vue diff --git a/src/api/account.js b/src/api/account.js index 2903522..4a45f2d 100644 --- a/src/api/account.js +++ b/src/api/account.js @@ -13,6 +13,10 @@ export function getIdByName(name) { export function update(data) { return postAxios({ url: 'api/save_data/update', data }) } +//批量修改主播建联状态 +export function updates(data) { + return postAxios({ url: 'api/save_data/updates', data }) +} //获取话术 export function prologue() { diff --git a/src/api/chat.js b/src/api/chat.js index 296ef1f..aa131d6 100644 --- a/src/api/chat.js +++ b/src/api/chat.js @@ -12,3 +12,8 @@ export function translationToChinese(data) { export function translation(data) { return postAxios({ url: '/translation', data }) } + + +export function customTranslation(data) { + return postAxios({ url: '/customTranslation', data }) +} diff --git a/src/api/ios.js b/src/api/ios.js index 5496b92..2222a93 100644 --- a/src/api/ios.js +++ b/src/api/ios.js @@ -87,6 +87,11 @@ export function updateAnchorList(data) { export function aiConfig(data) { return postAxios({ url: 'aiConfig', data }) } +//设置经纪人信息 +export function getAiConfig() { + return getAxios({ url: 'getAiConfig' }) +} + //获取消息列表 export function selectLast(data) { return getAxios({ url: 'select_last_message', data }) @@ -103,3 +108,13 @@ export function deleteLast(data) { export function stopAllTask(data) { return postAxios({ url: 'stopAllTask', data }) } + +//换号 +export function changeAccount(data) { + return postAxios({ url: 'changeAccount', data }) +} +//重置tk +export function restartTikTok(data) { + return postAxios({ url: 'restartTikTok', data }) +} + diff --git a/src/assets/wifi.png b/src/assets/wifi.png new file mode 100644 index 0000000000000000000000000000000000000000..30bf3cb214c4bbcd0df6e3c16a5e1c6fe3500008 GIT binary patch literal 6299 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 it.country || '—' }} {{ it.invitationType == 2 ? '金票' : '普票' - }} + }} diff --git a/src/components/MessageDialogd.vue b/src/components/MessageDialogd.vue index cdc08d4..1ade298 100644 --- a/src/components/MessageDialogd.vue +++ b/src/components/MessageDialogd.vue @@ -25,6 +25,8 @@ {{ item.sender || '未知用户' }} · {{ item.device || '未知设备' }} + · + {{ item.time || '未知时间' }}
+ @@ -71,6 +74,7 @@ const normalizedMessages = computed(() => { raw: m, sender: m?.sender ?? m?.name ?? m?.user ?? m?.from ?? '', device: m?.device ?? m?.deviceName ?? m?.udid ?? '', + time: m?.time ?? m?.timestamp ?? '', text: m?.text ?? m?.content ?? '', // 统一转成 0/1,兼容旧的 seen/read 布尔 status: Number( diff --git a/src/components/MultiLineInputDialog.vue b/src/components/MultiLineInputDialog.vue index 3e48873..86c631f 100644 --- a/src/components/MultiLineInputDialog.vue +++ b/src/components/MultiLineInputDialog.vue @@ -8,12 +8,30 @@ {{ lineCount }}/{{ MAX_LINES_FOR_ANCHOR }} 行 + + 自动回复 - + + + + + + + + 评论 + + + + 导入{{ title }} + 取消 确定 @@ -26,8 +44,13 @@ import { ref, computed, watch } from 'vue'; import { prologue, comment } from '@/api/account'; const MAX_LINES_FOR_ANCHOR = 100; -let value1 = ref(false); + +let data = ref({ + // needTranslate: false, + auto: false, + common: true, +}); const props = defineProps({ visible: { type: Boolean, required: true }, title: { type: String, default: '' }, @@ -67,7 +90,7 @@ function handleConfirm() { return; } const items = parseLines(); - emit('confirm', items, props.title, props.index, value1.value); + emit('confirm', items, props.title, props.index, data.value); closingByConfirm.value = true; emit('update:visible', false); } @@ -83,7 +106,11 @@ function onClosed() { // 重置表单状态 rawText.value = ''; - value1.value = false; + // data.value = { + // needTranslate: false, + // auto: false, + // common: true, + // } // 只有非“确定”关闭才对外发 cancel if (!byConfirm) emit('cancel'); @@ -93,12 +120,13 @@ function onClosed() { } function exportPrologue(title) { - if (title === '评论') { - comment().then(res => { + if (title === '私信') { + prologue().then(res => { rawText.value = res.map(item => item).join('\n\n'); }); + } else { - prologue().then(res => { + comment().then(res => { rawText.value = res.map(item => item).join('\n\n'); }); } diff --git a/src/components/translationDialog.vue b/src/components/translationDialog.vue new file mode 100644 index 0000000..7793cdd --- /dev/null +++ b/src/components/translationDialog.vue @@ -0,0 +1,538 @@ + + + + + \ No newline at end of file diff --git a/src/stores/storage.js b/src/stores/storage.js index 5a5da19..88ff599 100644 --- a/src/stores/storage.js +++ b/src/stores/storage.js @@ -84,10 +84,14 @@ export function setContentpriList(data) { localStorage.setItem('Contentpri', JSON.stringify(data)); } +/** 以“换行”拼接输出,用于 textarea 等展示 */ +export function getContentListMultiline() { + return getContentList() == null ? '' : getContentList().join('\n'); +} // 用于获取评论信息 export function getContentList() { const arr = JSON.parse(localStorage.getItem('Content')) - return [arr, arr, arr, arr, arr, arr, arr, arr]; + return arr; } // 用于设置评论信息 export function setContentList(data) { diff --git a/src/utils/axios.js b/src/utils/axios.js index aebac99..3e3d30c 100644 --- a/src/utils/axios.js +++ b/src/utils/axios.js @@ -20,11 +20,11 @@ function attachInterceptors(instance) { instance.interceptors.request.use((config) => { // 登录/换租户接口可能不需要 token,根据你的需求放行 const urlLast = sliceUrl(config.url || '') - if ((urlLast === 'prologue' || urlLast === 'comment' || urlLast === 'aiChat-logout')) { + if ((urlLast === 'prologue' || urlLast === 'comment' || urlLast === 'aiChat-logout' || urlLast === 'updates')) { config.headers['vvtoken'] = getToken() } // 超时 & 通用头 - config.timeout = 60000 + config.timeout = 600000 if (!config.headers) config.headers = {} // 大多数 POST 走 x-www-form-urlencoded(保持你原来的行为) // console.log(config.method) diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index df21c6d..9e72f94 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -79,7 +79,7 @@ import { getToken, setToken, setUser, setUserPass, getUserPass } from '@/stores/ import { ElLoading, ElMessage } from 'element-plus'; import { passToken } from '@/api/ios'; -let version = ref('1.6.4'); +let version = ref('2.5.0'); onMounted(() => { @@ -122,7 +122,7 @@ const onSubmit = () => { }).catch((err) => { loading.close(); }).finally((err) => { - loading.close(); + // loading.close(); }) }) }; diff --git a/src/views/VideoStream.vue b/src/views/VideoStream.vue index 9b7b309..01dda5d 100644 --- a/src/views/VideoStream.vue +++ b/src/views/VideoStream.vue @@ -2,8 +2,17 @@
- 人设编辑 + + +
@@ -37,7 +46,7 @@ @mousemove.stop="(e) => onCanvasMove(device.deviceId, e, index)" />
- +
重置tiktok
获取当前聊天记录
停止任务
开启
@@ -47,13 +56,17 @@
+
+ +
- - 分钟换一次 - --> + + 小时换一次 +
联盟号
@@ -121,19 +134,21 @@ import { setphoneXYinfo, getphoneXYinfo, getUser, getHostList, setHostList, getContentpriList, setContentpriList, getContentList, setContentList, - setsessionId, getsessionId, getContentpriListMultiline + setsessionId, getsessionId, getContentListMultiline, getContentpriListMultiline } from '@/stores/storage' import { connectSSE } from '@/utils/sseUtils' import { ElMessage, ElMessageBox, ElLoading } from 'element-plus' -import { chat, translationToChinese, translation } from "@/api/chat"; +import { chat, translationToChinese, translation, customTranslation } from "@/api/chat"; import HostListManagerDialog from '@/components/HostListManagerDialog.vue' import AgentGuildDialog from '@/components/AgentGuildDialog.vue' import MultiLineInputDialog from '@/components/MultiLineInputDialog.vue'; // 根据实际路径修改 +import TranslationDialog from '@/components/translationDialog.vue'; // 根据实际路径修改 + import ChatDialog from '@/components/ChatDialog.vue' import MessageDialogd from '@/components/MessageDialogd.vue' import { pickTikTokBundleId } from '@/utils/arrUtils' -import { logout } from '@/api/account'; +import { logout, updates } from '@/api/account'; import { getDeviceList, toHome, @@ -153,7 +168,10 @@ import { aiConfig, selectLast, updatelast, - stopAllTask + changeAccount, + stopAllTask, + anchorList, + restartTikTok } from '@/api/ios'; import ding from '@/assets/mes.wav' import { set } from "lodash"; @@ -163,6 +181,10 @@ const openShowChat = ref(true) const showHostDlg = ref(false) //ai人设弹框 const showMyInfo = ref(false) +//翻译弹框 +const showtransDlg = ref(false) +let transDlgType = ref('') + // 假设这是你已有的数据 const borkerConfig = reactive({ agentName: '', @@ -171,12 +193,15 @@ const borkerConfig = reactive({ contact: '' }) +//评论 自动化 +let common = ref(true); let initialTextStr = ref('') // 初始文本字符串 // 批次缓冲(仅用于当前“波”) let batch = []; // [{ country, text }] let flushTimer = null; -let hostList = [] +let hostList = [] // 主播列表 +let comonList = [] //评论列表 //查询列表轮询 let getListtimer = null; let userdata = getUser(); @@ -288,9 +313,15 @@ const buttons = [ }; if (isLocked('like')) return + // runType.value = 'like' + // deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId })) + dialogTitle.value = '视频评论'; + setTimeout(() => { + showDialog.value = true; + + initialTextStr.value = getContentListMultiline(); + }, 500) - runType.value = 'like' - deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId })) }, show: () => true, img: { @@ -314,24 +345,31 @@ const buttons = [ { label: '监测消息', onClick: () => { - if (runType.value == 'lisen') { + if (runType.value == 'listen') { deviceInformation.value.forEach((item) => { stopScript({ udid: item.deviceId }) }) runType.value = '' return }; - if (isLocked('lisen')) return - - runType.value = 'lisen' + if (isLocked('listen')) return + //如果传评论就注释一下两行代码,解开后面代码 + runType.value = 'listen' deviceInformation.value.forEach((item) => monitorMessages({ udid: item.deviceId })) + // dialogTitle.value = '评论(无消息将刷视频)'; + // setTimeout(() => { + // showDialog.value = true; + + // initialTextStr.value = getContentListMultiline(); + // }, 500) + }, show: () => true, img: { normal: new URL('@/assets/video/leftBtn1.png', import.meta.url).href, hover: new URL('@/assets/video/leftBtn1-1.png', import.meta.url).href }, - style: () => ctrlStyle('lisen') + style: () => ctrlStyle('listen') }, // { @@ -371,9 +409,9 @@ const isAlliance = ref(false) // 是否开启 const interruptEnabled = ref(false) // 每隔多少分钟打断一次(可做弹窗配置) -const interruptEveryMin = ref(15) +const interruptEveryMin = ref(2) // 打断器最大重试次数 & 每次超时(按需调) -const interruptMaxRetries = 3 +const interruptMaxRetries = 1 const interruptCallTimeoutMs = 120_000 // 运行态 @@ -673,44 +711,76 @@ const selectDevice = (index) => { const dragState = ref({}) // 以 index 作为 key 保存 {ox, oy, t} const onCanvasDown = (udid, e, index) => { - // 记录起点(Canvas 内的 offset) - dragState.value[index] = { ox: e.offsetX, oy: e.offsetY, t: Date.now(), udid } + // 记录起点(Canvas 内 offset)和时间 + const startDev = mapToDeviceXY(index, e.offsetX, e.offsetY) // 也记录“设备坐标”起点,便于直接打印 + dragState.value[index] = { + ox: e.offsetX, + oy: e.offsetY, + t: Date.now(), + udid, + startDevXY: startDev, + startOffsetXY: { x: e.offsetX, y: e.offsetY } + } } const onCanvasMove = (udid, e, index) => { - // 如需在 overlay 上画指示、十字线等,可在这里使用 e.offsetX/e.offsetY + // 若要实时观察滑动轨迹(可选) + const st = dragState.value[index] + if (!st) return + const curDev = mapToDeviceXY(index, e.offsetX, e.offsetY) + // 建议:调试阶段打开,稳定后可注释或做节流 + // console.log('[MOVE]', { + // udid, + // offsetXY: { x: e.offsetX, y: e.offsetY }, + // deviceXY: curDev, + // elapsedMs: Date.now() - st.t + // }) } const onCanvasUp = async (udid, e, index) => { const st = dragState.value[index] if (!st) return - const { ox, oy, t } = st + const { ox, oy, t, startDevXY, startOffsetXY } = st const dx = e.offsetX - ox const dy = e.offsetY - oy const elapsed = Date.now() - t delete dragState.value[index] - // 映射到真实分辨率 - const p0 = mapToDeviceXY(index, ox, oy) - const p1 = mapToDeviceXY(index, e.offsetX, e.offsetY) - console.log(" x, y", p0, p1) - // 判断是“点按”还是“滑动” - const MOVE_THR = 5 // 像素阈值(可按需调整) + // 终点(设备坐标 & 画布 offset) + const endDevXY = mapToDeviceXY(index, e.offsetX, e.offsetY) + const endOffsetXY = { x: e.offsetX, y: e.offsetY } + + // ✅ 这里打印:起点/终点(两套坐标)+ 耗时 + console.log('[鼠标滑动,起点/终点)+ 耗时]', { + udid, + start: { + offsetXY: startOffsetXY, // 画布内起点 + deviceXY: startDevXY // 设备坐标起点 + }, + end: { + offsetXY: endOffsetXY, // 画布内终点 + deviceXY: endDevXY // 设备坐标终点 + }, + deltaOffset: { dx, dy }, + durationMs: elapsed, + }) + + // === 你原有逻辑:tap or swipe === + const MOVE_THR = 5 const isTap = Math.hypot(dx, dy) < MOVE_THR && elapsed < 500 try { if (isTap) { - await tapAction({ udid, x: p1.x, y: p1.y }) - // console.log('tap', p1) + await tapAction({ udid, x: endDevXY.x, y: endDevXY.y }) } else { - // 只需要方向:1上/2左/3下/4右 - const rotation = Number((deviceInformation.value[index] || {}).rotation || 0) - - const code = getSwipeCodeWithRotation(dx, dy, rotation) // 必定得到1~4 - console.log("code", code) - await swipeAction({ udid, direction: code }) + // //通过方向判断code 1234 + // const rotation = Number((deviceInformation.value[index] || {}).rotation || 0) + // const code = getSwipeCodeWithRotation(dx, dy, rotation) + // await swipeAction({ udid, direction: code }) + //通过自定义滑动坐标和时间传参 + await swipeAction({ udid, sx: startDevXY.x, sy: startDevXY.y, ex: endDevXY.x, ey: endDevXY.y, duration: elapsed / 1000 }) } } catch (err) { console.error(err) @@ -826,64 +896,90 @@ async function stopAll() { } //确认多行文本框内容 -function onDialogConfirm(result, type, index, isMon) { - // console.log(type, result, isMon); - console.log(type) +function onDialogConfirm(result, type, index, data) { + console.log(type, result, data); if (type == '主播ID') { hostList = (result || []).map(id => ({ id, country: '' })) - dialogTitle.value = '私信'; + //无需评论,注释,如果需要注释下面代码,放开后面代码 + // dialogTitle.value = '私信'; + // setTimeout(() => { + // showDialog.value = true; + + // initialTextStr.value = getContentpriListMultiline(); + // }, 500) + dialogTitle.value = '评论'; setTimeout(() => { showDialog.value = true; - initialTextStr.value = getContentpriListMultiline(); + initialTextStr.value = getContentListMultiline(); }, 500) + } else if (type == '评论') { + comonList = result + setContentList(result) + common.value = data.common + // dialogTitle.value = '私信'; + // setTimeout(() => { + // showDialog.value = true; + + // initialTextStr.value = getContentpriListMultiline(); + // }, 500) + transDlgType.value = '私信' + showtransDlg.value = true } else if (type == '私信') { - runType.value = 'follow' - setContentpriList(result) - // console.log('hostList', hostList) + // runType.value = 'follow' + // setContentpriList(result) - if (isAlliance.value) { - followAndGreetUnion( - { - deviceList: deviceInformation.value.map(item => item.deviceId), - anchorList: hostList.map(item => ({ - anchorId: item.id, - country: item.country, - invitationType: item.invitationType, - state: stateByInvType(item.invitationType), - })), - prologueList: result, - needReply: isMon - } - ).then((res) => { - ElMessage({ type: 'success', message: '任务开启成功' }); + // if (isAlliance.value) { + // followAndGreetUnion( + // { + // deviceList: deviceInformation.value.map(item => item.deviceId), + // anchorList: hostList.map(item => ({ + // anchorId: item.id, + // country: item.country, + // invitationType: item.invitationType, + // state: stateByInvType(item.invitationType), + // })), + // prologueList: result, + // needReply: data.auto, + // needTranslate: data.needTranslate, + // } + // ).then((res) => { + // ElMessage({ type: 'success', message: '任务开启成功' }); - hostList = [] - }) - } else { - passAnchorData( - { - deviceList: deviceInformation.value.map(item => item.deviceId), - anchorList: hostList.map(item => ({ - anchorId: item.id, - country: item.country, - invitationType: item.invitationType, - state: stateByInvType(item.invitationType), - })), - prologueList: result, - needReply: isMon - } - ).then((res) => { - ElMessage({ type: 'success', message: '任务开启成功' }); - - hostList = [] - }) - - } + // hostList = [] + // }) + // } else { + // passAnchorData( + // { + // deviceList: deviceInformation.value.map(item => item.deviceId), + // anchorList: hostList.map(item => ({ + // anchorId: item.id, + // country: item.country, + // invitationType: item.invitationType, + // state: stateByInvType(item.invitationType), + // })), + // prologueList: result, + // comment: comonList, + // needReply: data.auto, + // needTranslate: data.needTranslate, + // isComment: data.common + // } + // ).then((res) => { + // ElMessage({ type: 'success', message: '任务开启成功' }); + // hostList = [] + // }) + // } + } else if (type == '视频评论') { + runType.value = 'like' + deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId, comment: result, isComment: data.common })) + } else if (type == '评论(无消息将刷视频)') { + runType.value = 'listen' + deviceInformation.value.forEach((item) => monitorMessages({ udid: item.deviceId, comment: result })) } + } onMounted(async () => { @@ -898,6 +994,7 @@ onMounted(async () => { const res = await window.electronAPI.isiproxy({ intervalMs: 2000, exeName: 'iproxy.exe', + // maxWaitMs: 3000, // 可选:5分钟超时 maxWaitMs: 300000, // 可选:5分钟超时 }); if (res.running) { @@ -915,12 +1012,34 @@ onMounted(async () => { window.electronAPI.startMq(userdata.tenantId, userdata.id) // 初始化时获取设备列表 - getListtimer = setInterval(() => { + + //每3秒获取一次设备列表 消息列表 和 查询主播列表是否还有主播 + getListtimer = setInterval(async () => { getDeviceListFun() selectLastFun() + const hostsList = await getStoredHostList() + // console.log(hostsList.length) + //当私信主播时,主播列表没有数据了,提示列表空了 并且关闭私信 + if (runType.value == 'follow') { + if (hostsList.length <= 0) { + await stopAll() + runType.value = 'like' + deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId })) + ElMessageBox.alert('私信全部完成!(刷视频中)', '提示', { + confirmButtonText: 'OK', + callback: (action) => { + }, + }) + } + } }, 3000) + + setInterval(async () => { + await checkVPN() + }, 1000 * 20) + if (!await isAiConfig()) { showMyInfo.value = true } @@ -959,9 +1078,10 @@ onMounted(async () => { const country = data && data.country != null ? data.country : '' const text = data && (data.hostsId != null ? data.hostsId : data.text) const invitationType = data && (data.invitationType != null ? data.invitationType : '') + const id = data && data.id != null ? data.id : '' if (!text) return - batch.push({ country, text, invitationType }) + batch.push({ country, text, invitationType, id }) scheduleFlush((items) => { // 批量入库 @@ -971,6 +1091,10 @@ onMounted(async () => { invitationType: h.invitationType, state: stateByInvType(h.invitationType), })) + updates(items.map(h => ({ + id: h.id, + aiOperation: 1, + }))) addTempAnchorData(list) }, 400) }) @@ -1014,21 +1138,16 @@ const getDeviceListFun = () => { //获取新消息 const selectLastFun = () => { selectLast().then((res) => { - // console.log("返回", deviceInformation, res) let mesInfoData = res mesInfoData.forEach(element => { deviceInformation.value.forEach((item, index) => { - console.log(res) - console.log(item.deviceId == element.device) if (item.deviceId == element.device) { element.device = index + 1 + '号设备' - console.log(element.device) } }) }); - // console.log('============', mesInfoData) - MesNewList.value = mesInfoData + MesNewList.value = [...mesInfoData]; }) } @@ -1079,7 +1198,7 @@ async function uploadLogFile() { } } -function runTask(key, deviceId) { +function runTask(key, deviceId, type) { console.log('[schedule] 切换到任务:', key, printCurrentTime()) forceActivate(key, async () => { @@ -1094,6 +1213,7 @@ function runTask(key, deviceId) { deviceList: [deviceId], anchorList: [], prologueList: getContentpriList(), + comment: comonList, needReply: false } ).then((res) => { @@ -1102,7 +1222,7 @@ function runTask(key, deviceId) { return } - + //第一个小时结束后,第二轮开始的时候,直接进入follow setTimeout(() => { runType.value = 'follow' @@ -1111,6 +1231,7 @@ function runTask(key, deviceId) { deviceList: deviceInformation.value.map(item => item.deviceId), anchorList: [], prologueList: getContentpriList(), + comment: comonList, needReply: false } ).then((res) => { @@ -1194,20 +1315,46 @@ function resumeAfterInterrupt() { pauseSnapshot = null } +/** + * 执行一次中断器函数 + * 该函数会尝试使用设备列表中的每个设备更改账户,并检查是否所有操作都成功 + * @returns {Promise} 返回一个Promise,解析为布尔值,表示操作是否全部成功 + */ async function runInterrupterOnce() { - // TODO: 换成你的真实调用,比如:替换 - // const ok = await someApi({ ... }) - // return !!ok - - // 示例:包装一个带超时的 Promise;把你的方法放到 promiseFn 里 + // 定义一个异步函数,用于执行设备账户更改操作 const promiseFn = async () => { - // 这里放你的真实逻辑 - changeAccount - console.log('[换号] 正在执行...') - // 比如 await doDailyCheck(); 然后返回 true/false - return true - } - return await withTimeout(promiseFn(), interruptCallTimeoutMs).catch(() => false) + // 从deviceInformation中提取设备ID,并过滤掉无效值 + const devices = (deviceInformation.value || []) + .map(d => d?.deviceId) + .filter(Boolean); + + // 如果没有有效设备,直接返回false + if (!devices.length) return false; + console.log(devices.length + '台设备换账号') + // 为每个设备创建一个检查函数,用于尝试更改账户 + const checks = devices.map(async (udid) => { + try { + // 尝试更改账户并检查返回结果 + const res = await changeAccount({ udid: udid }); + // 检查返回值是否表示成功 + return ( + res.code === 200 + ); + } catch { + // 如果出错,返回false + return false; + } + }); + + // 等待所有检查完成,并获取结果 + const settled = await Promise.allSettled(checks); + // 检查是否所有操作都成功完成 + const allOk = settled.every(s => s.status === 'fulfilled' && s.value === true); + return allOk; + }; + + // 使用超时机制执行promiseFn,捕获超时或其他错误并返回false + return await withTimeout(promiseFn(), interruptCallTimeoutMs).catch(() => false); } function withTimeout(p, ms) { @@ -1222,7 +1369,7 @@ function startScheduleLoop() { lastInterruptTs = Date.now() localStorage.setItem('INT_LAST_TS', String(lastInterruptTs)) // 先按当前 index 跑一次,保持“即刻对齐” - runTask(schedulePlan[scheduleState.index].key) + runTask(schedulePlan[scheduleState.index].key, null, 1) if (scheduleTimer) clearInterval(scheduleTimer) @@ -1235,12 +1382,12 @@ function startScheduleLoop() { const now = Date.now() if (!lastInterruptTs) lastInterruptTs = now // 首次初始化 - const due = now - lastInterruptTs >= interruptEveryMin.value * 60_000 + const due = now - lastInterruptTs >= interruptEveryMin.value * 60_000 * 60_000 console.log( 'due=', due, 'elapsed=', now - lastInterruptTs, - 'threshold=', interruptEveryMin.value * 60_000 + 'threshold=', interruptEveryMin.value * 60_000 * 60_000 ) if (due) { interrupting = true @@ -1251,7 +1398,8 @@ function startScheduleLoop() { pauseSnapshot = { index: scheduleState.index, elapsedBeforePause } // 停掉当前片段 - await stopCurrentMode() + await stopAll() + // 执行中断任务(带重试) let ok = false @@ -1296,7 +1444,7 @@ function startScheduleLoop() { function forceActivate(key, runner) { // 跳过互斥逻辑,直接切换 - runType.value = key; + // runType.value = key; if (typeof runner === 'function') runner(); } @@ -1402,6 +1550,131 @@ function stopOne(deviceId) { }) } +//查看主播库主播信息 +async function getStoredHostList() { + const v = await anchorList() + return v ? v : [] +} + + +let isWifi = ref(false); +const checkVPN = async () => { + try { + // 设置超时 5 秒钟 + const timeout = new Promise((_, reject) => + setTimeout(() => reject(new Error('请求超时')), 10000) // 10秒超时 + ); + + // 使用 Promise.race 来进行超时控制 + const response = await Promise.race([ + fetch('https://www.google.com', { method: 'HEAD', mode: 'no-cors' }), + timeout + ]); + + // 判断 fetch 请求是否成功 + if (response && response.type === 'opaque') { + // ElMessage.success('VPN连接正常!'); + isWifi.value = false; + } else { + ElMessage.error('VPN连接失败,无法访问网络。'); + isWifi.value = true; + } + } catch (error) { + // 捕获超时错误或其他错误 + ElMessage.error('VPN连接失败,无法访问网络。'); + isWifi.value = true; + } +}; + +// 语言选项(也可以使用内置默认) +// const languages = [ +// { label: '简体中文 (zh-CN)', value: 'zh-CN' }, +// { label: 'English (en)', value: 'en' }, +// { label: '日本語 (ja)', value: 'ja' }, +// ] + +// 模拟翻译函数:你可以接入自己后端或第三方接口 sentences文本 targetLang语言 +async function doTranslate(sentences, targetLang) { + const str = arrayToString(sentences) + try { + const response = await customTranslation({ + "msg": str, + "language": targetLang + }) + console.log(response) + + + // 1️⃣ 去掉首尾的大括号 + const raw = response.replace(/^{|}$/g, '') + + // 2️⃣ 按换行符切割为数组 + const arr = raw.split('\n').map(s => s.trim()).filter(Boolean) + + console.log(arr) + // 简单演示逻辑(真实情况应调用 API) + return arr + } catch (error) { + console.error('Translation error:', error) + // 发生错误时返回空数组 + return [] + } +} + +// 接收“确定”事件返回结果 +function onConfirm({ type, strings, auto }) { + console.log('✅ 确认返回:', type, strings, auto) + showtransDlg.value = false + runType.value = 'follow' + setContentpriList(strings) + + if (isAlliance.value) { + followAndGreetUnion( + { + deviceList: deviceInformation.value.map(item => item.deviceId), + anchorList: hostList.map(item => ({ + anchorId: item.id, + country: item.country, + invitationType: item.invitationType, + state: stateByInvType(item.invitationType), + })), + prologueList: strings, + needReply: data.auto, + // needTranslate: data.needTranslate, + } + ).then((res) => { + ElMessage({ type: 'success', message: '任务开启成功' }); + + hostList = [] + }) + } else { + passAnchorData( + { + deviceList: deviceInformation.value.map(item => item.deviceId), + anchorList: hostList.map(item => ({ + anchorId: item.id, + country: item.country, + invitationType: item.invitationType, + state: stateByInvType(item.invitationType), + })), + prologueList: strings, //私信对象 + comment: comonList, //评论列表 + needReply: auto, //自动回复 + // needTranslate: data.needTranslate, + isComment: common.value //是否评论 + } + ).then((res) => { + ElMessage({ type: 'success', message: '任务开启成功' }); + console.log("启动成功") + hostList = [] + }) + + } +} + +function arrayToString(arr) { + // 过滤空项并用 \n 连接 + return arr.filter(Boolean).join(' \n') +}