From 7fdf8d3124a1a1c09280f2c6d1ba1020f697dfd0 Mon Sep 17 00:00:00 2001 From: Mark Chan <45706356+WindowsMEMZ@users.noreply.github.com> Date: Thu, 30 Nov 2023 01:11:48 +0800 Subject: [PATCH 1/4] Fix Crash Issue when Seeing User's Videos in UserDetailsView (#15) * Update UserDetailView.swift * Merge main workflows (#16) * Update status-check.yml * Update statuscheck-runner.yml * Update CodeExt.swift * Update UserDetailView.swift Fixed optional unwrap issue * Update status-check.yml * Update status-check.yml * Update status-check.yml * Update status-check.yml * Update status-check.yml * Update status-check.yml * Update UserDetailView.swift --- .github/workflows/status-check.yml | 50 ++++++++++++++++++- .github/workflows/statuscheck-runner.yml | 28 +++++++++-- DarockBili Watch App/Extension/CodeExt.swift | 20 -------- .../PersonalCenter/UserDetailView.swift | 13 ++++- 4 files changed, 84 insertions(+), 27 deletions(-) diff --git a/.github/workflows/status-check.yml b/.github/workflows/status-check.yml index e574df19d..a6f2cd6ca 100644 --- a/.github/workflows/status-check.yml +++ b/.github/workflows/status-check.yml @@ -1,12 +1,34 @@ name: Status Check Workflow -on: workflow_call +on: + workflow_dispatch: + inputs: + psha: + required: true jobs: build: name: Check Build runs-on: macos-13 + permissions: + checks: write + statuses: write + env: + FIN_STATUS: "error" + GH_TOKEN: ${{ github.token }} steps: + - name: Update Check Status + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/${{ github.repository }}/statuses/$GITHUB_SHA \ + -f state='pending' \ + -f target_url='https://github.com/Darock-Studio/Darock-Bili/actions/runs/${{ github.run_id }}' \ + -f description='Building...' \ + -f context='API Status Checker' + - name: Checkout uses: actions/checkout@v3 @@ -19,8 +41,32 @@ jobs: key: check-build-cache path: | ~/Library/Developer/Xcode/DerivedData + + - name: Cache Package Caches + uses: actions/cache@v3 + with: + path: .build + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + + - name: Update Status Env + run: echo "FIN_STATUS=failure" >> $GITHUB_ENV - name: Build DarockBili App run: | xcodebuild -scheme 'DarockBili Watch App' -configuration Release build CODE_SIGN_IDENTITY="" - + echo "FIN_STATUS=success" >> $GITHUB_ENV + + - name: Update Check Status + if: always() + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/${{ github.repository }}/statuses/$GITHUB_SHA \ + -f state='${{ env.FIN_STATUS }}' \ + -f target_url='https://github.com/Darock-Studio/Darock-Bili/actions/runs/${{ github.run_id }}' \ + -f context='API Status Checker' + diff --git a/.github/workflows/statuscheck-runner.yml b/.github/workflows/statuscheck-runner.yml index 0d3195cf1..c5c67f027 100644 --- a/.github/workflows/statuscheck-runner.yml +++ b/.github/workflows/statuscheck-runner.yml @@ -22,9 +22,31 @@ jobs: echo "The comment content contains the specified text. Continuing with further actions." echo "::set-output name=shouldc::true" fi - call-check-workflow: - name: Run + get-head-sha: + name: Get Pull Request Head SHA needs: checkif if: ${{ needs.checkif.outputs.shouldc == 'true' }} - uses: Darock-Studio/Darock-Bili/.github/workflows/status-check.yml@main + runs-on: ubuntu-latest + outputs: + psha: ${{ steps.getsha.outputs.psha }} + env: + slink: ${{ github.event.issue.pull_request.url }} + steps: + - name: Get SHA + id: getsha + run: | + json_data=$(curl -s "$slink") + sha=$(echo "$json_data" | jq -r '.head.ref') + echo "psha=$sha" >> $GITHUB_OUTPUT + call-check-workflow: + name: Call Check Overflow + needs: get-head-sha + runs-on: ubuntu-latest + steps: + - name: Call + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: status-check.yml + ref: ${{ needs.get-head-sha.outputs.psha }} + inputs: '{ "psha": "${{ needs.get-head-sha.outputs.psha }}"}' diff --git a/DarockBili Watch App/Extension/CodeExt.swift b/DarockBili Watch App/Extension/CodeExt.swift index c15207233..d98db2d37 100644 --- a/DarockBili Watch App/Extension/CodeExt.swift +++ b/DarockBili Watch App/Extension/CodeExt.swift @@ -248,24 +248,4 @@ extension Int { } } -postfix operator / -extension Optional { - static postfix func / (opt: Int?) -> Int { - return opt ?? 0 - } - static postfix func / (opt: String?) -> String { - return opt ?? "" - } - static postfix func / (opt: Double?) -> Double { - return opt ?? 0.0 - } - static postfix func / (opt: Float?) -> Float { - return opt ?? 0.0 - } - static postfix func / (opt: Bool?) -> Bool { - return opt ?? false - } -} - - diff --git a/DarockBili Watch App/PersonalCenter/UserDetailView.swift b/DarockBili Watch App/PersonalCenter/UserDetailView.swift index eb07d00dc..901cfc3ed 100644 --- a/DarockBili Watch App/PersonalCenter/UserDetailView.swift +++ b/DarockBili Watch App/PersonalCenter/UserDetailView.swift @@ -560,7 +560,8 @@ struct UserDetailView: View { func RefreshVideos() { videos = [[String: String]]() let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata);" + "cookie": "SESSDATA=\(sessdata);", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" ] DarockKit.Network.shared.requestString("https://api.darock.top/bili/wbi/sign/\("mid=\(uid)&ps=50&pn=\(videoNowPage)".base64Encoded())") { respStr, isSuccess in if isSuccess { @@ -568,11 +569,15 @@ struct UserDetailView: View { DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/arc/search?\(respStr.apiFixed())", headers: headers) { respJson, isSuccess in if isSuccess { debugPrint(respJson) + if (respJson["code"].int ?? 0) != 0 { + tipWithText("加载失败:\(respJson["message"].string ?? "")", symbol: "xmark.circle.fill") + return + } let vlist = respJson["data"]["list"]["vlist"] for video in vlist { videos.append(["Title": video.1["title"].string ?? "[加载失败]", "Length": video.1["length"].string ?? "E", "PlayCount": String(video.1["play"].int ?? -1), "PicUrl": video.1["pic"].string ?? "E", "BV": video.1["bvid"].string ?? "E", "Timestamp": String(video.1["created"].int ?? 0), "DanmakuCount": String(video.1["video_review"].int ?? -1)]) } - debugPrint(respJson["data"]["page"]["count"].int!) + debugPrint(respJson["data"]["page"]["count"].int ?? 0) videoTotalPage = Int((respJson["data"]["page"]["count"].int ?? 0) / 50) + 1 videoCount = respJson["data"]["page"]["count"].int ?? 0 if !isVideosLoaded { @@ -597,6 +602,10 @@ struct UserDetailView: View { DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/article?\(respStr.apiFixed())", headers: headers) { respJson, isSuccess in if isSuccess { debugPrint(respJson) + if (respJson["code"].int ?? 0) != 0 { + tipWithText("加载失败:\(respJson["message"].string ?? "")", symbol: "xmark.circle.fill") + return + } let articlesJson = respJson["data"]["articles"] for article in articlesJson { articles.append(["Title": article.1["title"].string ?? "[加载失败]", "Summary": article.1["summary"].string ?? "[加载失败]", "Type": article.1["categories"][0]["name"].string ?? "[加载失败]", "View": String(article.1["stats"]["view"].int ?? -1), "Like": String(article.1["stats"]["like"].int ?? -1), "Pic": article.1["image_urls"][0].string ?? "E", "CV": String(article.1["id"].int ?? 0)]) From c00614a0146a850566a7545a66962ddacae4eeb3 Mon Sep 17 00:00:00 2001 From: Mark Chan <45706356+WindowsMEMZ@users.noreply.github.com> Date: Sat, 2 Dec 2023 12:48:02 +0800 Subject: [PATCH 2/4] Add local wbi sign feature (#18) * Update CodeExt.swift Added wbi calculation logic * Update MainView.swift * Update PersonAccountView.swift * Update UserDetailView.swift Might fix the article access issue at the same time. * Update CodeExt.swift * Update CodeExt.swift * Update CodeExt.swift * Update status-check.yml * Update status-check.yml * Update CodeExt.swift --- .github/workflows/status-check.yml | 16 +-- DarockBili Watch App/Extension/CodeExt.swift | 133 +++++++++--------- DarockBili Watch App/InMain/MainView.swift | 8 +- .../PersonalCenter/PersonAccountView.swift | 8 +- .../PersonalCenter/UserDetailView.swift | 27 ++-- 5 files changed, 92 insertions(+), 100 deletions(-) diff --git a/.github/workflows/status-check.yml b/.github/workflows/status-check.yml index a6f2cd6ca..382d5f63f 100644 --- a/.github/workflows/status-check.yml +++ b/.github/workflows/status-check.yml @@ -35,21 +35,19 @@ jobs: - name: Set Xcode Version run: sudo xcode-select -s /Applications/Xcode_15.0.app + - name: Get Current Time + id: current-time + run: echo "time=$(date +"%Y%m%d%H%M%S")" >> $GITHUB_OUTPUT + - name: Cache Build Caches uses: actions/cache@v3 with: - key: check-build-cache + key: ${{ runner.os }}-check-build-cache-${{ steps.current-time.outputs.time }} path: | ~/Library/Developer/Xcode/DerivedData - - - name: Cache Package Caches - uses: actions/cache@v3 - with: - path: .build - key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm- - + ${{ runner.os }}-check-build-cache- + - name: Update Status Env run: echo "FIN_STATUS=failure" >> $GITHUB_ENV diff --git a/DarockBili Watch App/Extension/CodeExt.swift b/DarockBili Watch App/Extension/CodeExt.swift index d98db2d37..6a2e8ad3a 100644 --- a/DarockBili Watch App/Extension/CodeExt.swift +++ b/DarockBili Watch App/Extension/CodeExt.swift @@ -19,7 +19,9 @@ import OSLog import SwiftUI import Dynamic import CryptoKit +import DarockKit import Alamofire +import SwiftyJSON import Foundation import AVFoundation import CommonCrypto @@ -136,84 +138,75 @@ public func hideDigitalTime(_ b: Bool) { app._setStatusBarTimeHidden(b, animated: true, completion: nil) } -public class WbiSign: ObservableObject { - @State var img_key = "" - @State var sub_key = "" - func MD5Hash(_ string: String) -> String { - let data = Data(string.utf8) - var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) - data.withUnsafeBytes { bytes in - _ = CC_MD5(bytes.baseAddress, CC_LONG(data.count), &hash) - } - return hash.map { String(format: "%02x", $0) }.joined() - } - static let mixinKeyEncTab: [Int] = [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] - - func getMixinKey(orig: String) -> String { - let result = WbiSign.mixinKeyEncTab.reduce("") { result, index in - if index < orig.count { - let start = orig.index(orig.startIndex, offsetBy: index) - return result + String(orig[start]) - } else { - return result - } - } - print("Mixin Key: \(result)") - return result - } - - func filterInvalidCharacters(from value: Any) -> String { - let invalidCharacters = CharacterSet(charactersIn: "!'()*") - if let strValue = value as? String { - return strValue.components(separatedBy: invalidCharacters).joined() - } else { - return String(describing: value) - } - } - - func md5(data: String) -> String { - let digest = Insecure.MD5.hash(data: data.data(using: .utf8) ?? Data()) - return digest.map { String(format: "%02hhx", $0) }.joined() +func biliWbiSign(paramEncoded: 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]) -> String { - var newParams = params - let mixinKey = getMixinKey(orig: img_key + sub_key) - let currTime = Int(Date().timeIntervalSince1970) - newParams["wts"] = currTime - let sortedParams = newParams.sorted { $0.key < $1.key } - let query = sortedParams.map { "\($0.key)=\(filterInvalidCharacters(from: $0.value))" }.joined(separator: "&") - print("Query: \(query + mixinKey)") - let dataToHash = (query.urlEncoded() + mixinKey.prefix(32)) - let wbiSign = (dataToHash).DDMD5Encrypt() - print("WBI Sign: \(wbiSign)") - newParams["w_rid"] = wbiSign - var paramStr = "" - for param in newParams { - paramStr += "\(param.key)=\(param.value)&" - } - paramStr.removeLast() - return paramStr + 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(completionHandler: @escaping (String, String) -> Void) { + + 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): - if let dict = value as? [String: Any], - let data = dict["data"] as? [String: Any], - let wbiImg = data["wbi_img"] as? [String: Any], - let imgUrl = wbiImg["img_url"] as? String, - let subUrl = wbiImg["sub_url"] as? String { - let imgKey = String(imgUrl.split(separator: "/").last!.split(separator: ".").first!) - let subKey = String(subUrl.split(separator: "/").last!.split(separator: ".").first!) - print("Image Key: \(imgKey)") - print("Sub Key: \(subKey)") - completionHandler(imgKey, subKey) - } + 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): - print(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 decParam = paramEncoded.base64Decoded() ?? "" + let spdParam = decParam.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) } } } diff --git a/DarockBili Watch App/InMain/MainView.swift b/DarockBili Watch App/InMain/MainView.swift index 0d40949e0..1c1884507 100644 --- a/DarockBili Watch App/InMain/MainView.swift +++ b/DarockBili Watch App/InMain/MainView.swift @@ -160,10 +160,10 @@ struct MainView: View { let headers: HTTPHeaders = [ "cookie": "SESSDATA=\(sessdata)" ] - DarockKit.Network.shared.requestString("https://api.darock.top/bili/wbi/sign/\("ps=\(isInLowBatteryMode ? 10 : 30)".base64Encoded())") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr.apiFixed()) - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd?\(respStr.apiFixed())", headers: headers) { respJson, isSuccess in + biliWbiSign(paramEncoded: "ps=\(isInLowBatteryMode ? 10 : 30)".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd?\(signed)", headers: headers) { respJson, isSuccess in if isSuccess { debugPrint(respJson) let datas = respJson["data"]["item"] diff --git a/DarockBili Watch App/PersonalCenter/PersonAccountView.swift b/DarockBili Watch App/PersonalCenter/PersonAccountView.swift index f8d773157..e0bb4517a 100644 --- a/DarockBili Watch App/PersonalCenter/PersonAccountView.swift +++ b/DarockBili Watch App/PersonalCenter/PersonAccountView.swift @@ -234,10 +234,10 @@ struct PersonAccountView: View { isNetworkFixPresented = true } } - DarockKit.Network.shared.requestString("https://api.darock.top/bili/wbi/sign/\("mid=\(dedeUserID)".base64Encoded())") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr.apiFixed()) - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/acc/info?\(respStr.apiFixed())", headers: headers) { respJson, isSuccess in + biliWbiSign(paramEncoded: "mid=\(dedeUserID)".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/acc/info?\(signed)", headers: headers) { respJson, isSuccess in if isSuccess { debugPrint(respJson) userFaceUrl = respJson["data"]["face"].string ?? "E" diff --git a/DarockBili Watch App/PersonalCenter/UserDetailView.swift b/DarockBili Watch App/PersonalCenter/UserDetailView.swift index 901cfc3ed..400923b8f 100644 --- a/DarockBili Watch App/PersonalCenter/UserDetailView.swift +++ b/DarockBili Watch App/PersonalCenter/UserDetailView.swift @@ -146,10 +146,10 @@ struct UserDetailView: View { let headers: HTTPHeaders = [ "cookie": "SESSDATA=\(sessdata);" ] - DarockKit.Network.shared.requestString("https://api.darock.top/bili/wbi/sign/\("mid=\(uid)".base64Encoded())") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr.apiFixed()) - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/acc/info?\(respStr.apiFixed())", headers: headers) { respJson, isSuccess in + biliWbiSign(paramEncoded: "mid=\(uid)".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/acc/info?\(signed)", headers: headers) { respJson, isSuccess in if isSuccess { debugPrint(respJson) userFaceUrl = respJson["data"]["face"].string ?? "E" @@ -563,10 +563,10 @@ struct UserDetailView: View { "cookie": "SESSDATA=\(sessdata);", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" ] - DarockKit.Network.shared.requestString("https://api.darock.top/bili/wbi/sign/\("mid=\(uid)&ps=50&pn=\(videoNowPage)".base64Encoded())") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr.apiFixed()) - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/arc/search?\(respStr.apiFixed())", headers: headers) { respJson, isSuccess in + biliWbiSign(paramEncoded: "mid=\(uid)&ps=50&pn=\(videoNowPage)".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/arc/search?\(signed)", headers: headers) { respJson, isSuccess in if isSuccess { debugPrint(respJson) if (respJson["code"].int ?? 0) != 0 { @@ -594,12 +594,13 @@ struct UserDetailView: View { func RefreshArticles() { articles = [[String: String]]() let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata);" + "cookie": "SESSDATA=\(sessdata);", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" ] - DarockKit.Network.shared.requestString("https://api.darock.top/bili/wbi/sign/\("mid=\(uid)&ps=30&pn=\(articleNowPage)&sort=publish_time&platform=web".base64Encoded())") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr.apiFixed()) - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/article?\(respStr.apiFixed())", headers: headers) { respJson, isSuccess in + biliWbiSign(paramEncoded: "mid=\(uid)&ps=30&pn=\(articleNowPage)&sort=publish_time&platform=web".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/space/wbi/article?\(signed)", headers: headers) { respJson, isSuccess in if isSuccess { debugPrint(respJson) if (respJson["code"].int ?? 0) != 0 { From 0897bf1c44746ef6795fb5e4cce115f7883b98fc Mon Sep 17 00:00:00 2001 From: Mark Chan <45706356+WindowsMEMZ@users.noreply.github.com> Date: Thu, 7 Dec 2023 00:19:20 +0800 Subject: [PATCH 3/4] Update UserDetailView.swift --- DarockBili Watch App/PersonalCenter/UserDetailView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DarockBili Watch App/PersonalCenter/UserDetailView.swift b/DarockBili Watch App/PersonalCenter/UserDetailView.swift index 400923b8f..6906d2d29 100644 --- a/DarockBili Watch App/PersonalCenter/UserDetailView.swift +++ b/DarockBili Watch App/PersonalCenter/UserDetailView.swift @@ -560,6 +560,7 @@ struct UserDetailView: View { func RefreshVideos() { videos = [[String: String]]() let headers: HTTPHeaders = [ + "accept-language": "en,zh-CN;q=0.9,zh;q=0.8", "cookie": "SESSDATA=\(sessdata);", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" ] From 38d94c879b25885424d312ebaa7b4cb62871e155 Mon Sep 17 00:00:00 2001 From: Mark Chan <45706356+WindowsMEMZ@users.noreply.github.com> Date: Sun, 10 Dec 2023 01:10:20 +0800 Subject: [PATCH 4/4] Add local av bv id calculation logic (#22) * Update CodeExt.swift * Update VideoCommentsView.swift * Update CodeExt.swift * Update status-check.yml Updated checker with building logs shader * Update status-check.yml * Update status-check.yml * Update status-check.yml * Update CodeExt.swift * Update CodeExt.swift Updated for new ver logic * Update CodeExt.swift * Update VideoCommentsView.swift * Update VideoCommentsView.swift * Update VideoCommentsView.swift * Update CodeExt.swift * Update CodeExt.swift --- DarockBili Watch App/Extension/CodeExt.swift | 53 ++++++++ .../Video/VideoCommentsView.swift | 128 ++++++++---------- 2 files changed, 112 insertions(+), 69 deletions(-) diff --git a/DarockBili Watch App/Extension/CodeExt.swift b/DarockBili Watch App/Extension/CodeExt.swift index 6a2e8ad3a..bf47115f8 100644 --- a/DarockBili Watch App/Extension/CodeExt.swift +++ b/DarockBili Watch App/Extension/CodeExt.swift @@ -211,6 +211,59 @@ func biliWbiSign(paramEncoded: String, completion: @escaping (String?) -> Void) } } +// AV & BV ID Convert Logic +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 +} + postfix operator ++ postfix operator -- prefix operator ++ diff --git a/DarockBili Watch App/Video/VideoCommentsView.swift b/DarockBili Watch App/Video/VideoCommentsView.swift index d4f685727..321aab315 100644 --- a/DarockBili Watch App/Video/VideoCommentsView.swift +++ b/DarockBili Watch App/Video/VideoCommentsView.swift @@ -56,7 +56,7 @@ struct VideoCommentsView: View { @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" @AppStorage("SESSDATA") var sessdata = "" @AppStorage("bili_jct") var biliJct = "" - @State var avid = -1 + @State var avid: UInt64 = 0 @State var comments = [[String: String]]() @State var sepTexts = [[String]]() @State var emojiUrls = [[String]]() @@ -231,74 +231,68 @@ struct VideoCommentsView: View { } func ContinueLoadComment() { - DarockKit.Network.shared.requestString("https://api.darock.top/bili/toav/\(oid)") { respStr, isSuccess in + avid = bv2av(bvid: oid) + debugPrint(avid) + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata);" + ] + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/v2/reply?type=1&oid=\(avid)&sort=1&ps=20&pn=\(nowPage)", headers: headers) { respJson, isSuccess in if isSuccess { - avid = Int(respStr)! - debugPrint(avid) - let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata);" - ] - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/v2/reply?type=1&oid=\(avid)&sort=1&ps=20&pn=\(nowPage)", headers: headers) { respJson, isSuccess in - if isSuccess { - debugPrint(respJson) - let replies = respJson["data"]["replies"] - var calNum = 0 - for reply in replies { - isSenderDetailsPresented.append(false) - commentOffsets.append(20) - let repliesInComment = reply.1["replies"] - commentReplies.append([]) - for sigReply in repliesInComment { - commentReplies[calNum].append(["Text": sigReply.1["content"]["message"].string ?? "[加载失败]", "Sender": sigReply.1["member"]["uname"].string ?? "[加载失败]", "SenderPic": sigReply.1["member"]["avatar"].string ?? "E", "SenderID": sigReply.1["member"]["mid"].string ?? "E", "IP": sigReply.1["reply_control"]["location"].string ?? "", "UserAction": String(sigReply.1["action"].int ?? 0), "Rpid": String(sigReply.1["rpid"].int ?? -1), "Like": String(sigReply.1["like"].int ?? -1)]) - } - let text = reply.1["content"]["message"].string ?? "[加载失败]" - comments.append(["Text": text, "Sender": reply.1["member"]["uname"].string ?? "[加载失败]", "SenderPic": reply.1["member"]["avatar"].string ?? "E", "SenderID": reply.1["member"]["mid"].string ?? "E", "IP": reply.1["reply_control"]["location"].string ?? "", "UserAction": String(reply.1["action"].int ?? 0), "Rpid": String(reply.1["rpid"].int ?? -1), "Like": String(reply.1["like"].int ?? -1)]) -// sepTexts.append([]) -// isEmoted.append(false) -// // 文本中包含表情 -// if text.range(of: "\\[(.*?)\\]", options: .regularExpression) != nil { -// let regex = try! NSRegularExpression(pattern: "\\[(.*?)\\]") -// debugPrint("Contains") -// // 分割文本,同时去除表情 -// let tmpSpdText = regex.stringByReplacingMatches(in: text, range: NSRange(text.startIndex..