您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
为 USTC 学生定制的各类实用功能:绕过验证码,自动登录,睿客网性能优化以及更多。
当前为
// ==UserScript== // @name USTC Helper // @name:zh-CN USTC 助手 // @license gpl-3.0 // @namespace http://tampermonkey.net/ // @version 0.10.1 // @description Various useful functions for USTC students: verification code bypass, auto login, rec performance improvement and more. // @description:zh-CN 为 USTC 学生定制的各类实用功能:绕过验证码,自动登录,睿客网性能优化以及更多。 // @author PRO // @match https://mail.ustc.edu.cn/ // @match https://mail.ustc.edu.cn/coremail/index.jsp* // @match https://passport.ustc.edu.cn/* // @match https://rec.ustc.edu.cn/* // @match https://recapi.ustc.edu.cn/identity/other_login?* // @match https://www.bb.ustc.edu.cn/* // @match https://jw.ustc.edu.cn/* // @match https://young.ustc.edu.cn/login/* // @match https://young.ustc.edu.cn/nginx_auth/* // @match https://wvpn.ustc.edu.cn/* // @icon https://passport.ustc.edu.cn/images/favicon.ico // @grant none // @require http://greasyfork.icu/scripts/462234-message/code/Message.js?version=1192786 // ==/UserScript== (function () { 'use strict'; var uhp_config = { passport: { enabled: true, // If false, all features will be disabled for passport.ustc.edu.cn bypass_code: true, // Whether to bypass verification code or not focus: true, // Whether to focus on "Login" button service: true // Hint service domain and its credibility }, mail: { enabled: true, // If false, all features will be disabled for mail.ustc.edu.cn focus: true, // Whether to focus on "Login" button domain: 'mail.ustc.edu.cn' // Automatically switch to given mail domain // Expected values: // 'mail.ustc.edu.cn' // 'ustc.edu.cn' // 'ah.edu.cn' // '' (Do nothing) }, rec: { enabled: true, // If false, all features will be disabled for rec.ustc.edu.cn & recapi.ustc.edu.cn autologin: true, // Whether automatically clicks login (USTC cas login) opencurrent: true // Whether open links in current tab (Significantly improves performance) }, bb: { enabled: true, // If false, all features will be disabled for www.bb.ustc.edu.cn autoauth: true, // Whether automatically authenticate when accessing outside school net autologin: true, // Whether automatically clicks login showhwstatus: true // Whether to display homework status (may consume some traffic) }, jw: { enabled: true, // ... login: 'focus', // What to do to the login button: 'none', 'focus', 'click' shortcut: true, // Shortcut support score_mask: true // Hide/reveal your scores with dblclick }, young: { enabled: true, auto_auth: true, // Whether automatically authenticate when accessing outside school net default_tab: "/myproject/SignUp", // The tab on entering auto_tab: true, // Auto navigate to frequently-used submenu no_datascreen: true, // Remove annoying data screen image shortcut: true // Shortcut support }, wvpn: { enabled: true, custom_collection: true // Allows for customizing collections } }; let name = "USTC Helper"; window.QMSG_GLOBALS = { DEFAULTS: { showClose:true, timeout: 2000 } } switch (window.location.host) { case 'mail.ustc.edu.cn': { if (!uhp_config.mail.enabled) { console.info("[USTC Helper] 'mail' feature disabled."); break; } if (uhp_config.mail.domain) { changeDomain(uhp_config.mail.domain); console.info(`[USTC Helper] Domain changed to ${uhp_config.mail.domain}.`); } if (uhp_config.mail.focus) { document.getElementById("login_button").focus(); console.info("[USTC Helper] Login button focused."); } break; } case 'passport.ustc.edu.cn': { if (!uhp_config.passport.enabled) { console.info("[USTC Helper] 'passport' feature disabled."); break; } let form = document.getElementsByClassName('loginForm')[0]; if (!form) { console.log("[USTC Helper] Form not found!"); break; } let options = { childList: true, attributes: false, subtree: true } function bypass() { let showCode = document.getElementsByName('showCode')[0]; showCode.value = ""; let code = document.querySelector('#valiCode'); if (code) { code.remove(); console.info("[USTC Helper] Verification code bypassed."); } else { console.info("[USTC Helper] Verification code not found."); } } function focus() { document.getElementById('login').focus(); console.info("[USTC Helper] Login button focused."); } function hint() { let notice = document.createElement('p'); let params = new URL(window.location.href).searchParams; let service_url = params.get('service'); if (!service_url) return; service_url = decodeURIComponent(service_url); let domain = service_url.split('/')[2]; let color; let status; // Official Student/Staff Third-party let suffix; if (/.+\.ustc\.edu\.cn/.test(domain)) { if (domain == 'home.ustc.edu.cn') { status = "Student"; color = "#d0d01b"; suffix = "@mail.ustc.edu.cn"; } else if (domain == 'staff.ustc.edu.cn') { status = "Staff"; color = "#d0d01b"; suffix = "@ustc.edu.cn"; } else { status = "Official"; color = "green"; } } else { status = "Third-party"; color = "red"; } console.info(`[USTC Helper] ${status} service: ${service_url}`); if (color == "#d0d01b") { let regex = new RegExp(/https?:\/\/(home|staff)\.ustc\.edu\.cn\/~([^\/]+)/i); let match = service_url.match(regex); if (match) { let name = match[2]; let email = name + suffix; console.log("[USTC Helper] Contact email: " + email); notice.innerHTML = `<a style="color: #d0d01b;" title="Contact" href="mailto:${email}">${status}</a> service: <span style="color: grey;" title="${service_url}">${domain}</span>`; } else { console.log("[USTC Helper] Unable to determine contact email!"); notice.innerHTML = `<a style="color: #d0d01b;" title="Unrecognized">${status}</a> service: <span style="color: grey;" title="${service_url}">${domain}</span>`; } } else { notice.innerHTML = `<span style="color: ${color};">${status}</span> service: <span style="color: grey;" title="${service_url}">${domain}</span>`; } let main_card = document.getElementsByClassName('card')[0]; main_card.insertAdjacentElement('afterbegin', notice); } function main() { if (uhp_config.passport.bypass_code) bypass(); if (uhp_config.passport.focus) focus(); if (uhp_config.passport.service) hint(); observer.disconnect(); } let observer = new MutationObserver(main); observer.observe(form, options); break; } case 'rec.ustc.edu.cn': { if (!uhp_config.rec.enabled) { console.info("[USTC Helper] 'rec' feature disabled."); break; } if (uhp_config.rec.opencurrent) { window.webpackJsonp.push_ = window.webpackJsonp.push; window.webpackJsonp.push = (val) => { if (val[0][0] !== "chunk-5ae262a1") return window.webpackJsonp.push_(val); else { // Following script is adapted from https://rec.ustc.edu.cn/js/chunk-5ae262a1.b84e1461.js val[1]["2c03"] = function (t, e, s) { "use strict"; (function (t) { s("55dd"); var r = s("a67e"); e["a"] = { name: "GroupLister", components: { GroupCreate: function () { return Promise.all([s.e("chunk-390136ce"), s.e("chunk-662e27b9")]).then(s.bind(null, "18fa")) }, GroupAdd: function () { return s.e("chunk-5b916374").then(s.bind(null, "c1c7")) }, GroupEdit: function () { return Promise.all([s.e("chunk-390136ce"), s.e("chunk-0daeb591")]).then(s.bind(null, "1fa6")) } }, data: function () { return { status: { GroupCreateStatus: !1, GroupAddStatus: !1, GroupEditStatus: !1 }, loading: !1, nothing: !1, group: {}, sortBy: {}, headers: [{ id: 1, title: "群名称", class: "groupname", sort: "asc", showSort: !0, field: "group_name" }, { id: 2, title: "群号", class: "groupid", sort: "des", showSort: !1, field: "group_number" }, { id: 3, title: "成员", class: "groupuser", sort: "des", showSort: !1, field: "group_memeber_count" }, { id: 5, title: "分享", class: "groupshare", sort: "des", showSort: !1, field: "group_share_file_count" }, { id: 6, title: "操作", class: "groupmenu", sort: "", showSort: !1 }] } }, created: function () { this.sortBy = this.headers[0], this.getGroups() }, computed: { userInfo: function () { return this.$store.state.user.userInfo } }, watch: { $route: function () { this.getGroups() } }, filters: { identityNameFilter: function (t) { var e; switch (t) { case "owner": e = "群主"; break; case "admin": e = "管理员"; break; case "user": e = "成员"; break; default: break } return e } }, methods: { createGroup: function () { t("#newgroup").modal("show") }, addGroup: function () { t("#addgroup").modal("show") }, invite: function (t) { var e = this.$router.resolve({ name: "group", params: { groupNumber: t.group_number } }); this.$confirm({ showYesBtn: !1, showCopyBtn: !0, copyBtnText: "复制文字", title: "邀请入群", type: "confirm", content: "打开链接进入群组主页即可申请加入群组:".concat(t.group_name, ",群组主页链接:").concat(window.location.origin).concat(e.href) }).then((function () { } )).catch((function () { } )) }, goToGroupCloud: function (t, e) { if (["owner", "admin", "user"].indexOf(t.group_member_identity) < 0) return this.$message({ type: "warning", message: "您不是组群成员,无法进入群盘" }), !1; this.$store.commit("setSetting", { from: !0, drive: "groupdisk", tab: e, group: t }), this.$router.push({ name: "groupDisk", params: { groupNumber: t.group_number } }) }, isShowMenu: function (t) { return ["owner", "admin", "user"].indexOf(t.group_member_identity) > -1 }, isEditGroup: function (t) { return ["owner", "admin"].indexOf(t.group_member_identity) > -1 }, goToGroup: function (t) { var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "group"; if ("wait" === t.group_is_review) return this.$message({ type: "warning", message: "群组待审核,不允许操作!" }), !1; if ("refuse" === t.group_is_review) return this.$message({ type: "warning", message: "群组审核未通过,不允许操作!" }), !1; // Instead of opening in new tab, we prefer to use vue's solution // Modifiy start this.$router.replace({ name: e, params: { groupNumber: t.group_number } }); // Modify end }, goToGroupHome: function (t) { this.$store.commit("SET_GROUP_SHOWDESC", !1), this.$router.push({ name: "group", params: { groupNumber: t } }) }, handleEditGroup: function (e) { var s = this; Object(r["g"])(e.group_number).then((function (t) { s.group = t.entity } )).catch((function (t) { s.$message({ type: "error", message: t }) } )), t("#editgroup").modal("show") }, groupRefresh: function () { this.getGroups() }, sortGroup: function (t) { if (6 === t) return !1; var e = this; this.headers.map((function (s) { return s.id === t ? (s.showSort = !0, s.sort = "des" === s.sort ? "asc" : "des", e.sortBy = s, s) : (s.showSort = !1, s.sort = "des", s) } )), this.sortGroupBy() }, getGroups: function () { var t = this; this.groups = [], this.loading = !0, this.nothing = !1, Object(r["r"])({}).then((function (e) { if (200 === e.status_code) if (t.loading = !1, t.groups = e.entity.datas, e.entity.total > 0) { var s = 0; e.entity.datas.map((function (t) { "user" != t.group_member_identity && t.group_pending_member_count > 0 && (s += t.group_pending_member_count) } )), t.$store.commit("setRequestNums", s), t.sortGroupBy(!0) } else t.nothing = !0; else t.$message({ type: "error", message: e.message }) } )).catch((function (e) { t.$message({ type: "error", message: e }) } )) }, sortGroupBy: function () { var t = this , e = arguments.length > 0 && void 0 !== arguments[0] && arguments[0]; this.groups.sort((function (s, r) { var o; return o = e ? r.group_is_review.localeCompare(s.group_is_review) : "group_name" === t.sortBy.field ? s[t.sortBy.field].localeCompare(r[t.sortBy.field]) : s[t.sortBy.field] - r[t.sortBy.field], o = "asc" === t.sortBy.sort ? o : -o, o } )) }, groupCancel: function (t) { var e = this , s = "adopt" === t.group_is_review ? "解散" : "删除"; this.$confirm({ type: "confirm", content: "".concat(s, "群后,所有关于本群组的信息都将被删除且无法恢复,确定").concat(s, "【").concat(t.group_name, "】吗?"), showCancleBtn: !0, showYesBtn: !0, custom: [] }).then((function () { Object(r["u"])({ groups_list: [t.group_number] }).then((function (t) { 200 === t.status_code ? (e.$message({ type: "success", message: t.message }), e.getGroups()) : e.$message({ type: "error", message: t.message }) } )).catch((function (t) { e.$message({ type: "error", message: t }) } )) } )).catch((function () { } )) }, groupQuit: function (t) { var e = this; this.$confirm({ type: "confirm", content: "确定退出该群组吗?", showCancleBtn: !0, showYesBtn: !0, custom: [] }).then((function () { Object(r["v"])({ group_number: t, action: "quit", members_list: [e.userInfo.user_number] }).then((function (t) { 200 === t.status_code ? (e.$message({ type: "success", message: t.message }), e.getGroups()) : e.$message({ type: "error", message: t.message }) } )).catch((function (t) { e.$message({ type: "error", message: t }) } )) } )).catch((function () { } )) } }, mounted: function () { var t = this; setTimeout((function () { for (var e in t.status) t.status[e] = !0 } ), 500) } } } ).call(this, s("1157")) }; // console.log(val); return window.webpackJsonp.push_(val); } }; } if (uhp_config.rec.autologin && document.location.pathname == '/') { let app = document.getElementById("app"); let options = { childList: true, attributes: false, subtree: true } let observer = new MutationObserver(() => { let btn = document.getElementsByClassName('navbar-login-btn')[0]; if (btn) { btn.click(); observer.disconnect(); } }); observer.observe(app, options); } else if (uhp_config.rec.opencurrent) { let app = document.getElementById("app"); let options = { childList: true, attributes: false, subtree: true } let observer = new MutationObserver(() => { let l = document.getElementsByClassName("app-list").length; if (l) { let links = app.getElementsByTagName("a"); for (let link of links) { if (link.target == '_blank') link.removeAttribute("target"); } } }); observer.observe(app, options); } break; } case 'recapi.ustc.edu.cn': { if (!uhp_config.rec.enabled) { console.info("[USTC Helper] 'rec' feature disabled."); break; } if (uhp_config.rec.autologin) { let btn = document.querySelector("#ltwo > div > button"); if (!btn) { console.error("[USTC Helper] Login button not found!"); } else { btn.click(); } } break; } case 'www.bb.ustc.edu.cn': { if (!uhp_config.bb.enabled) { console.info("[USTC Helper] 'bb' feature disabled."); break; } if (window.location.pathname == '/nginx_auth/' && uhp_config.bb.autoauth) { document.getElementsByTagName('a')[0].click(); } else if ((window.location.pathname == '/' || window.location.pathname == '/webapps/login/') && uhp_config.bb.autologin) { document.querySelector('#login > table > tbody > tr > td:nth-child(2) > span > a').click(); } else if (uhp_config.bb.showhwstatus && window.location.pathname == '/webapps/blackboard/content/listContent.jsp' && document.getElementById('pageTitleText').children[0].textContent == '作业区') { let hw_list = document.getElementById('content_listContainer'); let color_config = ['grey', 'green', 'red', 'yellow']; let hint_text = ['查询中', '已提交', '未提交', '查询错误']; // let hint_text = ['Checking', 'Submitted', 'Not submitted', 'Error']; async function query_status(link) { const r = await fetch(link); if (!r.ok) { console.log(`[USTC Helper] Failed to fetch "${r.url}": ${r.status} ${r.statusText}`); return 3; } else { let html = await r.text(); if (html.match(/<span id="pageTitleText">\n 复查提交历史记录: .+<\/span>/)) return 1; else if (html.match(/<span id="pageTitleText">\n 上载作业:.+<\/span>/)) return 2; else return 3; } } async function process(hw) { let link_ = hw.querySelector("h3 > a"); if (link_) { let status = 0; // 0: Checking 1: Uploaded 2: Not uploaded 3: Error let hint = document.createElement('span'); let ret = ''; hint.style.color = color_config[status]; hint.textContent = `(${hint_text[status]})`; link_.appendChild(hint); let link = link_.href; // https://www.bb.ustc.edu.cn/webapps/assignment/uploadAssignment?content_id=_106763_1&course_id=_12559_1&group_id=&mode=view let params = new URL(link).searchParams; let course_id = params.get("course_id"); let content_id = params.get("content_id"); let uploaded = sessionStorage.getItem(course_id); // Query from cache first if (uploaded) { uploaded = JSON.parse(uploaded); if (uploaded.indexOf(content_id) >= 0) { status = 1; console.log(`[USTC Helper] "${course_id}/${content_id}" present in cache, so this homework is uploaded.`); } } // Not in cache if (!status) { status = await query_status(link); if (status == 1) { ret = content_id; console.log(`[USTC Helper] Online query indicated that "${course_id}/${content_id}" is uploaded.`); } else if (status == 2) { console.log(`[USTC Helper] Online query indicated that "${course_id}/${content_id}" is not uploaded.`); } else { console.warn(`[USTC Helper] Online query "${course_id}/${content_id}" failed!`); } } hint.style.color = color_config[status]; hint.textContent = `(${hint_text[status]})`; return ret; } } let promises = []; for (let hw of hw_list.children) { promises.push(process(hw)); } Promise.all(promises).then( (values) => { let params = new URL(window.location.href).searchParams; let course_id = params.get('course_id'); let uploaded = sessionStorage.getItem(course_id); if (uploaded) { uploaded = JSON.parse(uploaded); } else { uploaded = []; } for (let content_id of values) { if (content_id.length) { uploaded.push(content_id); console.log(`[USTC Helper] Saving "${course_id}/${content_id}" to cache...`); } } sessionStorage.setItem(course_id, JSON.stringify(uploaded)); } ); } break; } case 'jw.ustc.edu.cn': { if (!uhp_config.jw.enabled) { console.info("[USTC Helper] 'jw' feature disabled."); break; } if (uhp_config.jw.login && window.location.pathname == "/login") { let btn = document.getElementById('login-unified-wrapper'); if (uhp_config.jw.login == 'focus') { btn.focus(); } else if (uhp_config.jw.login == 'click') { btn.click(); } else { console.error(`[USTC Helper] Unknown option for jw.login: ${uhp_config.jw.login}`); } } if (uhp_config.jw.shortcut && window.top.location.pathname == "/home") { let shortcuts = ["ArrowLeft", "ArrowRight", "x", '1', '2', '3', '4', '5', '6', '7', '8', '9']; document.addEventListener("keydown", (e) => { if (document.activeElement.nodeName != "INPUT" && shortcuts.includes(e.key)) { let menu = window.top.document.getElementById("e-home-tab-list"); let tabs = Array.from(menu.children); let home = window.top.document.querySelector("#e-top-home-page > li > a"); tabs.push(home); let count = tabs.length; let current = 0; for (let tab of tabs) { if (tab.classList.contains('active')) { break; } current++; } if (current == count) current--; switch (e.key) { case "ArrowLeft": tabs[(current - 1 + count) % count].click(); break; case "ArrowRight": tabs[(current + 1) % count].click(); break; case "x": let close = tabs[current].querySelector("a > i") if (close) close.click(); break; default: if (e.key.length == 1) { let idx = (Number(e.key) - 2 + count) % count; if (0 <= idx && idx < count) { tabs[idx].click(); } } break; } } }); } if (uhp_config.jw.score_mask && window.location.pathname == "/for-std/grade/sheet") { function get_status(entry) { // Status: // false: Normal display // true: Masked if (entry.classList.contains("masked")) return true; else return false; } function set_status_internal(entry, state) { let gpa = entry.children[entry.children.length - 2]; let score = entry.lastChild; if (state) { entry.classList.add("masked"); entry.setAttribute("data-gpa", gpa.textContent); entry.setAttribute("data-score", score.textContent); gpa.textContent = ""; score.textContent = ""; } else { entry.classList.remove("masked"); let gpa_val = entry.getAttribute("data-gpa"); let score_val = entry.getAttribute("data-score"); if (gpa_val) gpa.textContent = gpa_val; if (score_val) score.textContent = score_val; } } function toggle() { set_status_internal(this, !get_status(this)); } function set_status(entry, state) { if (get_status(entry) == state) return; set_status_internal(entry, state); } function toggle_view() { if (this.hasAttribute("data-value")) { this.lastChild.textContent = this.getAttribute("data-value"); this.removeAttribute("data-value"); } else { this.setAttribute("data-value", this.lastChild.textContent); this.lastChild.textContent = "尚未评教"; } } function toggle_rank() { if (this.hasAttribute("data-value")) { this.textContent = this.getAttribute("data-value"); this.removeAttribute("data-value"); } else { this.setAttribute("data-value", this.textContent); this.textContent = "尚未评教"; } } function setup() { let tables = document.querySelectorAll("div.semesters > section > div.semester > table"); tables.forEach((table) => { let head = table.querySelector("thead"); let entries = table.querySelectorAll("tbody > tr"); head.addEventListener("dblclick", (e) => { let status = head.getAttribute("data-masked") === ""; entries.forEach((entry) => { set_status(entry, !status); }); if (status) head.removeAttribute("data-masked"); else head.setAttribute("data-masked", ""); }); entries.forEach((entry) => { entry.addEventListener("dblclick", toggle); }); }); let history_table = document.querySelector("table.history-table"); history_table.tHead.addEventListener("dblclick", (e) => { history_table.querySelectorAll("tbody:not(.hidden)").forEach((tbody) => { let status = tbody.getAttribute("data-masked") === ""; tbody.querySelectorAll("tr").forEach((entry) => { set_status(entry, !status); }); if (status) tbody.removeAttribute("data-masked"); else tbody.setAttribute("data-masked", ""); }); }); history_table.querySelectorAll("tbody > tr").forEach((entry) => { entry.addEventListener("dblclick", toggle); }); let view = document.querySelector("div.overview > ul"); view.childNodes.forEach((node) => { node.addEventListener("dblclick", toggle_view); }); let rank = document.querySelector("div.rankinfo > div"); rank.querySelectorAll("b").forEach((node) => { node.addEventListener("dblclick", toggle_rank); }); } let timer = window.setInterval(() => { let test = document.querySelector("div.overview > ul > li > span:nth-child(2)"); if (test.textContent != "NaN") { window.clearInterval(timer); setup(); } }, 1000); } break; } case 'young.ustc.edu.cn': { if (!uhp_config.young.enabled) { console.info("[USTC Helper] 'young' feature disabled."); break; } if (window.location.pathname == '/nginx_auth/' && uhp_config.young.auto_auth) { document.getElementsByTagName('a')[0].click(); return; } let app = document.getElementById("app"); let router = app.__vue__.$router; function main(mutations, observer) { let menu = app.querySelector(".ant-menu-root"); if (!menu) return; let default_tab = uhp_config.young.default_tab; if (default_tab.length) router.push(default_tab); let submenus = menu.querySelectorAll("li.ant-menu-submenu-horizontal:not(.ant-menu-overflowed-submenu) > div"); if (!submenus.length) return; observer.disconnect(); if (uhp_config.young.auto_tab) { submenus[0].onclick = (e) => { router.push('/dataAnalysis/studentAnalysis'); e.stopImmediatePropagation(); } submenus[1].onclick = (e) => { router.push('/personalInformation/personalReport'); } submenus[2].onclick = (e) => { router.push('/myproject/SignUp'); } submenus[5].onclick = (e) => { router.push('/isystem/departUserList'); } app.querySelector(".user-dropdown-menu").onclick = (e) => { document.querySelector("ul.user-dropdown-menu-wrapper > li:nth-child(7) > a").click(); } } if (uhp_config.young.no_datascreen) { app.querySelector("div.header-index-wide > a").remove(); } if (uhp_config.young.shortcut) { document.addEventListener("keydown", (e) => { let tabs = document.querySelector(".ant-tabs-nav-animated > div").children; let count = tabs.length; let current = 0; for (let tab of tabs) { if (tab.attributes["aria-selected"].value == "true") { break; } current++; } if (document.activeElement.nodeName != "INPUT") { switch (e.key) { case "ArrowLeft": tabs[(current - 1 + count) % count].click(); break; case "ArrowRight": tabs[(current + 1) % count].click(); break; case "x": tabs[current].querySelector("div > i").click(); break; default: if (e.key.length == 1) { let idx = Number(e.key); if (idx && 0 < idx && idx <= count) { tabs[idx - 1].click(); } } break; } } }) } } let options = { childList: true, attributes: false, subtree: true } let observer = new MutationObserver(main); observer.observe(app, options); break; } case 'wvpn.ustc.edu.cn': { if (!uhp_config.wvpn.enabled) { console.info("[USTC Helper] 'wvpn' feature disabled."); break; } if (uhp_config.wvpn.custom_collection) { // let element = document.querySelector("div.portal-search-input-wrap"); let options = { childList: true, attributes: false, subtree: true } let callback = (mutations, observer) => { let input = document.querySelector("input.portal-search__input"); if (!input) return; observer.disconnect(); let loading = Qmsg.loading("📦 正在加载依赖库..."); let node = document.createElement("script"); node.src = "https://cdn.bootcdn.net/ajax/libs/aes-js/3.1.2/index.js"; function fail(s, hint) { console.error("[USTC Helper]", s); Qmsg.error(hint); } function success(s, hint) { console.info("[USTC Helper]", s); Qmsg.success(hint); } function cancel() { console.info("[USTC Helper] User calcelled the operation."); Qmsg.info("你终止了收藏操作!😢"); } function invalid() { console.warn("[USTC Helper] Invalid input!"); Qmsg.warning("你输入了一个不合法的值!🤔"); } node.onload = () => { loading.close(); success("Aes-js loaded.", "成功加载依赖库!🥳"); input.placeholder = "点击五角星或 Ctrl+D 以收藏 🍻"; // Encryption, adapted from https://blog.csdn.net/lijiext/article/details/110931285 var utf8 = aesjs.utils.utf8; var hex = aesjs.utils.hex; var AesCfb = aesjs.ModeOfOperation.cfb; var wrdvpnKey = 'wrdvpnisthebest!'; var wrdvpnIV = 'wrdvpnisthebest!'; function textRightAppend(text, mode) { var segmentByteSize = mode === 'utf8' ? 16 : 32; if (text.length % segmentByteSize === 0) { return text; } var appendLength = segmentByteSize - text.length % segmentByteSize; var i = 0; while (i++ < appendLength) { text += '0'; } return text; } function encrypt(text, key, iv) { var textLength = text.length; text = textRightAppend(text, 'utf8'); var keyBytes = utf8.toBytes(key); var ivBytes = utf8.toBytes(iv); var textBytes = utf8.toBytes(text); var aesCfb = new AesCfb(keyBytes, ivBytes, 16); var encryptBytes = aesCfb.encrypt(textBytes); return hex.fromBytes(ivBytes) + hex.fromBytes(encryptBytes).slice(0, textLength * 2); } function encryptUrl(url) { var port = ""; var segments = ""; var protocol = ""; if (url.startsWith("http://")) { url = url.substr(7); protocol = "http"; } else if (url.startsWith("https://")) { url = url.substr(8); protocol = "https"; } else { return ""; } var v6 = ""; var match = /\[[0-9a-fA-F:]+?\]/.exec(url); if (match) { v6 = match[0]; url = url.slice(match[0].length); } segments = url.split("?")[0].split(":"); if (segments.length > 1) { port = segments[1].split("/")[0] url = url.substr(0, segments[0].length) + url.substr(segments[0].length + port.length + 1); } var i = url.indexOf('/'); if (i == -1) { if (v6 != "") { url = v6; } url = encrypt(url, wrdvpnKey, wrdvpnIV) } else { var host = url.slice(0, i); var path = url.slice(i); if (v6 != "") { host = v6; } url = encrypt(host, wrdvpnKey, wrdvpnIV) + path; } if (port != "") { url = "/" + protocol + "-" + port + "/" + url; } else { url = "/" + protocol + "/" + url; } return url; } // Main functions function random_color() { let r = Math.floor(Math.random() * 256); let g = Math.floor(Math.random() * 256); let b = Math.floor(Math.random() * 256); return `rgb(${r}, ${g}, ${b})`; } function add_collect() { // Get url let url = input.value; if (url.length == 0) { url = prompt("请输入要收藏的网址:"); } else { input.value = ''; } if (url == undefined || url == null) { cancel(); return; } else if (url.length == 0) { invalid(); return; } if (!url.startsWith("http://") && !url.startsWith("https://")) { url = "https://" + url; } let url_; try { url_ = new URL(url); } catch (error) { invalid(); return; } // Get name let name = ""; let desc = ""; name = prompt("请输入收藏项目的名称:", url_.hostname); if (name == null) { cancel(); return; } desc = prompt("请输入收藏项目的备注:", url_.hostname); if (desc == null) { cancel(); return; } let id = document.querySelector("div[data-id=collection].block-group > div.block-group__content").childElementCount; let post_data = { "resource_type": "vpn", "name": name, "detail": desc, "url": url, "redirect": encryptUrl(url), "id": id, "group_id": "2", "logo": "", "_isCollect": "false", "_displayName": name, "_desc": desc, "_icon[color]": random_color(), "_icon[content]": name[0] } let form = new FormData(); for (let [k, v] of Object.entries(post_data)) { form.append(k, v); } fetch("/user/portal/collections", { method: "POST", body: form }) .then(r => r.json(), r => { fail("Failed to add collection due to network error: " + r.toString(), "网络原因,收藏失败!⚠️"); }) .then(data => { if (data["data"]) { success("Successfully added to collection.", "成功加入收藏,刷新以查看效果!🥳"); } else { fail("Failed to add collection:" + (data["msg"] ? data["msg"] : "Unknown error."), "请求收藏接口失败!⚠️"); } }); } // Simple UI let a = document.createElement("a"); a.text = "⭐"; a.style = "position: absolute;left: 150px;top: 20px;"; a.onclick = add_collect; input.parentElement.appendChild(a); // Shortcut input.addEventListener("keydown", (e) => { if (e.key === 'd' && e.ctrlKey) { e.preventDefault(); add_collect(); } }); } node.onerror = (e) => { fail("Failed to load Aes-js. You won't be able to use \"custom_collection\" feature.", "依赖库加载失败,您将无法使用自定义收藏功能!⚠️"); } document.head.appendChild(node); } let observer = new MutationObserver(callback); observer.observe(document.body, options); } break; } default: console.error("[USTC Helper] Unexpected host: " + window.location.host); break; } })();