diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a97205..edf8adb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: - main jobs: build: - name: Build IPA + name: Build IPA and Mac Catalyst runs-on: macOS-latest steps: - name: Use Node.js 20 @@ -27,3 +27,15 @@ jobs: name: Sora-IPA path: build/Sora.ipa compression-level: 0 + + - name: Run macbuild.sh + run: | + chmod +x macbuild.sh + ./macbuild.sh + + - name: Upload Mac Catalyst artifact + uses: actions/upload-artifact@v4 + with: + name: Sora-Catalyst + path: build/Sora-catalyst.zip + compression-level: 0 \ No newline at end of file diff --git a/Sora.xcodeproj/project.pbxproj b/Sora.xcodeproj/project.pbxproj index 755cd34..3d8897f 100644 --- a/Sora.xcodeproj/project.pbxproj +++ b/Sora.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D42D13240200B4F2D2 /* EpisodeCell.swift */; }; 132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */; }; 132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */; }; + 1352BA712D1ABC30000A9AF9 /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1352BA702D1ABC30000A9AF9 /* URLSession.swift */; }; 13B3A4B22D1477F100BCC0D5 /* StorageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B3A4B12D1477F100BCC0D5 /* StorageSettingsView.swift */; }; /* End PBXBuildFile section */ @@ -73,6 +74,8 @@ 132417D42D13240200B4F2D2 /* EpisodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; }; 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; + 1352BA6F2D1AB113000A9AF9 /* Sora.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sora.entitlements; sourceTree = ""; }; + 1352BA702D1ABC30000A9AF9 /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = ""; }; 13B3A4B12D1477F100BCC0D5 /* StorageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSettingsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -126,6 +129,7 @@ 132417822D13198000B4F2D2 /* Sora */ = { isa = PBXGroup; children = ( + 1352BA6F2D1AB113000A9AF9 /* Sora.entitlements */, 132417C52D131AA500B4F2D2 /* Info.plist */, 132417912D1319E800B4F2D2 /* Utils */, 132417A52D131A0600B4F2D2 /* Views */, @@ -170,6 +174,7 @@ isa = PBXGroup; children = ( 132417952D1319E800B4F2D2 /* Notification.swift */, + 1352BA702D1ABC30000A9AF9 /* URLSession.swift */, ); path = Extensions; sourceTree = ""; @@ -376,6 +381,7 @@ 1324179E2D1319E800B4F2D2 /* MiruDataStruct.swift in Sources */, 1308CFBE2D19844D004CD38C /* MusicProgressSlider.swift in Sources */, 132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */, + 1352BA712D1ABC30000A9AF9 /* URLSession.swift in Sources */, 132417A02D1319E800B4F2D2 /* HistoryManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -504,6 +510,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Sora/Sora.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\""; @@ -524,6 +531,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = me.cranci.Sora; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -535,6 +543,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Sora/Sora.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\""; @@ -555,6 +564,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = me.cranci.Sora; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Sora.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate b/Sora.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate index ce2f924..946ce4e 100644 Binary files a/Sora.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate and b/Sora.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Sora/Sora.entitlements b/Sora/Sora.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/Sora/Sora.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/Sora/Utils/Extensions/URLSession.swift b/Sora/Utils/Extensions/URLSession.swift new file mode 100644 index 0000000..0ec1021 --- /dev/null +++ b/Sora/Utils/Extensions/URLSession.swift @@ -0,0 +1,18 @@ +// +// URLSession.swift +// Sora +// +// Created by Francesco on 24/12/24. +// + +import Foundation + +extension URLSession { + static let custom: URLSession = { + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = [ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + ] + return URLSession(configuration: configuration) + }() +} diff --git a/Sora/Utils/Modules/ModuleStruct.swift b/Sora/Utils/Modules/ModuleStruct.swift index 20a05b7..72a3203 100644 --- a/Sora/Utils/Modules/ModuleStruct.swift +++ b/Sora/Utils/Modules/ModuleStruct.swift @@ -71,6 +71,7 @@ struct ModuleStruct: Codable { struct Episodes: Codable, Hashable { let selector: String let order: String + let pattern: String } } } diff --git a/Sora/Utils/Modules/ModulesManager.swift b/Sora/Utils/Modules/ModulesManager.swift index 3240c17..2539e88 100644 --- a/Sora/Utils/Modules/ModulesManager.swift +++ b/Sora/Utils/Modules/ModulesManager.swift @@ -30,7 +30,7 @@ class ModulesManager: ObservableObject { completion(.failure(ModuleError.invalidURL)) return } - let task = URLSession.shared.dataTask(with: url) { data, response, error in + let task = URLSession.custom.dataTask(with: url) { data, response, error in guard let data = data, error == nil else { completion(.failure(error ?? ModuleError.unknown)) return @@ -67,7 +67,7 @@ class ModulesManager: ObservableObject { func refreshModules() { for (name, urlString) in moduleURLs { guard let url = URL(string: urlString) else { continue } - let task = URLSession.shared.dataTask(with: url) { data, response, error in + let task = URLSession.custom.dataTask(with: url) { data, response, error in guard let data = data, error == nil else { return } do { let updatedModule = try JSONDecoder().decode(ModuleStruct.self, from: data) diff --git a/Sora/Views/AnimeViews/AnimeInfoExtraction.swift b/Sora/Views/AnimeViews/AnimeInfoExtraction.swift index c00c5dc..bb4ff47 100644 --- a/Sora/Views/AnimeViews/AnimeInfoExtraction.swift +++ b/Sora/Views/AnimeViews/AnimeInfoExtraction.swift @@ -12,7 +12,7 @@ extension AnimeInfoView { func fetchAnimeDetails() { guard let url = URL(string: anime.href.hasPrefix("https") ? anime.href : "\(module.module[0].details.baseURL)\(anime.href)") else { return } - URLSession.shared.dataTask(with: url) { data, response, error in + URLSession.custom.dataTask(with: url) { data, response, error in defer { isLoading = false } guard let data = data, error == nil else { return } @@ -101,7 +101,7 @@ extension AnimeInfoView { guard let url = URL(string: urlString) else { return } Logger.shared.log("Pressed episode button") - URLSession.shared.dataTask(with: url) { data, response, error in + URLSession.custom.dataTask(with: url) { data, response, error in guard let data = data, error == nil else { return } let html = String(data: data, encoding: .utf8) ?? "" diff --git a/Sora/Views/AnimeViews/AnimeInfoView.swift b/Sora/Views/AnimeViews/AnimeInfoView.swift index 43dbf2d..cb4ce88 100644 --- a/Sora/Views/AnimeViews/AnimeInfoView.swift +++ b/Sora/Views/AnimeViews/AnimeInfoView.swift @@ -133,7 +133,7 @@ struct AnimeInfoView: View { .fontWeight(.bold) ForEach(episodes.indices, id: \.self) { index in - let episodeURL = "\(module.module[0].details.baseURL)\(episodes[index])" + let episodeURL = episodes[index].hasPrefix("https") ? episodes[index] : "\(module.module[0].details.baseURL)\(episodes[index])" let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(episodeURL)") let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(episodeURL)") let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0 @@ -278,7 +278,7 @@ struct AnimeInfoView: View { let parameters: [String: Any] = ["query": query] request.httpBody = try? JSONSerialization.data(withJSONObject: parameters) - URLSession.shared.dataTask(with: request) { data, response, error in + URLSession.custom.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(error)) return diff --git a/Sora/Views/AnimeViews/EpisodeCell/EpisodeCell.swift b/Sora/Views/AnimeViews/EpisodeCell/EpisodeCell.swift index b0145b6..f0d148d 100644 --- a/Sora/Views/AnimeViews/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/AnimeViews/EpisodeCell/EpisodeCell.swift @@ -60,7 +60,7 @@ struct EpisodeCell: View { return } - URLSession.shared.dataTask(with: url) { data, response, error in + URLSession.custom.dataTask(with: url) { data, response, error in if let error = error { print("Failed to fetch episode details: \(error)") DispatchQueue.main.async { diff --git a/Sora/Views/SearchViews/SearchResultsView.swift b/Sora/Views/SearchViews/SearchResultsView.swift index 4e8d357..f36dfde 100644 --- a/Sora/Views/SearchViews/SearchResultsView.swift +++ b/Sora/Views/SearchViews/SearchResultsView.swift @@ -175,7 +175,7 @@ struct SearchResultsView: View { let urlString = "\(module.module[0].search.url)?\(module.module[0].search.parameter)=\(encodedSearchText)" guard let url = URL(string: urlString) else { return } - URLSession.shared.dataTask(with: url) { data, response, error in + URLSession.custom.dataTask(with: url) { data, response, error in defer { isLoading = false } guard let data = data, error == nil else { return } diff --git a/macbuild.sh b/macbuild.sh new file mode 100755 index 0000000..1472471 --- /dev/null +++ b/macbuild.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -e + +cd "$(dirname "$0")" + +WORKING_LOCATION="$(pwd)" +APPLICATION_NAME=Sora + +if [ ! -d "build" ]; then + mkdir build +fi + +cd build + +xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \ + -scheme "$APPLICATION_NAME" \ + -configuration Release \ + -derivedDataPath "$WORKING_LOCATION/build/DerivedDataApp" \ + -destination 'platform=macOS,variant=Mac Catalyst' \ + clean build \ + CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" + +DD_APP_PATH="$WORKING_LOCATION/build/DerivedDataApp/Build/Products/Release-maccatalyst/$APPLICATION_NAME.app" +TARGET_APP="$WORKING_LOCATION/build/$APPLICATION_NAME.app" + +if [ -e "$TARGET_APP" ]; then + rm -rf "$TARGET_APP" +fi + +cp -r "$DD_APP_PATH" "$TARGET_APP" + +codesign --remove "$TARGET_APP" +if [ -e "$TARGET_APP/_CodeSignature" ]; then + rm -rf "$TARGET_APP/_CodeSignature" +fi + +zip -vr "$APPLICATION_NAME-catalyst.zip" "$APPLICATION_NAME.app" +rm -rf "$APPLICATION_NAME.app"