// // 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: Encodable { var name: String var oid: String var id: String var xydLink: String var serviceLink: String var studyLink: String init() { name = "-" oid = "-" id = "-" xydLink = "-" serviceLink = "-" studyLink = "-" } } 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) } } } } struct DecodableType: Decodable { let url: String } var Count: Int = 0 func addCompleted(userInfo: UserInfo, action: @escaping (_: UserInfo) -> Void) { Count += 1 if Count == 3 { 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 self } 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 self } 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 print(res) 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) } }