account.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. //
  2. // account.swift
  3. // 74 桌
  4. //
  5. // Created by yunli on 2023/5/20.
  6. //
  7. import Alamofire
  8. import SwiftUI
  9. struct Constant {
  10. var cookiesDefaultsKey: String
  11. }
  12. let Constants: Constant = .init(cookiesDefaultsKey: "JSESSIONID")
  13. class CookieHandler {
  14. static let shared: CookieHandler = .init()
  15. let defaults = UserDefaults.standard
  16. let cookieStorage = HTTPCookieStorage.shared
  17. func getCookie(forURL url: String) -> [HTTPCookie] {
  18. let computedUrl = URL(string: url)
  19. let cookies = cookieStorage.cookies(for: computedUrl!) ?? []
  20. return cookies
  21. }
  22. func getAllCookie() -> [HTTPCookie] {
  23. let cookies = cookieStorage.cookies ?? []
  24. return cookies
  25. }
  26. func delCookie() {}
  27. }
  28. struct Login: Encodable {
  29. let user: String
  30. let pwd: String
  31. let cook: String
  32. }
  33. struct UserInfo: Encodable {
  34. var name: String
  35. var oid: String
  36. var id: String
  37. var xydLink: String
  38. var serviceLink: String
  39. var studyLink: String
  40. init() {
  41. name = "-"
  42. oid = "-"
  43. id = "-"
  44. xydLink = "-"
  45. serviceLink = "-"
  46. studyLink = "-"
  47. }
  48. }
  49. class LoginHandler {
  50. static let sharedInstance = LoginHandler()
  51. func getCookie(name: String) -> [HTTPCookie] {
  52. return CookieHandler().getCookie(forURL: "https://debug.sdsz.icu:81/").filter { cookie -> Bool in
  53. cookie.name == name
  54. }
  55. }
  56. func getAllCookie(name: String) -> [HTTPCookie] {
  57. return CookieHandler().getAllCookie().filter { cookie -> Bool in
  58. cookie.name == name
  59. }
  60. }
  61. func fetchLoginCookie(action: @escaping (_: String) -> Void) {
  62. if let cookie = getCookie(name: "JSESSIONID").first {
  63. HTTPCookieStorage.shared.deleteCookie(cookie)
  64. print(cookie)
  65. }
  66. let url = "https://debug.sdsz.icu:81/sso/login"
  67. AF.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).response { _ in
  68. if let cookie = self.getCookie(name: "JSESSIONID").first {
  69. action(cookie.value)
  70. }
  71. }
  72. }
  73. }
  74. class FetchHandler {
  75. static let sharedInstance = FetchHandler()
  76. func matches(url: String) -> Bool {
  77. do {
  78. return try url.matches(of: Regex(#"(^\[\]$|param":"|"workRests"|<tr>|DOCTYPE|in\?username=|requestParams|\"newmessage\"|Sorry, Page Not Found|北师大实验中学--登录|404 Not Found|502 Bad Gateway)"#)).count != 0
  79. } catch {
  80. return true
  81. }
  82. }
  83. func getUrl(url: String) -> String {
  84. var ret: String = url
  85. ret = ret
  86. .replacing("dd.sdsz.com.cn", with: "debug.sdsz.icu:81")
  87. .replacing(/^http:/, with: "https:")
  88. .replacing("service=http%3A%2F%2Fdebug.sdsz.icu:81", with: "service=http%3A%2F%2Fdd.sdsz.com.cn")
  89. print("URL: \(ret)")
  90. return ret
  91. }
  92. func workFetch(url: String, action: @escaping (_: String) -> Void) {
  93. if matches(url: url) {
  94. action(url)
  95. } else {
  96. AF.request(getUrl(url: url), method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseString { res in
  97. if let resv = res.value {
  98. self.workFetch(url: resv, action: action)
  99. }
  100. }
  101. }
  102. }
  103. func fetchAny(url: String, action: @escaping (_: String) -> Void) {
  104. let urlFull = "https://debug.sdsz.icu:81/\(url)"
  105. workFetch(url: urlFull, action: action)
  106. }
  107. }
  108. func doLogin(user: String, password: String, action: @escaping (_: Int) -> Void) {
  109. LoginHandler().fetchLoginCookie { (cookie: String) in
  110. let login = Login(user: user, pwd: password, cook: cookie)
  111. print(login)
  112. AF.request("https://debug.sdsz.icu/andlogin", method: .post, parameters: login, encoder: JSONParameterEncoder.default, headers: nil).responseString { res in
  113. print("\(res)")
  114. if res.value == "success" {
  115. action(1)
  116. } else {
  117. action(0)
  118. }
  119. }
  120. }
  121. }
  122. struct DecodableType: Decodable { let url: String }
  123. var Count: Int = 0
  124. func addCompleted(userInfo: UserInfo, action: @escaping (_: UserInfo) -> Void) {
  125. Count += 1
  126. if Count == 3 {
  127. action(userInfo)
  128. }
  129. }
  130. extension String {
  131. func replace(regex: String, with: String, options: NSRegularExpression.Options) -> String {
  132. do {
  133. let RE = try NSRegularExpression(pattern: regex, options: options)
  134. let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
  135. return self
  136. } catch {
  137. return "ERR"
  138. }
  139. }
  140. func replace(regex: String, with: String) -> String {
  141. do {
  142. let RE = try NSRegularExpression(pattern: regex)
  143. let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
  144. return self
  145. } catch {
  146. return "ERR"
  147. }
  148. }
  149. }
  150. struct TableParam: Encodable {
  151. var oid: String
  152. }
  153. func getUserInfo(userInfoIn: UserInfo, action: @escaping (_: UserInfo) -> Void) {
  154. var userInfo: UserInfo = userInfoIn
  155. FetchHandler().fetchAny(url: "bxn-portal/portal/osforstudent/index") { res in
  156. if let mat = res.firstMatch(of: /userFullName" value="(.*?)"/) {
  157. userInfo.name = "\(mat.1)"
  158. }
  159. if let mat = res.firstMatch(of: /userId" value="(.*?)"/) {
  160. userInfo.oid = "\(mat.1)"
  161. }
  162. if let mat = res.firstMatch(of: /'https:\/\/service.*?'/) {
  163. userInfo.serviceLink = "\(mat.output.split(separator: "'")[0])"
  164. userInfo.studyLink = mat.output.split(separator: "'")[0] + "%2Ffe-pc%2Fb%2Ffe_leco_student%2Fportal%2F%3Fsystem_partition_gId%3D3"
  165. }
  166. print(userInfo)
  167. let tableParam: TableParam = .init(oid: userInfo.oid)
  168. let header: HTTPHeaders = [
  169. "user": userInfo.id,
  170. ]
  171. AF.request("https://debug.sdsz.icu/getWeek", method: .post, parameters: tableParam, encoder: JSONParameterEncoder.default, headers: header).responseString { res in
  172. FetchHandler().fetchAny(url: "bxn-timetable/timetable/monitor/homepage/data/student?studentId=\(userInfo.oid)&dateScope=\(res.value ?? "")&_=0") { res in
  173. print(res)
  174. addCompleted(userInfo: userInfo, action: action)
  175. }
  176. }
  177. addCompleted(userInfo: userInfo, action: action)
  178. }
  179. FetchHandler().fetchAny(url: "bxn-library/library/jumpExamreport?jumpUrl=http://36.112.23.77/analysis/auto/%23/autoLogin") { res in
  180. userInfo.xydLink = res
  181. addCompleted(userInfo: userInfo, action: action)
  182. }
  183. }
  184. struct loginView: View {
  185. @State var username: String = ""
  186. @State var password: String = ""
  187. @State var btnText: String = "登录"
  188. @State var btnColor: Color = .blue
  189. @State var inProgress: Bool = false
  190. @FocusState private var isFocused: Bool
  191. @Binding var isLoggedIn: Int
  192. @Binding var userInfo: UserInfo
  193. var body: some View {
  194. VStack {
  195. Form {
  196. List {
  197. HStack {
  198. Image(systemName: "person.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
  199. TextField(text: $username, prompt: Text("数字校园号")) {
  200. Text("数字校园号")
  201. }.focused($isFocused)
  202. .keyboardType(.numberPad)
  203. }
  204. HStack {
  205. Image(systemName: "key.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
  206. SecureField(text: $password, prompt: Text("密码")) {
  207. Text("密码")
  208. }.focused($isFocused)
  209. }
  210. }.onTapGesture {
  211. isFocused = false
  212. }
  213. HStack {
  214. Spacer()
  215. Button(action: {
  216. inProgress = true
  217. doLogin(user: username, password: password) { (ret: Int) in
  218. isLoggedIn = ret
  219. inProgress = false
  220. if ret == 0 {
  221. btnText = "登录失败"
  222. btnColor = .red
  223. } else {
  224. userInfo.id = username
  225. getUserInfo(userInfoIn: userInfo) { res in
  226. userInfo = res
  227. }
  228. }
  229. }
  230. }) {
  231. HStack {
  232. Text(btnText)
  233. if inProgress {
  234. ProgressView()
  235. } else {
  236. Image(systemName: "chevron.right")
  237. }
  238. }
  239. }.foregroundColor(btnColor).buttonStyle(.bordered)
  240. }
  241. }
  242. }
  243. }
  244. }
  245. struct accountView: View {
  246. @State var username: String = ""
  247. @State var password: String = ""
  248. @FocusState private var isFocused: Bool
  249. @Binding var isLoggedIn: Int
  250. @Binding var userInfo: UserInfo
  251. func delAllCookie(name: String) {
  252. for cookie in LoginHandler().getAllCookie(name: name) {
  253. print(cookie)
  254. HTTPCookieStorage.shared.deleteCookie(cookie)
  255. }
  256. }
  257. var body: some View {
  258. VStack {
  259. Form {
  260. Section(header: Text("基本信息")) {
  261. HStack {
  262. Text("姓名")
  263. Spacer()
  264. Text(userInfo.name).foregroundColor(.gray)
  265. }
  266. HStack {
  267. Text("内部 ID")
  268. Spacer()
  269. Text(userInfo.oid).foregroundColor(.gray)
  270. }
  271. }
  272. Section {
  273. NavigationLink(destination: aboutView()) {
  274. Text("关于")
  275. }
  276. }
  277. Section {
  278. Button(action: {
  279. isLoggedIn = 0
  280. delAllCookie(name: "CASTGC")
  281. delAllCookie(name: "JSESSIONID")
  282. }) {
  283. VStack {
  284. Text("退出").foregroundColor(.red)
  285. }
  286. }
  287. }
  288. }
  289. Spacer()
  290. }
  291. }
  292. }
  293. struct aboutView: View {
  294. var body: some View {
  295. List {
  296. Section(footer: Text("实验中学 74 桌是一款面向北师大附属实验中学学生的 App,提供查分及数字校园基础服务。")) {
  297. Text("实验中学 74 桌")
  298. HStack {
  299. Text("开发者")
  300. Spacer()
  301. Text("74 开发组").foregroundColor(.gray)
  302. }
  303. }
  304. }.navigationBarTitle("关于")
  305. }
  306. }
  307. struct accountView_Previews: PreviewProvider {
  308. @State static var isLoggedIn = 1
  309. @State static var userInfo: UserInfo = .init()
  310. static var previews: some View {
  311. loginView(isLoggedIn: $isLoggedIn, userInfo: $userInfo)
  312. }
  313. }