🎉 begin a project

This commit is contained in:
omg-xtao 2023-03-06 14:11:16 +00:00 committed by GitHub
parent bdfbdeccb7
commit fad79bc923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 297 additions and 0 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
FILE_PATH=test.wakeup_schedule
XIAOAI_URL=https://i.ai.mi.com/course-multi/table?ctId=1&userId=2&deviceId=3&sourceName=course-app-browser

3
.gitignore vendored
View File

@ -127,3 +127,6 @@ dmypy.json
# Pyre type checker
.pyre/
# data
*.wakeup_schedule

16
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: main",
"type": "python",
"request": "launch",
"program": "main.py",
"console": "integratedTerminal",
"justMyCode": true
}
]
}

View File

@ -1,2 +1,29 @@
# WakeUp2XiaoAi
A python script to convert WakeUp json to Xiao Ai Schedule.
将 WakeUp 课程表的数据导入到小爱课程表,解决小爱课程表教务导入过于破烂的问题
# 使用方法
## 导出课程表
将 WakeUp 中已经导入好的课程表使用右上方分享按钮 `导出为备份(可导入)文件` 放置于 `main.py` 同目录下,假设此文件名称为:`test.wakeup_schedule`
## 建立课程表
在小爱课程表建立一个课程表,设置好上课时间、总周数、课程节数、课表时间
## 导出编辑链接
在建好的课程表设置页,选择 `PC编辑课表` 导出编辑链接,然后复制链接,假设此链接为:`https://i.ai.mi.com/h5/precache/ai-schedule/#/pceditor?token=1`
## 运行脚本
`python3 main.py`
然后输入 `test.wakeup_schedule`
然后输入 `https://i.ai.mi.com/h5/precache/ai-schedule/#/pceditor?token=1`
回到小爱课程表,发现同步完成。

7
config.py Normal file
View File

@ -0,0 +1,7 @@
import os
from dotenv import load_dotenv
load_dotenv()
FILE_PATH = os.getenv('FILE_PATH')
XIAOAI_URL = os.getenv("XIAOAI_URL")

65
defs/wakeup.py Normal file
View File

@ -0,0 +1,65 @@
import json
from pathlib import Path
from typing import Dict, List
from models.wakeup import Course, CourseInfo
from models.xiaoai import AiCourse
class WakeUp:
def __init__(self, file_path: str) -> None:
while True:
self.file_path = file_path or input("请输入文件路径 (例如 test.wakeup_schedule ): ")
path = Path(self.file_path)
if not path.exists():
print("文件不存在, 请重新输入路径.")
file_path = None
continue
break
self.old_data: List[Course] = []
self.old_data_info: List[CourseInfo] = []
self.old_data_info_map: Dict[int, CourseInfo] = {}
self.weeks_map: Dict[str, List[int]] = {}
self.new_data: List[AiCourse] = []
self.new_data_map: Dict[str, List[AiCourse]] = {}
def load_data(self) -> None:
""" 解析 wakeup 备份文件数据 """
with open(self.file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
self.old_data_info = [CourseInfo(**i) for i in json.loads(lines[3])]
for i in self.old_data_info:
self.old_data_info_map[i.id] = i
self.old_data = [Course(**i) for i in json.loads(lines[4])]
for i in self.old_data:
data = self.weeks_map.get(i.key, [].copy())
data.extend(i.weeks)
self.weeks_map[i.key] = data
# 移除重复 id
temp_keys, old_data = [], []
for i in self.old_data:
if i.key in temp_keys:
continue
old_data.append(i)
temp_keys.append(i.key)
self.old_data = old_data
def get_weeks(self, temp: Course) -> str:
""" 获取当前课程周数 """
return ",".join(list(map(str, self.weeks_map[temp.key])))
def convert_data(self) -> List[AiCourse]:
""" 转换为小爱课程表格式 """
for i in self.old_data:
old_data_info = self.old_data_info_map[i.id]
self.new_data.append(
AiCourse(
name=old_data_info.courseName,
position=i.room,
teacher=i.teacher,
weeks=self.get_weeks(i),
day=i.day,
style=old_data_info.style,
sections=i.sections,
)
)
return self.new_data

81
defs/xiaoai.py Normal file
View File

@ -0,0 +1,81 @@
import base64
import re
from datetime import datetime
from typing import Dict
from httpx import AsyncClient
from models.xiaoai import AiCourse, AiCourseInfo, Table, TabCourse
class XiaoAi:
HEADERS = {
'authority': 'i.ai.mi.com',
'accept': 'application/json',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5',
'access-control-allow-origin': 'true',
'content-type': 'application/json',
'origin': 'https://i.ai.mi.com',
'referer': 'https://i.ai.mi.com/h5/precache/ai-schedule/',
'sec-ch-ua': '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',
}
URL = "https://i.ai.mi.com/course-multi/courseInfo"
TABLE_URL = "https://i.ai.mi.com/course-multi/table"
def __init__(self, url_temp: str) -> None:
while True:
url = url_temp or input("请输入包含 token 的小爱课程表链接 : ")
token = re.search(r"token=(.*)", url)
if not token:
print("链接错误, 请重新输入。")
url_temp = None
continue
decoded_text = base64.b64decode(token[1]).decode('utf-8').split("%26")
self.user_id = int(decoded_text[0])
self.device_id = decoded_text[1]
timestamp = int(decoded_text[2]) / 1000
self.expire_time = datetime.fromtimestamp(timestamp)
self.ct_id = int(decoded_text[3])
break
self.client: AsyncClient = AsyncClient(headers=self.HEADERS)
self.table: Table = None
async def get_all_course(self) -> Table:
""" 获取课程表数据 """
params = {
'ctId': str(self.ct_id),
'userId': str(self.user_id),
'deviceId': self.device_id,
'sourceName': 'course-app-browser',
}
data = await self.client.get(self.TABLE_URL, params=params)
json_data = data.json()
if json_data["code"] != 0:
raise ValueError("课程表不存在或者链接已过期")
self.table = Table(**json_data["data"])
return self.table
async def delete_course(self, course: TabCourse) -> None:
""" 删除某个课程 """
json_data = {
'ctId': self.ct_id,
'userId': self.user_id,
'deviceId': self.device_id,
'cId': course.id,
'sourceName': 'course-app-browser',
}
await self.client.request("DELETE", self.URL, json=json_data)
async def delete_all_course(self) -> None:
""" 删除课程表数据 """
for i in self.table.courses:
await self.delete_course(i)
async def add_course(self, course: AiCourse) -> None:
""" 添加课程 """
info = AiCourseInfo(userId=self.user_id, deviceId=self.device_id, ctId=self.ct_id, course=course)
await self.client.post(self.URL, json=info.dict())

27
main.py Normal file
View File

@ -0,0 +1,27 @@
import asyncio
import warnings
from config import FILE_PATH, XIAOAI_URL
from defs.wakeup import WakeUp
from defs.xiaoai import XiaoAi
warnings.filterwarnings("ignore", category=DeprecationWarning)
async def main():
wakeup = WakeUp(FILE_PATH)
xiaoai = XiaoAi(XIAOAI_URL)
print(f"链接过期时间:{xiaoai.expire_time.strftime('%Y-%m-%d %H:%M:%S')}")
wakeup.load_data()
data = wakeup.convert_data()
table = await xiaoai.get_all_course()
print(f"开始处理课程表 - {table.name}")
print("开始删除课程表数据")
await xiaoai.delete_all_course()
print("开始导入课程表数据")
for i in data:
await xiaoai.add_course(i)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

36
models/wakeup.py Normal file
View File

@ -0,0 +1,36 @@
from typing import List
from pydantic import BaseModel
class Course(BaseModel):
id: int
day: int # 周几
startWeek: int # 开始的周次
endWeek: int # 停止的周次
startNode: int # 开始的节次
step: int # 有多少节
room: str # 位置
teacher: str # 老师
@property
def sections(self) -> str:
data = map(str, list(range(self.startNode, self.startNode + self.step)))
return ",".join(data)
@property
def key(self) -> str:
return f"{self.id}_{self.day}_{self.startNode}_{self.step}"
@property
def weeks(self) -> List[int]:
return list(range(self.startWeek, self.endWeek + 1))
class CourseInfo(BaseModel):
id: int
color: str
courseName: str
@property
def style(self) -> str:
return '{"color":"#FFFFFF", "background":"#' + self.color[3:] + '"}'

30
models/xiaoai.py Normal file
View File

@ -0,0 +1,30 @@
from typing import List
from pydantic import BaseModel
class AiCourse(BaseModel):
name: str
position: str
teacher: str
weeks: str
day: int
style: str
sections: str
class TabCourse(AiCourse):
id: int
class AiCourseInfo(BaseModel):
userId: int
deviceId: str
ctId: int
course: AiCourse
sourceName: str = "course-app-browser"
class Table(BaseModel):
id: int
name: str
courses: List[TabCourse]

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
pydantic==1.10.5
httpx==0.23.3
python-dotenv==1.0.0