account.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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: Decodable {
  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. var tableData: [PurpleDatum]
  41. init() {
  42. name = "-"
  43. oid = "-"
  44. id = "-"
  45. xydLink = "-"
  46. serviceLink = "-"
  47. studyLink = "-"
  48. tableData = []
  49. }
  50. }
  51. class LoginHandler {
  52. static let sharedInstance = LoginHandler()
  53. func getCookie(name: String) -> [HTTPCookie] {
  54. return CookieHandler().getCookie(forURL: "https://debug.sdsz.icu:81/").filter { cookie -> Bool in
  55. cookie.name == name
  56. }
  57. }
  58. func getAllCookie(name: String) -> [HTTPCookie] {
  59. return CookieHandler().getAllCookie().filter { cookie -> Bool in
  60. cookie.name == name
  61. }
  62. }
  63. func fetchLoginCookie(action: @escaping (_: String) -> Void) {
  64. if let cookie = getCookie(name: "JSESSIONID").first {
  65. HTTPCookieStorage.shared.deleteCookie(cookie)
  66. print(cookie)
  67. }
  68. let url = "https://debug.sdsz.icu:81/sso/login"
  69. AF.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).response { _ in
  70. if let cookie = self.getCookie(name: "JSESSIONID").first {
  71. action(cookie.value)
  72. }
  73. }
  74. }
  75. }
  76. class FetchHandler {
  77. static let sharedInstance = FetchHandler()
  78. func matches(url: String) -> Bool {
  79. do {
  80. 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
  81. } catch {
  82. return true
  83. }
  84. }
  85. func getUrl(url: String) -> String {
  86. var ret: String = url
  87. ret = ret
  88. .replacing("dd.sdsz.com.cn", with: "debug.sdsz.icu:81")
  89. .replacing(/^http:/, with: "https:")
  90. .replacing("service=http%3A%2F%2Fdebug.sdsz.icu:81", with: "service=http%3A%2F%2Fdd.sdsz.com.cn")
  91. print("URL: \(ret)")
  92. return ret
  93. }
  94. func workFetch(url: String, action: @escaping (_: String) -> Void) {
  95. if matches(url: url) {
  96. action(url)
  97. } else {
  98. AF.request(getUrl(url: url), method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseString { res in
  99. if let resv = res.value {
  100. self.workFetch(url: resv, action: action)
  101. }
  102. }
  103. }
  104. }
  105. func fetchAny(url: String, action: @escaping (_: String) -> Void) {
  106. let urlFull = "https://debug.sdsz.icu:81/\(url)"
  107. workFetch(url: urlFull, action: action)
  108. }
  109. }
  110. func doLogin(user: String, password: String, action: @escaping (_: Int) -> Void) {
  111. LoginHandler().fetchLoginCookie { (cookie: String) in
  112. let login = Login(user: user, pwd: password, cook: cookie)
  113. print(login)
  114. AF.request("https://debug.sdsz.icu/andlogin", method: .post, parameters: login, encoder: JSONParameterEncoder.default, headers: nil).responseString { res in
  115. print("\(res)")
  116. if res.value == "success" {
  117. action(1)
  118. } else {
  119. action(0)
  120. }
  121. }
  122. }
  123. }
  124. // This file was generated from JSON Schema using quicktype, do not modify it directly.
  125. // To parse the JSON, add this file to your project and do:
  126. //
  127. // let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
  128. import Foundation
  129. // MARK: - Welcome
  130. struct Welcome: Codable {
  131. let workRests: [WorkREST]
  132. let blocks: [Block]
  133. }
  134. // MARK: - Block
  135. struct Block: Codable {
  136. let ownObjID, type: String
  137. let name: Name
  138. let data: [BlockDatum]
  139. let weekMetas: JSONNull?
  140. enum CodingKeys: String, CodingKey {
  141. case ownObjID = "ownObjId"
  142. case type, name, data, weekMetas
  143. }
  144. }
  145. // MARK: - BlockDatum
  146. struct BlockDatum: Codable {
  147. let week: Int
  148. let weekMeta: WeekMeta
  149. let data: [PurpleDatum]
  150. }
  151. // MARK: - PurpleDatum
  152. struct PurpleDatum: Codable {
  153. let day, lesson: Int
  154. let data: [FluffyDatum]
  155. }
  156. // MARK: - FluffyDatum
  157. struct FluffyDatum: Codable {
  158. let timetableID: JSONNull?
  159. let className: Name
  160. let classID: ClassID
  161. let courseName: String
  162. let courseID, courseType: Int
  163. let placeID: JSONNull?
  164. let placeName: PlaceName
  165. let placeSn: PlaceSn
  166. let teacher: [Teacher]
  167. let room: JSONNull?
  168. let start, end: String
  169. let timeScope: Int
  170. enum CodingKeys: String, CodingKey {
  171. case timetableID = "timetableId"
  172. case className
  173. case classID = "classId"
  174. case courseName
  175. case courseID = "courseId"
  176. case courseType
  177. case placeID = "placeId"
  178. case placeName, placeSn, teacher, room, start, end, timeScope
  179. }
  180. }
  181. enum ClassID: String, Codable {
  182. case ac342897 = "AC_342897"
  183. case tac8109 = "TAC_8109"
  184. case tac8123 = "TAC_8123"
  185. }
  186. enum Name: String, Codable {
  187. case 高一数学P1班 = "高一数学[P]1班"
  188. case 高一物理A5班 = "高一物理[A]5班"
  189. case 高中2025届11班
  190. }
  191. enum PlaceName: String, Codable {
  192. case 信毅楼408 = "信毅楼4-08"
  193. case 信毅楼412 = "信毅楼4-12"
  194. case 本班教室
  195. }
  196. enum PlaceSn: String, Codable {
  197. case bxXy408 = " BX-XY-408 "
  198. case bxXy410 = " BX-XY-410 "
  199. case empty = ""
  200. }
  201. // MARK: - Teacher
  202. struct Teacher: Codable {
  203. let userID: Int
  204. let userName: JSONNull?
  205. let fullName: String
  206. let userNo: JSONNull?
  207. enum CodingKeys: String, CodingKey {
  208. case userID = "userId"
  209. case userName, fullName, userNo
  210. }
  211. }
  212. // MARK: - WeekMeta
  213. struct WeekMeta: Codable {
  214. let morningRead: Bool
  215. let night, afternoon, morning, teachingDay: Int
  216. let time: [Time]
  217. }
  218. // MARK: - Time
  219. struct Time: Codable {
  220. let seq: Int
  221. let start, end: String
  222. }
  223. // MARK: - WorkREST
  224. struct WorkREST: Codable {
  225. let beginTime, endTime, time, lessonName: String
  226. }
  227. // MARK: - Encode/decode helpers
  228. class JSONNull: Codable, Hashable {
  229. public static func == (_: JSONNull, _: JSONNull) -> Bool {
  230. return true
  231. }
  232. func hash(into _: inout Hasher) {}
  233. public init() {}
  234. public required init(from decoder: Decoder) throws {
  235. let container = try decoder.singleValueContainer()
  236. if !container.decodeNil() {
  237. throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
  238. }
  239. }
  240. public func encode(to encoder: Encoder) throws {
  241. var container = encoder.singleValueContainer()
  242. try container.encodeNil()
  243. }
  244. }
  245. var Count: Int = 0
  246. func addCompleted(userInfo: UserInfo, action: @escaping (_: UserInfo) -> Void) {
  247. Count += 1
  248. if Count == 3 {
  249. print(userInfo.tableData)
  250. action(userInfo)
  251. }
  252. }
  253. extension String {
  254. func replace(regex: String, with: String, options: NSRegularExpression.Options) -> String {
  255. do {
  256. let RE = try NSRegularExpression(pattern: regex, options: options)
  257. let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
  258. return modified
  259. } catch {
  260. return "ERR"
  261. }
  262. }
  263. func replace(regex: String, with: String) -> String {
  264. do {
  265. let RE = try NSRegularExpression(pattern: regex)
  266. let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
  267. return modified
  268. } catch {
  269. return "ERR"
  270. }
  271. }
  272. }
  273. struct TableParam: Encodable {
  274. var oid: String
  275. }
  276. func getUserInfo(userInfoIn: UserInfo, action: @escaping (_: UserInfo) -> Void) {
  277. var userInfo: UserInfo = userInfoIn
  278. FetchHandler().fetchAny(url: "bxn-portal/portal/osforstudent/index") { res in
  279. if let mat = res.firstMatch(of: /userFullName" value="(.*?)"/) {
  280. userInfo.name = "\(mat.1)"
  281. }
  282. if let mat = res.firstMatch(of: /userId" value="(.*?)"/) {
  283. userInfo.oid = "\(mat.1)"
  284. }
  285. if let mat = res.firstMatch(of: /'https:\/\/service.*?'/) {
  286. userInfo.serviceLink = "\(mat.output.split(separator: "'")[0])"
  287. userInfo.studyLink = mat.output.split(separator: "'")[0] + "%2Ffe-pc%2Fb%2Ffe_leco_student%2Fportal%2F%3Fsystem_partition_gId%3D3"
  288. }
  289. print(userInfo)
  290. let tableParam: TableParam = .init(oid: userInfo.oid)
  291. let header: HTTPHeaders = [
  292. "user": userInfo.id,
  293. ]
  294. AF.request("https://debug.sdsz.icu/getWeek", method: .post, parameters: tableParam, encoder: JSONParameterEncoder.default, headers: header).responseString { res in
  295. FetchHandler().fetchAny(url: "bxn-timetable/timetable/monitor/homepage/data/student?studentId=\(userInfo.oid)&dateScope=\(res.value ?? "")&_=0") { res in
  296. do {
  297. let data = res.data(using: String.Encoding.utf8)
  298. let table = try! JSONSerialization.jsonObject(with: data!) as? [String: Any]
  299. let decoder = JSONDecoder()
  300. let kkd = try decoder.decode(Welcome.self, from: data!)
  301. userInfo.tableData = kkd.blocks.first?.data.first?.data ?? []
  302. } catch {
  303. print(error)
  304. }
  305. addCompleted(userInfo: userInfo, action: action)
  306. }
  307. }
  308. addCompleted(userInfo: userInfo, action: action)
  309. }
  310. FetchHandler().fetchAny(url: "bxn-library/library/jumpExamreport?jumpUrl=http://36.112.23.77/analysis/auto/%23/autoLogin") { res in
  311. userInfo.xydLink = res
  312. addCompleted(userInfo: userInfo, action: action)
  313. }
  314. }
  315. struct loginView: View {
  316. @State var username: String = ""
  317. @State var password: String = ""
  318. @State var btnText: String = "登录"
  319. @State var btnColor: Color = .blue
  320. @State var inProgress: Bool = false
  321. @FocusState private var isFocused: Bool
  322. @Binding var isLoggedIn: Int
  323. @Binding var userInfo: UserInfo
  324. var body: some View {
  325. VStack {
  326. Form {
  327. List {
  328. HStack {
  329. Image(systemName: "person.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
  330. TextField(text: $username, prompt: Text("数字校园号")) {
  331. Text("数字校园号")
  332. }.focused($isFocused)
  333. .keyboardType(.numberPad)
  334. }
  335. HStack {
  336. Image(systemName: "key.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
  337. SecureField(text: $password, prompt: Text("密码")) {
  338. Text("密码")
  339. }.focused($isFocused)
  340. }
  341. }.onTapGesture {
  342. isFocused = false
  343. }
  344. HStack {
  345. Spacer()
  346. Button(action: {
  347. inProgress = true
  348. doLogin(user: username, password: password) { (ret: Int) in
  349. isLoggedIn = ret
  350. inProgress = false
  351. if ret == 0 {
  352. btnText = "登录失败"
  353. btnColor = .red
  354. } else {
  355. userInfo.id = username
  356. getUserInfo(userInfoIn: userInfo) { res in
  357. userInfo = res
  358. }
  359. }
  360. }
  361. }) {
  362. HStack {
  363. Text(btnText)
  364. if inProgress {
  365. ProgressView()
  366. } else {
  367. Image(systemName: "chevron.right")
  368. }
  369. }
  370. }.foregroundColor(btnColor).buttonStyle(.bordered)
  371. }
  372. }
  373. }
  374. }
  375. }
  376. struct accountView: View {
  377. @State var username: String = ""
  378. @State var password: String = ""
  379. @FocusState private var isFocused: Bool
  380. @Binding var isLoggedIn: Int
  381. @Binding var userInfo: UserInfo
  382. func delAllCookie(name: String) {
  383. for cookie in LoginHandler().getAllCookie(name: name) {
  384. print(cookie)
  385. HTTPCookieStorage.shared.deleteCookie(cookie)
  386. }
  387. }
  388. var body: some View {
  389. VStack {
  390. Form {
  391. Section(header: Text("基本信息")) {
  392. HStack {
  393. Text("姓名")
  394. Spacer()
  395. Text(userInfo.name).foregroundColor(.gray)
  396. }
  397. HStack {
  398. Text("内部 ID")
  399. Spacer()
  400. Text(userInfo.oid).foregroundColor(.gray)
  401. }
  402. }
  403. Section {
  404. NavigationLink(destination: aboutView()) {
  405. Text("关于")
  406. }
  407. }
  408. Section {
  409. Button(action: {
  410. isLoggedIn = 0
  411. delAllCookie(name: "CASTGC")
  412. delAllCookie(name: "JSESSIONID")
  413. }) {
  414. VStack {
  415. Text("退出").foregroundColor(.red)
  416. }
  417. }
  418. }
  419. }
  420. Spacer()
  421. }
  422. }
  423. }
  424. struct aboutView: View {
  425. var body: some View {
  426. List {
  427. Section(footer: Text("实验中学 74 桌是一款面向北师大附属实验中学学生的 App,提供查分及数字校园基础服务。")) {
  428. Text("实验中学 74 桌")
  429. HStack {
  430. Text("开发者")
  431. Spacer()
  432. Text("74 开发组").foregroundColor(.gray)
  433. }
  434. }
  435. }.navigationBarTitle("关于")
  436. }
  437. }
  438. struct accountView_Previews: PreviewProvider {
  439. @State static var isLoggedIn = 1
  440. @State static var userInfo: UserInfo = .init()
  441. static var previews: some View {
  442. loginView(isLoggedIn: $isLoggedIn, userInfo: $userInfo)
  443. }
  444. }