// // MP_WebWork.swift // MusicPlayer // // Created by Mr.Zhou on 2024/4/30. // import Foundation import WebKit import Alamofire ///WebView管理器,通过拉取base.js文件来获取解密函数,完成对请求权限的解密 class MP_WebWork:NSObject { //单例工具 static let shared = MP_WebWork() ///加载web(调用之后记得销毁) private var webView:WKWebView? ///油管首页 private lazy var homePath = "https://music.youtube.com/" private var jsPath:String = "https://music.youtube.com/" ///解码方法名 private var codeFunctionName:String! ///签名时间缀 var signatureTimestamp:Int! private override init() { super.init() //实例化webView webView = .init(frame: .zero) webView?.navigationDelegate = self webView?.uiDelegate = self // webView?.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" } deinit { webView = nil } /// 访问youtube主页 func pingYoutubeHome() { //实现一个请求 let request = URLRequest(url: .init(string: homePath)!) //加载web webView?.load(request) } ///获取base.js文件地址 private func getBasePath(_ jsContent:String) { let pattern = "s/player/[0-9a-fA-F]{8}/player_ias.vflset/[a-zA-Z_]+/base\\.js" guard let regex = try? NSRegularExpression(pattern: pattern, options: .dotMatchesLineSeparators) else { print("Regular expression compilation failed") return } // 在整个字符串范围内搜索正则表达式匹配项 let nsRange = NSRange(jsContent.startIndex.. Void)){ //检索webView是否存在 guard webView != nil, codeFunctionName != nil else { return } //当webView和解码方法名同时存在时 let function = "_yt_player.SNa('\(signatureString)');" //调用解密方法 webView?.evaluateJavaScript(function){result,error in if let error = error { print("Code Error:\(error)") } if let result = result as? String { //该方法返回结果是一个字符串 completion(result) } } } ///获取方法名的正则表达 private func regexFunction(_ jsContent:String) -> String? { var result:String? //唯一性的方法字符串 let pattern = "\\w+=function\\(a\\)\\{[^}]*?a=a\\.split\\(\\\"\\\"\\);" // 使用NSRegularExpression进行正则匹配 guard let regex = try? NSRegularExpression(pattern: pattern, options: .dotMatchesLineSeparators) else { print("Regular expression compilation failed") return result } // 执行正则匹配 let matches = regex.matches(in: jsContent, options: [], range: NSRange(location: 0, length: jsContent.utf16.count)) // 遍历匹配项并打印函数名 for match in matches { if let range = Range(match.range(at: 0), in: jsContent) { // 提取函数体字符串 let functionbody = String(jsContent[range]) //将函数体字符串进行截取(以=为基准) let results = functionbody.split(separator: "=").map(String.init) //第一个元素就是方法名 result = results[0] } } return result } ///获取签名时间值 private func getSignatureTimestamp(_ jsContent:String) -> Int?{ let regexPattern = "signatureTimestamp:(\\d+)" guard let regex = try? NSRegularExpression(pattern: regexPattern, options: .dotMatchesLineSeparators) else { print("Regular expression compilation failed") return nil } let range = NSRange(jsContent.startIndex.. Void){ guard let url = navigationResponse.response.url, url.lastPathComponent == "base.js", codeFunctionName == nil else { decisionHandler(.allow) // 允许非JavaScript文件的加载 return } //调用的是base.js文件,拦截该请求 decisionHandler(.cancel) // 使用URLSession重新请求base.js文件 let task = URLSession.shared.dataTask(with: url){ [weak self] data, response, error in guard let self = self, let data = data, error == nil else { print("Request base.JavaScript error: \(error?.localizedDescription ?? "")") decisionHandler(.allow) return } // 修改JavaScript文件内容 guard let jsContent = String(data: data, encoding: .utf8) else { decisionHandler(.allow) return } guard let function = regexFunction(jsContent), let timeValue = getSignatureTimestamp(jsContent) else { decisionHandler(.allow) return } //获取需要的方法名,和签名时间缀 signatureTimestamp = timeValue codeFunctionName = function //由于该解密函数方法是私有的,因此创建一句暴露代码 let code = "g.SNa=\(codeFunctionName!);" let pattern = "\(codeFunctionName!)=function\\(a\\)\\{[^\\}]*\\};" let regex = try? NSRegularExpression(pattern: pattern, options: .dotMatchesLineSeparators) let nsRange = NSRange(jsContent.startIndex..