//
// account.swift
// 74 桌
//
// Created by yunli on 2023/5/20.
//
import Alamofire
import SwiftUI
struct Constant {
var cookiesDefaultsKey: String
}
let Constants: Constant = .init(cookiesDefaultsKey: "JSESSIONID")
class CookieHandler {
static let shared: CookieHandler = .init()
let defaults = UserDefaults.standard
let cookieStorage = HTTPCookieStorage.shared
func getCookie(forURL url: String) -> [HTTPCookie] {
let computedUrl = URL(string: url)
let cookies = cookieStorage.cookies(for: computedUrl!) ?? []
return cookies
}
func getAllCookie() -> [HTTPCookie] {
let cookies = cookieStorage.cookies ?? []
return cookies
}
func delCookie() {}
}
struct Login: Encodable {
let user: String
let pwd: String
let cook: String
}
struct UserInfo: Decodable {
var name: String
var oid: String
var id: String
var xydLink: String
var serviceLink: String
var studyLink: String
var tableData: [PurpleDatum]
init() {
name = "-"
oid = "-"
id = "-"
xydLink = "-"
serviceLink = "-"
studyLink = "-"
tableData = []
}
}
class LoginHandler {
static let sharedInstance = LoginHandler()
func getCookie(name: String) -> [HTTPCookie] {
return CookieHandler().getCookie(forURL: "https://debug.sdsz.icu:81/").filter { cookie -> Bool in
cookie.name == name
}
}
func getAllCookie(name: String) -> [HTTPCookie] {
return CookieHandler().getAllCookie().filter { cookie -> Bool in
cookie.name == name
}
}
func fetchLoginCookie(action: @escaping (_: String) -> Void) {
if let cookie = getCookie(name: "JSESSIONID").first {
HTTPCookieStorage.shared.deleteCookie(cookie)
print(cookie)
}
let url = "https://debug.sdsz.icu:81/sso/login"
AF.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).response { _ in
if let cookie = self.getCookie(name: "JSESSIONID").first {
action(cookie.value)
}
}
}
}
class FetchHandler {
static let sharedInstance = FetchHandler()
func matches(url: String) -> Bool {
do {
return try url.matches(of: Regex(#"(^\[\]$|param":"|"workRests"|
|DOCTYPE|in\?username=|requestParams|\"newmessage\"|Sorry, Page Not Found|北师大实验中学--登录|404 Not Found|502 Bad Gateway)"#)).count != 0
} catch {
return true
}
}
func getUrl(url: String) -> String {
var ret: String = url
ret = ret
.replacing("dd.sdsz.com.cn", with: "debug.sdsz.icu:81")
.replacing(/^http:/, with: "https:")
.replacing("service=http%3A%2F%2Fdebug.sdsz.icu:81", with: "service=http%3A%2F%2Fdd.sdsz.com.cn")
print("URL: \(ret)")
return ret
}
func workFetch(url: String, action: @escaping (_: String) -> Void) {
if matches(url: url) {
action(url)
} else {
AF.request(getUrl(url: url), method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseString { res in
if let resv = res.value {
self.workFetch(url: resv, action: action)
}
}
}
}
func fetchAny(url: String, action: @escaping (_: String) -> Void) {
let urlFull = "https://debug.sdsz.icu:81/\(url)"
workFetch(url: urlFull, action: action)
}
}
func doLogin(user: String, password: String, action: @escaping (_: Int) -> Void) {
LoginHandler().fetchLoginCookie { (cookie: String) in
let login = Login(user: user, pwd: password, cook: cookie)
print(login)
AF.request("https://debug.sdsz.icu/andlogin", method: .post, parameters: login, encoder: JSONParameterEncoder.default, headers: nil).responseString { res in
print("\(res)")
if res.value == "success" {
action(1)
} else {
action(0)
}
}
}
}
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let workRests: [WorkREST]
let blocks: [Block]
}
// MARK: - Block
struct Block: Codable {
let ownObjID, type: String
let name: Name
let data: [BlockDatum]
let weekMetas: JSONNull?
enum CodingKeys: String, CodingKey {
case ownObjID = "ownObjId"
case type, name, data, weekMetas
}
}
// MARK: - BlockDatum
struct BlockDatum: Codable {
let week: Int
let weekMeta: WeekMeta
let data: [PurpleDatum]
}
// MARK: - PurpleDatum
struct PurpleDatum: Codable {
let day, lesson: Int
let data: [FluffyDatum]
}
// MARK: - FluffyDatum
struct FluffyDatum: Codable {
let timetableID: JSONNull?
let className: Name
let classID: ClassID
let courseName: String
let courseID, courseType: Int
let placeID: JSONNull?
let placeName: PlaceName
let placeSn: PlaceSn
let teacher: [Teacher]
let room: JSONNull?
let start, end: String
let timeScope: Int
enum CodingKeys: String, CodingKey {
case timetableID = "timetableId"
case className
case classID = "classId"
case courseName
case courseID = "courseId"
case courseType
case placeID = "placeId"
case placeName, placeSn, teacher, room, start, end, timeScope
}
}
enum ClassID: String, Codable {
case ac342897 = "AC_342897"
case tac8109 = "TAC_8109"
case tac8123 = "TAC_8123"
}
enum Name: String, Codable {
case 高一数学P1班 = "高一数学[P]1班"
case 高一物理A5班 = "高一物理[A]5班"
case 高中2025届11班
}
enum PlaceName: String, Codable {
case 信毅楼408 = "信毅楼4-08"
case 信毅楼412 = "信毅楼4-12"
case 本班教室
}
enum PlaceSn: String, Codable {
case bxXy408 = " BX-XY-408 "
case bxXy410 = " BX-XY-410 "
case empty = ""
}
// MARK: - Teacher
struct Teacher: Codable {
let userID: Int
let userName: JSONNull?
let fullName: String
let userNo: JSONNull?
enum CodingKeys: String, CodingKey {
case userID = "userId"
case userName, fullName, userNo
}
}
// MARK: - WeekMeta
struct WeekMeta: Codable {
let morningRead: Bool
let night, afternoon, morning, teachingDay: Int
let time: [Time]
}
// MARK: - Time
struct Time: Codable {
let seq: Int
let start, end: String
}
// MARK: - WorkREST
struct WorkREST: Codable {
let beginTime, endTime, time, lessonName: String
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (_: JSONNull, _: JSONNull) -> Bool {
return true
}
func hash(into _: inout Hasher) {}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
var Count: Int = 0
func addCompleted(userInfo: UserInfo, action: @escaping (_: UserInfo) -> Void) {
Count += 1
if Count == 3 {
print(userInfo.tableData)
action(userInfo)
}
}
extension String {
func replace(regex: String, with: String, options: NSRegularExpression.Options) -> String {
do {
let RE = try NSRegularExpression(pattern: regex, options: options)
let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
return modified
} catch {
return "ERR"
}
}
func replace(regex: String, with: String) -> String {
do {
let RE = try NSRegularExpression(pattern: regex)
let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
return modified
} catch {
return "ERR"
}
}
}
struct TableParam: Encodable {
var oid: String
}
func getUserInfo(userInfoIn: UserInfo, action: @escaping (_: UserInfo) -> Void) {
var userInfo: UserInfo = userInfoIn
FetchHandler().fetchAny(url: "bxn-portal/portal/osforstudent/index") { res in
if let mat = res.firstMatch(of: /userFullName" value="(.*?)"/) {
userInfo.name = "\(mat.1)"
}
if let mat = res.firstMatch(of: /userId" value="(.*?)"/) {
userInfo.oid = "\(mat.1)"
}
if let mat = res.firstMatch(of: /'https:\/\/service.*?'/) {
userInfo.serviceLink = "\(mat.output.split(separator: "'")[0])"
userInfo.studyLink = mat.output.split(separator: "'")[0] + "%2Ffe-pc%2Fb%2Ffe_leco_student%2Fportal%2F%3Fsystem_partition_gId%3D3"
}
print(userInfo)
let tableParam: TableParam = .init(oid: userInfo.oid)
let header: HTTPHeaders = [
"user": userInfo.id,
]
AF.request("https://debug.sdsz.icu/getWeek", method: .post, parameters: tableParam, encoder: JSONParameterEncoder.default, headers: header).responseString { res in
FetchHandler().fetchAny(url: "bxn-timetable/timetable/monitor/homepage/data/student?studentId=\(userInfo.oid)&dateScope=\(res.value ?? "")&_=0") { res in
do {
let data = res.data(using: String.Encoding.utf8)
let table = try! JSONSerialization.jsonObject(with: data!) as? [String: Any]
let decoder = JSONDecoder()
let kkd = try decoder.decode(Welcome.self, from: data!)
userInfo.tableData = kkd.blocks.first?.data.first?.data ?? []
} catch {
print(error)
}
addCompleted(userInfo: userInfo, action: action)
}
}
addCompleted(userInfo: userInfo, action: action)
}
FetchHandler().fetchAny(url: "bxn-library/library/jumpExamreport?jumpUrl=http://36.112.23.77/analysis/auto/%23/autoLogin") { res in
userInfo.xydLink = res
addCompleted(userInfo: userInfo, action: action)
}
}
struct loginView: View {
@State var username: String = ""
@State var password: String = ""
@State var btnText: String = "登录"
@State var btnColor: Color = .blue
@State var inProgress: Bool = false
@FocusState private var isFocused: Bool
@Binding var isLoggedIn: Int
@Binding var userInfo: UserInfo
var body: some View {
VStack {
Form {
List {
HStack {
Image(systemName: "person.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
TextField(text: $username, prompt: Text("数字校园号")) {
Text("数字校园号")
}.focused($isFocused)
.keyboardType(.numberPad)
}
HStack {
Image(systemName: "key.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
SecureField(text: $password, prompt: Text("密码")) {
Text("密码")
}.focused($isFocused)
}
}.onTapGesture {
isFocused = false
}
HStack {
Spacer()
Button(action: {
inProgress = true
doLogin(user: username, password: password) { (ret: Int) in
isLoggedIn = ret
inProgress = false
if ret == 0 {
btnText = "登录失败"
btnColor = .red
} else {
userInfo.id = username
getUserInfo(userInfoIn: userInfo) { res in
userInfo = res
}
}
}
}) {
HStack {
Text(btnText)
if inProgress {
ProgressView()
} else {
Image(systemName: "chevron.right")
}
}
}.foregroundColor(btnColor).buttonStyle(.bordered)
}
}
}
}
}
struct accountView: View {
@State var username: String = ""
@State var password: String = ""
@FocusState private var isFocused: Bool
@Binding var isLoggedIn: Int
@Binding var userInfo: UserInfo
func delAllCookie(name: String) {
for cookie in LoginHandler().getAllCookie(name: name) {
print(cookie)
HTTPCookieStorage.shared.deleteCookie(cookie)
}
}
var body: some View {
VStack {
Form {
Section(header: Text("基本信息")) {
HStack {
Text("姓名")
Spacer()
Text(userInfo.name).foregroundColor(.gray)
}
HStack {
Text("内部 ID")
Spacer()
Text(userInfo.oid).foregroundColor(.gray)
}
}
Section {
NavigationLink(destination: aboutView()) {
Text("关于")
}
}
Section {
Button(action: {
isLoggedIn = 0
delAllCookie(name: "CASTGC")
delAllCookie(name: "JSESSIONID")
}) {
VStack {
Text("退出").foregroundColor(.red)
}
}
}
}
Spacer()
}
}
}
struct aboutView: View {
var body: some View {
List {
Section(footer: Text("实验中学 74 桌是一款面向北师大附属实验中学学生的 App,提供查分及数字校园基础服务。")) {
Text("实验中学 74 桌")
HStack {
Text("开发者")
Spacer()
Text("74 开发组").foregroundColor(.gray)
}
}
}.navigationBarTitle("关于")
}
}
struct accountView_Previews: PreviewProvider {
@State static var isLoggedIn = 1
@State static var userInfo: UserInfo = .init()
static var previews: some View {
loginView(isLoggedIn: $isLoggedIn, userInfo: $userInfo)
}
}