南开三年

人马上就要润掉了,这三年我攒了不少 scripts, 值得分享一下,至于“感慨万千”什么的废话别指望我编了。

写在最前面——如何提学分绩

显而易见,如果你大一的课分数比别人低,就算你大二大三学年学分绩前三,排名依然会拉垮。提分只有一条路——拼命上选修课,当然这些课是没法用来提保研用的 ABCD 学分绩的。

选修课不止专门的“通识选修课”——绝大多数是根本不值那 240 块钱的大水课,上起来痛苦万分,事实上分数也不会保证 90+,在还有名额的情况下,可以选别的学院的专业课。比如说,我至今上过通信、电子、人工智能自动化等等的课程,其中一门三学分的通信课程还给了我 99 分。

到了期末感觉没自信拿高分?没关系,可以“期末退课”,也就是缓修。学校的精神科医生十分 obliging, 只要你说你压力大精神不正常,他们就会给你开证明,拿给学院教学办,填个表,no questions asked. 缓修同样会在成绩单里被标注为“退课”,也就是说面试的时候有可能被问到,但好歹不影响学分绩。选修课缓修后就不用再重新选了。

接下来是纯技术性内容

tty 联网

没有 GUI? 该死的 NKU_WLAN 网关登录页面非得要 JavaScript? 其实根本没那么麻烦。

curl "http://202.113.18.106:801/eportal/?c=ACSetting&a=Login&loginMethod=1&protocol=http%3A&hostname=202.113.18.106&port=&iTermType=1&wlanuserip=$(ip -f inet addr show wlp0s20f3 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p')&wlanacip=null&wlanacname=zx_&redirect=null&session=null&vlanid=0&mac=00-00-00-00-00-00&ip=$(ip -f inet addr show wlp0s20f3 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p')&enAdvert=0&jsVersion=2.4.3&DDDDD=学号&upass=密码&R1=0&R2=0&R3=0&R6=0¶=00&0MKKey=123456&buttonClicked=&redirect_url=&err_flag=&username=&password=&user=&cmd=&Login="

Some observations:

  1. GET 无加密明文传输?Someone in the middle could exploit the fact…
  2. 连续输错不会被 ban, 你猜猜这能用来干什么?
  3. 我用 $(ip -f inet addr show wlp0s20f3 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p') 获取分配的 IP 地址,你的网卡名字可能不一样。
  4. 当然也可以改成 requests 综合到其他 Python scripts 里。

SSO 登录

使用 Selenium 实现,麻烦之处在于那个滑块。

try:
	driver.find_element(By.CLASS_NAME, 'input.input.n').send_keys(username)
	driver.find_element(By.CLASS_NAME, 'input.input.p').send_keys(password)
		
	slider = driver.find_element(By.ID, 'btn')
	slide = webdriver.ActionChains(driver)
	slide.click_and_hold(slider).move_by_offset(250, 0).release().perform()
	driver.find_element(By.ID, 'submitRole').click()

提示:登上去之后不一定继续得用 Selenium,读取一下 cookies 之类的,然后就可以用别的网络库了。

eamis 自动查分

警告:eamis 网站的结构经常改变,而且每学期你的学分绩的 XPath 肯定都不一样(已修学分加了一行)。

(前面的是 SSO 登录部分)

while (credits == PREV_CREDITS):
	time.sleep(1200)

	#driver.get('https://eamis.nankai.edu.cn/eams/home.action')
	driver.get('https://webvpn.nankai.edu.cn/https/77726476706e69737468656265737421f5f64c95347e6651700388a5d6502720dc08a5/eams/home.action')
	WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.PARTIAL_LINK_TEXT, '我的成绩')))
	driver.find_element(By.PARTIAL_LINK_TEXT, '我的成绩').click()

	WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.CLASS_NAME, 'toolbar-icon.action-default')))
	driver.find_element(By.CLASS_NAME, 'toolbar-icon.action-default').click()

	WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.XPATH, '/html/body/table/tbody/tr/td[3]/div/div/div[3]/table[2]/tbody/tr[9]/th')))
	response = driver.find_element(By.XPATH, '/html/body/table/tbody/tr/td[3]/div/div/div[3]/table[2]/tbody/tr[9]/th').text
			
	score = response[response.find(':')+1:response.find(' /')]
	credits = driver.find_element(By.XPATH, '/html/body/table/tbody/tr/td[3]/div/div/div[3]/table[2]/tbody/tr[8]/th[3]').text

# ...
	
from pydub import AudioSegment
from pydub.playback import play

play(AudioSegment.from_wav('/media/d/Audio/Sounds/bell.wav'))

import tkinter as tk
from tkinter import messagebox

root = tk.Tk()
root.withdraw()
messagebox.showinfo('GPA', '出分了!%s' % score)

抢课

import threading
import requests
import lxml.html
import sys
from time import sleep
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
    'Referer': 'https://eamis.nankai.edu.cn/eams/stdElectCourse!defaultPage.action?electionProfile.id=1470',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest',
    'Origin': 'https://eamis.nankai.edu.cn',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Sec-Fetch-Dest': 'empty',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Site': 'same-origin'
}

params = {
    'profileId': '1470',
}

ids = {
	# '1': '577323',
	'2': '582777',
	# '3': '581667',
	'4': '582191',
	'5': '582690',
	'6': '581485'
}

def get_data(id):
	return {
		'optype': 'true',
		'operator0': f'{id}:true:0',
		'lesson0': id,
		f'expLessonGroup_{id}': 'undefined',
	}

cookies = {
    'semester.id': '4263',
    'srv_id': '45e1a8e1244b5f919fa64489a5313cf7',
    'JSESSIONID': '85926576E34B0B91CD68A68CCCB64D6A.std6',
}

def submit(data):
	while True:
		response = requests.post(
			'https://eamis.nankai.edu.cn/eams/stdElectCourse!batchOperator.action',
			params=params,
			cookies=cookies,
			headers=headers,
			data=data,
			verify=False
		)
		html = lxml.html.fromstring(response.text)
		msg = str(html.text_content()).strip()
		print(msg[:msg.find('\n')])
		sleep(0.44)

if __name__ == '__main__':
	# threads = [
	# 	threading.Thread(target=submit, args=(get_data(id),))
	# 	for id in ids.values()
	# ]
	# for thread in threads:
	# 	thread.start()
	# for thread in threads:
	# 	thread.join()
	data = get_data('581485')
	submit(data)

图书馆抢座

你猜猜什么样的座位需要写 script 来抢?

获取 cookie 和 token

cookie = driver.get_cookie('ic-cookie')['value']
token = driver.execute_script('return JSON.parse(sessionStorage.getItem("userInfo")).token')

获取座位编号

身为一名合格的前端开发人员,你应该知道怎么用 dev tools 记录网络请求并分析其参数,然后写成 curl 或者 requests.

开抢!

稍安勿躁,建议 sleep 到 22.00.00 再启动你的 worker processes, 图书馆服务器的时钟有点慢,我现在只能靠刚开始一瞬间的高并发来抢到座位。

计算平均分和 GPA

注:只适用于 22 级及以前。

#!/usr/bin/python3

import sys

def get_grade_point(mark: float) -> float:
	if mark >= 90.:
		return 4.
	elif mark >= 85.:
		return 3.7
	elif mark >= 81.:
		return 3.3
	elif mark >= 78.:
		return 3.
	elif mark >= 75.:
		return 2.7
	elif mark >= 72.:
		return 2.3
	elif mark >= 69.:
		return 2.
	elif mark >= 66.:
		return 1.7
	elif mark >= 63.:
		return 1.3
	elif mark >= 60.:
		return 1.
	else:
		return 0.

def get_scores(filename: str) -> list[tuple[float, float]]:
	with open(filename) as f:
		lines = [l for line in f.readlines() if (l := line.strip())]
	return [(float(line.split()[0]), float(line.split()[1])) for line in lines]

if __name__ == '__main__':
	option = sys.argv[1]
	score_filename = sys.argv[2]
	scores = get_scores(score_filename)
	if option == 'gpa':
		grade_points = [(credit, get_grade_point(mark)) for credit, mark in scores]
		credit_sum = sum((credit for credit, _ in grade_points))
		grade_point_sum = sum((credit * grade_point for credit, grade_point in grade_points))
		print(f'Total credits: {credit_sum}')
		print(f'GPA: {grade_point_sum / credit_sum}')
	else:
		credit_sum = sum((credit for credit, _ in scores))
		mark_sum = sum((credit * mark for credit, mark in scores))
		print(f'Total credits: {credit_sum}')
		print(f'Average mark: {mark_sum / credit_sum}')

各种论文的 LaTeX 模板

刚写下这个标题我才想到,全校应该只有我一个人坚决抵制 Word 和 PPT 的。需要的可以邮件联系我。

写在最后

一时半会儿能在手边找到的就是这些,当然还有很多因为一月份硬盘坏掉而丢失的(比如我大一处理物理实验的程序,主要是误差计算),或者 obsolete 的。以后发现了再慢慢补上。