From 27308c22c83e14bb67ac39425574dfbe32aa0b89 Mon Sep 17 00:00:00 2001 From: Mark Chan <45706356+WindowsMEMZ@users.noreply.github.com> Date: Sun, 10 Dec 2023 10:03:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BAavid-bvid=E8=BD=AC=E6=8D=A2=E5=92=8CWb?= =?UTF-8?q?i=E7=AD=BE=E5=90=8D=E6=B7=BB=E5=8A=A0Swift=E5=AE=9E=E7=8E=B0=20?= =?UTF-8?q?(#890)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update bvid_desc.md * Update wbi.md --- docs/misc/bvid_desc.md | 59 +++++++++++++++++++++++++++++ docs/misc/sign/wbi.md | 86 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/docs/misc/bvid_desc.md b/docs/misc/bvid_desc.md index a7e3f4a..ade770b 100644 --- a/docs/misc/bvid_desc.md +++ b/docs/misc/bvid_desc.md @@ -192,6 +192,65 @@ print(bv2av("BV1L9Uoa9EUx")) 参考 +### Swift + +```swift +fileprivate let XOR_CODE: UInt64 = 23442827791579 +fileprivate let MASK_CODE: UInt64 = 2251799813685247 +fileprivate let MAX_AID: UInt64 = 1 << 51 + +fileprivate let data: [UInt8] = [70, 99, 119, 65, 80, 78, 75, 84, 77, 117, 103, 51, 71, 86, 53, 76, 106, 55, 69, 74, 110, 72, 112, 87, 115, 120, 52, 116, 98, 56, 104, 97, 89, 101, 118, 105, 113, 66, 122, 54, 114, 107, 67, 121, 49, 50, 109, 85, 83, 68, 81, 88, 57, 82, 100, 111, 90, 102] + +fileprivate let BASE: UInt64 = 58 +fileprivate let BV_LEN: Int = 12 +fileprivate let PREFIX: String = "BV1" + +func av2bv(avid: UInt64) -> String { + var bytes: [UInt8] = [66, 86, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48] + var bvIdx = BV_LEN - 1 + var tmp = (MAX_AID | avid) ^ XOR_CODE + + while tmp != 0 { + bytes[bvIdx] = data[Int(tmp % BASE)] + tmp /= BASE + bvIdx -= 1 + } + + bytes.swapAt(3, 9) + bytes.swapAt(4, 7) + + return String(decoding: bytes, as: UTF8.self) +} + +func bv2av(bvid: String) -> UInt64 { + let fixedBvid: String + if bvid.hasPrefix("BV") { + fixedBvid = bvid + } else { + fixedBvid = "BV" + bvid + } + var bvidArray = Array(fixedBvid.utf8) + + bvidArray.swapAt(3, 9) + bvidArray.swapAt(4, 7) + + let trimmedBvid = String(decoding: bvidArray[3...], as: UTF8.self) + + var tmp: UInt64 = 0 + + for char in trimmedBvid { + if let idx = data.firstIndex(of: char.utf8.first!) { + tmp = tmp * BASE + UInt64(idx) + } + } + + return (tmp & MASK_CODE) ^ XOR_CODE +} + +print(av2bv(avid: 111298867365120)) +print(bv2av(bvid: "BV1L9Uoa9EUx")) +``` + ## 老版算法存档 算法参考自[【揭秘】av号转bv号的过程](https://www.bilibili.com/video/BV1N741127Tj) diff --git a/docs/misc/sign/wbi.md b/docs/misc/sign/wbi.md index b55e549..020b56d 100644 --- a/docs/misc/sign/wbi.md +++ b/docs/misc/sign/wbi.md @@ -115,7 +115,7 @@ ## Wbi签名算法实现Demo -该 Demo 提供 [Python](#Python)、[JavaScript](#JavaScript)、[Golang](#Golang)、[C#](#CSharp)和[Java](#Java) 语言 +该 Demo 提供 [Python](#Python)、[JavaScript](#JavaScript)、[Golang](#Golang)、[C#](#CSharp)、[Java](#Java)和[Swift](#Swift) 语言 ### Python @@ -816,3 +816,87 @@ async fn main() { } ``` +### Swift + +需要 [Alamofire](https://github.com/Alamofire/Alamofire) 和 [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON) 库 + +```swift +import Foundation +import CommonCrypto +import Alamofire +import SwiftyJSON + +func biliWbiSign(param: String, completion: @escaping (String?) -> Void) { + func getMixinKey(orig: String) -> String { + return String(mixinKeyEncTab.map { orig[orig.index(orig.startIndex, offsetBy: $0)] }.prefix(32)) + } + + func encWbi(params: [String: Any], imgKey: String, subKey: String) -> [String: Any] { + var params = params + let mixinKey = getMixinKey(orig: imgKey + subKey) + let currTime = round(Date().timeIntervalSince1970) + params["wts"] = currTime + params = params.sorted { $0.key < $1.key }.reduce(into: [:]) { $0[$1.key] = $1.value } + params = params.mapValues { String(describing: $0).filter { !"!'()*".contains($0) } } + let query = params.map { "\($0.key)=\($0.value)" }.joined(separator: "&") + let wbiSign = calculateMD5(string: query + mixinKey) + params["w_rid"] = wbiSign + return params + } + + func getWbiKeys(completion: @escaping (Result<(imgKey: String, subKey: String), Error>) -> Void) { + AF.request("https://api.bilibili.com/x/web-interface/nav").responseJSON { response in + switch response.result { + case .success(let value): + let json = JSON(value) + let imgURL = json["data"]["wbi_img"]["img_url"].string ?? "" + let subURL = json["data"]["wbi_img"]["sub_url"].string ?? "" + let imgKey = imgURL.components(separatedBy: "/").last?.components(separatedBy: ".").first ?? "" + let subKey = subURL.components(separatedBy: "/").last?.components(separatedBy: ".").first ?? "" + completion(.success((imgKey, subKey))) + case .failure(let error): + completion(.failure(error)) + } + } + } + + func calculateMD5(string: String) -> String { + let data = Data(string.utf8) + var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) + _ = data.withUnsafeBytes { + CC_MD5($0.baseAddress, CC_LONG(data.count), &digest) + } + return digest.map { String(format: "%02hhx", $0) }.joined() + } + + let mixinKeyEncTab = [ + 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, + 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, + 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, + 36, 20, 34, 44, 52 + ] + + getWbiKeys { result in + switch result { + case .success(let keys): + let spdParam = param.components(separatedBy: "&") + var spdDicParam = [String: String]() + spdParam.forEach { pair in + let components = pair.components(separatedBy: "=") + if components.count == 2 { + spdDicParam[components[0]] = components[1] + } + } + + let signedParams = encWbi(params: spdDicParam, imgKey: keys.imgKey, subKey: keys.subKey) + let query = signedParams.map { "\($0.key)=\($0.value)" }.joined(separator: "&") + completion(query) + case .failure(let error): + print("Error getting keys: \(error)") + completion(nil) + } + } +} + +``` +