// ==UserScript==
// @name NJU Helper
// @namespace Flying-Tom/NJU-Helper
// @version 0.2.0
// @author Flying-Tom
// @description A helper for you to automate your life in NJU.
// @license AGPL-3.0
// @icon https://z1.ax1x.com/2023/11/21/pia2Gtg.png
// @match https://authserver.nju.edu.cn/authserver/login*
// @match http://ndyy.nju.edu.cn/*
// @match https://zhtj.youth.cn/zhtj/
// @match https://ehall.nju.edu.cn/ywtb-portal/official/index.html*
// @match https://mail.smail.nju.edu.cn/cgi-bin/loginpage*
// @match https://ehallapp.nju.edu.cn/gsapp/sys/wspjapp/*
// @match https://epay.nju.edu.cn/epay/h5/*
// @match https://zzbdjgz.nju.edu.cn/consumer/*
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/ort.min.js#sha256-CdOXaLKpsqac99HNMqEaQ4sKAPkm73Se4ux6F8hzSfM=
// @resource ONNX_MODEL https://cdn.jsdelivr.net/gh/Do1e/NJUcaptcha@main/model/checkpoints/nju_captcha.onnx
// @grant GM_getResourceURL
// @grant GM_info
// ==/UserScript==
(function () {
'use strict';
var _GM_getResourceURL = /* @__PURE__ */ (() => typeof GM_getResourceURL != "undefined" ? GM_getResourceURL : void 0)();
var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
function wechat_page_handler() {
console.debug("Solving wechat page");
if (window.location.href.includes("electric")) {
return;
} else if (window.location.href.includes("recharge")) {
const styleElement = document.createElement("style");
styleElement.textContent = `
body {
width: 40%;
margin: 0 auto;
}
`;
document.head.appendChild(styleElement);
}
}
function waitForElm(selector) {
return new Promise((resolve) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(() => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
function waitForElms(selector) {
return new Promise((resolve) => {
if (document.querySelectorAll(selector).length > 0) {
return resolve(document.querySelectorAll(selector));
}
const observer = new MutationObserver(() => {
if (document.querySelectorAll(selector).length > 0) {
resolve(document.querySelectorAll(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
const charset = ["1", "2", "3", "4", "5", "6", "7", "8", "a", "b", "c", "d", "e", "f", "h", "k", "n", "p", "q", "x", "y", "z"];
const resizeWidth = 176;
const resizeHeight = 64;
const charCount = 4;
const numClasses = 22;
const mean = [0.743, 0.7432, 0.7431];
const std = [0.1917, 0.1918, 0.1917];
let modelSession = null;
let modelPromise = null;
function waitForOrt() {
return new Promise((resolve) => {
if (typeof ort !== "undefined") {
resolve();
return;
}
const checkOrt = () => {
if (typeof ort !== "undefined") {
resolve();
} else {
setTimeout(checkOrt, 100);
}
};
checkOrt();
});
}
async function initializeONNXRuntime() {
await waitForOrt();
ort.env.wasm.wasmPaths = "https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/";
}
async function loadONNXModel() {
if (modelSession) {
return modelSession;
}
if (modelPromise) {
return modelPromise;
}
modelPromise = (async () => {
try {
const modelURL = await _GM_getResourceURL("ONNX_MODEL");
const response = await fetch(modelURL);
if (!response.ok) {
throw new Error(`下载ONNX模型失败,状态码: ${response.status}`);
}
const modelData = await response.arrayBuffer();
const session = await ort.InferenceSession.create(modelData, {
executionProviders: ["wasm"]
});
modelSession = session;
return session;
} catch (error) {
console.error("ONNX模型加载失败:", error);
throw error;
}
})();
return modelPromise;
}
function preprocessImage(imageData) {
const { data, width, height } = imageData;
const float32Data = new Float32Array(3 * height * width);
for (let i = 0; i < height * width; i++) {
const r = data[i * 4] / 255;
const g = data[i * 4 + 1] / 255;
const b = data[i * 4 + 2] / 255;
float32Data[i] = (r - mean[0]) / std[0];
float32Data[i + height * width] = (g - mean[1]) / std[1];
float32Data[i + 2 * height * width] = (b - mean[2]) / std[2];
}
return float32Data;
}
async function recognizeCaptcha(imageElement) {
try {
const canvas = document.createElement("canvas");
canvas.width = resizeWidth;
canvas.height = resizeHeight;
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("无法创建Canvas上下文");
}
ctx.drawImage(imageElement, 0, 0, resizeWidth, resizeHeight);
const imageData = ctx.getImageData(0, 0, resizeWidth, resizeHeight);
const float32Data = preprocessImage(imageData);
const session = await loadONNXModel();
const inputTensor = new ort.Tensor("float32", float32Data, [1, 3, resizeHeight, resizeWidth]);
const feeds = { "input": inputTensor };
const results = await session.run(feeds);
const outputTensor = results.output;
const outputData = outputTensor.data;
let text = "";
for (let i = 0; i < charCount; i++) {
const startIndex = i * numClasses;
const endIndex = startIndex + numClasses;
const classScores = Array.from(outputData.slice(startIndex, endIndex));
let maxScore = -Infinity;
let maxIndex = -1;
for (let j = 0; j < classScores.length; j++) {
if (classScores[j] > maxScore) {
maxScore = classScores[j];
maxIndex = j;
}
}
text += charset[maxIndex];
}
return text;
} catch (error) {
console.error("验证码识别出错:", error);
throw error;
}
}
async function solveCAPTCHA(image_target, captcha_input_selectors, autoLogin) {
try {
const result = await recognizeCaptcha(image_target);
console.debug("验证码识别结果:", result);
const captchaInput = document.querySelector(captcha_input_selectors);
if (captchaInput) {
captchaInput.value = result;
}
if (autoLogin.enabled === true) {
const intervalId = setInterval(function() {
const usernameInput = document.querySelector(autoLogin.usernameSelectors);
const passwordInput = document.querySelector(autoLogin.passwordSelectors);
const submitButton = document.querySelector(autoLogin.submitSelectors);
if (usernameInput && passwordInput && submitButton) {
if (usernameInput.value && passwordInput.value) {
submitButton.click();
clearInterval(intervalId);
}
}
}, 1e3);
}
} catch (error) {
console.error("验证码识别失败:", error);
}
}
function handleCaptcha(captcha_img_selectors, captcha_input_selectors, autoLogin) {
void initializeONNXRuntime().then(() => {
void waitForElm(captcha_img_selectors).then((data) => {
const image_target = data;
if (image_target.complete === true) {
void solveCAPTCHA(image_target, captcha_input_selectors, autoLogin);
} else {
image_target.addEventListener("load", () => void solveCAPTCHA(image_target, captcha_input_selectors, autoLogin));
}
});
}).catch((error) => {
console.error("ONNX Runtime 初始化失败:", error);
});
}
function njuauth_handler() {
var _a;
console.debug("Solving njuauth captcha");
const inputElement = document.querySelector("input[name=dllt][value=userNamePasswordLogin]");
if (inputElement) {
inputElement.value = "mobileLogin";
}
const showPassElement = (_a = document.getElementsByClassName("showPass")) == null ? void 0 : _a[0];
if (showPassElement) {
showPassElement.click();
}
handleCaptcha("#captchaImg", "#captchaResponse", {
enabled: true,
// enabled: false,
usernameSelectors: "input[name=username]",
passwordSelectors: "input[type=password]",
submitSelectors: "button[type=submit]"
});
}
function njuhospital_handler() {
console.debug("Solving nju hospital captcha");
const inputElement = document.querySelector('input[name="NewWebYzm"]');
if (inputElement) {
inputElement.value = "";
}
handleCaptcha("#refreshCaptcha", 'input[name="NewWebYzm"]', {
enabled: false
});
}
function ehall_handler() {
console.debug("Solving ehall captcha");
const intervalId = setInterval(function() {
if (document.querySelector(".header-user")) {
const spanElement = document.evaluate("//span[contains(., '首页')]", document, null, XPathResult.ANY_TYPE).iterateNext();
if (spanElement) {
spanElement.click();
}
clearInterval(intervalId);
}
const btnLogin = document.querySelector(".btn-login");
if (btnLogin) {
btnLogin.click();
}
}, 200);
setTimeout(function() {
clearInterval(intervalId);
}, 5e3);
}
function email_trust_handler() {
console.debug("Solving email trust");
const intervalId = setInterval(function() {
const checkbox = document.getElementById("force_wx_scan_login_tc");
if (checkbox) {
checkbox.checked = true;
clearInterval(intervalId);
}
}, 500);
}
function zhtj_handler() {
console.debug("Solving zhtj captcha");
handleCaptcha("#codeImage", "#yzm", {
enabled: false
});
}
function wspj_handler() {
setInterval(checkForElement, 1e3);
function checkForElement() {
const element = document.querySelector("#pjfooter");
if (element) {
console.debug("Solving wspj page");
void waitForElms(".bh-radio-label.paper_dx").then(
(elems) => {
const choices = elems;
choices.forEach((elem) => {
const choice = elem;
const spanElement = choice.querySelector("span");
const input = choice.querySelector("input");
if (spanElement && input) {
if (spanElement.textContent === "符合") {
input.checked = true;
}
}
});
}
);
} else {
return;
}
}
}
function samrt_party_handler() {
function search_and_pay() {
console.debug("Try to search for input elements.");
if (window.location.href.includes("partyCostPay")) {
void waitForElms(".ivu-input-number-input").then((data) => {
const feeInputElems = data;
feeInputElems.forEach((inputElem) => {
const fee = "0.2";
inputElem.value = fee;
inputElem.dispatchEvent(new Event("input", { bubbles: true }));
});
});
}
}
void waitForElm("#app").then((data) => {
const observer = new MutationObserver(search_and_pay);
observer.observe(data, {
childList: true,
subtree: true
});
});
}
(function() {
(function() {
const matchIdx = _GM_info.script.matches.map((rule) => rule.replace(/\.|\*|\/|\?/g, (match) => ({ ".": "\\.", "*": ".*", "/": "\\/", "?": "\\?" })[match] ?? "")).map((rule) => new RegExp(rule)).map((regExp, index) => regExp.test(window.location.href) ? index : null).filter((index) => index != null).join().toString();
const strategyLoad = {
"0": njuauth_handler,
// 南大统一身份认证自动登录
"1": njuhospital_handler,
// 南大校医院验证码
"2": zhtj_handler,
// 智慧团建验证码
"3": ehall_handler,
// 南大 ehall 自动登录
"4": email_trust_handler,
// 南大学生邮箱信任本机
"5": wspj_handler
// 南大ehall 课程评教
};
const strategyInstant = {
"6": wechat_page_handler,
// 南大信息门户 pc 端
"7": samrt_party_handler
// 智慧党建
};
if (matchIdx in strategyInstant) {
const strategyInstantFunc = strategyInstant[matchIdx];
strategyInstantFunc();
} else if (matchIdx in strategyLoad) {
const strategyLoadFunc = strategyLoad[matchIdx];
if (document.readyState == "complete") {
strategyLoadFunc();
} else {
window.addEventListener("load", strategyLoadFunc);
}
}
})();
})();
})();