81 lines
3.6 KiB
Swift
81 lines
3.6 KiB
Swift
import Foundation
|
|
|
|
// Mirrors Kotlin MibFinancingClient exactly.
|
|
// Fetches the /financing?dashurl=1 HTML page and parses finance-card-holder data attributes.
|
|
actor MibFinancingClient {
|
|
|
|
private let wvBase = "https://faisamobilex-wv.mib.com.mv"
|
|
private let session: MibSession
|
|
|
|
private let urlSession: URLSession = {
|
|
let cfg = URLSessionConfiguration.ephemeral
|
|
cfg.timeoutIntervalForRequest = 30
|
|
cfg.timeoutIntervalForResource = 60
|
|
return URLSession(configuration: cfg)
|
|
}()
|
|
|
|
init(session: MibSession) {
|
|
self.session = session
|
|
}
|
|
|
|
func fetchFinancing() async throws -> [MibFinanceDeal] {
|
|
var req = URLRequest(url: URL(string: "\(wvBase)/financing?dashurl=1")!)
|
|
req.setValue(cookieHeader, forHTTPHeaderField: "Cookie")
|
|
req.setValue(
|
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
|
|
forHTTPHeaderField: "User-Agent"
|
|
)
|
|
req.setValue("mv.com.mib.faisamobilex", forHTTPHeaderField: "X-Requested-With")
|
|
|
|
let (data, response) = try await urlSession.data(for: req)
|
|
let code = (response as? HTTPURLResponse)?.statusCode ?? 0
|
|
if code == 419 { throw MibError.sessionExpired }
|
|
if code >= 500 { throw MibError.networkError("HTTP \(code)") }
|
|
|
|
guard let html = String(data: data, encoding: .utf8) else { return [] }
|
|
return parseHtml(html)
|
|
}
|
|
|
|
// MARK: - HTML parsing (mirrors Kotlin regex approach)
|
|
|
|
private func parseHtml(_ html: String) -> [MibFinanceDeal] {
|
|
guard let cardRe = try? NSRegularExpression(pattern: #"finance-card-holder[^>]+>"#),
|
|
let attrRe = try? NSRegularExpression(pattern: #"data-(\w+)\s*=\s*"([^"]*)""#) else {
|
|
return []
|
|
}
|
|
|
|
let ns = html as NSString
|
|
return cardRe.matches(in: html, range: NSRange(location: 0, length: ns.length)).compactMap { m in
|
|
let cardStr = ns.substring(with: m.range)
|
|
let cardNS = cardStr as NSString
|
|
var attrs: [String: String] = [:]
|
|
attrRe.matches(in: cardStr, range: NSRange(location: 0, length: cardNS.length)).forEach { a in
|
|
let key = cardNS.substring(with: a.range(at: 1))
|
|
let val = cardNS.substring(with: a.range(at: 2))
|
|
attrs[key] = val
|
|
}
|
|
guard let dealNo = attrs["dealNo"] else { return nil }
|
|
return MibFinanceDeal(
|
|
dealNo: dealNo,
|
|
productDesc: attrs["productDesc"] ?? "",
|
|
dealStatus: attrs["dealStatus"] ?? "",
|
|
statusDesc: attrs["statusDesc"] ?? "",
|
|
dealAmount: Double(attrs["dealAmount"] ?? "") ?? 0,
|
|
paidAmount: Double(attrs["paidAmount"] ?? "") ?? 0,
|
|
outstandingAmount: Double(attrs["outstandingAmount"] ?? "") ?? 0,
|
|
dealDate: attrs["dealDate"] ?? "",
|
|
overdueAmount: Double(attrs["overdueAmount"] ?? "") ?? 0,
|
|
installmentAmount: Double(attrs["installmentAmount"] ?? "") ?? 0,
|
|
noOfInstallments: Int(attrs["noOfInstallments"] ?? "") ?? 0,
|
|
lastPaidDate: attrs["lastPaidDate"] ?? "",
|
|
lastPayAmount: Double(attrs["lastPayAmount"] ?? "") ?? 0,
|
|
currency: attrs["curCodeDesc"] ?? "MVR"
|
|
)
|
|
}
|
|
}
|
|
|
|
private var cookieHeader: String {
|
|
"mbmodel=IOS-1.0; xxid=\(session.xxid); IBSID=\(session.xxid); mbnonce=\(session.nonceGenerator); time-tracker=597"
|
|
}
|
|
}
|