export default defineContentScript({ matches: [ `*://${import.meta.env.WXT_PUBLIC_UCLASS_HOSTNAME}/*`, `*://${import.meta.env.WXT_PUBLIC_INTRANET_HOSTNAME}/*` ], world: 'MAIN', runAt: 'document_start', main() { const uclassHostname = import.meta.env.WXT_PUBLIC_UCLASS_HOSTNAME; const intranetHostname = import.meta.env.WXT_PUBLIC_INTRANET_HOSTNAME; const intranetPort = import.meta.env.WXT_PUBLIC_INTRANET_PORT; const isTargetPage = window.location.hostname === uclassHostname || (window.location.hostname === intranetHostname && window.location.port === intranetPort) if (!isTargetPage) { return } console.log('[XHR Interceptor] 脚本已通过 WXT 成功注入。') if (typeof XMLHttpRequest === 'undefined') { return } const original_open = XMLHttpRequest.prototype.open const original_send = XMLHttpRequest.prototype.send interface PatchedXHR extends XMLHttpRequest { _url?: string | URL } XMLHttpRequest.prototype.open = function ( this: PatchedXHR, method: string, url: string | URL, ...args: any[] ) { this._url = url return original_open.apply(this, arguments as any) } XMLHttpRequest.prototype.send = function (this: PatchedXHR, body) { this.addEventListener('load', () => { if ( this._url && typeof this._url === 'string' && this._url.includes('/lms/api//user/course/registList') ) { try { console.log('========== [WXT Main World] 检测到目标XHR请求 ==========') console.log('请求URL:', this._url) const responseData = JSON.parse(this.responseText) console.log('响应JSON数据:', responseData) // 检查响应数据是否是我们期望的格式 if (responseData && responseData.page && Array.isArray(responseData.page.content)) { // 为课程对象定义一个更详细的类型 type Course = { name: string status: string score: number | string hour: number | string cumulativeStudyCount: number studyCount: number [key: string]: any } const courses: Course[] = responseData.page.content const cleanupAndApplyStyles = () => { document.querySelectorAll('.chrome-ext-course-prefix').forEach((el) => el.remove()) document.querySelectorAll('[data-course-processed="true"]').forEach((el) => { const htmlEl = el as HTMLElement const title = htmlEl.querySelector('.title, .course-name, h3, h4') // 尝试一些常见的标题选择器 if (title) title.style.color = '' htmlEl.removeAttribute('data-course-processed') }) courses.forEach((course) => { if (course && typeof course.name === 'string') { const courseName = course.name.trim() if (courseName === '') return try { const xpath = `//*[normalize-space(text())="${courseName}"]` const titleElements: HTMLElement[] = [] const result = document.evaluate( xpath, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null, ) let node: Node | null while ((node = result.iterateNext())) { if (node instanceof HTMLElement) { titleElements.push(node) } } titleElements.forEach((titleElement) => { const parent = titleElement.parentElement if (!parent) return parent.dataset.courseProcessed = 'true' const score = course.score ?? 'N/A' const hour = course.hour ?? 'N/A' const cumulativeStudyCount = course.cumulativeStudyCount ?? 'N/A' const studyCount = course.studyCount ?? 'N/A' const isVideo = cumulativeStudyCount / studyCount > 5 ? '大概率是文档' : '大概率是视频' const prefixSpan = document.createElement('span') prefixSpan.className = 'chrome-ext-course-prefix' prefixSpan.textContent = `(${score}分-${hour}学时)-${isVideo}` parent.insertBefore(prefixSpan, titleElement) titleElement.style.color = course.status !== 'FINISH' ? 'red' : '' }) } catch (e) { console.error(`为课程 "${courseName}" 应用样式时出错:`, e) } } }) console.log('样式和前缀已更新。') } setTimeout(cleanupAndApplyStyles, 1000) } else { console.log('[WXT Main World] 响应数据中未找到有效的课程列表 (response.page.content)') } console.log('======================================================') } catch (e) { console.error('[WXT Main World] 解析XHR响应失败:', e, '原始响应:', this.responseText) } } }) return original_send.apply(this, arguments as any) } }, })