import type { Accessor } from "solid-js"; import http, { createHttpClient, type HttpClient } from "~/service/http"; import type { CourseType } from "~/types/Course"; import type { userInfoType } from "~/types/Userinfo"; export type CourseKind = "run" | "finish" | "sign"; export type RecordType = "" | "/work" | "/exam" | "/discuss"; export type StudyStatus = 1 | 2 | 3; type ApiResponse = { code: number; message: string; data: T; }; export type LoginReq = { username: string; password: string; token: string; status: CourseKind; host: string; }; export type LoginData = { courses?: CourseType[]; session_id: string; user: userInfoType; }; export type LoginRes = ApiResponse; export type RecordItem = { id: string; name: string; type: string | null; chapterId: string; courseId: string; videoFile: string; videoDuration: string; votingPath: string | null; tabVideo: string; tabFile: string; tabVote: string; tabWork: string; tabExam: string; sort: string; videoMode: string; localFile: string; schoolId: string; lock: string; unlockTime: string; bid: string; duration: string; progress: string; state: string; viewCount: string; finalTime: string; error: number; errorMessage: string; beginTime: string; viewedDuration: string; url: string; }; export type PageInfo = { keyName: string; page: number; pageCount: number; recordsCount: number; onlyCount: number; pageSize: number; }; export type RecordData = { list: RecordItem[]; page_info: PageInfo; }; export type RecordRes = ApiResponse; export type StudyData = { state: number; studyId: number; status: boolean; msg: string; }; export type StudyRes = ApiResponse; export type LogoutRes = { code: number; message: string; }; export type HostItem = { host: string; name: string; }; export type HostRes = ApiResponse<{ list: HostItem[]; }>; export type VersionData = { BuildAt: string; GitAuthor: string; GitCommit: string; GitEmail: string; Version: string; }; export type VersionRes = ApiResponse; export type RecordReq = { course_id: string; page: number; record_type?: RecordType; }; export type CourseReq = { status: CourseKind; }; export type CourseData = { courses: CourseType[]; }; export type CourseRes = ApiResponse; export type StudyReq = { node_id: string; study_id: string; study_time: string; status: StudyStatus; }; export type StudyRunnerItem = { nodeId: string; name: string; currentTime: number; totalTime: number; progress: string; completed: boolean; }; export type StudyRunnerPayload = { accountId: string; courseId: number; intervalSeconds: number; items: StudyRunnerItem[]; isRunningStudy: Accessor; setIsRunningStudy: () => void; client: WkClient; onLog?: (message: string, accoundID: string) => void; }; export type WkClient = { courseApi: (payload: CourseReq) => Promise; recordApi: (payload: RecordReq) => Promise; studyApi: (payload: StudyReq) => Promise; logoutApi: () => Promise; }; export const loginApi = async (payload: LoginReq) => { const res = await http.post("/api/login", payload); return res; }; const createWkClientFromHttp = (client: HttpClient): WkClient => ({ courseApi(payload) { return client.post("/api/v2/course", payload); }, recordApi(payload) { return client.post("/api/v2/record", payload); }, studyApi(payload) { return client.post("/api/v2/study", payload); }, logoutApi() { return client.post("/api/v2/logout"); }, }); export const createWkClient = ( resolveSessionId: () => string | undefined, ): WkClient => { return createWkClientFromHttp(createHttpClient(resolveSessionId)); }; const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); export const runStudyQueue = async (_payload: StudyRunnerPayload) => { const stopFlag = _payload.isRunningStudy; const stepSeconds = Math.max(1, Math.floor(_payload.intervalSeconds || 5)); const sleepMs = stepSeconds * 1000; const maxRetryCount = 3; const maxStudySubmitRetry = 3; const studySubmitRetrySleepMs = 5000; for (const item of _payload.items) { const learnedTime = Math.max(0, Math.floor(item.currentTime || 0)); const rawTotalTime = Math.max(0, Math.floor(item.totalTime || 0)); const totalTime = Math.max(0, rawTotalTime - learnedTime); let currentTime = 0; let count = 0; let study_id = 0; let retryCount = 0; if (totalTime <= 0) { _payload.onLog?.(`跳过已完成节点: ${item.name}`, _payload.accountId); continue; } while (currentTime <= totalTime) { if (!stopFlag()) { _payload.onLog?.("⛔ 已手动停止", _payload.accountId); return; } const message = `[${item.name}]: ${currentTime}/${totalTime}`; _payload.onLog?.(message, _payload.accountId); try { const resp = await _payload.client.studyApi({ node_id: item.nodeId, study_id: String(study_id), study_time: String(currentTime), status: count === 0 ? 1 : currentTime >= totalTime ? 3 : 2, }); const submitMessage = String(resp.data.msg ?? ""); const isSubmitFailed = submitMessage.includes("提交学时失败") || resp.data.status === false; if (isSubmitFailed) { let submitRetry = 0; let fixedResp = resp; while (submitRetry < maxStudySubmitRetry) { submitRetry += 1; _payload.onLog?.( `⚠️ 提交学时失败,${submitRetry}/${maxStudySubmitRetry} 次重试后再提交: ${item.name}`, _payload.accountId, ); await sleep(studySubmitRetrySleepMs); if (!stopFlag()) { _payload.onLog?.("⛔ 已手动停止", _payload.accountId); return; } fixedResp = await _payload.client.studyApi({ node_id: item.nodeId, study_id: String(study_id), study_time: String(currentTime), status: count === 0 ? 1 : currentTime >= totalTime ? 3 : 2, }); const nextMsg = String(fixedResp.data.msg ?? ""); const nextFailed = nextMsg.includes("提交学时失败") || fixedResp.data.status === false; if (!nextFailed) { break; } } const finalMsg = String(fixedResp.data.msg ?? ""); const stillFailed = finalMsg.includes("提交学时失败") || fixedResp.data.status === false; if (stillFailed) { _payload.onLog?.( `⛔ 提交学时连续重试 ${maxStudySubmitRetry} 次仍失败,停止刷课: ${item.name}`, _payload.accountId, ); _payload.setIsRunningStudy(); return; } study_id = fixedResp.data.studyId; retryCount = 0; if (currentTime === totalTime) { break; } currentTime = Math.min(currentTime + stepSeconds, totalTime); count++; await sleep(sleepMs); continue; } if (resp.data.state != 0) { _payload.onLog?.(`⛔ 自动停止: ${resp.data.msg}`, _payload.accountId); _payload.setIsRunningStudy(); return; } study_id = resp.data.studyId; retryCount = 0; if (currentTime === totalTime) { break; } currentTime = Math.min(currentTime + stepSeconds, totalTime); count++; } catch (error) { retryCount += 1; const errorMessage = `请求失败: ${error instanceof Error ? error.message : String(error)}`; _payload.onLog?.(errorMessage, _payload.accountId); if (retryCount >= maxRetryCount) { _payload.onLog?.( `⚠️ 连续失败 ${maxRetryCount} 次,跳过节点: ${item.name}`, _payload.accountId, ); break; } } await sleep(sleepMs); } } }; export const hostApi = async () => { return await http.get("/api/v1/host"); }; export const versionApi = async () => { return await http.get("/api/version"); };