Initial Commit
This commit is contained in:
commit
2d575e2e84
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Project Configs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Node Modules
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Cache
|
5
.prettierrc.json
Normal file
5
.prettierrc.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 4
|
||||||
|
}
|
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "buct-cource-bridge",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "Bluemangoo",
|
||||||
|
"private": true,
|
||||||
|
"description": "",
|
||||||
|
"scripts": {
|
||||||
|
"start": "ts-node src/main.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.15.14",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vclight/router": "^1.5.1",
|
||||||
|
"vclight": "^3.3.2"
|
||||||
|
}
|
||||||
|
}
|
240
pnpm-lock.yaml
generated
Normal file
240
pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
'@vclight/router':
|
||||||
|
specifier: ^1.5.1
|
||||||
|
version: 1.5.1
|
||||||
|
vclight:
|
||||||
|
specifier: ^3.3.2
|
||||||
|
version: 3.3.2
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.15.14
|
||||||
|
version: 22.15.14
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.5.3
|
||||||
|
version: 3.5.3
|
||||||
|
ts-node:
|
||||||
|
specifier: ^10.9.2
|
||||||
|
version: 10.9.2(@types/node@22.15.14)(typescript@5.8.3)
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.8.3
|
||||||
|
version: 5.8.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
|
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@jridgewell/resolve-uri@3.1.2':
|
||||||
|
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.0':
|
||||||
|
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||||
|
|
||||||
|
'@jridgewell/trace-mapping@0.3.9':
|
||||||
|
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||||
|
|
||||||
|
'@tsconfig/node10@1.0.11':
|
||||||
|
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
|
||||||
|
|
||||||
|
'@tsconfig/node12@1.0.11':
|
||||||
|
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
|
||||||
|
|
||||||
|
'@tsconfig/node14@1.0.3':
|
||||||
|
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
|
||||||
|
|
||||||
|
'@tsconfig/node16@1.0.4':
|
||||||
|
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||||
|
|
||||||
|
'@types/node@22.15.14':
|
||||||
|
resolution: {integrity: sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==}
|
||||||
|
|
||||||
|
'@vclight/router@1.5.1':
|
||||||
|
resolution: {integrity: sha512-oLojdFEagVZtjomTLdTqg+MSV72YQAzoBxNsWr/fCCyPHxvZe2G5/lqc2KdDqIBpwCGkeMZP5n2AK03BnD51eg==}
|
||||||
|
|
||||||
|
acorn-walk@8.3.4:
|
||||||
|
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
acorn@8.14.1:
|
||||||
|
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
arg@4.1.3:
|
||||||
|
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||||
|
|
||||||
|
content-type@1.0.5:
|
||||||
|
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
cookie@0.7.0:
|
||||||
|
resolution: {integrity: sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
create-require@1.1.1:
|
||||||
|
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||||
|
|
||||||
|
diff@4.0.2:
|
||||||
|
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||||
|
engines: {node: '>=0.3.1'}
|
||||||
|
|
||||||
|
end-of-stream@1.4.4:
|
||||||
|
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||||
|
|
||||||
|
etag@1.8.1:
|
||||||
|
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
make-error@1.3.6:
|
||||||
|
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
|
prettier@3.5.3:
|
||||||
|
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
ts-node@10.9.2:
|
||||||
|
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@swc/core': '>=1.2.50'
|
||||||
|
'@swc/wasm': '>=1.2.50'
|
||||||
|
'@types/node': '*'
|
||||||
|
typescript: '>=2.7'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@swc/core':
|
||||||
|
optional: true
|
||||||
|
'@swc/wasm':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
typescript@5.8.3:
|
||||||
|
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.21.0:
|
||||||
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
v8-compile-cache-lib@3.0.1:
|
||||||
|
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||||
|
|
||||||
|
vclight@3.3.2:
|
||||||
|
resolution: {integrity: sha512-ZQ5pWzTo9iRblGL8WikUPT4O7MKl0hSSj/Tu/NMmFv1XCF04lfydXG0yDOoTcvLI5JvAawpABKH/anmhmTcjrw==}
|
||||||
|
|
||||||
|
wrappy@1.0.2:
|
||||||
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
|
yn@3.1.1:
|
||||||
|
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/trace-mapping': 0.3.9
|
||||||
|
|
||||||
|
'@jridgewell/resolve-uri@3.1.2': {}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||||
|
|
||||||
|
'@jridgewell/trace-mapping@0.3.9':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
'@tsconfig/node10@1.0.11': {}
|
||||||
|
|
||||||
|
'@tsconfig/node12@1.0.11': {}
|
||||||
|
|
||||||
|
'@tsconfig/node14@1.0.3': {}
|
||||||
|
|
||||||
|
'@tsconfig/node16@1.0.4': {}
|
||||||
|
|
||||||
|
'@types/node@22.15.14':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
'@vclight/router@1.5.1':
|
||||||
|
dependencies:
|
||||||
|
cookie: 0.7.0
|
||||||
|
vclight: 3.3.2
|
||||||
|
|
||||||
|
acorn-walk@8.3.4:
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.14.1
|
||||||
|
|
||||||
|
acorn@8.14.1: {}
|
||||||
|
|
||||||
|
arg@4.1.3: {}
|
||||||
|
|
||||||
|
content-type@1.0.5: {}
|
||||||
|
|
||||||
|
cookie@0.7.0: {}
|
||||||
|
|
||||||
|
create-require@1.1.1: {}
|
||||||
|
|
||||||
|
diff@4.0.2: {}
|
||||||
|
|
||||||
|
end-of-stream@1.4.4:
|
||||||
|
dependencies:
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
|
etag@1.8.1: {}
|
||||||
|
|
||||||
|
make-error@1.3.6: {}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
wrappy: 1.0.2
|
||||||
|
|
||||||
|
prettier@3.5.3: {}
|
||||||
|
|
||||||
|
ts-node@10.9.2(@types/node@22.15.14)(typescript@5.8.3):
|
||||||
|
dependencies:
|
||||||
|
'@cspotcode/source-map-support': 0.8.1
|
||||||
|
'@tsconfig/node10': 1.0.11
|
||||||
|
'@tsconfig/node12': 1.0.11
|
||||||
|
'@tsconfig/node14': 1.0.3
|
||||||
|
'@tsconfig/node16': 1.0.4
|
||||||
|
'@types/node': 22.15.14
|
||||||
|
acorn: 8.14.1
|
||||||
|
acorn-walk: 8.3.4
|
||||||
|
arg: 4.1.3
|
||||||
|
create-require: 1.1.1
|
||||||
|
diff: 4.0.2
|
||||||
|
make-error: 1.3.6
|
||||||
|
typescript: 5.8.3
|
||||||
|
v8-compile-cache-lib: 3.0.1
|
||||||
|
yn: 3.1.1
|
||||||
|
|
||||||
|
typescript@5.8.3: {}
|
||||||
|
|
||||||
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
|
v8-compile-cache-lib@3.0.1: {}
|
||||||
|
|
||||||
|
vclight@3.3.2:
|
||||||
|
dependencies:
|
||||||
|
content-type: 1.0.5
|
||||||
|
cookie: 0.7.0
|
||||||
|
end-of-stream: 1.4.4
|
||||||
|
etag: 1.8.1
|
||||||
|
|
||||||
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
|
yn@3.1.1: {}
|
146
script/buct-cource-copy.js
Normal file
146
script/buct-cource-copy.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name BUCT cource helper
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 2025-03-12
|
||||||
|
// @description 北化在线助手
|
||||||
|
// @author Bluemangoo
|
||||||
|
// @match https://course.buct.edu.cn/meol/jpk/course/layout/newpage/index.jsp?*
|
||||||
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=buct.edu.cn
|
||||||
|
// @grant GM_registerMenuCommand
|
||||||
|
// @grant GM_unregisterMenuCommand
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const HOST = "https://local.bluemangoo.net:3443";
|
||||||
|
|
||||||
|
let question = {
|
||||||
|
id: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
async function poll() {
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await fetch(`${HOST}/polling?env=course`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
return poll();
|
||||||
|
}
|
||||||
|
poll();
|
||||||
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status !== "nothing") {
|
||||||
|
const { questionID, answer } = data.data;
|
||||||
|
console.log("回答", questionID, "\n", answer);
|
||||||
|
answerQuestion(questionID, answer);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAnswerElement() {
|
||||||
|
const doc = document
|
||||||
|
.getElementById("mainFrame")
|
||||||
|
.contentDocument.getElementById("questionshow").contentDocument;
|
||||||
|
return doc.getElementsByClassName("extable")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContent() {
|
||||||
|
const doc = document
|
||||||
|
.getElementById("mainFrame")
|
||||||
|
.contentDocument.getElementById("questionshow").contentDocument;
|
||||||
|
const question = doc
|
||||||
|
.getElementsByTagName("iframe")[0]
|
||||||
|
.contentDocument.getElementById("body").innerText;
|
||||||
|
const answer = doc.getElementsByClassName("extable")[0].innerText.replaceAll("\t", "- ");
|
||||||
|
return question + answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard() {
|
||||||
|
navigator.clipboard.writeText(getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
function answerQuestion(id, answer) {
|
||||||
|
if (id !== question.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const answers = answer.split("\n");
|
||||||
|
const children = getAnswerElement().children[0].children;
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.classList[0] !== "optionContent") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (answers.includes(child.innerText.replaceAll("\t", ""))) {
|
||||||
|
child.children[0].children[0].click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ask() {
|
||||||
|
const q = getContent();
|
||||||
|
const req = await fetch(`${HOST}/question`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
question: q
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await req.json();
|
||||||
|
if (data.data.questionId) {
|
||||||
|
question.id = data.data.questionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const flag = {
|
||||||
|
copyButton: false,
|
||||||
|
aiButton: false
|
||||||
|
};
|
||||||
|
|
||||||
|
let aiButton;
|
||||||
|
|
||||||
|
function createButton(addAiButton = false) {
|
||||||
|
const base = document.getElementById("mainFrame");
|
||||||
|
const win = base.contentWindow;
|
||||||
|
const doc = base.contentDocument;
|
||||||
|
win.getContent = getContent;
|
||||||
|
win.getAnswerElement = getAnswerElement;
|
||||||
|
win.answerQuestion = answerQuestion;
|
||||||
|
win.copyToClipboard = copyToClipboard;
|
||||||
|
if (!flag.copyButton) {
|
||||||
|
const button = doc.createElement("input");
|
||||||
|
button.value = "复制";
|
||||||
|
button.style = "margin-left: 10px;";
|
||||||
|
button.type = "button";
|
||||||
|
button.onclick = copyToClipboard;
|
||||||
|
doc.getElementsByClassName("navigation")[0].childNodes[1].appendChild(button);
|
||||||
|
flag.copyButton = true;
|
||||||
|
}
|
||||||
|
if (addAiButton && !flag.aiButton) {
|
||||||
|
const button = doc.createElement("input");
|
||||||
|
aiButton = button;
|
||||||
|
button.value = "?";
|
||||||
|
button.style = "margin-left: 10px;";
|
||||||
|
button.type = "button";
|
||||||
|
button.onclick = ask;
|
||||||
|
doc.getElementsByClassName("navigation")[0].childNodes[1].appendChild(button);
|
||||||
|
flag.aiButton = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAiBridge() {
|
||||||
|
createButton(true);
|
||||||
|
poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
GM_registerMenuCommand("添加复制按钮", createButton);
|
||||||
|
GM_registerMenuCommand("初始化ai桥", initAiBridge);
|
||||||
|
})();
|
155
script/ds-listener.js
Normal file
155
script/ds-listener.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name BUCT cource deepseek listener
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 2025-05-07
|
||||||
|
// @description try to take over the world!
|
||||||
|
// @author You
|
||||||
|
// @match https://chat.deepseek.com/*
|
||||||
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=deepseek.com
|
||||||
|
// @grant GM_registerMenuCommand
|
||||||
|
// @grant GM_unregisterMenuCommand
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
const CLASS_MAP = {
|
||||||
|
input: "_27c9245",
|
||||||
|
send: "_7436101",
|
||||||
|
chat: "dad65929",
|
||||||
|
latest: "d7dc56a8",
|
||||||
|
done: "_43c05b5"
|
||||||
|
};
|
||||||
|
|
||||||
|
const HOST = "https://local.bluemangoo.net:3443";
|
||||||
|
|
||||||
|
const question = {
|
||||||
|
question: "",
|
||||||
|
questionID: "",
|
||||||
|
answered: true
|
||||||
|
};
|
||||||
|
|
||||||
|
async function poll() {
|
||||||
|
// query latest question
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await fetch(`${HOST}/polling?env=deepseek`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
return poll();
|
||||||
|
}
|
||||||
|
poll();
|
||||||
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status !== "nothing") {
|
||||||
|
if (question.answered) {
|
||||||
|
question.answered = false;
|
||||||
|
question.question = data.data.question;
|
||||||
|
question.questionID = data.data.questionID;
|
||||||
|
console.log("获取到新问题:", question.question);
|
||||||
|
ask(question.question);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function answer(questionID, answer) {
|
||||||
|
console.log("回答问题:", questionID, answer);
|
||||||
|
question.answered = true;
|
||||||
|
await fetch(`${HOST}/answer`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
questionID,
|
||||||
|
answer
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNativeValue(element, value) {
|
||||||
|
const valueSetter = Object.getOwnPropertyDescriptor(element, "value").set;
|
||||||
|
const prototype = Object.getPrototypeOf(element);
|
||||||
|
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, "value").set;
|
||||||
|
|
||||||
|
if (valueSetter && valueSetter !== prototypeValueSetter) {
|
||||||
|
prototypeValueSetter.call(element, value);
|
||||||
|
} else {
|
||||||
|
valueSetter.call(element, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ask(question) {
|
||||||
|
const input = document.getElementsByClassName(CLASS_MAP.input)[0];
|
||||||
|
setNativeValue(input, question);
|
||||||
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
document.getElementsByClassName(CLASS_MAP.send)[0].click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const chat = document.getElementsByClassName(CLASS_MAP.chat)[0];
|
||||||
|
|
||||||
|
// 创建一个 MutationObserver 来监听子元素变化
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
// 检查新增的节点
|
||||||
|
mutation.addedNodes.forEach((node) => {
|
||||||
|
// 确保是元素节点
|
||||||
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
const element = node;
|
||||||
|
// 检查新增的元素是否同时包含 "_4f9bf79" 和 "d7dc56a8" 类
|
||||||
|
if (
|
||||||
|
element.classList.contains("_4f9bf79") &&
|
||||||
|
element.classList.contains("d7dc56a8")
|
||||||
|
) {
|
||||||
|
console.log("目标元素已添加:", element);
|
||||||
|
|
||||||
|
// 对这个元素添加属性变化的监听器
|
||||||
|
const targetObserver = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
// 检查是否添加了 "_43c05b5" 类
|
||||||
|
if (
|
||||||
|
mutation.type === "attributes" &&
|
||||||
|
mutation.attributeName === "class" &&
|
||||||
|
element.classList.contains("_43c05b5")
|
||||||
|
) {
|
||||||
|
const ans = element.innerText.toString();
|
||||||
|
console.log("内容:", ans);
|
||||||
|
// 可以在这里执行其他操作
|
||||||
|
targetObserver.disconnect();
|
||||||
|
answer(question.questionID, ans);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开始观察目标元素的属性变化
|
||||||
|
targetObserver.observe(element, {
|
||||||
|
attributes: true, // 监听属性变化
|
||||||
|
attributeFilter: ["class"] // 只监听 class 变化
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开始观察父元素的子元素变化
|
||||||
|
observer.observe(chat, {
|
||||||
|
childList: true, // 监听子元素变化
|
||||||
|
subtree: true // 监听所有后代元素
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("正在监听父元素:", chat);
|
||||||
|
|
||||||
|
poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
GM_registerMenuCommand("初始化作业桥", init);
|
||||||
|
})();
|
308
script/tempermonkey.d.ts
vendored
Normal file
308
script/tempermonkey.d.ts
vendored
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
/**
|
||||||
|
unsafeWindow 对象提供对页面 javascript 函数和变量的完全访问。
|
||||||
|
*/
|
||||||
|
declare var unsafeWindow: Window;
|
||||||
|
|
||||||
|
/**
|
||||||
|
获取有关脚本和 TM 的一些信息。
|
||||||
|
*/
|
||||||
|
declare var GM_info: {
|
||||||
|
version: string;
|
||||||
|
scriptWillUpdate: boolean;
|
||||||
|
scriptHandler: "Tampermonkey";
|
||||||
|
scriptUpdateURL?: string;
|
||||||
|
scriptSource: string;
|
||||||
|
scriptMetaStr?: string;
|
||||||
|
isIncognito: boolean;
|
||||||
|
downloadMode: "native" | "disabled" | "browser";
|
||||||
|
script: {
|
||||||
|
author?: string;
|
||||||
|
description?: string;
|
||||||
|
excludes: string[];
|
||||||
|
homepage?: string;
|
||||||
|
icon?: string;
|
||||||
|
icon64?: string;
|
||||||
|
includes?: string[];
|
||||||
|
lastModified: number;
|
||||||
|
matches: string[];
|
||||||
|
name: string;
|
||||||
|
namespace?: string;
|
||||||
|
position: number;
|
||||||
|
"run-at": string;
|
||||||
|
resources: string[];
|
||||||
|
unwrap: boolean;
|
||||||
|
version: string;
|
||||||
|
options: {
|
||||||
|
awareOfChrome: boolean;
|
||||||
|
run_at: string;
|
||||||
|
noframes?: boolean;
|
||||||
|
compat_arrayLeft: boolean;
|
||||||
|
compat_foreach: boolean;
|
||||||
|
compat_forvarin: boolean;
|
||||||
|
compat_metadata: boolean;
|
||||||
|
compat_uW_gmonkey: boolean;
|
||||||
|
override: {
|
||||||
|
orig_excludes: string[];
|
||||||
|
orig_includes: string[];
|
||||||
|
use_includes: string[];
|
||||||
|
use_excludes: string[];
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
将给定的样式添加到文档并返回注入的样式元素。
|
||||||
|
*/
|
||||||
|
declare function GM_addStyle(css: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
创建一个由“tag_name”指定的 HTML 元素并应用所有给定的“属性”并返回注入的 HTML 元素。
|
||||||
|
*/
|
||||||
|
declare function GM_addElement(tag_name: string, attributes: object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
创建一个由“tag_name”指定的 HTML 元素并应用所有给定的“属性”并返回注入的 HTML 元素。如果给出了“parent_node”,则将其附加到它或以其他方式附加到文档头或体。
|
||||||
|
*/
|
||||||
|
declare function GM_addElement(parent_node: HTMLElement, tag_name: string, attributes: object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
从存储中删除“名称”。
|
||||||
|
*/
|
||||||
|
declare function GM_deleteValue(name: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
列出存储的所有名称。
|
||||||
|
*/
|
||||||
|
declare function GM_listValues(): string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
将更改侦听器添加到存储并返回侦听器 ID。
|
||||||
|
'name' 是观察变量的名称。
|
||||||
|
回调函数的“remote”参数显示该值是从另一个选项卡的实例 (true) 还是在此脚本实例 (false) 中修改的。
|
||||||
|
因此,不同浏览器选项卡的脚本可以使用此功能相互通信。
|
||||||
|
*/
|
||||||
|
declare function GM_addValueChangeListener(
|
||||||
|
name: string,
|
||||||
|
listener: GM_Types.ValueChangeListener
|
||||||
|
): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
按 ID 删除更改侦听器。
|
||||||
|
*/
|
||||||
|
declare function GM_removeValueChangeListener(listenerId: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
设置本地存储'name'的值。
|
||||||
|
*/
|
||||||
|
declare function GM_setValue(name: string, value: any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
从存储中获取 'name' 的值。
|
||||||
|
*/
|
||||||
|
declare function GM_getValue(name: string, defaultValue?: any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
将消息打印到控制台。
|
||||||
|
*/
|
||||||
|
declare function GM_log(message: string): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
获取脚本头中预定义的@resource 标记的内容。
|
||||||
|
*/
|
||||||
|
declare function GM_getResourceText(name: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
获取脚本标头处预定义 @resource 标记的 base64 编码 URI。
|
||||||
|
*/
|
||||||
|
declare function GM_getResourceURL(name: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
在运行此脚本的页面的 Tampermonkey 菜单中注册要显示的菜单,并返回菜单命令 ID。
|
||||||
|
*/
|
||||||
|
declare function GM_registerMenuCommand(
|
||||||
|
name: string,
|
||||||
|
listener: Function,
|
||||||
|
accessKey?: string
|
||||||
|
): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
使用给定的菜单命令 ID 取消注册先前由 GM_registerMenuCommand 注册的菜单命令。
|
||||||
|
*/
|
||||||
|
declare function GM_unregisterMenuCommand(id: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
使用此 url 打开一个新选项卡。
|
||||||
|
*/
|
||||||
|
declare function GM_openInTab(url: string, options: GM_Types.OpenTabOptions): void;
|
||||||
|
/**
|
||||||
|
使用此 url 打开一个新选项卡。
|
||||||
|
*/
|
||||||
|
declare function GM_openInTab(url: string, loadInBackground: boolean): void;
|
||||||
|
/**
|
||||||
|
使用此 url 打开一个新选项卡。
|
||||||
|
*/
|
||||||
|
declare function GM_openInTab(url: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
创建一个 xmlHttpRequest。
|
||||||
|
*/
|
||||||
|
declare function GM_xmlhttpRequest<CONTEXT_TYPE>(
|
||||||
|
details: GM_Types.XHRDetails<CONTEXT_TYPE>
|
||||||
|
): GM_Types.AbortHandle<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
将给定的 URL 下载到本地磁盘。
|
||||||
|
*/
|
||||||
|
declare function GM_download(details: GM_Types.DownloadDetails): GM_Types.AbortHandle<boolean>;
|
||||||
|
/**
|
||||||
|
将给定的 URL 下载到本地磁盘。
|
||||||
|
*/
|
||||||
|
declare function GM_download(url: string, filename: string): GM_Types.AbortHandle<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
只要此选项卡处于打开状态,就获取一个持久对象。
|
||||||
|
*/
|
||||||
|
declare function GM_getTab(callback: (obj: object) => any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
保存选项卡对象以在页面卸载后重新打开它。
|
||||||
|
*/
|
||||||
|
declare function GM_saveTab(obj: object): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
获取所有选项卡对象作为散列以与其他脚本实例通信。
|
||||||
|
*/
|
||||||
|
declare function GM_getTabs(callback: (objs: { [key: number]: object }) => any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
显示 HTML5 桌面通知和/或突出显示当前选项卡。
|
||||||
|
*/
|
||||||
|
declare function GM_notification(details: GM_Types.NotificationDetails, ondone: Function): void;
|
||||||
|
/**
|
||||||
|
显示 HTML5 桌面通知和/或突出显示当前选项卡。
|
||||||
|
*/
|
||||||
|
declare function GM_notification(
|
||||||
|
text: string,
|
||||||
|
title: string,
|
||||||
|
image: string,
|
||||||
|
onclick: Function
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
将数据复制到剪贴板。参数 'info' 可以是像“{ type: 'text', mimetype: 'text/plain'}”这样的对象,或者只是一个表示类型的字符串(“text”或“html”)。
|
||||||
|
*/
|
||||||
|
declare function GM_setClipboard(
|
||||||
|
data: string,
|
||||||
|
info?: string | { type?: string; mimetype?: string }
|
||||||
|
): void;
|
||||||
|
|
||||||
|
declare namespace GM_Types {
|
||||||
|
type ValueChangeListener = (name: string, oldValue: any, newValue: any, remote: boolean) => any;
|
||||||
|
|
||||||
|
interface OpenTabOptions {
|
||||||
|
active?: boolean;
|
||||||
|
insert?: boolean;
|
||||||
|
setParent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface XHRResponse<CONTEXT_TYPE> extends Function {
|
||||||
|
DONE: 4;
|
||||||
|
HEADERS_RECEIVED: 2;
|
||||||
|
LOADING: 3;
|
||||||
|
OPENED: 1;
|
||||||
|
UNSENT: 0;
|
||||||
|
|
||||||
|
context: CONTEXT_TYPE;
|
||||||
|
finalUrl: string;
|
||||||
|
readyState: 0 | 1 | 2 | 3 | 4;
|
||||||
|
responseHeaders: string;
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
response: string | null;
|
||||||
|
responseText: string;
|
||||||
|
responseXML: Document | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface XHRProgress<CONTEXT_TYPE> extends XHRResponse<CONTEXT_TYPE> {
|
||||||
|
done: number;
|
||||||
|
lengthComputable: boolean;
|
||||||
|
loaded: number;
|
||||||
|
position: number;
|
||||||
|
total: number;
|
||||||
|
totalSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Listener<OBJ> = (this: OBJ, event: OBJ) => any;
|
||||||
|
|
||||||
|
interface XHRDetails<CONTEXT_TYPE> {
|
||||||
|
method?: "GET" | "HEAD" | "POST";
|
||||||
|
url?: string;
|
||||||
|
headers?: { readonly [key: string]: string };
|
||||||
|
data?: string;
|
||||||
|
binary?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
context?: CONTEXT_TYPE;
|
||||||
|
responseType?: "arraybuffer" | "blob" | "json";
|
||||||
|
overrideMimeType?: string;
|
||||||
|
anonymous?: boolean;
|
||||||
|
fetch?: boolean;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
|
||||||
|
onload?: Listener<XHRResponse<CONTEXT_TYPE>>;
|
||||||
|
onloadstart?: Listener<XHRResponse<CONTEXT_TYPE>>;
|
||||||
|
onprogress?: Listener<XHRProgress<CONTEXT_TYPE>>;
|
||||||
|
onreadystatechange?: Listener<XHRResponse<CONTEXT_TYPE>>;
|
||||||
|
ontimeout?: Listener<Function>;
|
||||||
|
onabort?: Function;
|
||||||
|
onerror?: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AbortHandle<RETURN_TYPE> {
|
||||||
|
abort(): RETURN_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadError {
|
||||||
|
error:
|
||||||
|
| "not_enabled"
|
||||||
|
| "not_whitelisted"
|
||||||
|
| "not_permitted"
|
||||||
|
| "not_supported"
|
||||||
|
| "not_succeeded";
|
||||||
|
details?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadDetails {
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
headers?: { readonly [key: string]: string };
|
||||||
|
saveAs?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
onerror?: Listener<DownloadError>;
|
||||||
|
ontimeout?: Listener<object>;
|
||||||
|
onload?: Listener<object>;
|
||||||
|
onprogress?: Listener<XHRProgress<void>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotificationThis extends NotificationDetails {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationOnClick = (this: NotificationThis) => any;
|
||||||
|
type NotificationOnDone = (this: NotificationThis, clicked: boolean) => any;
|
||||||
|
|
||||||
|
interface NotificationDetails {
|
||||||
|
text?: string;
|
||||||
|
title?: string;
|
||||||
|
image?: string;
|
||||||
|
highlight?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
onclick?: NotificationOnClick;
|
||||||
|
ondone?: NotificationOnDone;
|
||||||
|
}
|
||||||
|
}
|
14
src/app/app.ts
Normal file
14
src/app/app.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import VCLight, { VCLightApp, VCLightRequest, VCLightResponse } from "vclight";
|
||||||
|
import router from "./router";
|
||||||
|
import "./initRouter";
|
||||||
|
|
||||||
|
const app = new VCLight();
|
||||||
|
app.use(router);
|
||||||
|
app.use({
|
||||||
|
async post(request: VCLightRequest, response: VCLightResponse, app: VCLightApp) {},
|
||||||
|
async process(request: VCLightRequest, response: VCLightResponse, app: VCLightApp) {
|
||||||
|
response.headers["access-control-allow-origin"] = "*";
|
||||||
|
response.headers["access-control-allow-headers"] = "*";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export default app;
|
45
src/app/data/polls.ts
Normal file
45
src/app/data/polls.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import Poll from "../../utils/poll";
|
||||||
|
import CONST from "../../const";
|
||||||
|
|
||||||
|
type Polls<T> = { value: Poll<T>[]; cache: T };
|
||||||
|
|
||||||
|
export const coursePolls: Polls<CoursePollData> = {
|
||||||
|
value: [],
|
||||||
|
cache: {
|
||||||
|
questionID: "",
|
||||||
|
answer: "",
|
||||||
|
flag: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const deepSeekPolls: Polls<DeepSeekPollData> = {
|
||||||
|
value: [],
|
||||||
|
cache: {
|
||||||
|
questionID: "",
|
||||||
|
question: "",
|
||||||
|
flag: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface CoursePollData {
|
||||||
|
questionID: string;
|
||||||
|
answer: string;
|
||||||
|
flag: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeepSeekPollData {
|
||||||
|
questionID: string;
|
||||||
|
question: string;
|
||||||
|
flag: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pollCourse() {
|
||||||
|
const poll = new Poll<CoursePollData>(CONST.pollInterval);
|
||||||
|
coursePolls.value.push(poll);
|
||||||
|
return poll.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pollDeepSeek() {
|
||||||
|
const poll = new Poll<DeepSeekPollData>(CONST.pollInterval);
|
||||||
|
deepSeekPolls.value.push(poll);
|
||||||
|
return poll.wait();
|
||||||
|
}
|
4
src/app/initRouter.ts
Normal file
4
src/app/initRouter.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import "./routers/answer";
|
||||||
|
import "./routers/polling";
|
||||||
|
import "./routers/question";
|
||||||
|
import "./routers/test";
|
4
src/app/router.ts
Normal file
4
src/app/router.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import VCLightRouter from "@vclight/router";
|
||||||
|
|
||||||
|
const router = new VCLightRouter();
|
||||||
|
export default router;
|
27
src/app/routers/answer.ts
Normal file
27
src/app/routers/answer.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import router from "../router";
|
||||||
|
import { coursePolls } from "../data/polls";
|
||||||
|
|
||||||
|
router.on("/answer", async function (data, response) {
|
||||||
|
console.log(data.body);
|
||||||
|
const { questionID, answer } = data.body;
|
||||||
|
response.contentType = "application/json";
|
||||||
|
if (typeof questionID != "string" || typeof answer != "string") {
|
||||||
|
response.response = {
|
||||||
|
status: "error",
|
||||||
|
message: "questionID and answer are required"
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Received answer", questionID, answer);
|
||||||
|
const polls = coursePolls.value;
|
||||||
|
const pollData = {
|
||||||
|
questionID: questionID,
|
||||||
|
answer: answer,
|
||||||
|
flag: true
|
||||||
|
};
|
||||||
|
coursePolls.value = [];
|
||||||
|
polls.forEach((poll) => {
|
||||||
|
poll.resolve(pollData);
|
||||||
|
});
|
||||||
|
coursePolls.cache = pollData;
|
||||||
|
});
|
48
src/app/routers/polling.ts
Normal file
48
src/app/routers/polling.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import router from "../router";
|
||||||
|
import {
|
||||||
|
CoursePollData,
|
||||||
|
coursePolls,
|
||||||
|
DeepSeekPollData,
|
||||||
|
deepSeekPolls,
|
||||||
|
pollCourse,
|
||||||
|
pollDeepSeek
|
||||||
|
} from "../data/polls";
|
||||||
|
import Poll from "../../utils/poll";
|
||||||
|
|
||||||
|
router.on("/polling", async function (data, response) {
|
||||||
|
if (data.method == "OPTIONS") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.query.env == "course" || data.query.env == "deepseek") {
|
||||||
|
const polls = data.query.env == "course" ? coursePolls : deepSeekPolls;
|
||||||
|
|
||||||
|
if (polls.cache.flag) {
|
||||||
|
response.response = {
|
||||||
|
status: "success",
|
||||||
|
data: JSON.parse(JSON.stringify(polls.cache))
|
||||||
|
};
|
||||||
|
polls.cache.flag = false;
|
||||||
|
console.log("Polling cache", polls.cache);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pollData: CoursePollData | DeepSeekPollData | typeof Poll.TIMEOUT = await (data.query
|
||||||
|
.env == "course"
|
||||||
|
? pollCourse()
|
||||||
|
: pollDeepSeek());
|
||||||
|
response.contentType = "application/json";
|
||||||
|
if (pollData == Poll.TIMEOUT) {
|
||||||
|
response.response = {
|
||||||
|
status: "nothing"
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pollData = <CoursePollData | DeepSeekPollData>pollData;
|
||||||
|
pollData.flag = false;
|
||||||
|
response.response = {
|
||||||
|
status: "success",
|
||||||
|
data: pollData
|
||||||
|
};
|
||||||
|
console.log("Polling data", pollData);
|
||||||
|
}
|
||||||
|
});
|
33
src/app/routers/question.ts
Normal file
33
src/app/routers/question.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import router from "../router";
|
||||||
|
import { coursePolls, deepSeekPolls } from "../data/polls";
|
||||||
|
|
||||||
|
router.on("/question", async function (data, response) {
|
||||||
|
const { question } = data.body;
|
||||||
|
const questionId = Math.random().toString(16).slice(2, 8);
|
||||||
|
response.contentType = "application/json";
|
||||||
|
if (typeof question != "string") {
|
||||||
|
response.response = {
|
||||||
|
status: "error",
|
||||||
|
message: "questionId is required"
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Received question", question);
|
||||||
|
const polls = deepSeekPolls.value;
|
||||||
|
coursePolls.value = [];
|
||||||
|
const pollData = {
|
||||||
|
questionID: questionId,
|
||||||
|
question: question,
|
||||||
|
flag: true
|
||||||
|
};
|
||||||
|
polls.forEach((poll) => {
|
||||||
|
poll.resolve(pollData);
|
||||||
|
});
|
||||||
|
deepSeekPolls.cache = pollData;
|
||||||
|
response.response = {
|
||||||
|
status: "success",
|
||||||
|
data: {
|
||||||
|
questionId: questionId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
31
src/app/routers/test.ts
Normal file
31
src/app/routers/test.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import router from "../router";
|
||||||
|
import { coursePolls, deepSeekPolls } from "../data/polls";
|
||||||
|
|
||||||
|
router.on("/test", async function (data, response) {
|
||||||
|
const question = "What is your name?";
|
||||||
|
const questionId = Math.random().toString(16).slice(2, 8);
|
||||||
|
response.contentType = "application/json";
|
||||||
|
console.log("Test question", question);
|
||||||
|
const polls = deepSeekPolls.value;
|
||||||
|
coursePolls.value = [];
|
||||||
|
if (polls.length == 0) {
|
||||||
|
deepSeekPolls.cache = {
|
||||||
|
questionID: questionId,
|
||||||
|
question: question,
|
||||||
|
flag: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
polls.forEach((poll) => {
|
||||||
|
poll.resolve({
|
||||||
|
questionID: questionId,
|
||||||
|
question: question,
|
||||||
|
flag: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
response.response = {
|
||||||
|
status: "success",
|
||||||
|
data: {
|
||||||
|
questionId: questionId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
5
src/const.ts
Normal file
5
src/const.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const CONST = {
|
||||||
|
pollInterval: 10000
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CONST;
|
11
src/main.ts
Normal file
11
src/main.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as http from "http";
|
||||||
|
import app from "./app/app";
|
||||||
|
|
||||||
|
const server = http.createServer();
|
||||||
|
|
||||||
|
server.on("request", app.httpHandler());
|
||||||
|
|
||||||
|
server.listen(3000, () => {
|
||||||
|
console.log("VCLight serve");
|
||||||
|
console.log("> Ready! Available at http://localhost:3000");
|
||||||
|
});
|
27
src/utils/poll.ts
Normal file
27
src/utils/poll.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export default class Poll<T> {
|
||||||
|
protected inner: Promise<T>;
|
||||||
|
protected timer: Promise<typeof Poll.TIMEOUT>;
|
||||||
|
protected resolver: (value: T | PromiseLike<T>) => void;
|
||||||
|
static TIMEOUT = class Timeout {};
|
||||||
|
|
||||||
|
constructor(protected timeout: number) {
|
||||||
|
let resolver: (value: T | PromiseLike<T>) => void;
|
||||||
|
this.inner = new Promise<T>((r) => {
|
||||||
|
resolver = r;
|
||||||
|
});
|
||||||
|
this.resolver = resolver!;
|
||||||
|
this.timer = new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(Poll.TIMEOUT);
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(value: T | PromiseLike<T>) {
|
||||||
|
this.resolver(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async wait(): Promise<T | typeof Poll.TIMEOUT> {
|
||||||
|
return Promise.race([this.inner, this.timer]);
|
||||||
|
}
|
||||||
|
}
|
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2024",
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user