diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c4b3c63308..61502df53c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -838,7 +838,6 @@ D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */; }; D62EC3BA2C246A7000FC9D04 /* YoutublePlayerNavigationHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62EC3B82C246A5600FC9D04 /* YoutublePlayerNavigationHandlerTests.swift */; }; D62EC3BC2C2470E000FC9D04 /* DuckPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62EC3BB2C2470E000FC9D04 /* DuckPlayerTests.swift */; }; - D62EC3BE2C24710F00FC9D04 /* DuckPlayerURLExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62EC3BD2C24710F00FC9D04 /* DuckPlayerURLExtensionTests.swift */; }; D62EC3C22C248AF800FC9D04 /* DuckNavigationHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62EC3C12C248AF800FC9D04 /* DuckNavigationHandling.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */; }; @@ -848,7 +847,6 @@ D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */; }; D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */; }; D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */; }; - D65625902C22D307006EF297 /* DuckPlayerURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */; }; D65625922C22D340006EF297 /* DuckPlayerNavigationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63FF8892C1B21C2006DE24D /* DuckPlayerNavigationHandler.swift */; }; D65625952C22D382006EF297 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */; }; D65625A12C232F5E006EF297 /* SettingsDuckPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65625A02C232F5E006EF297 /* SettingsDuckPlayerView.swift */; }; @@ -877,6 +875,7 @@ D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */; }; D6B67A122C332B6E002122EB /* DuckPlayerMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B67A112C332B6E002122EB /* DuckPlayerMocks.swift */; }; + D6BC8ACB2C5AA3860025375B /* DuckPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = D6BC8ACA2C5AA3860025375B /* DuckPlayer */; }; D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; @@ -2545,12 +2544,10 @@ D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptorTests.swift; sourceTree = ""; }; D62EC3B82C246A5600FC9D04 /* YoutublePlayerNavigationHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutublePlayerNavigationHandlerTests.swift; sourceTree = ""; }; D62EC3BB2C2470E000FC9D04 /* DuckPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerTests.swift; sourceTree = ""; }; - D62EC3BD2C24710F00FC9D04 /* DuckPlayerURLExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerURLExtensionTests.swift; sourceTree = ""; }; D62EC3C12C248AF800FC9D04 /* DuckNavigationHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckNavigationHandling.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxLogoNavbarTitle.swift; sourceTree = ""; }; D63FF8892C1B21C2006DE24D /* DuckPlayerNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerNavigationHandler.swift; sourceTree = ""; }; - D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerURLExtension.swift; sourceTree = ""; }; D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubePlayerUserScript.swift; sourceTree = ""; }; D63FF8942C1B67E8006DE24D /* YoutubeOverlayUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubeOverlayUserScript.swift; sourceTree = ""; }; D63FF8972C1B6A45006DE24D /* DuckPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayer.swift; sourceTree = ""; }; @@ -2907,6 +2904,7 @@ 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */, F486D33425069BBB002D07D7 /* Kingfisher in Frameworks */, EE8E568A2A56BCE400F11DCA /* NetworkProtection in Frameworks */, + D6BC8ACB2C5AA3860025375B /* DuckPlayer in Frameworks */, CBC83E3429B631780008E19C /* Configuration in Frameworks */, D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */, 851F74262B9A1BFD00747C42 /* Suggestions in Frameworks */, @@ -4801,7 +4799,6 @@ D6B67A112C332B6E002122EB /* DuckPlayerMocks.swift */, D62EC3BB2C2470E000FC9D04 /* DuckPlayerTests.swift */, D62EC3B82C246A5600FC9D04 /* YoutublePlayerNavigationHandlerTests.swift */, - D62EC3BD2C24710F00FC9D04 /* DuckPlayerURLExtensionTests.swift */, ); name = DuckPlayer; sourceTree = ""; @@ -4815,7 +4812,6 @@ D6037E682C32F2E7009AAEC0 /* DuckPlayerSettings.swift */, D62EC3C12C248AF800FC9D04 /* DuckNavigationHandling.swift */, D63FF8892C1B21C2006DE24D /* DuckPlayerNavigationHandler.swift */, - D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */, D63FF8942C1B67E8006DE24D /* YoutubeOverlayUserScript.swift */, D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */, 31860A5A2C57ED2D005561F5 /* DuckPlayerStorage.swift */, @@ -6199,6 +6195,7 @@ D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */, 858D009C2B9799FC004E5B4C /* History */, 851F74252B9A1BFD00747C42 /* Suggestions */, + D6BC8ACA2C5AA3860025375B /* DuckPlayer */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -7155,7 +7152,6 @@ D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, 1DEAADEE2BA45DFE00E25A97 /* SettingsDataClearingView.swift in Sources */, - D65625902C22D307006EF297 /* DuckPlayerURLExtension.swift in Sources */, 6F96FF102C2B128500162692 /* NewTabPageCustomizeButtonView.swift in Sources */, EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */, 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */, @@ -7338,7 +7334,6 @@ 569437342BE4E41500C0881B /* SyncErrorHandlerSyncErrorsAlertsTests.swift in Sources */, 85C11E4120904BBE00BFFEB4 /* VariantManagerTests.swift in Sources */, F1134ECE1F40EA9C00B73467 /* AtbParserTests.swift in Sources */, - D62EC3BE2C24710F00FC9D04 /* DuckPlayerURLExtensionTests.swift in Sources */, F189AEE41F18FDAF001EBAE1 /* LinkTests.swift in Sources */, F1BDDBFE2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift in Sources */, F1BDDC022C340DDF00459306 /* SyncManagementViewModelTests.swift in Sources */, @@ -10286,7 +10281,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 177.0.2; + version = 178.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { @@ -10516,6 +10511,11 @@ package = 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */; productName = ZIPFoundation; }; + D6BC8ACA2C5AA3860025375B /* DuckPlayer */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = DuckPlayer; + }; EE8E56892A56BCE400F11DCA /* NetworkProtection */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a71aa37e8c..9ce377cc91 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "ece50dc4a946de4d1d82c50a75a7dac101a7fb70", - "version" : "177.0.2" + "revision" : "ebad3db46e09913ac4c0e0c62085dcb40a6d6253", + "version" : "178.0.0" } }, { diff --git a/DuckDuckGo/AddressDisplayHelper.swift b/DuckDuckGo/AddressDisplayHelper.swift index a65a4e7a09..ff0ba4c022 100644 --- a/DuckDuckGo/AddressDisplayHelper.swift +++ b/DuckDuckGo/AddressDisplayHelper.swift @@ -18,6 +18,7 @@ // import Foundation +import DuckPlayer extension OmniBar { diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 29f0ebab75..86e3109d35 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -23,6 +23,7 @@ import WebKit import Core import Common import BrowserServicesKit +import DuckPlayer final class DuckPlayerNavigationHandler { diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift b/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift deleted file mode 100644 index 4b89c7bf4d..0000000000 --- a/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift +++ /dev/null @@ -1,165 +0,0 @@ -// -// DuckPlayerURLExtension.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Core - -extension String { - - var url: URL? { - return URL(trimmedAddressBarString: self) - } -} - -extension URL { - - static let duckPlayerHost: String = "player" - - static func duckPlayer(_ videoID: String, timestamp: String? = nil) -> URL { - let url = "\(NavigationalScheme.duck.rawValue)://player/\(videoID)".url! - return url.addingTimestamp(timestamp) - } - - static func youtubeNoCookie(_ videoID: String, timestamp: String? = nil) -> URL { - let url = "https://www.youtube-nocookie.com/embed/\(videoID)".url! - return url.addingTimestamp(timestamp) - } - - static func youtube(_ videoID: String, timestamp: String? = nil) -> URL { - #if os(iOS) - let baseUrl = "https://m.youtube.com/watch?v=\(videoID)" - #else - let baseUrl = "https://www.youtube.com/watch?v=\(videoID)" - #endif - - let url = URL(string: baseUrl)! - return url.addingTimestamp(timestamp) - } - - var isDuckURLScheme: Bool { - navigationalScheme == .duck - } - - private var isYoutubeWatch: Bool { - guard let host else { return false } - return host.contains("youtube.com") && path == "/watch" - } - - private var isYoutubeNoCookie: Bool { - host == "www.youtube-nocookie.com" && pathComponents.count == 3 && pathComponents[safe: 1] == "embed" - } - - /// Returns true only if the URL represents a playlist itself, i.e. doesn't have `index` query parameter - var isYoutubePlaylist: Bool { - guard isYoutubeWatch, let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { - return false - } - - let isPlaylistURL = components.queryItems?.contains(where: { $0.name == "list" }) == true && - components.queryItems?.contains(where: { $0.name == "v" }) == true && - components.queryItems?.contains(where: { $0.name == "index" }) == false - - return isPlaylistURL - } - - /// Returns true if the URL represents a YouTube video, but not the playlist (playlists are not supported by Private Player) - var isYoutubeVideo: Bool { - isYoutubeWatch && !isYoutubePlaylist - } - - /// Attempts extracting video ID and timestamp from the URL. Works with all types of YouTube URLs. - var youtubeVideoParams: (videoID: String, timestamp: String?)? { - if isDuckURLScheme { - guard let components = URLComponents(string: absoluteString) else { - return nil - } - let unsafeVideoID = components.path - let timestamp = components.queryItems?.first(where: { $0.name == "t" })?.value - return (unsafeVideoID.removingCharacters(in: .youtubeVideoIDNotAllowed), timestamp) - } - - if isDuckPlayer { - let unsafeVideoID = lastPathComponent - let timestamp = getParameter(named: "t") - return (unsafeVideoID.removingCharacters(in: .youtubeVideoIDNotAllowed), timestamp) - } - - guard isYoutubeVideo, - let components = URLComponents(url: self, resolvingAgainstBaseURL: false), - let unsafeVideoID = components.queryItems?.first(where: { $0.name == "v" })?.value - else { - return nil - } - - let timestamp = components.queryItems?.first(where: { $0.name == "t" })?.value - return (unsafeVideoID.removingCharacters(in: .youtubeVideoIDNotAllowed), timestamp) - } - - - var isDuckPlayer: Bool { - let isPrivatePlayer = isDuckURLScheme && host == Self.duckPlayerHost - return isPrivatePlayer || isYoutubeNoCookie - - } - - var isYoutube: Bool { - guard let host else { return false } - return host == "m.youtube.com" || host == "youtube.com" - - } - - func addingWatchInYoutubeQueryParameter() -> URL? { - guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { - return nil - } - - var queryItems = components.queryItems ?? [] - queryItems.append(URLQueryItem(name: "embeds_referring_euri", value: "some_value")) - components.queryItems = queryItems - - return components.url - } - - var hasWatchInYoutubeQueryParameter: Bool { - guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false), - let queryItems = components.queryItems else { - return false - } - - for queryItem in queryItems where queryItem.name == "embeds_referring_euri" { - return true - } - - return false - } - - private func addingTimestamp(_ timestamp: String?) -> URL { - guard let timestamp = timestamp, - let regex = try? NSRegularExpression(pattern: "^(\\d+[smh]?)+$"), - timestamp.matches(regex) - else { - return self - } - return appendingParameter(name: "t", value: timestamp) - } -} - -extension CharacterSet { - static let youtubeVideoIDNotAllowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_").inverted -} diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index d5185c7354..0370ec8daa 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -24,6 +24,7 @@ import UserScript import Combine import Core import BrowserServicesKit +import DuckPlayer final class YoutubeOverlayUserScript: NSObject, Subfeature { diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index f020b9a6d8..ef6a351698 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -22,6 +22,7 @@ import UIKit import Core import PrivacyDashboard import DesignResourcesKit +import DuckPlayer extension OmniBar: NibLoading {} diff --git a/DuckDuckGo/TabViewGridCell.swift b/DuckDuckGo/TabViewGridCell.swift index 19278f69be..ec131790f0 100644 --- a/DuckDuckGo/TabViewGridCell.swift +++ b/DuckDuckGo/TabViewGridCell.swift @@ -19,6 +19,7 @@ import UIKit import Core +import DuckPlayer class TabViewGridCell: TabViewCell { diff --git a/DuckDuckGo/TabViewListCell.swift b/DuckDuckGo/TabViewListCell.swift index d6a4976d9c..eedb9cb8d7 100644 --- a/DuckDuckGo/TabViewListCell.swift +++ b/DuckDuckGo/TabViewListCell.swift @@ -19,6 +19,7 @@ import UIKit import Core +import DuckPlayer class TabViewListCell: TabViewCell { diff --git a/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift b/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift deleted file mode 100644 index d45e6ca8f7..0000000000 --- a/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// DuckPlayerURLExtensionTests.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import XCTest -import os.log -@testable import DuckDuckGo - -final class DuckPlayerURLExtensionTests: XCTestCase { - - #if os(iOS) - let baseUrl = "https://m.youtube.com" - #else - let baseUrl = "https://www.youtube.com" - #endif - - func testIsDuckPlayerScheme() { - XCTAssertTrue("duck:player/abcdef12345".url!.isDuckURLScheme) - XCTAssertTrue("duck://player/abcdef12345".url!.isDuckURLScheme) - XCTAssertTrue("duck://player/abcdef".url!.isDuckURLScheme) - XCTAssertTrue("duck://player/12345".url!.isDuckURLScheme) - XCTAssertFalse("http://duckplayer/abcdef12345".url!.isDuckURLScheme) - XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345".url!.isDuckURLScheme) - XCTAssertFalse("https://www.youtube-nocookie.com/embed/abcdef12345".url!.isDuckURLScheme) - } - - func testIsDuckPlayer() { - XCTAssertTrue("https://www.youtube-nocookie.com/embed/abcdef12345".url!.isDuckPlayer) - XCTAssertTrue("https://www.youtube-nocookie.com/embed/abcdef12345?t=23s".url!.isDuckPlayer) - - XCTAssertFalse("https://www.youtube-nocookie.com/embed".url!.isDuckPlayer) - XCTAssertFalse("https://www.youtube-nocookie.com/embed?t=23s".url!.isDuckPlayer) - - XCTAssertTrue("duck://player/abcdef12345".url!.isDuckPlayer) - XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345".url!.isDuckPlayer) - XCTAssertFalse("https://duckduckgo.com".url!.isDuckPlayer) - } - - func testIsYoutubePlaylist() { - XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubePlaylist) - XCTAssertTrue("\(baseUrl)/watch?list=abcdefgh12345678&v=abcdef12345".url!.isYoutubePlaylist) - - XCTAssertFalse("https://duckduckgo.com/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubePlaylist) - XCTAssertFalse("\(baseUrl)/watch?list=abcdefgh12345678".url!.isYoutubePlaylist) - XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678&index=1".url!.isYoutubePlaylist) - } - - func testIsYoutubeVideo() { - XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345".url!.isYoutubeVideo) - XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678&index=1".url!.isYoutubeVideo) - XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345&t=5m".url!.isYoutubeVideo) - - XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubeVideo) - XCTAssertFalse("https://duckduckgo.com/watch?v=abcdef12345".url!.isYoutubeVideo) - } - - func testYoutubeVideoParamsFromDuckPlayerURL() { - let params = "duck://player/abcdef12345".url!.youtubeVideoParams - XCTAssertEqual(params?.videoID, "abcdef12345") - XCTAssertEqual(params?.timestamp, nil) - - let paramsWithTimestamp = "duck://player/abcdef12345?t=23s".url!.youtubeVideoParams - XCTAssertEqual(paramsWithTimestamp?.videoID, "abcdef12345") - XCTAssertEqual(paramsWithTimestamp?.timestamp, "23s") - } - - func testYoutubeVideoParamsFromYoutubeURL() { - let params = "\(baseUrl)/watch?v=abcdef12345".url!.youtubeVideoParams - XCTAssertEqual(params?.videoID, "abcdef12345") - XCTAssertEqual(params?.timestamp, nil) - - let paramsWithTimestamp = "\(baseUrl)/watch?v=abcdef12345&t=23s".url!.youtubeVideoParams - XCTAssertEqual(paramsWithTimestamp?.videoID, "abcdef12345") - XCTAssertEqual(paramsWithTimestamp?.timestamp, "23s") - - let paramsWithTimestampWithoutUnits = "\(baseUrl)/watch?t=102&v=abcdef12345&feature=youtu.be".url!.youtubeVideoParams - XCTAssertEqual(paramsWithTimestampWithoutUnits?.videoID, "abcdef12345") - XCTAssertEqual(paramsWithTimestampWithoutUnits?.timestamp, "102") - } - - func testYoutubeVideoParamsFromYoutubeNocookieURL() { - let params = "https://www.youtube-nocookie.com/embed/abcdef12345".url!.youtubeVideoParams - XCTAssertEqual(params?.videoID, "abcdef12345") - XCTAssertEqual(params?.timestamp, nil) - - let paramsWithTimestamp = "https://www.youtube-nocookie.com/embed/abcdef12345?t=23s".url!.youtubeVideoParams - XCTAssertEqual(paramsWithTimestamp?.videoID, "abcdef12345") - XCTAssertEqual(paramsWithTimestamp?.timestamp, "23s") - } - - func testDuckPlayerURLTimestampValidation() { - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: nil).absoluteString, "duck://player/abcdef12345") - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: "23s").absoluteString, "duck://player/abcdef12345?t=23s") - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: "5m5s").absoluteString, "duck://player/abcdef12345?t=5m5s") - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: "12h400m100s").absoluteString, "duck://player/abcdef12345?t=12h400m100s") - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: "12h2s2h").absoluteString, "duck://player/abcdef12345?t=12h2s2h") - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: "5m5m5m").absoluteString, "duck://player/abcdef12345?t=5m5m5m") - - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: "5").absoluteString, "duck://player/abcdef12345?t=5") - XCTAssertEqual(URL.duckPlayer("abcdef12345", timestamp: "10d").absoluteString, "duck://player/abcdef12345") - } - - func testYoutubeURLTimestampValidation() { - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: nil).absoluteString, "\(baseUrl)/watch?v=abcdef12345") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "23s").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=23s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5s").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=5m5s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h400m100s").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=12h400m100s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h2s2h").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=12h2s2h") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5m5m").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=5m5m5m") - - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=5") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "10d").absoluteString, "\(baseUrl)/watch?v=abcdef12345") - } - - func testYoutubeNoCookieURLTimestampValidation() { - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: nil).absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345") - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: "23s").absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345?t=23s") - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: "5m5s").absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345?t=5m5s") - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: "12h400m100s").absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345?t=12h400m100s") - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: "12h2s2h").absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345?t=12h2s2h") - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: "5m5m5m").absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345?t=5m5m5m") - - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: "5").absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345?t=5") - XCTAssertEqual(URL.youtubeNoCookie("abcdef12345", timestamp: "10d").absoluteString, "https://www.youtube-nocookie.com/embed/abcdef12345") - } - -}