diff --git a/Model/Applications/InvidiousAPI.swift b/Model/Applications/InvidiousAPI.swift index ac4999b2..9aee3d4e 100644 --- a/Model/Applications/InvidiousAPI.swift +++ b/Model/Applications/InvidiousAPI.swift @@ -215,6 +215,10 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI { nil } + func channelByUsername(_: String) -> Resource? { + nil + } + func channelVideos(_ id: String) -> Resource { resource(baseURL: account.url, path: basePathAppending("channels/\(id)/latest")) } diff --git a/Model/Applications/PipedAPI.swift b/Model/Applications/PipedAPI.swift index e8b59bb6..01162622 100644 --- a/Model/Applications/PipedAPI.swift +++ b/Model/Applications/PipedAPI.swift @@ -44,6 +44,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI { self.extractChannel(from: content.json) } + configureTransformer(pathPattern("user/*")) { (content: Entity) -> Channel? in + self.extractChannel(from: content.json) + } + configureTransformer(pathPattern("playlists/*")) { (content: Entity) -> ChannelPlaylist? in self.extractChannelPlaylist(from: content.json) } @@ -133,6 +137,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI { resource(baseURL: account.url, path: "c/\(name)") } + func channelByUsername(_ username: String) -> Resource? { + resource(baseURL: account.url, path: "user/\(username)") + } + func channelVideos(_ id: String) -> Resource { channel(id) } diff --git a/Model/Applications/VideosAPI.swift b/Model/Applications/VideosAPI.swift index c2f55baf..d4c4b9d6 100644 --- a/Model/Applications/VideosAPI.swift +++ b/Model/Applications/VideosAPI.swift @@ -8,6 +8,7 @@ protocol VideosAPI { func channel(_ id: String) -> Resource func channelByName(_ name: String) -> Resource? + func channelByUsername(_ username: String) -> Resource? func channelVideos(_ id: String) -> Resource func trending(country: Country, category: TrendingCategory?) -> Resource func search(_ query: SearchQuery, page: String?) -> Resource diff --git a/Shared Tests/URLParserTests.swift b/Shared Tests/URLParserTests.swift index c7bbaa22..d2902205 100644 --- a/Shared Tests/URLParserTests.swift +++ b/Shared Tests/URLParserTests.swift @@ -16,6 +16,11 @@ final class URLParserTests: XCTestCase { "c/ABCDE": "ABCDE" ] + private static let users: [String: String] = [ + "https://m.youtube.com/user/ARD": "ARD", + "m.youtube.com/user/ARD": "ARD" + ] + private static let channelsByID: [String: String] = [ "https://piped.kavin.rocks/channel/UCbcxFkd6B9xUU54InHv4Tig": "UCbcxFkd6B9xUU54InHv4Tig", "youtube.com/channel/UCbcxFkd6B9xUU54InHv4Tig": "UCbcxFkd6B9xUU54InHv4Tig", @@ -68,6 +73,16 @@ final class URLParserTests: XCTestCase { } } + func testUsersParsing() throws { + Self.users.forEach { url, user in + let parser = URLParser(url: URL(string: url)!) + XCTAssertEqual(parser.destination, .channel) + XCTAssertNil(parser.channelID) + XCTAssertNil(parser.channelName) + XCTAssertEqual(parser.username, user) + } + } + func testPlaylistsParsing() throws { Self.playlists.forEach { url, id in let parser = URLParser(url: URL(string: url)!) diff --git a/Shared/OpenURLHandler.swift b/Shared/OpenURLHandler.swift index 0b38cfbb..ee929b7a 100644 --- a/Shared/OpenURLHandler.swift +++ b/Shared/OpenURLHandler.swift @@ -180,6 +180,10 @@ struct OpenURLHandler { return accounts.api.channel(id) } + if let resource = resourceForUsernameUrl(parser) { + return resource + } + guard let name = parser.channelName else { return nil } @@ -195,6 +199,20 @@ struct OpenURLHandler { return nil } + private func resourceForUsernameUrl(_ parser: URLParser) -> Resource? { + guard let username = parser.username else { return nil } + + if accounts.app.supportsOpeningChannelsByName { + return accounts.api.channelByUsername(username) + } + + if let instance = InstancesModel.all.first(where: { $0.app.supportsOpeningChannelsByName }) { + return instance.anonymous.channelByUsername(username) + } + + return nil + } + private func handleSearchUrlOpen(_ parser: URLParser) { #if os(macOS) if alertIfNoMainWindowOpen() { return } diff --git a/Shared/URLParser.swift b/Shared/URLParser.swift index 85dc31fe..64d84bf8 100644 --- a/Shared/URLParser.swift +++ b/Shared/URLParser.swift @@ -4,7 +4,7 @@ import Foundation struct URLParser { static let prefixes: [Destination: [String]] = [ .playlist: ["/playlist", "playlist"], - .channel: ["/c", "c", "/channel", "channel"], + .channel: ["/c", "c", "/channel", "channel", "/user", "user"], .search: ["/results", "search"] ] @@ -13,6 +13,21 @@ struct URLParser { case favorites, subscriptions, popular, trending } + var url: URL + + init(url: URL) { + self.url = url + let urlString = url.absoluteString + let scheme = urlComponents?.scheme + if scheme == nil, + urlString.contains("youtube.com"), + let url = URL(string: "https://\(urlString)" + ) + { + self.url = url + } + } + var destination: Destination? { if hasAnyOfPrefixes(path, ["favorites"]) { return .favorites } if hasAnyOfPrefixes(path, ["subscriptions"]) { return .subscriptions } @@ -34,8 +49,6 @@ struct URLParser { return .video } - var url: URL - var videoID: String? { if host == "youtu.be", !path.isEmpty { return String(path.suffix(from: path.index(path.startIndex, offsetBy: 1))) @@ -85,6 +98,12 @@ struct URLParser { return removePrefixes(path, Self.prefixes[.channel]!.map { [$0, "/"].joined() }) } + var username: String? { + guard hasAnyOfPrefixes(path, ["user/", "/user/"]) else { return nil } + + return removePrefixes(path, ["user/", "/user/"]) + } + private var host: String { urlComponents?.host ?? "" }