Initial Commit
This commit is contained in:
@@ -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);
|
||||
})();
|
||||
@@ -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);
|
||||
})();
|
||||
Vendored
+308
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user