diff --git a/BiliFont.ttf b/BiliFont.ttf new file mode 100644 index 000000000..0b3a7d2ae Binary files /dev/null and b/BiliFont.ttf differ diff --git a/DarockBili Watch App/Errors/DarockFeedback/FeedbackDetailsView.swift b/DarockBili Watch App/Errors/DarockFeedback/FeedbackDetailsView.swift deleted file mode 100644 index e868647bf..000000000 --- a/DarockBili Watch App/Errors/DarockFeedback/FeedbackDetailsView.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// FeedbackDetailsView.swift -// DarockBili Watch App -// -// Created by WindowsMEMZ on 2023/7/13. -// - -import SwiftUI -import DarockKit - -struct FeedbackDetailsView: View { - var fbName: String - @AppStorage("DarockIDAccount") var darockIdAccount = "" - var body: some View { - ScrollView { - VStack { - - } - } - .onAppear { - DarockKit.Network.shared.requestString("https://api.darock.top/feedback/getDetail/\(darockIdAccount)/\(fbName)") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr.apiFixed()) - } - } - } - } -} - -struct FeedbackDetailsView_Previews: PreviewProvider { - static var previews: some View { - FeedbackDetailsView(fbName: "test") - } -} diff --git a/DarockBili Watch App/Errors/DarockFeedback/FeedbackEnterView.swift b/DarockBili Watch App/Errors/DarockFeedback/FeedbackEnterView.swift deleted file mode 100644 index 42b4b90ba..000000000 --- a/DarockBili Watch App/Errors/DarockFeedback/FeedbackEnterView.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// FeedbackEnterView.swift -// DarockBili Watch App -// -// Created by WindowsMEMZ on 2023/7/13. -// - -import SwiftUI - -struct FeedbackEnterView: View { - @AppStorage("DarockIDAccount") var darockIdAccount = "" - var body: some View { - if darockIdAccount == "" { - FeedbackLoginView() - } else { - FeedbackMainView() - } - } -} - -struct FeedbackEnterView_Previews: PreviewProvider { - static var previews: some View { - FeedbackEnterView() - } -} - diff --git a/DarockBili Watch App/Errors/DarockFeedback/FeedbackLoginView.swift b/DarockBili Watch App/Errors/DarockFeedback/FeedbackLoginView.swift deleted file mode 100644 index d7723e528..000000000 --- a/DarockBili Watch App/Errors/DarockFeedback/FeedbackLoginView.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// FeedbackLoginView.swift -// DarockBili Watch App -// -// Created by WindowsMEMZ on 2023/7/13. -// - -import SwiftUI -import DarockKit - -struct FeedbackLoginView: View { - @AppStorage("DarockIDAccount") var darockIdAccount = "" - @State var accountCache = "" - @State var passwdCache = "" - @State var tipText = "" - @State var isLoading = false - @State var isRegisterPresented = false - var body: some View { - ScrollView { - VStack { - Group { - Text("DarockID.login.title") - .font(.system(size: 18, weight: .bold)) - TextField("DarockID.account", text: $accountCache) - .autocorrectionDisabled() - .textInputAutocapitalization(.never) - SecureField("DarockID.password", text: $passwdCache) - Button(action: { - isLoading = true - DarockKit.Network.shared.requestString("https://api.darock.top/user/login/\(accountCache)/\(passwdCache)") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr) - if respStr.apiFixed() == "Success" { - darockIdAccount = accountCache - } else { - tipText = String(localized: "DarockID.incorrect") - } - } else { - tipText = String(localized: "DarockID.unable-to-connect") - } - isLoading = false - } - }, label: { - if !isLoading { - Text("DarockID.login") - } else { - ProgressView() - } - }) - .disabled(isLoading) - Button(action: { - isRegisterPresented = true - }, label: { - Text("DarockID.register") - }) - .sheet(isPresented: $isRegisterPresented, content: {RegisterView()}) - Text(tipText) - } - Group { - Spacer() - .frame(height: 20) - NavigationLink(destination: {FeedbackView()}, label: { - Text("DarockID.feedback-without-logging-in") - }) - } - } - } - } - - struct RegisterView: View { - @State var mailCache = "" - @State var mailCodeCache = "" - @State var serverMailCode = "" - @State var passwdCache = "" - @State var passwd2Cache = "" - @State var tipText = "" - var body: some View { - ScrollView { - VStack { - Text("DarockID.register.title") - .font(.system(size: 18, weight: .bold)) - TextField("DarockID.email", text: $mailCache) - .autocorrectionDisabled() - .textInputAutocapitalization(.never) - .onSubmit { - serverMailCode = "" - } - Button(action: { - DarockKit.Network.shared.requestString("https://api.darock.top/user/mail/send/\(mailCache)") { respStr, isSuccess in - if isSuccess { - serverMailCode = respStr - } - } - }, label: { - Text("DarockID.verification-code.send") - }) - .disabled(mailCache.isEmpty) - TextField("DarockID.verification-code", text: $mailCodeCache) - .autocorrectionDisabled() - .textInputAutocapitalization(.never) - SecureField("DarockID.password", text: $passwdCache) - SecureField("DarockID.password.confirm", text: $passwdCache) - Button(action: { - if mailCodeCache == serverMailCode { - if passwdCache == passwd2Cache { - DarockKit.Network.shared.requestString("https://api.darock.top/user/reg/\(mailCache)/\(passwdCache)") { respStr, isSuccess in - if isSuccess { - if respStr.apiFixed() == "Success" { - tipText = String(localized: "DarockID.register.success") - } - } - } - } else { - tipText = String(localized: "DarockID.password.unmatch") - } - } else { - tipText = String(localized: "DarockID.code.unmatch") - } - }, label: { - Text("DarockID.register") - }) - Text(tipText) - } - } - } - } -} - -struct FeedbackLoginView_Previews: PreviewProvider { - static var previews: some View { - FeedbackLoginView() - } -} - diff --git a/DarockBili Watch App/Errors/DarockFeedback/FeedbackMainView.swift b/DarockBili Watch App/Errors/DarockFeedback/FeedbackMainView.swift deleted file mode 100644 index aff6ab48d..000000000 --- a/DarockBili Watch App/Errors/DarockFeedback/FeedbackMainView.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// FeedbackMainView.swift -// DarockBili Watch App -// -// Created by WindowsMEMZ on 2023/7/13. -// - -import SwiftUI -import DarockKit - -struct FeedbackMainView: View { - @AppStorage("DarockIDAccount") var darockIdAccount = "" - @State var feedbackNames = [String]() - @State var isNone = false - var body: some View { - List { - if !isNone { - if feedbackNames.count != 0 { - ForEach(0...feedbackNames.count - 1, id: \.self) { i in - NavigationLink(destination: {FeedbackDetailsView(fbName: feedbackNames[i])}, label: { - Text(feedbackNames[i]) - }) - } - } else { - ProgressView() - } - } else { - Text("Feedback.nothing") - } - } -// .toolbar { -// ToolbarItem(placement: .topBarTrailing) { -// Button(action: { -// -// }, label: { -// Image(systemName: "square.and.pencil") -// }) -// } -// } - .onAppear { - DarockKit.Network.shared.requestString("https://api.darock.top/feedback/get/\(darockIdAccount)") { respStr, isSuccess in - if isSuccess { - debugPrint(respStr) - if respStr.apiFixed() != "None" { - let namesSpd = respStr.apiFixed().split(separator: "|") - for name in namesSpd { - feedbackNames.append(String(name)) - } - } else { - isNone = true - } - } - } - } - } -} - -struct FeedbackMainView_Previews: PreviewProvider { - static var previews: some View { - FeedbackMainView() - } -} diff --git a/DarockBili.xcodeproj/project.pbxproj b/DarockBili.xcodeproj/project.pbxproj index 701d8d7fb..192f5f6c8 100644 --- a/DarockBili.xcodeproj/project.pbxproj +++ b/DarockBili.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8C0557DF2B791B84009D9CD0 /* AZVideoPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 8C0557DE2B791B84009D9CD0 /* AZVideoPlayer */; }; 8C2337FA2B63DBF3009665B1 /* BPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 8C2337F92B63DBF3009665B1 /* BPlayer */; }; 8C372ED52B6CF079002033A1 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 8C372ED42B6CF079002033A1 /* Starscream */; }; 8C372ED72B6CF08D002033A1 /* LiveMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C372ED62B6CF08D002033A1 /* LiveMessagesView.swift */; }; @@ -16,6 +17,153 @@ 8C72E3392B66A76A0087486E /* DynamicSendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C72E3382B66A76A0087486E /* DynamicSendView.swift */; }; 8C81A8EA2B620D130045E986 /* BPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 8C81A8E92B620D130045E986 /* BPlayer */; }; 8C8CA05A2B6108460020DEDD /* Bangumi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8CA0592B6108460020DEDD /* Bangumi.swift */; }; + 8CA388992B789A0300F5F91F /* AZVideoPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA388982B789A0300F5F91F /* AZVideoPlayer */; }; + 8CA3889F2B78B1E300F5F91F /* AZVideoPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA3889E2B78B1E300F5F91F /* AZVideoPlayer */; }; + 8CA388A12B78B5D800F5F91F /* BiliFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8CA388A02B78B5D800F5F91F /* BiliFont.ttf */; }; + 8CA388A22B78B5D800F5F91F /* BiliFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8CA388A02B78B5D800F5F91F /* BiliFont.ttf */; }; + 8CA388A32B78B5D800F5F91F /* BiliFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8CA388A02B78B5D800F5F91F /* BiliFont.ttf */; }; + 8CA7CBC32B77AC4A008E587F /* MeowBiliApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CBC22B77AC4A008E587F /* MeowBiliApp.swift */; }; + 8CA7CBC52B77AC4A008E587F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CBC42B77AC4A008E587F /* ContentView.swift */; }; + 8CA7CBC72B77AC4C008E587F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8CA7CBC62B77AC4C008E587F /* Assets.xcassets */; }; + 8CA7CBCA2B77AC4C008E587F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8CA7CBC92B77AC4C008E587F /* Preview Assets.xcassets */; }; + 8CA7CBD02B77AC4C008E587F /* 喵哩喵哩.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 8CA7CBCF2B77AC4C008E587F /* 喵哩喵哩.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 8CA7CBE52B77AC8A008E587F /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBE42B77AC8A008E587F /* Alamofire */; }; + 8CA7CBE72B77AC8A008E587F /* Marquee in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBE62B77AC8A008E587F /* Marquee */; }; + 8CA7CBE92B77AC8A008E587F /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBE82B77AC8A008E587F /* SwiftDate */; }; + 8CA7CBEB2B77AC8A008E587F /* SFSymbol in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBEA2B77AC8A008E587F /* SFSymbol */; }; + 8CA7CBED2B77AC8A008E587F /* ZipArchive in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBEC2B77AC8A008E587F /* ZipArchive */; }; + 8CA7CBEF2B77AC8A008E587F /* EFQRCode in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBEE2B77AC8A008E587F /* EFQRCode */; }; + 8CA7CBF12B77AC8A008E587F /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBF02B77AC8A008E587F /* SDWebImageSwiftUI */; }; + 8CA7CBF32B77AC8A008E587F /* SDWebImageWebPCoder in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBF22B77AC8A008E587F /* SDWebImageWebPCoder */; }; + 8CA7CBF52B77AC8A008E587F /* SDWebImageSVGCoder in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBF42B77AC8A008E587F /* SDWebImageSVGCoder */; }; + 8CA7CBF72B77AC8A008E587F /* SDWebImagePDFCoder in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBF62B77AC8A008E587F /* SDWebImagePDFCoder */; }; + 8CA7CBF92B77AC8A008E587F /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBF82B77AC8A008E587F /* SwiftSoup */; }; + 8CA7CBFB2B77AC8A008E587F /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBFA2B77AC8A008E587F /* CachedAsyncImage */; }; + 8CA7CBFD2B77AC8A008E587F /* Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBFC2B77AC8A008E587F /* Dynamic */; }; + 8CA7CBFF2B77AC8A008E587F /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CBFE2B77AC8A008E587F /* SwiftyJSON */; }; + 8CA7CC012B77AC8A008E587F /* DarockKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC002B77AC8A008E587F /* DarockKit */; }; + 8CA7CC032B77AC8A008E587F /* BPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC022B77AC8A008E587F /* BPlayer */; }; + 8CA7CC052B77AC8A008E587F /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC042B77AC8A008E587F /* Starscream */; }; + 8CA7CC072B77AC93008E587F /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC062B77AC93008E587F /* Alamofire */; }; + 8CA7CC092B77AC93008E587F /* Marquee in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC082B77AC93008E587F /* Marquee */; }; + 8CA7CC0B2B77AC93008E587F /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC0A2B77AC93008E587F /* SwiftDate */; }; + 8CA7CC0F2B77AC93008E587F /* ZipArchive in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC0E2B77AC93008E587F /* ZipArchive */; }; + 8CA7CC112B77AC93008E587F /* EFQRCode in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC102B77AC93008E587F /* EFQRCode */; }; + 8CA7CC132B77AC93008E587F /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC122B77AC93008E587F /* SDWebImageSwiftUI */; }; + 8CA7CC152B77AC93008E587F /* SDWebImageWebPCoder in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC142B77AC93008E587F /* SDWebImageWebPCoder */; }; + 8CA7CC172B77AC93008E587F /* SDWebImageSVGCoder in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC162B77AC93008E587F /* SDWebImageSVGCoder */; }; + 8CA7CC192B77AC93008E587F /* SDWebImagePDFCoder in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC182B77AC93008E587F /* SDWebImagePDFCoder */; }; + 8CA7CC1B2B77AC93008E587F /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC1A2B77AC93008E587F /* SwiftSoup */; }; + 8CA7CC1D2B77AC93008E587F /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC1C2B77AC93008E587F /* CachedAsyncImage */; }; + 8CA7CC1F2B77AC93008E587F /* Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC1E2B77AC93008E587F /* Dynamic */; }; + 8CA7CC212B77AC93008E587F /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC202B77AC93008E587F /* SwiftyJSON */; }; + 8CA7CC232B77AC93008E587F /* DarockKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CC222B77AC93008E587F /* DarockKit */; }; + 8CA7CC292B77ACE8008E587F /* SkinDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A1F2B4FC24A002CCEB2 /* SkinDownloadView.swift */; }; + 8CA7CC2A2B77ACE8008E587F /* WatchLaterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A302B4FC24A002CCEB2 /* WatchLaterView.swift */; }; + 8CA7CC2B2B77ACE8008E587F /* UIExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689FC2B4FC249002CCEB2 /* UIExt.swift */; }; + 8CA7CC2C2B77ACE8008E587F /* backtrace.c in Sources */ = {isa = PBXBuildFile; fileRef = B44689ED2B4FC249002CCEB2 /* backtrace.c */; }; + 8CA7CC2D2B77ACE8008E587F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A032B4FC249002CCEB2 /* ContentView.swift */; }; + 8CA7CC2E2B77ACE8008E587F /* DarockBiliApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689FF2B4FC249002CCEB2 /* DarockBiliApp.swift */; }; + 8CA7CC2F2B77ACE8008E587F /* DownloadObj.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C72E32D2B6607240087486E /* DownloadObj.swift */; }; + 8CA7CC312B77ACE8008E587F /* ImageViewerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A062B4FC249002CCEB2 /* ImageViewerView.swift */; }; + 8CA7CC322B77ACE8008E587F /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A012B4FC249002CCEB2 /* SearchView.swift */; }; + 8CA7CC332B77ACE8008E587F /* ErrorGetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F52B4FC249002CCEB2 /* ErrorGetView.swift */; }; + 8CA7CC342B77ACE8008E587F /* BangumiPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2565AF82B52C076000E01ED /* BangumiPlayerView.swift */; }; + 8CA7CC352B77ACE8008E587F /* CodingTime.m in Sources */ = {isa = PBXBuildFile; fileRef = B4468A252B4FC24A002CCEB2 /* CodingTime.m */; }; + 8CA7CC362B77ACE8008E587F /* Passthroughs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C72E32F2B6621E00087486E /* Passthroughs.swift */; }; + 8CA7CC382B77ACE8008E587F /* VideoDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A102B4FC24A002CCEB2 /* VideoDetailView.swift */; }; + 8CA7CC392B77ACE8008E587F /* OfflineMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A0A2B4FC249002CCEB2 /* OfflineMainView.swift */; }; + 8CA7CC3A2B77ACE8008E587F /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A1A2B4FC24A002CCEB2 /* SettingsView.swift */; }; + 8CA7CC3B2B77ACE8008E587F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4468A132B4FC24A002CCEB2 /* Preview Assets.xcassets */; }; + 8CA7CC3C2B77ACE8008E587F /* LiveDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2565AFA2B52C114000E01ED /* LiveDetailView.swift */; }; + 8CA7CC3D2B77ACE8008E587F /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A022B4FC249002CCEB2 /* MainView.swift */; }; + 8CA7CC3E2B77ACE8008E587F /* FavoriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A2B2B4FC24A002CCEB2 /* FavoriteView.swift */; }; + 8CA7CC3F2B77ACE8008E587F /* BangumiDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2565AF42B52C017000E01ED /* BangumiDownloadView.swift */; }; + 8CA7CC402B77ACE8008E587F /* Bangumi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8CA0592B6108460020DEDD /* Bangumi.swift */; }; + 8CA7CC412B77ACE8008E587F /* AVExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = B4468A242B4FC24A002CCEB2 /* AVExtension.m */; }; + 8CA7CC422B77ACE8008E587F /* Backtrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689EE2B4FC249002CCEB2 /* Backtrace.swift */; }; + 8CA7CC432B77ACE8008E587F /* LiveMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C372ED62B6CF08D002033A1 /* LiveMessagesView.swift */; }; + 8CA7CC442B77ACE8008E587F /* CommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A0F2B4FC24A002CCEB2 /* CommentsView.swift */; }; + 8CA7CC452B77ACE8008E587F /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A2F2B4FC24A002CCEB2 /* HistoryView.swift */; }; + 8CA7CC462B77ACE8008E587F /* AppFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F82B4FC249002CCEB2 /* AppFileManager.swift */; }; + 8CA7CC472B77ACE8008E587F /* LyricerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A0D2B4FC24A002CCEB2 /* LyricerView.swift */; }; + 8CA7CC482B77ACE8008E587F /* LivePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689FE2B4FC249002CCEB2 /* LivePlayerView.swift */; }; + 8CA7CC492B77ACE8008E587F /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A1B2B4FC24A002CCEB2 /* LoginView.swift */; }; + 8CA7CC4A2B77ACE8008E587F /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A112B4FC24A002CCEB2 /* AudioPlayerView.swift */; }; + 8CA7CC4B2B77ACE8008E587F /* UserDynamicMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A072B4FC249002CCEB2 /* UserDynamicMainView.swift */; }; + 8CA7CC4C2B77ACE8008E587F /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A0C2B4FC24A002CCEB2 /* VideoPlayerView.swift */; }; + 8CA7CC4D2B77ACE8008E587F /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A202B4FC24A002CCEB2 /* NoticeView.swift */; }; + 8CA7CC4E2B77ACE8008E587F /* UserDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A2E2B4FC24A002CCEB2 /* UserDetailView.swift */; }; + 8CA7CC4F2B77ACE8008E587F /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A182B4FC24A002CCEB2 /* AboutView.swift */; }; + 8CA7CC502B77ACE8008E587F /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689EF2B4FC249002CCEB2 /* FeedbackView.swift */; }; + 8CA7CC512B77ACE8008E587F /* DynamicDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C72E3332B668B7B0087486E /* DynamicDetailView.swift */; }; + 8CA7CC522B77ACE8008E587F /* VideoDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A0E2B4FC24A002CCEB2 /* VideoDownloadView.swift */; }; + 8CA7CC532B77ACE8008E587F /* BangumiDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2565AF22B52BFDF000E01ED /* BangumiDetailView.swift */; }; + 8CA7CC542B77ACE8008E587F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4468A662B4FC286002CCEB2 /* Assets.xcassets */; }; + 8CA7CC552B77ACE8008E587F /* DynamicSendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C72E3382B66A76A0087486E /* DynamicSendView.swift */; }; + 8CA7CC562B77ACE8008E587F /* DownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A272B4FC24A002CCEB2 /* DownloadsView.swift */; }; + 8CA7CC572B77ACE8008E587F /* PersonAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A2A2B4FC24A002CCEB2 /* PersonAccountView.swift */; }; + 8CA7CC582B77ACE8008E587F /* CodeExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F72B4FC249002CCEB2 /* CodeExt.swift */; }; + 8CA7CC592B77ACE8008E587F /* FollowListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A2C2B4FC24A002CCEB2 /* FollowListView.swift */; }; + 8CA7CC5A2B77ACE8008E587F /* SkinExplorerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A1E2B4FC24A002CCEB2 /* SkinExplorerView.swift */; }; + 8CA7CC5B2B77ACE8008E587F /* ArticleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A2D2B4FC24A002CCEB2 /* ArticleView.swift */; }; + 8CA7CC5C2B77ACE8008E587F /* MemoryWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689EA2B4FC249002CCEB2 /* MemoryWarningView.swift */; }; + 8CA7CC5D2B77ACE8008E587F /* bMessageSendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A292B4FC24A002CCEB2 /* bMessageSendView.swift */; }; + 8CA7CC5F2B77ACE8008E587F /* NetworkFixView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689E82B4FC249002CCEB2 /* NetworkFixView.swift */; }; + 8CA7CC602B77ACE8008E587F /* SignalErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689E92B4FC249002CCEB2 /* SignalErrorView.swift */; }; + 8CA7CC612B77ACE8008E587F /* SkinChooserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4468A1D2B4FC24A002CCEB2 /* SkinChooserView.swift */; }; + 8CA7CC6A2B77AE62008E587F /* Bangumi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC692B77AE62008E587F /* Bangumi.swift */; }; + 8CA7CC6C2B77AE80008E587F /* DownloadObj.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC6B2B77AE80008E587F /* DownloadObj.swift */; }; + 8CA7CC6F2B77AEB4008E587F /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC6E2B77AEB4008E587F /* SearchView.swift */; }; + 8CA7CC712B77AEE4008E587F /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC702B77AEE4008E587F /* MainView.swift */; }; + 8CA7CC772B77AF4C008E587F /* backtrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC762B77AF4C008E587F /* backtrace.c */; }; + 8CA7CC792B77AF79008E587F /* backtrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC782B77AF79008E587F /* backtrace.swift */; }; + 8CA7CC7B2B77AF93008E587F /* NetwokFixView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC7A2B77AF93008E587F /* NetwokFixView.swift */; }; + 8CA7CC7D2B77AFAC008E587F /* SignalErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC7C2B77AFAC008E587F /* SignalErrorView.swift */; }; + 8CA7CC7F2B77AFC5008E587F /* MemoryWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC7E2B77AFC5008E587F /* MemoryWarningView.swift */; }; + 8CA7CC812B77AFDE008E587F /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC802B77AFDE008E587F /* FeedbackView.swift */; }; + 8CA7CC832B77AFF2008E587F /* ErrorGetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC822B77AFF2008E587F /* ErrorGetView.swift */; }; + 8CA7CC862B77B015008E587F /* CodeExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC852B77B015008E587F /* CodeExt.swift */; }; + 8CA7CC882B77B039008E587F /* UIExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC872B77B039008E587F /* UIExt.swift */; }; + 8CA7CC8A2B77B061008E587F /* AppFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC892B77B061008E587F /* AppFileManager.swift */; }; + 8CA7CC8E2B77B094008E587F /* AVExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC8D2B77B094008E587F /* AVExtension.m */; }; + 8CA7CC922B77B0D4008E587F /* CodingTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC912B77B0D4008E587F /* CodingTime.m */; }; + 8CA7CC952B77B0FB008E587F /* SkinChooserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC942B77B0FB008E587F /* SkinChooserView.swift */; }; + 8CA7CC972B77B115008E587F /* SkinExplorerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC962B77B115008E587F /* SkinExplorerView.swift */; }; + 8CA7CC992B77B127008E587F /* SkinDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC982B77B127008E587F /* SkinDownloadView.swift */; }; + 8CA7CC9B2B77B13D008E587F /* Passthroughs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC9A2B77B13D008E587F /* Passthroughs.swift */; }; + 8CA7CC9D2B77B150008E587F /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC9C2B77B150008E587F /* AboutView.swift */; }; + 8CA7CC9F2B77B166008E587F /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CC9E2B77B166008E587F /* SettingsView.swift */; }; + 8CA7CCA12B77B2DC008E587F /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCA02B77B2DC008E587F /* LoginView.swift */; }; + 8CA7CCA32B77B2F3008E587F /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCA22B77B2F2008E587F /* NoticeView.swift */; }; + 8CA7CCA62B77B315008E587F /* CommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCA52B77B314008E587F /* CommentsView.swift */; }; + 8CA7CCAA2B77B340008E587F /* bMessageSendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCA92B77B340008E587F /* bMessageSendView.swift */; }; + 8CA7CCAC2B77B354008E587F /* DownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCAB2B77B354008E587F /* DownloadsView.swift */; }; + 8CA7CCAE2B77B36E008E587F /* PersonAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCAD2B77B36E008E587F /* PersonAccountView.swift */; }; + 8CA7CCB02B77B383008E587F /* FavoriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCAF2B77B383008E587F /* FavoriteView.swift */; }; + 8CA7CCB22B77B398008E587F /* FollowListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCB12B77B398008E587F /* FollowListView.swift */; }; + 8CA7CCB42B77B3AC008E587F /* ArticleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCB32B77B3AC008E587F /* ArticleView.swift */; }; + 8CA7CCB62B77B3BF008E587F /* UserDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCB52B77B3BF008E587F /* UserDetailView.swift */; }; + 8CA7CCB82B77B3D6008E587F /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCB72B77B3D6008E587F /* HistoryView.swift */; }; + 8CA7CCBA2B77B3EA008E587F /* WatchLaterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCB92B77B3EA008E587F /* WatchLaterView.swift */; }; + 8CA7CCBD2B77B40B008E587F /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCBC2B77B40B008E587F /* VideoPlayerView.swift */; }; + 8CA7CCBF2B77B42E008E587F /* LyricerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCBE2B77B42E008E587F /* LyricerView.swift */; }; + 8CA7CCC12B77B44E008E587F /* VideoDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCC02B77B44E008E587F /* VideoDownloadView.swift */; }; + 8CA7CCC32B77B45F008E587F /* VideoDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCC22B77B45F008E587F /* VideoDetailView.swift */; }; + 8CA7CCC52B77B47A008E587F /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCC42B77B47A008E587F /* AudioPlayerView.swift */; }; + 8CA7CCC82B77B49D008E587F /* ImageViewerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCC72B77B49D008E587F /* ImageViewerView.swift */; }; + 8CA7CCCA2B77B4B2008E587F /* UserDynamicMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCC92B77B4B2008E587F /* UserDynamicMainView.swift */; }; + 8CA7CCCC2B77B4CA008E587F /* DynamicDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCCB2B77B4CA008E587F /* DynamicDetailView.swift */; }; + 8CA7CCCE2B77B4DF008E587F /* DynamicSendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCCD2B77B4DF008E587F /* DynamicSendView.swift */; }; + 8CA7CCD12B77B504008E587F /* BangumiDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCD02B77B504008E587F /* BangumiDetailView.swift */; }; + 8CA7CCD32B77B51A008E587F /* BangumiDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCD22B77B51A008E587F /* BangumiDownloadView.swift */; }; + 8CA7CCD52B77B530008E587F /* BangumiPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCD42B77B530008E587F /* BangumiPlayerView.swift */; }; + 8CA7CCD82B77B54F008E587F /* LivePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCD72B77B54F008E587F /* LivePlayerView.swift */; }; + 8CA7CCDA2B77B55F008E587F /* LiveDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCD92B77B55F008E587F /* LiveDetailView.swift */; }; + 8CA7CCDC2B77B571008E587F /* LiveMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CCDB2B77B571008E587F /* LiveMessagesView.swift */; }; + 8CA7CCDF2B77BB5A008E587F /* AlertKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA7CCDE2B77BB5A008E587F /* AlertKit */; }; + 8CA7CCE02B77C3A8008E587F /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = E2B720932B67858E00ABB0A6 /* Localizable.xcstrings */; }; + 8CA7CCE12B77C3A9008E587F /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = E2B720932B67858E00ABB0A6 /* Localizable.xcstrings */; }; B44689D12B4FC15A002CCEB2 /* 喵哩喵哩.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = B44689D02B4FC15A002CCEB2 /* 喵哩喵哩.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; B4468A312B4FC24A002CCEB2 /* NetworkFixView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689E82B4FC249002CCEB2 /* NetworkFixView.swift */; }; B4468A322B4FC24A002CCEB2 /* SignalErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689E92B4FC249002CCEB2 /* SignalErrorView.swift */; }; @@ -23,10 +171,6 @@ B4468A342B4FC24A002CCEB2 /* backtrace.c in Sources */ = {isa = PBXBuildFile; fileRef = B44689ED2B4FC249002CCEB2 /* backtrace.c */; }; B4468A352B4FC24A002CCEB2 /* Backtrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689EE2B4FC249002CCEB2 /* Backtrace.swift */; }; B4468A362B4FC24A002CCEB2 /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689EF2B4FC249002CCEB2 /* FeedbackView.swift */; }; - B4468A372B4FC24A002CCEB2 /* FeedbackLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F12B4FC249002CCEB2 /* FeedbackLoginView.swift */; }; - B4468A382B4FC24A002CCEB2 /* FeedbackEnterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F22B4FC249002CCEB2 /* FeedbackEnterView.swift */; }; - B4468A392B4FC24A002CCEB2 /* FeedbackMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F32B4FC249002CCEB2 /* FeedbackMainView.swift */; }; - B4468A3A2B4FC24A002CCEB2 /* FeedbackDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F42B4FC249002CCEB2 /* FeedbackDetailsView.swift */; }; B4468A3B2B4FC24A002CCEB2 /* ErrorGetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F52B4FC249002CCEB2 /* ErrorGetView.swift */; }; B4468A3C2B4FC24A002CCEB2 /* CodeExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F72B4FC249002CCEB2 /* CodeExt.swift */; }; B4468A3D2B4FC24A002CCEB2 /* AppFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44689F82B4FC249002CCEB2 /* AppFileManager.swift */; }; @@ -89,6 +233,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 8CA7CBD12B77AC4C008E587F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B44689C42B4FC15A002CCEB2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8CA7CBCE2B77AC4C008E587F; + remoteInfo = "MeowBili Watch App"; + }; B44689D22B4FC15A002CCEB2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B44689C42B4FC15A002CCEB2 /* Project object */; @@ -99,6 +250,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 8CA7CBDF2B77AC4D008E587F /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + 8CA7CBD02B77AC4C008E587F /* 喵哩喵哩.app in Embed Watch Content */, + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; B44689E32B4FC15B002CCEB2 /* Embed Watch Content */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -120,6 +282,68 @@ 8C72E3332B668B7B0087486E /* DynamicDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDetailView.swift; sourceTree = ""; }; 8C72E3382B66A76A0087486E /* DynamicSendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicSendView.swift; sourceTree = ""; }; 8C8CA0592B6108460020DEDD /* Bangumi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bangumi.swift; sourceTree = ""; }; + 8CA388A02B78B5D800F5F91F /* BiliFont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BiliFont.ttf; sourceTree = ""; }; + 8CA7CBC02B77AC4A008E587F /* MeowBili.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeowBili.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8CA7CBC22B77AC4A008E587F /* MeowBiliApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeowBiliApp.swift; sourceTree = ""; }; + 8CA7CBC42B77AC4A008E587F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 8CA7CBC62B77AC4C008E587F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8CA7CBC92B77AC4C008E587F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 8CA7CBCF2B77AC4C008E587F /* 喵哩喵哩.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "喵哩喵哩.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8CA7CC622B77AD06008E587F /* MeowBili-Watch-App-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MeowBili-Watch-App-Info.plist"; sourceTree = ""; }; + 8CA7CC632B77AD5B008E587F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 8CA7CC692B77AE62008E587F /* Bangumi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bangumi.swift; sourceTree = ""; }; + 8CA7CC6B2B77AE80008E587F /* DownloadObj.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadObj.swift; sourceTree = ""; }; + 8CA7CC6E2B77AEB4008E587F /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; + 8CA7CC702B77AEE4008E587F /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 8CA7CC742B77AF2E008E587F /* backtrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = backtrace.h; sourceTree = ""; }; + 8CA7CC752B77AF4B008E587F /* MeowBili-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MeowBili-Bridging-Header.h"; sourceTree = ""; }; + 8CA7CC762B77AF4C008E587F /* backtrace.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = backtrace.c; sourceTree = ""; }; + 8CA7CC782B77AF79008E587F /* backtrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = backtrace.swift; sourceTree = ""; }; + 8CA7CC7A2B77AF93008E587F /* NetwokFixView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetwokFixView.swift; sourceTree = ""; }; + 8CA7CC7C2B77AFAC008E587F /* SignalErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalErrorView.swift; sourceTree = ""; }; + 8CA7CC7E2B77AFC5008E587F /* MemoryWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningView.swift; sourceTree = ""; }; + 8CA7CC802B77AFDE008E587F /* FeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackView.swift; sourceTree = ""; }; + 8CA7CC822B77AFF2008E587F /* ErrorGetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorGetView.swift; sourceTree = ""; }; + 8CA7CC852B77B015008E587F /* CodeExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeExt.swift; sourceTree = ""; }; + 8CA7CC872B77B039008E587F /* UIExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExt.swift; sourceTree = ""; }; + 8CA7CC892B77B061008E587F /* AppFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFileManager.swift; sourceTree = ""; }; + 8CA7CC8D2B77B094008E587F /* AVExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AVExtension.m; sourceTree = ""; }; + 8CA7CC8F2B77B0AE008E587F /* AVExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AVExtension.h; sourceTree = ""; }; + 8CA7CC902B77B0C1008E587F /* CodingTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CodingTime.h; sourceTree = ""; }; + 8CA7CC912B77B0D4008E587F /* CodingTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CodingTime.m; sourceTree = ""; }; + 8CA7CC942B77B0FB008E587F /* SkinChooserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkinChooserView.swift; sourceTree = ""; }; + 8CA7CC962B77B115008E587F /* SkinExplorerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkinExplorerView.swift; sourceTree = ""; }; + 8CA7CC982B77B127008E587F /* SkinDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkinDownloadView.swift; sourceTree = ""; }; + 8CA7CC9A2B77B13D008E587F /* Passthroughs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Passthroughs.swift; sourceTree = ""; }; + 8CA7CC9C2B77B150008E587F /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + 8CA7CC9E2B77B166008E587F /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 8CA7CCA02B77B2DC008E587F /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + 8CA7CCA22B77B2F2008E587F /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = ""; }; + 8CA7CCA52B77B314008E587F /* CommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsView.swift; sourceTree = ""; }; + 8CA7CCA92B77B340008E587F /* bMessageSendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = bMessageSendView.swift; sourceTree = ""; }; + 8CA7CCAB2B77B354008E587F /* DownloadsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsView.swift; sourceTree = ""; }; + 8CA7CCAD2B77B36E008E587F /* PersonAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonAccountView.swift; sourceTree = ""; }; + 8CA7CCAF2B77B383008E587F /* FavoriteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteView.swift; sourceTree = ""; }; + 8CA7CCB12B77B398008E587F /* FollowListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowListView.swift; sourceTree = ""; }; + 8CA7CCB32B77B3AC008E587F /* ArticleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleView.swift; sourceTree = ""; }; + 8CA7CCB52B77B3BF008E587F /* UserDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailView.swift; sourceTree = ""; }; + 8CA7CCB72B77B3D6008E587F /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; }; + 8CA7CCB92B77B3EA008E587F /* WatchLaterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchLaterView.swift; sourceTree = ""; }; + 8CA7CCBC2B77B40B008E587F /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; + 8CA7CCBE2B77B42E008E587F /* LyricerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricerView.swift; sourceTree = ""; }; + 8CA7CCC02B77B44E008E587F /* VideoDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDownloadView.swift; sourceTree = ""; }; + 8CA7CCC22B77B45F008E587F /* VideoDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailView.swift; sourceTree = ""; }; + 8CA7CCC42B77B47A008E587F /* AudioPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerView.swift; sourceTree = ""; }; + 8CA7CCC72B77B49D008E587F /* ImageViewerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewerView.swift; sourceTree = ""; }; + 8CA7CCC92B77B4B2008E587F /* UserDynamicMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDynamicMainView.swift; sourceTree = ""; }; + 8CA7CCCB2B77B4CA008E587F /* DynamicDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDetailView.swift; sourceTree = ""; }; + 8CA7CCCD2B77B4DF008E587F /* DynamicSendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicSendView.swift; sourceTree = ""; }; + 8CA7CCD02B77B504008E587F /* BangumiDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BangumiDetailView.swift; sourceTree = ""; }; + 8CA7CCD22B77B51A008E587F /* BangumiDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BangumiDownloadView.swift; sourceTree = ""; }; + 8CA7CCD42B77B530008E587F /* BangumiPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BangumiPlayerView.swift; sourceTree = ""; }; + 8CA7CCD72B77B54F008E587F /* LivePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LivePlayerView.swift; sourceTree = ""; }; + 8CA7CCD92B77B55F008E587F /* LiveDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveDetailView.swift; sourceTree = ""; }; + 8CA7CCDB2B77B571008E587F /* LiveMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveMessagesView.swift; sourceTree = ""; }; 8CD9463A2B64CD0F005D00D0 /* Bangumi_Detail_Season.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Bangumi_Detail_Season.json; sourceTree = ""; }; B44689CA2B4FC15A002CCEB2 /* DarockBili.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DarockBili.app; sourceTree = BUILT_PRODUCTS_DIR; }; B44689D02B4FC15A002CCEB2 /* 喵哩喵哩.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "喵哩喵哩.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -130,10 +354,6 @@ B44689ED2B4FC249002CCEB2 /* backtrace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = backtrace.c; sourceTree = ""; }; B44689EE2B4FC249002CCEB2 /* Backtrace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Backtrace.swift; sourceTree = ""; }; B44689EF2B4FC249002CCEB2 /* FeedbackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackView.swift; sourceTree = ""; }; - B44689F12B4FC249002CCEB2 /* FeedbackLoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackLoginView.swift; sourceTree = ""; }; - B44689F22B4FC249002CCEB2 /* FeedbackEnterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackEnterView.swift; sourceTree = ""; }; - B44689F32B4FC249002CCEB2 /* FeedbackMainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackMainView.swift; sourceTree = ""; }; - B44689F42B4FC249002CCEB2 /* FeedbackDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackDetailsView.swift; sourceTree = ""; }; B44689F52B4FC249002CCEB2 /* ErrorGetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorGetView.swift; sourceTree = ""; }; B44689F72B4FC249002CCEB2 /* CodeExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeExt.swift; sourceTree = ""; }; B44689F82B4FC249002CCEB2 /* AppFileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppFileManager.swift; sourceTree = ""; }; @@ -186,6 +406,55 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 8CA7CBBD2B77AC4A008E587F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CA7CC132B77AC93008E587F /* SDWebImageSwiftUI in Frameworks */, + 8CA7CC0F2B77AC93008E587F /* ZipArchive in Frameworks */, + 8CA7CC072B77AC93008E587F /* Alamofire in Frameworks */, + 8CA7CCDF2B77BB5A008E587F /* AlertKit in Frameworks */, + 8CA7CC192B77AC93008E587F /* SDWebImagePDFCoder in Frameworks */, + 8CA7CC172B77AC93008E587F /* SDWebImageSVGCoder in Frameworks */, + 8CA7CC232B77AC93008E587F /* DarockKit in Frameworks */, + 8CA7CC092B77AC93008E587F /* Marquee in Frameworks */, + 8C0557DF2B791B84009D9CD0 /* AZVideoPlayer in Frameworks */, + 8CA7CC0B2B77AC93008E587F /* SwiftDate in Frameworks */, + 8CA7CC1F2B77AC93008E587F /* Dynamic in Frameworks */, + 8CA7CC212B77AC93008E587F /* SwiftyJSON in Frameworks */, + 8CA7CC1B2B77AC93008E587F /* SwiftSoup in Frameworks */, + 8CA3889F2B78B1E300F5F91F /* AZVideoPlayer in Frameworks */, + 8CA388992B789A0300F5F91F /* AZVideoPlayer in Frameworks */, + 8CA7CC1D2B77AC93008E587F /* CachedAsyncImage in Frameworks */, + 8CA7CC152B77AC93008E587F /* SDWebImageWebPCoder in Frameworks */, + 8CA7CC112B77AC93008E587F /* EFQRCode in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8CA7CBCC2B77AC4C008E587F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CA7CBF12B77AC8A008E587F /* SDWebImageSwiftUI in Frameworks */, + 8CA7CBED2B77AC8A008E587F /* ZipArchive in Frameworks */, + 8CA7CBE52B77AC8A008E587F /* Alamofire in Frameworks */, + 8CA7CBF72B77AC8A008E587F /* SDWebImagePDFCoder in Frameworks */, + 8CA7CBF52B77AC8A008E587F /* SDWebImageSVGCoder in Frameworks */, + 8CA7CC012B77AC8A008E587F /* DarockKit in Frameworks */, + 8CA7CC032B77AC8A008E587F /* BPlayer in Frameworks */, + 8CA7CBE72B77AC8A008E587F /* Marquee in Frameworks */, + 8CA7CBE92B77AC8A008E587F /* SwiftDate in Frameworks */, + 8CA7CBFD2B77AC8A008E587F /* Dynamic in Frameworks */, + 8CA7CBFF2B77AC8A008E587F /* SwiftyJSON in Frameworks */, + 8CA7CBF92B77AC8A008E587F /* SwiftSoup in Frameworks */, + 8CA7CBEB2B77AC8A008E587F /* SFSymbol in Frameworks */, + 8CA7CBFB2B77AC8A008E587F /* CachedAsyncImage in Frameworks */, + 8CA7CBF32B77AC8A008E587F /* SDWebImageWebPCoder in Frameworks */, + 8CA7CBEF2B77AC8A008E587F /* EFQRCode in Frameworks */, + 8CA7CC052B77AC8A008E587F /* Starscream in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B44689CD2B4FC15A002CCEB2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -232,13 +501,208 @@ path = Models; sourceTree = ""; }; + 8CA7CBC12B77AC4A008E587F /* MeowBili */ = { + isa = PBXGroup; + children = ( + 8CA7CC632B77AD5B008E587F /* Info.plist */, + 8CA7CC752B77AF4B008E587F /* MeowBili-Bridging-Header.h */, + 8CA7CBC22B77AC4A008E587F /* MeowBiliApp.swift */, + 8CA7CC642B77AE33008E587F /* Models */, + 8CA7CC6D2B77AEA4008E587F /* InMain */, + 8CA7CC722B77AEFB008E587F /* Errors */, + 8CA7CC842B77B003008E587F /* Extension */, + 8CA7CC8B2B77B06F008E587F /* Others */, + 8CA7CCA42B77B303008E587F /* GlobalView */, + 8CA7CCA72B77B329008E587F /* PersonalCenter */, + 8CA7CCBB2B77B3FC008E587F /* Video */, + 8CA7CCC62B77B48D008E587F /* UserDynamic */, + 8CA7CCCF2B77B4F2008E587F /* Bangumi */, + 8CA7CCD62B77B542008E587F /* Live */, + 8CA7CBC62B77AC4C008E587F /* Assets.xcassets */, + 8CA7CBC82B77AC4C008E587F /* Preview Content */, + ); + path = MeowBili; + sourceTree = ""; + }; + 8CA7CBC82B77AC4C008E587F /* Preview Content */ = { + isa = PBXGroup; + children = ( + 8CA7CBC92B77AC4C008E587F /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 8CA7CC642B77AE33008E587F /* Models */ = { + isa = PBXGroup; + children = ( + 8CA7CC692B77AE62008E587F /* Bangumi.swift */, + 8CA7CC6B2B77AE80008E587F /* DownloadObj.swift */, + ); + path = Models; + sourceTree = ""; + }; + 8CA7CC6D2B77AEA4008E587F /* InMain */ = { + isa = PBXGroup; + children = ( + 8CA7CC6E2B77AEB4008E587F /* SearchView.swift */, + 8CA7CC702B77AEE4008E587F /* MainView.swift */, + 8CA7CBC42B77AC4A008E587F /* ContentView.swift */, + ); + path = InMain; + sourceTree = ""; + }; + 8CA7CC722B77AEFB008E587F /* Errors */ = { + isa = PBXGroup; + children = ( + 8CA7CC732B77AF1B008E587F /* Backtrace */, + 8CA7CC7A2B77AF93008E587F /* NetwokFixView.swift */, + 8CA7CC7C2B77AFAC008E587F /* SignalErrorView.swift */, + 8CA7CC7E2B77AFC5008E587F /* MemoryWarningView.swift */, + 8CA7CC802B77AFDE008E587F /* FeedbackView.swift */, + 8CA7CC822B77AFF2008E587F /* ErrorGetView.swift */, + ); + path = Errors; + sourceTree = ""; + }; + 8CA7CC732B77AF1B008E587F /* Backtrace */ = { + isa = PBXGroup; + children = ( + 8CA7CC742B77AF2E008E587F /* backtrace.h */, + 8CA7CC762B77AF4C008E587F /* backtrace.c */, + 8CA7CC782B77AF79008E587F /* backtrace.swift */, + ); + path = Backtrace; + sourceTree = ""; + }; + 8CA7CC842B77B003008E587F /* Extension */ = { + isa = PBXGroup; + children = ( + 8CA7CC852B77B015008E587F /* CodeExt.swift */, + 8CA7CC872B77B039008E587F /* UIExt.swift */, + 8CA7CC892B77B061008E587F /* AppFileManager.swift */, + ); + path = Extension; + sourceTree = ""; + }; + 8CA7CC8B2B77B06F008E587F /* Others */ = { + isa = PBXGroup; + children = ( + 8CA7CC8C2B77B07E008E587F /* CCodes */, + 8CA7CC932B77B0E4008E587F /* Skins */, + 8CA7CC9A2B77B13D008E587F /* Passthroughs.swift */, + 8CA7CC9C2B77B150008E587F /* AboutView.swift */, + 8CA7CC9E2B77B166008E587F /* SettingsView.swift */, + 8CA7CCA02B77B2DC008E587F /* LoginView.swift */, + 8CA7CCA22B77B2F2008E587F /* NoticeView.swift */, + ); + path = Others; + sourceTree = ""; + }; + 8CA7CC8C2B77B07E008E587F /* CCodes */ = { + isa = PBXGroup; + children = ( + 8CA7CC8F2B77B0AE008E587F /* AVExtension.h */, + 8CA7CC902B77B0C1008E587F /* CodingTime.h */, + 8CA7CC8D2B77B094008E587F /* AVExtension.m */, + 8CA7CC912B77B0D4008E587F /* CodingTime.m */, + ); + path = CCodes; + sourceTree = ""; + }; + 8CA7CC932B77B0E4008E587F /* Skins */ = { + isa = PBXGroup; + children = ( + 8CA7CC942B77B0FB008E587F /* SkinChooserView.swift */, + 8CA7CC962B77B115008E587F /* SkinExplorerView.swift */, + 8CA7CC982B77B127008E587F /* SkinDownloadView.swift */, + ); + path = Skins; + sourceTree = ""; + }; + 8CA7CCA42B77B303008E587F /* GlobalView */ = { + isa = PBXGroup; + children = ( + 8CA7CCA52B77B314008E587F /* CommentsView.swift */, + ); + path = GlobalView; + sourceTree = ""; + }; + 8CA7CCA72B77B329008E587F /* PersonalCenter */ = { + isa = PBXGroup; + children = ( + 8CA7CCA82B77B331008E587F /* bMessage */, + 8CA7CCAB2B77B354008E587F /* DownloadsView.swift */, + 8CA7CCAD2B77B36E008E587F /* PersonAccountView.swift */, + 8CA7CCAF2B77B383008E587F /* FavoriteView.swift */, + 8CA7CCB12B77B398008E587F /* FollowListView.swift */, + 8CA7CCB32B77B3AC008E587F /* ArticleView.swift */, + 8CA7CCB52B77B3BF008E587F /* UserDetailView.swift */, + 8CA7CCB72B77B3D6008E587F /* HistoryView.swift */, + 8CA7CCB92B77B3EA008E587F /* WatchLaterView.swift */, + ); + path = PersonalCenter; + sourceTree = ""; + }; + 8CA7CCA82B77B331008E587F /* bMessage */ = { + isa = PBXGroup; + children = ( + 8CA7CCA92B77B340008E587F /* bMessageSendView.swift */, + ); + path = bMessage; + sourceTree = ""; + }; + 8CA7CCBB2B77B3FC008E587F /* Video */ = { + isa = PBXGroup; + children = ( + 8CA7CCBC2B77B40B008E587F /* VideoPlayerView.swift */, + 8CA7CCBE2B77B42E008E587F /* LyricerView.swift */, + 8CA7CCC02B77B44E008E587F /* VideoDownloadView.swift */, + 8CA7CCC22B77B45F008E587F /* VideoDetailView.swift */, + 8CA7CCC42B77B47A008E587F /* AudioPlayerView.swift */, + ); + path = Video; + sourceTree = ""; + }; + 8CA7CCC62B77B48D008E587F /* UserDynamic */ = { + isa = PBXGroup; + children = ( + 8CA7CCC72B77B49D008E587F /* ImageViewerView.swift */, + 8CA7CCC92B77B4B2008E587F /* UserDynamicMainView.swift */, + 8CA7CCCB2B77B4CA008E587F /* DynamicDetailView.swift */, + 8CA7CCCD2B77B4DF008E587F /* DynamicSendView.swift */, + ); + path = UserDynamic; + sourceTree = ""; + }; + 8CA7CCCF2B77B4F2008E587F /* Bangumi */ = { + isa = PBXGroup; + children = ( + 8CA7CCD02B77B504008E587F /* BangumiDetailView.swift */, + 8CA7CCD22B77B51A008E587F /* BangumiDownloadView.swift */, + 8CA7CCD42B77B530008E587F /* BangumiPlayerView.swift */, + ); + path = Bangumi; + sourceTree = ""; + }; + 8CA7CCD62B77B542008E587F /* Live */ = { + isa = PBXGroup; + children = ( + 8CA7CCD72B77B54F008E587F /* LivePlayerView.swift */, + 8CA7CCD92B77B55F008E587F /* LiveDetailView.swift */, + 8CA7CCDB2B77B571008E587F /* LiveMessagesView.swift */, + ); + path = Live; + sourceTree = ""; + }; B44689C32B4FC15A002CCEB2 = { isa = PBXGroup; children = ( E2B720932B67858E00ABB0A6 /* Localizable.xcstrings */, 8C72E32A2B6605B80087486E /* README.md */, + 8CA388A02B78B5D800F5F91F /* BiliFont.ttf */, B4468A142B4FC24A002CCEB2 /* JSONReturnExamples */, B44689D42B4FC15A002CCEB2 /* DarockBili Watch App */, + 8CA7CBC12B77AC4A008E587F /* MeowBili */, B44689CB2B4FC15A002CCEB2 /* Products */, B4468A902B4FC8B7002CCEB2 /* Frameworks */, ); @@ -249,6 +713,8 @@ children = ( B44689CA2B4FC15A002CCEB2 /* DarockBili.app */, B44689D02B4FC15A002CCEB2 /* 喵哩喵哩.app */, + 8CA7CBC02B77AC4A008E587F /* MeowBili.app */, + 8CA7CBCF2B77AC4C008E587F /* 喵哩喵哩.app */, ); name = Products; sourceTree = ""; @@ -256,6 +722,7 @@ B44689D42B4FC15A002CCEB2 /* DarockBili Watch App */ = { isa = PBXGroup; children = ( + 8CA7CC622B77AD06008E587F /* MeowBili-Watch-App-Info.plist */, B4C5E9562B4FD60600E2605B /* DarockBili-Watch-App-Info.plist */, B4468A042B4FC249002CCEB2 /* DarockBili-Bridging-Header.h */, B44689FF2B4FC249002CCEB2 /* DarockBiliApp.swift */, @@ -281,7 +748,6 @@ isa = PBXGroup; children = ( B44689EB2B4FC249002CCEB2 /* Backtrace */, - B44689F02B4FC249002CCEB2 /* DarockFeedback */, B44689E82B4FC249002CCEB2 /* NetworkFixView.swift */, B44689E92B4FC249002CCEB2 /* SignalErrorView.swift */, B44689EA2B4FC249002CCEB2 /* MemoryWarningView.swift */, @@ -301,17 +767,6 @@ path = Backtrace; sourceTree = ""; }; - B44689F02B4FC249002CCEB2 /* DarockFeedback */ = { - isa = PBXGroup; - children = ( - B44689F12B4FC249002CCEB2 /* FeedbackLoginView.swift */, - B44689F22B4FC249002CCEB2 /* FeedbackEnterView.swift */, - B44689F32B4FC249002CCEB2 /* FeedbackMainView.swift */, - B44689F42B4FC249002CCEB2 /* FeedbackDetailsView.swift */, - ); - path = DarockFeedback; - sourceTree = ""; - }; B44689F62B4FC249002CCEB2 /* Extension */ = { isa = PBXGroup; children = ( @@ -470,6 +925,81 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 8CA7CBBF2B77AC4A008E587F /* MeowBili */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8CA7CBE32B77AC4D008E587F /* Build configuration list for PBXNativeTarget "MeowBili" */; + buildPhases = ( + 8CA7CBBC2B77AC4A008E587F /* Sources */, + 8CA7CBBD2B77AC4A008E587F /* Frameworks */, + 8CA7CBBE2B77AC4A008E587F /* Resources */, + 8CA7CBDF2B77AC4D008E587F /* Embed Watch Content */, + ); + buildRules = ( + ); + dependencies = ( + 8CA7CBD22B77AC4C008E587F /* PBXTargetDependency */, + ); + name = MeowBili; + packageProductDependencies = ( + 8CA7CC062B77AC93008E587F /* Alamofire */, + 8CA7CC082B77AC93008E587F /* Marquee */, + 8CA7CC0A2B77AC93008E587F /* SwiftDate */, + 8CA7CC0E2B77AC93008E587F /* ZipArchive */, + 8CA7CC102B77AC93008E587F /* EFQRCode */, + 8CA7CC122B77AC93008E587F /* SDWebImageSwiftUI */, + 8CA7CC142B77AC93008E587F /* SDWebImageWebPCoder */, + 8CA7CC162B77AC93008E587F /* SDWebImageSVGCoder */, + 8CA7CC182B77AC93008E587F /* SDWebImagePDFCoder */, + 8CA7CC1A2B77AC93008E587F /* SwiftSoup */, + 8CA7CC1C2B77AC93008E587F /* CachedAsyncImage */, + 8CA7CC1E2B77AC93008E587F /* Dynamic */, + 8CA7CC202B77AC93008E587F /* SwiftyJSON */, + 8CA7CC222B77AC93008E587F /* DarockKit */, + 8CA7CCDE2B77BB5A008E587F /* AlertKit */, + 8CA388982B789A0300F5F91F /* AZVideoPlayer */, + 8CA3889E2B78B1E300F5F91F /* AZVideoPlayer */, + 8C0557DE2B791B84009D9CD0 /* AZVideoPlayer */, + ); + productName = MeowBili; + productReference = 8CA7CBC02B77AC4A008E587F /* MeowBili.app */; + productType = "com.apple.product-type.application"; + }; + 8CA7CBCE2B77AC4C008E587F /* MeowBili Watch App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8CA7CBE22B77AC4D008E587F /* Build configuration list for PBXNativeTarget "MeowBili Watch App" */; + buildPhases = ( + 8CA7CBCB2B77AC4C008E587F /* Sources */, + 8CA7CBCC2B77AC4C008E587F /* Frameworks */, + 8CA7CBCD2B77AC4C008E587F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "MeowBili Watch App"; + packageProductDependencies = ( + 8CA7CBE42B77AC8A008E587F /* Alamofire */, + 8CA7CBE62B77AC8A008E587F /* Marquee */, + 8CA7CBE82B77AC8A008E587F /* SwiftDate */, + 8CA7CBEA2B77AC8A008E587F /* SFSymbol */, + 8CA7CBEC2B77AC8A008E587F /* ZipArchive */, + 8CA7CBEE2B77AC8A008E587F /* EFQRCode */, + 8CA7CBF02B77AC8A008E587F /* SDWebImageSwiftUI */, + 8CA7CBF22B77AC8A008E587F /* SDWebImageWebPCoder */, + 8CA7CBF42B77AC8A008E587F /* SDWebImageSVGCoder */, + 8CA7CBF62B77AC8A008E587F /* SDWebImagePDFCoder */, + 8CA7CBF82B77AC8A008E587F /* SwiftSoup */, + 8CA7CBFA2B77AC8A008E587F /* CachedAsyncImage */, + 8CA7CBFC2B77AC8A008E587F /* Dynamic */, + 8CA7CBFE2B77AC8A008E587F /* SwiftyJSON */, + 8CA7CC002B77AC8A008E587F /* DarockKit */, + 8CA7CC022B77AC8A008E587F /* BPlayer */, + 8CA7CC042B77AC8A008E587F /* Starscream */, + ); + productName = "MeowBili Watch App"; + productReference = 8CA7CBCF2B77AC4C008E587F /* 喵哩喵哩.app */; + productType = "com.apple.product-type.application"; + }; B44689C92B4FC15A002CCEB2 /* DarockBili */ = { isa = PBXNativeTarget; buildConfigurationList = B44689E42B4FC15B002CCEB2 /* Build configuration list for PBXNativeTarget "DarockBili" */; @@ -535,6 +1065,13 @@ LastSwiftUpdateCheck = 1520; LastUpgradeCheck = 1520; TargetAttributes = { + 8CA7CBBF2B77AC4A008E587F = { + CreatedOnToolsVersion = 15.2; + LastSwiftMigration = 1520; + }; + 8CA7CBCE2B77AC4C008E587F = { + CreatedOnToolsVersion = 15.2; + }; B44689C92B4FC15A002CCEB2 = { CreatedOnToolsVersion = 15.2; }; @@ -571,6 +1108,8 @@ B4468A8D2B4FC8AC002CCEB2 /* XCRemoteSwiftPackageReference "DarockKit" */, 8C2337F82B63DBF3009665B1 /* XCRemoteSwiftPackageReference "bplayer" */, 8C372ED32B6CF079002033A1 /* XCRemoteSwiftPackageReference "Starscream" */, + 8CA7CCDD2B77BB5A008E587F /* XCRemoteSwiftPackageReference "AlertKit" */, + 8C0557DD2B791B84009D9CD0 /* XCRemoteSwiftPackageReference "AZVideoPlayer" */, ); productRefGroup = B44689CB2B4FC15A002CCEB2 /* Products */; projectDirPath = ""; @@ -578,11 +1117,35 @@ targets = ( B44689C92B4FC15A002CCEB2 /* DarockBili */, B44689CF2B4FC15A002CCEB2 /* DarockBili Watch App */, + 8CA7CBBF2B77AC4A008E587F /* MeowBili */, + 8CA7CBCE2B77AC4C008E587F /* MeowBili Watch App */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 8CA7CBBE2B77AC4A008E587F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CA7CBCA2B77AC4C008E587F /* Preview Assets.xcassets in Resources */, + 8CA388A22B78B5D800F5F91F /* BiliFont.ttf in Resources */, + 8CA7CBC72B77AC4C008E587F /* Assets.xcassets in Resources */, + 8CA7CCE02B77C3A8008E587F /* Localizable.xcstrings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8CA7CBCD2B77AC4C008E587F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CA7CC542B77ACE8008E587F /* Assets.xcassets in Resources */, + 8CA388A32B78B5D800F5F91F /* BiliFont.ttf in Resources */, + 8CA7CC3B2B77ACE8008E587F /* Preview Assets.xcassets in Resources */, + 8CA7CCE12B77C3A9008E587F /* Localizable.xcstrings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B44689C82B4FC15A002CCEB2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -595,6 +1158,7 @@ buildActionMask = 2147483647; files = ( B4468A502B4FC24A002CCEB2 /* Preview Assets.xcassets in Resources */, + 8CA388A12B78B5D800F5F91F /* BiliFont.ttf in Resources */, E2B720942B67858E00ABB0A6 /* Localizable.xcstrings in Resources */, B4468A672B4FC286002CCEB2 /* Assets.xcassets in Resources */, ); @@ -603,6 +1167,123 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 8CA7CBBC2B77AC4A008E587F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CA7CCA32B77B2F3008E587F /* NoticeView.swift in Sources */, + 8CA7CC952B77B0FB008E587F /* SkinChooserView.swift in Sources */, + 8CA7CCA62B77B315008E587F /* CommentsView.swift in Sources */, + 8CA7CC6F2B77AEB4008E587F /* SearchView.swift in Sources */, + 8CA7CCD82B77B54F008E587F /* LivePlayerView.swift in Sources */, + 8CA7CCB62B77B3BF008E587F /* UserDetailView.swift in Sources */, + 8CA7CCCE2B77B4DF008E587F /* DynamicSendView.swift in Sources */, + 8CA7CCCC2B77B4CA008E587F /* DynamicDetailView.swift in Sources */, + 8CA7CCC52B77B47A008E587F /* AudioPlayerView.swift in Sources */, + 8CA7CCC82B77B49D008E587F /* ImageViewerView.swift in Sources */, + 8CA7CC972B77B115008E587F /* SkinExplorerView.swift in Sources */, + 8CA7CC712B77AEE4008E587F /* MainView.swift in Sources */, + 8CA7CCBF2B77B42E008E587F /* LyricerView.swift in Sources */, + 8CA7CCB82B77B3D6008E587F /* HistoryView.swift in Sources */, + 8CA7CCDC2B77B571008E587F /* LiveMessagesView.swift in Sources */, + 8CA7CC792B77AF79008E587F /* backtrace.swift in Sources */, + 8CA7CC6A2B77AE62008E587F /* Bangumi.swift in Sources */, + 8CA7CBC52B77AC4A008E587F /* ContentView.swift in Sources */, + 8CA7CCAA2B77B340008E587F /* bMessageSendView.swift in Sources */, + 8CA7CCB42B77B3AC008E587F /* ArticleView.swift in Sources */, + 8CA7CC812B77AFDE008E587F /* FeedbackView.swift in Sources */, + 8CA7CC862B77B015008E587F /* CodeExt.swift in Sources */, + 8CA7CC9F2B77B166008E587F /* SettingsView.swift in Sources */, + 8CA7CC7D2B77AFAC008E587F /* SignalErrorView.swift in Sources */, + 8CA7CCBA2B77B3EA008E587F /* WatchLaterView.swift in Sources */, + 8CA7CCBD2B77B40B008E587F /* VideoPlayerView.swift in Sources */, + 8CA7CC8E2B77B094008E587F /* AVExtension.m in Sources */, + 8CA7CC7F2B77AFC5008E587F /* MemoryWarningView.swift in Sources */, + 8CA7CCA12B77B2DC008E587F /* LoginView.swift in Sources */, + 8CA7CCCA2B77B4B2008E587F /* UserDynamicMainView.swift in Sources */, + 8CA7CCAE2B77B36E008E587F /* PersonAccountView.swift in Sources */, + 8CA7CCD52B77B530008E587F /* BangumiPlayerView.swift in Sources */, + 8CA7CC9B2B77B13D008E587F /* Passthroughs.swift in Sources */, + 8CA7CCB02B77B383008E587F /* FavoriteView.swift in Sources */, + 8CA7CC9D2B77B150008E587F /* AboutView.swift in Sources */, + 8CA7CC882B77B039008E587F /* UIExt.swift in Sources */, + 8CA7CC772B77AF4C008E587F /* backtrace.c in Sources */, + 8CA7CCC32B77B45F008E587F /* VideoDetailView.swift in Sources */, + 8CA7CCAC2B77B354008E587F /* DownloadsView.swift in Sources */, + 8CA7CC8A2B77B061008E587F /* AppFileManager.swift in Sources */, + 8CA7CCB22B77B398008E587F /* FollowListView.swift in Sources */, + 8CA7CC7B2B77AF93008E587F /* NetwokFixView.swift in Sources */, + 8CA7CCD32B77B51A008E587F /* BangumiDownloadView.swift in Sources */, + 8CA7CC922B77B0D4008E587F /* CodingTime.m in Sources */, + 8CA7CBC32B77AC4A008E587F /* MeowBiliApp.swift in Sources */, + 8CA7CCC12B77B44E008E587F /* VideoDownloadView.swift in Sources */, + 8CA7CC832B77AFF2008E587F /* ErrorGetView.swift in Sources */, + 8CA7CC992B77B127008E587F /* SkinDownloadView.swift in Sources */, + 8CA7CCDA2B77B55F008E587F /* LiveDetailView.swift in Sources */, + 8CA7CC6C2B77AE80008E587F /* DownloadObj.swift in Sources */, + 8CA7CCD12B77B504008E587F /* BangumiDetailView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8CA7CBCB2B77AC4C008E587F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CA7CC4E2B77ACE8008E587F /* UserDetailView.swift in Sources */, + 8CA7CC3D2B77ACE8008E587F /* MainView.swift in Sources */, + 8CA7CC2B2B77ACE8008E587F /* UIExt.swift in Sources */, + 8CA7CC4F2B77ACE8008E587F /* AboutView.swift in Sources */, + 8CA7CC452B77ACE8008E587F /* HistoryView.swift in Sources */, + 8CA7CC402B77ACE8008E587F /* Bangumi.swift in Sources */, + 8CA7CC332B77ACE8008E587F /* ErrorGetView.swift in Sources */, + 8CA7CC2D2B77ACE8008E587F /* ContentView.swift in Sources */, + 8CA7CC482B77ACE8008E587F /* LivePlayerView.swift in Sources */, + 8CA7CC5C2B77ACE8008E587F /* MemoryWarningView.swift in Sources */, + 8CA7CC442B77ACE8008E587F /* CommentsView.swift in Sources */, + 8CA7CC292B77ACE8008E587F /* SkinDownloadView.swift in Sources */, + 8CA7CC5D2B77ACE8008E587F /* bMessageSendView.swift in Sources */, + 8CA7CC2E2B77ACE8008E587F /* DarockBiliApp.swift in Sources */, + 8CA7CC5F2B77ACE8008E587F /* NetworkFixView.swift in Sources */, + 8CA7CC422B77ACE8008E587F /* Backtrace.swift in Sources */, + 8CA7CC382B77ACE8008E587F /* VideoDetailView.swift in Sources */, + 8CA7CC562B77ACE8008E587F /* DownloadsView.swift in Sources */, + 8CA7CC5B2B77ACE8008E587F /* ArticleView.swift in Sources */, + 8CA7CC512B77ACE8008E587F /* DynamicDetailView.swift in Sources */, + 8CA7CC462B77ACE8008E587F /* AppFileManager.swift in Sources */, + 8CA7CC2A2B77ACE8008E587F /* WatchLaterView.swift in Sources */, + 8CA7CC582B77ACE8008E587F /* CodeExt.swift in Sources */, + 8CA7CC362B77ACE8008E587F /* Passthroughs.swift in Sources */, + 8CA7CC2C2B77ACE8008E587F /* backtrace.c in Sources */, + 8CA7CC612B77ACE8008E587F /* SkinChooserView.swift in Sources */, + 8CA7CC392B77ACE8008E587F /* OfflineMainView.swift in Sources */, + 8CA7CC502B77ACE8008E587F /* FeedbackView.swift in Sources */, + 8CA7CC592B77ACE8008E587F /* FollowListView.swift in Sources */, + 8CA7CC4D2B77ACE8008E587F /* NoticeView.swift in Sources */, + 8CA7CC4B2B77ACE8008E587F /* UserDynamicMainView.swift in Sources */, + 8CA7CC522B77ACE8008E587F /* VideoDownloadView.swift in Sources */, + 8CA7CC472B77ACE8008E587F /* LyricerView.swift in Sources */, + 8CA7CC572B77ACE8008E587F /* PersonAccountView.swift in Sources */, + 8CA7CC312B77ACE8008E587F /* ImageViewerView.swift in Sources */, + 8CA7CC4C2B77ACE8008E587F /* VideoPlayerView.swift in Sources */, + 8CA7CC2F2B77ACE8008E587F /* DownloadObj.swift in Sources */, + 8CA7CC532B77ACE8008E587F /* BangumiDetailView.swift in Sources */, + 8CA7CC3E2B77ACE8008E587F /* FavoriteView.swift in Sources */, + 8CA7CC432B77ACE8008E587F /* LiveMessagesView.swift in Sources */, + 8CA7CC3F2B77ACE8008E587F /* BangumiDownloadView.swift in Sources */, + 8CA7CC342B77ACE8008E587F /* BangumiPlayerView.swift in Sources */, + 8CA7CC412B77ACE8008E587F /* AVExtension.m in Sources */, + 8CA7CC352B77ACE8008E587F /* CodingTime.m in Sources */, + 8CA7CC3C2B77ACE8008E587F /* LiveDetailView.swift in Sources */, + 8CA7CC4A2B77ACE8008E587F /* AudioPlayerView.swift in Sources */, + 8CA7CC492B77ACE8008E587F /* LoginView.swift in Sources */, + 8CA7CC322B77ACE8008E587F /* SearchView.swift in Sources */, + 8CA7CC602B77ACE8008E587F /* SignalErrorView.swift in Sources */, + 8CA7CC552B77ACE8008E587F /* DynamicSendView.swift in Sources */, + 8CA7CC5A2B77ACE8008E587F /* SkinExplorerView.swift in Sources */, + 8CA7CC3A2B77ACE8008E587F /* SettingsView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B44689CC2B4FC15A002CCEB2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -616,15 +1297,12 @@ B4468A412B4FC24A002CCEB2 /* LivePlayerView.swift in Sources */, E2565AF32B52BFDF000E01ED /* BangumiDetailView.swift in Sources */, B4468A342B4FC24A002CCEB2 /* backtrace.c in Sources */, - B4468A372B4FC24A002CCEB2 /* FeedbackLoginView.swift in Sources */, B4468A642B4FC24A002CCEB2 /* HistoryView.swift in Sources */, - B4468A3A2B4FC24A002CCEB2 /* FeedbackDetailsView.swift in Sources */, B4468A472B4FC24A002CCEB2 /* UserDynamicMainView.swift in Sources */, B4468A5A2B4FC24A002CCEB2 /* NoticeView.swift in Sources */, B4468A612B4FC24A002CCEB2 /* FollowListView.swift in Sources */, B4468A4C2B4FC24A002CCEB2 /* VideoDownloadView.swift in Sources */, B4468A402B4FC24A002CCEB2 /* UIExt.swift in Sources */, - B4468A382B4FC24A002CCEB2 /* FeedbackEnterView.swift in Sources */, B4468A312B4FC24A002CCEB2 /* NetworkFixView.swift in Sources */, 8C72E3302B6621E00087486E /* Passthroughs.swift in Sources */, B4468A4F2B4FC24A002CCEB2 /* AudioPlayerView.swift in Sources */, @@ -661,7 +1339,6 @@ B4468A3D2B4FC24A002CCEB2 /* AppFileManager.swift in Sources */, B4468A622B4FC24A002CCEB2 /* ArticleView.swift in Sources */, B4468A4B2B4FC24A002CCEB2 /* LyricerView.swift in Sources */, - B4468A392B4FC24A002CCEB2 /* FeedbackMainView.swift in Sources */, B4468A5D2B4FC24A002CCEB2 /* DownloadsView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -669,6 +1346,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 8CA7CBD22B77AC4C008E587F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8CA7CBCE2B77AC4C008E587F /* MeowBili Watch App */; + targetProxy = 8CA7CBD12B77AC4C008E587F /* PBXContainerItemProxy */; + }; B44689D32B4FC15A002CCEB2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B44689CF2B4FC15A002CCEB2 /* DarockBili Watch App */; @@ -677,6 +1359,149 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 8CA7CBDD2B77AC4D008E587F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 433; + DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; + DEVELOPMENT_TEAM = B57D8PP775; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MeowBili/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "喵哩喵哩"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIStatusBarHidden = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.darock.DarockBili; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "MeowBili/MeowBili-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8CA7CBDE2B77AC4D008E587F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 433; + DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; + DEVELOPMENT_TEAM = B57D8PP775; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MeowBili/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "喵哩喵哩"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIStatusBarHidden = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.darock.DarockBili; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "MeowBili/MeowBili-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8CA7CBE02B77AC4D008E587F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 433; + DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; + DEVELOPMENT_TEAM = B57D8PP775; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "MeowBili-Watch-App-Info.plist"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.darock.DarockBili; + INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.darock.DarockBili.watchkitapp; + PRODUCT_NAME = "喵哩喵哩"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "DarockBili Watch App/DarockBili-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Debug; + }; + 8CA7CBE12B77AC4D008E587F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 433; + DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; + DEVELOPMENT_TEAM = B57D8PP775; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "MeowBili-Watch-App-Info.plist"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.darock.DarockBili; + INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.darock.DarockBili.watchkitapp; + PRODUCT_NAME = "喵哩喵哩"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "DarockBili Watch App/DarockBili-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Release; + }; B44689DE2B4FC15B002CCEB2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -803,7 +1628,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 433; DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -835,7 +1660,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 433; DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -867,7 +1692,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 433; DEVELOPMENT_TEAM = B57D8PP775; INFOPLIST_KEY_CFBundleDisplayName = DarockBili; MARKETING_VERSION = 1.0.0; @@ -883,7 +1708,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 433; DEVELOPMENT_TEAM = B57D8PP775; INFOPLIST_KEY_CFBundleDisplayName = DarockBili; MARKETING_VERSION = 1.0.0; @@ -898,6 +1723,24 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 8CA7CBE22B77AC4D008E587F /* Build configuration list for PBXNativeTarget "MeowBili Watch App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8CA7CBE02B77AC4D008E587F /* Debug */, + 8CA7CBE12B77AC4D008E587F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8CA7CBE32B77AC4D008E587F /* Build configuration list for PBXNativeTarget "MeowBili" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8CA7CBDD2B77AC4D008E587F /* Debug */, + 8CA7CBDE2B77AC4D008E587F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; B44689C72B4FC15A002CCEB2 /* Build configuration list for PBXProject "DarockBili" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -928,6 +1771,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 8C0557DD2B791B84009D9CD0 /* XCRemoteSwiftPackageReference "AZVideoPlayer" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/adamzarn/AZVideoPlayer"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; 8C2337F82B63DBF3009665B1 /* XCRemoteSwiftPackageReference "bplayer" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/WindowsMEMZ/bplayer"; @@ -944,6 +1795,14 @@ minimumVersion = 4.0.6; }; }; + 8CA7CCDD2B77BB5A008E587F /* XCRemoteSwiftPackageReference "AlertKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sparrowcode/AlertKit"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.1.8; + }; + }; B4468A6B2B4FC2C8002CCEB2 /* XCRemoteSwiftPackageReference "Alamofire" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/Alamofire"; @@ -1067,6 +1926,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 8C0557DE2B791B84009D9CD0 /* AZVideoPlayer */ = { + isa = XCSwiftPackageProductDependency; + package = 8C0557DD2B791B84009D9CD0 /* XCRemoteSwiftPackageReference "AZVideoPlayer" */; + productName = AZVideoPlayer; + }; 8C2337F92B63DBF3009665B1 /* BPlayer */ = { isa = XCSwiftPackageProductDependency; package = 8C2337F82B63DBF3009665B1 /* XCRemoteSwiftPackageReference "bplayer" */; @@ -1081,6 +1945,174 @@ isa = XCSwiftPackageProductDependency; productName = BPlayer; }; + 8CA388982B789A0300F5F91F /* AZVideoPlayer */ = { + isa = XCSwiftPackageProductDependency; + productName = AZVideoPlayer; + }; + 8CA3889E2B78B1E300F5F91F /* AZVideoPlayer */ = { + isa = XCSwiftPackageProductDependency; + productName = AZVideoPlayer; + }; + 8CA7CBE42B77AC8A008E587F /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A6B2B4FC2C8002CCEB2 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + 8CA7CBE62B77AC8A008E587F /* Marquee */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A6E2B4FC5E7002CCEB2 /* XCRemoteSwiftPackageReference "Marquee" */; + productName = Marquee; + }; + 8CA7CBE82B77AC8A008E587F /* SwiftDate */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A712B4FC5F1002CCEB2 /* XCRemoteSwiftPackageReference "SwiftDate" */; + productName = SwiftDate; + }; + 8CA7CBEA2B77AC8A008E587F /* SFSymbol */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A742B4FC602002CCEB2 /* XCRemoteSwiftPackageReference "SFSymbol" */; + productName = SFSymbol; + }; + 8CA7CBEC2B77AC8A008E587F /* ZipArchive */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A792B4FC618002CCEB2 /* XCRemoteSwiftPackageReference "ZipArchive" */; + productName = ZipArchive; + }; + 8CA7CBEE2B77AC8A008E587F /* EFQRCode */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A7E2B4FC675002CCEB2 /* XCRemoteSwiftPackageReference "EFQRCode" */; + productName = EFQRCode; + }; + 8CA7CBF02B77AC8A008E587F /* SDWebImageSwiftUI */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A7F2B4FC681002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; + productName = SDWebImageSwiftUI; + }; + 8CA7CBF22B77AC8A008E587F /* SDWebImageWebPCoder */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A802B4FC68C002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImageWebPCoder" */; + productName = SDWebImageWebPCoder; + }; + 8CA7CBF42B77AC8A008E587F /* SDWebImageSVGCoder */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A812B4FC697002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImageSVGCoder" */; + productName = SDWebImageSVGCoder; + }; + 8CA7CBF62B77AC8A008E587F /* SDWebImagePDFCoder */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A822B4FC6A1002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImagePDFCoder" */; + productName = SDWebImagePDFCoder; + }; + 8CA7CBF82B77AC8A008E587F /* SwiftSoup */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A832B4FC6B1002CCEB2 /* XCRemoteSwiftPackageReference "SwiftSoup" */; + productName = SwiftSoup; + }; + 8CA7CBFA2B77AC8A008E587F /* CachedAsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A842B4FC6BE002CCEB2 /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */; + productName = CachedAsyncImage; + }; + 8CA7CBFC2B77AC8A008E587F /* Dynamic */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A852B4FC6C6002CCEB2 /* XCRemoteSwiftPackageReference "Dynamic" */; + productName = Dynamic; + }; + 8CA7CBFE2B77AC8A008E587F /* SwiftyJSON */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A8A2B4FC85A002CCEB2 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; + productName = SwiftyJSON; + }; + 8CA7CC002B77AC8A008E587F /* DarockKit */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A8D2B4FC8AC002CCEB2 /* XCRemoteSwiftPackageReference "DarockKit" */; + productName = DarockKit; + }; + 8CA7CC022B77AC8A008E587F /* BPlayer */ = { + isa = XCSwiftPackageProductDependency; + package = 8C2337F82B63DBF3009665B1 /* XCRemoteSwiftPackageReference "bplayer" */; + productName = BPlayer; + }; + 8CA7CC042B77AC8A008E587F /* Starscream */ = { + isa = XCSwiftPackageProductDependency; + package = 8C372ED32B6CF079002033A1 /* XCRemoteSwiftPackageReference "Starscream" */; + productName = Starscream; + }; + 8CA7CC062B77AC93008E587F /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A6B2B4FC2C8002CCEB2 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + 8CA7CC082B77AC93008E587F /* Marquee */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A6E2B4FC5E7002CCEB2 /* XCRemoteSwiftPackageReference "Marquee" */; + productName = Marquee; + }; + 8CA7CC0A2B77AC93008E587F /* SwiftDate */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A712B4FC5F1002CCEB2 /* XCRemoteSwiftPackageReference "SwiftDate" */; + productName = SwiftDate; + }; + 8CA7CC0E2B77AC93008E587F /* ZipArchive */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A792B4FC618002CCEB2 /* XCRemoteSwiftPackageReference "ZipArchive" */; + productName = ZipArchive; + }; + 8CA7CC102B77AC93008E587F /* EFQRCode */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A7E2B4FC675002CCEB2 /* XCRemoteSwiftPackageReference "EFQRCode" */; + productName = EFQRCode; + }; + 8CA7CC122B77AC93008E587F /* SDWebImageSwiftUI */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A7F2B4FC681002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; + productName = SDWebImageSwiftUI; + }; + 8CA7CC142B77AC93008E587F /* SDWebImageWebPCoder */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A802B4FC68C002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImageWebPCoder" */; + productName = SDWebImageWebPCoder; + }; + 8CA7CC162B77AC93008E587F /* SDWebImageSVGCoder */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A812B4FC697002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImageSVGCoder" */; + productName = SDWebImageSVGCoder; + }; + 8CA7CC182B77AC93008E587F /* SDWebImagePDFCoder */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A822B4FC6A1002CCEB2 /* XCRemoteSwiftPackageReference "SDWebImagePDFCoder" */; + productName = SDWebImagePDFCoder; + }; + 8CA7CC1A2B77AC93008E587F /* SwiftSoup */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A832B4FC6B1002CCEB2 /* XCRemoteSwiftPackageReference "SwiftSoup" */; + productName = SwiftSoup; + }; + 8CA7CC1C2B77AC93008E587F /* CachedAsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A842B4FC6BE002CCEB2 /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */; + productName = CachedAsyncImage; + }; + 8CA7CC1E2B77AC93008E587F /* Dynamic */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A852B4FC6C6002CCEB2 /* XCRemoteSwiftPackageReference "Dynamic" */; + productName = Dynamic; + }; + 8CA7CC202B77AC93008E587F /* SwiftyJSON */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A8A2B4FC85A002CCEB2 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; + productName = SwiftyJSON; + }; + 8CA7CC222B77AC93008E587F /* DarockKit */ = { + isa = XCSwiftPackageProductDependency; + package = B4468A8D2B4FC8AC002CCEB2 /* XCRemoteSwiftPackageReference "DarockKit" */; + productName = DarockKit; + }; + 8CA7CCDE2B77BB5A008E587F /* AlertKit */ = { + isa = XCSwiftPackageProductDependency; + package = 8CA7CCDD2B77BB5A008E587F /* XCRemoteSwiftPackageReference "AlertKit" */; + productName = AlertKit; + }; B4468A6C2B4FC2C8002CCEB2 /* Alamofire */ = { isa = XCSwiftPackageProductDependency; package = B4468A6B2B4FC2C8002CCEB2 /* XCRemoteSwiftPackageReference "Alamofire" */; diff --git a/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 296ce20a9..2feb8a07a 100644 --- a/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,24 @@ "version" : "5.5.0" } }, + { + "identity" : "alertkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparrowcode/AlertKit", + "state" : { + "revision" : "3d35de60ad26aab58395ebecdd63b533546862ed", + "version" : "5.1.8" + } + }, + { + "identity" : "azvideoplayer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/adamzarn/AZVideoPlayer", + "state" : { + "revision" : "e2dde8754ee34706cc69fa640cde85506e239742", + "version" : "1.0.0" + } + }, { "identity" : "bplayer", "kind" : "remoteSourceControl", diff --git a/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme new file mode 100644 index 000000000..b35b43229 --- /dev/null +++ b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili.xcscheme b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili.xcscheme new file mode 100644 index 000000000..ceceec061 --- /dev/null +++ b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 2d2251854..5263b1403 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -96,9 +96,6 @@ } } } - }, - "“屏幕使用时间”会记录您每天使用喵哩喵哩的时间并作出统计" : { - }, "%@" : { "localizations" : { @@ -970,6 +967,7 @@ } }, "DarockID.account" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -986,6 +984,7 @@ } }, "DarockID.code.unmatch" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1002,6 +1001,7 @@ } }, "DarockID.email" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1018,6 +1018,7 @@ } }, "DarockID.feedback-without-logging-in" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1034,6 +1035,7 @@ } }, "DarockID.incorrect" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1050,6 +1052,7 @@ } }, "DarockID.login" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1066,6 +1069,7 @@ } }, "DarockID.login.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1082,6 +1086,7 @@ } }, "DarockID.password" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1098,6 +1103,7 @@ } }, "DarockID.password.confirm" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1114,6 +1120,7 @@ } }, "DarockID.password.unmatch" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1130,6 +1137,7 @@ } }, "DarockID.register" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1146,6 +1154,7 @@ } }, "DarockID.register.success" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1162,6 +1171,7 @@ } }, "DarockID.register.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1178,6 +1188,7 @@ } }, "DarockID.unable-to-connect" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1194,6 +1205,7 @@ } }, "DarockID.verification-code" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1210,6 +1222,7 @@ } }, "DarockID.verification-code.send" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1803,6 +1816,7 @@ } }, "Feedback.nothing" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -4279,6 +4293,9 @@ } } } + }, + "UsernamePlaceholder" : { + }, "v%@ Build %@" : { "localizations" : { @@ -4696,7 +4713,7 @@ } } }, - "于 %@ 开始" : { + "人机验证已完成" : { }, "令枫" : { @@ -4714,12 +4731,27 @@ } } } + }, + "动态" : { + + }, + "动态内容" : { + + }, + "动态评论" : { + + }, + "动态详情" : { + }, "发送动态" : { }, "可选, 最多9个" : { + }, + "国际冠字码" : { + }, "在使用本 App 前,您需要先知晓以下信息:\n· 本 App 由第三方开发者以及部分社区用户贡献,与哔哩哔哩无合作关系,哔哩哔哩是上海宽娱数码科技有限公司的商标。\n· 本 App 并不是哔哩哔哩的替代品,我们建议您在能够使用官方客户端时尽量使用官方客户端。\n· 本 App 均使用来源于网络的公开信息进行开发。\n· 本 App 中和B站相关的功能完全免费\n· 本 App 中所呈现的B站内容来自哔哩哔哩官方。\n· 本 App 的开发者、负责人和实际责任人是%@\n 联系QQ:3245146430" : { "localizations" : { @@ -4737,31 +4769,94 @@ } } }, - "将不再记录您的屏幕使用时间, 已记录的数据不会被删除" : { + "将作为动态主体" : { + + }, + "小尾巴内容" : { + + }, + "已选择的图片" : { + + }, + "应用" : { }, "开启“屏幕使用时间”" : { }, - "番剧" : { + "您可以更改默认的小尾巴内容, 如果不想添加小尾巴, 请清空上方文本框内容. 您可以随时在设置中更改此内容" : { + + }, + "我的" : { + + }, + "手机号" : { + + }, + "推荐" : { + + }, + "登录" : { + + }, + "稿件" : { + + }, + "第一步: 手机号信息" : { + + }, + "第三步: 验证码" : { + + }, + "第二步: 人机验证" : { + + }, + "获取验证码" : { + + }, + "要使用动态小尾巴吗?" : { + + }, + "评论" : { + + }, + "详情" : { + + }, + "进行人机验证" : { + + }, + "选择图片" : { + + }, + "重新载入" : { + + }, + "验证码" : { + + }, + "" : { + + }, + "" : { }, - "直播" : { + "" : { }, - "确认" : { + "" : { }, - "视频" : { + "" : { }, - "视频下载列表" : { + "" : { }, - "神秘代码" : { + "" : { }, - "这里空空如也" : { + "" : { } }, diff --git a/MeowBili-Watch-App-Info.plist b/MeowBili-Watch-App-Info.plist index f6cad2bc6..b2d450c61 100644 --- a/MeowBili-Watch-App-Info.plist +++ b/MeowBili-Watch-App-Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/MeowBili/Assets.xcassets/AccentColor.colorset/Contents.json b/MeowBili/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..2b3f335a3 --- /dev/null +++ b/MeowBili/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.729", + "green" : "0.537", + "red" : "0.973" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/AppIcon.appiconset/Contents.json b/MeowBili/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..f32144199 --- /dev/null +++ b/MeowBili/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "未标题-2.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git "a/MeowBili/Assets.xcassets/AppIcon.appiconset/\346\234\252\346\240\207\351\242\230-2.png" "b/MeowBili/Assets.xcassets/AppIcon.appiconset/\346\234\252\346\240\207\351\242\230-2.png" new file mode 100644 index 000000000..324f01d9e Binary files /dev/null and "b/MeowBili/Assets.xcassets/AppIcon.appiconset/\346\234\252\346\240\207\351\242\230-2.png" differ diff --git a/MeowBili/Assets.xcassets/AppIconImage.imageset/Contents.json b/MeowBili/Assets.xcassets/AppIconImage.imageset/Contents.json new file mode 100644 index 000000000..073f106ae --- /dev/null +++ b/MeowBili/Assets.xcassets/AppIconImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "ico.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ico2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ico1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/AppIconImage.imageset/ico.png b/MeowBili/Assets.xcassets/AppIconImage.imageset/ico.png new file mode 100644 index 000000000..324f01d9e Binary files /dev/null and b/MeowBili/Assets.xcassets/AppIconImage.imageset/ico.png differ diff --git a/MeowBili/Assets.xcassets/AppIconImage.imageset/ico1.png b/MeowBili/Assets.xcassets/AppIconImage.imageset/ico1.png new file mode 100644 index 000000000..324f01d9e Binary files /dev/null and b/MeowBili/Assets.xcassets/AppIconImage.imageset/ico1.png differ diff --git a/MeowBili/Assets.xcassets/AppIconImage.imageset/ico2.png b/MeowBili/Assets.xcassets/AppIconImage.imageset/ico2.png new file mode 100644 index 000000000..324f01d9e Binary files /dev/null and b/MeowBili/Assets.xcassets/AppIconImage.imageset/ico2.png differ diff --git a/MeowBili/Assets.xcassets/Coin1Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Coin1Icon.imageset/Contents.json new file mode 100644 index 000000000..7f01adb85 --- /dev/null +++ b/MeowBili/Assets.xcassets/Coin1Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "coin1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "coin1 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "coin1 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1 1.png b/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1 1.png new file mode 100644 index 000000000..9c6543fa2 Binary files /dev/null and b/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1 1.png differ diff --git a/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1 2.png b/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1 2.png new file mode 100644 index 000000000..9c6543fa2 Binary files /dev/null and b/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1 2.png differ diff --git a/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1.png b/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1.png new file mode 100644 index 000000000..9c6543fa2 Binary files /dev/null and b/MeowBili/Assets.xcassets/Coin1Icon.imageset/coin1.png differ diff --git a/MeowBili/Assets.xcassets/Coin2Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Coin2Icon.imageset/Contents.json new file mode 100644 index 000000000..9d9fed283 --- /dev/null +++ b/MeowBili/Assets.xcassets/Coin2Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "coin2 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "coin2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "coin2 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2 1.png b/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2 1.png new file mode 100644 index 000000000..8a20b1912 Binary files /dev/null and b/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2 1.png differ diff --git a/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2 2.png b/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2 2.png new file mode 100644 index 000000000..8a20b1912 Binary files /dev/null and b/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2 2.png differ diff --git a/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2.png b/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2.png new file mode 100644 index 000000000..8a20b1912 Binary files /dev/null and b/MeowBili/Assets.xcassets/Coin2Icon.imageset/coin2.png differ diff --git a/MeowBili/Assets.xcassets/Contents.json b/MeowBili/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/MeowBili/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv0Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv0Icon.imageset/Contents.json new file mode 100644 index 000000000..3838c6bb0 --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv0Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv0.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv0 2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv0 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0 1.png b/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0 1.png new file mode 100644 index 000000000..17bb5adf3 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0 2.png b/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0 2.png new file mode 100644 index 000000000..17bb5adf3 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0.png b/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0.png new file mode 100644 index 000000000..17bb5adf3 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv0Icon.imageset/lv0.png differ diff --git a/MeowBili/Assets.xcassets/Lv1Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv1Icon.imageset/Contents.json new file mode 100644 index 000000000..9388a8938 --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv1Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv1_1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv1_1 2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv1_1 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1 1.png b/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1 1.png new file mode 100644 index 000000000..d756ac6fa Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1 2.png b/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1 2.png new file mode 100644 index 000000000..d756ac6fa Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1.png b/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1.png new file mode 100644 index 000000000..d756ac6fa Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv1Icon.imageset/lv1_1.png differ diff --git a/MeowBili/Assets.xcassets/Lv2Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv2Icon.imageset/Contents.json new file mode 100644 index 000000000..19bd0ab90 --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv2Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv2 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv2 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2 1.png b/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2 1.png new file mode 100644 index 000000000..15ed2c51f Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2 2.png b/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2 2.png new file mode 100644 index 000000000..15ed2c51f Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2.png b/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2.png new file mode 100644 index 000000000..15ed2c51f Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv2Icon.imageset/lv2.png differ diff --git a/MeowBili/Assets.xcassets/Lv3Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv3Icon.imageset/Contents.json new file mode 100644 index 000000000..6c47c59ee --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv3Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv3 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv3.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv3 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3 1.png b/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3 1.png new file mode 100644 index 000000000..641488074 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3 2.png b/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3 2.png new file mode 100644 index 000000000..641488074 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3.png b/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3.png new file mode 100644 index 000000000..641488074 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv3Icon.imageset/lv3.png differ diff --git a/MeowBili/Assets.xcassets/Lv4Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv4Icon.imageset/Contents.json new file mode 100644 index 000000000..1d2bea826 --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv4Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv4 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv4.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv4 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4 1.png b/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4 1.png new file mode 100644 index 000000000..6ad3b7cfa Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4 2.png b/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4 2.png new file mode 100644 index 000000000..6ad3b7cfa Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4.png b/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4.png new file mode 100644 index 000000000..6ad3b7cfa Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv4Icon.imageset/lv4.png differ diff --git a/MeowBili/Assets.xcassets/Lv5Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv5Icon.imageset/Contents.json new file mode 100644 index 000000000..4608ba70b --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv5Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv5 1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv5.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv5 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5 1.png b/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5 1.png new file mode 100644 index 000000000..118e7e65b Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5 2.png b/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5 2.png new file mode 100644 index 000000000..118e7e65b Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5.png b/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5.png new file mode 100644 index 000000000..118e7e65b Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv5Icon.imageset/lv5.png differ diff --git a/MeowBili/Assets.xcassets/Lv6Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv6Icon.imageset/Contents.json new file mode 100644 index 000000000..3e38af178 --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv6Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv6 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv6.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv6 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6 1.png b/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6 1.png new file mode 100644 index 000000000..518bc1630 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6 2.png b/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6 2.png new file mode 100644 index 000000000..518bc1630 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6.png b/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6.png new file mode 100644 index 000000000..518bc1630 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv6Icon.imageset/lv6.png differ diff --git a/MeowBili/Assets.xcassets/Lv7Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv7Icon.imageset/Contents.json new file mode 100644 index 000000000..148c3cdce --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv7Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv7.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv7 2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv7 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7 1.png b/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7 1.png new file mode 100644 index 000000000..ea77054c4 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7 2.png b/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7 2.png new file mode 100644 index 000000000..ea77054c4 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7.png b/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7.png new file mode 100644 index 000000000..ea77054c4 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv7Icon.imageset/lv7.png differ diff --git a/MeowBili/Assets.xcassets/Lv8Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv8Icon.imageset/Contents.json new file mode 100644 index 000000000..92c9d4fa9 --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv8Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv8 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv8.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv8 1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8 1.png b/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8 1.png new file mode 100644 index 000000000..547b488e4 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8 2.png b/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8 2.png new file mode 100644 index 000000000..547b488e4 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8.png b/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8.png new file mode 100644 index 000000000..547b488e4 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv8Icon.imageset/lv8.png differ diff --git a/MeowBili/Assets.xcassets/Lv9Icon.imageset/Contents.json b/MeowBili/Assets.xcassets/Lv9Icon.imageset/Contents.json new file mode 100644 index 000000000..991c50a1c --- /dev/null +++ b/MeowBili/Assets.xcassets/Lv9Icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lv9 1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lv9.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lv9 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9 1.png b/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9 1.png new file mode 100644 index 000000000..978d8e681 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9 1.png differ diff --git a/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9 2.png b/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9 2.png new file mode 100644 index 000000000..978d8e681 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9 2.png differ diff --git a/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9.png b/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9.png new file mode 100644 index 000000000..978d8e681 Binary files /dev/null and b/MeowBili/Assets.xcassets/Lv9Icon.imageset/lv9.png differ diff --git a/MeowBili/Assets.xcassets/Placeholder.imageset/Contents.json b/MeowBili/Assets.xcassets/Placeholder.imageset/Contents.json new file mode 100644 index 000000000..92127c3c6 --- /dev/null +++ b/MeowBili/Assets.xcassets/Placeholder.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "未标题-1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "未标题-1 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "未标题-1 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git "a/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1 1.png" "b/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1 1.png" new file mode 100644 index 000000000..0a7b32b98 Binary files /dev/null and "b/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1 1.png" differ diff --git "a/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1 2.png" "b/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1 2.png" new file mode 100644 index 000000000..0a7b32b98 Binary files /dev/null and "b/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1 2.png" differ diff --git "a/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1.png" "b/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1.png" new file mode 100644 index 000000000..0a7b32b98 Binary files /dev/null and "b/MeowBili/Assets.xcassets/Placeholder.imageset/\346\234\252\346\240\207\351\242\230-1.png" differ diff --git a/MeowBili/Bangumi/BangumiDetailView.swift b/MeowBili/Bangumi/BangumiDetailView.swift new file mode 100644 index 000000000..7a55d5ba0 --- /dev/null +++ b/MeowBili/Bangumi/BangumiDetailView.swift @@ -0,0 +1,258 @@ +// +// +// BangumiDetailView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import Marquee +import DarockKit +import Alamofire +import SwiftyJSON +import CachedAsyncImage +import SDWebImageSwiftUI + +struct BangumiDetailView: View { + public static var willPlayBangumiLink = "" + public static var willPlayBangumiData: BangumiData? + @State var bangumiData: BangumiData + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @AppStorage("RecordHistoryTime") var recordHistoryTime = "into" + @State var paymentData: BangumiPayment? = nil + @State var epDatas = [BangumiEp]() + @State var isLoading = false + @State var mainTabSelection = 1 + @State var isBangumiPlayerPresented = false + @State var isMoreMenuPresented = false + @State var backgroundPicOpacity = 0.0 + @State var navigationSelectedEpdata: BangumiEp? = nil + var body: some View { + TabView { + ZStack { + Group { + ScrollView { + DetailViewFirstPageBase(bangumiData: $bangumiData, isBangumiPlayerPresented: $isBangumiPlayerPresented, isLoading: $isLoading) + DetailViewSecondPageBase(bangumiData: $bangumiData, paymentData: $paymentData) + EpisodeListView(bangumiData: $bangumiData, epDatas: $epDatas, isBangumiPlayerPresented: $isBangumiPlayerPresented, isLoading: $isLoading) + } + } + .blur(radius: isLoading ? 14 : 0) + if isLoading { + Text("Video.analyzing") + .font(.title2) + .bold() + } + } + .tag(1) + + Group { + if epDatas.count != 0 { + List { + Text("Bangumi.comments.select") + .listRowBackground(Color.clear) + ForEach(0.. 0 { + playerRotate -= 90 + } else { + playerRotate = 270 + } + }, label: { + Image(systemName: "rotate.left") + }) + Button(action: { + if playerRotate + 90 < 360 { + playerRotate += 90 + } else { + playerRotate = 0 + } + }, label: { + Image(systemName: "rotate.right") + }) + } + } + } + .tag(2) + } + .tabViewStyle(.page) + } + .ignoresSafeArea() + .onAppear { + let asset = AVURLAsset(url: URL(string: BangumiDetailView.willPlayBangumiLink)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["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", "Referer": "https://www.bilibili.com"]]) + let item = AVPlayerItem(asset: asset) + player = AVPlayer(playerItem: item) + + debugPrint(URL(string: BangumiDetailView.willPlayBangumiLink)!) +// let headers: HTTPHeaders = [ +// "cookie": "SESSDATA=\(sessdata)" +// ] +// if recordHistoryTime == "play" { +// AF.request("https://api.bilibili.com/x/click-interface/web/heartbeat", method: .post, parameters: ["bvid": VideoDetailView.willPlayVideoBV, "mid": dedeUserID, "type": 3, "dt": 2, "play_type": 2, "csrf": biliJct], headers: headers).response { response in +// debugPrint(response) +// } +// } + } + .onDisappear { + playerTimer?.invalidate() + } + } +} + +#Preview { + BangumiPlayerView() +} diff --git a/MeowBili/Errors/Backtrace/backtrace.c b/MeowBili/Errors/Backtrace/backtrace.c new file mode 100644 index 000000000..eecd84f5d --- /dev/null +++ b/MeowBili/Errors/Backtrace/backtrace.c @@ -0,0 +1,71 @@ +// +// +// backtrace.c +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#include "backtrace.h" +#include +#include +#include + +#if defined __i386__ +#define THREAD_STATE_FLAVOR x86_THREAD_STATE +#define THREAD_STATE_COUNT x86_THREAD_STATE_COUNT +#define __framePointer __ebp + +#elif defined __x86_64__ +#define THREAD_STATE_FLAVOR x86_THREAD_STATE64 +#define THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT +#define __framePointer __rbp + +#elif defined __arm__ +#define THREAD_STATE_FLAVOR ARM_THREAD_STATE +#define THREAD_STATE_COUNT ARM_THREAD_STATE_COUNT +#define __framePointer __r[7] + +#elif defined __arm64__ +#define THREAD_STATE_FLAVOR ARM_THREAD_STATE64 +#define THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT +#define __framePointer __fp + +#else +#error "Current CPU Architecture is not supported" +#endif + +int df_backtrace(thread_t thread, void** stack, int maxSymbols) { + _STRUCT_MCONTEXT machineContext; +// mach_msg_type_number_t stateCount = THREAD_STATE_COUNT; +// kern_return_t kret = thread_get_state(thread, THREAD_STATE_FLAVOR, (thread_state_t)&(machineContext.__ss), &stateCount); +// if (kret != KERN_SUCCESS) { +// return 0; +// } + int i = 0; +#if defined(__arm__) || defined (__arm64__) + stack[i] = (void *)machineContext.__ss.__lr; + ++i; +#endif + void **currentFramePointer = (void **)machineContext.__ss.__framePointer; + while (i < maxSymbols && currentFramePointer) { + void **previousFramePointer = *currentFramePointer; + if (!previousFramePointer) { + break; + } + stack[i] = *(currentFramePointer + 1); + currentFramePointer = previousFramePointer; + ++i; + } + return i; +} diff --git a/MeowBili/Errors/Backtrace/backtrace.h b/MeowBili/Errors/Backtrace/backtrace.h new file mode 100644 index 000000000..9eb845399 --- /dev/null +++ b/MeowBili/Errors/Backtrace/backtrace.h @@ -0,0 +1,26 @@ +// +// +// backtrace.h +// DarockBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef backtrace_h +#define backtrace_h + +#include + +int df_backtrace(thread_t thread, void** stack, int maxSymbols); + +#endif /* backtrace_h */ diff --git a/MeowBili/Errors/Backtrace/backtrace.swift b/MeowBili/Errors/Backtrace/backtrace.swift new file mode 100644 index 000000000..d24a89a61 --- /dev/null +++ b/MeowBili/Errors/Backtrace/backtrace.swift @@ -0,0 +1,113 @@ +// +// +// backtrace.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Foundation + +public func backtrace(thread: Thread)->String{ + let name = "Backtrace of : \(thread.description)\n" + if Thread.current == thread { + return name + Thread.callStackSymbols.joined(separator: "\n") + } + let mach = machThread(from: thread) + return name + backtrace(t: mach) +} +public func backtraceMainThread()->String{ + return backtrace(thread: Thread.main) +} +public func backtraceCurrentThread()->String{ + return backtrace(thread: Thread.current) +} +public func backtraceAllThread()->[String]{ + var count: mach_msg_type_number_t = 0 + var threads: thread_act_array_t! + guard task_threads(mach_task_self_, &(threads), &count) == KERN_SUCCESS else { + return [backtrace(t: mach_thread_self())] + } + var symbols = [String]() + for i in 0.. String{ + let maxSize: Int32 = 128 + let addrs = UnsafeMutablePointer.allocate(capacity: Int(maxSize)) + defer { addrs.deallocate() } + let count = backtrace(t, stack: addrs, maxSize) + var symbols: [String] = [] + if let bs = backtrace_symbols(addrs, count) { + symbols = UnsafeBufferPointer(start: bs, count: Int(count)).map { + guard let symbol = $0 else { + return "" + } + return String(cString: symbol) + } + } + return symbols.joined(separator: "\n") +} + +/// 声明C的符号 +@_silgen_name("df_backtrace") +fileprivate func backtrace(_ thread: thread_t, stack: UnsafeMutablePointer!, _ maxSymbols: Int32) -> Int32 + +@_silgen_name("backtrace_symbols") +fileprivate func backtrace_symbols(_ stack: UnsafePointer!, _ frame: Int32) -> UnsafeMutablePointer?>! + + +/** + 这里主要利用了Thread 和 pThread 共用一个Name的特性,找到对应 thread的内核线程thread_t + 但是主线程不行,主线程设置Name无效. + */ +public var main_thread_t: mach_port_t? +fileprivate func machThread(from thread: Thread) -> thread_t { + var count: mach_msg_type_number_t = 0 + var threads: thread_act_array_t! + + guard task_threads(mach_task_self_, &(threads), &count) == KERN_SUCCESS else { + return mach_thread_self() + } + + /// 如果当前线程不是主线程,但是需要获取主线程的堆栈 + if !Thread.isMainThread && thread.isMainThread && main_thread_t == nil { + DispatchQueue.main.sync { + main_thread_t = mach_thread_self() + } + return main_thread_t ?? mach_thread_self() + } + + let originName = thread.name + defer { + thread.name = originName + } + let newName = String(Int(Date.init().timeIntervalSince1970)) + thread.name = newName + for i in 0..(repeating: 0, count: 128) + pthread_getname_np(p_thread, &name, name.count) + if thread.name == String(cString: name) { + return machThread + } + } + } + return mach_thread_self() +} + diff --git a/MeowBili/Errors/ErrorGetView.swift b/MeowBili/Errors/ErrorGetView.swift new file mode 100644 index 000000000..af1379846 --- /dev/null +++ b/MeowBili/Errors/ErrorGetView.swift @@ -0,0 +1,152 @@ +// +// +// ErrorGetView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit + +struct ErrorGetView: View { + var error: GetableError + @Environment(\.dismiss) var dismiss + @State var doing = "" + @State var isClosePresented = false + @State var isSendPresented = false + @State var isSent = false + @State var sentCode = "" + @State var isError = false + @State var isNetworkFixPresented = false + var body: some View { + ScrollView { + Group { + Text("Error.ran-into-a-problem") + .font(.system(size: 22, weight: .bold)) + Spacer() + .frame(height: 15) + Text(String(localized: "Error.sorry.\(error.when)") + "\(error.ignoreable ? "" : String(localized: "Error.fatal"))") + if error.area == "网络请求" { + Spacer() + .frame(height: 15) + Button(action: { + isNetworkFixPresented = true + }, label: { + Text("Error.network-troubleshoot") + }) + .sheet(isPresented: $isNetworkFixPresented, content: {NetworkFixView()}) + } + Spacer() + .frame(height: 10) + Text("Error.information") + Text("Error.area.\(error.area)") + Text("Error.place.\(error.inAppArea)") + Text("Error.details.\(error.errDetail)") + Spacer() + .frame(height: 15) + } + Group { + if error.sendable { + Text("Error.send-to-Darock-advice") + Spacer() + .frame(height: 10) + Text("Error") + TextField("Error.before-ranning-into-problem", text: $doing) + Spacer() + .frame(height: 15) + Button(action: { + isSendPresented = true + }, label: { + Text("Error.send") + .bold() + }) + .sheet(isPresented: $isSendPresented, content: { + VStack { + if !isSent { + ProgressView() + Text("Error.sending") + .bold() + Text("Error.appriciate") + .bold() + } else { + Text("Error.sent") + .bold() + Text("Error.number.\(Text(sentCode))") + .font(.system(size: 18, weight: .bold)) + if error.ignoreable { + Button(action: { + dismiss() + }, label: { + Text("Error.leave") + .bold() + }) + } else { + Button(action: { + exit(114514) + }, label: { + Text("Error.exit") + .bold() + }) + } + } + } + .onAppear { + DarockKit.Network.shared.requestString("https://api.darock.top/bili/feedback/\(("范围:\(error.area)\n位置:\(error.inAppArea)\n详细信息:\(error.errDetail)\n用户描述:\(doing)").base64Encoded())") { respStr, isSuccess in + if isSuccess { + sentCode = respStr + isSent = true + } + } + } + }) + Button(action: { + if error.ignoreable { + dismiss() + } else { + isClosePresented = true + } + }, label: { + Text("Error.do-not-send") + }) + .sheet(isPresented: $isClosePresented, content: { + Text("Error.exiting") + .onAppear { + Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { timer in + timer.invalidate() + exit(114514) + } + } + }) + } else { + Text("Error.no-need-to-send") + } + } + } + } +} + +struct GetableError { + let when: String + let area: String + let inAppArea: String + let errDetail: String + var ignoreable: Bool = true + var sendable: Bool = true +} + +struct ErrorGetView_Previews: PreviewProvider { + static var previews: some View { + ErrorGetView(error: GetableError(when: "Test", area: "Test", inAppArea: "Test", errDetail: "Test")) + } +} diff --git a/MeowBili/Errors/FeedbackView.swift b/MeowBili/Errors/FeedbackView.swift new file mode 100644 index 000000000..e1739510b --- /dev/null +++ b/MeowBili/Errors/FeedbackView.swift @@ -0,0 +1,106 @@ +// +// +// FeedbackView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import EFQRCode + +//struct FeedbackView: View { +// @Environment(\.dismiss) var dismiss +// @State var choseFeedbackType = "错误/异常行为" +// @State var title = "" +// @State var detail = "" +// @State var isLoading = false +// @State var sendStep = "" +// @State var feedbackCode = "" +// var body: some View { +// List { +// Section { +// Picker(selection: $choseFeedbackType, label: Text("反馈类型")) { +// Text("错误/异常行为").tag("错误/异常行为") +// Text("建议").tag("建议") +// } +// } +// Section(footer: Text("简单地描述问题")) { +// TextField("标题", text: $title) +// } +// Section(footer: Text("描述发生问题前做了什么?预期的结果?实际的结果?")) { +// TextField("详细信息", text: $detail) +// } +// Section { +// Text("您的 App 版本将会一并被发送") +// Text("发送的反馈中不含任何您的个人信息") +// Text("请不要在问题描述中填写个人信息") +// } +// Section { +// Button(action: { +// isLoading = true +// }, label: { +// Text("发送") +// }) +// .sheet(isPresented: $isLoading, onDismiss: { +// dismiss() +// }, content: { +// VStack { +// if feedbackCode == "" { +// ProgressView() +// Text("正在发送...") +// .onAppear { +// DarockKit.Network.shared.requestString("https://api.darock.top/bili/feedback/\(("类型:\(choseFeedbackType)\n标题:\(title)\n详细信息:\(detail)\n版本:v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String) Build \(Bundle.main.infoDictionary?["CFBundleVersion"] as! String)").base64Encoded().replacingOccurrences(of: "/", with: "{slash}"))") { respStr, isSuccess in +// if isSuccess { +// feedbackCode = respStr +// } +// } +// } +// //Text("正在\(sendStep)") +// } else { +// Text("反馈成功!后续可使用反馈 ID:\(Text(feedbackCode).font(.system(size: 18, design: .monospaced)).bold()) 跟进情况") +// } +// } +// +// }) +// } +// } +// } +//} + +struct FeedbackView: View { + @State var qrImage: CGImage? + var body: some View { + VStack { + if qrImage != nil { + Image(uiImage: UIImage(cgImage: qrImage!)) + .resizable() + .frame(width: 140, height: 140) + Text("Feedback.continue-on-other-device") + .bold() + } + } + .onAppear { + if let image = EFQRCode.generate(for: "https://github.com/Darock-Studio/Darock-Bili/issues") { + qrImage = image + } + } + } +} + +struct FeedbackView_Previews: PreviewProvider { + static var previews: some View { + FeedbackView() + } +} diff --git a/MeowBili/Errors/MemoryWarningView.swift b/MeowBili/Errors/MemoryWarningView.swift new file mode 100644 index 000000000..c7f18fc7a --- /dev/null +++ b/MeowBili/Errors/MemoryWarningView.swift @@ -0,0 +1,51 @@ +// +// +// MemoryWarningView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI + +struct MemoryWarningView: View { + @Environment(\.dismiss) var dismiss + var body: some View { + ScrollView { + VStack { + Text("Memory.caution") + .font(.system(size: 18, weight: .bold)) + Text("Memory.too-much-occupied") + Text("Memory.limit") + Button(action: { + dismiss() + }, label: { + Text("Memory.understand") + }) + Button(action: { + isShowMemoryInScreen = true + dismiss() + }, label: { + Text("Memory.display-usage") + }) + } + .multilineTextAlignment(.center) + } + } +} + +struct MemoryWarningView_Previews: PreviewProvider { + static var previews: some View { + MemoryWarningView() + } +} diff --git a/MeowBili/Errors/NetwokFixView.swift b/MeowBili/Errors/NetwokFixView.swift new file mode 100644 index 000000000..3ef8715bc --- /dev/null +++ b/MeowBili/Errors/NetwokFixView.swift @@ -0,0 +1,464 @@ +// +// +// NetwokFixView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import Alamofire +import SwiftyJSON +import Foundation + +struct NetworkFixView: View { + @State var progressTimer: Timer? + @State var networkState = 0 + @State var darockAPIState = 0 + @State var bilibiliAPIState = 0 + @State var isTroubleshooting = false + // 0 尚未检查 + // 1 正在检查 + // 2 不可用 + // 3 可用 + // 4 无效返回 + var lightColors: [Color] = [.secondary, .orange, .red, .green, .red] + var body: some View { + NavigationStack { + List { + Section { + if !isTroubleshooting { + if networkState == 3 && darockAPIState == 3 && bilibiliAPIState == 3 { + HStack { + Image(systemName: "checkmark") + .foregroundColor(.green) + Text("Troubleshoot.fine") + .bold() + } + NavigationLink(destination: {FeedbackView()}, label: { + VStack(alignment: .leading) { + Text("Troubleshoot.fine.weird") + Text("Troubleshoot.feedback") + .font(.system(size: 13)) + .foregroundColor(.gray) + } + }) + } else { + Text("Troubleshoot.problems-found") + .bold() + if networkState == 2 { + NavigationLink(destination: {networkProblemDetailsView()}, label: { + Text("Troubleshoot.problems.internet") + }) + } + if darockAPIState == 2 || darockAPIState == 4 { + NavigationLink(destination: {darockAPIProblemDetailsView()}, label: { + Text(darockAPIState == 2 ? "Troubleshoot.problems.darock-api.unavailable" : "Troubleshoot.problems.darock-api.invalid-return") + }) + } + if bilibiliAPIState == 2 { + NavigationLink(destination: {bilibiliAPIProblemDetailsView()}, label: { + Text("Troubleshoot.problems.bilibili-api.unavailable") + }) + } + } + } else { + Text("Troubleshoot.troubleshooting") + .bold() + } + } footer: { + if !(networkState == 3 && darockAPIState == 3 && bilibiliAPIState == 3) { + Text("Troubleshoot.problem.tips") //轻点问题以查看详细信息 + } + } + Section("Troubleshoot.connection-states") { + HStack { + Circle() + .frame(width: 10) + .foregroundStyle(lightColors[networkState]) + .padding(.trailing, 7) + if networkState == 0 { + Text("Troubleshoot.internet") + } else if networkState == 1 { + Text("Troubleshoot.internet.checking") + } else if networkState == 2 { + Text("Troubleshoot.internet.offline") + } else if networkState == 3 { + Text("Troubleshoot.internet.online") + } + Spacer() + } + .padding() + HStack { + Circle() + .frame(width: 10) + .foregroundStyle(lightColors[darockAPIState]) + .padding(.trailing, 7) + if darockAPIState == 0 { + Text("Troubleshoot.darock-api") + .foregroundStyle(networkState != 3 ? Color.secondary : .primary) + } else if darockAPIState == 1 { + Text("Troubleshoot.darock-api.checking") + } else if darockAPIState == 2 { + Text("Troubleshoot.darock-api.unavailable") + } else if darockAPIState == 3 { + Text("Troubleshoot.darock-api.available") + } else if darockAPIState == 4 { + Text("Troubleshoot.darock-api.invalid-return") + } + Spacer() + } + .disabled(networkState != 3) + .padding() + HStack { + Circle() + .frame(width: 10) + .foregroundStyle(lightColors[bilibiliAPIState]) + .padding(.trailing, 7) + if bilibiliAPIState == 0 { + Text("Troubleshoot.bilibili-api") + .foregroundStyle(networkState != 3 ? Color.secondary : .primary) + } else if bilibiliAPIState == 1 { + Text("Troubleshoot.bilibili-api.checking") + } else if bilibiliAPIState == 2 { + Text("Troubleshoot.bilibili-api.unavailable") + } else if bilibiliAPIState == 3 { + Text("Troubleshoot.bilibili-api.available") + } + Spacer() + } + .disabled(networkState != 3) + .padding() + Button(action: { + isTroubleshooting = true + networkState = 0 + darockAPIState = 0 + bilibiliAPIState = 0 + checkInternet() + func checkInternet() { + Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { timer in + timer.invalidate() + networkState = 1 + DarockKit.Network.shared.requestString("https://apple.com.cn") { _, isSuccess in + if isSuccess { + checkDarock() + networkState = 3 + } else { + networkState = 2 + isTroubleshooting = false + } + } + } + + func checkDarock() { + darockAPIState = 1 + DarockKit.Network.shared.requestString("https://api.darock.top") { respStr, isSuccess in + if isSuccess { + if respStr.apiFixed() == "OK" { + darockAPIState = 3 + } else { + darockAPIState = 4 + } + } else { + darockAPIState = 1 + } + checkBilibili() + } + } + + func checkBilibili() { + bilibiliAPIState = 2 + DarockKit.Network.shared.requestString("https://api.bilibili.com/") { _, isSuccess in + if isSuccess { + bilibiliAPIState = 3 + } else { + bilibiliAPIState = 2 + } + } + isTroubleshooting = false + } + } + }, label: { + Text(isTroubleshooting ? "Troubleshoot.troubleshooting" : "Troubleshoot.re-troubleshoot") + }) + .disabled(isTroubleshooting) + } + } + /* VStack { + if !isFinish { + ProgressView(value: progress, total: 100.0) + .progressViewStyle(.linear) + .foregroundColor(.blue) + Text("正在\(fixingItem)") + } else { + if troubles.count != 0 { + List { + Section { + Text("Troubleshooter.what-we-found") + } + Section { + ForEach(0...troubles.count - 1, id: \.self) { i in + Text(troubles[i]) + .bold() + .onAppear { + if troubles[i].contains("Darock") { + isDarockIssue = true + } else if troubles[i] == "Troubleshooter.no-internet" { + isNetworkIssue = true + } + } + } + } + if isNetworkIssue { + Section { + NavigationLink(destination: {UserNetworkGuide()}, label: { + Text("问题来自您的网络连接,点此查看网络说明") + }) + } + } + if isDarockIssue { + Section { + Text("问题可能来自 Darock,请联系开发者或等待问题解决") + } + } + } + } else { + Text("Troubleshooter.failed") + Text("Troubleshooter.failed.discription") //请重试之前的操作,如果问题依然存在,请联系开发者 + } + } + }*/ + } + .onAppear { + isTroubleshooting = true + networkState = 0 + darockAPIState = 0 + bilibiliAPIState = 0 + checkInternet() + func checkInternet() { + Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in + timer.invalidate() + networkState = 1 + DarockKit.Network.shared.requestString("https://baidu.com") { _, isSuccess in + if isSuccess { + checkDarock() + networkState = 3 + } else { + networkState = 2 + isTroubleshooting = false + } + } + } + } + + /* func checkDarockTest() { + Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in + timer.invalidate() + darockAPIState = 1 + DarockKit.Network.shared.requestString("https://baidu.com") { _, isSuccess in + if isSuccess { + darockAPIState = 3 + } else { + darockAPIState = 2 + isTroubleshooting = false + } + } + } + } */ + + func checkDarock() { + darockAPIState = 1 + DarockKit.Network.shared.requestString("https://api.darock.top") { respStr, isSuccess in + Timer.scheduledTimer(withTimeInterval: 2.5, repeats: false) { timer in + if isSuccess { + if respStr.apiFixed() == "OK" { + darockAPIState = 3 + } else { + darockAPIState = 4 + isTroubleshooting = false + } + } else { + darockAPIState = 4 + isTroubleshooting = false + } + timer.invalidate() + } + checkBilibili() + } + + } + + func checkBilibili() { + bilibiliAPIState = 2 + DarockKit.Network.shared.requestString("https://api.bilibili.com/") { _, isSuccess in + if isSuccess { + bilibiliAPIState = 3 + } else { + bilibiliAPIState = 2 + } + } + isTroubleshooting = false + } + } + .onDisappear { + progressTimer?.invalidate() + } + } +} + +struct networkProblemDetailsView: View { + var body: some View { + List { + Section { + Text("Troubleshoot.problem.internet") + .bold() + } + Section("Troubleshoot.problem.meaning") { + Text("Troubleshoot.problem.internet.meaning") + } + Section("Troubleshoot.problem.solution") { + Text("Troubleshoot.problem.internet.solution1") + Text("Troubleshoot.problem.internet.solution2") + } + Section("Troubleshoot.problem.plan-b") { + Text("Troubleshoot.problem.internet.plan-b") + } + } + } +} + +struct darockAPIProblemDetailsView: View { + var body: some View { + NavigationStack { + List { + Section { + Text("Troubleshoot.problem.darock-api") + .bold() + } + Section("Troubleshoot.problem.meaning") { + Text("Troubleshoot.problem.darock-api.meaning") + } + Section("Troubleshoot.problem.solution") { + Text("Troubleshoot.problem.darock-api.solution") + } + } + } + } +} + +struct bilibiliAPIProblemDetailsView: View { + var body: some View { + List { + Section { + Text("Troubleshoot.problem.bilibili-api") + .bold() + } + Section("Troubleshoot.problem.meaning") { + Text("Troubleshoot.problem.bilibili-api.meaning") + } + Section("Troubleshoot.problem.solution") { + Text("Troubleshoot.problem.bilibili-api.solution") + } + } + } +} + +/* struct UserNetworkGuide: View { + var body: some View { + List { + Section { + Text("网络说明") + } + Section { + Text("您应当确认:") + .bold() + Text("Watch 的状态栏(控制中心上方)显示为\(Image(systemName: "wifi"))而不是\(Image(systemName: "iphone"))") + } + Section { + Text("怎么做?") + Text("关闭 iPhone 的 Wi-Fi 以及蓝牙") + .bold() + } + } + } +} */ + +let errorCodeTextDic = [ + -1: "应用程序不存在或已被封禁", + -2: "Access Key 错误", + -3: "API 校验密匙错误", + -4: "调用方对该 Method 没有权限", + -101: "账号未登录", + -102: "账号被封停", + -103: "积分不足", + -104: "硬币不足", + -105: "验证码错误", + -106: "账号非正式会员或在适应期", + -107: "应用不存在或者被封禁", + -108: "未绑定手机", + -110: "未绑定手机", + -111: "csrf 校验失败", + -112: "系统升级中", + -113: "账号尚未实名认证", + -114: "请先绑定手机", + -115: "请先完成实名认证", + -304: "木有改动", + -307: "撞车跳转", + -400: "请求错误", + -401: "未认证 / 非法请求", + -403: "访问权限不足", + -404: "啥都木有", + -405: "不支持该方法", + -409: "冲突", + -412: "请求被拦截", + -500: "服务器错误", + -503: "过载保护,服务暂不可用", + -504: "服务调用超时", + -509: "超出限制", + -616: "上传文件不存在", + -617: "上传文件太大", + -625: "登录失败次数太多", + -626: "用户不存在", + -628: "密码太弱", + -629: "用户名或密码错误", + -632: "操作对象数量限制", + -643: "被锁定", + -650: "用户等级太低", + -652: "重复的用户", + -658: "Token 过期", + -662: "密码时间戳过期", + -688: "地理区域限制", + -689: "版权限制", + -701: "扣节操失败", + -799: "请求过于频繁,请稍后再试", + -8888: "对不起,服务器开小差了~" +] + +public func CheckBApiError(from input: JSON, noTip: Bool = false) -> Bool { + let code = input["code"].int ?? 0 + if code == 0 { + return true + } + let msg = errorCodeTextDic[code] ?? (input["message"].string ?? "") + if !noTip { + AlertKitAPI.present(title: msg, icon: .error, style: .iOS17AppleMusic, haptic: .error) + } + return false +} + +struct NetworkFixView_Previews: PreviewProvider { + static var previews: some View { + NetworkFixView() + } +} diff --git a/MeowBili/Errors/SignalErrorView.swift b/MeowBili/Errors/SignalErrorView.swift new file mode 100644 index 000000000..1a8f7e264 --- /dev/null +++ b/MeowBili/Errors/SignalErrorView.swift @@ -0,0 +1,104 @@ +// +// +// SignalErrorView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import Alamofire + +struct SignalErrorView: View { + @State var userDesc = "" + @State var isSending = false + @State var errorText = "" + var body: some View { + ScrollView { + VStack { + HStack { + Text("Error.oops") + .font(.system(size: 22, weight: .bold)) + Spacer() + } + HStack { + Text("Error.ran-into-a-problem") + .font(.system(size: 17)) + Spacer() + } + HStack { + Text("Error.details") + .font(.system(size: 15)) + Spacer() + } + ScrollView { + VStack { + Text(errorText) + .font(.system(size: 10)) + .padding(3) + Spacer() + } + .frame(height: 10000) + } + .frame(maxHeight: 100) + .border(Color.accentColor, width: 2) + .cornerRadius(5) + Text("Error.send-to-Darock-advice") + .bold() + .multilineTextAlignment(.leading) + TextField("Error.before-ranning-into-problem", text: $userDesc) + Button(action: { + isSending = true + let headers: HTTPHeaders = [ + "accept": "application/json", + "Content-Type": "multipart/form-data" + ] + AF.upload(multipartFormData: { multipartFormData in + multipartFormData.append(("User Description: " + userDesc + "\n\n" + errorText).data(using: .utf8)!, withName: "file", fileName: UserDefaults.standard.string(forKey: "NewSignalError")!) + }, to: "https://api.darock.top/bili/crashfeedback/\(UserDefaults.standard.string(forKey: "NewSignalError")!.base64Encoded())", method: .post, headers: headers).responseString { response in + let rspStr = String(data: response.data!, encoding: .utf8)! + debugPrint(response) + isSending = false + UserDefaults.standard.set("", forKey: "NewSignalError") + } + }, label: { + if !isSending { + Text("Error.send") + .bold() + } else { + ProgressView() + } + }) + .disabled(isSending) + Button(action: { + UserDefaults.standard.set("", forKey: "NewSignalError") + }, label: { + Text("Error.do-not-send") + }) + } + } + .onAppear { + let fileName = UserDefaults.standard.string(forKey: "NewSignalError")! + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + errorText = try! String(contentsOf: URL(string: (urlForDocument[0] as URL).absoluteString + fileName.replacingOccurrences(of: " ", with: "_").replacingOccurrences(of: "/", with: "-").replacingOccurrences(of: ":", with: "__"))!) + } + } +} + +struct SignalErrorView_Previews: PreviewProvider { + static var previews: some View { + SignalErrorView() + } +} diff --git a/MeowBili/Extension/AppFileManager.swift b/MeowBili/Extension/AppFileManager.swift new file mode 100644 index 000000000..cb7318147 --- /dev/null +++ b/MeowBili/Extension/AppFileManager.swift @@ -0,0 +1,231 @@ +// +// +// AppFileManager.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Foundation + +class AppFileManager { + var path: String + + init(path: String) { + self.path = path + } + + /// 获取单个文件路径 + /// - Parameters: + /// - name: 文件名 + /// - folder: 文件夹名,默认根目录 + /// - Returns: 文件URL + public func GetFilePath(name: String, folder: String = "root") -> URL { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + if folder == "root" { + let url = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(name)"))! + return url + } else { + let url = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(folder)/\(name)"))! + return url + } + } + + /// 获取文件夹下所有图片路径 + /// - Parameter folder: 文件夹名 + /// - Returns: 图片URL + public func GetImagePath(folder: String) -> [URL]? { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let folderUrl: URL + if folder == "root" { + folderUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "images"))! + } else { + folderUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(folder)"))! + } + let contentsOfPath = try? manager.contentsOfDirectory(atPath: folderUrl.path) + if contentsOfPath != nil { + var urls = [URL]() + for i in contentsOfPath! { + if !directoryIsExists(folderUrl.absoluteString + "/\(i)") { + urls.append(URL(string: folderUrl.absoluteString + "/\(i)")!) + } + } + return urls + } else { + return nil + } + } + /// 获取文件夹下所有图片名 + /// - Parameters: + /// - folder: 文件夹名 + /// - withExt: 是否包含扩展名 + /// - Returns: 图片名 + public func GetImageName(folder: String, withExt: Bool = false) -> [String]? { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let folderUrl: URL + if folder == "root" { + folderUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/"))! + } else { + folderUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(folder)/"))! + } + let contentsOfPath = try? manager.contentsOfDirectory(atPath: folderUrl.path) + if contentsOfPath != nil { + var names = [String]() + for i in contentsOfPath! { + if i.contains(".") { + if i.split(separator: ".")[1] != "plist" { + names.append(withExt ? i : String(i.split(separator: ".")[0])) + } + } + } + return names + } else { + return nil + } + } + /// 获取根目录下文件和文件夹 + /// - Returns: [["isDirectory": 是否为文件夹, "name": 文件(夹)名]] + public func GetRoot() -> [[String: String]]? { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let folderUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + path))! + let contentsOfPath = try? manager.contentsOfDirectory(atPath: folderUrl.path) + if contentsOfPath != nil { + var files = [[String: String]]() + for i in contentsOfPath! { + files.append(["isDirectory": String(directoryIsExists(folderUrl.path + "/\(i)")), "name": i]) + } + return files + } else { + return nil + } + } + /// 创建文件夹 + /// - Parameter name: 文件夹名 + public func CreateFolder(_ name: String) { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let folderStr = urlForDocument[0].path + "/\(path)/\(name)" + try! manager.createDirectory(atPath: folderStr, withIntermediateDirectories: true) + } + /// 删除文件(夹) + /// - Parameter name: 文件(夹)名 + public func DeleteFile(_ name: String) { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let fileStr = urlForDocument[0].path + "/\(path)/\(name)" + try! manager.removeItem(atPath: fileStr) + } + /// 存储文件夹下图片排序 + /// - Parameters: + /// - folder: 文件夹名 + /// - index: 顺序 Example: ["pic1.png", "pic2.png", "pic3.png"]... + @available(*, unavailable) + public func SaveImagesIndex(_ folder: String, index: [String]) { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let fileStr: String + if folder == "root" { + fileStr = urlForDocument[0].path + "/\(path)/metadata.plist" + } else { + fileStr = urlForDocument[0].path + "/\(path)/\(folder)/metadata.plist" + } + let nArray = NSArray(array: index) + nArray.write(toFile: fileStr, atomically: true) + } + /// 获取文件夹下图片排序 + /// - Parameter folder: 文件夹名 + /// - Returns: 顺序 Example: ["pic1.png", "pic2.png", "pic3.png"]... + @available(*, unavailable) + public func GetImagesIndex(_ folder: String) -> [String]? { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let fileUrl: URL + if folder == "root" { + fileUrl = URL(string: urlForDocument[0].absoluteString + "\(path)/metadata.plist")! + } else { + fileUrl = URL(string: urlForDocument[0].absoluteString + "\(path)/\(folder)/metadata.plist")! + } + if let nArray = NSArray(contentsOf: fileUrl) { + return nArray as! [String]? + } else { + return nil + } + } + + /// 获取路径 + /// - Parameter folder: 文件夹名 + /// - Returns: absoluteString, URL, path + public func GetPath(_ folder: String?) -> (string: String, url: URL, path: String) { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let fileUrl: URL + if folder == "root" || folder == nil { + fileUrl = URL(string: urlForDocument[0].absoluteString + "\(path)/")! + } else { + fileUrl = URL(string: urlForDocument[0].absoluteString + "\(path)/\(folder!)/")! + } + return (fileUrl.absoluteString, fileUrl, fileUrl.path) + } + /// 查询文件夹是否为空 + /// - Parameter folder: 文件夹名 + /// - Returns: 是否为空 + public func isFolderEmpty(_ folder: String) -> Bool { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let folderUrl: URL + if folder == "root" { + folderUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "images"))! + } else { + folderUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(folder)"))! + } + let contentsOfPath = try? manager.contentsOfDirectory(atPath: folderUrl.path) + if contentsOfPath != nil { + return false + } else { + return true + } + } + + /// 移动文件 + /// - Parameters: + /// - file: 源文件 + /// - withSrcFolder: 源文件文件夹 + /// - to: 目标文件夹 + public func MoveFile(_ file: String, withSrcFolder: String = "root", to: String) { + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + let srcFileUrl: URL + if withSrcFolder == "root" { + srcFileUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(file)"))! + } else { + srcFileUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(withSrcFolder)/\(file)"))! + } + let toFileUrl: URL + if to == "root" { + toFileUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(file)"))! + } else { + toFileUrl = URL(string: ((urlForDocument[0] as URL).absoluteString + "\(path)/\(to)/\(file)"))! + } + try! manager.moveItem(at: srcFileUrl, to: toFileUrl) + } + + private func directoryIsExists(_ path: String) -> Bool { + var directoryExists = ObjCBool.init(false) + let fileExists = FileManager.default.fileExists(atPath: path, isDirectory: &directoryExists) + return fileExists && directoryExists.boolValue + } +} diff --git a/MeowBili/Extension/CodeExt.swift b/MeowBili/Extension/CodeExt.swift new file mode 100644 index 000000000..2d609c3ab --- /dev/null +++ b/MeowBili/Extension/CodeExt.swift @@ -0,0 +1,677 @@ +// +// +// CodeExt.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import OSLog +import SwiftUI +import Dynamic +import CryptoKit +import DarockKit +import Alamofire +import SwiftyJSON +import Foundation +import AVFoundation +import CommonCrypto + +extension String { + func shorter() -> String { + let intData = Int(self) + if intData != nil { + if intData! >= 10000 { + let thined = Double(intData!) / 10000.0 + let toThinStr = String(format: "%.1f", thined) + if toThinStr.hasSuffix(".0") { + return String(toThinStr.split(separator: ".")[0]) + " 万" + } else { + return toThinStr + " 万" + } + } else { + return self + } + } else { + Logger().error("转换数据时错误:无法将 String 转为 Int") + return self + } + } + + /// MD5加密类型 + enum MD5EncryptType { + /// 32位小写 + case lowercase32 + /// 32位大写 + case uppercase32 + /// 16位小写 + case lowercase16 + /// 16位大写 + case uppercase16 + } + /// MD5加密 默认是32位小写加密 + /// - Parameter type: 加密类型 + /// - Returns: 加密字符串 + func DDMD5Encrypt(_ md5Type: MD5EncryptType = .lowercase32) -> String { + guard self.count > 0 else { + print("⚠️⚠️⚠️md5加密无效的字符串⚠️⚠️⚠️") + return "" + } + /// 1.把待加密的字符串转成char类型数据 因为MD5加密是C语言加密 + let cCharArray = self.cString(using: .utf8) + /// 2.创建一个字符串数组接受MD5的值 + var uint8Array = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) + /// 3.计算MD5的值 + /* + 第一个参数:要加密的字符串 + 第二个参数: 获取要加密字符串的长度 + 第三个参数: 接收结果的数组 + */ + CC_MD5(cCharArray, CC_LONG(cCharArray!.count - 1), &uint8Array) + + switch md5Type { + /// 32位小写 + case .lowercase32: + return uint8Array.reduce("") { $0 + String(format: "%02x", $1)} + /// 32位大写 + case .uppercase32: + return uint8Array.reduce("") { $0 + String(format: "%02X", $1)} + /// 16位小写 + case .lowercase16: + //let tempStr = uint8Array.reduce("") { $0 + String(format: "%02x", $1)} + return "" + // tempStr.getString(startIndex: 8, endIndex: 24) + /// 16位大写 + case .uppercase16: + //let tempStr = uint8Array.reduce("") { $0 + String(format: "%02X", $1)} + return "" + // tempStr.getString(startIndex: 8, endIndex: 24) + } + } +} + +extension Date { + /// 获取当前 秒级 时间戳 - 10位 + var timeStamp: Int { + let timeInterval: TimeInterval = self.timeIntervalSince1970 + let timeStamp = Int(timeInterval) + return timeStamp + } + +/// 获取当前 毫秒级 时间戳 - 13位 + var milliStamp: String { + let timeInterval: TimeInterval = self.timeIntervalSince1970 + let millisecond = CLongLong(round(timeInterval*1000)) + return "\(millisecond)" + } +} + + +@discardableResult +public func getMemory() -> Float { + var taskInfo = task_vm_info_data_t() + var count = mach_msg_type_number_t(MemoryLayout.size) / 4 + let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) { + $0.withMemoryRebound(to: integer_t.self, capacity: 1) { + task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count) + } + } + let usedMb = Float(taskInfo.phys_footprint) / 1048576.0 + let totalMb = Float(ProcessInfo.processInfo.physicalMemory) / 1048576.0 + result != KERN_SUCCESS ? debugPrint("Memory used: ? of \(totalMb)") : debugPrint("Memory used: \(usedMb) of \(totalMb)") + return usedMb +} + +/// 切换时间显示 +/// - Parameter b: 是否显示 +public func hideDigitalTime(_ b: Bool) { + let app = Dynamic.PUICApplication.sharedPUICApplication() + app._setStatusBarTimeHidden(b, animated: true, completion: nil) +} + +// MARK: Networking +public func autoRetryRequestApi(_ url: String, headers: HTTPHeaders?, maxReqCount: Int = 10, callback: @escaping (JSON, Bool) -> Void) { + var retryCount = 0 + DispatchQueue.global().async { + sigReq(url, headers: headers, maxReqCount: maxReqCount, callback: callback) + } + + func sigReq(_ url: String, headers: HTTPHeaders?, maxReqCount: Int = 10, callback: @escaping (JSON, Bool) -> Void) { + DarockKit.Network.shared.requestJSON(url, headers: headers) { respJson, isSuccess in + if isSuccess { + if CheckBApiError(from: respJson, noTip: true) { // Requesting succeed + callback(respJson, true) + } else if retryCount < maxReqCount { // Failed but can retry + retryCount++ + sigReq(url, headers: headers, maxReqCount: maxReqCount, callback: callback) + } else { // Failed and not able to retry, callback json for next level code processing error. + callback(respJson, true) + } + } else { + callback(respJson, isSuccess) + } + } + } +} + +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], imgKey: String, subKey: String) -> (wts: String, w_rid: String) { + var params = params + let mixinKey = getMixinKey(orig: imgKey + subKey) + let currTime = round(Date().timeIntervalSince1970) + params["wts"] = Int(currTime) + let std = params.sorted { $0.key < $1.key } + var query = "" + for q in std { + query += "\(q.key)=\(q.value)&" + } + query.removeLast() + query = query.urlEncoded() + let wbiSign = calculateMD5(string: query + mixinKey) + return (String(Int(currTime)), wbiSign) + } + + func getWbiKeys(completion: @escaping (Result<(imgKey: String, subKey: String), Error>) -> Void) { + let headers: HTTPHeaders = [ + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/web-interface/nav", headers: headers).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 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) + var query = decParam + "&w_rid=\(signedParams.w_rid)&wts=\(signedParams.wts)" + query = query.urlEncoded() + completion(query) + case .failure(let error): + print("Error getting keys: \(error)") + completion(nil) + } + } +} + +// 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 +} + +// MARK: Get buvid_fp cookie +enum BuvidFpError: Error { + case readError +} + +struct BuvidFp { + static func gen(key: String, seed: UInt32) throws -> String { + let m = try murmur3_x64_128(key: key, seed: seed) + return String(format: "%016llx%016llx", m.low, m.high) + } + + private static func murmur3_x64_128(key: String, seed: UInt32) throws -> UInt128 { + let C1: UInt64 = 0x87c3_7b91_1142_53d5 + let C2: UInt64 = 0x4cf5_ad43_2745_937f + let C3: UInt64 = 0x52dc_e729 + let C4: UInt64 = 0x3849_5ab5 + let R1: UInt32 = 27 + let R2: UInt32 = 31 + let R3: UInt32 = 33 + let M: UInt64 = 5 + + var h1: UInt64 = UInt64(seed) + var h2: UInt64 = UInt64(seed) + var processed: Int = 0 + var index = key.startIndex + + var buf = [UInt8](repeating: 0, count: 16) + + while index < key.endIndex { + let remaining = key.distance(from: index, to: key.endIndex) + let read = min(remaining, 16) + + _ = key.withCString { cString in + // Using withCString to convert the Swift String to a C-style string + memcpy(&buf, cString + index.utf16Offset(in: key), read) + } + + processed += read + if read == 16 { + let k1 = UInt64(bitPattern: Int64(littleEndianBytes: buf[0..<8])) + let k2 = UInt64(bitPattern: Int64(littleEndianBytes: buf[8..<16])) + + h1 ^= k1.multipliedFullWidth(by: C1).high &<< R2 + h1 = h1 &<< R1 &+ h2 &* M &+ C3 + h2 ^= k2.multipliedFullWidth(by: C2).high &<< R3 + h2 = h2 &<< R2 &+ h1 &* M &+ C4 + } else if read == 0 { + h1 ^= UInt64(processed) + h2 ^= UInt64(processed) + h1 = h1 &+ h2 + h2 = h2 &+ h1 + h1 = fmix64(k: h1) + h2 = fmix64(k: h2) + h1 = h1 &+ h2 + h2 = h2 &+ h1 + + let x: UInt128 = UInt128(high: h2, low: h1) + return x + } else { + var k1: UInt64 = 0 + var k2: UInt64 = 0 + + if read >= 15 { k2 ^= UInt64(buf[14]) &<< 48 } + if read >= 14 { k2 ^= UInt64(buf[13]) &<< 40 } + if read >= 13 { k2 ^= UInt64(buf[12]) &<< 32 } + if read >= 12 { k2 ^= UInt64(buf[11]) &<< 24 } + if read >= 11 { k2 ^= UInt64(buf[10]) &<< 16 } + if read >= 10 { k2 ^= UInt64(buf[9]) &<< 8 } + if read >= 9 { + k2 ^= UInt64(buf[8]) + k2 = k2.multipliedFullWidth(by: C2).high &<< 33 + k2 = k2.multipliedFullWidth(by: C1).high &<< 32 + h2 ^= k2 + } + if read >= 8 { k1 ^= UInt64(buf[7]) &<< 56 } + if read >= 7 { k1 ^= UInt64(buf[6]) &<< 48 } + if read >= 6 { k1 ^= UInt64(buf[5]) &<< 40 } + if read >= 5 { k1 ^= UInt64(buf[4]) &<< 32 } + if read >= 4 { k1 ^= UInt64(buf[3]) &<< 24 } + if read >= 3 { k1 ^= UInt64(buf[2]) &<< 16 } + if read >= 2 { k1 ^= UInt64(buf[1]) &<< 8 } + if read >= 1 { k1 ^= UInt64(buf[0]) } + + k1 = k1.multipliedFullWidth(by: C1).high &<< 31 + k1 = k1.multipliedFullWidth(by: C2).high &<< 32 + h1 ^= k1 + } + + index = key.index(index, offsetBy: read) + } + + throw BuvidFpError.readError + } + + private static func fmix64(k: UInt64) -> UInt64 { + let C1: UInt64 = 0xff51_afd7_ed55_8ccd + let C2: UInt64 = 0xc4ce_b9fe_1a85_ec53 + let R: UInt32 = 33 + var tmp = k + tmp ^= tmp &>> R + tmp = tmp.multipliedFullWidth(by: C1).high + tmp ^= tmp &>> R + tmp = tmp.multipliedFullWidth(by: C2).high + tmp ^= tmp &>> R + return tmp + } +} + +extension Int64 { + init(littleEndianBytes bytes: ArraySlice) { + self = Int64(bitPattern: UInt64(littleEndianBytes: bytes)) + } +} + +extension UInt64 { + init(littleEndianBytes bytes: ArraySlice) { + var value: UInt64 = 0 + withUnsafeMutableBytes(of: &value) { buffer in + for (index, byte) in bytes.enumerated() { + buffer[index] = byte + } + } + self = UInt64(littleEndian: value) + } +} + +struct UInt128 { + let low: UInt64 + let high: UInt64 + + init(high: UInt64, low: UInt64) { + self.high = high + self.low = low + } + + static func &<< (lhs: UInt128, rhs: UInt64) -> UInt128 { + if rhs == 0 { + return lhs + } else if rhs < 64 { + return UInt128(high: lhs.high << rhs | lhs.low >> (64 - rhs), low: lhs.low << rhs) + } else if rhs < 128 { + return UInt128(high: lhs.low << (rhs - 64), low: 0) + } else { + return UInt128(high: 0, low: 0) + } + } +} + +// MARK: Get _uuid cookie +struct UuidInfoc { + static func gen() -> String { + let digitMap: [String] = [ + "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "10" + ] + let t = Int64(Date().timeIntervalSince1970 * 1000) % 100_000 + + return randomChoice(range: [8, 4, 4, 4, 12], separator: "-", choices: digitMap) + String(format: "%05d", t) + "infoc" + } +} + +func randomChoice(range: [Int], separator: String, choices: [String]) -> String { + var result = "" + + for r in range { + for _ in 0.. Void) { + let _uuid = UuidInfoc.gen() + let postParams: [String: Any] = [ + "3064":1, // ptype, mobile => 2, others => 1 + "5062":Date.now.milliStamp, // timestamp + "03bf":url, // url accessed + "39c8":"333.1007.fp.risk", // spm_id, + "34f1":"", // target_url, default empty now + "d402":"", // screenx, default empty + "654a":"", // screeny, default empty + "6e7c":"3440x1440", // browser_resolution, window.innerWidth || document.body && document.body.clientWidth + "x" + window.innerHeight || document.body && document.body.clientHeight + "3c43":[ // 3c43 => msg + "2673":1, // hasLiedResolution, window.screen.width < window.screen.availWidth || window.screen.height < window.screen.availHeight + "5766":24, // colorDepth, window.screen.colorDepth + "6527":0, // addBehavior, !!window.HTMLElement.prototype.addBehavior, html5 api + "7003":1, // indexedDb, !!window.indexedDB, html5 api + "807e":1, // cookieEnabled, navigator.cookieEnabled + "b8ce":"Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebK…KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", // ua + "641c":0, // webdriver, navigator.webdriver, like Selenium + "07a4":"zh-CN", // language + "1c57":4, // deviceMemory in GB, navigator.deviceMemory + "0bd0":4, // hardwareConcurrency, navigator.hardwareConcurrency + "748e":[ + 3440, // window.screen.width + 1440 // window.screen.height + ], // screenResolution + "d61f":[ + 3440, // window.screen.availWidth + 1440 // window.screen.availHeight + ], // availableScreenResolution + "fc9d":-480, // timezoneOffset, (new Date).getTimezoneOffset() + "6aa9":"Asia/Shanghai", // timezone, (new window.Intl.DateTimeFormat).resolvedOptions().timeZone + "75b8":1, // sessionStorage, window.sessionStorage, html5 api + "3b21":1, // localStorage, window.localStorage, html5 api + "8a1c":0, // openDatabase, window.openDatabase, html5 api + "d52f":"not available", // cpuClass, navigator.cpuClass + "adca":"Win32", // platform, navigator.platform + "80c9":[ + [ + "PDF Viewer", + "Portable Document Format", + [ + [ + "application/pdf", + "pdf" + ], + [ + "text/pdf", + "pdf" + ] + ] + ], + [ + "Chrome PDF Viewer", + "Portable Document Format", + [ + [ + "application/pdf", + "pdf" + ], + [ + "text/pdf", + "pdf" + ] + ] + ], + [ + "Chromium PDF Viewer", + "Portable Document Format", + [ + [ + "application/pdf", + "pdf" + ], + [ + "text/pdf", + "pdf" + ] + ] + ], + [ + "Microsoft Edge PDF Viewer", + "Portable Document Format", + [ + [ + "application/pdf", + "pdf" + ], + [ + "text/pdf", + "pdf" + ] + ] + ], + [ + "WebKit built-in PDF", + "Portable Document Format", + [ + [ + "application/pdf", + "pdf" + ], + [ + "text/pdf", + "pdf" + ] + ] + ] + ], // plugins + "13ab":"mTUAAAAASUVORK5CYII=", // canvas fingerprint + "bfe9":"aTot0S1jJ7Ws0JC6QkvAL/A4H1PbV+/QA3AAAAAElFTkSuQmCC", // webgl_str + "a3c1":[], // webgl_params, cab be set to [] if webgl is not supported + "6bc5":"Broadcom~V3D 4.2", // webglVendorAndRenderer + "ed31":0, // hasLiedLanguages + "72bd":0, // hasLiedOs + "097b":0, // hasLiedBrowser + "52cd":[ + 0, // void 0 !== navigator.maxTouchPoints ? t = navigator.maxTouchPoints : void 0 !== navigator.msMaxTouchPoints && (t = navigator.msMaxTouchPoints); + 0, // document.createEvent("TouchEvent"), if succeed 1 else 0 + 0 // "ontouchstart" in window ? 1 : 0 + ], // touch support + "a658":[ + "Arial", + "Courier", + "Courier New", + "Helvetica", + "Times", + "Times New Roman" + ], // font details. see https://github.com/fingerprintjs/fingerprintjs for implementation details + "d02f":"124.04347527516074" // audio fingerprint. see https://github.com/fingerprintjs/fingerprintjs for implementation details + ], + "54ef":"{\"b_ut\":\"7\",\"home_version\":\"V8\",\"i-wanna-go-back\":\"-1\",\"in_new_ab\":true,\"ab_version\":{\"for_ai_home_version\":\"V8\",\"tianma_banner_inline\":\"CONTROL\",\"enable_web_push\":\"DISABLE\"},\"ab_split_num\":{\"for_ai_home_version\":54,\"tianma_banner_inline\":54,\"enable_web_push\":10}}", // abtest info, embedded in html + "8b94":"", // refer_url, document.referrer ? encodeURIComponent(document.referrer).substr(0, 1e3) : "" + "df35":_uuid, // _uuid, set from cookie, generated by client side(algorithm remains unknown) + "07a4":"zh-CN", // language + "5f45":0, // laboratory, set from cookie, null if empty, source remains unknown + "db46":0 // is_selfdef, default 0 + ] + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/frontend/finger/spi") { respJson, isSuccess in + if isSuccess { + let buvid3 = respJson["data"]["b_3"].string ?? "" + let buvid4 = respJson["data"]["b_4"].string ?? "" + let postHeaders: HTTPHeaders = [ + "cookie": "innersign=0; buvid3=\(buvid3); b_nut=\(Date.now.timeStamp); i-wanna-go-back=-1; b_ut=7; b_lsid=9910433CB_18CF260AB89; _uuid=\(_uuid); enable_web_push=DISABLE; header_theme_version=undefined; home_feed_column=4; browser_resolution=3440-1440; buvid4=\(buvid4); buvid_fp=e651c1a382430ea93631e09474e0b395" + ] + AF.request("https://api.bilibili.com/x/internal/gaia-gateway/ExClimbWuzhi", method: .post, parameters: postParams, encoding: JSONEncoding.default, headers: postHeaders).response { response in + callback(buvid3, buvid4, _uuid, response.debugDescription) + } + } + } +} + +postfix operator ++ +postfix operator -- +prefix operator ++ +prefix operator -- +extension Int { + @discardableResult + static postfix func ++ (num: inout Int) -> Int { + num += 1 + return num - 1 + } + + @discardableResult + static postfix func -- (num: inout Int) -> Int { + num -= 1 + return num + 1 + } + + @discardableResult + static prefix func ++ (num: inout Int) -> Int { + num += 1 + return num + } + + @discardableResult + static prefix func -- (num: inout Int) -> Int { + num -= 1 + return num + } +} + +extension Bool { + init(_ input: Int) { + if input == 0 { + self = false + } else { + self = true + } + } +} + +infix operator ~ +extension Float { + static func ~ (lhs: Float, rhs: Int) -> String { + return String(format: "%.\(rhs)f", lhs) + } +} +extension Double { + static func ~ (lhs: Double, rhs: Int) -> String { + return String(format: "%.\(rhs)f", lhs) + } +} diff --git a/MeowBili/Extension/UIExt.swift b/MeowBili/Extension/UIExt.swift new file mode 100644 index 000000000..60a2c8f12 --- /dev/null +++ b/MeowBili/Extension/UIExt.swift @@ -0,0 +1,204 @@ +// +// +// UIExt.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import UIKit +import SwiftUI +import Foundation +import SDWebImageSwiftUI +import AuthenticationServices + +@ViewBuilder func VideoCard(_ videoDetails: [String: String]) -> some View { + NavigationLink(destination: {VideoDetailView(videoDetails: videoDetails)}, label: { + VStack { + HStack { + WebImage(url: URL(string: videoDetails["Pic"]! + "@200w")!, options: [.progressiveLoad, .scaleDownLargeImages]) + .placeholder { + RoundedRectangle(cornerRadius: 7) + .frame(width: 100, height: 60) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + .resizable() + .scaledToFit() + .frame(width: 100) + .cornerRadius(7) + Text(videoDetails["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(4) + .multilineTextAlignment(.leading) + Spacer() + } + HStack { + Image(systemName: "play.circle") + Text(videoDetails["View"]!.shorter()) + .offset(x: -3) + Image(systemName: "person") + Text(videoDetails["UP"]!) + .offset(x: -3) + Spacer() + } + .lineLimit(1) + .font(.system(size: 11)) + .foregroundColor(.gray) + } + }) + .buttonBorderShape(.roundedRectangle(radius: 14)) +} + +@ViewBuilder func BangumiCard(_ bangumiData: BangumiData) -> some View { + NavigationLink(destination: {BangumiDetailView(bangumiData: bangumiData)}, label: { + VStack { + HStack { + WebImage(url: URL(string: bangumiData.cover + "@100w")!, options: [.progressiveLoad, .scaleDownLargeImages]) + .placeholder { + RoundedRectangle(cornerRadius: 7) + .frame(width: 50, height: 30) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + .resizable() + .scaledToFit() + .frame(width: 50) + .cornerRadius(7) + Text(bangumiData.title) + .font(.system(size: 14, weight: .bold)) + .lineLimit(2) + Spacer() + } + HStack { + if let score = bangumiData.score { + Image(systemName: "star.fill") + Text(score.score ~ 1) + .offset(x: -3) + } + if let style = bangumiData.style { + Image(systemName: "sparkles") + Text(style) + .lineLimit(1) + .offset(x: -3) + } + Spacer() + } + .lineLimit(1) + .font(.system(size: 11)) + .foregroundColor(.gray) + } + }) + .buttonBorderShape(.roundedRectangle(radius: 14)) +} + +@ViewBuilder func LiveCard(_ liveDetails: [String: String]) -> some View { + NavigationLink(destination: {LiveDetailView(liveDetails: liveDetails)}, label: { + VStack { + HStack { + WebImage(url: URL(string: liveDetails["Cover"]! + "@100w")!, options: [.progressiveLoad, .scaleDownLargeImages]) + .placeholder { + RoundedRectangle(cornerRadius: 7) + .frame(width: 50, height: 30) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + .resizable() + .scaledToFit() + .frame(width: 50) + .cornerRadius(7) + Text(liveDetails["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(2) + .multilineTextAlignment(.leading) + Spacer() + } + HStack { + Image(systemName: "tag") + Text(liveDetails["Type"]!) + .offset(x: -3) + Spacer() + } + .lineLimit(1) + .font(.system(size: 11)) + .foregroundColor(.gray) + } + }) + .buttonBorderShape(.roundedRectangle(radius: 14)) +} + +//struct zoomable: ViewModifier { +// @AppStorage("MaxmiumScale") var maxmiumScale = 6.0 +// @State var scale: CGFloat = 1.0 +// @State var offset = CGSize.zero +// @State var lastOffset = CGSize.zero +// func body(content: Content) -> some View { +// content +// .scaleEffect(self.scale) +// .focusable() +// .digitalCrownRotation($scale, from: 0.5, through: maxmiumScale, by: 0.02, sensitivity: .low, isHapticFeedbackEnabled: true) +// .offset(x: offset.width, y: offset.height) +// .gesture( +// DragGesture() +// .onChanged { gesture in +// offset = CGSize(width: gesture.translation.width + lastOffset.width, height: gesture.translation.height + lastOffset.height) +// } +// .onEnded { _ in +// lastOffset = offset +// } +// ) +// .onDisappear { +// offset = CGSize.zero +// lastOffset = CGSize.zero +// } +// .onChange(of: scale) { value in +// if value < 2.0 { +// withAnimation(.easeInOut(duration: 0.3)) { +// offset = CGSize.zero +// lastOffset = CGSize.zero +// } +// } +// } +// } +//} + +extension Indicator where T == ProgressView { + static var activity: Indicator { + Indicator { isAnimating, progress in + ProgressView() + } + } + + static var progress: Indicator { + Indicator { isAnimating, progress in + ProgressView(value: progress.wrappedValue) + } + } +} + +struct UIImageTransfer: Transferable { + let image: UIImage + enum TransferError: Error { + case importFailed + } + + static var transferRepresentation: some TransferRepresentation { + DataRepresentation(importedContentType: .image) { data in + guard let uiImage = UIImage(data: data) else { + throw TransferError.importFailed + } + return UIImageTransfer(image: uiImage) + } + } +} + diff --git a/MeowBili/GlobalView/CommentsView.swift b/MeowBili/GlobalView/CommentsView.swift new file mode 100644 index 000000000..127cb5960 --- /dev/null +++ b/MeowBili/GlobalView/CommentsView.swift @@ -0,0 +1,478 @@ +// +// +// CommentsView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import OSLog +import SwiftUI +import DarockKit +import Alamofire +import SwiftyJSON +import SDWebImageSwiftUI + +struct CommentsView: View { + var oid: String + var type: Int = 1 + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var isSendCommentPresented = false + var body: some View { + VStack { + if #available(watchOS 10, *) { + CommentMainView(oid: oid, type: type) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button(action: { + isSendCommentPresented = true + }, label: { + Image(systemName: "square.and.pencil") + }) + .sheet(isPresented: $isSendCommentPresented, content: {CommentSendView(oid: oid, type: type)}) + } + } + } else { + CommentMainView(oid: oid, type: type) + } + } + } + + struct CommentMainView: View { + var oid: String + var type: Int + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var id = "" + @State var comments = [[String: String]]() + @State var sepTexts = [[String]]() + @State var emojiUrls = [[String]]() + @State var isEmoted = [Bool]() + @State var commentReplies = [[[String: String]]]() + @State var nowPage = 1 + @State var isSenderDetailsPresented = [Bool]() + @State var commentOffsets = [CGFloat]() + @State var isLoaded = false + @State var isSendCommentPresented = false + var body: some View { + ScrollView { + LazyVStack { + if #unavailable(watchOS 10) { + Button(action: { + isSendCommentPresented = true + }, label: { + HStack { + Image(systemName: "square.and.pencil") + Text("Comment.send") + } + }) + .sheet(isPresented: $isSendCommentPresented, content: {CommentSendView(oid: oid, type: type)}) + } + if comments.count != 0 { + ForEach(0...comments.count - 1, id: \.self) { i in + VStack { + HStack { + WebImage(url: URL(string: comments[i]["SenderPic"]! + "@30w"), options: [.progressiveLoad]) + .cornerRadius(100) + VStack { + NavigationLink("", isActive: $isSenderDetailsPresented[i], destination: {UserDetailView(uid: comments[i]["SenderID"]!)}) + .frame(width: 0, height: 0) + HStack { + Text(comments[i]["Sender"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + } + Text(comments[i]["IP"]!) + .font(.system(size: 10)) + .foregroundColor(.gray) + .lineLimit(1) + } + Spacer() + } + .onTapGesture { + isSenderDetailsPresented[i] = true + } + HStack { +// if isEmoted[i] { +// ForEach(Array(zip(0..= replies.count { + ForEach(0...replies.count - 1, id: \.self) { i in + VStack { + HStack { + WebImage(url: URL(string: replies[i]["SenderPic"]! + "@30w"), options: [.progressiveLoad]) + .cornerRadius(100) + VStack { + NavigationLink("", isActive: $isSenderDetailsPresented[i], destination: {UserDetailView(uid: replies[i]["SenderID"]!)}) + .frame(width: 0, height: 0) + HStack { + Text(replies[i]["Sender"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + } + Text(replies[i]["IP"]!) + .font(.system(size: 10)) + .foregroundColor(.gray) + .lineLimit(1) + } + Spacer() + } + .onTapGesture { + isSenderDetailsPresented[i] = true + } + HStack { + // Text({ () -> String in + // var showText = comments[i]["Text"]! + // if comments[i]["Text"]!.contains("[") && comments[i]["Text"]!.contains("]") { + // var emojiNames = [String]() + // let textSpd = comments[i]["Text"]!.split(separator: "[") + // for text in textSpd { + // emojiNames.append(String(text.split(separator: "]")[0])) + // } + // var emojiLinks = [String]() + // for name in emojiNames { + // emojiLinks.append(biliEmojiDictionary[name] ?? name) + // } + // for i in 0...emojiLinks.count - 1 { + // showText = showText.replacingOccurrences(of: "[\(emojiNames[i])]", with: "\(AsyncImage(url: URL(string: emojiLinks[i])))") + // } + // } + // return showText + // }()) + Text(replies[i]["Text"]!) + .font(.system(size: 16, weight: .bold)) + .lineLimit((replies[i]["isFull"] ?? "false") == "true" ? 1000 : 8) + .onTapGesture { + replies[i].updateValue((replies[i]["isFull"] ?? "false") == "true" ? "false" : "true", forKey: "isFull") + } + Spacer() + } + HStack { + Label(replies[i]["Like"]!, systemImage: replies[i]["UserAction"]! == "1" ? "hand.thumbsup.fill" : "hand.thumbsup") + .font(.system(size: 14)) + .foregroundColor(replies[i]["UserAction"]! == "1" ? Color(hex: 0xF889BA) : .white) + .lineLimit(1) + .opacity(replies[i]["UserAction"]! == "1" ? 1 : 0.6) + .onTapGesture { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/reply/action", method: .post, parameters: BiliCommentLike(type: type, oid: avid, rpid: Int(replies[i]["Rpid"]!)!, action: replies[i]["UserAction"]! == "1" ? 0 : 1, csrf: biliJct), headers: headers).response { response in + debugPrint(response) + replies[i]["UserAction"]! = replies[i]["UserAction"]! == "1" ? "0" : "1" + } + } + Label("", systemImage: replies[i]["UserAction"]! == "2" ? "hand.thumbsdown.fill" : "hand.thumbsdown") + .font(.system(size: 14)) + .foregroundColor(replies[i]["UserAction"]! == "2" ? Color(hex: 0xF889BA) : .white) + .lineLimit(1) + .opacity(replies[i]["UserAction"]! == "2" ? 1 : 0.6) + .onTapGesture { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/reply/hate", method: .post, parameters: BiliCommentLike(type: type, oid: avid, rpid: Int(replies[i]["Rpid"]!)!, action: replies[i]["UserAction"]! == "2" ? 0 : 1, csrf: biliJct), headers: headers).response { response in + debugPrint(response) + replies[i]["UserAction"]! = replies[i]["UserAction"]! == "2" ? "0" : "2" + } + } + Spacer() + } + Divider() + } + .tag(replies[i]["Rpid"]!) + .padding(5) + .offset(y: commentOffsets[i]) + .animation(.easeOut(duration: 0.4), value: commentOffsets[i]) + .onAppear { + commentOffsets[i] = 0 + } + } + } + } + .onAppear { + if goto != nil { + proxy.scrollTo(goto!, anchor: .top) + } + for _ in replies { + commentOffsets.append(20) + isSenderDetailsPresented.append(false) + } + } + } + } + } + } + } + struct CommentSendView: View { + var oid: String + var type: Int + @Environment(\.dismiss) var dismiss + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var sendCommentCache = "" + @State var isSendingComment = false + @State var id = "" + var body: some View { + VStack { + if !isSendingComment { + TextField("Comment.send", text: $sendCommentCache) + .onSubmit { + if sendCommentCache != "" { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/reply/add", method: .post, parameters: BiliSubmitComment(type: type, oid: id, message: sendCommentCache, csrf: biliJct), headers: headers).response { response in + sendCommentCache = "" + debugPrint(response) + isSendingComment = false + dismiss() + } + } + } + Spacer() + } else { + ProgressView() + } + } + .onAppear { + if Int(oid) == nil, type == 1 { + id = String(bv2av(bvid: oid)) + } else { + id = oid + } + debugPrint(id) + } + } + } +} + +struct BiliCommentLike: Codable { + var type: Int + let oid: String + let rpid: Int + let action: Int + let csrf: String +} + +struct BiliSubmitComment: Codable { + var type: Int + let oid: String + var root: Int? = nil + var parent: Int? = nil + let message: String + let csrf: String +} + +struct CommentsView_Previews: PreviewProvider { + static var previews: some View { + CommentsView(oid: "1tV4y1379v") + } +} + diff --git a/MeowBili/InMain/ContentView.swift b/MeowBili/InMain/ContentView.swift new file mode 100644 index 000000000..1948d5db3 --- /dev/null +++ b/MeowBili/InMain/ContentView.swift @@ -0,0 +1,60 @@ +// +// +// ContentView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI + +struct ContentView: View { + public static var nowAppVer = "1.0.0|106" + @AppStorage("IsFirstUsing") var isFirstUsing = true + @AppStorage("LastUsingVer") var lastUsingVer = "" + @State var mainTabSelection = 1 + //@State var isSystemVerTipPresented = false + var body: some View { + NavigationStack { + TabView(selection: $mainTabSelection) { + MainView(mainTabSelection: $mainTabSelection) + .tag(1) + .tabItem { + Label("推荐", systemImage: "sparkles") + } + PersonAccountView() + .tag(2) + .tabItem { + Label("我的", systemImage: "person.fill") + } + UserDynamicMainView() + .tag(3) + .tabItem { + Label("动态", systemImage: "rectangle.stack.fill") + } + } + .accessibility(identifier: "MainTabView") + .onAppear { +// if isFirstUsing { +// isGuidePresented = true +// } + } + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/MeowBili/InMain/MainView.swift b/MeowBili/InMain/MainView.swift new file mode 100644 index 000000000..eeee4d4d8 --- /dev/null +++ b/MeowBili/InMain/MainView.swift @@ -0,0 +1,251 @@ +// +// +// MainView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import SwiftyJSON +import Dynamic +import Alamofire +import SDWebImageSwiftUI +import CachedAsyncImage + +struct MainView: View { + @Binding var mainTabSelection: Int + @Namespace public var imageAnimation + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @AppStorage("IsShowNetworkFixing") var isShowNetworkFixing = true + @State var userFaceUrl = "" + @State var username = "" + @State var userSign = "" + @State var isNetworkFixPresented = false + @State var isLoginPresented = false + @State var userList1: [Any] = [] + @State var userList2: [Any] = [] + @State var userList3: [Any] = [] + @State var userList4: [Any] = [] + @State var isNewUserPresenting = false + var body: some View { + MainViewMain() + .navigationBarTitleDisplayMode(.large) + .sheet(isPresented: $isNetworkFixPresented, content: {NetworkFixView()}) + .sheet(isPresented: $isLoginPresented, content: {LoginView()}) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + if dedeUserID != "" { + Button(action: { + mainTabSelection = 2 + }, label: { + CachedAsyncImage(url: URL(string: userFaceUrl + "@35w")) + .frame(width: 35) + .clipShape(Circle()) + .matchedGeometryEffect(id: "image", in: imageAnimation) + }) + .buttonStyle(.borderless) + } + } + } + .sheet(isPresented: $isNewUserPresenting, content: {LoginView()}) + .onAppear { + if username == "" { + getBuvid(url: "https://api.bilibili.com/x/space/wbi/acc/info".urlEncoded()) { buvid3, buvid4, _uuid, resp in + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata); innersign=0; buvid3=\(buvid3); b_nut=1704873471; i-wanna-go-back=-1; b_ut=7; b_lsid=9910433CB_18CF260AB89; _uuid=\(_uuid); enable_web_push=DISABLE; header_theme_version=undefined; home_feed_column=4; browser_resolution=3440-1440; buvid4=\(buvid4);", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + biliWbiSign(paramEncoded: "mid=\(dedeUserID)".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + autoRetryRequestApi("https://api.bilibili.com/x/space/wbi/acc/info?\(signed)", headers: headers) { respJson, isSuccess in + if isSuccess { + debugPrint(respJson) + if !CheckBApiError(from: respJson) { return } + username = respJson["data"]["name"].string ?? "" + userSign = respJson["data"]["sign"].string ?? "" + userFaceUrl = respJson["data"]["face"].string ?? "E" + } else if isShowNetworkFixing { + isNetworkFixPresented = true + } + } + } + } + } + } + } + } + struct MainViewMain: View { + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @AppStorage("UpdateTipIgnoreVersion") var updateTipIgnoreVersion = "" + @AppStorage("IsShowNetworkFixing") var isShowNetworkFixing = true + @State var videos = [[String: String]]() + @State var notice = "" + @State var isNetworkFixPresented = false + @State var isFirstLoaded = false + @State var newMajorVer = "" + @State var newBuildVer = "" + @State var isShowDisableNewVerTip = false + @State var isLoadingNew = false + @State var isFailedToLoad = false + var body: some View { + Group { + List { + Section { + if debug { + Button(action: { + //tipWithText("Test") +// Dynamic.PUICApplication.sharedPUICApplication._setStatusBarTimeHidden(true, animated: false, completion: nil) + //Dynamic.WatchKit.sharedPUICApplication._setStatusBarTimeHidden(true, animated: false) + }, label: { + Text("Home.debug") + }) + } + if notice != "" { + NavigationLink(destination: {NoticeView()}, label: { + Text(notice) + .bold() + }) + } + if newMajorVer != "" && newBuildVer != "" { + let nowMajorVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String + let nowBuildVer = Bundle.main.infoDictionary?["CFBundleVersion"] as! String + if (nowMajorVer < newMajorVer || nowBuildVer < newBuildVer) && updateTipIgnoreVersion != "\(newMajorVer)\(newBuildVer)" { + VStack { + Text("Home.update.\(newMajorVer).\(newBuildVer)") + if isShowDisableNewVerTip { + Text("Home.update.skip") + .font(.system(size: 12)) + .foregroundColor(.gray) + } + } + .onTapGesture { + if isShowDisableNewVerTip { + updateTipIgnoreVersion = "\(newMajorVer)\(newBuildVer)" + } else { + isShowDisableNewVerTip = true + } + } + } + } + } + Section { + NavigationLink(destination: {SearchMainView()}, label: { + HStack { + Image(systemName: "magnifyingglass") + Text("Home.search") + } + .foregroundColor(.gray) + }) + } + if videos.count != 0 { + Section { + ForEach(0...videos.count - 1, id: \.self) { i in + VideoCard(videos[i]) + .onAppear { + if i == videos.count - 1 { + LoadNewVideos() + } + } + } + } + Section { + if isLoadingNew { + ProgressView() + } + } + } else if isFailedToLoad { + Button { + LoadNewVideos() + } label: { + Label("Home.more.error", systemImage: "wifi.exclamationmark") + } + Text("Home.no-internet") + } else { + ProgressView() + } + } + } + .navigationTitle("Home") + .refreshable { + videos.removeAll() + LoadNewVideos(clearWhenFinish: true) + } + .onAppear { + if !isFirstLoaded { + LoadNewVideos() + isFirstLoaded = true + } + DarockKit.Network.shared.requestString("https://api.darock.top/bili/notice") { respStr, isSuccess in + if isSuccess { + notice = respStr.apiFixed() + } + } + DarockKit.Network.shared.requestString("https://api.darock.top/bili/newver") { respStr, isSuccess in + if isSuccess && respStr.apiFixed().contains("|") { + newMajorVer = String(respStr.apiFixed().split(separator: "|")[0]) + newBuildVer = String(respStr.apiFixed().split(separator: "|")[1]) + } + } + } + .sheet(isPresented: $isNetworkFixPresented, content: {NetworkFixView()}) + } + + func LoadNewVideos(clearWhenFinish: Bool = false) { + isLoadingNew = true + isFailedToLoad = false + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + biliWbiSign(paramEncoded: "ps=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) + if !CheckBApiError(from: respJson) { return } + let datas = respJson["data"]["item"] + if clearWhenFinish { + videos = [[String: String]]() + } + for videoInfo in datas { + videos.append(["Pic": videoInfo.1["pic"].string ?? "E", "Title": videoInfo.1["title"].string ?? "[加载失败]", "BV": videoInfo.1["bvid"].string ?? "E", "UP": videoInfo.1["owner"]["name"].string ?? "[加载失败]", "View": String(videoInfo.1["stat"]["view"].int ?? -1), "Danmaku": String(videoInfo.1["stat"]["danmaku"].int ?? -1)]) + } + isLoadingNew = false + } else { + isFailedToLoad = true + if isShowNetworkFixing { + isNetworkFixPresented = true + } + } + } + } else { + isFailedToLoad = true + if isShowNetworkFixing { + isNetworkFixPresented = true + } + } + } + } + } +} + diff --git a/MeowBili/InMain/SearchView.swift b/MeowBili/InMain/SearchView.swift new file mode 100644 index 000000000..6dfc3f559 --- /dev/null +++ b/MeowBili/InMain/SearchView.swift @@ -0,0 +1,341 @@ +// +// +// SearchView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import Alamofire +import SwiftyJSON +import SDWebImageSwiftUI +import AuthenticationServices + +struct SearchMainView: View { + @State var searchText = "" + @State var isSearchPresented = false + @State var searchHistory = [String]() + var body: some View { + NavigationStack { + List { + Section { + VStack { + NavigationLink("", destination: SearchView(keyword: searchText), isActive: $isSearchPresented) + .frame(width: 0, height: 0) + .hidden() + TextField("Search.\(Image(systemName: "magnifyingglass"))", text: $searchText) + .onSubmit { + isSearchPresented = true + if searchText != (searchHistory.first ?? "") { + UserDefaults.standard.set([searchText] + searchHistory, forKey: "SearchHistory") + } + } + .onDisappear { + searchText = "" + } + } + if debug { + Button(action: { + searchText = "Darock" + isSearchPresented = true + }, label: { + Text("Search.debug") + }) + .accessibilityIdentifier("SearchDebugButton") + } + } + if searchHistory.count != 0 { + Section(header: Text("Search.history")) { + ForEach(0...searchHistory.count - 1, id: \.self) { i in + NavigationLink(destination: {SearchView(keyword: searchHistory[i])}, label: { + Text(searchHistory[i]) + }) + .swipeActions { + Button(role: .destructive, action: { + searchHistory.remove(at: i) + UserDefaults.standard.set(searchHistory, forKey: "SearchHistory") + }, label: { + Image(systemName: "trash") + }) + } + } + } + } + } + .onAppear { + searchHistory.removeAll() + searchHistory = UserDefaults.standard.stringArray(forKey: "SearchHistory") ?? [String]() + } + } + } +} + +struct SearchView: View { + var keyword: String + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var videos = [[String: String]]() + @State var users = [[String: Any]]() + @State var articles = [[String: String]]() + @State var bangumis = [BangumiData]() + @State var liverooms = [[String: String]]() + @State var isUserDetailPresented = [Bool]() + @State var isLoaded = false + @State var searchType = SearchType.video + @State var isNoResult = false + var body: some View { + ScrollView { + VStack { + // FIXME: Wtf is Apple doing? It shows a really fking bad view when the selecter presents. + Picker("Search.type", selection: $searchType) { + Text("Search.type.video").tag(SearchType.video) + Text("Search.type.user").tag(SearchType.user) + Text("Search.type.article").tag(SearchType.article) + Text("Search.type.bangumi").tag(SearchType.bangumi) + Text("Search.type.live").tag(SearchType.liveRoom) + } + .onChange(of: searchType) { value in + NewSearch(keyword: keyword, type: value, clear: true) + } + Divider() + Group { + if searchType == .video { + if videos.count != 0 { + ForEach(0...videos.count - 1, id: \.self) { i in + VideoCard(videos[i]) + } + } else if isNoResult { + Text("Search.no-result") + } + } else if searchType == .user { + if users.count != 0 { + ForEach(0..>).count != 0 { + ForEach(0..<(users[i]["Videos"]! as! Array>).count, id: \.self) { j in + VideoCard((users[i]["Videos"]! as! Array>)[j]) + .padding(.horizontal, 5) + } + Divider() + Spacer() + .frame(height: 20) + } + } + } + } else if isNoResult { + Text("Search.no-result") + } + } else if searchType == .article { + if articles.count != 0 { + ForEach(0..", with: "").replacingOccurrences(of: "", with: ""), "View": String(video.1["play"].int ?? -1), "Danmaku": String(video.1["video_review"].int ?? -1), "UP": video.1["author"].string ?? "[加载失败]", "BV": video.1["bvid"].string ?? "E"]) + } + case .user: + for user in result { + isUserDetailPresented.append(false) + users.append(["Name": user.1["uname"].string ?? "[加载失败]", "Pic": "https:" + (user.1["upic"].string ?? "E"), "ID": String(user.1["mid"].int ?? -1), "Fans": String(user.1["fans"].int ?? -1), "VideoCount": String(user.1["videos"].int ?? -1), "Videos": { () -> [[String: String]] in + var tVideos = [[String: String]]() + for video in user.1["res"] { + tVideos.append(["Pic": "https:" + (video.1["pic"].string ?? "E"), "Title": video.1["title"].string ?? "[加载失败]", "BV": video.1["bvid"].string ?? "E", "UP": user.1["uname"].string ?? "[加载失败]", "View": video.1["play"].string ?? "-1", "Danmaku": String(video.1["dm"].int ?? -1)]) + } + return tVideos + }()]) + } + case .article: + for article in result { + articles.append(["Title": (article.1["title"].string ?? "[加载失败]").replacingOccurrences(of: "", with: "").replacingOccurrences(of: "", with: ""), "Summary": article.1["desc"].string ?? "[加载失败]", "Type": article.1["category_name"].string ?? "[加载失败]", "View": String(article.1["view"].int ?? -1), "Like": String(article.1["like"].int ?? -1), "Pic": "https:" + (article.1["image_urls"][0].string ?? "E"), "CV": String(article.1["id"].int ?? 0)]) + } + case .bangumi: + for bangumi in result { + if (bangumi.1["type"].string ?? "") == "media_bangumi" { + bangumis.append(BangumiData(mediaId: bangumi.1["media_id"].int64 ?? 0, seasonId: bangumi.1["season_id"].int64 ?? 0, title: (bangumi.1["title"].string ?? "[加载失败]").replacingOccurrences(of: "", with: "").replacingOccurrences(of: "", with: ""), originalTitle: bangumi.1["org_title"].string ?? "[加载失败]", cover: /*"https:" + */(bangumi.1["cover"].string ?? "E"), area: bangumi.1["areas"].string ?? "[加载失败]", style: bangumi.1["styles"].string ?? "[加载失败]", cvs: (bangumi.1["cv"].string ?? "[加载失败]").split(separator: "\\n").map { String($0) }, staffs: (bangumi.1["staff"].string ?? "[加载失败]").split(separator: "\\n").map { String($0) }, description: bangumi.1["desc"].string ?? "[加载失败]", pubtime: bangumi.1["pubtime"].int ?? 0, eps: { () -> [BangumiEp] in + let eps = bangumi.1["eps"] + var tmp = [BangumiEp]() + for ep in eps { + tmp.append(BangumiEp(epid: ep.1["id"].int64 ?? 0, cover: ep.1["cover"].string ?? "E", title: ep.1["title"].string ?? "[加载失败]", indexTitle: ep.1["index_title"].string ?? "[加载失败]", longTitle: ep.1["long_title"].string ?? "[加载失败]")) + } + return tmp + }(), score: { () -> BangumiData.Score? in + if let score = bangumi.1["media_score"]["score"].float { + return .init(userCount: bangumi.1["media_score"]["user_count"].int ?? 0, score: score) + } else { + return nil + } + }(), isFollow: Bool(bangumi.1["is_follow"].int ?? 0))) + } + } + case .liveRoom: + for liveRoom in result { + liverooms.append(["Cover": "https:" + (liveRoom.1["user_cover"].string ?? "E"), "Title": (liveRoom.1["title"].string ?? "[加载失败]").replacingOccurrences(of: "", with: "").replacingOccurrences(of: "", with: ""), "ID": String(liveRoom.1["roomid"].int ?? 0), "Type": (liveRoom.1["cate_name"].string ?? "[加载失败]").replacingOccurrences(of: "", with: "").replacingOccurrences(of: "", with: "")]) + } + } + } + } + } + } + isLoaded = true + } + } + enum SearchType: String { + case video = "video" + case bangumi = "media_bangumi" + case liveRoom = "live_room" + case article = "article" + case user = "bili_user" + } +} + +struct SearchView_Previews: PreviewProvider { + static var previews: some View { + //SearchView(keyword: "Darock") + SearchMainView() + } +} diff --git a/MeowBili/Info.plist b/MeowBili/Info.plist index bc11256bd..def3bc56b 100644 --- a/MeowBili/Info.plist +++ b/MeowBili/Info.plist @@ -2,7 +2,31 @@ + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + drkbili + + + ITSAppUsesNonExemptEncryption + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIAppFonts + + BiliFont.ttf + + UIBackgroundModes + + audio + diff --git a/MeowBili/Live/LiveDetailView.swift b/MeowBili/Live/LiveDetailView.swift new file mode 100644 index 000000000..33d5b9ed6 --- /dev/null +++ b/MeowBili/Live/LiveDetailView.swift @@ -0,0 +1,307 @@ +// +// +// LiveDetailView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import Marquee +import DarockKit +import Alamofire +import SwiftyJSON +import CachedAsyncImage +import SDWebImageSwiftUI + +struct LiveDetailView: View { + var liveDetails: [String: String] + public static var willPlayStreamUrl = "" + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var watchingCount = 0 + @State var description = "" + @State var liveStatus = LiveRoomStatus.notStart + @State var startTime = "" + @State var streamerId: Int64 = 0 + @State var streamerName = "" + @State var streamerFaceUrl = "" + @State var streamerFansCount = 0 + @State var tagName = "" + @State var isLoading = false + @State var backgroundPicOpacity = 0.0 + @State var isLivePlayerPresented = false + var body: some View { + ZStack { + Group { + ScrollView { + FirstPageBase(liveDetails: liveDetails, streamerName: $streamerName, isLoading: $isLoading, isLivePlayerPresented: $isLivePlayerPresented) + SecondPageBase(liveDetails: liveDetails, watchingCount: $watchingCount, description: $description, liveStatus: $liveStatus, startTime: $startTime, streamerId: $streamerId, streamerName: $streamerName, streamerFaceUrl: $streamerFaceUrl, streamerFansCount: $streamerFansCount, tagName: $tagName) + } + } + .blur(radius: isLoading ? 14 : 0) + if isLoading { + Text("Video.analyzing") + .font(.title2) + .bold() + } + } + .navigationTitle("Live") + .navigationBarTitleDisplayMode(.inline) + .sheet(isPresented: $isLivePlayerPresented, content: {LivePlayerView()}) + .onAppear { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + DarockKit.Network.shared.requestJSON("https://api.live.bilibili.com/room/v1/Room/get_info?room_id=\(liveDetails["ID"]!)") { respJson, isSuccess in + if isSuccess { + watchingCount = respJson["data"]["online"].int ?? 0 + description = respJson["data"]["description"].string ?? "[加载失败]" + liveStatus = LiveRoomStatus(rawValue: respJson["data"]["live_status"].int ?? 0) ?? .notStart + startTime = respJson["data"]["live_time"].string ?? "0000-00-00 00:00:00" + tagName = respJson["data"]["tags"].string ?? "[加载失败]" + if let upUid = respJson["data"]["uid"].int64 { + streamerId = upUid + biliWbiSign(paramEncoded: "mid=\(upUid)".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + autoRetryRequestApi("https://api.bilibili.com/x/space/wbi/acc/info?\(signed)", headers: headers) { respJson, isSuccess in + if isSuccess { + if !CheckBApiError(from: respJson) { return } + streamerFaceUrl = respJson["data"]["face"].string ?? "E" + streamerName = respJson["data"]["name"].string ?? "[加载失败]" + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/relation/stat?vmid=\(upUid)", headers: headers) { respJson, isSuccess in + if isSuccess { + streamerFansCount = respJson["data"]["follower"].int ?? -1 + } + } + } + } + } + } + } + } + } + } + } + + struct FirstPageBase: View { + var liveDetails: [String: String] + @Binding var streamerName: String + @Binding var isLoading: Bool + @Binding var isLivePlayerPresented: Bool + @State var isCoverImageViewPresented = false + var body: some View { + VStack { + Spacer() + WebImage(url: URL(string: liveDetails["Cover"]! + "@240w_160h")!, options: [.progressiveLoad, .scaleDownLargeImages]) + .placeholder { + RoundedRectangle(cornerRadius: 14) + .frame(width: 120, height: 80) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + .resizable() + .scaledToFit() + .frame(width: 120, height: 80) + .cornerRadius(14) + .shadow(color: .black.opacity(0.5), radius: 5, x: 1, y: 2) + .offset(y: 8) + .sheet(isPresented: $isCoverImageViewPresented, content: {ImageViewerView(url: liveDetails["Cover"]!)}) + .onTapGesture { + isCoverImageViewPresented = true + } + Spacer() + .frame(height: 20) + Marquee { + HStack { + Text(liveDetails["Title"]!) + .lineLimit(1) + .font(.system(size: 12, weight: .bold)) + .multilineTextAlignment(.center) + } + } + .marqueeWhenNotFit(true) + .marqueeDuration(10) + .marqueeIdleAlignment(.center) + .frame(height: 20) + .padding(.horizontal, 10) + Text(streamerName) + .lineLimit(1) + .font(.system(size: 12)) + .padding(.horizontal, 40) + .padding(.vertical, 0) + .opacity(0.65) + Spacer() + .frame(height: 20) + if #unavailable(watchOS 10) { + Button(action: { + isLoading = true + + DarockKit.Network.shared.requestJSON("https://api.live.bilibili.com/room/v1/Room/playUrl?cid=\(liveDetails["ID"]!)&qn=150&platform=h5") { respJson, isSuccess in + if isSuccess { + debugPrint(respJson) + LiveDetailView.willPlayStreamUrl = respJson["data"]["durl"][0]["url"].string ?? "" + debugPrint(LiveDetailView.willPlayStreamUrl) + isLivePlayerPresented = true + isLoading = false + } + } + }, label: { + Label("Video.play", systemImage: "play.fill") + }) + } + } + } + } + + struct SecondPageBase: View { + var liveDetails: [String: String] + @Binding var watchingCount: Int + @Binding var description: String + @Binding var liveStatus: LiveRoomStatus + @Binding var startTime: String + @Binding var streamerId: Int64 + @Binding var streamerName: String + @Binding var streamerFaceUrl: String + @Binding var streamerFansCount: Int + @Binding var tagName: String + @State var ownerBlockOffset: CGFloat = 20 + @State var nowWatchingCountOffset: CGFloat = 20 + @State var publishTimeTextOffset: CGFloat = 20 + @State var liveIdTextOffset: CGFloat = 20 + @State var descOffset: CGFloat = 20 + var body: some View { + ScrollView { + VStack { + if streamerId != 0 { + NavigationLink(destination: {UserDetailView(uid: String(streamerId))}, label: { + HStack { + AsyncImage(url: URL(string: streamerFaceUrl + "@40w")) + .cornerRadius(100) + .frame(width: 40, height: 40) + VStack { + HStack { + Text(streamerName) + .font(.system(size: 16, weight: .bold)) + .lineLimit(1) + .minimumScaleFactor(0.1) + Spacer() + } + HStack { + Text("Video.fans.\(Int(String(streamerFansCount).shorter()) ?? 0)") + .font(.system(size: 11)) + .lineLimit(1) + .opacity(0.6) + Spacer() + } + } + Spacer() + } + }) + .buttonBorderShape(.roundedRectangle(radius: 18)) + .offset(y: ownerBlockOffset) + .animation(.easeOut(duration: 0.3), value: ownerBlockOffset) + .onAppear { + ownerBlockOffset = 0 + } + } + LazyVStack { + Spacer() + .frame(height: 10) + VStack { + HStack { + Image(systemName: "person.2") + Text("Video.details.watching-people.\(Int(watchingCount) ?? 0)") + .offset(x: -1) + Spacer() + } + .offset(x: -2, y: nowWatchingCountOffset) + .animation(.easeOut(duration: 0.55), value: nowWatchingCountOffset) + .onAppear { + nowWatchingCountOffset = 0 + } + HStack { + Image(systemName: "clock") + Text("Live.starting.\(startTime)") + Spacer() + } + .offset(y: publishTimeTextOffset) + .animation(.easeOut(duration: 0.65), value: publishTimeTextOffset) + .onAppear { + publishTimeTextOffset = 0 + } + HStack { + Image(systemName: "movieclapper") + Text(liveDetails["ID"]!) + Spacer() + } + .offset(x: -1, y: liveIdTextOffset) + .animation(.easeOut(duration: 0.75), value: liveIdTextOffset) + .onAppear { + liveIdTextOffset = 0 + } + } + .font(.system(size: 11)) + .opacity(0.6) + .padding(.horizontal, 10) + Spacer() + .frame(height: 5) + HStack { + VStack { + Image(systemName: "info.circle") + Spacer() + } + Text(description) + Spacer() + } + .font(.system(size: 12)) + .opacity(0.65) + .padding(.horizontal, 8) + .offset(y: descOffset) + .animation(.easeOut(duration: 0.4), value: descOffset) + .onAppear { + descOffset = 0 + } + HStack { + VStack { + Image(systemName: "tag") + Spacer() + } + Text(tagName) + Spacer() + } + .font(.system(size: 12)) + .opacity(0.65) + .padding(.horizontal, 8) + .offset(y: descOffset) + .animation(.easeOut(duration: 0.4), value: descOffset) + } + } + } + } + } +} + +enum LiveRoomStatus: Int { + case notStart = 0 + case streaming = 1 + case playbacking = 2 +} + +//#Preview { +// LiveDetailView() +//} diff --git a/MeowBili/Live/LiveMessagesView.swift b/MeowBili/Live/LiveMessagesView.swift new file mode 100644 index 000000000..cd6edd740 --- /dev/null +++ b/MeowBili/Live/LiveMessagesView.swift @@ -0,0 +1,57 @@ +// +// +// LiveMessagesiew.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import Alamofire +import SwiftyJSON + +struct LiveMessagesView: View { + var roomId: Int + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var token = "" + @State var server = "" + var body: some View { + ScrollView { + VStack { + + } + } + .onAppear { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + DarockKit.Network.shared.requestJSON("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=\(roomId)", headers: headers) { respJson, isSuccess in + if isSuccess { + token = respJson["data"]["token"].string ?? "E" + server = respJson["data"]["host_list"][0]["host"].string ?? "E" + + + } + } + } + } +} + +//#Preview { +// LiveMessagesView() +//} diff --git a/MeowBili/Live/LivePlayerView.swift b/MeowBili/Live/LivePlayerView.swift new file mode 100644 index 000000000..294dcc7ec --- /dev/null +++ b/MeowBili/Live/LivePlayerView.swift @@ -0,0 +1,41 @@ +// +// +// LivePlayerView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import AVKit +import SwiftUI +import DarockKit +import Alamofire +import SwiftyJSON +import AVFoundation + +struct LivePlayerView: View { + @State var livePlayer: AVPlayer? = nil + @State var tabviewChoseTab = 2 + var body: some View { + TabView(selection: $tabviewChoseTab) { + VideoPlayer(player: livePlayer) + .ignoresSafeArea() + .tag(2) + } + .onAppear { + let asset = AVURLAsset(url: URL(string: LiveDetailView.willPlayStreamUrl)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["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", "Referer": "https://www.bilibili.com"]]) + let item = AVPlayerItem(asset: asset) + livePlayer = AVPlayer(playerItem: item) + } + } +} diff --git a/MeowBili/MeowBili-Bridging-Header.h b/MeowBili/MeowBili-Bridging-Header.h new file mode 100644 index 000000000..026a05908 --- /dev/null +++ b/MeowBili/MeowBili-Bridging-Header.h @@ -0,0 +1,11 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#ifndef DarockBili_Bridging_Header_h +#define DarockBili_Bridging_Header_h + +#import "CodingTime.h" +#import "AVExtension.h" + +#endif /* DarockBili_Bridging_Header_h */ diff --git a/MeowBili/MeowBiliApp.swift b/MeowBili/MeowBiliApp.swift new file mode 100644 index 000000000..df9e9e2da --- /dev/null +++ b/MeowBili/MeowBiliApp.swift @@ -0,0 +1,334 @@ +// +// +// MeowBiliApp.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Darwin +import SwiftUI +import DarockKit +import SwiftyJSON +import SDWebImage +import SDWebImagePDFCoder +import SDWebImageSVGCoder +import SDWebImageWebPCoder + +//!!!: Debug Setting, Set false Before Release +var debug = false + +var debugControlStdout = "stdo\n" + +var pShowTipText = "" +var pShowTipSymbol = "" +var pTipBoxOffset: CGFloat = 80 + +var isShowMemoryInScreen = false + +var isInOfflineMode = false + +// BUVID +var globalBuvid3 = "" +var globalBuvid4 = "" + +/* + ::::::::::::::-:=**=========+===++++++++++*%+*%%%%#%%%%%*++#@%%%@@@%%@@@%%%%%%%*+*#****#%%@@@@@@@@@% + ::::::::::::::::===========+===++++++++++#@*+%%%%%%%%%%@%*++#@%%@@@@%%@@%%%%%%@%*+********#%@@@@@@@% + :::::::::::::::::-========+==+++++=++++*%@#+%%%%%#%%%%%%%@%*+#@%%%%%@%%@%%%%%%%@%++***##%@@@@@@@@%%* + ::::::::::::::::=========+==+++====+++*%%%+#%%%%#%%%%%%%%%@%*+%@%%%%%@%%@%%%%%%%%*+**#@@@@@@@@@@@@#: + :::::::::::::::============++=====+++#%%%#*@%%%##@%%%%%%%%%%@*+%%%%%%%@%%@%%%%%%@*+***%@@@@@@@@@@#:- + ::::::::::::::============++=====+++#%%%%*%%%%%#%%#%%%%%%%%%%@**@%@%%%%@%%%%%%%%@#****#%%@@@@@@@@%*- + :::::::::::::============++======++*%%#%%#%%%%#%%%#@%%%%%%%%%%@*%@%%@%%%%%%%%%%%%%%#+*#%@@@@@@@%%*=- + ::::::::::::=-==========++===+==++*%%##%%%%%%%%%%*%%%%%%%%%%#%%%####%@%%%%%%%%%%%%@%+***#%@@@@*--::- + :::::::::::=-===========+======+++%%%*%%%%%%%%%@*+%%%%%%%%%%%%%%%**%#%@%%%%%%%%%%%%%*+*****#%@#----- + ::::::::::=:-==========+======+++*%%#*%%%%%%#%%*+*%%%%%%%%%%%#%%%#++%#%@%%%%%%%%%%%%*****#++*##=---- + :::::::::=::==================+++#%%*#%%%%%%%@#+*+%%%%%%%%%%%%%%%%%++%#%@%%%%%%%%%%%#@%#%@%+%@%=:--- + ::::::::--:=========+==+====+=+++%%%*%%%%%%%%***#+#%%%%%%%%%%%#%%%%%*+#%#@%%%%%%%%%%#%%@%%@*%%@*-::- + :::::::--:-=========+========+++*%%##%%%#%@%+*%*#**%%%%%%%%%%%#%%%%%%#+#%#%%%%%%%%%%#%%%%%@##@%%#*=: + ::::::--::=========+=-+====+====*%%#*%%#%%*+*%####+%%%%%%#%%%%##%%%%#@#+#%#%@%%%###%%%%%%%@%#@%@*-** + ::::::-::-=========+======+*+=++*%%**#%%#++#@##%#%+*@%%%%%%%%%%#%%%%*%@%+*%#%%##%%%%%%@%%%%@#%@%%-:= + :::::-:::==========+======##+=+++%%###*+=*%%%##%%%%+%%%%%##%%*#*%%%@#*@%%+#%%#%%%@%%%%@%%%%@%%@%##-: + ::::-:::-==========+-==+=***#++++###*=+*#%%%%%#%@%@#+%%%%#%%%*#**%%%%+#%%%##%#%@%%%%%%@%%%@%%%%@*%#- + ::::::::===========+----::..:::-=+###*%%%%%%%@##%@%@#*@%%#%%#%#%*#%%%+#%**+**%#%%%%%%%@%%%@%%%%%%+%% + ::::::::=========+++=-- :==+-.::::=*@#%%%%#%%@%##@@%@##@%%%#%@#@%*%%*#***%*#%*%#%%%%%#@%%%%%%%%%%*+% + :::::::-=========*#+==#-=@@@=-===--+#%#%%%##%@@@##@@@@%##%##%##@@%#%%+=*@@*#@#+##%@%%#%%#%%%@%%@%#++ + :::::::-========+%%+==##+%@@---=--+#*#%##%%*%@@@@%#%@@@#*+=-----======++%@*#%%#+##%%%#%%*%%%%%%%%%#= + :::::::=========#%%#=-*%%%@@==*+-=+-*@%@##%%%@@@@@@%%@%**#-:-----::-.:--+%*%%%@#*##%%#%#*@%%%%%%%%@# + :::::::=======++#%%%+=+#%@%@%*%%###+%@@@@%%%#%@@@@@@@@%@@%-==+*==*%@%*:.-#*%%%%%%%%%%#%+*@%%%%%%%%%% + .-::::-========+%%%%+++#%@@@@%#%%%#%@@@@@@@@%%%@@@@@@@@@@*-==##===+@@%+*##*%%%%%%%%%%%++#%%%%%%%%%%% + .--::--====-===+#%%%*#=*#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+#####+-#@%%%%#+#%%%%%%%%%%%++#%%%%%%%#%%% + .--:----========*@%%##*+*%@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@##%%%#*#@@@@%##+%%%%%%%%%%%%*+%%%%%#%@#*%% + .-=:-=--==-=-==++%%%#**#*#%@@@@@@@@@@@@@@%%%@@@@@@@@@@@@@@@@@@@%@@@@@%#%**@%%%%%%%%%%%#+%%%%%#%%#+#% + ::=-=--===-======+%%%%==+#%@@@@@@@@@@@@@@%%%@@@@@@@@@@@@@@@@@@@@@@@@@%##=%%%%%%%%%%%%%%*%%%%**%%*=*% + -===-====--=====+=*%%%*=-*#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@##**%%%%%%%%%%%#%%%%%%%+#%%*:+# + -=======--:======+=*%%=+==##%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%###%%%%%%%%%%%%*#%%%%%#+%%%+:-* + =======-:::=========*%*-==+##%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#*#*#%%%%%%%%%%%%+*%#%%%+*%%#-:-= + ======---==-=========+%+-==+*#%@@@@@@@@@@@%%%%@@@@@@@@@@@@@@@@@@@%+-=++%%#%%%%%%%%%*+*%%#%*=#%%*-:-= + =====--====-===========#+====++#%@@@@@@@@@%%%%@@@@@@@@@@@@@@@@@%*--+==#%#%%%%%%%%%#+++%%##%#%%*=:-== + ===-:-====---===========+++===+++*%@@@@@@@@@@@@@@@@@@@@@@@@@%*=:::+==+%#*%%%%%%%%%+*++%%%#%%%*+--=+= + ==-::===-:----=========+=-+-::-=+++=*%@@@@@@@@@@@@@@@@@%%#+==-==+**++%%+#%%%%%%%%#+*++%%%%#%*+-:-==+ + =-:::==-..-----=========#+-::-++=++: :*#%@@@@@@@@%%%###****#%%%%%#+=#%++%%%%%%%%%=*+++%%#%#++=:--=+% + -::::=-:-------=========+#-::+%#*==:.:-+++*##########%%%%%%##*++=-:+%+=*%%%%%%%%*-*+++%%*#%#-::::-== + -----=--------++=========+#--*##%#+:--:*+++**##%%%%##**+==---------%*-=#%%%%%%%#++==++%%++#%*::::::: + --------------++++========+*=+%###%*--+*#%%%%##*++=------==+**##+=*#*+=#%%%%%%%+*+=+=*%#+++#@#=::::: + ------:-------++++==========*+#####%#+*%##*+=-----=++*###%%%%%%#==###+=*%##%%%#+*+++=*%*+++==#%*=::: + ----::...:---:=++++*++=:-====++*######+=----=+**##%%%%%########*=+*##*++%##%%%*+++++-%#=+++::-+**+++ + --:....::..-:::=++#%#+:::======++######+-+*#%##################*=+####++*%*%%#+++++=*#++++=+++==+### + :.....-+=..-::.-++#*=:-+#***++-::-+***###++**###################=+#####++*##%#+++==+*++++++==+*#%### + .....:++- -=::.:-+=::=*########*-:-*######+==++**###############*=%#####+=++%#++=++=+=+====+#%%##### + ..:..+++..+-.:.::::-+############=::=*#####*+++++++*############%**%%#%%%#+=+%++++=====-=*#%######%% + :+::++: -+---:::::###############*=::=*######***#*#**#***********+=***++=-::=*===+++++*########%#*+ + :+*-=+=.:---::.::::=################*=::-+*#####*=-:-:::::----------------=-::+++++++**######%#*+-:: + ++--++=-:::::.::::::=+*###############*+=--==+=-:::-+:--====================-:=+++++**+#**#%#+-::::: + ++:+++=:.:::..:::::-*==+**###############*+=:::---+%--=======================--+++++#++++**+-::::::: + +++++=::::..--:::::-##*++=++***##***++=+*===+*****%=-=========================-++++**++**+--------:: + + Xcode哥😭,求你了🙏别把fatal往main抛,闭包,精确到闭包就行求你了🙏我在也不跑了哥,我真的太痒了,求你了,我听话,我以后再也不闹了🤐,求你了哥,给我定位到闭包吧,我现在身体痒的要死😫,求求你了🙏,哥,你就最后再给我一次吧我求求你了🙏哥,你要什么我都给你,你再给我一集🤌就好,我现在身体里像是有蚂蚁在爬😫,太痒了 哥,给我一集吧,最后一次,我发誓✋,真的是最后一次了 + | main + | + \/ +*/ +@main +struct DarockBili_Watch_AppApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @Environment(\.scenePhase) var scenePhase + // Screen Time + @AppStorage("isSleepNotificationOn") var isSleepNotificationOn = false + @AppStorage("notifyHour") var notifyHour = 0 + @AppStorage("notifyMinute") var notifyMinute = 0 + @AppStorage("IsScreenTimeEnabled") var isScreenTimeEnabled = true + @State var screenTimeCaculateTimer: Timer? = nil + @State var showTipText = "" + @State var showTipSymbol = "" + @State var tipBoxOffset: CGFloat = 80 + @State var isOfflineMode = false + @State var isLowBatteryMode = false + // Debug Controls + @State var isShowingDebugControls = false + @State var systemResourceRefreshTimer: Timer? + @State var memoryUsage: Float = 0.0 + @State var isShowMemoryUsage = false + @State var currentHour = 0 + @State var currentMinute = 0 + var body: some Scene { + WindowGroup { + if UserDefaults.standard.string(forKey: "NewSignalError") ?? "" != "" { + SignalErrorView() + } else { + ContentView() + .onAppear { + Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in + showTipText = pShowTipText + showTipSymbol = pShowTipSymbol + UserDefaults.standard.set(isLowBatteryMode, forKey: "IsInLowBatteryMode") + Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { timer in + tipBoxOffset = pTipBoxOffset + timer.invalidate() + } + } + + Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in + if isShowMemoryInScreen { + isShowMemoryUsage = true + Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in + memoryUsage = getMemory() + } + timer.invalidate() + } + } + let timer = Timer(timeInterval: 1, repeats: true) { timer in + currentHour = getCurrentTime().hour + currentMinute = getCurrentTime().minute + } + let sleepTimeCheck = Timer(timeInterval: 60, repeats: true) { timer in + if currentHour == notifyHour && currentMinute == notifyMinute && isSleepNotificationOn { + AlertKitAPI.present(title: String(localized: "Sleep.notification"), icon: .heart, style: .iOS17AppleMusic, haptic: .warning) + } + } + RunLoop.current.add(timer, forMode: .default) + timer.fire() + RunLoop.current.add(sleepTimeCheck, forMode: .default) + sleepTimeCheck.fire() + } + .overlay { + VStack { + HStack { + if isLowBatteryMode { + Image(systemName: "circle") + .font(.system(size: 17, weight: .heavy)) + .foregroundColor(.accentColor) + .offset(y: 10) + } + } + Spacer() + } + .ignoresSafeArea() + if isShowMemoryUsage { + VStack { + HStack { + Spacer() + Text("Memory.indicator.\(String(format: "%.2f", memoryUsage))") + .font(.system(size: 10, weight: .medium)) + .offset(y: 26) + } + Spacer() + } + .ignoresSafeArea() + } + if debug { + HStack { + VStack { + Button(action: { + isShowingDebugControls.toggle() + }, label: { + Text(isShowingDebugControls ? "Close Debug Controls" : "Show Debug Controls") + .font(.system(size: 12)) + .foregroundColor(.blue) + }) + .buttonStyle(.plain) + .offset(x: 15, y: 5) + if isShowingDebugControls { + VStack { + HStack { + Text("Memory Usage: \(memoryUsage) MB") + Spacer() + } + .allowsHitTesting(false) + } + .font(.system(size: 10)) + + .onAppear { + Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in + systemResourceRefreshTimer = timer + memoryUsage = getMemory() + } + } + .onDisappear { + systemResourceRefreshTimer?.invalidate() + } + } + Spacer() + } + .padding(.horizontal, 3) + .padding(.vertical, 1) + Spacer() + } + .ignoresSafeArea() + } + } + } + } + .onChange(of: scenePhase) { value in + switch value { + case .background: + break + case .inactive: + break + case .active: + SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared) + SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared) + SDImageCodersManager.shared.addCoder(SDImagePDFCoder.shared) + SDImageCache.shared.config.maxMemoryCost = 1024 * 1024 * 10 + SDImageCache.shared.config.shouldCacheImagesInMemory = false + SDImageCache.shared.config.shouldUseWeakMemoryCache = true + SDImageCache.shared.clearMemory() + + updateBuvid() + + if isScreenTimeEnabled { + if screenTimeCaculateTimer == nil { + Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in + screenTimeCaculateTimer = timer + let df = DateFormatter() + df.dateFormat = "yyyy-MM-dd" + let dateStr = df.string(from: Date.now) + UserDefaults.standard.set(UserDefaults.standard.integer(forKey: "ScreenTime\(dateStr)") + 1, forKey: "ScreenTime\(dateStr)") + } + } + } + @unknown default: + break + } + } + } +} + +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + let audioSession = AVAudioSession.sharedInstance() + do { + try audioSession.setCategory(.playback) + try audioSession.setActive(true, options: []) + } catch { + print("Setting category to AVAudioSessionCategoryPlayback failed.") + } + + return true + } + + func applicationDidReceiveMemoryWarning(_ application: UIApplication) { + AlertKitAPI.present(title: "低内存警告", subtitle: "喵哩喵哩收到了低内存警告", icon: .error, style: .iOS17AppleMusic, haptic: .warning) + } +} + +func signalErrorRecord(_ errorNum: Int32, _ errorSignal: String) { + var symbols = "" + for symbol in Thread.callStackSymbols { + symbols += symbol + "\n" + } + let dateN = Date.now + let df = DateFormatter() + df.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSS Z" + let dateStr = df.string(from: dateN) + let fullString = """ + ------------------------------------- + Translated Report (Full Report Below) + ------------------------------------- + + Date/Time: \(dateStr) + Version: \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String) Build \(Bundle.main.infoDictionary?["CFBundleVersion"] as! String) + OS Version: \(UIDevice.current.systemName) \(UIDevice.current.systemVersion) + + Exception Type: \(errorSignal) + Termination Reason: \(errorSignal) \(errorNum) + + Main Symbols + + \(backtraceMainThread()) + + + Current Thread Symbols: + + \(backtraceCurrentThread()) + + Swift Thread Symbols: + + \(symbols) + + EOF + """ + let manager = FileManager.default + let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask) + try! fullString.write(to: URL(string: (urlForDocument[0] as URL).absoluteString + "\(dateStr.replacingOccurrences(of: " ", with: "_").replacingOccurrences(of: "/", with: "-").replacingOccurrences(of: ":", with: "__")).ddf")!, atomically: true, encoding: .utf8) + UserDefaults.standard.set("\(dateStr).ddf", forKey: "NewSignalError") +} + +public func updateBuvid() { + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/frontend/finger/spi") { respJson, isSuccess in + if isSuccess { + globalBuvid3 = respJson["data"]["b_3"].string ?? globalBuvid3 + globalBuvid4 = respJson["data"]["b_4"].string ?? globalBuvid4 + } + } +} diff --git a/MeowBili/Models/Bangumi.swift b/MeowBili/Models/Bangumi.swift new file mode 100644 index 000000000..7a9206510 --- /dev/null +++ b/MeowBili/Models/Bangumi.swift @@ -0,0 +1,70 @@ +// +// +// Bangumi.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Foundation + +struct BangumiData { + var mediaId: Int64 + var seasonId: Int64 + var title: String + var originalTitle: String + var cover: String + var area: String? + var style: String? + var cvs: [String]? + var staffs: [String]? + var description: String? + var pubtime: Int? + var eps: [BangumiEp]? + var score: Score? + var isFollow: Bool = false + + struct Score { + var userCount: Int + var score: Float + } +} + +struct BangumiEp: Identifiable, Hashable { + var id: Int64 + + var aid: Int64? + var epid: Int64 + var cid: Int64? + var cover: String + var title: String + var indexTitle: String? + var longTitle: String + + init(aid: Int64? = nil, epid: Int64, cid: Int64? = nil, cover: String, title: String, indexTitle: String? = nil, longTitle: String) { + self.id = epid + self.aid = aid + self.epid = epid + self.cid = cid + self.cover = cover + self.title = title + self.indexTitle = indexTitle + self.longTitle = longTitle + } +} + +struct BangumiPayment { + var discount: Int + var tip: String + var promotion: String +} diff --git a/MeowBili/Models/DownloadObj.swift b/MeowBili/Models/DownloadObj.swift new file mode 100644 index 000000000..99e5c68c1 --- /dev/null +++ b/MeowBili/Models/DownloadObj.swift @@ -0,0 +1,33 @@ +// +// +// DownloadObj.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Foundation + +public struct DownloadProgressData: Equatable { + var progress: Double + var currentSize: Int64 + var totalSize: Int64 + + public static func ==(lhs: DownloadProgressData, rhs: DownloadProgressData) -> Bool { + return lhs.progress == rhs.progress + } +} +public struct DownloadTaskDetailData: Equatable { + var data: DownloadProgressData + var videoDetails: [String: String] +} diff --git a/MeowBili/Others/AboutView.swift b/MeowBili/Others/AboutView.swift new file mode 100644 index 000000000..4eda36626 --- /dev/null +++ b/MeowBili/Others/AboutView.swift @@ -0,0 +1,222 @@ +// +// +// AboutView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit + +struct AboutView: View { + var body: some View { + NavigationStack { + TabView { + AboutApp() + .navigationTitle("About") + .tabItem { + Label("About", systemImage: "info.circle.fill") + } + AboutCredits() + .navigationTitle("About.credits") + .tabItem { + Label("About.credits", systemImage: "person.3.sequence.fill") + } + } + } + } +} + +struct AboutApp: View { + let AppIconLength: CGFloat = 140 + var body: some View { + VStack(alignment: .center) { + Image("AppIconImage") + .resizable() + .frame(width: AppIconLength, height: AppIconLength) + .mask(Circle()) + Text("About.meowbili") + .bold() + .font(.title2) + Group { + Text("v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String) Build \(Bundle.main.infoDictionary?["CFBundleVersion"] as! String)") + .font(.system(size: 18)) + Group { + if debug { + Text(CodingTime.getCodingTime()) + } else { + Text("\(CodingTime.getCodingTime().components(separatedBy: " ")[0] + " " + CodingTime.getCodingTime().components(separatedBy: " ")[1] + " " + CodingTime.getCodingTime().components(separatedBy: " ")[2])") + } + } + .font(.system(size: 18)) + } + .font(.caption) + .monospaced() + .foregroundStyle(.secondary) + .onTapGesture(count: 9) { + debug.toggle() + if debug { + AlertKitAPI.present(title: "Dev On", icon: .done, style: .iOS17AppleMusic, haptic: .success) + } else { + AlertKitAPI.present(title: "Dev Off", icon: .done, style: .iOS17AppleMusic, haptic: .success) + } + } + } + } +} + +struct AboutCredits: View { + @Environment(\.dismiss) var dismiss + @State var isEasterEgg1Presented = false + @State var isGenshin = false + @State var genshinOverlayTextOpacity: CGFloat = 0.0 + var body: some View { + List { + Section { + Text("WindowsMEMZ") + Text("Lightning-Lion") + Text("Linecom") + Text("令枫") + Text("ThreeManager785") + Text("Dignite") + Text("-- And You --") + .sheet(isPresented: $isEasterEgg1Presented, content: {EasterEgg1View(isGenshin: $isGenshin)}) + .onTapGesture(count: 10) { + isEasterEgg1Presented = true + } + } + Section { + NavigationLink(destination: { + OpenSourceView() + .navigationTitle("About.open-source") + }, label: { + Text("About.open-source") + }) + } + } + .navigationTitle("About.credits") + .navigationBarHidden(isGenshin) + .overlay { + if isGenshin { + ZStack(alignment: .center) { + Color.white + Text("About.genshin") + .font(.system(size: 30, weight: .heavy)) + .foregroundColor(.black) + .opacity(genshinOverlayTextOpacity) + } + .ignoresSafeArea() + .animation(.smooth(duration: 2.0), value: genshinOverlayTextOpacity) + .onAppear { + genshinOverlayTextOpacity = 1.0 + Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in + isGenshin = false + dismiss() + } + } + } + } + } + + // MARK: Easter Eggs + struct EasterEgg1View: View { + @Binding var isGenshin: Bool + @Environment(\.dismiss) var dismiss + @State var codeInput = "" + var body: some View { + VStack { + TextField("About.mystery-code", text: $codeInput) + Button(action: { + if codeInput == "Genshin" { + isGenshin = true + dismiss() + } else { + codeInput = String(localized: "About.mystery-code.error") + } + }, label: { + Text("About.confirm") + }) + } + .presentationDetents([.medium]) + } + } +} + + +struct OpenSourceView: View { + let openSourceTexts = """ + --- Alamofire --- + Licensed under MIT license + ----------------- + + --- Dynamic --- + Licensed under Apache License 2.0 + --------------- + + --- EFQRCode --- + Licensed under MIT license + ---------------- + + --- libwebp --- + Licensed under BSD-3-Clause license + --------------- + + --- SDWebImage --- + Licensed under MIT license + ------------------ + + --- SDWebImagePDFCoder --- + Licensed under MIT license + -------------------------- + + --- SDWebImageSVGCoder --- + Licensed under MIT license + -------------------------- + + --- SDWebImageSwiftUI --- + Licensed under MIT license + ------------------------- + + --- SDWebImageWebPCoder --- + Licensed under MIT license + --------------------------- + + --- SFSymbol --- + Licensed under MIT license + ---------------- + + --- swift_qrcodejs --- + Licensed under MIT license + ---------------------- + + --- SwiftyJSON --- + Licensed under MIT license + ------------------ + """ + var body: some View { + ScrollView { + HStack { + Spacer() + Text(openSourceTexts) + Spacer() + } + } + } +} + +struct AboutView_Previews: PreviewProvider { + static var previews: some View { + AboutView() + } +} diff --git a/MeowBili/Others/CCodes/AVExtension.h b/MeowBili/Others/CCodes/AVExtension.h new file mode 100644 index 000000000..1d7fdfa81 --- /dev/null +++ b/MeowBili/Others/CCodes/AVExtension.h @@ -0,0 +1,42 @@ +// +// +// AVExtension.h +// DarockBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef AVExtension_h +#define AVExtension_h + +#import +#import + +@interface AVExtension: NSObject { + AVPlayer *networkPlayer; +} + +- (instancetype) init: (NSString *) playerUrl; + +- (AVPlayer *) GetPlayer; +- (CMTime) GetCurrentPlayTime; +- (double) GetCurrentPlayTimeSeconds; ++ (CMTime) GetCurrentPlayTime: (AVPlayer *) player; ++ (double) GetCurrentPlayTimeSeconds: (AVPlayer *) player; + ++ (void) AVPlayerPausePlay: (AVPlayer *) player; ++ (void) AVPlayerStartPlay: (AVPlayer *) player; + +@end + +#endif /* AVExtension_h */ diff --git a/MeowBili/Others/CCodes/AVExtension.m b/MeowBili/Others/CCodes/AVExtension.m new file mode 100644 index 000000000..2ce89f038 --- /dev/null +++ b/MeowBili/Others/CCodes/AVExtension.m @@ -0,0 +1,71 @@ +// +// +// AVExtension.m +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#import +#import +#import "AVExtension.h" + +@implementation AVExtension + +- (instancetype)init: (NSString *) playerUrl { + self = [super init]; + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + [headers setObject:@"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" forKey:@"User-Agent"]; + AVURLAsset *asset = [[AVURLAsset alloc] initWithURL: [[NSURL alloc] initWithString: playerUrl] options: headers]; + AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset: asset]; + AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem: item]; + networkPlayer = player; + return self; +} + +- (AVPlayer *) GetPlayer { + return networkPlayer; +} + +- (CMTime) GetCurrentPlayTime { + AVPlayerItem *currentItem = networkPlayer.currentItem; + CMTime currentTime = currentItem.currentTime; + return currentTime; +} +- (double) GetCurrentPlayTimeSeconds { + AVPlayerItem *currentItem = networkPlayer.currentItem; + CMTime currentTime = currentItem.currentTime; + double seconds = CMTimeGetSeconds(currentTime); + return seconds; +} + ++ (CMTime) GetCurrentPlayTime: (AVPlayer *) player { + AVPlayerItem *currentItem = player.currentItem; + CMTime currentTime = currentItem.currentTime; + return currentTime; +} ++ (double) GetCurrentPlayTimeSeconds: (AVPlayer *) player { + AVPlayerItem *currentItem = player.currentItem; + CMTime currentTime = currentItem.currentTime; + double seconds = CMTimeGetSeconds(currentTime); + return seconds; +} + ++ (void) AVPlayerPausePlay: (AVPlayer *) player { + [player pause]; +} ++ (void) AVPlayerStartPlay: (AVPlayer *) player { + [player play]; +} + +@end diff --git a/MeowBili/Others/CCodes/CodingTime.h b/MeowBili/Others/CCodes/CodingTime.h new file mode 100644 index 000000000..42181841e --- /dev/null +++ b/MeowBili/Others/CCodes/CodingTime.h @@ -0,0 +1,30 @@ +// +// +// CodingTime.h +// DarockBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef CodingTime_h +#define CodingTime_h + +#import + +@interface CodingTime : NSObject + ++ (NSString *) getCodingTime; + +@end + +#endif /* CodingTime_h */ diff --git a/MeowBili/Others/CCodes/CodingTime.m b/MeowBili/Others/CCodes/CodingTime.m new file mode 100644 index 000000000..3896f7655 --- /dev/null +++ b/MeowBili/Others/CCodes/CodingTime.m @@ -0,0 +1,31 @@ +// +// +// CodingTime.m +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#import +#import "CodingTime.h" + +#define kCodingDate __DATE__ +#define kCodingTime __TIME__ + +@implementation CodingTime + ++ (NSString *) getCodingTime { + return [@kCodingDate stringByAppendingString: [@" " stringByAppendingString: @kCodingTime]]; +} + +@end diff --git a/MeowBili/Others/LoginView.swift b/MeowBili/Others/LoginView.swift new file mode 100644 index 000000000..db26d8b38 --- /dev/null +++ b/MeowBili/Others/LoginView.swift @@ -0,0 +1,260 @@ +// +// +// LoginView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import SwiftyJSON +import Alamofire +import EFQRCode +import AuthenticationServices + +struct LoginView: View { + @Environment(\.dismiss) var dismiss + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + //Captcha + @State var loginToken = "" + @State var challenge = "" + @State var gt = "" + @State var validate = "" + @State var seccode = "" + //Bili Returns + @State var salt = "" + @State var publicKey = "" + //User Input + @State var accountInput = "" + @State var passwdInput = "" + @State var phoneCode = "86" + //---QR Login--- + @State var qrImage: CGImage? + @State var qrKey = "" + @State var isScanned = false + @State var qrTimer: Timer? + + @State var smsLoginToken = "" + + @State var userList1: [Any] = [] + @State var userList2: [Any] = [] + @State var userList3: [Any] = [] + @State var userList4: [Any] = [] + var body: some View { + TabView { +// ScrollView { +// if qrImage != nil { +// ZStack { +// VStack { +// Image(uiImage: UIImage(cgImage: qrImage!)) +// .resizable() +// .frame(width: 140, height: 140) +// .blur(radius: isScanned ? 8 : 0) +// Text("Login.scan") +// .bold() +// } +// if isScanned { +// Text("Login.scanned") +// .font(.title2) +// //.foregroundColor(.white) +// } +// } +// } else { +// ProgressView() +// } +// } +// .tag(0) +// .onAppear { +// userList1 = UserDefaults.standard.array(forKey: "userList1") ?? [] +// userList2 = UserDefaults.standard.array(forKey: "userList2") ?? [] +// userList3 = UserDefaults.standard.array(forKey: "userList3") ?? [] +// userList4 = UserDefaults.standard.array(forKey: "userList4") ?? [] +// let headers: HTTPHeaders = [ +// "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" +// ] +// DarockKit.Network.shared.requestJSON("https://passport.bilibili.com/x/passport-login/web/qrcode/generate", headers: headers) { respJson, isSuccess in +// if isSuccess { +// let qrUrl = respJson["data"]["url"].string!.replacingOccurrences(of: "\\u0026", with: "&") +// debugPrint(qrUrl) +// if let image = EFQRCode.generate(for: qrUrl) { +// qrImage = image +// } +// qrKey = respJson["data"]["qrcode_key"].string! +// Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { timer in +// qrTimer = timer +// DarockKit.Network.shared.requestJSON("https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=\(qrKey)", headers: headers) { respJson, isSuccess in +// if respJson["data"]["code"].int == 86090 { +// isScanned = true +// } else if respJson["data"]["code"].int == 0 { +// timer.invalidate() +// debugPrint(respJson) +// let respUrl = respJson["data"]["url"].string! +// dedeUserID = String(respUrl.split(separator: "DedeUserID=")[1].split(separator: "&")[0]) +// dedeUserID__ckMd5 = String(respUrl.split(separator: "DedeUserID__ckMd5=")[1].split(separator: "&")[0]) +// sessdata = String(respUrl.split(separator: "SESSDATA=")[1].split(separator: "&")[0]) +// biliJct = String(respUrl.split(separator: "bili_jct=")[1].split(separator: "&")[0]) +// userList1.append(dedeUserID) +// userList2.append(dedeUserID__ckMd5) +// userList3.append(sessdata) +// userList4.append(biliJct) +// UserDefaults.standard.set(userList1, forKey: "userList1") +// UserDefaults.standard.set(userList2, forKey: "userList2") +// UserDefaults.standard.set(userList3, forKey: "userList3") +// UserDefaults.standard.set(userList4, forKey: "userList4") +// dismiss() +// } +// +// } +// } +// } +// } +// } +// .onDisappear { +// if qrTimer != nil { +// qrTimer!.invalidate() +// } +// } + + //--SMS Login-- + List { + Section { + TextField("国际冠字码", text: $phoneCode) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + TextField("手机号", text: $accountInput) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + } header: { + Text("第一步: 手机号信息") + } + Section { + Button(action: { + UIApplication.shared.open(URL(string: "https://darock.top/geetest?gt=\(gt)&challenge=\(challenge)")!) + }, label: { + Text(validate == "" ? "进行人机验证" : "人机验证已完成") + .bold() + }) + .disabled(validate != "") + } header: { + Text("第二步: 人机验证") + } + Section { + Button(action: { + let headers: HTTPHeaders = [ + "Host": "passport.bilibili.com", + "Origin": "https://www.bilibili.com", + "Referer": "https://www.bilibili.com/", + "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", + "Cookie": "browser_resolution=1580-497; FEED_LIVE_VERSION=V8; buvid4=818BA302-8EAC-0630-67AB-BB978A5797AF60982-023042618-ho21%2BqF6LZokzAShrGptM4EHZm2TE4%2FTXmfZyPpzfCnLuUmUckb8wg%3D%3D; buvid_fp=5a716236853dd1e737d439882c685594; header_theme_version=CLOSE; home_feed_column=5; _uuid=15B5A2103-BBC2-9109A-7458-6410C3CF101028B94909infoc; b_lsid=CCF71993_18991563B31; b_ut=7; i-wanna-go-back=-1; innersign=0; b_nut=1690360493; buvid3=6481EDF5-10C43-9593-251E-89210B4A1C10A193894infoc" + ] + AF.request("https://passport.bilibili.com/x/passport-login/web/sms/send", method: .post, parameters: BiliSmsCodePost(cid: Int(phoneCode)!, tel: Int(accountInput)!, token: loginToken, challenge: challenge, validate: validate, seccode: seccode), headers: headers).response { response in + debugPrint(response) + let json = try! JSON(data: response.data!) + smsLoginToken = json["data"]["captcha_key"].string! + } + }, label: { + Text("获取验证码") + }) + .disabled(accountInput == "" || validate == "" || smsLoginToken != "") + SecureField("验证码", text: $passwdInput) + } header: { + Text("第三步: 验证码") + } + Section { + Button(action: { + AF.request("https://passport.bilibili.com/x/passport-login/web/login/sms", method: .post, parameters: BiliLoginPost(cid: Int(phoneCode)!, tel: Int(accountInput)!, code: Int(passwdInput)!, captcha_key: smsLoginToken)).response { response in + let data = response.data + if data != nil { + let json = try! JSON(data: data!) + debugPrint(json) + if json["code"].int == 0 { + if json["data"]["status"].int == 0 { + debugPrint(response.response!.headers) + let setCookie = response.response!.headers["Set-Cookie"]! + dedeUserID = String(setCookie.split(separator: "DedeUserID=")[1].split(separator: ";")[0]) + dedeUserID__ckMd5 = String(setCookie.split(separator: "DedeUserID__ckMd5=")[1].split(separator: ";")[0]) + if setCookie.hasPrefix("SESSDATA") { + sessdata = String(setCookie.split(separator: "SESSDATA=")[0].split(separator: ";")[0]) + } else { + sessdata = String(setCookie.split(separator: "SESSDATA=")[1].split(separator: ";")[0]) + } + biliJct = String(setCookie.split(separator: "bili_jct=")[1].split(separator: ";")[0]) + dismiss() + } else if json["data"]["status"].int == 1006 { + + } else if json["data"]["status"].int == 1007 { + + } + } + } + } + }, label: { + Text("登录") + }) + .disabled(accountInput == "" || passwdInput == "") + } + } + .tag(1) + .onOpenURL { url in + let surl = url.absoluteString.urlDecoded() + let ds = surl.components(separatedBy: "?") + if ds[0].contains("logincap") { + debugPrint(ds) + let spdF = ds[1].split(separator: "&") + validate = String(spdF[0]) + seccode = String(spdF[1]) + } + } + .onAppear { + let headers: HTTPHeaders = [ + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + DarockKit.Network.shared.requestJSON("https://passport.bilibili.com/x/passport-login/captcha?source=main_web", headers: headers) { respJson, isSuccess in + if isSuccess { + challenge = respJson["data"]["geetest"]["challenge"].string! + gt = respJson["data"]["geetest"]["gt"].string! + loginToken = respJson["data"]["token"].string! + } + } + } + } + } + + struct BiliLoginPost: Codable { + let cid: Int + let tel: Int + let code: Int + var source: String = "main_web" + let captcha_key: String + var keep: Bool = true + } + struct BiliSmsCodePost: Codable { + let cid: Int + let tel: Int + var source: String = "main_web" + let token: String + let challenge: String + let validate: String + let seccode: String + } +} + +struct LoginView_Previews: PreviewProvider { + static var previews: some View { + LoginView() + } +} diff --git a/MeowBili/Others/NoticeView.swift b/MeowBili/Others/NoticeView.swift new file mode 100644 index 000000000..88b0b9ac3 --- /dev/null +++ b/MeowBili/Others/NoticeView.swift @@ -0,0 +1,51 @@ +// +// +// NoticeView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit + +struct NoticeView: View { + @State var noticeDetail = "" + var body: some View { + List { + Text(noticeDetail) + .bold() + } + .onAppear { + DarockKit.Network.shared.requestString("https://api.darock.top/bili/notice/detail") { respStr, isSuccess in + if isSuccess { + if respStr.apiFixed() != "" { + noticeDetail = respStr.apiFixed().replacingOccurrences(of: "\\n", with: "\n") + } else { + DarockKit.Network.shared.requestString("https://api.darock.top/bili/notice") { respStr, isSuccess in + if isSuccess { + noticeDetail = respStr.apiFixed() + } + } + } + } + } + } + } +} + +struct NoticeView_Previews: PreviewProvider { + static var previews: some View { + NoticeView() + } +} diff --git a/MeowBili/Others/Passthroughs.swift b/MeowBili/Others/Passthroughs.swift new file mode 100644 index 000000000..10240a0e2 --- /dev/null +++ b/MeowBili/Others/Passthroughs.swift @@ -0,0 +1,22 @@ +// +// +// Passthroughs.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Combine +import Foundation + +public var downloadingProgressDatas = [(pts: PassthroughSubject, isFinished: Bool)]() diff --git a/MeowBili/Others/SettingsView.swift b/MeowBili/Others/SettingsView.swift new file mode 100644 index 000000000..f1280a7a0 --- /dev/null +++ b/MeowBili/Others/SettingsView.swift @@ -0,0 +1,449 @@ +// +// +// SettingsView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Charts +import SwiftUI +import SwiftDate +import DarockKit +import AuthenticationServices + +struct SettingsView: View { + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var isLogoutAlertPresented = false + var body: some View { + List { + Section { + NavigationLink(destination: {PlayerSettingsView().navigationTitle("Settings.player")}, label: { + HStack { + ZStack { + Color.gray + .frame(width: 26, height: 26) + .clipShape(RoundedRectangle(cornerRadius: 5)) + Image(systemName: "play.square") + .font(.system(size: 16)) + .foregroundColor(.white) + } + Text("Settings.player") + } + }) + NavigationLink(destination: {NetworkSettingsView().navigationTitle("Settings.internet")}, label: { + HStack { + ZStack { + Color.blue + .frame(width: 26, height: 26) + .clipShape(RoundedRectangle(cornerRadius: 5)) + Image(systemName: "network") + .font(.system(size: 16)) + } + Text("Settings.internet") + } + }) + NavigationLink(destination: {ScreenTimeSettingsView().navigationTitle("Settings.screen-time")}, label: { + HStack { + ZStack { + Color.blue + .frame(width: 26, height: 26) + .clipShape(RoundedRectangle(cornerRadius: 5)) + Image(systemName: "hourglass") + .font(.system(size: 16)) + } + Text("Settings.screen-time") + } + }) + NavigationLink(destination: {SleepTimeView().navigationTitle("Settings.sleep")}, label: { + HStack { + ZStack { + Color.cyan + .frame(width: 26, height: 26) + .clipShape(RoundedRectangle(cornerRadius: 5)) + Image(systemName: "bed.double.fill") + .font(.system(size: 14)) + } + Text("Settings.sleep") + } + }) + NavigationLink(destination: {FeedbackView().navigationTitle("Settings.feedback")}, label: { + HStack { + ZStack { + Color.purple + .frame(width: 26, height: 26) + .clipShape(RoundedRectangle(cornerRadius: 5)) + Image(systemName: "exclamationmark") + .font(.system(size: 16)) + } + Text("Settings.feedback") + } + }) + } + Section { + NavigationLink(destination: {AboutView()}, label: { + HStack { + ZStack { + Color.gray + .frame(width: 26, height: 26) + .clipShape(RoundedRectangle(cornerRadius: 5)) + Image(systemName: "info") + .font(.system(size: 16)) + } + Text("Settings.about") + } + }) + if debug { + Section { + NavigationLink(destination: {DebugMenuView().navigationTitle("Settings.debug")}, label: { + HStack { + ZStack { + Color.blue + .frame(width: 26, height: 26) + .clipShape(RoundedRectangle(cornerRadius: 5)) + Image(systemName: "hammer.fill") + .font(.system(size: 16)) + } + Text("Settings.developer") + } + }) + } + } + if !sessdata.isEmpty { + Button(role: .destructive, action: { + isLogoutAlertPresented = true + }, label: { + HStack { +// ZStack { +// Color.red +// .frame(width: 20, height: 20) +// .clipShape(Circle()) +// Image(systemName: "person.slash") +// .font(.system(size: 12)) +// } + Text("Settings.log-out") + } + }) + .buttonBorderShape(.roundedRectangle(radius: 13)) + .alert("Settings.log-out", isPresented: $isLogoutAlertPresented, actions: { + Button(role: .destructive, action: { + dedeUserID = "" + dedeUserID__ckMd5 = "" + sessdata = "" + biliJct = "" + }, label: { + HStack { + Text("Settings.log-out.confirm") + Spacer() + } + }) + }, message: { + Text("Settings.log-out.message") + }) + } + } + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.large) + } +} + +struct PlayerSettingsView: View { + @AppStorage("RecordHistoryTime") var recordHistoryTime = "into" + @AppStorage("VideoGetterSource") var videoGetterSource = "official" + var body: some View { + List { + Section { + Picker("Player.record-history", selection: $recordHistoryTime) { + Text("Player.record-history.when-entering-page").tag("into") + Text("Player.record-history.when-video-plays").tag("play") + Text("Player.record-history.never").tag("never") + } + } + Section(footer: Text("Player.analyzying-source.description")) { + Picker("Player.analyzying-source", selection: $videoGetterSource) { + Text("Player.analyzying-source.offical").tag("official") + Text("Player.analyzying-source.third-party").tag("injahow") + } + } + } + } +} + +struct NetworkSettingsView: View { + @AppStorage("IsShowNetworkFixing") var isShowNetworkFixing = true + var body: some View { + List { + Section { + NavigationLink(destination: {NetworkFixView()}, label: { + Text("Troubleshoot") + }) + Toggle("Troubleshoot.auto-pop-up", isOn: $isShowNetworkFixing) + } + } + } +} + +struct ScreenTimeSettingsView: View { + @AppStorage("IsScreenTimeEnabled") var isScreenTimeEnabled = true + @State var screenTimes = [Int]() + @State var mainBarData = [SingleTimeBarMarkData]() + @State var dayAverageTime = 0 // Minutes + var body: some View { + List { + if isScreenTimeEnabled { + Section { + VStack { + HStack { + Text("Screen-time.daily-average") + .font(.system(size: 14)) + .foregroundColor(.gray) + Spacer() + } + HStack { + Text("Screen-time.minutes.\(dayAverageTime)") + .font(.system(size: 20)) + Spacer() + } + Chart(mainBarData) { + BarMark( + x: .value("name", $0.name), + y: .value("time", $0.time) + ) + // RuleMark( + // y: .value("Highlight", dayAverageTime) + // ) + // .foregroundStyle(.green) + } + .chartYAxis { + AxisMarks(preset: .aligned, position: .trailing) { value in + AxisValueLabel("Screen-time.minutes.\(value.index)") + } + } + } + } + Section { + Button(role: .destructive, action: { + isScreenTimeEnabled = false + }, label: { + Text("Screen-time.off") + }) + } footer: { + Text("Screen-time.description") + } + } else { + Section { + Button(action: { + isScreenTimeEnabled = true + }, label: { + Text("Screen-time.on") + }) + } footer: { + Text("Screen-time.usage") + } + } + } + .onAppear { + let df = DateFormatter() + df.dateFormat = "yyyy-MM-dd" + for i in 0...6 { + let dateStr = df.string(from: Date.now - i.days) + screenTimes.append(UserDefaults.standard.integer(forKey: "ScreenTime\(dateStr)")) + let wdf = DateFormatter() + wdf.dateFormat = "EEEE" + mainBarData.append(SingleTimeBarMarkData(name: String(wdf.string(from: Date.now - i.days).last!), time: UserDefaults.standard.integer(forKey: "ScreenTime\(dateStr)") / 60)) + } + screenTimes.reverse() + mainBarData.reverse() + var totalTime = 0 + for time in screenTimes { + totalTime += time / 60 + } + dayAverageTime = totalTime / 7 + } + } + + struct SingleTimeBarMarkData: Identifiable { + let name: String + let time: Int + var id: String{ name } + } +} + +struct SleepTimeView: View { + @AppStorage("isSleepNotificationOn") var isSleepNotificationOn = false + @AppStorage("notifyHour") var notifyHour = 0 + @AppStorage("notifyMinute") var notifyMinute = 0 + @State var currentHour = 0 + @State var currentMinute = 0 + @State var currentSecond = 0 + @State var isEditingTime = false + var body: some View { + List { + Section(content: { + Toggle(isOn: $isSleepNotificationOn, label: { + Text("Sleep") + }) + if isSleepNotificationOn { + Button(action: { + isEditingTime = true + }, label: { + Text("Sleep.edit.\(notifyHour<10 ? "0\(notifyHour)" : "\(notifyHour)").\(notifyMinute<10 ? "0\(notifyMinute)" : "\(notifyMinute)")") + }) + } + }, footer: { + Text("Sleep.discription") + }) + Section { + Text("Sleep.current.\(currentHour<10 ? "0\(currentHour)" : "\(currentHour)").\(currentMinute<10 ? "0\(currentMinute)" : "\(currentMinute)").\(currentSecond<10 ? "0\(currentSecond)" : "\(currentSecond)")") + } + } + .navigationTitle("Sleep") + .onAppear { + let timer = Timer(timeInterval: 0.5, repeats: true) { timer in + currentHour = getCurrentTime().hour + currentMinute = getCurrentTime().minute + currentSecond = getCurrentTime().second + } + RunLoop.current.add(timer, forMode: .default) + timer.fire() + } + .sheet(isPresented: $isEditingTime, content: { + VStack { + Text("Sleep.edit.title") + .bold() + HStack { + Picker("Sleep.edit.hour", selection: $notifyHour) { + ForEach(0..<24) { index in + Text("\(index<10 ? "0\(index)" : "\(index)")").tag(index) + } + } + Text(":") + Picker("Sleep.edit.minute", selection: $notifyMinute) { + ForEach(0..<60) { index in + Text("\(index<10 ? "0\(index)" : "\(index)")").tag(index) + } + } + } + } + }) + } +} + +struct Time { + var hour: Int + var minute: Int + var second: Int +} + +func getCurrentTime() -> Time { + let calendar = Calendar.current + let components = calendar.dateComponents([.hour, .minute, .second], from: Date()) + let currentTime = Time(hour: components.hour ?? 0, minute: components.minute ?? 0, second: components.second ?? 0) + return currentTime +} + + + +struct DebugMenuView: View { + var body: some View { + List { + NavigationLink(destination: {UserDetailView(uid: "3546572635768935")}, label: { + Text("LongUIDUserTest") + }) + NavigationLink(destination: {BuvidFpDebug()}, label: { + Text("buvid_fpTest") + }) + NavigationLink(destination: {UuidDebug()}, label: { + Text("_uuid_Gen") + }) + NavigationLink(destination: {Buvid34Debug()}, label: { + Text("buvid3_4_actived") + }) + } + } + + struct BuvidFpDebug: View { + @State var fp = "" + @State var resu = "" + var body: some View { + List { + TextField("fp", text: $fp) + Button(action: { + do { + resu = try BuvidFp.gen(key: fp, seed: 31) + } catch { + resu = "Failed: \(error)" + } + }, label: { + Text("Gen") + }) + Text(resu) + } + } + } + struct UuidDebug: View { + @State var uuid = "" + var body: some View { + List { + Button(action: { + uuid = UuidInfoc.gen() + }, label: { + Text("Gen") + }) + Text(uuid) + } + } + } + struct Buvid34Debug: View { + @State var activeBdUrl = "https://www.bilibili.com/" + @State var locBuvid3 = "" + @State var locBuvid4 = "" + @State var locUplResp = "" + var body: some View { + List { + Section { + Text("Current Global Buvid3: \(globalBuvid3)") + Text("Current Global Buvid4: \(globalBuvid4)") + } + Section { + TextField("activeBdUrl", text: $activeBdUrl) + Button(action: { + getBuvid(url: activeBdUrl.urlEncoded()) { buvid3, buvid4, _, resp in + locBuvid3 = buvid3 + locBuvid4 = buvid4 + locUplResp = resp + } + }, label: { + Text("Get new & active") + }) + Text(locBuvid3) + Text(locBuvid4) + Text(locUplResp) + } + } + } + } +} + + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + NavigationStack { + SettingsView() + } + } +} diff --git a/MeowBili/Others/Skins/SkinChooserView.swift b/MeowBili/Others/Skins/SkinChooserView.swift new file mode 100644 index 000000000..d0d158f6e --- /dev/null +++ b/MeowBili/Others/Skins/SkinChooserView.swift @@ -0,0 +1,413 @@ +// +// +// SkinChooserView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI + +struct SkinChooserView: View { + let skinsPath = [ + "#EveOneCat": "https://darock.top/meowbili/res/skin/#EveOneCat/#EveOneCat_package.zip", + "12周年夏日狂欢": "https://darock.top/meowbili/res/skin/12周年夏日狂欢/12周年夏日狂欢_package.zip", + "2020拜年祭": "https://darock.top/meowbili/res/skin/2020拜年祭/2020拜年祭_package.zip", + "2021拜年祭": "https://darock.top/meowbili/res/skin/2021拜年祭/2021拜年祭_package.zip", + "2021最美的夜魔法": "https://darock.top/meowbili/res/skin/2021最美的夜魔法/2021最美的夜魔法_package.zip", + "2021最美的夜赛博": "https://darock.top/meowbili/res/skin/2021最美的夜赛博/2021最美的夜赛博_package.zip", + "2022拜年纪": "https://darock.top/meowbili/res/skin/2022拜年纪/2022拜年纪_package.zip", + "2022王者荣耀世冠": "https://darock.top/meowbili/res/skin/2022王者荣耀世冠/2022王者荣耀世冠_package.zip", + "2023拜年纪": "https://darock.top/meowbili/res/skin/2023拜年纪/2023拜年纪_package.zip", + "2233暗黑童话": "https://darock.top/meowbili/res/skin/#EveOneCat/2233暗黑童话_package.zip", + "2233白色情人节": "https://darock.top/meowbili/res/skin/2233白色情人节/2233白色情人节_package.zip", + "2233不思议之旅": "https://darock.top/meowbili/res/skin/2233不思议之旅/不思议之旅_package.zip", + "2233电子喵": "https://darock.top/meowbili/res/skin/2233电子喵/电子喵_package.zip", + "2233幻星集": "https://darock.top/meowbili/res/skin/2233幻星集/2233幻星集_package.zip", + "2233魔法学院": "https://darock.top/meowbili/res/skin/2233魔法学院/魔法学院.zip", + "2233人生百戏-花木兰": "https://darock.top/meowbili/res/skin/2233人生百戏-花木兰/2233人生百戏-花木兰_package.zip", + "2233人生百戏·白蛇传": "https://darock.top/meowbili/res/skin/2233人生百戏·白蛇传/2233人生百戏·白蛇传_package.zip", + "2233人生百戏·梁祝": "https://darock.top/meowbili/res/skin/2233人生百戏·梁祝/2233人生百戏·梁祝_package.zip", + "2233人生百戏·牡丹亭": "https://darock.top/meowbili/res/skin/2233人生百戏·牡丹亭/2233人生百戏·牡丹亭_package.zip", + "2233赛博朋克": "https://darock.top/meowbili/res/skin/2233赛博朋克/2233赛博朋克_package.zip", + "2233山海故事": "https://darock.top/meowbili/res/skin/2233山海故事/2233山海故事_package.zip", + "2233十一周年纪念": "https://darock.top/meowbili/res/skin/2233十一周年纪念/2233十一周年纪_package.zip", + "2233夏日冰品": "https://darock.top/meowbili/res/skin/2233夏日冰品/2233夏日冰品_package.zip", + "阿狸·冬日来信": "https://darock.top/meowbili/res/skin/阿狸·冬日来信/阿狸·冬日来信_package.zip", + "阿萨Aza": "https://darock.top/meowbili/res/skin/阿萨Aza/阿萨Aza_package.zip", + "阿萨Aza-多面体": "https://darock.top/meowbili/res/skin/阿萨Aza-多面体/阿萨Aza-多面体_package.zip", + "阿山和他的朋友们": "https://darock.top/meowbili/res/skin/阿山和他的朋友们/阿山和他的朋友们_package.zip", + "阿巳与小铃铛": "https://darock.top/meowbili/res/skin/阿巳与小铃铛/阿巳与小铃铛_package.zip", + "阿梓从小就很可爱": "https://darock.top/meowbili/res/skin/阿梓从小就很可爱/阿梓从小就很可爱_package.zip", + "阿梓从小就很可爱新装扮": "https://darock.top/meowbili/res/skin/阿梓从小就很可爱新装扮/阿梓从小就很可爱新装扮_package.zip", + "爱宠大机密·喜迎兔兔": "https://darock.top/meowbili/res/skin/爱宠大机密·喜迎兔兔/爱宠大机密·喜迎兔兔_package.zip", + "凹凸世界": "https://darock.top/meowbili/res/skin/凹凸世界/凹凸世界_package.zip", + "凹凸世界第二弹": "https://darock.top/meowbili/res/skin/凹凸世界第二弹/凹凸世界第二弹_package.zip", + "奥特曼系列": "https://darock.top/meowbili/res/skin/奥特曼系列/奥特曼系列_package.zip", + "爸妈来自二次元": "https://darock.top/meowbili/res/skin/爸妈来自二次元/爸妈来自二次元_package.zip", + "白蛇2:青蛇劫起": "https://darock.top/meowbili/res/skin/白蛇2:青蛇劫起/白蛇2:青蛇劫起_package.zip", + "白神遥Haruka": "https://darock.top/meowbili/res/skin/白神遥Haruka/白神遥Haruka_package.zip", + "百变星瞳": "https://darock.top/meowbili/res/skin/百变星瞳/百变星瞳_package.zip", + "百鬼幼儿园": "https://darock.top/meowbili/res/skin/百鬼幼儿园/百鬼幼儿园_package.zip", + "百妖谱": "https://darock.top/meowbili/res/skin/百妖谱/百妖谱_package.zip", + "百妖谱第二季": "https://darock.top/meowbili/res/skin/百妖谱第二季/百妖谱第二季_package.zip", + "绊爱第二弹": "https://darock.top/meowbili/res/skin/绊爱第二弹/绊爱第二弹_package.zip", + "贝拉个性装扮2.0": "https://darock.top/meowbili/res/skin/贝拉个性装扮2.0/贝拉个性装扮2.0_package.zip", + "贝拉kira": "https://darock.top/meowbili/res/skin/贝拉kira/贝拉kira_package.zip", + "崩坏3·光辉矢愿": "https://darock.top/meowbili/res/skin/崩坏3·光辉矢愿/崩坏3·光辉矢愿_package.zip", + "崩坏3·雷鸣彻空": "https://darock.top/meowbili/res/skin/崩坏3·雷鸣彻空/崩坏3·雷鸣彻空_package.zip", + "崩坏3·天穹流星": "https://darock.top/meowbili/res/skin/崩坏3·天穹流星/崩坏3·天穹流星_package.zip", + "崩坏3·终焉归始": "https://darock.top/meowbili/res/skin/崩坏3·终焉归始/崩坏3·终焉归始_package.zip", + "崩坏学园2": "https://darock.top/meowbili/res/skin/崩坏学园2/崩坏学园2_package.zip", + "哔哩哔哩舞蹈嘉年华": "https://darock.top/meowbili/res/skin/哔哩哔哩舞蹈嘉年华/哔哩哔哩舞蹈嘉年华_package.zip", + "碧蓝航线2020": "https://darock.top/meowbili/res/skin/碧蓝航线2020/碧蓝航线2020_package.zip", + "冰糖IO 蜕变·闪耀": "https://darock.top/meowbili/res/skin/冰糖IO 蜕变·闪耀/冰糖IO 蜕变·闪耀_package.zip", + "博柏利:无尽探索": "https://darock.top/meowbili/res/skin/博柏利:无尽探索/博柏利:无尽探索_package.zip", + "菜菜子": "https://darock.top/meowbili/res/skin/菜菜子/菜菜子_package.zip", + "草莓大福": "https://darock.top/meowbili/res/skin/草莓大福/草莓大福_package.zip", + "测不准的阿波连同学": "https://darock.top/meowbili/res/skin/测不准的阿波连同学/测不准的阿波连同学_package.zip", + "查理苏": "https://darock.top/meowbili/res/skin/查理苏/查理苏_package.zip", + "茶啊二中居居男孩日常": "https://darock.top/meowbili/res/skin/茶啊二中居居男孩日常/茶啊二中居居男孩日常_package.zip", + "茶茶龙": "https://darock.top/meowbili/res/skin/茶茶龙/茶茶龙_package.zip", + "沉默寡言白河愁": "https://darock.top/meowbili/res/skin/沉默寡言白河愁/沉默寡言白河愁_package.zip", + "出游季": "https://darock.top/meowbili/res/skin/出游季/出游季_package.zip", + "初音未来-日版": "https://darock.top/meowbili/res/skin/初音未来-日版/初音未来-日版_package.zip", + "初音未来-夜版": "https://darock.top/meowbili/res/skin/初音未来-夜版/初音未来-夜版_package.zip", + "初音未来圣诞快乐": "https://darock.top/meowbili/res/skin/初音未来圣诞快乐/初音未来圣诞快乐_package.zip", + "初音未来周年纪念": "https://darock.top/meowbili/res/skin/初音未来周年纪念/初音未来周年纪念_package.zip", + "初音未来V4C五周年": "https://darock.top/meowbili/res/skin/初音未来V4C五周年/初音未来V4C五周年_package.zip", + "穿越西元3000后": "https://darock.top/meowbili/res/skin/穿越西元3000后/穿越西元3000后_package.zip", + "传武": "https://darock.top/meowbili/res/skin/传武/传武_package.zip", + "椎名菜羽": "https://darock.top/meowbili/res/skin/椎名菜羽/椎名菜羽_package.zip", + "春季特辑·二月律动": "https://darock.top/meowbili/res/skin/春季特辑·二月律动/春季特辑·二月律动_package.zip", + "春季特辑·鹿鹿套装": "https://darock.top/meowbili/res/skin/春季特辑·鹿鹿套装/春季特辑·鹿鹿套装_package.zip", + "春季特辑·暖春双人游": "https://darock.top/meowbili/res/skin/春季特辑·暖春双人游/春季特辑·暖春双人游_package.zip", + "春节许愿": "https://darock.top/meowbili/res/skin/春节许愿/春节许愿_package.zip", + "春日限定花园蜜语": "https://darock.top/meowbili/res/skin/春日限定花园蜜语/春日限定花园蜜语_package.zip", + "刺客信条15周年": "https://darock.top/meowbili/res/skin/刺客信条15周年/刺客信条15周年_package.zip", + "湊-阿库娅": "https://darock.top/meowbili/res/skin/湊-阿库娅/湊-阿库娅_package.zip", + "大炒面制造者CEN": "https://darock.top/meowbili/res/skin/大炒面制造者CEN/大炒面制造者CEN_package.zip", + "大航海韩小沐": "https://darock.top/meowbili/res/skin/大航海韩小沐/大航海韩小沐_package.zip", + "大航海嘉然": "https://darock.top/meowbili/res/skin/大航海嘉然/大航海嘉然_package.zip", + "大航海舰长": "https://darock.top/meowbili/res/skin/大航海舰长/大航海舰长_package.zip", + "大航海提督": "https://darock.top/meowbili/res/skin/大航海提督/大航海提督_package.zip", + "大航海逍遥散人": "https://darock.top/meowbili/res/skin/大航海逍遥散人/大航海逍遥散人_package.zip", + "大航海总督": "https://darock.top/meowbili/res/skin/大航海总督/大航海总督_package.zip", + "大会员5周年": "https://darock.top/meowbili/res/skin/大会员5周年/大会员5周年_package.zip", + "大理寺日志": "https://darock.top/meowbili/res/skin/大理寺日志/大理寺日志_package.zip", + "大王不高兴": "https://darock.top/meowbili/res/skin/大王不高兴/大王不高兴_package.zip", + "呆呆小可爱": "https://darock.top/meowbili/res/skin/呆呆小可爱/呆呆小可爱_package.zip", + "蛋酒一家": "https://darock.top/meowbili/res/skin/蛋酒一家/蛋酒一家_package.zip", + "登乐V计划": "https://darock.top/meowbili/res/skin/登乐V计划/登乐V计划_package.zip", + "第五人格": "https://darock.top/meowbili/res/skin/第五人格/第五人格_package.zip", + "东爱璃Lovely": "https://darock.top/meowbili/res/skin/东爱璃Lovely/东爱璃Lovely_package.zip", + "冬日颂歌欧皇套装": "https://darock.top/meowbili/res/skin/冬日颂歌欧皇套装/冬日颂歌欧皇套装_package.zip", + "東雪蓮": "https://darock.top/meowbili/res/skin/東雪蓮/東雪蓮_package.zip", + "杜松子_Gin": "https://darock.top/meowbili/res/skin/杜松子_Gin/杜松子_Gin_package.zip", + "多多poi": "https://darock.top/meowbili/res/skin/多多poi/多多poi_package.zip", + "凡人修仙传": "https://darock.top/meowbili/res/skin/凡人修仙传/凡人修仙传_package.zip", + "饭粒猫与包子鸭": "https://darock.top/meowbili/res/skin/饭粒猫与包子鸭/饭粒猫与包子鸭_package.zip", + "非人哉": "https://darock.top/meowbili/res/skin/非人哉/非人哉_package.zip", + "绯赤艾莉欧": "https://darock.top/meowbili/res/skin/绯赤艾莉欧/绯赤艾莉欧_package.zip", + "肥肥鲨": "https://darock.top/meowbili/res/skin/肥肥鲨/肥肥鲨_package.zip", + "肥肥鲨打工人": "https://darock.top/meowbili/res/skin/肥肥鲨打工人/肥肥鲨打工人_package.zip", + "粉红兔子": "https://darock.top/meowbili/res/skin/粉红兔子/粉红兔子_package.zip", + "疯狂兔子宇宙博物馆": "https://darock.top/meowbili/res/skin/疯狂兔子宇宙博物馆/疯狂兔子宇宙博物馆_package.zip", + "扶桑大红花": "https://darock.top/meowbili/res/skin/扶桑大红花/扶桑大红花_package.zip", + "干物妹!小埋": "https://darock.top/meowbili/res/skin/干物妹!小埋/干物妹!小埋_package.zip", + "高能手办团": "https://darock.top/meowbili/res/skin/高能手办团/高能手办团_package.zip", + "工作细胞": "https://darock.top/meowbili/res/skin/工作细胞/工作细胞_package.zip", + "古守血遊": "https://darock.top/meowbili/res/skin/古守血遊/古守血遊_package.zip", + "怪盗基德": "https://darock.top/meowbili/res/skin/怪盗基德/怪盗基德_package.zip", + "莞儿睡不醒": "https://darock.top/meowbili/res/skin/莞儿睡不醒/莞儿睡不醒_package.zip", + "桂客盈门": "https://darock.top/meowbili/res/skin/桂客盈门/桂客盈门_package.zip", + "还有醒着的么": "https://darock.top/meowbili/res/skin/还有醒着的么/还有醒着的么_package.zip", + "还有醒着的么2.0": "https://darock.top/meowbili/res/skin/还有醒着的么2.0/还有醒着的么2.0_package.zip", + "海伊": "https://darock.top/meowbili/res/skin/海伊/海伊_package.zip", + "黑潮之上": "https://darock.top/meowbili/res/skin/黑潮之上/黑潮之上_package.zip", + "黑猫大少爷": "https://darock.top/meowbili/res/skin/黑猫大少爷/黑猫大少爷_package.zip", + "黑门": "https://darock.top/meowbili/res/skin/黑门/黑门_package.zip", + "黑鸦鸦": "https://darock.top/meowbili/res/skin/黑鸦鸦/黑鸦鸦_package.zip", + "黑泽诺亚NOIR": "https://darock.top/meowbili/res/skin/黑泽诺亚NOIR/黑泽诺亚NOIR_package.zip", + "黑之召唤士": "https://darock.top/meowbili/res/skin/黑之召唤士/黑之召唤士_package.zip", + "胡桃Usa": "https://darock.top/meowbili/res/skin/胡桃Usa/胡桃Usa_package.zip", + "虎皮喵一家": "https://darock.top/meowbili/res/skin/虎皮喵一家/虎皮喵一家_package.zip", + "花花雪精灵": "https://darock.top/meowbili/res/skin/花花雪精灵/花花雪精灵_package.zip", + "花卷羊": "https://darock.top/meowbili/res/skin/花卷羊/花卷羊_package.zip", + "花丸晴琉": "https://darock.top/meowbili/res/skin/花丸晴琉/花丸晴琉_package.zip", + "花园Serena": "https://darock.top/meowbili/res/skin/花园Serena/花园Serena_package.zip", + "幻塔": "https://darock.top/meowbili/res/skin/幻塔/幻塔_package.zip", + "黄绿合战5th": "https://darock.top/meowbili/res/skin/黄绿合战5th/黄绿合战5th_package.zip", + "黄油小狗": "https://darock.top/meowbili/res/skin/黄油小狗/黄油小狗_package.zip", + "姬拉Kira": "https://darock.top/meowbili/res/skin/姬拉Kira/姬拉Kira_package.zip", + "珈乐Carol": "https://darock.top/meowbili/res/skin/珈乐Carol/珈乐Carol_package.zip", + "嘉然个性装扮2.0": "https://darock.top/meowbili/res/skin/嘉然个性装扮2.0/嘉然个性装扮2.0_package.zip", + "嘉然今天吃什么": "https://darock.top/meowbili/res/skin/嘉然今天吃什么/嘉然今天吃什么_package.zip", + "剑网3·侠肝义胆沈剑心": "https://darock.top/meowbili/res/skin/剑网3·侠肝义胆沈剑心/剑网3·侠肝义胆沈剑心_package.zip", + "剑与远征": "https://darock.top/meowbili/res/skin/剑与远征/剑与远征_package.zip", + "今天鸽子球咕咕了吗": "https://darock.top/meowbili/res/skin/今天鸽子球咕咕了吗/今天鸽子球咕咕了吗_package.zip", + "进化之实": "https://darock.top/meowbili/res/skin/进化之实/进化之实_package.zip", + "进化之实 踏上胜利的人生": "https://darock.top/meowbili/res/skin/进化之实 踏上胜利的人生/进化之实 踏上胜利的人生_package.zip", + "进击的冰糖": "https://darock.top/meowbili/res/skin/进击的冰糖/进击的冰糖_package.zip", + "旌旗卷平川": "https://darock.top/meowbili/res/skin/旌旗卷平川/旌旗卷平川_package.zip", + "镜音铃·连": "https://darock.top/meowbili/res/skin/镜音铃·连/镜音铃·连_package.zip", + "九重紫": "https://darock.top/meowbili/res/skin/九重紫/九重紫_package.zip", + "决战!平安京-灯影戏梦": "https://darock.top/meowbili/res/skin/决战!平安京-灯影戏梦/决战!平安京-灯影戏梦_package.zip", + "君有云": "https://darock.top/meowbili/res/skin/君有云/君有云_package.zip", + "咖喱饭": "https://darock.top/meowbili/res/skin/咖喱饭/咖喱饭_package.zip", + "卡慕SaMa": "https://darock.top/meowbili/res/skin/卡慕SaMa/卡慕SaMa_package.zip", + "坎公骑冠剑": "https://darock.top/meowbili/res/skin/坎公骑冠剑/坎公骑冠剑_package.zip", + "靠你啦!战神系统": "https://darock.top/meowbili/res/skin/靠你啦!战神系统/靠你啦!战神系统_package.zip", + "柯南万圣节的新娘": "https://darock.top/meowbili/res/skin/柯南万圣节的新娘/柯南万圣节的新娘_package.zip", + "可爱联盟-新年特辑": "https://darock.top/meowbili/res/skin/可爱联盟-新年特辑/可爱联盟-新年特辑_package.zip", + "可爱团子": "https://darock.top/meowbili/res/skin/可爱团子/可爱团子_package.zip", + "瀬兎一也": "https://darock.top/meowbili/res/skin/瀬兎一也/瀬兎一也_package.zip", + "兰音Reine": "https://darock.top/meowbili/res/skin/兰音Reine/兰音Reine_package.zip", + "蓝色时期": "https://darock.top/meowbili/res/skin/蓝色时期/蓝色时期_package.zip", + "蓝溪镇": "https://darock.top/meowbili/res/skin/蓝溪镇/蓝溪镇_package.zip", + "懒懒兔": "https://darock.top/meowbili/res/skin/懒懒兔/懒懒兔_package.zip", + "狼行者": "https://darock.top/meowbili/res/skin/狼行者/狼行者_package.zip", + "老E": "https://darock.top/meowbili/res/skin/老E/老E_package.zip", + "乐正绫五周年纪念": "https://darock.top/meowbili/res/skin/乐正绫五周年纪念/乐正绫五周年纪念_package.zip", + "乐正龙牙": "https://darock.top/meowbili/res/skin/乐正龙牙/乐正龙牙_package.zip", + "蕾尔娜Leona": "https://darock.top/meowbili/res/skin/蕾尔娜Leona/蕾尔娜Leona_package.zip", + "蕾蕾大表哥": "https://darock.top/meowbili/res/skin/蕾蕾大表哥/蕾蕾大表哥_package.zip", + "冷兔宝宝": "https://darock.top/meowbili/res/skin/冷兔宝宝/冷兔宝宝_package.zip", + "狸喵唤太子": "https://darock.top/meowbili/res/skin/狸喵唤太子/狸喵唤太子_package.zip", + "例行纵火": "https://darock.top/meowbili/res/skin/例行纵火/例行纵火_package.zip", + "炼气练了3000年": "https://darock.top/meowbili/res/skin/炼气练了3000年/炼气练了3000年_package.zip", + "恋乃夜舞": "https://darock.top/meowbili/res/skin/恋乃夜舞/恋乃夜舞_package.zip", + "良辰美景·不问天": "https://darock.top/meowbili/res/skin/良辰美景·不问天/良辰美景·不问天_package.zip", + "两不疑": "https://darock.top/meowbili/res/skin/两不疑/两不疑_package.zip", + "两不疑第二季": "https://darock.top/meowbili/res/skin/两不疑第二季/两不疑第二季_package.zip", + "两米兔和两斤兔": "https://darock.top/meowbili/res/skin/两米兔和两斤兔/两米兔和两斤兔_package.zip", + "量子少年-慕宇": "https://darock.top/meowbili/res/skin/量子少年-慕宇/量子少年-慕宇_package.zip", + "烈火浇愁": "https://darock.top/meowbili/res/skin/烈火浇愁/烈火浇愁_package.zip", + "灵魂潮汐周年庆典": "https://darock.top/meowbili/res/skin/灵魂潮汐周年庆典/灵魂潮汐周年庆典_package.zip", + "泠鸢登门喜鹊": "https://darock.top/meowbili/res/skin/泠鸢登门喜鹊/泠鸢登门喜鹊_package.zip", + "泠鸢yousa": "https://darock.top/meowbili/res/skin/泠鸢yousa/泠鸢yousa_package.zip", + "泠鸢yousa十周年": "https://darock.top/meowbili/res/skin/泠鸢yousa十周年/泠鸢yousa十周年_package.zip", + "铃宫铃": "https://darock.top/meowbili/res/skin/铃宫铃/铃宫铃_package.zip", + "琉绮Ruki": "https://darock.top/meowbili/res/skin/琉绮Ruki/琉绮Ruki_package.zip", + "陆沉": "https://darock.top/meowbili/res/skin/陆沉/陆沉_package.zip", + "陆夫人的Flag园": "https://darock.top/meowbili/res/skin/陆夫人的Flag园/陆夫人的Flag园_package.zip", + "陆鳐LuLu": "https://darock.top/meowbili/res/skin/陆鳐LuLu/陆鳐LuLu_package.zip", + "鹿乃": "https://darock.top/meowbili/res/skin/鹿乃/鹿乃_package.zip", + "鹿乃桜帆": "https://darock.top/meowbili/res/skin/鹿乃桜帆/鹿乃桜帆_package.zip", + "露早GOGO": "https://darock.top/meowbili/res/skin/露早GOGO/露早GOGO_package.zip", + "罗小黑战记": "https://darock.top/meowbili/res/skin/罗小黑战记/罗小黑战记_package.zip", + "罗伊": "https://darock.top/meowbili/res/skin/罗伊/罗伊_package.zip", + "洛少爷": "https://darock.top/meowbili/res/skin/洛少爷/洛少爷_package.zip", + "洛天依·最美的夜": "https://darock.top/meowbili/res/skin/洛天依·最美的夜/洛天依·最美的夜_package.zip", + "洛天依8th生日纪念": "https://darock.top/meowbili/res/skin/洛天依8th生日纪念/洛天依8th生日纪念_package.zip", + "洛天依9th生日纪念": "https://darock.top/meowbili/res/skin/洛天依9th生日纪念/洛天依9th生日纪念_package.zip", + "洛天依十周年": "https://darock.top/meowbili/res/skin/洛天依十周年/洛天依十周年_package.zip", + "马里奥红叔": "https://darock.top/meowbili/res/skin/马里奥红叔/马里奥红叔_package.zip", + "猫不理咖啡": "https://darock.top/meowbili/res/skin/猫不理咖啡/猫不理咖啡_package.zip", + "猫雷Nyaru": "https://darock.top/meowbili/res/skin/猫雷Nyaru/猫雷Nyaru_package.zip", + "猫灵相册": "https://darock.top/meowbili/res/skin/猫灵相册/猫灵相册_package.zip", + "猫诺装扮套装": "https://darock.top/meowbili/res/skin/猫诺装扮套装/猫诺装扮套装_package.zip", + "猫之茗": "https://darock.top/meowbili/res/skin/猫之茗/猫之茗_package.zip", + "美波七海": "https://darock.top/meowbili/res/skin/美波七海/美波七海_package.zip", + "美月もも": "https://darock.top/meowbili/res/skin/美月もも/美月もも_package.zip", + "美妆妖精COSELF": "https://darock.top/meowbili/res/skin/美妆妖精COSELF/美妆妖精COSELF_package.zip", + "妹子与科学": "https://darock.top/meowbili/res/skin/妹子与科学/妹子与科学_package.zip", + "萌宠狗狗": "https://darock.top/meowbili/res/skin/萌宠狗狗/萌宠狗狗_package.zip", + "萌宠橘猫": "https://darock.top/meowbili/res/skin/萌宠橘猫/萌宠橘猫_package.zip", + "萌宠小兔": "https://darock.top/meowbili/res/skin/萌宠小兔/萌宠小兔_package.zip", + "萌宠小熊": "https://darock.top/meowbili/res/skin/萌宠小熊/萌宠小熊_package.zip", + "萌节六周年装扮": "https://darock.top/meowbili/res/skin/萌节六周年装扮/萌节六周年装扮_package.zip", + "萌妹": "https://darock.top/meowbili/res/skin/萌妹/萌妹_package.zip", + "萌妻食神": "https://darock.top/meowbili/res/skin/萌妻食神/萌妻食神_package.zip", + "萌妻食神2": "https://darock.top/meowbili/res/skin/萌妻食神2/萌妻食神2_package.zip", + "梦音茶糯": "https://darock.top/meowbili/res/skin/梦音茶糯/梦音茶糯_package.zip", + "米洛与米姐姐的冒险": "https://darock.top/meowbili/res/skin/米洛与米姐姐的冒险/米洛与米姐姐的冒险_package.zip", + "米诺高分少女": "https://darock.top/meowbili/res/skin/米诺高分少女/米诺高分少女_package.zip", + "喵铃铛": "https://darock.top/meowbili/res/skin/喵铃铛/喵铃铛_package.zip", + "喵铃铛 · 轻松一刻": "https://darock.top/meowbili/res/skin/喵铃铛 · 轻松一刻/喵铃铛 · 轻松一刻_package.zip", + "咩栗": "https://darock.top/meowbili/res/skin/咩栗/咩栗_package.zip", + "明前奶绿": "https://darock.top/meowbili/res/skin/明前奶绿/明前奶绿_package.zip", + "明日方舟": "https://darock.top/meowbili/res/skin/明日方舟/明日方舟_package.zip", + "明日方舟-灯下定影": "https://darock.top/meowbili/res/skin/明日方舟-灯下定影/明日方舟-灯下定影_package.zip", + "冥冥meichan": "https://darock.top/meowbili/res/skin/冥冥meichan/冥冥meichan_package.zip", + "魔道祖师动画": "https://darock.top/meowbili/res/skin/魔道祖师动画/魔道祖师动画_package.zip", + "魔法美少女ZC": "https://darock.top/meowbili/res/skin/魔法美少女ZC/魔法美少女ZC_package.zip", + "魔法学院": "https://darock.top/meowbili/res/skin/魔法学院/魔法学院_package.zip", + "魔狼咪莉娅": "https://darock.top/meowbili/res/skin/魔狼咪莉娅/魔狼咪莉娅_package.zip", + "姆明": "https://darock.top/meowbili/res/skin/姆明/姆明_package.zip", + "牧场少女谬可可": "https://darock.top/meowbili/res/skin/牧场少女谬可可/牧场少女谬可可_package.zip", + "幕末替身传说": "https://darock.top/meowbili/res/skin/幕末替身传说/幕末替身传说_package.zip", + "穆小泠": "https://darock.top/meowbili/res/skin/穆小泠/穆小泠_package.zip", + "雫るる": "https://darock.top/meowbili/res/skin/雫るる/雫るる_package.zip", + "雫るる制服ver": "https://darock.top/meowbili/res/skin/雫るる制服ver/雫るる制服ver._package.zip", + "纳豆 nado": "https://darock.top/meowbili/res/skin/纳豆 nado/纳豆 nado_package.zip", + "乃琳个性装扮2.0": "https://darock.top/meowbili/res/skin/乃琳个性装扮2.0/乃琳个性装扮2.0_package.zip", + "乃琳Queen": "https://darock.top/meowbili/res/skin/乃琳Queen/乃琳Queen_package.zip", + "奶油兔": "https://darock.top/meowbili/res/skin/奶油兔/奶油兔_package.zip", + "奈姬niki": "https://darock.top/meowbili/res/skin/奈姬niki/奈姬niki_package.zip", + "奈奈莉娅": "https://darock.top/meowbili/res/skin/奈奈莉娅/奈奈莉娅_package.zip", + "尼奈": "https://darock.top/meowbili/res/skin/尼奈/尼奈_package.zip", + "暖雪": "https://darock.top/meowbili/res/skin/暖雪/暖雪_package.zip", + "偶像梦幻祭2": "https://darock.top/meowbili/res/skin/偶像梦幻祭2/偶像梦幻祭2_package.zip", + "帕里": "https://darock.top/meowbili/res/skin/帕里/帕里_package.zip", + "配音演员小N": "https://darock.top/meowbili/res/skin/配音演员小N/配音演员小N_package.zip", + "七海地雷套装": "https://darock.top/meowbili/res/skin/七海地雷套装/七海地雷套装_package.zip", + "七海演唱会": "https://darock.top/meowbili/res/skin/七海演唱会/七海演唱会_package.zip", + "七海Nana7mi": "https://darock.top/meowbili/res/skin/七海Nana7mi/七海Nana7mi_package.zip", + "七濑胡桃": "https://darock.top/meowbili/res/skin/七濑胡桃/七濑胡桃_package.zip", + "齐司礼": "https://darock.top/meowbili/res/skin/齐司礼/齐司礼_package.zip", + "千从狩": "https://darock.top/meowbili/res/skin/千从狩/千从狩_package.zip", + "切茜娅Chelsea": "https://darock.top/meowbili/res/skin/切茜娅Chelsea/切茜娅Chelsea_package.zip", + "秦淮八艳-小宛": "https://darock.top/meowbili/res/skin/秦淮八艳-小宛/秦淮八艳-小宛_package.zip", + "轻视频羊咩咩": "https://darock.top/meowbili/res/skin/轻视频羊咩咩/轻视频羊咩咩_package.zip", + "全职高手": "https://darock.top/meowbili/res/skin/全职高手/全职高手_package.zip", + "入幕之臣": "https://darock.top/meowbili/res/skin/入幕之臣/入幕之臣_package.zip", + "三周年恋曲": "https://darock.top/meowbili/res/skin/三周年恋曲/三周年恋曲_package.zip", + "扇宝": "https://darock.top/meowbili/res/skin/扇宝/扇宝_package.zip", + "神都夜行录": "https://darock.top/meowbili/res/skin/神都夜行录/神都夜行录_package.zip", + "神乐七奈": "https://darock.top/meowbili/res/skin/神乐七奈/神乐七奈_package.zip", + "神楽Mea": "https://darock.top/meowbili/res/skin/神楽Mea/神楽Mea_package.zip", + "时空之隙": "https://darock.top/meowbili/res/skin/时空之隙/时空之隙_package.zip", + "拾忆长安·明月几时有": "https://darock.top/meowbili/res/skin/拾忆长安·明月几时有/拾忆长安·明月几时有_package.zip", + "食草老龙": "https://darock.top/meowbili/res/skin/食草老龙/食草老龙_package.zip", + "双生幻想": "https://darock.top/meowbili/res/skin/双生幻想/双生幻想_package.zip", + "双生视界·花嫁": "https://darock.top/meowbili/res/skin/双生视界·花嫁/双生视界·花嫁_package.zip", + "斯特拉和戴安娜": "https://darock.top/meowbili/res/skin/斯特拉和戴安娜/斯特拉和戴安娜_package.zip", + "塔克": "https://darock.top/meowbili/res/skin/塔克/塔克_package.zip", + "泰蕾莎": "https://darock.top/meowbili/res/skin/泰蕾莎/泰蕾莎_package.zip", + "汤圆酱": "https://darock.top/meowbili/res/skin/汤圆酱/汤圆酱_package.zip", + "桃桃喵和荔枝喵": "https://darock.top/meowbili/res/skin/桃桃喵和荔枝喵/桃桃喵和荔枝喵_package.zip", + "天宝伏妖录": "https://darock.top/meowbili/res/skin/天宝伏妖录/天宝伏妖录_package.zip", + "天官赐福·花怜生日快乐": "https://darock.top/meowbili/res/skin/天官赐福·花怜生日快乐/天官赐福·花怜生日快乐_package.zip", + "天官赐福动画": "https://darock.top/meowbili/res/skin/天官赐福动画/天官赐福动画_package.zip", + "天气愈报": "https://darock.top/meowbili/res/skin/天气愈报/天气愈报_package.zip", + "天涯明月刀从龙": "https://darock.top/meowbili/res/skin/天涯明月刀从龙/天涯明月刀从龙_package.zip", + "天涯明月刀伙伴": "https://darock.top/meowbili/res/skin/天涯明月刀伙伴/天涯明月刀伙伴_package.zip", + "天涯明月刀移花": "https://darock.top/meowbili/res/skin/天涯明月刀移花/天涯明月刀移花_package.zip", + "天曰小雏圣诞": "https://darock.top/meowbili/res/skin/天曰小雏圣诞/天曰小雏圣诞_package.zip", + "田中姬铃木雏": "https://darock.top/meowbili/res/skin/田中姬铃木雏/田中姬铃木雏_package.zip", + "甜心nono狗": "https://darock.top/meowbili/res/skin/甜心nono狗/甜心nono狗_package.zip", + "童话系列·阿拉丁": "https://darock.top/meowbili/res/skin/童话系列·阿拉丁/童话系列·阿拉丁_package.zip", + "童话系列·胡桃夹子": "https://darock.top/meowbili/res/skin/童话系列·胡桃夹子/童话系列·胡桃夹子_package.zip", + "童话系列·豌豆公主": "https://darock.top/meowbili/res/skin/童话系列·豌豆公主/童话系列·豌豆公主_package.zip", + "兔年吉祥東雪蓮": "https://darock.top/meowbili/res/skin/兔年吉祥東雪蓮/兔年吉祥東雪蓮_package.zip", + "兔崽大长腿": "https://darock.top/meowbili/res/skin/兔崽大长腿/兔崽大长腿_package.zip", + "兔崽一米八": "https://darock.top/meowbili/res/skin/兔崽一米八/兔崽一米八_package.zip", + "团团猫": "https://darock.top/meowbili/res/skin/团团猫/团团猫_package.zip", + "团团奇米莫": "https://darock.top/meowbili/res/skin/团团奇米莫/团团奇米莫_package.zip", + "万圣街": "https://darock.top/meowbili/res/skin/万圣街/万圣街_package.zip", + "王牌御史": "https://darock.top/meowbili/res/skin/王牌御史/王牌御史_package.zip", + "忘川 · 旌旗卷平川": "https://darock.top/meowbili/res/skin/忘川 · 旌旗卷平川/忘川 · 旌旗卷平川_package.zip", + "未来有你5周年": "https://darock.top/meowbili/res/skin/未来有你5周年/未来有你5周年_package.zip", + "我开动物园那些年": "https://darock.top/meowbili/res/skin/我开动物园那些年/我开动物园那些年_package.zip", + "我是大神仙": "https://darock.top/meowbili/res/skin/我是大神仙/我是大神仙_package.zip", + "呜米": "https://darock.top/meowbili/res/skin/呜米/呜米_package.zip", + "吾皇巴扎黑": "https://darock.top/meowbili/res/skin/吾皇巴扎黑/吾皇巴扎黑_package.zip", + "五橙喵 · 玉兔迎春": "https://darock.top/meowbili/res/skin/五橙喵 · 玉兔迎春/五橙喵 · 玉兔迎春_package.zip", + "武炼巅峰": "https://darock.top/meowbili/res/skin/武炼巅峰/武炼巅峰_package.zip", + "物述有栖": "https://darock.top/meowbili/res/skin/物述有栖/物述有栖_package.zip", + "雾山五行": "https://darock.top/meowbili/res/skin/雾山五行/雾山五行_package.zip", + "希萝Hiiro": "https://darock.top/meowbili/res/skin/希萝Hiiro/希萝Hiiro_package.zip", + "希月萌奈": "https://darock.top/meowbili/res/skin/希月萌奈/希月萌奈_package.zip", + "夏鸣星": "https://darock.top/meowbili/res/skin/夏鸣星/夏鸣星_package.zip", + "夏诺雅": "https://darock.top/meowbili/res/skin/夏诺雅/夏诺雅_package.zip", + "仙王的日常生活": "https://darock.top/meowbili/res/skin/仙王的日常生活/仙王的日常生活_package.zip", + "仙王的日常生活第二弹": "https://darock.top/meowbili/res/skin/仙王的日常生活第二弹/仙王的日常生活第二弹_package.zip", + "向晚大魔王": "https://darock.top/meowbili/res/skin/向晚大魔王/向晚大魔王_package.zip", + "向晚个性装扮2.0": "https://darock.top/meowbili/res/skin/向晚个性装扮2.0/向晚个性装扮2.0_package.zip", + "小可学妹": "https://darock.top/meowbili/res/skin/小可学妹/小可学妹_package.zip", + "小柔Channel": "https://darock.top/meowbili/res/skin/小柔Channel/小柔Channel_package.zip", + "小希小桃": "https://darock.top/meowbili/res/skin/小希小桃/小希小桃_package.zip", + "小小约yoo": "https://darock.top/meowbili/res/skin/小小约yoo/小小约yoo_package.zip", + "小熊奶黄包": "https://darock.top/meowbili/res/skin/小熊奶黄包/小熊奶黄包_package.zip", + "小丫丫": "https://darock.top/meowbili/res/skin/小丫丫/小丫丫_package.zip", + "新科娘": "https://darock.top/meowbili/res/skin/新科娘/新科娘_package.zip", + "新神榜:杨戬": "https://darock.top/meowbili/res/skin/新神榜:杨戬/新神榜:杨戬_package.zip", + "星宮汐": "https://darock.top/meowbili/res/skin/星宮汐/星宮汐_package.zip", + "星律动": "https://darock.top/meowbili/res/skin/星律动/星律动_package.zip", + "星瞳": "https://darock.top/meowbili/res/skin/星瞳/星瞳_package.zip", + "星汐seki": "https://darock.top/meowbili/res/skin/星汐seki/星汐seki_package.zip", + "雪狐桑": "https://darock.top/meowbili/res/skin/雪狐桑/雪狐桑_package.zip", + "雪未来": "https://darock.top/meowbili/res/skin/雪未来/雪未来_package.zip", + "巡音流歌": "https://darock.top/meowbili/res/skin/巡音流歌/巡音流歌_package.zip", + "言和7th生日纪念": "https://darock.top/meowbili/res/skin/言和7th生日纪念/言和7th生日纪念_package.zip", + "异想少女": "https://darock.top/meowbili/res/skin/异想少女/异想少女_package.zip", + "银河之心": "https://darock.top/meowbili/res/skin/银河之心/银河之心_package.zip", + "银杏鎏金": "https://darock.top/meowbili/res/skin/银杏鎏金/银杏鎏金_package.zip", + "永雏塔菲": "https://darock.top/meowbili/res/skin/永雏塔菲/永雏塔菲_package.zip", + "永雏塔菲·1883": "https://darock.top/meowbili/res/skin/永雏塔菲·1883/永雏塔菲·1883_package.zip", + "有栖mana": "https://darock.top/meowbili/res/skin/有栖mana/有栖mana_package.zip", + "柚恩不加糖": "https://darock.top/meowbili/res/skin/柚恩不加糖/柚恩不加糖_package.zip", + "虞莫MOMO": "https://darock.top/meowbili/res/skin/虞莫MOMO/虞莫MOMO_package.zip", + "早稻叽": "https://darock.top/meowbili/res/skin/早稻叽/早稻叽_package.zip", + "早稻叽潮妹": "https://darock.top/meowbili/res/skin/早稻叽潮妹/早稻叽潮妹_package.zip", + "早见咲": "https://darock.top/meowbili/res/skin/早见咲/早见咲_package.zip", + "战双帕弥什": "https://darock.top/meowbili/res/skin/战双帕弥什/战双帕弥什_package.zip", + "战双帕弥什·夏日派对": "https://darock.top/meowbili/res/skin/战双帕弥什·夏日派对/战双帕弥什·夏日派对_package.zip", + "张京华": "https://darock.top/meowbili/res/skin/张京华/张京华_package.zip", + "长草颜团子": "https://darock.top/meowbili/res/skin/长草颜团子/长草颜团子_package.zip", + "眞白花音": "https://darock.top/meowbili/res/skin/眞白花音/眞白花音_package.zip", + "装扮小姐姐·梦幻冬季": "https://darock.top/meowbili/res/skin/装扮小姐姐·梦幻冬季/装扮小姐姐·梦幻冬季_package.zip", + "装扮小姐姐·偶像舞台": "https://darock.top/meowbili/res/skin/装扮小姐姐·偶像舞台/装扮小姐姐·偶像舞台_package.zip", + "装扮小姐姐·秋日午后": "https://darock.top/meowbili/res/skin/装扮小姐姐·秋日午后/装扮小姐姐·秋日午后_package.zip", + "装扮小姐姐红墙踏雪": "https://darock.top/meowbili/res/skin/装扮小姐姐红墙踏雪/装扮小姐姐红墙踏雪_package.zip", + "装扮小姐姐梦幻冬季": "https://darock.top/meowbili/res/skin/装扮小姐姐梦幻冬季/装扮小姐姐梦幻冬季_package.zip", + "adogsama秋": "https://darock.top/meowbili/res/skin/adogsama秋/adogsama秋_package.zip", + "Akie秋绘": "https://darock.top/meowbili/res/skin/Akie秋绘/Akie秋绘_package.zip", + "C酱兔兔纪念装扮": "https://darock.top/meowbili/res/skin/C酱兔兔纪念装扮/C酱兔兔纪念装扮_package.zip", + "C酱です": "https://darock.top/meowbili/res/skin/C酱です/C酱です_package.zip", + "hanser": "https://darock.top/meowbili/res/skin/hanser/hanser_package.zip", + "hanser动物套装": "https://darock.top/meowbili/res/skin/hanser动物套装/hanser动物套装_package.zip", + "Hiiro二周年": "https://darock.top/meowbili/res/skin/Hiiro二周年/Hiiro二周年_package.zip", + "Ike Eveland": "https://darock.top/meowbili/res/skin/Ike Eveland/Ike Eveland_package.zip", + "Luca": "https://darock.top/meowbili/res/skin/Luca/Luca_package.zip", + "M木糖M": "https://darock.top/meowbili/res/skin/M木糖M/M木糖M_package.zip", + "MEIKO": "https://darock.top/meowbili/res/skin/MEIKO/MEIKO_package.zip", + "Mysta Rias": "https://darock.top/meowbili/res/skin/Mysta Rias/Mysta Rias_package.zip", + "NoWorld": "https://darock.top/meowbili/res/skin/NoWorld/NoWorld_package.zip", + "shoto": "https://darock.top/meowbili/res/skin/shoto/shoto_package.zip", + "Shu Yamino": "https://darock.top/meowbili/res/skin/Shu Yamino/Shu Yamino_package.zip", + "Uzi": "https://darock.top/meowbili/res/skin/Uzi/Uzi_package.zip", + "V我套餐": "https://darock.top/meowbili/res/skin/V我套餐/V我套餐_package.zip" + ] + @State var listSearchCache = "" + var filteredSkins: [String: String] { + if listSearchCache.isEmpty { + return skinsPath + } else { + return skinsPath.filter { key, value in + key.localizedCaseInsensitiveContains(listSearchCache) + } + } + } + + var body: some View { + List { + Section { + ForEach(filteredSkins.sorted(by: <), id: \.key) { key, value in + NavigationLink(destination: {SkinDownloadView(name: key, link: value)}, label: { + Text(key) + }) + } + } + } + .searchable(text: $listSearchCache, placement: .automatic, prompt: "Home.search") + .navigationTitle("Skin.add") + } +} + +struct SkinChooserView_Previews: PreviewProvider { + static var previews: some View { + SkinChooserView() + } +} diff --git a/MeowBili/Others/Skins/SkinDownloadView.swift b/MeowBili/Others/Skins/SkinDownloadView.swift new file mode 100644 index 000000000..9ea118983 --- /dev/null +++ b/MeowBili/Others/Skins/SkinDownloadView.swift @@ -0,0 +1,65 @@ +// +// +// SkinDownloadView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import Alamofire +import ZipArchive + +struct SkinDownloadView: View { + @Environment(\.dismiss) var dismiss + var name: String + var link: String + @State var downloadProgress = 0.0 + @State var isUnzipping = false + var body: some View { + VStack { + Text(isUnzipping ? "Skin.unzipping" : "Skin.downloading") + .font(.system(size: 18, weight: .bold)) + ProgressView(value: downloadProgress, total: 1.0) + } + .onAppear { + let destination: DownloadRequest.Destination = { _, _ in + let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + let fileURL = documentsURL.appendingPathComponent("skin/package.zip") + return (fileURL, [.removePreviousFile, .createIntermediateDirectories]) + } + AF.download(link, to: destination) + .downloadProgress { p in + downloadProgress = p.fractionCompleted + } + .response { r in + if r.error == nil, let filePath = r.fileURL?.path { + debugPrint(filePath) + isUnzipping = true + try! SSZipArchive.unzipFile(atPath: filePath, toDestination: filePath.replacingOccurrences(of: "package.zip", with: "") + name, overwrite: true, password: nil) + isUnzipping = false + debugPrint(AppFileManager(path: "skin").GetRoot() ?? [[:]]) + dismiss() + } else { + debugPrint(r.error as Any) + } + } + } + } +} + +struct SkinDownloadView_Previews: PreviewProvider { + static var previews: some View { + SkinDownloadView(name: "", link: "") + } +} diff --git a/MeowBili/Others/Skins/SkinExplorerView.swift b/MeowBili/Others/Skins/SkinExplorerView.swift new file mode 100644 index 000000000..f49247e0e --- /dev/null +++ b/MeowBili/Others/Skins/SkinExplorerView.swift @@ -0,0 +1,92 @@ +// +// +// SkinExplorerView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI + +struct SkinExplorerView: View { + var body: some View { + if #available(watchOS 10, *) { + MainView() + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + NavigationLink(destination: {SkinChooserView()}, label: { + Image(systemName: "plus") + }) + } + } + } else { + MainView() + } + } + + struct MainView: View { + @AppStorage("UsingSkin") var usingSkin = "" + @State var skinNames = [String]() + var body: some View { + List { + if #unavailable(watchOS 10) { + NavigationLink(destination: {SkinChooserView()}, label: { + HStack { + Image(systemName: "plus") + Text("Skin.add") + } + .font(.system(size: 16, weight: .bold)) + }) + } + if skinNames.count != 0 { + Section { + Button(action: { + usingSkin = "" + }, label: { + Label("Skin.none", systemImage: usingSkin == "" ? "checkmark" : "") + }) + } + Section { + ForEach(0.. videoTotalPage { + videoTargetJumpPageCache = String(videoTotalPage) + } + } else { + videoTargetJumpPageCache = String(videoNowPage) + } + } + Text(" / \(videoTotalPage)") + } + Button(action: { + if let cInt = Int(videoTargetJumpPageCache) { + videoNowPage = cInt + RefreshVideos() + } + isVideoPageJumpPresented = false + }, label: { + Text("Account.list.go") + }) + } + }) + .onTapGesture { + videoTargetJumpPageCache = String(videoNowPage) + isVideoPageJumpPresented = true + } + if videoNowPage != videoTotalPage { + Button(action: { + videoNowPage += 1 + RefreshVideos() + }, label: { + Text("Account.list.next-page") + .bold() + }) + } + } + } else { + if isNoVideo { + Text("Account.list.no-video") + } else { + ProgressView() + } + } + } else if viewSelector == .article { + if articles.count != 0 { + Section { + ForEach(0...articles.count - 1, id: \.self) { i in + Button(action: { + let session = ASWebAuthenticationSession(url: URL(string: "https://www.bilibili.com/read/cv\(articles[i]["CV"]!)")!, callbackURLScheme: nil) { _, _ in + return + } + session.prefersEphemeralWebBrowserSession = true + session.start() + }, label: { + VStack { + Text(articles[i]["Title"]!) + .font(.system(size: 16, weight: .bold)) + .lineLimit(3) + HStack { + VStack { + Text(articles[i]["Summary"]!) + .font(.system(size: 10, weight: .bold)) + .lineLimit(3) + .foregroundColor(.gray) + HStack { + Text(articles[i]["Type"]!) + .font(.system(size: 10)) + .lineLimit(1) + .foregroundColor(.gray) + Label(articles[i]["View"]!, systemImage: "eye.fill") + .font(.system(size: 10)) + .lineLimit(1) + .foregroundColor(.gray) + Label(articles[i]["Like"]!, systemImage: "hand.thumbsup.fill") + .font(.system(size: 10)) + .lineLimit(1) + .foregroundColor(.gray) + } + } + WebImage(url: URL(string: articles[i]["Pic"]! + "@60w"), options: [.progressiveLoad]) + .cornerRadius(5) + } + } + }) + .buttonBorderShape(.roundedRectangle(radius: 14)) + } + } + Section { + if articleNowPage != 1 { + Button(action: { + articleNowPage -= 1 + RefreshArticles() + }, label: { + Text("Account.list.last-page") + .bold() + }) + } + Text("\(articleNowPage) / \(articleTotalPage)") + .font(.system(size: 18, weight: .bold)) + .sheet(isPresented: $isArticalPageJumpPresented, content: { + VStack { + Text("Account.list.goto") + .font(.system(size: 20, weight: .bold)) + HStack { + TextField("Account.list.destination", text: $articleTargetJumpPageCache) + .onSubmit { + if let cInt = Int(articleTargetJumpPageCache) { + if cInt <= 0 { + articleTargetJumpPageCache = "1" + } + if cInt > articleTotalPage { + articleTargetJumpPageCache = String(articleTotalPage) + } + } else { + articleTargetJumpPageCache = String(articleNowPage) + } + } + Text(" / \(articleTotalPage)") + } + Button(action: { + if let cInt = Int(articleTargetJumpPageCache) { + articleNowPage = cInt + RefreshArticles() + } + isArticalPageJumpPresented = false + }, label: { + Text("Account.list.go") + }) + } + }) + .onTapGesture { + articleTargetJumpPageCache = String(articleNowPage) + isArticalPageJumpPresented = true + } + if articleNowPage != articleTotalPage { + Button(action: { + articleNowPage += 1 + RefreshArticles() + }, label: { + Text("Account.list.next-page") + .bold() + }) + } + } + } else { + if isNoArticle { + Text("Account.list.no-article") + } else { + ProgressView() + } + } + } + } + .onAppear { + if !isVideosLoaded { + RefreshVideos() + } + } + } + + func RefreshVideos() { + videos = [[String: String]]() + let headers: HTTPHeaders = [ + //"accept": "*/*", + //"accept-encoding": "gzip, deflate, br", + //"accept-language": "zh-CN,zh;q=0.9", + //"cookie": "\(sessdata == "" ? "" : "SESSDATA=\(sessdata); ")buvid3=\(globalBuvid3); b_nut=\(Date.now.timeStamp); buvid4=\(globalBuvid4);", + "cookie": "SESSDATA=\(sessdata); buvid_fp=e651c1a382430ea93631e09474e0b395; buvid3=\(UuidInfoc.gen()); buvid4=buvid4-failed-1", + //"origin": "https://space.bilibili.com", + //"referer": "https://space.bilibili.com/\(uid)/video", + //"User-Agent": "Mozilla/5.0" // Bypass? drdar://gh/SocialSisterYi/bilibili-API-collect/issues/868/1859065874 + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + ] + // FIXME: Official Wbi crypto logic for this request seems different from other APIs, some IP can get but some can't. It's hard to fix ~_~ + biliWbiSign(paramEncoded: "mid=\(uid)&ps=50&pn=\(videoNowPage)&dm_img_list=[]&dm_img_str=V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ&dm_cover_img_str=VjNEIDQuMkJyb2FkY2".base64Encoded()) { signed in + if let signed { + debugPrint(signed) + autoRetryRequestApi("https://api.bilibili.com/x/space/wbi/arc/search?\(signed)", headers: headers) { respJson, isSuccess in + if isSuccess { + debugPrint(respJson) + if !CheckBApiError(from: respJson) { 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 ?? 0) + videoTotalPage = Int((respJson["data"]["page"]["count"].int ?? 0) / 50) + 1 + videoCount = respJson["data"]["page"]["count"].int ?? 0 + if !isVideosLoaded { + if videos.count == 0 { + isNoVideo = true + } + isVideosLoaded = true + } + } + } + } + } + } + func RefreshArticles() { + articles = [[String: String]]() + let headers: HTTPHeaders = [ + "accept-language": "en,zh-CN;q=0.9,zh;q=0.8", + "cookie": "SESSDATA=\(sessdata);buvid3=\(globalBuvid3); buvid4=\(globalBuvid4);", + "User-Agent": "Mozilla/5.0" // Bypass? drdar://gh/SocialSisterYi/bilibili-API-collect/issues/868/1859065874 + ] + 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 !CheckBApiError(from: respJson) { 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)]) + } + articleTotalPage = Int(respJson["data"]["count"].int ?? 0 / 30) + 1 + articalCount = respJson["data"]["count"].int ?? 0 + if !isArticlesLoaded { + if articles.count == 0 { + isNoArticle = true + } + isArticlesLoaded = true + } + } + } + } + } + } + } + + struct ModifyUserRelation: Codable { + let fid: Int64 + let act: Int + var re_src: Int = 11 + let csrf: String + } +} + +enum UserDetailViewPubsType { + case video + case article +} + +struct UserDetailView_Previews: PreviewProvider { + static var previews: some View { + UserDetailView(uid: "356891781") + } +} diff --git a/MeowBili/PersonalCenter/WatchLaterView.swift b/MeowBili/PersonalCenter/WatchLaterView.swift new file mode 100644 index 000000000..d4f28ae73 --- /dev/null +++ b/MeowBili/PersonalCenter/WatchLaterView.swift @@ -0,0 +1,61 @@ +// +// +// WatchLaterView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import Alamofire +import SwiftyJSON + +struct WatchLaterView: View { + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var laters = [[String: String]]() + var body: some View { + List { + if laters.count != 0 { + ForEach(0...laters.count - 1, id: \.self) { i in + VideoCard(laters[i]) + } + } + } + .onAppear { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata);", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/v2/history/toview", headers: headers) { respJson, isSuccess in + if isSuccess { + debugPrint(respJson) + if !CheckBApiError(from: respJson) { return } + let datas = respJson["data"]["list"] + for data in datas { + laters.append(["Title": data.1["title"].string!, "Pic": data.1["pic"].string!, "UP": data.1["owner"]["name"].string!, "View": String(data.1["stat"]["view"].int!), "Danmaku": String(data.1["stat"]["danmaku"].int!), "BV": data.1["bvid"].string!]) + } + } + } + } + } +} + +struct WatchLaterView_Previews: PreviewProvider { + static var previews: some View { + WatchLaterView() + } +} diff --git a/MeowBili/PersonalCenter/bMessage/bMessageSendView.swift b/MeowBili/PersonalCenter/bMessage/bMessageSendView.swift new file mode 100644 index 000000000..48eafca91 --- /dev/null +++ b/MeowBili/PersonalCenter/bMessage/bMessageSendView.swift @@ -0,0 +1,221 @@ +// +// +// bMessageSendView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import AlertKit +import DarockKit +import Alamofire +import SwiftyJSON + +struct bMessageSendView: View { + var uid: Int64 + var username: String + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var messages = [[String: String]]() + @State var sendTextCache = "" + @State var refreshTimer: Timer? + var body: some View { + ScrollView { + VStack { + if messages.count != 0 { + ScrollView(.horizontal, showsIndicators: false) { + ForEach(0...messages.count - 1, id: \.self) { i in + if messages[i]["SenderID"] == String(uid) { + HStack { + HStack { + Text(messages[i]["Text"]!) + //.frame(maxWidth: 130) + .padding() + .background { + RoundedCornersView(color: Color(hex: 0x1F1F20), topLeading: 12, topTrailing: 12, bottomLeading: { () -> CGFloat in + if messages.count > i + 1 { + if messages[i + 1]["SenderID"] == String(uid) { + return 12 + } else { + return 0 + } + } else { + return 0 + } + }(), bottomTrailing: 12) + } + Spacer(minLength: 5) + } + .frame(width: UIScreen.main.bounds.width - 10) + Spacer() + .frame(width: 15) + Text({ () -> String in + let df = DateFormatter() + df.dateFormat = "a hh:mm" + return df.string(from: Date(timeIntervalSince1970: Double(messages[i]["Timestamp"]!)!)) + }()) + .font(.system(size: 14)) + .opacity(0.6) + } + } else { + HStack { + HStack { + Spacer(minLength: 5) + Text(messages[i]["Text"]!) + .padding() + .background { + RoundedCornersView(color: Color(hex: 0xF889BA), topLeading: 12, topTrailing: 12, bottomLeading: 12, bottomTrailing: { () -> CGFloat in + if messages.count > i + 1 { + if messages[i + 1]["SenderID"] != String(uid) { + return 12 + } else { + return 0 + } + } else { + return 0 + } + }()) + } + } + .frame(width: UIScreen.main.bounds.width - 10) + Spacer() + .frame(width: 15) + Text({ () -> String in + let df = DateFormatter() + df.dateFormat = "a hh:mm" + return df.string(from: Date(timeIntervalSince1970: Double(messages[i]["Timestamp"]!)!)) + }()) + .font(.system(size: 14)) + .opacity(0.6) + } + } + } + } + } + HStack { +// Button(action: { +// +// }, label: { +// ZStack { +// Circle() +// .foregroundStyle(Color(red: 31/255, green: 31/255, blue: 31/255)) +// Image(systemName: "plus") +// .font(.title3) +// } +// }) +// .frame(width: 50, height: 30) + TextField("Account.direct-message", text: $sendTextCache) + .opacity(0.0100000002421438702673861521) // MARK: You can find the limit here. If opacity lower than this value, this control won't be loaded. + .background { + ZStack { + Capsule() + .stroke(Color(red: 31/255, green: 31/255, blue: 31/255), lineWidth: 2) + HStack { + Text("Account.direct-message") + .foregroundStyle(.secondary) + .lineLimit(1) + Spacer() + } + .padding(.leading) + } + } + .textFieldStyle(.plain) + .submitLabel(.send) + .onSubmit { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata);" + ] + AF.request("https://api.vc.bilibili.com/web_im/v1/web_im/send_msg", method: .post, parameters: ["msg[sender_uid]": Int64(dedeUserID)!, "msg[receiver_id]": uid, "msg[receiver_type]": 1, "msg[msg_type]": 1, "msg[dev_id]": "372778FD-E359-461D-86A3-EA2BCC6FF52A", "msg[timestamp]": Date.now.timeStamp, "msg[content]": "{\"content\":\"\(sendTextCache)\"}", "csrf": biliJct], headers: headers).response { response in + messages.append(["SenderID": dedeUserID, "Text": sendTextCache, "Timestamp": String(Date.now.timeStamp)]) + sendTextCache = "" + debugPrint(response) + let json = try! JSON(data: response.data!) + if json["code"].int! == 0 { + //tipWithText("发送成功", symbol: "checkmark.circle.fill") + } else { + AlertKitAPI.present(title: String(localized: "Direct-message.failed"), icon: .error, style: .iOS17AppleMusic, haptic: .error) + } + } + } + } + } + } + .onAppear { + RefreshMessages() + Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { timer in + refreshTimer = timer + RefreshMessages(deleteAll: true) + } + } + } + + func RefreshMessages(deleteAll: Bool = false) { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata);" + ] + DarockKit.Network.shared.requestJSON("https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs?talker_id=\(uid)&session_type=1&size=20", headers: headers) { respJson, isSuccess in + if isSuccess { + debugPrint(respJson) + if deleteAll { + messages.removeAll() + } + for message in respJson["data"]["messages"] { + messages.insert(["SenderID": String(message.1["sender_uid"].int!), "Text": String(message.1["content"].string!.split(separator: ":\"")[1].split(separator: "\"}")[0]).replacingOccurrences(of: "\\n", with: "\n"), "Timestamp": String(message.1["timestamp"].int!)], at: 0) + } + } + } + } + + struct RoundedCornersView: View { + var color: Color + var topLeading: CGFloat + var topTrailing: CGFloat + var bottomLeading: CGFloat + var bottomTrailing: CGFloat + + var body: some View { + GeometryReader { geometry in + Path { path in + let w = geometry.size.width + let h = geometry.size.height + + let tr = min(min(self.topTrailing, h/2), w/2) + let tl = min(min(self.topLeading, h/2), w/2) + let bl = min(min(self.bottomLeading, h/2), w/2) + let br = min(min(self.bottomTrailing, h/2), w/2) + + path.move(to: CGPoint(x: w / 2.0, y: 0)) + path.addLine(to: CGPoint(x: w - tr, y: 0)) + path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false) + path.addLine(to: CGPoint(x: w, y: h - br)) + path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false) + path.addLine(to: CGPoint(x: bl, y: h)) + path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false) + path.addLine(to: CGPoint(x: 0, y: tl)) + path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false) + } + .fill(self.color) + } + } + } +} + +struct bMessageSendView_Previews: PreviewProvider { + static var previews: some View { + bMessageSendView(uid: 114514, username: "ReX-We") + } +} + diff --git a/MeowBili/Preview Content/Preview Assets.xcassets/Contents.json b/MeowBili/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/MeowBili/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeowBili/UserDynamic/DynamicDetailView.swift b/MeowBili/UserDynamic/DynamicDetailView.swift new file mode 100644 index 000000000..6c338730a --- /dev/null +++ b/MeowBili/UserDynamic/DynamicDetailView.swift @@ -0,0 +1,239 @@ +// +// +// DynamicDetailView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import SDWebImageSwiftUI + +struct DynamicDetailView: View { + var dynamicDetails: [String: Any?] + @State var isDynamicImagePresented = [Bool]() + var body: some View { + TabView { + ScrollView { + VStack { + NavigationLink(destination: {UserDetailView(uid: dynamicDetails["SenderID"]! as! String)}, label: { + HStack { + WebImage(url: URL(string: dynamicDetails["SenderPic"]! as! String + "@30w"), options: [.progressiveLoad]) + .cornerRadius(100) + VStack { + HStack { + Text(dynamicDetails["SenderName"]! as! String) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + Spacer() + } + HStack { + Text(dynamicDetails["SendTimeStr"]! as! String + { () -> String in + switch dynamicDetails["Type"]! as! BiliDynamicType { + case .draw, .text: + return "" + case .video: + return " · 投稿了视频" + case .live: + return "直播了" + case .forward: + return " · 转发动态" + case .article: + return " · 投稿了专栏" + } + }()) + .font(.system(size: 10)) + .foregroundColor(.gray) + .lineLimit(1) + Spacer() + } + } + Spacer() + } + }) + .buttonStyle(.plain) + if dynamicDetails["WithText"]! as! String != "" { + HStack { + Text(dynamicDetails["WithText"]! as! String) + .font(.system(size: 16)) + Spacer() + } + } + if dynamicDetails["Type"]! as! BiliDynamicType == .draw { + if let draws = dynamicDetails["Draws"] as? [[String: String]] { + LazyVGrid(columns: [GridItem(.fixed(50)), GridItem(.fixed(50)), GridItem(.fixed(50))]) { + ForEach(0.. i { + VStack { + NavigationLink("", isActive: $isDynamicImagePresented[i], destination: {ImageViewerView(url: draws[i]["Src"]!)}) + .frame(width: 0, height: 0) + WebImage(url: URL(string: draws[i]["Src"]! + "@60w_40h"), options: [.progressiveLoad]) + .cornerRadius(5) + .onTapGesture { + isDynamicImagePresented[i] = true + } + } + } + } + } + } + } else if dynamicDetails["Type"]! as! BiliDynamicType == .video { + if let archive = dynamicDetails["Archive"] as? [String: String] { + VideoCard(archive) + } + } else if dynamicDetails["Type"]! as! BiliDynamicType == .live { + if let liveInfo = dynamicDetails["Live"] as? [String: String] { + NavigationLink(destination: {LiveDetailView(liveDetails: liveInfo)}, label: { + VStack { + HStack { + WebImage(url: URL(string: liveInfo["Cover"]! + "@50w")!, options: [.progressiveLoad, .scaleDownLargeImages]) + .placeholder { + RoundedRectangle(cornerRadius: 7) + .frame(width: 50) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + .cornerRadius(7) + Text(liveInfo["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(2) + Spacer() + } + HStack { + Text("\(liveInfo["Type"]!) · \(liveInfo["ViewStr"]!)") + Spacer() + } + .lineLimit(1) + .font(.system(size: 11)) + .foregroundColor(.gray) + } + }) + .buttonBorderShape(.roundedRectangle(radius: 14)) + } + } else if dynamicDetails["Type"]! as! BiliDynamicType == .forward { + if let origData = dynamicDetails["Forward"] as? [String: Any?]? { + if let orig = origData { + NavigationLink(destination: {DynamicDetailView(dynamicDetails: orig)}, label: { + VStack { + HStack { + WebImage(url: URL(string: orig["SenderPic"]! as! String + "@30w"), options: [.progressiveLoad]) + .cornerRadius(100) + VStack { + HStack { + Text(orig["SenderName"]! as! String) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + Spacer() + } + HStack { + Text(orig["SendTimeStr"]! as! String + { () -> String in + switch orig["Type"]! as! BiliDynamicType { + case .draw, .text: + return "" + case .video: + return "投稿了视频" + case .live: + return "直播了" + case .forward: + return "转发动态" + case .article: + return "投稿了专栏" + } + }()) + .font(.system(size: 10)) + .foregroundColor(.gray) + .lineLimit(1) + Spacer() + } + } + Spacer() + } + if orig["WithText"]! as! String != "" { + HStack { + Text(orig["WithText"]! as! String) + .font(.system(size: 16)) + .lineLimit(5) + Spacer() + } + } + if orig["Type"]! as! BiliDynamicType == .draw { + if let draws = orig["Draws"] as? [[String: String]] { + LazyVGrid(columns: [GridItem(.fixed(50)), GridItem(.fixed(50)), GridItem(.fixed(50))]) { + ForEach(0...allocate(capacity: 1) + currentUploadImageIndex.initialize(to: 0) + let uploadedImageUrl = UnsafeMutablePointer.allocate(capacity: 9) + for i in 0..<9 { + uploadedImageUrl.advanced(by: i).initialize(to: "") + } + uploadImageBfs(image: convertedImages[currentUploadImageIndex.pointee], params: parameters, headers: headers) { response in + uploadImageRespHander(response, cuiiPtr: currentUploadImageIndex, upliPtr: uploadedImageUrl, params: parameters, headers: headers) + } + } + } + }, label: { + if isSending { + ProgressView() + } else { + Text("发送动态") + } + }) + .disabled(isSending) + .sheet(isPresented: $isTailInitPresented, content: {DynamicTailSetView()}) + } footer: { + Text(sendProgressText) + } + } + .navigationTitle("发送动态") + .onChange(of: selectedPhotos) { value in + convertedImages.removeAll() + for photo in value { + photo.loadTransferable(type: UIImageTransfer.self) { result in + switch result { + case .success(let success): + if let image = success { + convertedImages.append(image.image) + } + case .failure: + break + } + } + } + } + } + + func uploadImageBfs(image: UIImage, params: [String: String], headers: HTTPHeaders, callback: @escaping (AFDataResponse) -> Void) { + AF.upload(multipartFormData: { multipartFormData in + for image in convertedImages { + multipartFormData.append(image.pngData()!, withName: "file_up", fileName: "\(Int.random(in: 1...100)).png", mimeType: "image/png") + } + for (key, value) in params { + if let data = value.data(using: .utf8) { + multipartFormData.append(data, withName: key) + } + } + }, to: "https://api.bilibili.com/x/dynamic/feed/draw/upload_bfs", method: .post, headers: headers).response { response in + callback(response) + } + } + func uploadImageRespHander(_ response: AFDataResponse, cuiiPtr currentUploadImageIndex: UnsafeMutablePointer, upliPtr: UnsafeMutablePointer, params: [String: String], headers: HTTPHeaders) { + sendProgressText = "正在上传图片 #\(currentUploadImageIndex + 1)" + if let rd = response.data, let json = try? JSON(data: rd) { + if !CheckBApiError(from: json) { return } + if let url = json["data"]["image_url"].string { + upliPtr.advanced(by: currentUploadImageIndex.pointee).pointee = "\(url)||\(convertedImages[currentUploadImageIndex.pointee].size.width)||\(convertedImages[currentUploadImageIndex.pointee].size.height)||\(Double(convertedImages[currentUploadImageIndex.pointee].pngData()!.count) / 1024.0)" + } else { + currentUploadImageIndex.deallocate() + AlertKitAPI.present(title: "上传图片时失败,未知错误", icon: .error, style: .iOS17AppleMusic, haptic: .error) + return + } + currentUploadImageIndex.pointee++ + if currentUploadImageIndex.pointee < convertedImages.count { + uploadImageBfs(image: convertedImages[currentUploadImageIndex.pointee], params: params, headers: headers) { response in + uploadImageRespHander(response, cuiiPtr: currentUploadImageIndex, upliPtr: upliPtr, params: params, headers: headers) + } + } else { + currentUploadImageIndex.deallocate() + sendProgressText = "正在上传动态" + AF.request("https://api.bilibili.com/x/dynamic/feed/create/dyn?csrf=\(biliJct)", method: .post, parameters: [ + "dyn_req": [ + "content": [ + "contents": [ + [ + "raw_text": "\(dynamicText)\(dynamicTailSetting == "" ? "" : "\n\n \(dynamicTailSetting)")", + "type": 1, + "biz_id": "" + ] + ] + ], + "scene": 2, + "pics": { () -> [[String: Any]] in + var tmp = [[String: Any]]() + for i in 0..<9 { + if upliPtr.advanced(by: i).pointee == "" { + break + } + tmp.append([ + "img_src": String(upliPtr.advanced(by: i).pointee.split(separator: "||")[0]), + "img_height": Int(Float(upliPtr.advanced(by: i).pointee.split(separator: "||")[1])!), + "img_width": Int(Float(upliPtr.advanced(by: i).pointee.split(separator: "||")[2])!), + "img_size": Double(upliPtr.advanced(by: i).pointee.split(separator: "||")[3])!, + ]) + } + return tmp + }() + ] + ], encoding: JSONEncoding.default, headers: headers).response { response in + debugPrint(response) + } + } + } else { + currentUploadImageIndex.deallocate() + AlertKitAPI.present(title: "上传图片时失败,未知错误", icon: .error, style: .iOS17AppleMusic, haptic: .error) + } + } + + struct DynamicTailSetView: View { + @Environment(\.dismiss) var dismiss + @AppStorage("DynamicTailSetting") var dynamicTailSetting = "NotSet" + @State var tailContent = "———— 来自 watchOS 喵哩喵哩客户端" + var body: some View { + List { + Section { + Text("要使用动态小尾巴吗?") + .bold() + .listRowBackground(Color.clear) + } + Section { + TextField("小尾巴内容", text: $tailContent) + } header: { + Text("小尾巴内容") + } footer: { + Text("您可以更改默认的小尾巴内容, 如果不想添加小尾巴, 请清空上方文本框内容. 您可以随时在设置中更改此内容") + } + Section { + Button(action: { + dynamicTailSetting = tailContent + dismiss() + }, label: { + Text("应用") + }) + } + } + } + } +} + +#Preview { + DynamicSendView() +} diff --git a/MeowBili/UserDynamic/ImageViewerView.swift b/MeowBili/UserDynamic/ImageViewerView.swift new file mode 100644 index 000000000..4aafd94df --- /dev/null +++ b/MeowBili/UserDynamic/ImageViewerView.swift @@ -0,0 +1,40 @@ +// +// +// ImageViewerView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import SDWebImageSwiftUI + +struct ImageViewerView: View { + var url: String + var body: some View { + WebImage(url: URL(string: url), options: [.progressiveLoad], isAnimating: .constant(true)) + .resizable() +// .indicator(.activity) + .transition(.fade(duration: 0.5)) + .scaledToFit() + .frame(width: CGFloat(100), height: CGFloat(100), alignment: .center) +// .modifier(zoomable()) + } +} + +struct ImageViewerView_Previews: PreviewProvider { + static var previews: some View { + ImageViewerView(url: "") + } +} diff --git a/MeowBili/UserDynamic/UserDynamicMainView.swift b/MeowBili/UserDynamic/UserDynamicMainView.swift new file mode 100644 index 000000000..ccc6d4823 --- /dev/null +++ b/MeowBili/UserDynamic/UserDynamicMainView.swift @@ -0,0 +1,426 @@ +// +// +// UserDynamicMainView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI +import DarockKit +import Alamofire +import SwiftyJSON +import SDWebImageSwiftUI + +struct UserDynamicMainView: View { + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var dynamics = [[String: Any?]]() + @State var isSenderDetailsPresented = [Bool]() + @State var isDynamicImagePresented = [[Bool]]() + @State var isLoaded = false + @State var nextLoadPage = 1 + @State var lastDynamicID = "" + @State var isLoadingNew = false + @State var isDynamicSendPresented = false + var body: some View { + if sessdata != "" { + ScrollView { + LazyVStack { + if dynamics.count != 0 { + Button(action: { + isDynamicSendPresented = true + }, label: { + Label("发送动态", systemImage: "square.and.pencil") + }) + .buttonStyle(.borderedProminent) + ForEach(0.. String in + switch dynamics[i]["Type"]! as! BiliDynamicType { + case .draw, .text: + return "" + case .video: + return " · 投稿了视频" + case .live: + return "直播了" + case .forward: + return " · 转发动态" + case .article: + return " · 投稿了专栏" + } + }()) + .font(.system(size: 10)) + .foregroundColor(.gray) + .lineLimit(1) + Spacer() + } + } + Spacer() + } + .onTapGesture { + isSenderDetailsPresented[i] = true + } + if dynamics[i]["WithText"]! as! String != "" { + NavigationLink(destination: {DynamicDetailView(dynamicDetails: dynamics[i])}, label: { + HStack { + Text(dynamics[i]["WithText"]! as! String) + .font(.system(size: 16)) + Spacer() + } + }) + .buttonStyle(.plain) + } + if dynamics[i]["Type"]! as! BiliDynamicType == .draw { + if let draws = dynamics[i]["Draws"] as? [[String: String]] { + LazyVGrid(columns: [GridItem(.fixed(50)), GridItem(.fixed(50)), GridItem(.fixed(50))]) { + ForEach(0.. j { + VStack { + NavigationLink("", isActive: $isDynamicImagePresented[i][j], destination: {ImageViewerView(url: draws[j]["Src"]!)}) + .frame(width: 0, height: 0) + WebImage(url: URL(string: draws[j]["Src"]! + "@60w_40h"), options: [.progressiveLoad]) + .cornerRadius(5) + .onTapGesture { + isDynamicImagePresented[i][j] = true + } + } + } + } + } + } + } else if dynamics[i]["Type"]! as! BiliDynamicType == .video { + if let archive = dynamics[i]["Archive"] as? [String: String] { + VideoCard(archive) + } + } else if dynamics[i]["Type"]! as! BiliDynamicType == .live { + if let liveInfo = dynamics[i]["Live"] as? [String: String] { + NavigationLink(destination: {LiveDetailView(liveDetails: liveInfo)}, label: { + VStack { + HStack { + WebImage(url: URL(string: liveInfo["Cover"]! + "@50w")!, options: [.progressiveLoad, .scaleDownLargeImages]) + .placeholder { + RoundedRectangle(cornerRadius: 7) + .frame(width: 50) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + .cornerRadius(7) + Text(liveInfo["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(2) + Spacer() + } + HStack { + Text("\(liveInfo["Type"]!) · \(liveInfo["ViewStr"]!)") + Spacer() + } + .lineLimit(1) + .font(.system(size: 11)) + .foregroundColor(.gray) + } + }) + .buttonBorderShape(.roundedRectangle(radius: 14)) + } + } else if dynamics[i]["Type"]! as! BiliDynamicType == .forward { + if let origData = dynamics[i]["Forward"] as? [String: Any?]? { + if let orig = origData { + NavigationLink(destination: {DynamicDetailView(dynamicDetails: orig)}, label: { + VStack { + HStack { + WebImage(url: URL(string: orig["SenderPic"]! as! String + "@30w"), options: [.progressiveLoad]) + .cornerRadius(100) + VStack { + NavigationLink("", isActive: $isSenderDetailsPresented[i], destination: {UserDetailView(uid: orig["SenderID"]! as! String)}) + .frame(width: 0, height: 0) + HStack { + Text(orig["SenderName"]! as! String) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + Spacer() + } + HStack { + Text(orig["SendTimeStr"]! as! String + { () -> String in + switch orig["Type"]! as! BiliDynamicType { + case .draw, .text: + return "" + case .video: + return "投稿了视频" + case .live: + return "直播了" + case .forward: + return "转发动态" + case .article: + return "投稿了专栏" + } + }()) + .font(.system(size: 10)) + .foregroundColor(.gray) + .lineLimit(1) + Spacer() + } + } + Spacer() + } + if orig["WithText"]! as! String != "" { + HStack { + Text(orig["WithText"]! as! String) + .font(.system(size: 16)) + .lineLimit(5) + Spacer() + } + } + if orig["Type"]! as! BiliDynamicType == .draw { + if let draws = orig["Draws"] as? [[String: String]] { + LazyVGrid(columns: [GridItem(.fixed(50)), GridItem(.fixed(50)), GridItem(.fixed(50))]) { + ForEach(0.. String in if lastDynamicID != "" { return "&offset=\(lastDynamicID)"; } else { return ""; }; }())&page=\(nextLoadPage)", headers: headers) { respJson, isSuccess in + if isSuccess { + debugPrint(respJson) + if !CheckBApiError(from: respJson) { return } + let items = respJson["data"]["items"] + var itemForCount = 0 + for item in items { + isSenderDetailsPresented.append(false) + isDynamicImagePresented.append([]) + dynamics.append([ + "WithText": item.1["modules"]["module_dynamic"]["desc"]["text"].string ?? "", + "Type": BiliDynamicType(rawValue: item.1["type"].string ?? "DYNAMIC_TYPE_WORD") ?? .text, + "Draws": { () -> [[String: String]]? in + if BiliDynamicType(rawValue: item.1["type"].string ?? "DYNAMIC_TYPE_WORD") == .draw { + var dTmp = [[String: String]]() + for draw in item.1["modules"]["module_dynamic"]["major"]["draw"]["items"] { + isDynamicImagePresented[itemForCount].append(false) + dTmp.append(["Src": draw.1["src"].string ?? ""]) + } + return dTmp + } else { + return nil + } + }(), + "Archive": { () -> [String: String]? in + if BiliDynamicType(rawValue: item.1["type"].string ?? "DYNAMIC_TYPE_WORD") == .video { + let archive = item.1["modules"]["module_dynamic"]["major"]["archive"] + return ["Pic": archive["cover"].string ?? "", "Title": archive["title"].string ?? "", "BV": archive["bvid"].string ?? "", "UP": item.1["modules"]["module_author"]["name"].string ?? "", "View": archive["stat"]["play"].string ?? "-1", "Danmaku": archive["stat"]["danmaku"].string ?? "-1"] + } else { + return nil + } + }(), + "Live": { () -> [String: String]? in + if BiliDynamicType(rawValue: item.1["type"].string ?? "DYNAMIC_TYPE_WORD") == .live { + do { + let liveContentJson = try JSON(data: (item.1["modules"]["module_dynamic"]["major"]["live_rcmd"]["content"].string ?? "").data(using: .utf8) ?? Data()) + debugPrint(liveContentJson) + return ["Cover": liveContentJson["live_play_info"]["cover"].string ?? "", "Title": liveContentJson["live_play_info"]["title"].string ?? "", "ID": String(liveContentJson["live_play_info"]["room_id"].int ?? 0), "Type": liveContentJson["live_play_info"]["area_name"].string ?? "", "ViewStr": liveContentJson["live_play_info"]["watched_show"]["text_large"].string ?? "-1"] + } catch { + return nil + } + } else { + return nil + } + }(), + "Forward": { () -> [String: Any?]? in + if BiliDynamicType(rawValue: item.1["type"].string ?? "DYNAMIC_TYPE_WORD") == .forward { + let origData = item.1["orig"] + return [ + "WithText": origData["modules"]["module_dynamic"]["desc"]["text"].string ?? "", + "Type": BiliDynamicType(rawValue: origData["type"].string ?? "DYNAMIC_TYPE_WORD") ?? .text, + "Draws": { () -> [[String: String]]? in + if BiliDynamicType(rawValue: origData["type"].string ?? "DYNAMIC_TYPE_WORD") == .draw { + var dTmp = [[String: String]]() + for draw in origData["modules"]["module_dynamic"]["major"]["draw"]["items"] { + isDynamicImagePresented[itemForCount].append(false) + dTmp.append(["Src": draw.1["src"].string ?? ""]) + } + return dTmp + } else { + return nil + } + }(), + "Archive": { () -> [String: String]? in + if BiliDynamicType(rawValue: origData["type"].string ?? "DYNAMIC_TYPE_WORD") == .video { + let archive = origData["modules"]["module_dynamic"]["major"]["archive"] + return ["Pic": archive["cover"].string ?? "", "Title": archive["title"].string ?? "", "BV": archive["bvid"].string ?? "", "UP": origData["modules"]["module_author"]["name"].string ?? "", "View": archive["stat"]["play"].string ?? "-1", "Danmaku": archive["stat"]["danmaku"].string ?? "-1"] + } else { + return nil + } + }(), + "Live": { () -> [String: String]? in + if BiliDynamicType(rawValue: origData["type"].string ?? "DYNAMIC_TYPE_WORD") == .live { + do { + let liveContentJson = try JSON(data: (origData["modules"]["module_dynamic"]["major"]["live_rcmd"]["content"].string ?? "").data(using: .utf8) ?? Data()) + debugPrint(liveContentJson) + return ["Cover": liveContentJson["live_play_info"]["cover"].string ?? "", "Title": liveContentJson["live_play_info"]["title"].string ?? "", "ID": String(liveContentJson["live_play_info"]["room_id"].int ?? 0), "Type": liveContentJson["live_play_info"]["area_name"].string ?? "", "ViewStr": liveContentJson["live_play_info"]["watched_show"]["text_large"].string ?? "-1"] + } catch { + return nil + } + } else { + return nil + } + }(), + "SenderPic": origData["modules"]["module_author"]["face"].string ?? "", + "SenderName": origData["modules"]["module_author"]["name"].string ?? "", + "SenderID": String(origData["modules"]["module_author"]["mid"].int ?? 0), + "SendTimeStr": origData["modules"]["module_author"]["pub_time"].string ?? "0000/00/00", + "SharedCount": String(origData["modules"]["module_stat"]["forward"]["count"].int ?? -1), + "LikedCount": String(origData["modules"]["module_stat"]["like"]["count"].int ?? -1), + "IsLiked": origData["modules"]["module_stat"]["like"]["status"].bool ?? false, + "CommentCount": String(origData["modules"]["module_stat"]["comment"]["count"].int ?? -1), + "DynamicID": origData["id_str"].string ?? "" + ] + } else { + return nil + } + }(), + "SenderPic": item.1["modules"]["module_author"]["face"].string ?? "", + "SenderName": item.1["modules"]["module_author"]["name"].string ?? "", + "SenderID": String(item.1["modules"]["module_author"]["mid"].int ?? 0), + "SendTimeStr": item.1["modules"]["module_author"]["pub_time"].string ?? "0000/00/00", + "SharedCount": String(item.1["modules"]["module_stat"]["forward"]["count"].int ?? -1), + "LikedCount": String(item.1["modules"]["module_stat"]["like"]["count"].int ?? -1), + "IsLiked": item.1["modules"]["module_stat"]["like"]["status"].bool ?? false, + "CommentCount": String(item.1["modules"]["module_stat"]["comment"]["count"].int ?? -1), + "DynamicID": item.1["id_str"].string ?? "" + ]) + itemForCount += 1 + } + lastDynamicID = dynamics.last?["DynamicID"] as! String + nextLoadPage += 1 + isLoadingNew = false + } + } + } +} + +enum BiliDynamicType: String { + case forward = "DYNAMIC_TYPE_FORWARD" // 转发 + case video = "DYNAMIC_TYPE_AV" // 投稿视频 + case text = "DYNAMIC_TYPE_WORD" // 纯文本 + case draw = "DYNAMIC_TYPE_DRAW" // 带图 + case article = "DYNAMIC_TYPE_ARTICLE" // 专栏 + case live = "DYNAMIC_TYPE_LIVE_RCMD" // 直播开播 +} + +struct UserDynamicMainView_Previews: PreviewProvider { + static var previews: some View { + UserDynamicMainView() + } +} diff --git a/MeowBili/Video/AudioPlayerView.swift b/MeowBili/Video/AudioPlayerView.swift new file mode 100644 index 000000000..9d382e76f --- /dev/null +++ b/MeowBili/Video/AudioPlayerView.swift @@ -0,0 +1,158 @@ +// +// +// AudioPlayerView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import AVKit +import Combine +import SwiftUI +import DarockKit +import Alamofire +import AVFoundation +import CachedAsyncImage + +struct AudioPlayerView: View { + var videoDetails: [String: String] + var subTitles: [[String: String]] + @AppStorage("AudioPlayBehavior") var audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue + @State var audioPlayer = AVPlayer() + @State var playerItem: AVPlayerItem! = nil + @State var isPlaying = true + @State var finishObserver: AnyCancellable? + @State var startObserver: AnyCancellable? + @State var backgroundPicOpacity = 0.0 + @State var nowPlayTimeTimer: Timer? + var body: some View { + VStack { + VStack { + AsyncImage(url: URL(string: videoDetails["Pic"]! + "@110w_85h")) + .cornerRadius(10) + Text(videoDetails["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + .padding(.vertical, 7) + .padding(.horizontal, 20) + Text(videoDetails["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + .padding(.vertical, 7) + .padding(.horizontal, 20) + Button(action: { + let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! + switch behaviorCase { + case .singleLoop: + audioPlayBehavior = AudioPlayerBehavior.pauseWhenFinish.rawValue + case .pauseWhenFinish: + audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue + case .listLoop: + audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue + case .exitWhenFinish: + audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue + } + RefreshPlayerBehavior(player: audioPlayer, playerItem: playerItem) + }, label: { + Image(systemName: { () -> String in + let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! + switch behaviorCase { + case .singleLoop: + return "repeat.1" + case .pauseWhenFinish: + return "pause" + case .listLoop: + return "repeat" + case .exitWhenFinish: + return "rectangle.portrait.and.arrow.forward" + } + }()) + }) + Button(action: { + isPlaying.toggle() + if isPlaying { + audioPlayer.play() + } else { + audioPlayer.pause() + } + }, label: { + Image(systemName: isPlaying ? "pause.fill" : "play.fill") + }) +// VolumeControlView() + } + } + .onAppear { + // Background Session + do { + try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers]) + try AVAudioSession.sharedInstance().setActive(true) + } catch { + print(error) + } + + let asset = AVURLAsset(url: URL(string: VideoDetailView.willPlayVideoLink)!, options: [AVURLAssetHTTPUserAgentKey: "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"]) + playerItem = AVPlayerItem(asset: asset) + audioPlayer = AVPlayer(playerItem: playerItem) + + startObserver = playerItem.publisher(for: \.status) + .sink { status in + if status == .readyToPlay { + audioPlayer.play() + } + } + + Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in + nowPlayTimeTimer = timer + debugPrint(audioPlayer.currentTime().seconds) + } + + RefreshPlayerBehavior(player: audioPlayer, playerItem: playerItem) + } + .onDisappear { + startObserver?.cancel() + finishObserver?.cancel() + nowPlayTimeTimer?.invalidate() + } + } + + func RefreshPlayerBehavior(player: AVPlayer, playerItem: AVPlayerItem) { + let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! + switch behaviorCase { + case .singleLoop: + finishObserver = NotificationCenter.default + .publisher(for: .AVPlayerItemDidPlayToEndTime, object: playerItem) + .sink { _ in + player.seek(to: CMTime.zero) + player.play() + } + case .pauseWhenFinish: + finishObserver?.cancel() + default: + break + } + } + +} + +public enum AudioPlayerBehavior: String { + case singleLoop = "singleLoop" + case pauseWhenFinish = "pause" + case listLoop = "listLoop" + case exitWhenFinish = "exit" +} + +struct AudioPlayerView_Previews: PreviewProvider { + static var previews: some View { + AudioPlayerView(videoDetails: ["Pic": "http://i1.hdslb.com/bfs/archive/453a7f8deacb98c3b083ead733291f080383723a.jpg", "Title": "解压视频:20000个小球Marble run动画", "BV": "BV1PP41137Px", "UP": "小球模拟", "View": "114514", "Danmaku": "1919810"], subTitles: [[:]]) + } +} diff --git a/MeowBili/Video/LyricerView.swift b/MeowBili/Video/LyricerView.swift new file mode 100644 index 000000000..c353be26d --- /dev/null +++ b/MeowBili/Video/LyricerView.swift @@ -0,0 +1,88 @@ +// +// +// LyricerView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import SwiftUI + +struct LyricerView: View { + var lyrics: [[String: String]] + @State var nowTime: Double = 10 + @State var nowPlayingIndex = 0 + @State var isShowingBlur = true + @State var lyricGoTimer: Timer? + var body: some View { + ZStack { + ScrollViewReader { proxy in + List { + ForEach(0..= dStartTime && nowTime <= dEndTime { + withAnimation(.easeOut) { + nowPlayingIndex = loopTimes + proxy.scrollTo(nowPlayingIndex, anchor: .top) + } + break + } + loopTimes += 1 + } + } + + Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in + nowTime += 0.5 + debugPrint(nowTime) + } + } + .onDisappear { + lyricGoTimer?.invalidate() + } + } + if isShowingBlur { + VStack { + Spacer() + Color.clear + .frame(height: 60) + .background(.ultraThinMaterial) + .offset(y: 10) + .blur(radius: 4) + .ignoresSafeArea() + } + } + } + } +} + +struct LyricerView_Previews: PreviewProvider { + static var previews: some View { + LyricerView(lyrics: [["Start": "22.3", "End": "26.025", "content": "chunzhenboidontsmoke is on the track."], ["Start": "30", "End": "32", "content": "chunzhenboidontsmoke is on the track."], ["Start": "32.8", "End": "34", "content": "chunzhenboidontsmoke is on the track."], ["Start": "35.3", "End": "37.1", "content": "chunzhenboidontsmoke is on the track."], ["Start": "38", "End": "39.4", "content": "chunzhenboidontsmoke is on the track."], ["Start": "40.2", "End": "43.8", "content": "chunzhenboidontsmoke is on the track."]]) + } +} diff --git a/MeowBili/Video/VideoDetailView.swift b/MeowBili/Video/VideoDetailView.swift new file mode 100644 index 000000000..0cf2f3345 --- /dev/null +++ b/MeowBili/Video/VideoDetailView.swift @@ -0,0 +1,824 @@ +// +// +// VideoDetailView.swift +// MeowBili +// +// Created by memz233 on 2024/2/10. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2023 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import UIKit +import AVKit +import SwiftUI +import Marquee +import DarockKit +import Alamofire +import SwiftyJSON +import AVFoundation +import CachedAsyncImage +import SDWebImageSwiftUI + +struct VideoDetailView: View { + public static var willPlayVideoLink = "" + public static var willPlayVideoBV = "" + public static var willPlayVideoCID: Int64 = 0 + @State var videoDetails: [String: String] + @Environment(\.colorScheme) var colorScheme + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @AppStorage("RecordHistoryTime") var recordHistoryTime = "into" + @AppStorage("VideoGetterSource") var videoGetterSource = "official" + @AppStorage("IsDanmakuEnabled") var isDanmakuEnabled = true + @State var isDecoded = false + @State var isLiked = false + @State var isCoined = false + @State var isFavoured = false + @State var isCoinViewPresented = false + @State var videoPages = [[String: String]]() + @State var goodVideos = [[String: String]]() + @State var owner = [String: String]() + @State var stat = [String: String]() + @State var honors = [String]() + @State var tags = [String]() + @State var subTitles = [[String: String]]() + @State var ownerFansCount = 0 + @State var nowPlayingCount = "0" + @State var publishTime = "" + @State var videoDesc = "" + @State var isVideoPlayerPresented = false + @State var isMoreMenuPresented = false + @State var isDownloadPresented = false + @State var isNowPlayingPresented = false + @State var backgroundPicOpacity = 0.0 + @State var mainVerticalTabViewSelection = 1 + @State var videoPartShouldShowDownloadTip = false + @State var isCoverImageViewPresented = false + @State var tagText = "" + @State var isFavoriteChoosePresented = false + @State var tagDisplayedNum = 0 + @State var currentDetailSelection = 1 + var body: some View { + VStack { + if isDecoded { + VideoPlayerView(isDanmakuEnabled: $isDanmakuEnabled) + .frame(height: 240) + } else { + Rectangle() + .frame(height: 240) + .redacted(reason: .placeholder) + } + HStack { + Spacer() + Button(action: { + isDanmakuEnabled.toggle() + }, label: { + Text(isDanmakuEnabled ? "" : "") + .font(.custom("bilibili", size: 28)) + .foregroundColor(isDanmakuEnabled ? .accentColor : .gray) + }) + } + .padding(.horizontal) + VStack { + Picker("", selection: $currentDetailSelection) { + Text("详情").tag(1) + Text("评论").tag(2) + if goodVideos.count != 0 { + Text("推荐").tag(3) + } + } + .pickerStyle(.segmented) + .padding(.horizontal) + if currentDetailSelection == 1 { + ScrollView { + VStack { + HStack { + if honors.count >= 4 { + if honors[3] != "" { + HStack { + Image(systemName: "flame.fill") + Text("Video.trending") + } + .font(.system(size: 11)) + .foregroundColor(.red) + .padding(5) + .background { + Capsule() + .fill(.white) + .opacity(0.3) + } + } + } + Marquee { + HStack { + Text(videoDetails["Title"]!) + .lineLimit(1) + .font(.system(size: 18, weight: .bold)) + .multilineTextAlignment(.center) + } + } + .marqueeWhenNotFit(true) + .marqueeDuration(10) + .frame(height: 20) + .padding(.horizontal, 10) + } + Spacer() + .frame(height: 20) + if #unavailable(watchOS 10) { + NavigationLink("", isActive: $isNowPlayingPresented, destination: {AudioPlayerView(videoDetails: videoDetails, subTitles: subTitles)}) + .frame(width: 0, height: 0) + Button(action: { + if videoGetterSource == "official" { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/web-interface/view?bvid=\(videoDetails["BV"]!)", headers: headers).response { response in + let cid = Int64((String(data: response.data!, encoding: .utf8)?.components(separatedBy: "\"pages\":[{\"cid\":")[1].components(separatedBy: ",")[0])!)! + VideoDetailView.willPlayVideoCID = cid + AF.request("https://api.bilibili.com/x/player/playurl?platform=html5&bvid=\(videoDetails["BV"]!)&cid=\(cid)", headers: headers).response { response in + VideoDetailView.willPlayVideoLink = (String(data: response.data!, encoding: .utf8)?.components(separatedBy: ",\"url\":\"")[1].components(separatedBy: "\",")[0])!.replacingOccurrences(of: "\\u0026", with: "&") + VideoDetailView.willPlayVideoBV = videoDetails["BV"]! + isNowPlayingPresented = true + + } + } + } else if videoGetterSource == "injahow" { + DarockKit.Network.shared.requestString("https://api.injahow.cn/bparse/?bv=\(videoDetails["BV"]!.dropFirst().dropFirst())&p=1&type=video&q=32&format=mp4&otype=url") { respStr, isSuccess in + if isSuccess { + VideoDetailView.willPlayVideoLink = respStr + VideoDetailView.willPlayVideoBV = videoDetails["BV"]! + isNowPlayingPresented = true + + } + } + } + }, label: { + Label("Video.play-in-audio", systemImage: "waveform") + }) + Button(action: { + isMoreMenuPresented = true + }, label: { + Label("Video.more", systemImage: "ellipsis") + }) + .sheet(isPresented: $isMoreMenuPresented, content: { + List { + Button(action: { + isDownloadPresented = true + }, label: { + Label("Video.download", image: "arrow.down.doc") + }) + .sheet(isPresented: $isDownloadPresented, content: {VideoDownloadView(bvid: videoDetails["BV"]!, videoDetails: videoDetails)}) + Button(action: { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/history/toview/add", method: .post, parameters: ["bvid": videoDetails["BV"]!, "csrf": biliJct], headers: headers).response { response in + do { + let json = try JSON(data: response.data ?? Data()) + if let code = json["code"].int { + if code == 0 { + AlertKitAPI.present(title: String(localized: "Video.added"), icon: .done, style: .iOS17AppleMusic, haptic: .success) + } else { + AlertKitAPI.present(title: json["message"].string ?? String(localized: "Video.unkonwn-error"), icon: .error, style: .iOS17AppleMusic, haptic: .error) + } + } else { + AlertKitAPI.present(title: String(localized: "Video.unkonwn-error"), icon: .error, style: .iOS17AppleMusic, haptic: .error) + } + } catch { + AlertKitAPI.present(title: String(localized: "Video.unkonwn-error"), icon: .error, style: .iOS17AppleMusic, haptic: .error) + } + } + }, label: { + Label("Video.watch-later", systemImage: "memories.badge.plus") + }) + } + }) + } + if owner["ID"] != nil { + NavigationLink(destination: {UserDetailView(uid: owner["ID"]!)}, label: { + HStack { + AsyncImage(url: URL(string: owner["Face"]! + "@40w")) + .cornerRadius(100) + .frame(width: 40, height: 40) + VStack { + HStack { + Text(owner["Name"]!) + .font(.system(size: 16, weight: .bold)) + .lineLimit(1) + .minimumScaleFactor(0.1) + .foregroundColor(colorScheme == .dark ? .white : .black) + Spacer() + } + HStack { + Text("Video.fans.\(Int(String(ownerFansCount).shorter()) ?? 0)") + .font(.system(size: 11)) + .lineLimit(1) + .foregroundColor(.gray) + Spacer() + } + } + Spacer() + } + .padding(5) + .background(Color.gray.opacity(0.35)) + .cornerRadius(12) + }) + } + LazyVStack { + HStack { + Button(action: { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata); buvid3=\(globalBuvid3)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/web-interface/archive/like", method: .post, parameters: ["bvid": videoDetails["BV"]!, "like": isLiked ? 2 : 1, "eab_x": 2, "ramval": 0, "source": "web_normal", "ga": 1, "csrf": biliJct], headers: headers).response { response in + debugPrint(response) + isLiked ? AlertKitAPI.present(title: String(localized: "Video.action.canceled"), icon: .done, style: .iOS17AppleMusic, haptic: .success) : AlertKitAPI.present(title: String(localized: "Video.action.liked"), icon: .done, style: .iOS17AppleMusic, haptic: .success) + isLiked.toggle() + } + }, label: { + ZStack { + RoundedRectangle(cornerRadius: 12) + .foregroundStyle(isLiked ? Color(hex: 0xfa678e) : Color.gray.opacity(0.35)) + VStack { + Text(isLiked ? "" : "") + .font(.custom("bilibili", size: 20)) + .foregroundColor(isLiked ? .white : (colorScheme == .dark ? .white : .black)) + Text(stat["Like"]?.shorter() ?? "") + .font(.system(size: 11)) + .foregroundColor(isLiked ? .white : (colorScheme == .dark ? .white : .black)) + .opacity(isLiked ? 1 : 0.6) + .minimumScaleFactor(0.1) + .scaledToFit() + } + .padding(.vertical, 5) + } + }) + Button(action: { + if !isCoined { + isCoinViewPresented = true + } + }, label: { + ZStack { + RoundedRectangle(cornerRadius: 12) + .foregroundStyle(isCoined ? Color(hex: 0xfa678e) : Color.gray.opacity(0.35)) + VStack { + Text(isCoined ? "" : "") + .font(.custom("bilibili", size: 20)) + .foregroundColor(isCoined ? .white : (colorScheme == .dark ? .white : .black)) + Text(stat["Coin"]?.shorter() ?? "") + .font(.system(size: 11)) + .foregroundColor(isCoined ? .white : (colorScheme == .dark ? .white : .black)) + .opacity(isCoined ? 1 : 0.6) + .minimumScaleFactor(0.1) + .scaledToFit() + } + .padding(.vertical, 5) + } + }) + .sheet(isPresented: $isCoinViewPresented, content: { + VideoThrowCoinView(bvid: videoDetails["BV"]!) + .presentationDetents([.medium]) + }) + Button(action: { + isFavoriteChoosePresented = true + }, label: { + ZStack { + RoundedRectangle(cornerRadius: 12) + .foregroundStyle(isFavoured ? Color(hex: 0xfa678e) : Color.gray.opacity(0.35)) + VStack { + Text(isFavoured ? "" : "") + .font(.custom("bilibili", size: 20)) + .foregroundColor(isFavoured ? .white : (colorScheme == .dark ? .white : .black)) + Text(stat["Favorite"]?.shorter() ?? "") + .font(.system(size: 11)) + .foregroundColor(isFavoured ? .white : (colorScheme == .dark ? .white : .black)) + .opacity(isFavoured ? 1 : 0.6) + .minimumScaleFactor(0.1) + .scaledToFit() + } + .padding(.vertical, 5) + } + }) + } + .buttonBorderShape(.roundedRectangle(radius: 18)) + .sheet(isPresented: $isFavoriteChoosePresented, content: {VideoFavoriteAddView(videoDetails: $videoDetails, isFavoured: $isFavoured)}) + Spacer() + .frame(height: 10) + VStack { + HStack { + Image(systemName: "text.word.spacing") + Text("Video.details.danmaku.\(Int(videoDetails["Danmaku"]!.shorter()) ?? 0)") + Spacer() + } + HStack { + Image(systemName: "person.2") + Text("Video.details.watching-people.\(Int(nowPlayingCount) ?? 0)") + .offset(x: -1) + Spacer() + } + HStack { + Image(systemName: "play.circle") + Text("Video.details.watches.\(Int(videoDetails["View"]!.shorter()) ?? 0)") + .offset(x: 1) + Spacer() + } + HStack { + Image(systemName: "clock") + Text("Video.details.publish-time.\(publishTime)") + Spacer() + } + HStack { + Image(systemName: "movieclapper") + Text(videoDetails["BV"]!) + Spacer() + } + } + .font(.system(size: 11)) + .opacity(0.6) + .padding(.horizontal, 10) + Spacer() + .frame(height: 5) + HStack { + VStack { + Image(systemName: "info.circle") + Spacer() + } + Text(videoDesc) + Spacer() + } + .font(.system(size: 12)) + .opacity(0.65) + .padding(.horizontal, 8) + HStack { + VStack { + Image(systemName: "tag") + Spacer() + } + Text(tagText) + Spacer() + } + .font(.system(size: 12)) + .opacity(0.65) + .padding(.horizontal, 8) + .onAppear { + tagDisplayedNum = 0 + tagText = "" + for text in tags { + tagDisplayedNum += 1 + if tagDisplayedNum == tags.count { + tagText += text + } else { + tagText += text + " / " + } + } + } + } + } + .padding() + } + } else if currentDetailSelection == 2 { + CommentsView(oid: String(videoDetails["BV"]!.dropFirst().dropFirst())) + } else if currentDetailSelection == 3 { + if goodVideos.count != 0 { + List { + ForEach(0...goodVideos.count - 1, id: \.self) { i in + VideoCard(goodVideos[i]) + } + } + } else { + + } + } + } + } + .navigationTitle("Video") + .navigationBarTitleDisplayMode(.inline) + .onAppear { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/archive/has/like?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in + if isSuccess { + if respJson["data"].int ?? 0 == 1 { + isLiked = true + } + } + } + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/archive/coins?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in + if isSuccess { + if respJson["data"]["multiply"].int ?? 0 != 0 { + isCoined = true + } + } + } + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/v2/fav/video/favoured?aid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in + if isSuccess { + if respJson["data"]["favoured"].bool ?? false == true { + isFavoured = true + } + } + } + + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/archive/related?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in + if isSuccess { + let datas = respJson["data"] + for data in datas { + if data.1["bvid"].string == nil { + return + } + goodVideos.append(["Pic": data.1["pic"].string ?? "E", "Title": data.1["title"].string ?? "[加载失败]", "BV": data.1["bvid"].string!, "UP": data.1["owner"]["name"].string ?? "[加载失败]", "View": String(data.1["stat"]["view"].int ?? -1), "Danmaku": String(data.1["stat"]["danmaku"].int ?? -1)]) + } + } + } + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/view?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in + if isSuccess { + debugPrint("----------Prints from VideoDetailView.onAppear.*.requsetJSON(*/view)----------") + debugPrint(respJson) + if !CheckBApiError(from: respJson) { return } + owner = ["Name": respJson["data"]["owner"]["name"].string ?? "[加载失败]", "Face": respJson["data"]["owner"]["face"].string ?? "E", "ID": String(respJson["data"]["owner"]["mid"].int64 ?? -1)] + stat = ["Like": String(respJson["data"]["stat"]["like"].int ?? -1), "Coin": String(respJson["data"]["stat"]["coin"].int ?? -1), "Favorite": String(respJson["data"]["stat"]["favorite"].int ?? -1)] + videoDesc = respJson["data"]["desc"].string ?? "[加载失败]".replacingOccurrences(of: "\\n", with: "\n") + + // Publish time calculation + let df = DateFormatter() + df.dateFormat = "yyyy-MM-dd HH:mm:ss" + let pubTimestamp = respJson["data"]["pubdate"].int ?? 1 + publishTime = df.string(from: Date(timeIntervalSince1970: Double(pubTimestamp))) + + for _ in 1...4 { + honors.append("") + } + for honor in respJson["data"]["honor_reply"]["honor"] { + honors[honor.1["type"].int! - 1] = honor.1["desc"].string ?? "[加载失败]" + } + for page in respJson["data"]["pages"] { + videoPages.append(["CID": String(page.1["cid"].int ?? 0), "Index": String(page.1["page"].int ?? 0), "Title": page.1["part"].string ?? "[加载失败]"]) + } + if let mid = respJson["data"]["owner"]["mid"].int { + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/relation/stat?vmid=\(mid)", headers: headers) { respJson, isSuccess in + if isSuccess { + ownerFansCount = respJson["data"]["follower"].int ?? -1 + } + } + } + if let cid = respJson["data"]["pages"][0]["cid"].int { + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/player/online/total?bvid=\(videoDetails["BV"]!)&cid=\(cid)", headers: headers) { respJson, isSuccess in + if isSuccess { + nowPlayingCount = respJson["data"]["total"].string ?? "[加载失败]" + } + } + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/player/v2?bvid=\(videoDetails["BV"]!)&cid=\(cid)", headers: headers) { respJson, isSuccess in + debugPrint("----------Prints from VideoDetailView.onAppear.*.requsetJSON(*/player/v2)----------") + debugPrint(respJson) + if let subTitleUrl = respJson["data"]["subtitle"]["subtitles"][0]["subtitle_url"].string { + DarockKit.Network.shared.requestJSON(subTitleUrl) { respJson, isSuccess in + let subTitles = respJson["body"] + for subTitle in subTitles { + self.subTitles.append(["Start": String(subTitle.1["from"].double!), "End": String(subTitle.1["to"].double!), "Content": subTitle.1["content"].string!]) + } + } + } + } + } + } + } + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/tag/archive/tags?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in + if isSuccess { + debugPrint("----------Prints from VideoDetailView.onAppear.*.requsetJSON(*/tags)----------") + debugPrint(respJson) + for tag in respJson["data"] { + tags.append(tag.1["tag_name"].string ?? "[加载失败]") + } + } + } + if recordHistoryTime == "into" { + AF.request("https://api.bilibili.com/x/click-interface/web/heartbeat", method: .post, parameters: ["bvid": videoDetails["BV"]!, "mid": dedeUserID, "type": 3, "dt": 2, "play_type": 2, "csrf": biliJct], headers: headers).response { response in + debugPrint(response) + } + } + + if videoDetails["Title"]!.contains("") { + videoDetails["Title"] = "\(String(videoDetails["Title"]!.hasPrefix("") ? "" : (videoDetails["Title"]!.split(separator: "")[0])))\(String(videoDetails["Title"]!.split(separator: "")[videoDetails["Title"]!.hasPrefix("") ? 0 : 1].split(separator: "")[0]))\(String(videoDetails["Title"]!.hasSuffix("") ? "" : videoDetails["Title"]!.split(separator: "")[1]))" + } + + if videoGetterSource == "official" { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/view?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in + if !CheckBApiError(from: respJson) { return } + let cid = respJson["data"]["pages"][0]["cid"].int64! + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/player/playurl?bvid=\(videoDetails["BV"]!)&cid=\(cid)&qn=\(sessdata == "" ? 64 : 80)", headers: headers) { respJson, isSuccess in + if !CheckBApiError(from: respJson) { return } + VideoDetailView.willPlayVideoLink = respJson["data"]["durl"][0]["url"].string!.replacingOccurrences(of: "\\u0026", with: "&") + VideoDetailView.willPlayVideoCID = cid + VideoDetailView.willPlayVideoBV = videoDetails["BV"]! + isVideoPlayerPresented = true + isDecoded = true + } + } + } else if videoGetterSource == "injahow" { + DarockKit.Network.shared.requestString("https://api.injahow.cn/bparse/?bv=\(videoDetails["BV"]!.dropFirst().dropFirst())&p=1&type=video&q=80&format=mp4&otype=url") { respStr, isSuccess in + if isSuccess { + VideoDetailView.willPlayVideoLink = respStr + VideoDetailView.willPlayVideoBV = videoDetails["BV"]! + isVideoPlayerPresented = true + isDecoded = true + } + } + } + } + } + + struct VideoFavoriteAddView: View { + @Binding var videoDetails: [String: String] + @Binding var isFavoured: Bool + @Environment(\.dismiss) var dismiss + @AppStorage("DedeUserID") var dedeUserID = "" + @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" + @AppStorage("SESSDATA") var sessdata = "" + @AppStorage("bili_jct") var biliJct = "" + @State var favoriteFolderList = [[String: String]]() + @State var isFavoriteTargetIn = [Bool]() + @State var isItemLoading = [Bool]() + var body: some View { + NavigationStack { + List { + if isItemLoading.count != 0 { + ForEach(0..")[1].split(separator: "")[0] + let danmakuSpd = danmakuOnly.split(separator: "") + for singleDanmaku in danmakuSpd { + let p = singleDanmaku.split(separator: "").count < 2 { + return + } + let danmakuText = String(singleDanmaku.split(separator: "\">")[1].split(separator: "")[0]) + if stredSpdP[5] == "0" { + showDanmakus.append(["Appear": stredSpdP[0], "Type": stredSpdP[1], "Size": stredSpdP[2], "Color": stredSpdP[3], "Text": danmakuText]) + if showDanmakus.count >= 1000 { + break + } + } + } + showDanmakus.sort { dict1, dict2 in + if let time1 = dict1["Appear"], let time2 = dict2["Appear"] { + return Double(time1)! < Double(time2)! + } + return false + } + debugPrint(showDanmakus) + } + } + + Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in + danmakuTimer = timer + danmakuOffset = player.currentTime().seconds * 50 + } + } + } + .onDisappear { + guard !willBeginFullScreenPresentation else { + willBeginFullScreenPresentation = false + return + } + playerTimer?.invalidate() + danmakuTimer?.invalidate() + player?.pause() + player?.seek(to: .zero) + } + .overlay { + ZStack { + if isDanmakuEnabled { + VStack { + ForEach(0...4, id: \.self) { i in + ZStack { + ForEach(0.. player.currentTime().seconds { + Text(showDanmakus[j]["Text"]!) + .font(.system(size: 14)) + .foregroundColor(Color(hex: Int(showDanmakus[j]["Color"]!)!)) + .offset(x: Double(showDanmakus[j]["Appear"]!)! * 50) + } + } + } + } + } + } + Spacer() + } + .allowsHitTesting(false) + .offset(x: -danmakuOffset) + .animation(.smooth, value: danmakuOffset) + } + } + } + } + + func willBeginFullScreen(_ playerViewController: AVPlayerViewController, _ coordinator: UIViewControllerTransitionCoordinator) { + willBeginFullScreenPresentation = true + } + func willEndFullScreen(_ playerViewController: AVPlayerViewController,_ coordinator: UIViewControllerTransitionCoordinator) { + // This is a static helper method provided by AZVideoPlayer to keep + // the video playing if it was playing when full screen presentation ended + AZVideoPlayer.continuePlayingIfPlaying(player, coordinator) + } + + struct StrokeText: View { + let text: String + let width: CGFloat + let color: Color + + var body: some View { + ZStack { + ZStack { + Text(text).offset(x: width, y: width) + Text(text).offset(x: -width, y: -width) + Text(text).offset(x: -width, y: width) + Text(text).offset(x: width, y: -width) + } + .foregroundColor(color) + Text(text) + } + } + } +}