// // 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: String 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: String let classID: String let courseName: String let courseID, courseType: Int let placeID: JSONNull? let placeName: String let placeSn: String 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 } } // 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!) let pp = kkd.blocks.first?.data.first?.data ?? [] var ret: [[PurpleDatum]] = [], mx: Int = 0 for i in 0 ..< pp.count { print(pp[i]) mx = max(mx, pp[i].lesson) } for i in 0 ... mx { ret.append([]) for j in 0 ... 5 { ret[i].append(PurpleDatum(day: 0, lesson: 0, data: [])) } } for i in 0 ..< pp.count { ret[pp[i].lesson][pp[i].day] = pp[i] } userInfo.tableData = ret } 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) } }