// ==UserScript==
// @name SUSTech tis cheater
// @namespace https://blog.vollate.top/
// @version 1.3.3
// @description SUSTech 可能会变质,但绝对不会倒闭
// @author Vollate
// @match https://tis.sustech.edu.cn/*
// @icon https://www.sustech.edu.cn/static/images/favicon.ico
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @require https://s4.zstatic.net/ajax/libs/jquery/3.7.1/jquery.min.js
// @run-at document-end
// ==/UserScript==
(function ($) {
$(document).ready(function () {
'use strict';
const DEFAULT_INTERVAL = 1600;
const language = navigator.language || navigator.userLanguage;
const platform = navigator.platform;
const ua = navigator.userAgent;
let secChUa = "";
let secChUaMobile = "";
let secChUaPlatform = "";
if (navigator.userAgentData) {
secChUa = navigator.userAgentData.brands.map(b => `"${b.brand}";v="${b.version}"`).join(", ");
secChUaMobile = navigator.userAgentData.mobile ? "?1" : "?0";
secChUaPlatform = navigator.userAgentData.platform;
} else {
secChUa = `"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"`;
secChUaMobile = "?0";
secChUaPlatform = platform;
}
const FetchHeaders = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": language,
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Host": "tis.sustech.edu.cn",
"Origin": "https://tis.sustech.edu.cn",
"Referer": "https://tis.sustech.edu.cn/Xsxk/query/1",
"RoleCode": "01",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"X-Requested-With": "XMLHttpRequest",
"sec-ch-ua": secChUa,
"sec-ch-ua-mobile": secChUaMobile,
"sec-ch-ua-platform": secChUaPlatform
};
const CSSManager = {
modalOverlay: {
position: 'fixed',
top: '0',
left: '0',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
zIndex: '1000'
}, modal: {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: '#f9f9f9',
padding: '20px',
borderRadius: '10px',
boxShadow: '0 5px 15px rgba(0, 0, 0, 0.3)',
zIndex: '9999',
maxWidth: '400px',
width: '100%'
}, modalTitle: {
margin: '0 0 20px 0',
fontSize: '20px',
textAlign: 'center',
color: '#333',
borderBottom: '2px solid #ddd',
paddingBottom: '10px'
}, courseLine: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '12px',
padding: '10px',
backgroundColor: '#fff',
borderRadius: '5px',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
fontSize: '16px',
color: '#555'
}, deleteButton: {
padding: '5px 10px',
backgroundColor: '#e74c3c',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontSize: '14px'
}, closeButton: {
display: 'block',
margin: '20px auto 0',
padding: '10px 20px',
backgroundColor: '#3498db',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontSize: '16px'
}, confirmButton: {
display: 'block',
margin: '20px auto 0',
padding: '10px 20px',
backgroundColor: '#0cdf24',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontSize: '16px'
}, popupNotification: {
position: 'fixed',
top: '20px',
right: '20px',
backgroundColor: '#01af15',
color: '#fff',
padding: '10px 20px',
borderRadius: '5px',
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
zIndex: '1001',
fontSize: '16px',
opacity: '0.9',
display: 'flex',
alignItems: 'center'
}, popupErrorNotification: {
position: 'fixed',
top: '20px',
right: '20px',
backgroundColor: '#af012a',
color: '#fff',
padding: '10px 20px',
borderRadius: '5px',
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
zIndex: '1001',
fontSize: '16px',
opacity: '0.9',
display: 'flex',
alignItems: 'center'
}, messageContainer: {
position: 'fixed',
bottom: '20px',
right: '20px',
width: '300px',
maxHeight: '200px',
backgroundColor: '#f1f1f1',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
padding: '10px',
zIndex: '1001',
fontSize: '14px',
color: '#333',
display: 'flex',
flexDirection: 'column'
}, messageHeader: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
borderBottom: '2px solid #ddd',
paddingBottom: '8px',
marginBottom: '10px',
fontWeight: 'bold',
position: 'sticky',
top: '0',
backgroundColor: '#f1f1f1',
zIndex: '1001'
}, timeInput: {
width: '60px', padding: '5px', marginRight: '10px', fontSize: '16px'
}
};
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
function showAsyncPopup(message, css_style, duration) {
let $popup = $('
').css(css_style);
$popup.text(message);
$('body').append($popup);
setTimeout(() => {
$popup.fadeOut(300, function () {
$(this).remove();
});
}, duration);
}
function getGMAry_(key) {
var str = GM_getValue(key);
return str ? JSON.parse(str) : [];
}
function appendGMAry_(key, val) {
var vals = getGMAry_(key);
vals.push(val);
GM_setValue(key, JSON.stringify(vals));
}
function clearGM_(key) {
GM_setValue(key, undefined)
}
function enableAllButtons_() {
var allButtons = document.querySelectorAll('button');
var targetButtons = Array.prototype.filter.call(allButtons, function (button) {
var span = button.querySelector('span');
return span && span.textContent === '选课' && button.hasAttribute('disabled');
});
targetButtons.forEach(function (button) {
button.removeAttribute('disabled');
});
}
function xhrBodyToFetchBody(body) {
if (body instanceof FormData) {
return body;
} else if (typeof body === 'string') {
try {
const json = JSON.parse(body);
return JSON.stringify(json);
} catch (e) {
return body;
}
} else if (body instanceof ArrayBuffer || body instanceof Blob) {
return body;
} else if (typeof body === 'object' && body !== null) {
return JSON.stringify(body);
}
return null;
}
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
this._url = url;
originalXHROpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
if (this._url.endsWith('Xsxk/addGouwuche')) {
this.addEventListener('readystatechange', function () {
if (this.readyState === 4) {
new Promise(resolve => setTimeout(resolve, 4000)).then(() => {
fetch(this._url, {
method: 'POST',
headers: FetchHeaders,
body: xhrBodyToFetchBody(body),
credentials: 'include'
}).then(res => res.json()).then(data => {
console.log(data);
let courseName = data.message.split('课程:')[1];
showAsyncPopup("Add course \"" + courseName + "\" to course list", CSSManager.popupNotification, 5000);
appendGMAry_('SelectedCourses', {name: courseName, body: xhrBodyToFetchBody(body)});
}).catch(err => {
console.error(err);
showAsyncPopup("Failed to add course to course list", CSSManager.popupErrorNotification, 5000);
})
});
}
});
}
return originalXHRSend.apply(this, arguments);
};
function createMessageContainer() {
let $container = $('
').css(CSSManager.messageContainer);
let $header = $('
').css(CSSManager.messageHeader);
$header.text('Messages');
let $closeButton = $('
').text('×').css({
cursor: 'pointer', fontSize: '18px', fontWeight: 'bold', lineHeight: '1'
});
$closeButton.on('click', function () {
$container.remove();
});
$header.append($closeButton);
$container.append($header);
$container.append($('').css({
flex: '1', overflowY: 'auto'
}));
$('body').append($container);
}
function appendMessage(courseName) {
let $message = $('
').text('Success: ' + courseName).css({
padding: '5px 0', borderBottom: '1px solid #ddd'
});
$('#messageContent').append($message);
// $('#messageContent').scrollTop($('#messageContent')[0].scrollHeight);
}
async function startRace_(targetCourses, interval) {
if (targetCourses.length === 0) {
alert('Your course list is empty, you need to add at least one course');
return;
}
showAsyncPopup('SUSTech tis cheater: start', CSSManager.popupNotification, 5000);
if ($('#messageContainer').length === 0) {
createMessageContainer();
}
while (GM_getValue('Start') && targetCourses.length > 0) {
for (let i = 0; i < targetCourses.length; i++) {
fetch('https://tis.sustech.edu.cn/Xsxk/addGouwuche', {
method: 'POST', headers: FetchHeaders, body: targetCourses[i].body, credentials: 'include'
}).then(res => res.json())
.then(data => {
if (data.jg === '1') {
console.log('Success: ' + targetCourses[i].name);
appendMessage(targetCourses[i].name);
targetCourses.splice(i, 1);
updateCourses(targetCourses);
}
});
await new Promise(resolve => setTimeout(resolve, interval));
}
}
showAsyncPopup('SUSTech tis cheater: stop', CSSManager.popupNotification, 5000);
}
function updateCourses(courses) {
GM_setValue('SelectedCourses', JSON.stringify(courses));
}
let hadInit = false;
async function initMenu() {
if (hadInit) {
return;
}
hadInit = true;
GM_registerMenuCommand("Start", () => {
GM_setValue('Start', '1');
startRace_(getGMAry_('SelectedCourses'), GM_getValue('Interval', DEFAULT_INTERVAL));
});
GM_registerMenuCommand("Schedule Start", () => {
const $modalOverlay = $('
')
.css(CSSManager.modalOverlay)
.attr('id', 'schedule-modal-overlay');
const $modal = $('
')
.css(CSSManager.modal)
.attr('id', 'schedule-modal');
const $title = $('
')
.text('Schedule Start')
.css(CSSManager.modalTitle);
const $inputGroup = $('')
.css({
display: 'flex', justifyContent: 'space-around', margin: '20px 0'
});
const $hourInput = $('
')
.attr({
type: 'number', placeholder: 'HH', min: '0', max: '23'
})
.css(CSSManager.timeInput);
const $minuteInput = $('
')
.attr({
type: 'number', placeholder: 'MM', min: '0', max: '59'
})
.css(CSSManager.timeInput);
const $secondInput = $('
')
.attr({
type: 'number', placeholder: 'SS', min: '0', max: '59'
})
.css(CSSManager.timeInput);
$inputGroup.append($hourInput, $minuteInput, $secondInput);
const $confirmButton = $('