Initial Commit

This commit is contained in:
2025-05-08 20:05:49 +08:00
commit 2d575e2e84
19 changed files with 1141 additions and 0 deletions
+146
View 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
View 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
View 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;
}
}