Skip to content

Commit

Permalink
feat: 支持评论区bvid跳转
Browse files Browse the repository at this point in the history
  • Loading branch information
yichengchen committed Dec 13, 2024
1 parent 0dca915 commit e49f52f
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 2 deletions.
4 changes: 4 additions & 0 deletions BilibiliLive.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
499C76162931A7AE003160FB /* SwiftyXMLParser in Frameworks */ = {isa = PBXBuildFile; productRef = 499C76152931A7AE003160FB /* SwiftyXMLParser */; };
49A441CD293F6DFD0007606C /* FollowUpsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A441CC293F6DFD0007606C /* FollowUpsViewController.swift */; };
49ADA8392D0BCD79004801EF /* MainActor+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ADA8382D0BCD71004801EF /* MainActor+Safe.swift */; };
49ADA83D2D0C0FC2004801EF /* BvidConvertor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ADA83C2D0C0FC2004801EF /* BvidConvertor.swift */; };
49D250A02C118FA700173908 /* URLPlayPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D2509F2C118FA700173908 /* URLPlayPlugin.swift */; };
49D250A22C11A82B00173908 /* AVPlayerMetaUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D250A12C11A82B00173908 /* AVPlayerMetaUtils.swift */; };
49D39F28263AD40000F14497 /* WebRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D39F27263AD40000F14497 /* WebRequest.swift */; };
Expand Down Expand Up @@ -299,6 +300,7 @@
499C760E2930E068003160FB /* NVASocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NVASocket.swift; sourceTree = "<group>"; };
49A441CC293F6DFD0007606C /* FollowUpsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowUpsViewController.swift; sourceTree = "<group>"; };
49ADA8382D0BCD71004801EF /* MainActor+Safe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainActor+Safe.swift"; sourceTree = "<group>"; };
49ADA83C2D0C0FC2004801EF /* BvidConvertor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BvidConvertor.swift; sourceTree = "<group>"; };
49D2509F2C118FA700173908 /* URLPlayPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLPlayPlugin.swift; sourceTree = "<group>"; };
49D250A12C11A82B00173908 /* AVPlayerMetaUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerMetaUtils.swift; sourceTree = "<group>"; };
49D39F27263AD40000F14497 /* WebRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRequest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -634,6 +636,7 @@
49D39F26263AD3F100F14497 /* Request */ = {
isa = PBXGroup;
children = (
49ADA83C2D0C0FC2004801EF /* BvidConvertor.swift */,
0A41EE1A2A63102B0066444C /* dm.pb.swift */,
0A41EE1B2A63102B0066444C /* dmView.pb.swift */,
49D39F27263AD40000F14497 /* WebRequest.swift */,
Expand Down Expand Up @@ -1011,6 +1014,7 @@
F927ED8826103CFB00EAB8E3 /* DanmakuTextCell.swift in Sources */,
0A41EE1C2A63102B0066444C /* dm.pb.swift in Sources */,
F9171D6429010DF1002868C7 /* FeedCollectionViewCell.swift in Sources */,
49ADA83D2D0C0FC2004801EF /* BvidConvertor.swift in Sources */,
F927ED902610A5E900EAB8E3 /* CookieManager.swift in Sources */,
F927ED792610395400EAB8E3 /* DanmakuCellModel.swift in Sources */,
497CF2372C16EDE5006E1488 /* BVideoClipsPlugin.swift in Sources */,
Expand Down
56 changes: 54 additions & 2 deletions BilibiliLive/Component/Video/ReplyDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ReplyDetailViewController: UIViewController {
private var replyLabel: UIButton!
private var replyCollectionView: UICollectionView!
private var imageStackView: UIStackView!
private var buttonStackView: UIStackView!

private let reply: Replys.Reply

Expand Down Expand Up @@ -44,10 +45,26 @@ class ReplyDetailViewController: UIViewController {
}
imageStackView.addArrangedSubview(imageView)
}

reply.content.jump_url?.forEach { url, jump in
guard let bvId = ReplyUrlBVParser.parser(url: url) else { return }
let button = BLCustomTextButton()
button.title = jump.title
button.onPrimaryAction = { [weak self] _ in
self?.jumpLink(bvid: bvId)
}
buttonStackView.addArrangedSubview(button)
}
}

// MARK: - Private

private func jumpLink(bvid: String) {
let aid = BvidConvertor.bv2av(bvid: bvid)
let detailVC = VideoDetailViewController.create(aid: Int(aid), cid: nil)
detailVC.present(from: self)
}

private func setUpViews() {
scrollView = {
let scroll = UIScrollView()
Expand Down Expand Up @@ -89,11 +106,15 @@ class ReplyDetailViewController: UIViewController {
label.titleLabel?.textAlignment = .left
label.titleLabel?.font = .preferredFont(forTextStyle: .headline)
label.contentHorizontalAlignment = .left
label.setContentCompressionResistancePriority(.required, for: .vertical)
label.snp.makeConstraints { make in
make.top.equalTo(self.titleLabel.snp.bottom).offset(60)
make.leading.equalTo(contentView.snp.leadingMargin)
make.trailing.equalTo(contentView.snp.trailingMargin)
}
label.titleLabel?.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
}

return label
}()
Expand All @@ -103,7 +124,7 @@ class ReplyDetailViewController: UIViewController {
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.spacing = 10
contentView.addSubview(stackView) // 改为添加到 contentView
contentView.addSubview(stackView)

stackView.snp.makeConstraints { make in
make.top.equalTo(self.replyLabel.snp.bottom).offset(60)
Expand All @@ -113,6 +134,21 @@ class ReplyDetailViewController: UIViewController {
return stackView
}()

buttonStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .leading
stackView.spacing = 10
stackView.distribution = .equalSpacing
contentView.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.top.equalTo(self.imageStackView.snp.bottom).offset(60)
make.leading.equalTo(contentView.snp.leadingMargin)
make.trailing.lessThanOrEqualTo(contentView.snp.trailingMargin)
}
return stackView
}()

replyCollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSize(width: 582, height: 360)
Expand All @@ -128,7 +164,7 @@ class ReplyDetailViewController: UIViewController {

collectionView.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
make.top.equalTo(self.imageStackView.snp.bottom).offset(60)
make.top.equalTo(self.buttonStackView.snp.bottom).offset(60)
make.height.width.equalTo(360)
make.bottom.equalToSuperview()
}
Expand Down Expand Up @@ -163,3 +199,19 @@ extension ReplyDetailViewController: UICollectionViewDataSource, UICollectionVie
present(detail, animated: true)
}
}

enum ReplyUrlBVParser {
static func parser(url: String) -> String? {
if url.hasPrefix("BV"), url.count == 12 {
return url
}
if url.hasPrefix("https://www.bilibili.com/video/") {
// get bvid in url
guard let url = URL(string: url),
let bvid = url.path.split(separator: "/").filter({ !$0.isEmpty }).last
else { return nil }
return parser(url: String(bvid))
}
return nil
}
}
4 changes: 4 additions & 0 deletions BilibiliLive/Component/View/BLButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class BLCustomButton: BLButton {
@MainActor
class BLCustomTextButton: BLButton {
private let titleLabel = UILabel()
var object: Any?

@IBInspectable var title: String? {
didSet { titleLabel.text = title }
Expand Down Expand Up @@ -145,6 +146,8 @@ class BLButton: UIControl {
fileprivate let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
private let selectedWhiteView = UIView()

var onPrimaryAction: ((BLButton) -> Void)?

override init(frame: CGRect) {
super.init(frame: frame)
setup()
Expand Down Expand Up @@ -182,6 +185,7 @@ class BLButton: UIControl {
super.pressesEnded(presses, with: event)
if presses.first?.type == .select {
sendActions(for: .primaryActionTriggered)
onPrimaryAction?(self)
}
}

Expand Down
61 changes: 61 additions & 0 deletions BilibiliLive/Request/BvidConvertor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// BvidConvertor.swift
// BilibiliLive
//
// Created by yicheng on 2024/12/13.
//

enum BvidConvertor {
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/bvid_desc.md
private static let XOR_CODE: UInt64 = 23442827791579
private static let MASK_CODE: UInt64 = 2251799813685247
private static let MAX_AID: UInt64 = 1 << 51

private static 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]

private static let BASE: UInt64 = 58
private static let BV_LEN: Int = 12
private static let PREFIX: String = "BV1"

static 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)
}

static 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
}
}
5 changes: 5 additions & 0 deletions BilibiliLive/Request/WebRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -728,10 +728,15 @@ struct Replys: Codable, Hashable {
let url: String
}

struct JumpUrl: Codable, Hashable {
let title: String
}

struct Content: Codable, Hashable {
let message: String
let pictures: [Picture]?
let emote: [String: Emote]?
let jump_url: [String: JumpUrl]?

struct Picture: Codable, Hashable {
let img_src: String
Expand Down

0 comments on commit e49f52f

Please sign in to comment.