├── .gitignore ├── .swiftpm ├── configuration │ └── Package.resolved └── xcode │ ├── package.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ └── ScrobbleKit.xcscheme ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── ScrobbleKit │ ├── Extensions │ ├── Bool+Int.swift │ ├── OptinalStringInitializers.swift │ ├── SBKParameter+URLQueryItem.swift │ ├── String+MD5.swift │ ├── URLQuertyItem+Numeric.swift │ └── URLQueryItem+Bool.swift │ ├── Helpers │ ├── Error │ │ ├── SBKClientError.swift │ │ ├── SBKError.swift │ │ ├── SBKErrorMessage.swift │ │ └── SBKScrobbleError.swift │ ├── SBKHttpMethod.swift │ ├── SBKLanguage.swift │ ├── SBKMethod.swift │ └── SBKParameter.swift │ ├── Internal │ ├── Models │ │ └── Common │ │ │ ├── NumericStringDecoder.swift │ │ │ ├── SBKCorrectedResponse.swift │ │ │ ├── SBKEmptyResponse.swift │ │ │ ├── SBKSearchAttribute.swift │ │ │ ├── SBKSearchQuery.swift │ │ │ ├── SBKSearchResultsResponse.swift │ │ │ ├── SBKThrowable.swift │ │ │ └── SBKTimestamp.swift │ └── Services │ │ ├── Common │ │ ├── AddTagsService.swift │ │ └── RemoveTagService.swift │ │ ├── Methods │ │ ├── Album │ │ │ ├── AlbumGetInfoService.swift │ │ │ ├── AlbumGetTagsService.swift │ │ │ ├── AlbumGetTopTagsService.swift │ │ │ ├── AlbumSearchService.swift │ │ │ └── Model │ │ │ │ ├── SBKAddTagAlbumResponse.swift │ │ │ │ ├── SBKAlbumRequestResponseList.swift │ │ │ │ ├── SBKAlbumSearchResultListResponse.swift │ │ │ │ └── SBKAlbumTopTagsResponse.swift │ │ ├── Artist │ │ │ ├── ArtistGetCorrectionService.swift │ │ │ ├── ArtistGetInfoService.swift │ │ │ ├── ArtistGetSimilarService.swift │ │ │ ├── ArtistGetTopAlbumsService.swift │ │ │ ├── ArtistGetTopTracksService.swift │ │ │ ├── ArtistSearchService.swift │ │ │ └── Model │ │ │ │ ├── ArtistGetTagsService.swift │ │ │ │ ├── SBKArtistGetInfoRequestResponse.swift │ │ │ │ ├── SBKArtistGetInfoSimilarArtist.swift │ │ │ │ ├── SBKArtistGetSimilarResponse.swift │ │ │ │ ├── SBKArtistGetTopAlbumsResponse.swift │ │ │ │ ├── SBKArtistSearchResultsList.swift │ │ │ │ ├── SBKArtistTopTracksResponse.swift │ │ │ │ └── SBKManager+TagGetWeeklyChartList.swift │ │ ├── Auth │ │ │ └── AuthSessionService.swift │ │ ├── Chart │ │ │ ├── ChartGetTopArtistsService.swift │ │ │ ├── ChartGetTopTracksService.swift │ │ │ ├── ChartsGetTopTagsService.swift │ │ │ └── Model │ │ │ │ ├── SBKArtistTopChartsRequestResponse.swift │ │ │ │ ├── SBKChartGetTopTagsResponse.swift │ │ │ │ └── SBKChartGetTopTracksResponse.swift │ │ ├── Geo │ │ │ ├── GeoGetTopArtistsService.swift │ │ │ ├── GeoGetTopTracksService.swift │ │ │ └── Model │ │ │ │ └── SBKGeoTopArtistsResponse copy.swift │ │ ├── Library │ │ │ ├── LibraryGetArtistsService.swift │ │ │ └── Model │ │ │ │ └── SBKLibraryGetArtistsResponse.swift │ │ ├── Tag │ │ │ ├── TagGetInfoService.swift │ │ │ ├── TagGetSimilarService.swift │ │ │ ├── TagGetTopAlbumsService.swift │ │ │ ├── TagGetTopArtistsService.swift │ │ │ ├── TagGetTopTagsService.swift │ │ │ ├── TagGetTopTracksService.swift │ │ │ └── TagGetWeeklyChartListService.swift │ │ ├── Track │ │ │ ├── Model │ │ │ │ ├── SBKScrobbleList.swift │ │ │ │ ├── SBKTrackGetSimilarResponse.swift │ │ │ │ ├── SBKTrackRequestResponseList.swift │ │ │ │ ├── SBKTrackUpdateNowPlayingCode.swift │ │ │ │ └── SBKTrackUpdateNowPlayingResponse.swift │ │ │ ├── SBKTrackSearchResultListResponse.swift │ │ │ ├── ScrobbleService.swift │ │ │ ├── TrackCorrectionService.swift │ │ │ ├── TrackGetInfoService.swift │ │ │ ├── TrackGetSimilarService.swift │ │ │ ├── TrackGetTagsService.swift │ │ │ ├── TrackGetTopTagsService.swift │ │ │ ├── TrackLoveService.swift │ │ │ ├── TrackSearchService.swift │ │ │ ├── TrackUnloveService.swift │ │ │ └── TrackUpdateNowPlayingService.swift │ │ └── User │ │ │ ├── Model │ │ │ ├── SBKUserGetFriendsResponse.swift │ │ │ ├── SBKUserGetRecentTracksResponse.swift │ │ │ ├── SBKUserGetTopAlbumsResponse.swift │ │ │ ├── SBKUserInfoResponse.swift │ │ │ ├── SBKUserPersonalTags.swift │ │ │ ├── SBKUserTagsResponse.swift │ │ │ └── UserGetLovedTracksResponse.swift │ │ │ ├── UserGetFriendsService.swift │ │ │ ├── UserGetInfoService.swift │ │ │ ├── UserGetLovedTracksService.swift │ │ │ ├── UserGetPersonalTagsService.swift │ │ │ ├── UserGetRecentTracksService.swift │ │ │ ├── UserGetTopAlbumsService.swift │ │ │ ├── UserGetTopArtistsService.swift │ │ │ ├── UserGetTopTagsService.swift │ │ │ └── UserGetTopTracksService.swift │ │ └── Protocols │ │ ├── SBKAuthenticatedService.swift │ │ ├── SBKOptionallyAuthenticatedService.swift │ │ └── SBKService.swift │ ├── Public │ ├── Helpers │ │ ├── SBKURLBuilder.swift │ │ └── SBKURLBuilderError.swift │ ├── Methods │ │ ├── Album │ │ │ ├── AddTags │ │ │ │ └── SBKManager+AlbumAddTags.swift │ │ │ ├── GetInfo │ │ │ │ └── SBKManager+AlbumGetInfo.swift │ │ │ ├── GetTags │ │ │ │ └── SBKManager+AlbumGetTags.swift │ │ │ ├── GetTopTags │ │ │ │ └── SBKManager+AlbumGetTopTags.swift │ │ │ ├── RemoveTag │ │ │ │ └── SBKManager+AlbumRemoveTagService.swift │ │ │ └── Search │ │ │ │ └── SBKManager+AlbumSearchService.swift │ │ ├── Artist │ │ │ ├── AddTags │ │ │ │ └── SBKManager+ArtistAddTags.swift │ │ │ ├── GetCorrection │ │ │ │ └── SBKManager+ArtistGetCorrectionService.swift │ │ │ ├── GetInfo │ │ │ │ └── SBKManager+ArtistGetInfoService.swift │ │ │ ├── GetSimilar │ │ │ │ └── SBKManager+ArtistGetSimilar.swift │ │ │ ├── GetTags │ │ │ │ └── SBKManager+ArtistGetTagsService.swift │ │ │ ├── GetTopAlbums │ │ │ │ └── SBKManager+ArtistGetTopAlbums.swift │ │ │ ├── GetTopTracks │ │ │ │ └── SBKManager+ArtistGetTopTracksService.swift │ │ │ ├── RemoveTag │ │ │ │ └── SBKManager+ArtistRemoveTagService.swift │ │ │ └── Search │ │ │ │ └── SBKManager+ArtistSearchService.swift │ │ ├── Chart │ │ │ ├── GetTopArtists │ │ │ │ └── SBKManager+ChartGetTopArtistsService.swift │ │ │ ├── GetTopTags │ │ │ │ └── SBKManager+ChartGetTopTagsService.swift │ │ │ └── GetTopTracks │ │ │ │ └── SBKManager+ChartGetTopTracksService.swift │ │ ├── Geo │ │ │ ├── GetTopArtists │ │ │ │ └── SBKManager+GeoGetTopArtistsService.swift │ │ │ ├── GetTopTracks │ │ │ │ └── SBKManager+GeoGetTopTracksService.swift │ │ │ └── Model │ │ │ │ └── SBKCountry.swift │ │ ├── Library │ │ │ └── SBKService+LibraryGetArtistsService.swift │ │ ├── Tag │ │ │ ├── SBKManager+TagGetInfo.swift │ │ │ ├── SBKManager+TagGetSimilar.swift │ │ │ ├── SBKManager+TagGetTopAlbums.swift │ │ │ ├── SBKManager+TagGetTopArtistsService.swift │ │ │ ├── SBKManager+TagGetTopTagsService.swift │ │ │ └── SBKService+TagGetTopTracksService.swift │ │ ├── Track │ │ │ ├── AddTag │ │ │ │ └── SBKManager+TrackAddTags.swift │ │ │ ├── GetCorrection │ │ │ │ └── SBKManager+TrackCorrectionService.swift │ │ │ ├── GetInfo │ │ │ │ └── SBKManager+TrackGetInfoService.swift │ │ │ ├── GetSimilar │ │ │ │ └── SBKManager+TrackGetSimilarService.swift │ │ │ ├── GetTags │ │ │ │ └── SBKmanager+TrackGetTagsService.swift │ │ │ ├── GetTopTags │ │ │ │ └── SBKManager+TrackGetTopTagsService.swift │ │ │ ├── Love │ │ │ │ └── SBKManager+TrackLoveService.swift │ │ │ ├── RemoveTag │ │ │ │ └── SBKManager+TrackRemoveTag.swift │ │ │ ├── SBKTrackSearchMethod.swift │ │ │ ├── Scrobble │ │ │ │ └── SBKManager+Scrobble.swift │ │ │ ├── Search │ │ │ │ └── SBKManager+TrackSearchService.swift │ │ │ ├── Unlove │ │ │ │ └── SBKManager+TrackUnloveService.swift │ │ │ └── UpdateNowPlaying │ │ │ │ └── SBKManager+TrackUpdateNowPlayingService.swift │ │ └── User │ │ │ ├── GetFriends │ │ │ └── SBKManager+UserGetFriendsService.swift │ │ │ ├── GetInfo │ │ │ └── SBKManager+UserGetInfoService.swift │ │ │ ├── GetLovedTracks │ │ │ └── SBKManager+UserGetLovedTracksService.swift │ │ │ ├── GetPersonalTags │ │ │ └── SBKManager+UserGetPersonalTagsService.swift │ │ │ ├── GetRecentTracks │ │ │ └── SBKManager+UserGetRecentTracksService.swift │ │ │ ├── GetTopAlbums │ │ │ └── SBKManager+UserGetTopAlbumsService.swift │ │ │ ├── GetTopArtists │ │ │ └── SBKManager+UserGetTopArtistsService.swift │ │ │ ├── GetTopTags │ │ │ └── SBKManager+UserGetTopTagsService.swift │ │ │ └── GetTopTracks │ │ │ └── SBKManager+UserGetTopTracksService.swift │ └── Models │ │ ├── SBKAlbum.swift │ │ ├── SBKAlbumSearchMethod.swift │ │ ├── SBKArtist.swift │ │ ├── SBKArtistSearchMethod.swift │ │ ├── SBKImage.swift │ │ ├── SBKLovedTrack.swift │ │ ├── SBKLovedTracks.swift │ │ ├── SBKNowPlayingCorrected.swift │ │ ├── SBKScrobbleResponse.swift │ │ ├── SBKScrobbleResponseAttribute.swift │ │ ├── SBKScrobbleResponseTrack.swift │ │ ├── SBKScrobbleResponseTrackStatus.swift │ │ ├── SBKScrobbledTrack.swift │ │ ├── SBKSearchAttributes.swift │ │ ├── SBKSearchPeriod.swift │ │ ├── SBKSearchResult.swift │ │ ├── SBKSearchTerm.swift │ │ ├── SBKSessionResponse.swift │ │ ├── SBKSimilarArtist.swift │ │ ├── SBKSimilarTrack.swift │ │ ├── SBKTag.swift │ │ ├── SBKTrack.swift │ │ ├── SBKTrackToScrobble.swift │ │ └── SBKWiki.swift │ ├── SBKManager.swift │ └── ScrobbleKit.docc │ ├── Authentication.md │ ├── QuickStart.md │ ├── ScrobbleKit.md │ └── Scrobbling.md └── Tests └── ScrobbleKitTests └── ScrobbleKitTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /.swiftpm/configuration/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "c1cc290842fae4aa91ae44f1125f36c006eb751d731bf6291df86bd948e31ae1", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-docc-plugin", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/swiftlang/swift-docc-plugin", 8 | "state" : { 9 | "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", 10 | "version" : "1.3.0" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-docc-symbolkit", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/apple/swift-docc-symbolkit", 17 | "state" : { 18 | "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", 19 | "version" : "1.0.0" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tomás Martins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "6a57ec6b5c2644f2958db0220a31fd2d354b63a9f53630bc3990db38ce581dcb", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-docc-plugin", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/swiftlang/swift-docc-plugin", 8 | "state" : { 9 | "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", 10 | "version" : "1.4.3" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-docc-symbolkit", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/swiftlang/swift-docc-symbolkit", 17 | "state" : { 18 | "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", 19 | "version" : "1.0.0" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ScrobbleKit", 8 | platforms: [ 9 | .iOS(.v13), 10 | .macOS(.v10_15), 11 | .macCatalyst(.v13), 12 | .watchOS(.v6), 13 | .tvOS(.v13), 14 | .visionOS(.v1) 15 | ], 16 | products: [ 17 | // Products define the executables and libraries a package produces, and make them visible to other packages. 18 | .library( 19 | name: "ScrobbleKit", 20 | targets: ["ScrobbleKit"]), 21 | ], 22 | dependencies: [ 23 | .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0") 24 | ], 25 | targets: [ 26 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 27 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 28 | .target( 29 | name: "ScrobbleKit", 30 | dependencies: []), 31 | .testTarget( 32 | name: "ScrobbleKitTests", 33 | dependencies: ["ScrobbleKit"]), 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScrobbleKit 2 | 3 | ScrobbleKit is a modern Swift library designed to simplify interactions with the [Last.fm API](https://www.last.fm/api) across Apple platforms and Linux. Built with Swift 6.0 concurrency in mind, it provides a thread-safe, async/await-based interface for seamless integration with your Swift apps. 4 | 5 | 6 | ## Getting Started 7 | 8 | To begin using ScrobbleKit, you'll need to create an instance of `SBKManager` by providing your [API and Secret key from Last.fm](https://www.last.fm/api/account/create). 9 | 10 | ```swift 11 | import ScrobbleKit 12 | 13 | let manager = SBKManager(apiKey: "YOUR_API_KEY", secret: "YOUR_SECRET_KEY") 14 | ``` 15 | 16 | ## Usage 17 | 18 | ScrobbleKit supports async/await for all API methods. Here's an example of how to fetch album information: 19 | 20 | ```swift 21 | do { 22 | let album = try await manager.getInfo(forAlbum: .albumArtist(album: "Random Access Memories", 23 | artist: "Daft Punk")) 24 | print("Album name: \(album.name)") 25 | print("Artist: \(album.artist)") 26 | print("Tracks: \(album.tracklist.count)") 27 | } catch { 28 | print("Error fetching album info: \(error)") 29 | } 30 | ``` 31 | 32 | ## Authentication 33 | 34 | For methods requiring authentication, you'll need to start a session: 35 | 36 | ```swift 37 | do { 38 | let session = try await manager.startSession(username: "YOUR_USERNAME", password: "YOUR_PASSWORD") 39 | print("Authenticated as: \(session.name)") 40 | } catch { 41 | print("Authentication failed: \(error)") 42 | } 43 | ``` 44 | 45 | ## Documentation 46 | 47 | For detailed information on all available API methods and models, check out the [full documentation](https://tfmart.github.io/ScrobbleKit/documentation/scrobblekit/). 48 | 49 | ## Requirements 50 | 51 | - iOS 13.0+ 52 | - macOS 10.15+ 53 | - watchOS 6.0+ 54 | - tvOS 13.0+ 55 | - visionOS 1.0+ 56 | - Linux 57 | - Swift 6.0+ 58 | 59 | ## Installation 60 | 61 | ### Swift Package Manager 62 | 63 | Add the following to your `Package.swift` file: 64 | 65 | ```swift 66 | dependencies: [ 67 | .package(url: "https://github.com/tfmart/ScrobbleKit.git", from: "0.1.0") 68 | ] 69 | ``` 70 | 71 | Or add it directly in Xcode using File > Add Packages > Search or Enter Package URL. 72 | 73 | ## Contributing 74 | 75 | Contributions to ScrobbleKit are welcome! Please feel free to submit a Pull Request or to open an Issue. 76 | 77 | ## License 78 | 79 | ScrobbleKit is available under the MIT license. See the LICENSE file for more info. 80 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Extensions/Bool+Int.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool+Int.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bool { 11 | init?(integer: Int?) { 12 | guard let integer else { return nil } 13 | self = integer != 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Extensions/OptinalStringInitializers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptinalStringInitializers.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 02/02/24. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URL { 11 | init?(optionalString: String?) { 12 | guard let optionalString, let url = URL(string: optionalString) else { return nil } 13 | self = url 14 | } 15 | } 16 | 17 | extension Int { 18 | init?(optionalString: String?) { 19 | guard let optionalString, let integerValue = Int(optionalString) else { return nil } 20 | self = integerValue 21 | } 22 | } 23 | 24 | extension UUID { 25 | init?(optionalString: String?) { 26 | guard let optionalString, let uuid = UUID(uuidString: optionalString) else { return nil } 27 | self = uuid 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Extensions/SBKParameter+URLQueryItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKParameter+URLQueryItem.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URLQueryItem { 11 | init(parameter: SBKParameter, value: String?) { 12 | self.init(name: parameter.rawValue, value: value) 13 | } 14 | 15 | init(parameter: SBKParameter, bool: Bool) { 16 | self.init(name: parameter.rawValue, bool: bool) 17 | } 18 | 19 | init(parameter: SBKParameter, numericValue: N?) { 20 | self.init(name: parameter.rawValue, numericValue: numericValue) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Extensions/URLQuertyItem+Numeric.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLQuertyItem+Numeric.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URLQueryItem { 11 | init(name: String, numericValue: N?) { 12 | if let numericValue { 13 | self.init(name: name, value: "\(numericValue)") 14 | } else { 15 | self.init(name: name, value: nil) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Extensions/URLQueryItem+Bool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLQueryItem+Bool.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URLQueryItem { 11 | init(name: String, bool: Bool) { 12 | switch bool { 13 | case true: self.init(name: name, value: "1") 14 | case false: self.init(name: name, value: "0") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Helpers/Error/SBKErrorMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKErrorMessage.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKErrorMessage: Codable { 11 | let message: String 12 | let error: SBKError 13 | } 14 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Helpers/Error/SBKScrobbleError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKScrobbleError.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | 9 | /// Represents possible error cases when scrobbling a track. 10 | public enum SBKScrobbleError: String, Error, Sendable { 11 | case artistIgnored = "Artist was ignored" 12 | case trackIgnored = "Track was ignored" 13 | case timestampTooOld = "Timestamp was too old" 14 | case timestampTooNew = "Timestamp was too new" 15 | case dailyScrobbleLimitExceeded = "Daily scrobble limit exceeded" 16 | case unknown = "Unknown error" 17 | 18 | init(code: String) { 19 | switch code { 20 | case "1": self = .artistIgnored 21 | case "2": self = .trackIgnored 22 | case "3": self = .timestampTooOld 23 | case "4": self = .timestampTooNew 24 | case "5": self = .dailyScrobbleLimitExceeded 25 | default: self = .unknown 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Helpers/SBKHttpMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKHttpMethod.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SBKHttpMethod: String, Sendable { 11 | case `get` = "GET" 12 | case post = "POST" 13 | case put = "PUT" 14 | case patch = "PATCH" 15 | case delete = "DELETE" 16 | case head = "HEAD" 17 | case options = "OPTIONS" 18 | case trace = "TRACE" 19 | case connect = "CONNECT" 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Helpers/SBKParameter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKParameter.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SBKParameter: String { 11 | case format 12 | case method 13 | case apiKey = "api_key" 14 | case apiSig = "api_sig" 15 | case sessionKey = "sk" 16 | case autoCorrect = "autocorrect" 17 | case username = "user" 18 | case musicBrainzID = "mbid" 19 | 20 | static let baseURL = "https://ws.audioscrobbler.com/2.0/" 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/NumericStringDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericStringDecoder.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 02/02/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct IntegerStringDecoder: Decodable { 11 | var intValue: Int? 12 | 13 | init(from decoder: Decoder) throws { 14 | let container = try decoder.singleValueContainer() 15 | if let stringValue = try? container.decode(String.self) { 16 | self.intValue = Int(stringValue) 17 | } else { 18 | self.intValue = try? container.decode(Int.self) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/SBKCorrectedResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKCorrectedResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKCorrectedResponse: Decodable, Sendable { 11 | var corrections: SBKCorrectedResult 12 | } 13 | 14 | struct SBKCorrectedResult: Decodable, Sendable { 15 | var correction: SBKCorrectedResultDetail 16 | } 17 | 18 | struct SBKCorrectedResultDetail: Decodable, Sendable { 19 | var result: T? 20 | 21 | enum CodingKeys: CodingKey { 22 | case result 23 | } 24 | 25 | private enum ResultCodingKeys: String, CodingKey { 26 | case artist = "artist" 27 | case track = "track" 28 | } 29 | 30 | init(from decoder: Decoder) throws { 31 | let container: KeyedDecodingContainer.ResultCodingKeys> = try decoder.container(keyedBy: SBKCorrectedResultDetail.ResultCodingKeys.self) 32 | 33 | switch "\(T.self)" { 34 | case "SBKArtist": 35 | self.result = try container.decode(T.self, forKey: SBKCorrectedResultDetail.ResultCodingKeys.artist) 36 | case "SBKTrack": 37 | self.result = try container.decode(T.self, forKey: SBKCorrectedResultDetail.ResultCodingKeys.track) 38 | default: 39 | fatalError("This response type is not supported") 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/SBKEmptyResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKEmptyResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | final class SBKEmptyResponse: Sendable, Decodable {} 11 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/SBKSearchAttribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSearchAttribute.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 17/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol SBKSearchAttribute: Decodable, Sendable { 11 | var page: String { get set } 12 | var perPage: String { get set } 13 | var totalPages: String { get set } 14 | var total: String { get set } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/SBKSearchQuery.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSearchQuery.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKSearchQuery: Codable { 11 | let text: String 12 | let role: String 13 | let searchTerms: String? 14 | let startPage: String 15 | 16 | private enum CodingKeys: String, CodingKey { 17 | case text = "#text" 18 | case role, searchTerms, startPage 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/SBKSearchResultsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSearchResultsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKSearchResultsResponse: Decodable, Sendable { 11 | let results: SBKSearchResultsDetails 12 | } 13 | 14 | struct SBKSearchResultsDetails: Decodable, Sendable { 15 | let query: SBKSearchQuery 16 | let totalResults: String 17 | let startIndex: String 18 | let itemsPerPage: String 19 | let matches: T 20 | 21 | private enum CodingKeys: String, CodingKey { 22 | case query = "opensearch:Query" 23 | case totalResults = "opensearch:totalResults" 24 | case startIndex = "opensearch:startIndex" 25 | case itemsPerPage = "opensearch:itemsPerPage" 26 | case matches 27 | } 28 | 29 | private enum MatchesCodingKeys: String, CodingKey { 30 | case album = "albummatches" 31 | case artist = "artistmatches" 32 | case track = "trackmatches" 33 | } 34 | 35 | init(from decoder: Decoder) throws { 36 | let container: KeyedDecodingContainer.CodingKeys> = try decoder.container(keyedBy: SBKSearchResultsDetails.CodingKeys.self) 37 | 38 | self.query = try container.decode(SBKSearchQuery.self, forKey: SBKSearchResultsDetails.CodingKeys.query) 39 | self.totalResults = try container.decode(String.self, forKey: SBKSearchResultsDetails.CodingKeys.totalResults) 40 | self.startIndex = try container.decode(String.self, forKey: SBKSearchResultsDetails.CodingKeys.startIndex) 41 | self.itemsPerPage = try container.decode(String.self, forKey: SBKSearchResultsDetails.CodingKeys.itemsPerPage) 42 | 43 | let matchesContainer: KeyedDecodingContainer.MatchesCodingKeys> = try decoder.container(keyedBy: SBKSearchResultsDetails.MatchesCodingKeys.self) 44 | switch "\(T.self)" { 45 | case "SBKArtistSearchResultsList": 46 | self.matches = try matchesContainer.decode(T.self, forKey: SBKSearchResultsDetails.MatchesCodingKeys.artist) 47 | case "SBKAlbumSearchResultListResponse": 48 | self.matches = try matchesContainer.decode(T.self, forKey: SBKSearchResultsDetails.MatchesCodingKeys.album) 49 | case "SBKTrackSearchResultListResponse": 50 | self.matches = try matchesContainer.decode(T.self, forKey: SBKSearchResultsDetails.MatchesCodingKeys.track) 51 | default: 52 | fatalError("This response type is not supported") 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/SBKThrowable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKThrowable.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/08/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKThrowable : Decodable { 11 | 12 | let object: D? 13 | 14 | init(from decoder: Decoder) throws { 15 | let container = try decoder.singleValueContainer() 16 | self.object = try? container.decode(D.self) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Models/Common/SBKTimestamp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTimestamp.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/08/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKTimestamp: Decodable, Sendable { 11 | public let timestamp: String 12 | public let date: String 13 | 14 | private enum CodingKeys: String, CodingKey { 15 | case timestamp = "uts" 16 | case date = "#text" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Common/AddTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKAddTagService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SBKTaggableContent { 11 | case artist(_ name: String) 12 | case album(_ name: String, artist: String) 13 | case track(_ name: String, artist: String) 14 | } 15 | 16 | struct AddTagsService: SBKAuthenticatedService { 17 | typealias ResponseType = SBKEmptyResponse 18 | 19 | var taggableContent: SBKTaggableContent 20 | 21 | var sbkMethod: SBKMethod { 22 | switch taggableContent { 23 | case .artist(_): 24 | return .addTagsToArtist 25 | case .album(_, _): 26 | return .addTagsToAlbum 27 | case .track(_, _): 28 | return .addTagsToTrack 29 | } 30 | } 31 | 32 | var queries: [URLQueryItem] 33 | var httpMethod: SBKHttpMethod = .post 34 | 35 | var apiKey: String 36 | var secretKey: String 37 | var sessionKey: String 38 | 39 | init(to taggableContent: SBKTaggableContent, tags: [String], 40 | apiKey: String, secretKey: String, sessionKey: String) { 41 | self.taggableContent = taggableContent 42 | self.apiKey = apiKey 43 | self.secretKey = secretKey 44 | self.sessionKey = sessionKey 45 | 46 | switch taggableContent { 47 | case .artist(let name): 48 | queries = [ 49 | .init(name: "artist", value: name), 50 | .init(name: "tags", value: tags.joined(separator: ",")) 51 | ] 52 | case .album(let name, let artist): 53 | queries = [ 54 | .init(name: "album", value: name), 55 | .init(name: "artist", value: artist), 56 | .init(name: "tags", value: tags.joined(separator: ",")) 57 | ] 58 | case .track(let title, let artist): 59 | queries = [ 60 | .init(name: "track", value: title), 61 | .init(name: "artist", value: artist), 62 | .init(name: "tags", value: tags.joined(separator: ",")) 63 | ] 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Common/RemoveTagService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoveTagService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RemoveTagService: SBKAuthenticatedService { 11 | typealias ResponseType = SBKEmptyResponse 12 | 13 | var taggableContent: SBKTaggableContent 14 | 15 | var sbkMethod: SBKMethod { 16 | switch taggableContent { 17 | case .artist(_): 18 | return .removeTagFromArtist 19 | case .album(_, _): 20 | return .removeTagFromAlbum 21 | case .track(_, _): 22 | return .removeTagFromTrack 23 | } 24 | } 25 | 26 | var queries: [URLQueryItem] 27 | var httpMethod: SBKHttpMethod = .post 28 | 29 | var apiKey: String 30 | var secretKey: String 31 | var sessionKey: String 32 | 33 | init(to taggableContent: SBKTaggableContent, tag: String, 34 | apiKey: String, secretKey: String, sessionKey: String) { 35 | self.taggableContent = taggableContent 36 | self.apiKey = apiKey 37 | self.secretKey = secretKey 38 | self.sessionKey = sessionKey 39 | 40 | switch taggableContent { 41 | case .artist(let name): 42 | queries = [ 43 | .init(name: "artist", value: name), 44 | .init(name: "tag", value: tag) 45 | ] 46 | case .album(let name, let artist): 47 | queries = [ 48 | .init(name: "album", value: name), 49 | .init(name: "artist", value: artist), 50 | .init(name: "tag", value: tag) 51 | ] 52 | case .track(let title, let artist): 53 | queries = [ 54 | .init(name: "track", value: title), 55 | .init(name: "artist", value: artist), 56 | .init(name: "tag", value: tag) 57 | ] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/AlbumGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumGetInfoService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AlbumGetInfoService: SBKService { 11 | var apiKey: String 12 | var secretKey: String 13 | 14 | typealias ResponseType = SBKAlbumRequestResponseList 15 | 16 | var sbkMethod: SBKMethod = .getAlbumInfo 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init( 21 | searchMethod: SBKAlbumSearchMethod, 22 | autoCorrect: Bool, 23 | username: String?, 24 | languageCode: SBKLanguageCode, 25 | apiKey: String, 26 | secretKey: String 27 | ) { 28 | self.apiKey = apiKey 29 | self.secretKey = secretKey 30 | switch searchMethod { 31 | case .albumArtist(let album, let artist): 32 | self.queries = [ 33 | .init(name: "artist", value: artist), 34 | .init(name: "album", value: album), 35 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 36 | .init(name: "user", value: username), 37 | .init(name: "lang", value: languageCode.rawValue) 38 | ] 39 | case .musicBrainzID(let id): 40 | self.queries = [ 41 | .init(name: SBKParameter.musicBrainzID.rawValue, value: id), 42 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 43 | .init(name: "user", value: username), 44 | .init(name: "lang", value: languageCode.rawValue) 45 | ] 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/AlbumGetTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumGetTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AlbumGetTagsService: SBKService { 11 | var apiKey: String 12 | var secretKey: String 13 | 14 | typealias ResponseType = SBKAddTagAlbumResponse 15 | 16 | var sbkMethod: SBKMethod = .getTagsForAlbum 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(searchMethod: SBKAlbumSearchMethod, 21 | autoCorrect: Bool, 22 | username: String?, 23 | apiKey: String, 24 | secretKey: String) { 25 | self.apiKey = apiKey 26 | self.secretKey = secretKey 27 | switch searchMethod { 28 | case .albumArtist(let album, let artist): 29 | self.queries = [ 30 | .init(name: "artist", value: artist), 31 | .init(name: "album", value: album), 32 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 33 | .init(name: "user", value: username) 34 | ] 35 | case .musicBrainzID(let id): 36 | self.queries = [ 37 | .init(name: SBKParameter.musicBrainzID.rawValue, value: id), 38 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 39 | .init(name: "user", value: username) 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/AlbumGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumGetTopTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AlbumGetTopTagsService: SBKService { 11 | var apiKey: String 12 | var secretKey: String 13 | 14 | typealias ResponseType = SBKAlbumTopTagsResponse 15 | 16 | var sbkMethod: SBKMethod = .getTopTagsForAlbum 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(searchMethod: SBKAlbumSearchMethod, 21 | autoCorrect: Bool, 22 | apiKey: String, 23 | secretKey: String) { 24 | self.apiKey = apiKey 25 | self.secretKey = secretKey 26 | switch searchMethod { 27 | case .albumArtist(let album, let artist): 28 | self.queries = [ 29 | .init(name: "artist", value: artist), 30 | .init(name: "album", value: album), 31 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect) 32 | ] 33 | case .musicBrainzID(let id): 34 | self.queries = [ 35 | .init(name: SBKParameter.musicBrainzID.rawValue, value: id), 36 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect) 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/AlbumSearchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumSearchService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AlbumSearchService: SBKService { 11 | typealias ResponseType = SBKSearchResultsResponse 12 | 13 | var sbkMethod: SBKMethod = .searchForAlbum 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(album: String, limit: Int, page: Int, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | queries = [ 25 | .init(name: "album", value: album), 26 | .init(name: "limit", numericValue: limit), 27 | .init(name: "page", numericValue: page) 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/Model/SBKAddTagAlbumResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKAddTagAlbumResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKAddTagAlbumResponse: Decodable, Sendable { 11 | let tags: SBKTagRequestResponseList 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/Model/SBKAlbumRequestResponseList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKAlbumRequestResponseList.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKAlbumRequestResponseList: Decodable, Sendable { 11 | var album: SBKAlbum 12 | } 13 | 14 | // MARK: - Tags 15 | struct SBKTagRequestResponse: Decodable, Sendable { 16 | var tags: SBKTagRequestResponseList 17 | } 18 | 19 | struct SBKTagRequestResponseList: Decodable, Sendable { 20 | var tag: [SBKTag]? 21 | 22 | enum CodingKeys: CodingKey { 23 | case tag 24 | } 25 | 26 | init(from decoder: Decoder) throws { 27 | let container = try decoder.container(keyedBy: CodingKeys.self) 28 | if let tagString = try? container.decodeIfPresent(String.self, forKey: .tag) { 29 | if tagString.isEmpty { self.tag = [] } 30 | else { self.tag = [.init(name: tagString)] } 31 | } else { 32 | self.tag = try container.decodeIfPresent([SBKTag].self, forKey: .tag) 33 | } 34 | } 35 | 36 | internal init(tag: [SBKTag]? = nil) { 37 | self.tag = tag 38 | } 39 | } 40 | 41 | // MARK: - Tracks 42 | struct SBKAlbumTracksRequestResponseList: Decodable, Sendable { 43 | var track: [SBKAlbumTrack] 44 | 45 | enum CodingKeys: CodingKey { 46 | case track 47 | } 48 | 49 | init(from decoder: Decoder) throws { 50 | let container = try decoder.container(keyedBy: CodingKeys.self) 51 | if let track = try? container.decode(SBKAlbumTrack.self, forKey: .track) { 52 | self.track = [track] 53 | } else { 54 | self.track = try container.decode([SBKAlbumTrack].self, forKey: .track) 55 | } 56 | } 57 | } 58 | 59 | // MARK: - Track 60 | struct SBKAlbumTrack: Codable, Sendable { 61 | var streamable: SBKAlbumTrackStreamable 62 | var duration: Int? 63 | var url: String 64 | var name: String 65 | var attr: SBKAlbumTrackAttribute 66 | var artist: SBKAlbumTrackArtist 67 | 68 | enum CodingKeys: String, CodingKey { 69 | case streamable, duration, url, name 70 | case attr = "@attr" 71 | case artist 72 | } 73 | } 74 | 75 | // MARK: - ArtistClass 76 | struct SBKAlbumTrackArtist: Codable, Sendable { 77 | var url: String 78 | var name: String 79 | var mbid: String 80 | } 81 | 82 | // MARK: - Attr 83 | struct SBKAlbumTrackAttribute: Codable, Sendable { 84 | var rank: Int 85 | } 86 | 87 | // MARK: - Streamable 88 | struct SBKAlbumTrackStreamable: Codable, Sendable { 89 | var fulltrack, text: String 90 | 91 | enum CodingKeys: String, CodingKey { 92 | case fulltrack 93 | case text = "#text" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/Model/SBKAlbumSearchResultListResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKAlbumSearchResultListResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKAlbumSearchResultListResponse: Decodable, Sendable { 11 | var album: [SBKAlbum]? 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Album/Model/SBKAlbumTopTagsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKAlbumTopTagsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKAlbumTopTagsResponse: Decodable, Sendable { 11 | let toptags: SBKTagRequestResponseList 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/ArtistGetCorrectionService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistGetCorrectionService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ArtistGetCorrectionService: SBKService { 11 | typealias ResponseType = SBKCorrectedResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getCorrectedArtistInfo 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(artist: String, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | queries = [.init(name: "artist", value: artist)] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/ArtistGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistGetInfoService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ArtistGetInfoService: SBKService { 11 | typealias ResponseType = SBKArtistGetInfoRequestResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getArtistInfo 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init( 21 | searchMethod: SBKArtistSearchMethod, 22 | autoCorrect: Bool, 23 | username: String?, 24 | languageCode: SBKLanguageCode, 25 | apiKey: String, 26 | secretKey: String 27 | ) { 28 | self.apiKey = apiKey 29 | self.secretKey = secretKey 30 | 31 | switch searchMethod { 32 | case .artistName(let artist): 33 | self.queries = [ 34 | .init(name: "artist", value: artist), 35 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 36 | .init(name: "user", value: username), 37 | .init(name: "lang", value: languageCode.rawValue) 38 | ] 39 | case .musicBrainzID(let id): 40 | self.queries = [ 41 | .init(name: SBKParameter.musicBrainzID.rawValue, value: id), 42 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 43 | .init(name: "user", value: username), 44 | .init(name: "lang", value: languageCode.rawValue) 45 | ] 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/ArtistGetSimilarService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistGetSimilarService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ArtistGetSimilarService: SBKService { 11 | typealias ResponseType = SBKArtistGetSimilarResponse 12 | 13 | var sbkMethod: SBKMethod = .getSimilarArtists 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(_ searchMethod: SBKArtistSearchMethod, 21 | limit: Int, 22 | autoCorrect: Bool, 23 | apiKey: String, 24 | secretKey: String) { 25 | self.apiKey = apiKey 26 | self.secretKey = secretKey 27 | 28 | switch searchMethod { 29 | case .artistName(let artist): 30 | queries = [ 31 | .init(name: "artist", value: artist), 32 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 33 | .init(name: "limit", numericValue: limit) 34 | ] 35 | case .musicBrainzID(let mbid): 36 | queries = [ 37 | .init(name: SBKParameter.musicBrainzID.rawValue, value: mbid), 38 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 39 | .init(name: "limit", numericValue: limit) 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/ArtistGetTopAlbumsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistGetTopAlbumsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ArtistGetTopAlbumsService: SBKService { 11 | typealias ResponseType = SBKArtistGetTopAlbumsResponse 12 | 13 | var sbkMethod: SBKMethod = .getTopAlbumsForArtist 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(_ searchMethod: SBKArtistSearchMethod, 21 | limit: Int, 22 | page: Int, 23 | autoCorrect: Bool, 24 | apiKey: String, 25 | secretKey: String) { 26 | self.apiKey = apiKey 27 | self.secretKey = secretKey 28 | 29 | switch searchMethod { 30 | case .artistName(let artist): 31 | queries = [ 32 | .init(name: "artist", value: artist), 33 | .init(name: "limit", numericValue: limit), 34 | .init(name: "page", numericValue: page), 35 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 36 | ] 37 | case .musicBrainzID(let mbid): 38 | queries = [ 39 | .init(name: SBKParameter.musicBrainzID.rawValue, value: mbid), 40 | .init(name: "limit", numericValue: limit), 41 | .init(name: "page", numericValue: page), 42 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/ArtistGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ArtistGetTopTracksService: SBKService { 11 | typealias ResponseType = SBKArtistTopTracksResponse 12 | 13 | var sbkMethod: SBKMethod = .getTopTracksForArtist 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(_ searchMethod: SBKArtistSearchMethod, 21 | limit: Int, 22 | page: Int, 23 | autoCorrect: Bool, 24 | apiKey: String, 25 | secretKey: String) { 26 | self.apiKey = apiKey 27 | self.secretKey = secretKey 28 | 29 | switch searchMethod { 30 | case .artistName(let artist): 31 | queries = [ 32 | .init(name: "artist", value: artist), 33 | .init(name: "limit", numericValue: limit), 34 | .init(name: "page", numericValue: page), 35 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 36 | ] 37 | case .musicBrainzID(let mbid): 38 | queries = [ 39 | .init(name: SBKParameter.musicBrainzID.rawValue, value: mbid), 40 | .init(name: "limit", numericValue: limit), 41 | .init(name: "page", numericValue: page), 42 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/ArtistSearchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistSearchService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ArtistSearchService: SBKService { 11 | typealias ResponseType = SBKSearchResultsResponse 12 | 13 | var sbkMethod: SBKMethod = .searchForArtist 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(_ searchQuery: String, 21 | limit: Int, 22 | page: Int, 23 | apiKey: String, 24 | secretKey: String) { 25 | self.apiKey = apiKey 26 | self.secretKey = secretKey 27 | 28 | queries = [ 29 | .init(name: "artist", value: searchQuery), 30 | .init(name: "limit", numericValue: limit), 31 | .init(name: "page", numericValue: page) 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/ArtistGetTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistGetTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ArtistGetTagsService: SBKService { 11 | typealias ResponseType = SBKAddTagAlbumResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getTagsForArtist 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | public init(searchMethod: SBKArtistSearchMethod, user: String?, autocorrect: Bool?, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | var queries: [URLQueryItem] = [] 25 | 26 | switch searchMethod { 27 | case .artistName(let artist): 28 | queries.append(URLQueryItem(name: "artist", value: artist)) 29 | case .musicBrainzID(let mbid): 30 | queries.append(URLQueryItem(name: SBKParameter.musicBrainzID.rawValue, value: mbid)) 31 | } 32 | 33 | if let user = user { 34 | queries.append(URLQueryItem(name: "user", value: user)) 35 | } 36 | 37 | if let autoCorrect = autocorrect { 38 | queries.append(.init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect)) 39 | } 40 | 41 | self.queries = queries 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/SBKArtistGetInfoRequestResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistGetInfoRequestResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKArtistGetInfoRequestResponse: Decodable, Sendable { 11 | var artist: SBKArtistGetInfoProperties 12 | } 13 | 14 | struct SBKArtistGetInfoProperties: Decodable, Sendable { 15 | var name: String 16 | var mbid: String? 17 | var image: [SBKImageResponse]? 18 | var url: String 19 | var streamable: String? 20 | var ontour: String? 21 | var stats: SBKArtistGetInfoPropertiesStats? 22 | var similar: SBKArtistGetInfoSimilarResponse? 23 | var tags: SBKTagRequestResponseList? 24 | var bio: SBKWiki? 25 | } 26 | 27 | struct SBKArtistGetInfoPropertiesStats: Decodable, Sendable { 28 | var listeners: String 29 | var playcount: String 30 | } 31 | 32 | struct SBKArtistGetInfoSimilarResponse: Decodable, Sendable { 33 | var artist: [SBKArtistGetInfoSimilarArtist] 34 | 35 | var sbkArtist: [SBKArtist] { 36 | return artist.map { SBKArtist(similarArtist: $0) } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/SBKArtistGetInfoSimilarArtist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistGetInfoSimilarArtist.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 02/02/24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents a similar artist for an artist. 11 | struct SBKArtistGetInfoSimilarArtist: Decodable, Sendable { 12 | public var name: String 13 | public var url: String 14 | private var imageResponse: [SBKImageResponse]? 15 | 16 | enum CodingKeys: String, CodingKey { 17 | case name 18 | case url 19 | case imageResponse = "image" 20 | } 21 | 22 | var image: SBKImage? { 23 | return .init(response: imageResponse) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/SBKArtistGetSimilarResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistGetSimilarResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKArtistGetSimilarResponse: Decodable, Sendable { 11 | var similarartists: SBKArtistGetSimilarList 12 | } 13 | 14 | struct SBKArtistGetSimilarList: Decodable, Sendable { 15 | var artist: [SBKSimilarArtist] 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/SBKArtistGetTopAlbumsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistGetTopAlbumsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKArtistGetTopAlbumsResponse: Decodable, Sendable { 11 | let topAlbums: SBKArtistGetTopAlbumsList 12 | 13 | enum CodingKeys: String, CodingKey { 14 | case topAlbums = "topalbums" 15 | } 16 | } 17 | 18 | struct SBKArtistGetTopAlbumsList: Decodable, Sendable { 19 | let albums: [SBKArtistTopAlbum] 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case albums = "album" 23 | } 24 | } 25 | 26 | struct SBKArtistTopAlbum: Decodable, Sendable { 27 | var name: String 28 | var url: String 29 | var playcount: Int 30 | var mbid: String? 31 | var image: [SBKImageResponse] 32 | var artist: SBKArtistTopAlbumArtist 33 | } 34 | 35 | struct SBKArtistTopAlbumArtist: Decodable, Sendable { 36 | var name: String 37 | var mbid: String? 38 | var url: String 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/SBKArtistSearchResultsList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistSearchResultsList.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKArtistSearchResultsList: Decodable, Sendable { 11 | let artists: [SBKArtist] 12 | 13 | private enum CodingKeys: String, CodingKey { 14 | case artists = "artist" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/SBKArtistTopTracksResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistTopTracksResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKArtistTopTracksResponse: Decodable, Sendable { 11 | var toptracks: SBKArtistTopTracksList 12 | } 13 | 14 | struct SBKArtistTopTracksList: Decodable, Sendable { 15 | var track: [SBKTrack] 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Artist/Model/SBKManager+TagGetWeeklyChartList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TagGetWeeklyChartList.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get a list of available charts for this tag, expressed as date ranges which can be sent to the chart services. 13 | 14 | - Parameter tag: The tag name. 15 | 16 | - Returns: An array of `SBKTag` objects representing available charts for the tag. 17 | 18 | - Throws: An error of type `SBKError` if the request fails. 19 | 20 | - Note: See [Last.fm's tag.getWeeklyChartList documentation](https://www.last.fm/api/show/tag.getWeeklyChartList) for more information. 21 | */ 22 | func getWeeklyChartList(forTag tag: String) async throws -> [SBKTag] { 23 | let service = TagGetWeeklyChartListService(tag: tag, apiKey: apiKey, secretKey: secret) 24 | let response = try await service.start() 25 | let chartList = response.weeklyChartList 26 | return chartList.compactMap { chartTag in 27 | guard let fromText = chartTag.from, let toText = chartTag.to, 28 | let fromDateDouble = Double(fromText), let toDateDouble = Double(toText) else { return nil } 29 | 30 | let fromDate = Date(timeIntervalSince1970: fromDateDouble) 31 | let toDate = Date(timeIntervalSince1970: toDateDouble) 32 | 33 | let url = try? SBKURLBuilder.url(forTag: tag) 34 | 35 | return SBKTag(name: tag, url: url, from: fromDate, to: toDate) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Auth/AuthSessionService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthSessionService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AuthSessionService: SBKService { 11 | typealias ResponseType = SBKSessionResponse 12 | var sbkMethod: SBKMethod = .getSession 13 | var httpMethod: SBKHttpMethod = .post 14 | 15 | var username: String 16 | var password: String 17 | 18 | var apiKey: String 19 | var secretKey: String 20 | 21 | var queries: [URLQueryItem] = [] 22 | 23 | init(username: String, password: String, 24 | apiKey: String, secretKey: String) { 25 | self.username = username 26 | self.password = password 27 | self.apiKey = apiKey 28 | self.secretKey = secretKey 29 | 30 | self.queries = [ 31 | .init(name: "username", value: username), 32 | .init(name: "password", value: password), 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Chart/ChartGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ChartGetTopArtistsService: SBKService { 11 | typealias ResponseType = SBKArtistTopChartsRequestResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getTopArtistsFromChart 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(limit: Int, page: Int, 21 | apiKey: String, secretKey: String) { 22 | self.apiKey = apiKey 23 | self.secretKey = secretKey 24 | queries = [ 25 | .init(name: "limit", numericValue: limit), 26 | .init(name: "page", numericValue: page), 27 | ] 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Chart/ChartGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ChartGetTopTracksService: SBKService { 11 | typealias ResponseType = SBKChartGetTopTracksResponse 12 | 13 | var sbkMethod: SBKMethod = .getTopTracksFromChart 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(limit: Int, 21 | page: Int, 22 | apiKey: String, 23 | secretKey: String) { 24 | self.apiKey = apiKey 25 | self.secretKey = secretKey 26 | 27 | queries = [ 28 | .init(name: "limit", numericValue: limit), 29 | .init(name: "page", numericValue: page) 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Chart/ChartsGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartsGetTopTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ChartGetTopTagsService: SBKService { 11 | typealias ResponseType = SBKChartGetTopTagsResponse 12 | 13 | var sbkMethod: SBKMethod = .tagTopTags 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(page: Int, limit: Int, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | queries = [ 25 | .init(name: "page", numericValue: page), 26 | .init(name: "limit", numericValue: limit) 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Chart/Model/SBKArtistTopChartsRequestResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistTopChartsRequestResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKArtistTopChartsRequestResponse: Decodable, Sendable { 11 | let artists: SBKArtistListResponse 12 | } 13 | 14 | struct SBKArtistListResponse: Decodable, Sendable { 15 | let artists: [SBKArtist] 16 | 17 | enum CodingKeys: String, CodingKey { 18 | case artists = "artist" 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Chart/Model/SBKChartGetTopTagsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKChartGetTopTagsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKChartGetTopTagsResponse: Decodable, Sendable { 11 | var toptags: SBKTagRequestResponseList 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Chart/Model/SBKChartGetTopTracksResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKChartGetTopTracksResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKChartGetTopTracksResponse: Decodable, Sendable { 11 | var tracks: SBKArtistTopTracksList 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Geo/GeoGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeoGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct GeoGetTopArtistsService: SBKService { 11 | typealias ResponseType = SBKGeoTopArtistsResponse 12 | 13 | var sbkMethod: SBKMethod = .getTopArtistsInGeo 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(country: SBKCountry, 21 | limit: Int, 22 | page: Int, 23 | apiKey: String, 24 | secretKey: String) { 25 | self.apiKey = apiKey 26 | self.secretKey = secretKey 27 | 28 | queries = [ 29 | .init(name: "country", value: country.rawValue), 30 | .init(name: "page", numericValue: page), 31 | .init(name: "limit", numericValue: limit) 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Geo/GeoGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeoGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 24/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct GeoGetTopTracksService: SBKService { 11 | typealias ResponseType = SBKChartGetTopTracksResponse 12 | 13 | var sbkMethod: SBKMethod = .getTopTracksInGeo 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(country: SBKCountry, 21 | limit: Int, 22 | page: Int, 23 | apiKey: String, 24 | secretKey: String) { 25 | self.apiKey = apiKey 26 | self.secretKey = secretKey 27 | 28 | queries = [ 29 | .init(name: "country", value: country.rawValue), 30 | .init(name: "page", numericValue: page), 31 | .init(name: "limit", numericValue: limit) 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Geo/Model/SBKGeoTopArtistsResponse copy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKGeoTopArtistsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKGeoTopArtistsResponse: Decodable, Sendable { 11 | var topartists: SBKGeoTopArtistsList 12 | } 13 | 14 | struct SBKGeoTopArtistsList: Decodable, Sendable { 15 | let artist: [SBKArtist] 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Library/LibraryGetArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibraryGetArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 24/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct LibraryGetArtistsService: SBKService { 11 | typealias ResponseType = SBKLibraryGetArtistsResponse 12 | 13 | var sbkMethod: SBKMethod = .libraryArtists 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | init(user: String, limit: Int, page: Int, apiKey: String, secret: String) { 20 | self.apiKey = apiKey 21 | self.secretKey = secret 22 | 23 | self.queries = [ 24 | .init(name: "user", value: user), 25 | .init(name: "limit", numericValue: limit), 26 | .init(name: "page", numericValue: page) 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Library/Model/SBKLibraryGetArtistsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKLibraryGetArtistsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 24/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKLibraryGetArtistsResponse: Decodable, Sendable { 11 | let artists: SBKLibraryGetArtistsList 12 | } 13 | 14 | struct SBKLibraryGetArtistsList: Decodable, Sendable { 15 | let artist: [SBKArtist] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Tag/TagGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetInfoService.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TagGetInfoService: SBKService { 11 | typealias ResponseType = TagGetInfoResponse 12 | 13 | var tag: String 14 | var language: SBKLanguageCode 15 | var apiKey: String 16 | var secretKey: String 17 | 18 | var sbkMethod: SBKMethod = .tagInfo 19 | var queries: [URLQueryItem] 20 | var httpMethod: SBKHttpMethod = .get 21 | 22 | init(tag: String, language: SBKLanguageCode, apiKey: String, secretKey: String) { 23 | self.tag = tag 24 | self.language = language 25 | self.apiKey = apiKey 26 | self.secretKey = secretKey 27 | 28 | self.queries = [ 29 | .init(name: "tag", value: tag) 30 | ] 31 | self.queries.append(.init(name: "lang", value: language.rawValue)) 32 | } 33 | } 34 | 35 | struct TagGetInfoResponse: Decodable, Sendable { 36 | let tag: SBKTag 37 | } 38 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Tag/TagGetSimilarService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetSimilarService.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TagGetSimilarService: SBKService { 11 | typealias ResponseType = TagSimilarResponse 12 | 13 | var tag: String 14 | var apiKey: String 15 | var secretKey: String 16 | 17 | var sbkMethod: SBKMethod = .tagSimilar 18 | var queries: [URLQueryItem] 19 | var httpMethod: SBKHttpMethod = .get 20 | 21 | init(tag: String, apiKey: String, secretKey: String) { 22 | self.tag = tag 23 | self.apiKey = apiKey 24 | self.secretKey = secretKey 25 | 26 | self.queries = [ 27 | .init(name: "tag", value: tag) 28 | ] 29 | } 30 | } 31 | 32 | struct TagSimilarResponse: Decodable, Sendable { 33 | let similarTags: [SBKTag] 34 | 35 | enum CodingKeys: String, CodingKey { 36 | case similarTags = "similartags" 37 | } 38 | 39 | struct SimilarTags: Decodable, Sendable { 40 | let tag: [SBKTag] 41 | } 42 | 43 | init(from decoder: Decoder) throws { 44 | let container = try decoder.container(keyedBy: CodingKeys.self) 45 | let similar = try container.decode(SimilarTags.self, forKey: .similarTags) 46 | self.similarTags = similar.tag 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Tag/TagGetTopAlbumsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetTopAlbumsService.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TagGetTopAlbumsService: SBKService { 11 | typealias ResponseType = TagTopAlbumsResponse 12 | 13 | var tag: String 14 | var limit: Int 15 | var page: Int 16 | var apiKey: String 17 | var secretKey: String 18 | 19 | var sbkMethod: SBKMethod = .tagTopAlbums 20 | var queries: [URLQueryItem] 21 | var httpMethod: SBKHttpMethod = .get 22 | 23 | init(tag: String, limit: Int, page: Int, apiKey: String, secretKey: String) { 24 | self.tag = tag 25 | self.limit = limit 26 | self.page = page 27 | self.apiKey = apiKey 28 | self.secretKey = secretKey 29 | 30 | self.queries = [ 31 | .init(name: "tag", value: tag), 32 | .init(name: "limit", value: "\(limit)"), 33 | .init(name: "page", value: "\(page)") 34 | ] 35 | } 36 | } 37 | 38 | struct TagTopAlbumsResponse: Sendable, Decodable { 39 | struct TagTopAlbumsList: Decodable, Sendable { 40 | let album: [SBKAlbum] 41 | } 42 | 43 | let albums: [SBKAlbum] 44 | 45 | enum CodingKeys: String, CodingKey { 46 | case albums = "albums" 47 | } 48 | 49 | init(from decoder: Decoder) throws { 50 | let container = try decoder.container(keyedBy: CodingKeys.self) 51 | let topAlbums = try container.decode(TagTopAlbumsList.self, forKey: .albums) 52 | self.albums = topAlbums.album 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Tag/TagGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TagGetTopArtistsService: SBKService { 11 | typealias ResponseType = TagTopArtistsResponse 12 | 13 | var tag: String 14 | var limit: Int 15 | var page: Int 16 | var apiKey: String 17 | var secretKey: String 18 | 19 | var sbkMethod: SBKMethod = .tagTopArtists 20 | var queries: [URLQueryItem] 21 | var httpMethod: SBKHttpMethod = .get 22 | 23 | init(tag: String, limit: Int, page: Int, apiKey: String, secretKey: String) { 24 | self.tag = tag 25 | self.limit = limit 26 | self.page = page 27 | self.apiKey = apiKey 28 | self.secretKey = secretKey 29 | 30 | self.queries = [ 31 | .init(name: "tag", value: tag), 32 | .init(name: "limit", value: "\(limit)"), 33 | .init(name: "page", value: "\(page)") 34 | ] 35 | } 36 | } 37 | 38 | struct TagTopArtistsResponse: Decodable, Sendable { 39 | let artists: [SBKArtist] 40 | 41 | enum CodingKeys: String, CodingKey { 42 | case artists = "topartists" 43 | } 44 | 45 | struct TopArtists: Decodable, Sendable { 46 | let artist: [SBKArtist] 47 | } 48 | 49 | init(from decoder: Decoder) throws { 50 | let container = try decoder.container(keyedBy: CodingKeys.self) 51 | let topArtists = try container.decode(TopArtists.self, forKey: .artists) 52 | self.artists = topArtists.artist 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Tag/TagGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetTopTagsService.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TagGetTopTagsService: SBKService { 11 | typealias ResponseType = TagTopTagsResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .tagTopTags 17 | var queries: [URLQueryItem] = [] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | } 24 | } 25 | 26 | struct TagTopTagsResponse: Decodable, Sendable { 27 | let tags: [SBKTag] 28 | 29 | enum CodingKeys: String, CodingKey { 30 | case tags = "toptags" 31 | } 32 | 33 | struct TopTags: Decodable, Sendable { 34 | let tag: [SBKTag] 35 | } 36 | 37 | init(from decoder: Decoder) throws { 38 | let container = try decoder.container(keyedBy: CodingKeys.self) 39 | let topTags = try container.decode(TopTags.self, forKey: .tags) 40 | self.tags = topTags.tag 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Tag/TagGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetTopTracksService.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TagGetTopTracksService: SBKService { 11 | typealias ResponseType = TagTopTracksResponse 12 | 13 | var tag: String 14 | var limit: Int 15 | var page: Int 16 | var apiKey: String 17 | var secretKey: String 18 | 19 | var sbkMethod: SBKMethod = .tagTopTracks 20 | var queries: [URLQueryItem] 21 | var httpMethod: SBKHttpMethod = .get 22 | 23 | init(tag: String, limit: Int, page: Int, apiKey: String, secretKey: String) { 24 | self.tag = tag 25 | self.limit = limit 26 | self.page = page 27 | self.apiKey = apiKey 28 | self.secretKey = secretKey 29 | 30 | self.queries = [ 31 | .init(name: "tag", value: tag), 32 | .init(name: "limit", value: "\(limit)"), 33 | .init(name: "page", value: "\(page)") 34 | ] 35 | } 36 | } 37 | 38 | struct TagTopTracksResponse: Sendable, Decodable { 39 | let tracks: [SBKTrack] 40 | 41 | enum CodingKeys: String, CodingKey { 42 | case tracks = "tracks" 43 | } 44 | 45 | struct TopTracks: Decodable, Sendable { 46 | let track: [SBKTrack] 47 | } 48 | 49 | init(from decoder: Decoder) throws { 50 | let container = try decoder.container(keyedBy: CodingKeys.self) 51 | let topTracks = try container.decode(TopTracks.self, forKey: .tracks) 52 | self.tracks = topTracks.track 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Tag/TagGetWeeklyChartListService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetWeeklyChartListService.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TagGetWeeklyChartListService: SBKService { 11 | typealias ResponseType = TagGetWeeklyChartListResponse 12 | 13 | var tag: String 14 | var apiKey: String 15 | var secretKey: String 16 | 17 | var sbkMethod: SBKMethod = .tagWeeklyChartList 18 | var queries: [URLQueryItem] 19 | var httpMethod: SBKHttpMethod = .get 20 | 21 | init(tag: String, apiKey: String, secretKey: String) { 22 | self.tag = tag 23 | self.apiKey = apiKey 24 | self.secretKey = secretKey 25 | 26 | self.queries = [ 27 | .init(name: "tag", value: tag) 28 | ] 29 | } 30 | } 31 | 32 | struct TagGetWeeklyChartListResponse: Decodable, Sendable { 33 | let weeklyChartList: [WeeklyChartTag] 34 | 35 | enum CodingKeys: String, CodingKey { 36 | case weeklyChartList = "weeklychartlist" 37 | } 38 | 39 | struct WeeklyChartList: Decodable, Sendable { 40 | let chart: [WeeklyChartTag] 41 | } 42 | 43 | struct WeeklyChartTag: Decodable, Sendable { 44 | var text, from, to: String? 45 | 46 | enum CodingKeys: String, CodingKey { 47 | case text = "#text" 48 | case from, to 49 | } 50 | } 51 | 52 | init(from decoder: Decoder) throws { 53 | let container = try decoder.container(keyedBy: CodingKeys.self) 54 | let chartList = try container.decode(WeeklyChartList.self, forKey: .weeklyChartList) 55 | self.weeklyChartList = chartList.chart 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/Model/SBKScrobbleList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKScrobbleList.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - Root object 11 | struct SBKScrobbleList: Decodable, Sendable { 12 | let scrobbles: SBKScrobbles 13 | } 14 | 15 | // MARK: - Scrobbles object 16 | struct SBKScrobbles: Decodable, Sendable { 17 | let scrobbles: [SBKScrobble] 18 | let attr: SBKAttr 19 | 20 | enum CodingKeys: String, CodingKey { 21 | case scrobbles = "scrobble" 22 | case attr = "@attr" 23 | } 24 | 25 | 26 | init(from decoder: Decoder) throws { 27 | let container: KeyedDecodingContainer = try decoder.container(keyedBy: SBKScrobbles.CodingKeys.self) 28 | 29 | if let scrobbleList = try? container.decode([SBKScrobble].self, forKey: SBKScrobbles.CodingKeys.scrobbles) { 30 | self.scrobbles = scrobbleList 31 | } else { 32 | let singleEntry = try container.decode(SBKScrobble.self, forKey: SBKScrobbles.CodingKeys.scrobbles) 33 | self.scrobbles = [singleEntry] 34 | } 35 | self.attr = try container.decode(SBKAttr.self, forKey: SBKScrobbles.CodingKeys.attr) 36 | } 37 | } 38 | 39 | // MARK: - Attr object 40 | struct SBKAttr: Decodable, Sendable { 41 | let ignored, accepted: Int 42 | } 43 | 44 | // MARK: - Scrobble object 45 | struct SBKScrobble: Decodable, Sendable { 46 | let artist: SBKScrobbleAttribute 47 | let album: SBKScrobbleAttribute 48 | let track: SBKScrobbleAttribute 49 | let ignoredMessage: SBKIgnoredMessage 50 | let albumArtist: SBKScrobbleAttribute 51 | let timestamp: String 52 | } 53 | 54 | struct SBKScrobbleAttribute: Decodable, Sendable { 55 | let corrected, text: String? 56 | 57 | enum CodingKeys: String, CodingKey { 58 | case corrected 59 | case text = "#text" 60 | } 61 | } 62 | 63 | // MARK: - IgnoredMessage object 64 | struct SBKIgnoredMessage: Decodable, Sendable { 65 | let code, text: String 66 | 67 | enum CodingKeys: String, CodingKey { 68 | case code 69 | case text = "#text" 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/Model/SBKTrackGetSimilarResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTrackGetSimilarResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKTrackGetSimilarResponse: Sendable, Decodable { 11 | var similarTracks: SBKTrackGetSimilarList 12 | 13 | enum CodingKeys: String, CodingKey { 14 | case similarTracks = "similartracks" 15 | } 16 | } 17 | 18 | struct SBKTrackGetSimilarList: Sendable, Decodable { 19 | var tracks: [SBKTrackGetSimilarListTrack] 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case tracks = "track" 23 | } 24 | } 25 | 26 | struct SBKTrackGetSimilarListTrack: Sendable, Decodable { 27 | var name: String 28 | var playcount: Int 29 | var mbid: String? 30 | var match: Double? 31 | var url: String 32 | var duration: Int? 33 | var artist: SBKArtist 34 | var image: [SBKImageResponse] 35 | } 36 | 37 | extension SBKSimilarTrack { 38 | init(similarTrack: SBKTrackGetSimilarListTrack) { 39 | var duration: String? { 40 | if let duration = similarTrack.duration { 41 | return "\(duration)" 42 | } else { 43 | return nil 44 | } 45 | } 46 | 47 | let track = SBKTrack(name: similarTrack.name, 48 | mbid: similarTrack.mbid, 49 | playcount: "\(similarTrack.playcount)", 50 | listeners: nil, 51 | duration: duration, 52 | artist: similarTrack.artist, 53 | url: URL(string: similarTrack.url), 54 | imageList: similarTrack.image) 55 | 56 | self.init(track: track, match: similarTrack.match) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/Model/SBKTrackRequestResponseList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTrackRequestResponseList.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKTrackRequestResponseList: Decodable, Sendable { 11 | var track: SBKTrack 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/Model/SBKTrackUpdateNowPlayingCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTrackUpdateNowPlayingCode.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 11/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | Enum representing the error codes for the track update now playing service. 12 | */ 13 | enum SBKTrackUpdateNowPlayingCode: String, Error, Decodable { 14 | case ok = "0" 15 | case ignoredArtist = "1" 16 | case ignoredTrack = "2" 17 | case tooOldTimestamp = "3" 18 | case tooNewTimestamp = "4" 19 | case dailyScrobbleLimitExceeded = "5" 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/Model/SBKTrackUpdateNowPlayingResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTrackUpdateNowPlayingResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 11/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKTrackUpdateNowPlayingResponse: Decodable, Sendable { 11 | let nowPlaying: NowPlaying 12 | 13 | enum CodingKeys: String, CodingKey { 14 | case nowPlaying = "nowplaying" 15 | } 16 | 17 | struct NowPlayingCorrectedInfo: Decodable, Sendable { 18 | var corrected: String 19 | var text: String 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case corrected 23 | case text = "#text" 24 | } 25 | } 26 | 27 | struct NowPlaying: Decodable, Sendable { 28 | let track: NowPlayingCorrectedInfo 29 | let artist: NowPlayingCorrectedInfo 30 | let album: NowPlayingCorrectedInfo 31 | let albumArtist: NowPlayingCorrectedInfo 32 | let ignoredMessage: IgnoredMessage 33 | 34 | enum CodingKeys: String, CodingKey { 35 | case track, artist, album, albumArtist = "albumArtist", ignoredMessage 36 | } 37 | } 38 | 39 | struct IgnoredMessage: Decodable, Sendable { 40 | let code: SBKTrackUpdateNowPlayingCode? 41 | 42 | enum CodingKeys: String, CodingKey { 43 | case code 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/SBKTrackSearchResultListResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTrackSearchResultListResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 15/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKTrackSearchResultListResponse: Decodable, Sendable { 11 | var track: [SBKTrack]? 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/ScrobbleService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrobbleService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ScrobbleService: SBKAuthenticatedService { 11 | typealias ResponseType = SBKScrobbleList 12 | 13 | var tracks: [SBKTrackToScrobble] 14 | 15 | var apiKey: String 16 | var secretKey: String 17 | var sessionKey: String 18 | 19 | var sbkMethod: SBKMethod = .scrobble 20 | var queries: [URLQueryItem] = [] 21 | var httpMethod: SBKHttpMethod = .post 22 | 23 | init(tracks: [SBKTrackToScrobble], sessionKey: String, apiKey: String, secretKey: String) { 24 | self.tracks = tracks 25 | self.sessionKey = sessionKey 26 | self.apiKey = apiKey 27 | self.secretKey = secretKey 28 | 29 | for (index, track) in tracks.enumerated() { 30 | queries.append(contentsOf: [ 31 | .init(name: "artist[\(index)]", value: track.artist), 32 | .init(name: "track[\(index)]", value: track.track), 33 | .init(name: "timestamp[\(index)]", value: "\(Int(track.timestamp.timeIntervalSince1970))") 34 | ]) 35 | if let album = track.album { 36 | queries.append(.init(name: "album[\(index)]", value: album)) 37 | } 38 | if let albumArtist = track.albumArtist { 39 | queries.append(.init(name: "albumArtist[\(index)]", value: albumArtist)) 40 | } 41 | if let trackNumber = track.trackNumber { 42 | queries.append(.init(name: "trackNumber[\(index)]", value: "\(trackNumber)")) 43 | } 44 | if let duration = track.duration { 45 | queries.append(.init(name: "duration[\(index)]", value: "\(duration)")) 46 | } 47 | if let chosenByUser = track.chosenByUser { 48 | queries.append(.init(name: "chosenByUser[\(index)]", value: chosenByUser ? "1" : "0")) 49 | } 50 | if let mbid = track.mbid { 51 | queries.append(.init(name: "mbid[\(index)]", value: mbid)) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackCorrectionService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackCorrectionService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackCorrectionService: SBKService { 11 | typealias ResponseType = SBKCorrectedResponse 12 | 13 | var sbkMethod: SBKMethod = .getCorrectedTrackInfo 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init(trackName: String, artistName: String, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | queries = [ 25 | .init(name: "track", value: trackName), 26 | .init(name: "artist", value: artistName) 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackGetInfoService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackGetInfoService: SBKService { 11 | var apiKey: String 12 | var secretKey: String 13 | 14 | typealias ResponseType = SBKTrackRequestResponseList 15 | 16 | var sbkMethod: SBKMethod = .getTrackInfo 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init( 21 | track: String, 22 | artist: String, 23 | mbid: String? = nil, 24 | username: String? = nil, 25 | autoCorrect: Bool = true, 26 | apiKey: String, 27 | secretKey: String 28 | ) { 29 | self.apiKey = apiKey 30 | self.secretKey = secretKey 31 | 32 | self.queries = [ 33 | .init(name: "track", value: track), 34 | .init(name: "artist", value: artist), 35 | .init(name: SBKParameter.autoCorrect.rawValue, bool: autoCorrect), 36 | .init(name: "username", value: username), 37 | .init(name: "mbid", value: mbid) 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackGetSimilarService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackGetSimilarService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackGetSimilarService: SBKService { 11 | var apiKey: String 12 | var secretKey: String 13 | 14 | typealias ResponseType = SBKTrackGetSimilarResponse 15 | 16 | var sbkMethod: SBKMethod = .getSimilarTracks 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init( 21 | track: SBKTrackSearchMethod, 22 | autoCorrect: Bool = true, 23 | limit: Int? = nil, 24 | apiKey: String, 25 | secretKey: String 26 | ) { 27 | self.apiKey = apiKey 28 | self.secretKey = secretKey 29 | 30 | var queries: [URLQueryItem] = [ 31 | .init(parameter: .autoCorrect, bool: autoCorrect), 32 | .init(name: "limit", numericValue: limit) 33 | ] 34 | 35 | 36 | switch track { 37 | case .trackInfo(let title, let artist): 38 | queries.append(contentsOf: [ 39 | .init(name: "track", value: title), 40 | .init(name: "artist", value: artist) 41 | ]) 42 | case .musicBrainzID(let mbid): 43 | queries.append(.init(parameter: .musicBrainzID, value: mbid)) 44 | } 45 | 46 | self.queries = queries 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackGetTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackGetTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackGetTagsService: SBKOptionallyAuthenticatedService { 11 | var username: String? 12 | var sessionKey: String? 13 | 14 | var apiKey: String 15 | var secretKey: String 16 | 17 | typealias ResponseType = SBKTagRequestResponse 18 | 19 | var sbkMethod: SBKMethod = .getTagsForTrack 20 | var queries: [URLQueryItem] 21 | var httpMethod: SBKHttpMethod = .get 22 | 23 | init(searchMethod: SBKTrackSearchMethod, 24 | autoCorrect: Bool = true, 25 | username: String?, 26 | apiKey: String, 27 | secretKey: String, 28 | sessionKey: String?) { 29 | self.apiKey = apiKey 30 | self.secretKey = secretKey 31 | self.sessionKey = sessionKey 32 | self.username = username 33 | 34 | var queries: [URLQueryItem] = [ 35 | .init(parameter: .autoCorrect, bool: autoCorrect) 36 | ] 37 | 38 | switch searchMethod { 39 | case .trackInfo(let title, artist: let artist): 40 | queries.append(contentsOf: [ 41 | .init(name: "track", value: title), 42 | .init(name: "artist", value: artist) 43 | ]) 44 | case .musicBrainzID(let mbid): 45 | queries.append(.init(parameter: .musicBrainzID, value: mbid)) 46 | } 47 | 48 | self.queries = queries 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackGetTopTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackGetTopTagsService: SBKService { 11 | var apiKey: String 12 | var secretKey: String 13 | 14 | typealias ResponseType = SBKChartGetTopTagsResponse 15 | 16 | var sbkMethod: SBKMethod = .getTopTagsForTrack 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init( 21 | searchMethod: SBKTrackSearchMethod, 22 | autoCorrect: Bool = true, 23 | apiKey: String, 24 | secretKey: String 25 | ) { 26 | self.apiKey = apiKey 27 | self.secretKey = secretKey 28 | 29 | var queries: [URLQueryItem] = [ 30 | .init(parameter: .autoCorrect, bool: true), 31 | ] 32 | 33 | switch searchMethod { 34 | case .trackInfo(let title, let artist): 35 | queries.append(contentsOf: [ 36 | .init(name: "track", value: title), 37 | .init(name: "artist", value: artist) 38 | ]) 39 | case .musicBrainzID(let mbid): 40 | queries.append(.init(parameter: .musicBrainzID, value: mbid)) 41 | } 42 | 43 | self.queries = queries 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackLoveService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackLoveService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackLoveService: SBKAuthenticatedService { 11 | var apiKey: String 12 | var secretKey: String 13 | var sessionKey: String 14 | 15 | typealias ResponseType = SBKEmptyResponse 16 | 17 | var sbkMethod: SBKMethod = .loveTrack 18 | var queries: [URLQueryItem] 19 | var httpMethod: SBKHttpMethod = .post 20 | 21 | init( 22 | track: String, 23 | artist: String, 24 | apiKey: String, 25 | secretKey: String, 26 | sessionKey: String 27 | ) { 28 | self.apiKey = apiKey 29 | self.secretKey = secretKey 30 | self.sessionKey = sessionKey 31 | self.queries = [ 32 | .init(name: "track", value: track), 33 | .init(name: "artist", value: artist) 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackSearchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackSearchService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 15/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackSearchService: SBKService { 11 | typealias ResponseType = SBKSearchResultsResponse 12 | 13 | var sbkMethod: SBKMethod = .searchForTrack 14 | var queries: [URLQueryItem] 15 | var httpMethod: SBKHttpMethod = .get 16 | 17 | var apiKey: String 18 | var secretKey: String 19 | 20 | init( 21 | track: String, 22 | artist: String?, 23 | limit: Int, 24 | page: Int, 25 | apiKey: String, 26 | secretKey: String 27 | ) { 28 | self.apiKey = apiKey 29 | self.secretKey = secretKey 30 | 31 | queries = [ 32 | .init(name: "track", value: track), 33 | .init(name: "artist", value: artist), 34 | .init(name: "limit", numericValue: limit), 35 | .init(name: "page", numericValue: page) 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackUnloveService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackUnloveService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 15/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackUnloveService: SBKAuthenticatedService { 11 | var apiKey: String 12 | var secretKey: String 13 | var sessionKey: String 14 | 15 | typealias ResponseType = SBKEmptyResponse 16 | 17 | var sbkMethod: SBKMethod = .unloveTrack 18 | var queries: [URLQueryItem] 19 | var httpMethod: SBKHttpMethod = .post 20 | 21 | init( 22 | track: String, 23 | artist: String, 24 | apiKey: String, 25 | secretKey: String, 26 | sessionKey: String 27 | ) { 28 | self.apiKey = apiKey 29 | self.secretKey = secretKey 30 | self.sessionKey = sessionKey 31 | self.queries = [ 32 | .init(name: "track", value: track), 33 | .init(name: "artist", value: artist) 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/Track/TrackUpdateNowPlayingService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackUpdateNowPlayingService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 11/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TrackUpdateNowPlayingService: SBKAuthenticatedService { 11 | var sessionKey: String 12 | 13 | typealias ResponseType = SBKTrackUpdateNowPlayingResponse 14 | 15 | var sbkMethod: SBKMethod = .updateNowPlaying 16 | var queries: [URLQueryItem] 17 | var httpMethod: SBKHttpMethod = .post 18 | 19 | var apiKey: String 20 | var secretKey: String 21 | 22 | init(artist: String, 23 | track: String, 24 | album: String?, 25 | trackNumber: Int?, 26 | context: String?, 27 | mbid: String?, 28 | duration: Int?, 29 | albumArtist: String?, 30 | apiKey: String, 31 | secretKey: String, 32 | sessionKey: String 33 | ) { 34 | self.apiKey = apiKey 35 | self.secretKey = secretKey 36 | self.sessionKey = sessionKey 37 | 38 | var queries: [URLQueryItem] = [ 39 | .init(name: "artist", value: artist), 40 | .init(name: "track", value: track) 41 | ] 42 | 43 | if let album = album { 44 | queries.append(.init(name: "album", value: album)) 45 | } 46 | 47 | if let trackNumber = trackNumber { 48 | queries.append(.init(name: "trackNumber", numericValue: trackNumber)) 49 | } 50 | 51 | if let context = context { 52 | queries.append(.init(name: "context", value: context)) 53 | } 54 | 55 | if let mbid = mbid { 56 | queries.append(.init(name: "mbid", value: mbid)) 57 | } 58 | 59 | if let duration = duration { 60 | queries.append(.init(name: "duration", numericValue: duration)) 61 | } 62 | 63 | if let albumArtist = albumArtist { 64 | queries.append(.init(name: "albumArtist", value: albumArtist)) 65 | } 66 | 67 | self.queries = queries 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/Model/SBKUserGetFriendsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKUserGetFriendsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKUserGetFriendsRootObject: Decodable, Sendable { 11 | let friends: SBKUserGetFriendsResponse 12 | } 13 | 14 | struct SBKUserGetFriendsResponse: Decodable, Sendable { 15 | let attributes: SBKUserGetFriendsAttribute 16 | let friends: [SBKUserInfoDataResponse] 17 | 18 | private enum CodingKeys: String, CodingKey { 19 | case friends = "user" 20 | case attributes = "@attr" 21 | } 22 | } 23 | 24 | struct SBKUserGetFriendsAttribute: Decodable, Sendable { 25 | var user: String 26 | var totalPages: String 27 | var page: String 28 | var total: String 29 | var perPage: String 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/Model/SBKUserGetRecentTracksResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKUserGetRecentTracksResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKUserGetRecentTracksResponse: Decodable, Sendable { 11 | var recenttracks: SBKUserGetRecentTracksResult 12 | } 13 | 14 | struct SBKUserGetRecentTracksResult: Decodable, Sendable { 15 | let tracks: [SBKScrobbledTrack] 16 | let attributes: SBKUserGetRecentTracksAttributes 17 | 18 | private enum CodingKeys: String, CodingKey { 19 | case tracks = "track" 20 | case attributes = "@attr" 21 | } 22 | 23 | 24 | init(from decoder: Decoder) throws { 25 | let container: KeyedDecodingContainer = try decoder.container(keyedBy: SBKUserGetRecentTracksResult.CodingKeys.self) 26 | let trackList = try container.decode([SBKThrowable].self, forKey: SBKUserGetRecentTracksResult.CodingKeys.tracks) 27 | self.tracks = trackList.compactMap { $0.object } 28 | self.attributes = try container.decode(SBKUserGetRecentTracksAttributes.self, forKey: SBKUserGetRecentTracksResult.CodingKeys.attributes) 29 | 30 | } 31 | } 32 | 33 | struct SBKUserGetRecentTracksAttributes: SBKSearchAttribute { 34 | var user: String 35 | var page: String 36 | var perPage: String 37 | var totalPages: String 38 | var total: String 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/Model/SBKUserGetTopAlbumsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKUserGetTopAlbumsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKUserGetTopAlbumsResponse: Decodable, Sendable { 11 | let topAlbums: SBKUserGetTopAlbumsResult 12 | 13 | private enum CodingKeys: String, CodingKey { 14 | case topAlbums = "topalbums" 15 | } 16 | } 17 | 18 | struct SBKUserGetTopAlbumsResult: Decodable, Sendable { 19 | let attributes: SBKUserGetTopAlbumsAttributes 20 | let albums: [SBKAlbum] 21 | 22 | private enum CodingKeys: String, CodingKey { 23 | case albums = "album" 24 | case attributes = "@attr" 25 | } 26 | } 27 | 28 | struct SBKUserGetTopAlbumsAttributes: SBKSearchAttribute { 29 | var user: String 30 | var page: String 31 | var perPage: String 32 | var totalPages: String 33 | var total: String 34 | } 35 | 36 | struct SBKUserGetTopArtistsResponse: Decodable, Sendable { 37 | let topArtists: SBKUserGetTopArtistsResult 38 | 39 | private enum CodingKeys: String, CodingKey { 40 | case topArtists = "topartists" 41 | } 42 | } 43 | 44 | struct SBKUserGetTopArtistsResult: Decodable, Sendable { 45 | let attributes: SBKUserGetTopAlbumsAttributes 46 | let artists: [SBKArtist] 47 | 48 | private enum CodingKeys: String, CodingKey { 49 | case artists = "artist" 50 | case attributes = "@attr" 51 | } 52 | } 53 | 54 | struct SBKUserGetTopTagsResponse: Decodable, Sendable { 55 | let topTags: SBKUserGetTopTagsResult 56 | 57 | private enum CodingKeys: String, CodingKey { 58 | case topTags = "toptags" 59 | } 60 | } 61 | 62 | struct SBKUserGetTopTagsResult: Decodable, Sendable { 63 | let attributes: SBKUserGetTopTagsAttribute 64 | let tags: [SBKTag] 65 | 66 | private enum CodingKeys: String, CodingKey { 67 | case tags = "tag" 68 | case attributes = "@attr" 69 | } 70 | } 71 | 72 | struct SBKUserGetTopTagsAttribute: Decodable, Sendable { 73 | let user: String 74 | } 75 | 76 | struct SBKUserGetTopTracksResponse: Decodable, Sendable { 77 | let topTracks: SBKUserGetTopTracksResult 78 | 79 | private enum CodingKeys: String, CodingKey { 80 | case topTracks = "toptracks" 81 | } 82 | } 83 | 84 | struct SBKUserGetTopTracksResult: Decodable, Sendable { 85 | let attributes: SBKUserGetTopAlbumsAttributes 86 | let tracks: [SBKTrack] 87 | 88 | private enum CodingKeys: String, CodingKey { 89 | case tracks = "track" 90 | case attributes = "@attr" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/Model/SBKUserPersonalTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKUserPersonalTags.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 17/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKUserPersonalTagsAttributes: SBKSearchAttribute { 11 | var user: String 12 | var tag: String 13 | var page: String 14 | var perPage: String 15 | var totalPages: String 16 | var total: String 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/Model/SBKUserTagsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKUserTagsResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 17/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKUserTagsResponse: Decodable, Sendable { 11 | var taggings: SBKUserTagsList 12 | } 13 | 14 | struct SBKUserTagsList: Decodable, Sendable { 15 | var tracks: SBKUserTagsListResults? 16 | var albums: SBKUserTagsListResults? 17 | var artists: SBKUserTagsListResults? 18 | var attributes: SBKUserPersonalTagsAttributes 19 | 20 | enum CodingKeys: String, CodingKey { 21 | case tracks, albums, artists 22 | case attributes = "@attr" 23 | } 24 | } 25 | 26 | struct SBKUserTagsListResults: Decodable, Sendable { 27 | var track: [SBKTrack]? 28 | var album: [SBKAlbum]? 29 | var artist: [SBKArtist]? 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/Model/UserGetLovedTracksResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetLovedTracksResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct _UserGetLovedTracksResponse: Decodable, Sendable { 11 | let lovedtracks: _UserLovedTracksList 12 | } 13 | 14 | struct _UserLovedTracksList: Decodable, Sendable { 15 | let track: [_UserLovedTrack] 16 | let attributes: _UserLovedTracksAttributes 17 | 18 | private enum CodingKeys: String, CodingKey { 19 | case track 20 | case attributes = "@attr" 21 | } 22 | } 23 | 24 | struct _UserLovedTrack: Decodable, Sendable { 25 | let artist: SBKArtist 26 | let date: SBKTimestamp 27 | let name: String 28 | let mbid: String? 29 | let url: String 30 | let image: [SBKImageResponse] 31 | } 32 | 33 | struct _UserLovedTracksAttributes: Decodable, Sendable { 34 | let user: String 35 | let totalPages: String 36 | let page: String 37 | let perPage: String 38 | let total: String 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetFriendsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetFriendsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 15/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetFriendsService: SBKService { 11 | typealias ResponseType = SBKUserGetFriendsRootObject 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getUserFriends 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init( 21 | user: String, 22 | recentTracks: Bool, 23 | limit: Int, 24 | page: Int, 25 | apiKey: String, 26 | secretKey: String 27 | ) { 28 | self.apiKey = apiKey 29 | self.secretKey = secretKey 30 | 31 | queries = [ 32 | .init(name: "user", value: user), 33 | .init(name: "recenttracks", bool: recentTracks), 34 | .init(name: "limit", numericValue: limit), 35 | .init(name: "page", numericValue: page) 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetInfoService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 25/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetInfoService: SBKOptionallyAuthenticatedService { 11 | var username: String? 12 | var sessionKey: String? 13 | 14 | typealias ResponseType = SBKUserInfoResponse 15 | 16 | var sbkMethod: SBKMethod = .getUserInfo 17 | var queries: [URLQueryItem] = [] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | var apiKey: String 21 | var secretKey: String 22 | 23 | init(username: String?, apiKey: String, secretKey: String, sessionKey: String?) { 24 | self.apiKey = apiKey 25 | self.secretKey = secretKey 26 | self.sessionKey = sessionKey 27 | self.username = username 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetLovedTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetLovedTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetLovedTracksService: SBKService { 11 | typealias ResponseType = _UserGetLovedTracksResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getUserLovedTracks 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init( 21 | user: String, 22 | limit: Int, 23 | page: Int, 24 | apiKey: String, 25 | secretKey: String 26 | ) { 27 | self.apiKey = apiKey 28 | self.secretKey = secretKey 29 | 30 | queries = [ 31 | .init(name: "user", value: user), 32 | .init(name: "limit", numericValue: limit), 33 | .init(name: "page", numericValue: page) 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetPersonalTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetPersonalTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetPersonalTagsService: SBKService { 11 | typealias ResponseType = SBKUserTagsResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getUserPersonalTags 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(user: String, tag: String, taggingType: String, limit: Int = 50, page: Int = 1, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | queries = [ 25 | .init(name: "user", value: user), 26 | .init(name: "tag", value: tag), 27 | .init(name: "taggingtype", value: taggingType), 28 | .init(name: "limit", numericValue: limit), 29 | .init(name: "page", numericValue: page) 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetRecentTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetRecentTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetRecentTracksService: SBKService { 11 | typealias ResponseType = SBKUserGetRecentTracksResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .recentTracks 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(user: String, 21 | limit: Int = 50, 22 | page: Int = 1, 23 | from: Date? = nil, 24 | extended: Bool? = nil, 25 | to: Date? = nil, 26 | apiKey: String, 27 | secretKey: String) { 28 | self.apiKey = apiKey 29 | self.secretKey = secretKey 30 | 31 | var queryItems: [URLQueryItem] = [ 32 | .init(name: "user", value: user), 33 | .init(name: "limit", numericValue: limit), 34 | .init(name: "page", numericValue: page) 35 | ] 36 | 37 | if let fromDate = from { 38 | queryItems.append(.init(name: "from", numericValue: Int(fromDate.timeIntervalSince1970))) 39 | } 40 | 41 | if let extendedData = extended { 42 | queryItems.append(.init(name: "extended", bool: extendedData)) 43 | } 44 | 45 | if let toDate = to { 46 | queryItems.append(.init(name: "to", numericValue: Int(toDate.timeIntervalSince1970))) 47 | } 48 | 49 | queries = queryItems 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetTopAlbumsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetTopAlbumsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetTopAlbumsService: SBKService { 11 | typealias ResponseType = SBKUserGetTopAlbumsResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getUserTopAlbums 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(user: String, period: String? = nil, limit: Int = 50, page: Int = 1, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | var queryItems: [URLQueryItem] = [ 25 | .init(name: "user", value: user), 26 | .init(name: "limit", numericValue: limit), 27 | .init(name: "page", numericValue: page) 28 | ] 29 | 30 | if let timePeriod = period { 31 | queryItems.append(.init(name: "period", value: timePeriod)) 32 | } 33 | 34 | queries = queryItems 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetTopArtistsService: SBKService { 11 | typealias ResponseType = SBKUserGetTopArtistsResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getUserTopArtists 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(user: String, period: String? = nil, limit: Int = 50, page: Int = 1, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | var queryItems: [URLQueryItem] = [ 25 | .init(name: "user", value: user), 26 | .init(name: "limit", numericValue: limit), 27 | .init(name: "page", numericValue: page) 28 | ] 29 | 30 | if let timePeriod = period { 31 | queryItems.append(.init(name: "period", value: timePeriod)) 32 | } 33 | 34 | queries = queryItems 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetTopTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetTopTagsService: SBKService { 11 | typealias ResponseType = SBKUserGetTopTagsResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getUserTopTags 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(user: String, limit: Int? = nil, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | var queryItems: [URLQueryItem] = [ 25 | .init(name: "user", value: user) 26 | ] 27 | 28 | if let tagLimit = limit { 29 | queryItems.append(.init(name: "limit", numericValue: tagLimit)) 30 | } 31 | 32 | queries = queryItems 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Methods/User/UserGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserGetTopTracksService: SBKService { 11 | typealias ResponseType = SBKUserGetTopTracksResponse 12 | 13 | var apiKey: String 14 | var secretKey: String 15 | 16 | var sbkMethod: SBKMethod = .getUserTopTracks 17 | var queries: [URLQueryItem] 18 | var httpMethod: SBKHttpMethod = .get 19 | 20 | init(user: String, period: String? = nil, limit: Int = 50, page: Int = 1, apiKey: String, secretKey: String) { 21 | self.apiKey = apiKey 22 | self.secretKey = secretKey 23 | 24 | var queryItems: [URLQueryItem] = [ 25 | .init(name: "user", value: user), 26 | .init(name: "limit", numericValue: limit), 27 | .init(name: "page", numericValue: page) 28 | ] 29 | 30 | if let timePeriod = period { 31 | queryItems.append(.init(name: "period", value: timePeriod)) 32 | } 33 | 34 | queries = queryItems 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Protocols/SBKAuthenticatedService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKAuthenticatedService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol SBKAuthenticatedService: SBKService { 11 | var sessionKey: String { get set } 12 | } 13 | 14 | extension SBKAuthenticatedService { 15 | var completedQueries: [URLQueryItem] { 16 | return [ 17 | .init(parameter: .format, value: "json"), 18 | .init(parameter: .method, value: sbkMethod.rawValue), 19 | .init(parameter: .apiKey, value: apiKey), 20 | .init(parameter: .sessionKey, value: sessionKey) 21 | ] + queries.filter { $0.value != nil} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Internal/Services/Protocols/SBKOptionallyAuthenticatedService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKOptionallyAuthenticatedService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 25/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol SBKOptionallyAuthenticatedService: SBKService { 11 | var username: String? { get set } 12 | var sessionKey: String? { get set } 13 | } 14 | 15 | extension SBKOptionallyAuthenticatedService { 16 | var completedQueries: [URLQueryItem] { 17 | var queries = [ 18 | .init(parameter: .format, value: "json"), 19 | .init(parameter: .method, value: sbkMethod.rawValue), 20 | .init(parameter: .apiKey, value: apiKey), 21 | ] + queries.filter { $0.value != nil} 22 | 23 | if let username { 24 | queries.append(.init(parameter: .username, value: username)) 25 | } else if let sessionKey { 26 | queries.append(.init(parameter: .sessionKey, value: sessionKey)) 27 | } 28 | 29 | return queries 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Helpers/SBKURLBuilderError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKURLBuilderError.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 09/08/24. 6 | // 7 | 8 | 9 | /// Represents errors that can occur when building URLs with SBKURLBuilder. 10 | public enum SBKURLBuilderError: Error { 11 | /// Indicates that the base URL for Last.fm is invalid. 12 | /// - This error should not occur under normal circumstances as the base URL is hardcoded. 13 | /// If you encounter this error: 14 | /// 1. Ensure you're using the latest version of the package. 15 | /// 2. If the error persists, please [file an issue on the package's GitHub repository](https://github.com/tfmart/ScrobbleKit/issues/new) 16 | /// with details about your environment, package version, and how you're using the package. 17 | case badBaseURL 18 | 19 | /// Indicates that the generated URL is invalid. 20 | /// Solution: Verify the input parameters and ensure they don't contain characters that could make the URL invalid. 21 | case generatedBadURL 22 | 23 | /// Indicates that a parameter couldn't be properly encoded. 24 | /// Solution: Check the input string for any unusual characters and consider pre-processing the input to remove or replace problematic characters. 25 | case badParameter(String) 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Album/AddTags/SBKManager+AlbumAddTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AlbumAddTags.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Tag an album using a list of user-supplied tags. 13 | 14 | - Parameters: 15 | - artist: The name of the artist. 16 | - album: The name of the album. 17 | - tags: A list of user-supplied tags to apply to this album. **Accepts a maximum of 10 tags.** 18 | 19 | - Returns: A boolean value indicating whether the operation was successful. 20 | 21 | - Note: See [Last.fm's album.addTags documentation](https://www.last.fm/api/show/album.addTags) for more information. 22 | */ 23 | @discardableResult 24 | func addTags(toAlbum album: String, artist: String, tags: [String]) async throws -> Bool { 25 | guard let sessionKey else { throw SBKClientError.missingSessionKey } 26 | let service = AddTagsService(to: .album(album, artist: artist), 27 | tags: tags, 28 | apiKey: apiKey, 29 | secretKey: secret, 30 | sessionKey: sessionKey) 31 | _ = try await service.start() 32 | return true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Album/GetInfo/SBKManager+AlbumGetInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AlbumGetInfo.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the metadata and tracklist for an album on Last.fm using the album name or a MusicBrainz ID. 13 | 14 | - Parameters: 15 | - searchMethod: The search method to use for the album. The search method can be an album name or a MusicBrainz ID. 16 | - autoCorrect: Transform misspelled artist or album names into correct names, returning the correct version instead. The corrected artist name will be returned in the response. The default value is `true`. 17 | - username: The username for the context of the request. If supplied, the user's playcount for this album is included in the response. The default value is `nil`. 18 | - languageCode: The language to return the biography in, expressed as an ISO 639 alpha-2 code. The default value is ``SBKLanguageCode/english``. 19 | 20 | - Returns: An ``SBKAlbumResponse`` object that contains the metadata and tracklist for the album. 21 | 22 | - Throws: An error of type ``SBKClientError``or ``SBKError`` if the operation fails 23 | 24 | - Note: See [Last.fm's album.addTags documentation](https://www.last.fm/api/show/album.getInfo) for more information. 25 | */ 26 | func getInfo(forAlbum searchMethod: SBKAlbumSearchMethod, 27 | autoCorrect: Bool = true, 28 | username: String? = nil, 29 | languageCode: SBKLanguageCode = .english 30 | ) async throws -> SBKAlbum { 31 | let service = AlbumGetInfoService( 32 | searchMethod: searchMethod, 33 | autoCorrect: autoCorrect, 34 | username: username, 35 | languageCode: languageCode, 36 | apiKey: apiKey, 37 | secretKey: secret 38 | ) 39 | let response = try await service.start() 40 | return .init(albumResponse: response) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Album/GetTags/SBKManager+AlbumGetTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AlbumGetTags.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension SBKManager { 11 | /** 12 | Gets the tags applied by an individual user to an album on Last.fm. 13 | 14 | To retrieve the list of tags applied to an album by all users use ``getTopTags(forAlbum:autoCorrect:)`` 15 | 16 | - Parameters: 17 | - searchMethod: The album search method, either by name or by a MusicBrainz ID. 18 | - autoCorrect: A Boolean value indicating whether to transform misspelled artist names into correct artist names. The default value is `true`. 19 | - username: The username for the context of the request. If supplied, the tags of this album applied by the user are included in the response. 20 | 21 | - Returns: An array of ``SBKTag`` objects representing the tags applied by the user to the album. 22 | 23 | - Throws: An error of type ``SBKClientError``or ``SBKError`` if the operation fails 24 | 25 | - Note: See [Last.fm's album.addTags documentation](https://www.last.fm/api/show/album.getTags) for more information. 26 | */ 27 | public func getTags( 28 | forAlbum searchMethod: SBKAlbumSearchMethod, 29 | autoCorrect: Bool = true, 30 | username: String? = nil 31 | ) async throws -> [SBKTag] { 32 | let service = AlbumGetTagsService( 33 | searchMethod: searchMethod, 34 | autoCorrect: autoCorrect, 35 | username: username, 36 | apiKey: apiKey, 37 | secretKey: secret 38 | ) 39 | let response = try await service.start() 40 | guard let tags = response.tags.tag else { 41 | return [] 42 | } 43 | return tags 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Album/GetTopTags/SBKManager+AlbumGetTopTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AlbumGetTopTags.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension SBKManager { 11 | /** 12 | Fetches the top tags for an album on Last.fm, ordered by popularity. 13 | 14 | - Parameters: 15 | - searchMethod: The search method for the album, either by album name and artist name or by MusicBrainz ID.. 16 | - autoCorrect: Whether to transform misspelled artist names into correct artist names, returning the correct version instead. The corrected artist name will be returned in the response. The default value is `true`. 17 | 18 | - Returns: An array of ``SBKTag`` objects representing the top tags for the specified album. 19 | 20 | - Throws: An error of type ``SBKClientError``or ``SBKError`` if the operation fails 21 | - Note: See [Last.fm's album.addTags documentation](https://www.last.fm/api/show/album.getTopTags) for more information. 22 | */ 23 | public func getTopTags( 24 | forAlbum searchMethod: SBKAlbumSearchMethod, 25 | autoCorrect: Bool = true 26 | ) async throws -> [SBKTag] { 27 | let service = AlbumGetTopTagsService( 28 | searchMethod: searchMethod, 29 | autoCorrect: autoCorrect, 30 | apiKey: apiKey, 31 | secretKey: secret 32 | ) 33 | let response = try await service.start() 34 | guard let tags = response.toptags.tag else { 35 | return [] 36 | } 37 | return tags 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Album/RemoveTag/SBKManager+AlbumRemoveTagService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AlbumRemoveTagService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Remove a user's tag from an album on Last.fm. 13 | 14 | - Parameter album: The name of the album. 15 | - Parameter artist: The name of the album's artist. 16 | - Parameter tag: The tag to remove from the album. 17 | - Returns: A Boolean value indicating whether the tag was removed from the album. 18 | - Throws: An error of type ``SBKClientError``or ``SBKError`` if the operation fails 19 | 20 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/album.removeTag). 21 | */ 22 | @discardableResult 23 | func removeTag(fromAlbum album: String, artist: String, tag: String) async throws -> Bool { 24 | guard let sessionKey else { throw SBKClientError.missingSessionKey } 25 | let service = RemoveTagService( 26 | to: .album(album, artist: artist), 27 | tag: tag, 28 | apiKey: apiKey, 29 | secretKey: secret, 30 | sessionKey: sessionKey 31 | ) 32 | _ = try await service.start() 33 | return true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Album/Search/SBKManager+AlbumSearchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AlbumSearchService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Searches for an album by name. Returns album matches sorted by relevance. 13 | 14 | - Parameters: 15 | - album: The album name to search for. 16 | - page: The page number to fetch. Defaults to 1. 17 | - limit: The number of results to fetch per page. Defaults to 50. 18 | 19 | - Returns: An array of ``SBKAlbum`` objects representing the search results. 20 | 21 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 22 | 23 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/album.search). 24 | */ 25 | func search( 26 | album: String, 27 | page: Int = 1, 28 | limit: Int = 50 29 | ) async throws -> [SBKAlbum] { 30 | let service = AlbumSearchService(album: album, limit: limit, page: page, apiKey: apiKey, secretKey: secret) 31 | let response = try await service.start() 32 | guard let albumList = response.results.matches.album else { 33 | throw SBKClientError.failedToDecodeResponse 34 | } 35 | return albumList 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/AddTags/SBKManager+ArtistAddTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AddTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /// Adds tags to an artist asynchronously. 12 | /// 13 | /// - Parameters: 14 | /// - artist: The name of the artist to add tags to. 15 | /// - tags: An array of tags to be added. 16 | /// - Returns: A boolean value indicating whether the operation was successful. 17 | /// - Throws: ``SBKClientError`` if the operation fails, or an error returned by the Last.fm 18 | @discardableResult 19 | func addTags(toArtist artist: String, tags: [String]) async throws -> Bool { 20 | guard let sessionKey else { throw SBKClientError.missingSessionKey } 21 | let service = AddTagsService(to: .artist(artist), 22 | tags: tags, 23 | apiKey: apiKey, 24 | secretKey: secret, 25 | sessionKey: sessionKey) 26 | _ = try await service.start() 27 | return true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/GetCorrection/SBKManager+ArtistGetCorrectionService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ArtistGetCorrectionService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /// Asynchronously checks whether the supplied artist has a correction to a canonical artist 12 | /// 13 | /// - Parameters: 14 | /// - artist: The artist name to correct. 15 | /// - Returns: The corrected artist name, or `nil` if no correction was made. 16 | /// - Throws: ``SBKClientError`` if the operation fails, or an error returned by the Last.fm 17 | func getCorrectedArtistName(_ artist: String) async throws -> SBKArtist? { 18 | let service = ArtistGetCorrectionService(artist: artist, apiKey: apiKey, secretKey: secret) 19 | let response = try await service.start() 20 | return response.corrections.correction.result 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/GetInfo/SBKManager+ArtistGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+AristGetInfoService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | public extension SBKManager { 12 | /** 13 | Get the metadata for an artist on Last.fm using the artist name or a MusicBrainz ID. 14 | 15 | - Parameters: 16 | - artist: The search method to use for the artist. The search method can be an artist name or a MusicBrainz ID. 17 | - autocorrect: Transform misspelled artist or album names into correct names, returning the correct version instead. The corrected artist name will be returned in the response. The default value is `true`. 18 | - username: The username for the context of the request. If supplied, the user's playcount for this artist is included in the response. The default value is `nil`. 19 | - language: The language to return the biography in, expressed as an ISO 639 alpha-2 code. The default value is ``SBKLanguageCode.english`. 20 | 21 | - Returns: An ``SBKArtist`` object that contains the metadata for the artist. 22 | 23 | - Throws: An error of type ``SBKClientError``or ``SBKError`` if the operation fails 24 | 25 | - Note: See [Last.fm's artist.getInfo documentation](https://www.last.fm/api/show/artist.getInfo) for more information. 26 | */ 27 | func getInfo( 28 | forArtist artist: SBKArtistSearchMethod, 29 | autocorrect: Bool = true, 30 | username: String? = nil, 31 | language: SBKLanguageCode = .english 32 | ) async throws -> SBKArtist { 33 | let service = ArtistGetInfoService( 34 | searchMethod: artist, 35 | autoCorrect: autocorrect, 36 | username: username, 37 | languageCode: language, 38 | apiKey: apiKey, 39 | secretKey: secret 40 | ) 41 | let response = try await service.start() 42 | return SBKArtist(getInfoData: response) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/GetSimilar/SBKManager+ArtistGetSimilar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ArtistGetSimilar.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Gets a list of similar artists for the given artist. 13 | 14 | - Parameters: 15 | - artist: The name of the artist to get similar artists for. 16 | - limit: The maximum number of similar artists to return. 17 | 18 | - Returns: An array of ``SBKArtist`` objects representing the similar artists. 19 | 20 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 21 | 22 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/artist.getSimilar). 23 | */ 24 | func getSimilarArtists( 25 | _ searchMethod: SBKArtistSearchMethod, 26 | limit: Int = 50, 27 | autoCorrect: Bool = true 28 | ) async throws -> [SBKSimilarArtist] { 29 | let service = ArtistGetSimilarService( 30 | searchMethod, 31 | limit: limit, 32 | autoCorrect: autoCorrect, 33 | apiKey: apiKey, 34 | secretKey: secret 35 | ) 36 | let response = try await service.start() 37 | return response.similarartists.artist 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/GetTags/SBKManager+ArtistGetTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ArtistGetTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Asynchronously retrieves the tags applied by an individual user to an artist on Last.fm. 13 | 14 | - Parameters: 15 | - searchMethod: The artist search method, either by name or by MusicBrainz ID. 16 | - user: The username of the user who applied the tags to the artist. If not provided, all user tags will be returned. 17 | - autocorrect: A Boolean value indicating whether to correct any misspelled artist names. The default value is `true`. 18 | 19 | - Returns: An array of ``SBKTag`` objects representing the tags applied by the user to the artist. If no tags were found, an empty array is returned. 20 | 21 | - Throws: An error of type ``SBKClientError`` or an error returned by the Last.fm API. 22 | 23 | - Note: See [Last.fm's artist.getTags documentation](https://www.last.fm/api/show/artist.getTags) for more information. 24 | */ 25 | func getTags(forArtist searchMethod: SBKArtistSearchMethod, 26 | user: String? = nil, 27 | autocorrect: Bool) async throws -> [SBKTag] { 28 | let service = ArtistGetTagsService( 29 | searchMethod: searchMethod, 30 | user: user, 31 | autocorrect: autocorrect, 32 | apiKey: apiKey, 33 | secretKey: secret) 34 | let response = try await service.start() 35 | guard let tags = response.tags.tag else { 36 | return [] 37 | } 38 | return tags 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/GetTopAlbums/SBKManager+ArtistGetTopAlbums.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ArtistGetTopAlbums.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Gets the top albums for the specified artist. 13 | 14 | - Parameters: 15 | - searchMethod: The search method to use to find the artist. Either by name or MusicBrainz ID. 16 | - limit: The maximum number of albums to return. Default is 50. 17 | - page: The page of results to return. Default is 1. 18 | 19 | - Returns: An array of ``SBKAlbum`` objects representing the artist's top albums. 20 | 21 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 22 | 23 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/artist.getTopAlbums). 24 | */ 25 | func getTopAlbums( 26 | forArtist searchMethod: SBKArtistSearchMethod, 27 | limit: Int = 50, 28 | page: Int = 1, 29 | autoCorrect: Bool = true 30 | ) async throws -> [SBKAlbum] { 31 | let service = ArtistGetTopAlbumsService( 32 | searchMethod, 33 | limit: limit, 34 | page: page, 35 | autoCorrect: autoCorrect, 36 | apiKey: apiKey, 37 | secretKey: secret 38 | ) 39 | let response = try await service.start() 40 | return response.topAlbums.albums.map({ SBKAlbum(topAlbumArtist: $0) }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/GetTopTracks/SBKManager+ArtistGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ArtistGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | public extension SBKManager { 12 | /** 13 | Gets the top tracks by an artist. 14 | 15 | - Parameters: 16 | - artist: The name of the artist. 17 | - limit: The number of tracks to return. 18 | - page: The page number to fetch. 19 | 20 | - Returns: An array of ``SBKTrack`` objects representing the artist's top tracks. 21 | 22 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 23 | 24 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/artist.getTopTracks). 25 | */ 26 | func getArtistTopTracks( 27 | _ searchMethod: SBKArtistSearchMethod, 28 | limit: Int = 50, 29 | page: Int = 1, 30 | autoCorrect: Bool = true 31 | ) async throws -> [SBKTrack] { 32 | let service = ArtistGetTopTracksService( 33 | searchMethod, 34 | limit: limit, 35 | page: page, 36 | autoCorrect: autoCorrect, 37 | apiKey: apiKey, 38 | secretKey: secret 39 | ) 40 | let response = try await service.start() 41 | return response.toptracks.track 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/RemoveTag/SBKManager+ArtistRemoveTagService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ArtistRemoveTagService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | 12 | /** 13 | Remove a user's tag from an artist on Last.fm. 14 | 15 | - Parameters: 16 | - artist: The name of the artist to remove the tag from. 17 | - tag: The tag to remove. 18 | 19 | - Returns: A boolean value indicating whether the tag was successfully removed. 20 | 21 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or the user is not authenticated. 22 | 23 | - Note: This method requires user authentication. Make sure the user is logged in before calling this method. 24 | For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/artist.removeTag). 25 | */ 26 | @discardableResult 27 | func removeTag(fromArtist artist: String, tag: String) async throws -> Bool { 28 | guard let sessionKey else { throw SBKClientError.missingSessionKey } 29 | let service = RemoveTagService( 30 | to: .artist(artist), 31 | tag: tag, 32 | apiKey: apiKey, 33 | secretKey: secret, 34 | sessionKey: sessionKey 35 | ) 36 | _ = try await service.start() 37 | return true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Artist/Search/SBKManager+ArtistSearchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ArtistSearchService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Searches for artists similar to the given query. 13 | 14 | - Parameters: 15 | - query: The search query. 16 | - limit: The maximum number of results to return. 17 | - page: The page number of results to return. 18 | 19 | - Returns: An array of ``SBKArtist`` objects representing the search results. 20 | 21 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 22 | 23 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/artist.search). 24 | */ 25 | func search( 26 | artist query: String, 27 | limit: Int = 50, 28 | page: Int = 1 29 | ) async throws -> [SBKArtist] { 30 | let service = ArtistSearchService( 31 | query, 32 | limit: limit, 33 | page: page, 34 | apiKey: apiKey, 35 | secretKey: secret 36 | ) 37 | let response = try await service.start() 38 | return response.results.matches.artists 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Chart/GetTopArtists/SBKManager+ChartGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ChartGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /// Fetches the top artists chart from Last.fm. 12 | /// 13 | /// - Parameters: 14 | /// - page: The page number to fetch. Defaults to first page. 15 | /// - limit: The number of results to fetch per page. Defaults to 50. 16 | /// - Returns: An array of ``SBKArtist`` objects. 17 | /// - Throws: An error if the API call fails or the response cannot be decoded. 18 | /// 19 | /// See [Last.fm API documentation](https://www.last.fm/api/show/chart.getTopArtists) for more information. 20 | func getArtistsChart( 21 | page: Int = 1, 22 | limit: Int = 50 23 | ) async throws -> [SBKArtist] { 24 | let service = ChartGetTopArtistsService( 25 | limit: limit, 26 | page: page, 27 | apiKey: apiKey, 28 | secretKey: secret 29 | ) 30 | let response = try await service.start() 31 | return response.artists.artists 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Chart/GetTopTags/SBKManager+ChartGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ChartGetTopTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Gets the top tags for the last week. 13 | 14 | - Parameters: 15 | - limit: The maximum number of tags to return per page. 16 | 17 | - Returns: An array of ``SBKTag`` objects representing the top tags. 18 | 19 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 20 | 21 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/chart.getTopTags). 22 | */ 23 | func getTagsChart(page: Int = 1, limit: Int = 50) async throws -> [SBKTag] { 24 | let service = ChartGetTopTagsService(page: page, limit: limit, apiKey: apiKey, secretKey: secret) 25 | let response = try await service.start() 26 | guard let tags = response.toptags.tag else { 27 | return [] 28 | } 29 | return tags 30 | } 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Chart/GetTopTracks/SBKManager+ChartGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+ChartGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Gets a list of the top tracks on Last.fm ordered by popularity. 13 | 14 | - Parameters: 15 | - limit: The maximum number of tracks to return. Maximum value is 1000. 16 | - page: The page number to fetch. Defaults to first page (page 1). 17 | 18 | - Returns: An array of ``SBKTrack`` objects representing the top tracks. 19 | 20 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 21 | 22 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/chart.getTopTracks). 23 | */ 24 | func getTracksChart(limit: Int = 50, page: Int = 1) async throws -> [SBKTrack] { 25 | let service = ChartGetTopTracksService(limit: limit, page: page, apiKey: apiKey, secretKey: secret) 26 | let response = try await service.start() 27 | return response.tracks.track 28 | } 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Geo/GetTopArtists/SBKManager+GeoGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+GeoGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 23/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Fetches the top artists for a given country or metro area. 13 | 14 | - Parameters: 15 | - location: The location for which to fetch the top artists. 16 | - limit: The maximum number of results to return. Defaults to 50. 17 | - page: The page number of results to return. Defaults to 1. 18 | 19 | - Returns: An array of `SBKArtist` objects representing the top artists for the given location. 20 | 21 | - Throws: An error of type `SBKClientError` or `SBKError` if there is an issue with the API request or decoding the response. 22 | 23 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/geo.getTopArtists). 24 | */ 25 | func getTopArtists( 26 | forCountry country: SBKCountry, 27 | limit: Int = 50, 28 | page: Int = 1 29 | ) async throws -> [SBKArtist] { 30 | let service = GeoGetTopArtistsService( 31 | country: country, 32 | limit: limit, 33 | page: page, 34 | apiKey: apiKey, 35 | secretKey: secret 36 | ) 37 | let response = try await service.start() 38 | return response.topartists.artist 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Geo/GetTopTracks/SBKManager+GeoGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+GeoGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 24/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the most popular tracks on Last.fm by country. 13 | 14 | - Parameters: 15 | - country: A country name, as defined by the ISO 3166-1 country names standard. 16 | - limit: The number of results to fetch per page. Defaults to 50. 17 | - page: The page number to fetch. Defaults to 1. 18 | 19 | - Returns: An array of ``SBKTrack`` objects representing the top tracks for the specified country. 20 | 21 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 22 | 23 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/geo.getTopTracks). 24 | */ 25 | func getTopTracks( 26 | forCountry country: SBKCountry, 27 | limit: Int = 50, 28 | page: Int = 1 29 | ) async throws -> [SBKTrack] { 30 | let service = GeoGetTopTracksService( 31 | country: country, 32 | limit: limit, 33 | page: page, 34 | apiKey: apiKey, 35 | secretKey: secret 36 | ) 37 | let response = try await service.start() 38 | return response.tracks.track 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Library/SBKService+LibraryGetArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKService+LibraryGetArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 24/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Returns a list of all the artists in a user's library. 13 | 14 | - Parameters: 15 | - user: The Last.fm username to fetch the library of. 16 | 17 | - Returns: An array of ``SBKArtist`` objects representing the library. 18 | 19 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 20 | 21 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/library.getArtists). 22 | */ 23 | func getArtistsFromLibrary(from user: String, limit: Int = 50, page: Int = 1) async throws -> [SBKArtist] { 24 | let service = LibraryGetArtistsService(user: user, limit: limit, page: page, apiKey: apiKey, secret: secret) 25 | let response = try await service.start() 26 | return response.artists.artist 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Tag/SBKManager+TagGetInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TagGetInfo.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the metadata for a tag. 13 | 14 | - Parameters: 15 | - tag: The tag name. 16 | - language: The language to return the wiki in, expressed as an ISO 639 alpha-2 code. The default value is ``SBKLanguageCode.english`. 17 | 18 | - Returns: An `SBKTag` object containing the tag's metadata. 19 | 20 | - Throws: An error of type `SBKError` if the request fails. 21 | 22 | - Note: See [Last.fm's tag.getInfo documentation](https://www.last.fm/api/show/tag.getInfo) for more information. 23 | */ 24 | func getInfo(forTag tag: String, language: SBKLanguageCode = .english) async throws -> SBKTag { 25 | let service = TagGetInfoService(tag: tag, language: language, apiKey: apiKey, secretKey: secret) 26 | let response = try await service.start() 27 | return response.tag 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Tag/SBKManager+TagGetSimilar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TagGetSimilar.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | 9 | import Foundation 10 | 11 | public extension SBKManager { 12 | /** 13 | Search for tags similar to this one. Returns tags ranked by similarity, based on listening data. 14 | 15 | - Parameter tag: The tag name. 16 | 17 | - Returns: An array of `SBKTag` representing similar tags. 18 | 19 | - Throws: An error of type `SBKError` if the request fails. 20 | 21 | - Note: See [Last.fm's tag.getSimilar documentation](https://www.last.fm/api/show/tag.getSimilar) for more information. 22 | */ 23 | func getSimilarTags(to tag: String) async throws -> [SBKTag] { 24 | let service = TagGetSimilarService(tag: tag, apiKey: apiKey, secretKey: secret) 25 | let response = try await service.start() 26 | return response.similarTags 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Tag/SBKManager+TagGetTopAlbums.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TagGetTopAlbums.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | 9 | import Foundation 10 | 11 | public extension SBKManager { 12 | /** 13 | Get the top albums tagged by this tag, ordered by tag count. 14 | 15 | - Parameters: 16 | - tag: The tag name. 17 | - limit: The number of results to fetch per page. Defaults to 50. 18 | - page: The page number to fetch. Defaults to first page. 19 | 20 | - Returns: An array of `SBKAlbum` representing the top albums. 21 | 22 | - Throws: An error of type `SBKError` if the request fails. 23 | 24 | - Note: See [Last.fm's tag.getTopAlbums documentation](https://www.last.fm/api/show/tag.getTopAlbums) for more information. 25 | */ 26 | func getTopAlbums(forTag tag: String, limit: Int = 50, page: Int = 1) async throws -> [SBKAlbum] { 27 | let service = TagGetTopAlbumsService(tag: tag, limit: limit, page: page, apiKey: apiKey, secretKey: secret) 28 | let response = try await service.start() 29 | return response.albums 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Tag/SBKManager+TagGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TagGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the top artists tagged by this tag, ordered by tag count. 13 | 14 | - Parameters: 15 | - tag: The tag name. 16 | - limit: The number of results to fetch per page. Defaults to 50. 17 | - page: The page number to fetch. Defaults to first page. 18 | 19 | - Returns: An array of `SBKArtist` representing the top artists. 20 | 21 | - Throws: An error of type `SBKError` if the request fails. 22 | 23 | - Note: See [Last.fm's tag.getTopArtists documentation](https://www.last.fm/api/show/tag.getTopArtists) for more information. 24 | */ 25 | func getTopArtists(forTag tag: String, limit: Int = 50, page: Int = 1) async throws -> [SBKArtist] { 26 | let service = TagGetTopArtistsService(tag: tag, limit: limit, page: page, apiKey: apiKey, secretKey: secret) 27 | let response = try await service.start() 28 | return response.artists 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Tag/SBKManager+TagGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension SBKManager { 4 | /** 5 | Get the top global tags on Last.fm, sorted by popularity. 6 | 7 | - Returns: An array of `SBKTag` representing the top tags. 8 | 9 | - Throws: An error of type `SBKError` if the request fails. 10 | 11 | - Note: See [Last.fm's tag.getTopTags documentation](https://www.last.fm/api/show/tag.getTopTags) for more information. 12 | */ 13 | func getTopTags() async throws -> [SBKTag] { 14 | let service = TagGetTopTagsService(apiKey: apiKey, secretKey: secret) 15 | let response = try await service.start() 16 | return response.tags 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Tag/SBKService+TagGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagGetTopTagsService.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | 9 | import Foundation 10 | 11 | public extension SBKManager { 12 | /** 13 | Get the top tracks tagged by this tag, ordered by tag count. 14 | 15 | - Parameters: 16 | - tag: The tag name. 17 | - limit: The number of results to fetch per page. Defaults to 50. 18 | - page: The page number to fetch. Defaults to first page. 19 | 20 | - Returns: An array of `SBKTrack` representing the top tracks. 21 | 22 | - Throws: An error of type `SBKError` if the request fails. 23 | 24 | - Note: See [Last.fm's tag.getTopTracks documentation](https://www.last.fm/api/show/tag.getTopTracks) for more information. 25 | */ 26 | func getTopTracks(forTag tag: String, limit: Int = 50, page: Int = 1) async throws -> [SBKTrack] { 27 | let service = TagGetTopTracksService(tag: tag, limit: limit, page: page, apiKey: apiKey, secretKey: secret) 28 | let response = try await service.start() 29 | return response.tracks 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/AddTag/SBKManager+TrackAddTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackAddTags.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Tag a track using a list of user-supplied tags. 13 | 14 | - Parameters: 15 | - track: The name of the track. 16 | - artist: The name of the artist. 17 | - tags: A list of user-supplied tags to apply to this track. Accepts a maximum of 10 tags. 18 | 19 | - Returns: A boolean value indicating whether the operation was successful. 20 | 21 | - Note: See [Last.fm's track.addTags documentation](https://www.last.fm/api/show/track.addTags) for more information. 22 | */ 23 | @discardableResult 24 | func addTags(toTrack track: String, artist: String, tags: [String]) async throws -> Bool { 25 | guard let sessionKey = sessionKey else { throw SBKClientError.missingSessionKey } 26 | 27 | let service = AddTagsService( 28 | to: .track(track, artist: artist), 29 | tags: tags, 30 | apiKey: apiKey, 31 | secretKey: secret, 32 | sessionKey: sessionKey 33 | ) 34 | 35 | _ = try await service.start() 36 | return true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/GetCorrection/SBKManager+TrackCorrectionService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackCorrectionService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Retrieves the corrected artist and track names for a given track name and artist name. 13 | 14 | - Parameters: 15 | - trackName: The name of the track to correct. 16 | - artistName: The name of the artist to correct. 17 | 18 | - Returns: The corrected track and artist names as optional strings. 19 | 20 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 21 | 22 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.getCorrection). 23 | */ 24 | func getCorrectedTrackInfo( 25 | for trackName: String, 26 | by artistName: String 27 | ) async throws -> SBKTrack? { 28 | let service = TrackCorrectionService(trackName: trackName, artistName: artistName, apiKey: apiKey, secretKey: secret) 29 | let response = try await service.start() 30 | return response.corrections.correction.result 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/GetInfo/SBKManager+TrackGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackGetInfoService.swift.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Retrieves the metadata for a track on Last.fm. 13 | 14 | - Parameters: 15 | - track: The name of the track to retrieve metadata for. 16 | - artist: The name of the artist of the track. 17 | - username: (Optional) The username for the context of the request. Defaults to `nil`. 18 | - autoCorrect: (Optional) Whether to automatically correct misspelled artist/track names. Defaults to `false`. 19 | - languageCode: (Optional) The ISO 639-1 language code for the language to return the biography in. Defaults to `.english`. 20 | 21 | - Returns: A `SBKTrack` object representing the track metadata. 22 | 23 | - Throws: An error of type `SBKClientError` or `SBKError` if there is an issue with the API request or decoding the response. 24 | 25 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.getInfo). 26 | */ 27 | func getInfo( 28 | forTrack track: String, 29 | artist: String, 30 | username: String? = nil, 31 | autoCorrect: Bool = false, 32 | languageCode: SBKLanguageCode = .english 33 | ) async throws -> SBKTrack { 34 | let service = TrackGetInfoService(track: track, artist: artist, username: username, autoCorrect: autoCorrect, apiKey: apiKey, secretKey: secret) 35 | let response = try await service.start() 36 | let track = response.track 37 | return track 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/GetSimilar/SBKManager+TrackGetSimilarService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackGetSimilarService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Retrieves a list of tracks similar to the given track and artist. 13 | 14 | - Parameters: 15 | - track: The search method for the track. It can be either the track name and artist name, or a MusicBrainz ID. 16 | - autoCorrect: Transform misspelled track or artist names into correct names, returning the correct version instead. The default value is `true`. 17 | - limit: The maximum number of similar tracks to retrieve. Pass `nil` to use the default limit. 18 | 19 | - Returns: an array of ``SBKTrack`` objects representing the similar tracks. 20 | 21 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 22 | 23 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.getSimilar). 24 | */ 25 | func getSimilarTracks( 26 | _ track: SBKTrackSearchMethod, 27 | autoCorrect: Bool = true, 28 | limit: Int? = nil 29 | ) async throws -> [SBKSimilarTrack] { 30 | let service = TrackGetSimilarService( 31 | track: track, 32 | autoCorrect: autoCorrect, 33 | limit: limit, 34 | apiKey: apiKey, 35 | secretKey: secret 36 | ) 37 | let response = try await service.start() 38 | 39 | var tracks: [SBKSimilarTrack] = [] 40 | for similarTrack in response.similarTracks.tracks { 41 | tracks.append(.init(similarTrack: similarTrack)) 42 | } 43 | 44 | return tracks 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/GetTags/SBKmanager+TrackGetTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKmanager+TrackGetTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Retrieves a list of tags associated with a given track. 13 | 14 | - Parameters: 15 | - searchMethod: The search method for the track. It can be either the track name and artist name or a MusicBrainz ID. 16 | - autoCorrect: Transform misspelled track or artist names into correct names, returning the correct version instead. The default value is `true`. 17 | - username: The Last.fm username. If called in non-authenticated mode you must specify the user to look up 18 | 19 | - Returns: An array of `SBKTag` objects representing the tags associated with the track. 20 | 21 | - Throws: An error of type `SBKClientError` or `SBKError` if there is an issue with the API request or decoding the response. 22 | 23 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.getTags). 24 | */ 25 | func getTags( 26 | forTrack searchMethod: SBKTrackSearchMethod, 27 | autoCorrect: Bool = true, 28 | username: String? = nil 29 | ) async throws -> [SBKTag] { 30 | let service = TrackGetTagsService( 31 | searchMethod: searchMethod, 32 | autoCorrect: autoCorrect, 33 | username: username, 34 | apiKey: apiKey, 35 | secretKey: secret, 36 | sessionKey: sessionKey 37 | ) 38 | let response = try await service.start() 39 | guard let tags = response.tags.tag else { 40 | return [] 41 | } 42 | return tags 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/GetTopTags/SBKManager+TrackGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackGetTopTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Retrieves the top tags for a given track. 13 | 14 | - Parameters: 15 | - searchMethod: The search method for the track. It can be either the track name and artist name or a MusicBrainz ID. 16 | - autoCorrect: Transform misspelled track or artist names into correct names, returning the correct version instead. The default value is `true`. 17 | 18 | - Returns: An optional `SBKChartGetTopTagsResponse` object representing the top tags associated with the track. 19 | 20 | - Throws: An error of type `SBKClientError` or `SBKError` if there is an issue with the API request or decoding the response. 21 | 22 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.getTopTags). 23 | */ 24 | func getTopTagsForTrack( 25 | searchMethod: SBKTrackSearchMethod, 26 | autoCorrect: Bool = true 27 | ) async throws -> [SBKTag] { 28 | let service = TrackGetTopTagsService( 29 | searchMethod: searchMethod, 30 | autoCorrect: autoCorrect, 31 | apiKey: apiKey, 32 | secretKey: secret 33 | ) 34 | let response = try await service.start() 35 | guard let tags = response.toptags.tag else { 36 | return [] 37 | } 38 | return tags 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/Love/SBKManager+TrackLoveService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackLoveService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Marks a track as loved for the authenticated user. 13 | 14 | - Parameters: 15 | - track: The name of the track. 16 | - artist: The name of the artist. 17 | 18 | - Throws: An error of type `SBKClientError` or `SBKError` if there is an issue with the API request or decoding the response. 19 | 20 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.love). 21 | */ 22 | func loveTrack( 23 | track: String, 24 | artist: String 25 | ) async throws { 26 | guard let sessionKey else { 27 | throw SBKClientError.missingSessionKey 28 | } 29 | 30 | let service = TrackLoveService( 31 | track: track, 32 | artist: artist, 33 | apiKey: apiKey, 34 | secretKey: secret, 35 | sessionKey: sessionKey 36 | ) 37 | 38 | _ = try await service.start() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/RemoveTag/SBKManager+TrackRemoveTag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackRemoveTag.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Remove a user's tag from a track on Last.fm. 13 | 14 | - Parameters: 15 | - track: The name of the track. 16 | - artist: The name of the artist. 17 | - tag: The tag to remove from the track. 18 | 19 | - Returns: A boolean value indicating whether the tag was removed from the track. 20 | 21 | - Note: See [Last.fm's track.removeTag documentation](https://www.last.fm/api/show/track.removeTag) for more information. 22 | */ 23 | @discardableResult 24 | func removeTag(fromTrack track: String, artist: String, tag: String) async throws -> Bool { 25 | guard let sessionKey = sessionKey else { throw SBKClientError.missingSessionKey } 26 | 27 | let service = RemoveTagService( 28 | to: .track(track, artist: artist), 29 | tag: tag, 30 | apiKey: apiKey, 31 | secretKey: secret, 32 | sessionKey: sessionKey 33 | ) 34 | 35 | _ = try await service.start() 36 | return true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/SBKTrackSearchMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTrackSearchMethod.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 14/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the different methods available for searching a track on Last.fm. 11 | public enum SBKTrackSearchMethod: Sendable { 12 | /// Search for a track by its name and artist. 13 | /// 14 | /// - Parameters: 15 | /// - album: The name of the track to search for. 16 | /// - artist: The name of the artist who created the track. 17 | case trackInfo(_ title: String, artist: String) 18 | /// Search for an track by their MusicBrainz ID. 19 | /// 20 | /// - Parameter id: The MusicBrainz ID of the track. 21 | case musicBrainzID(_ id: String) 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/Scrobble/SBKManager+Scrobble.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /// Scrobbles multiple tracks to a user's Last.fm profile. 12 | /// 13 | /// - Parameter tracks: An array of `SBKTrackToScrobble` objects representing the tracks to be scrobbled. 14 | /// - Returns: An `SBKScrobbleResponse` object containing information about the scrobbled tracks. 15 | /// - Throws: An error if the scrobbling process fails, including `SBKClientError.noTracksToScrobble` if the tracks array is empty, 16 | /// `SBKClientError.tooManyTracks` if more than 50 tracks are provided, or `SBKClientError.missingSessionKey` if not authenticated. 17 | /// 18 | /// - Note: You can scrobble up to 50 tracks in a single request. 19 | /// - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.scrobble). 20 | func scrobble(tracks: [SBKTrackToScrobble]) async throws -> SBKScrobbleResponse { 21 | guard !tracks.isEmpty else { 22 | throw SBKClientError.noTracksToScrobble 23 | } 24 | 25 | if tracks.count > 50 { 26 | throw SBKClientError.tooManyTracks 27 | } 28 | 29 | guard let sessionKey else { throw SBKClientError.missingSessionKey } 30 | let service = ScrobbleService( 31 | tracks: tracks, 32 | sessionKey: sessionKey, 33 | apiKey: self.apiKey, 34 | secretKey: self.secret 35 | ) 36 | do { 37 | let responseList = try await service.start() 38 | return SBKScrobbleResponse(list: responseList) 39 | } catch { 40 | throw error 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/Search/SBKManager+TrackSearchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackSearchService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 15/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Search for tracks matching the given query. 13 | 14 | - Parameters: 15 | - track: The track name to search for. 16 | - artist: The artist name to search for. 17 | - limit: The maximum number of results to return (default: 30). 18 | - page: The page number of results to return (default: 0). 19 | 20 | - Returns: An array of ``SBKTrack`` objects representing the search results. 21 | 22 | - Throws: An error of type `SBKClientError` or `SBKError` if the operation fails. 23 | 24 | - Note: See [Last.fm's track.search documentation](https://www.last.fm/api/show/track.search) for more information. 25 | */ 26 | func search(track: String, artist: String? = nil, limit: Int = 30, page: Int = 0) async throws -> [SBKTrack] { 27 | let service = TrackSearchService( 28 | track: track, 29 | artist: artist, 30 | limit: limit, 31 | page: page, 32 | apiKey: apiKey, 33 | secretKey: secret 34 | ) 35 | 36 | let response = try await service.start() 37 | let tracks = response.results.matches.track ?? [] 38 | return tracks 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/Unlove/SBKManager+TrackUnloveService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackUnloveService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 15/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Unlove a track on Last.fm. 13 | 14 | - Parameters: 15 | - track: The name of the track. 16 | - artist: The name of the artist. 17 | 18 | - Throws: An error of type `SBKClientError` or `SBKError` if the operation fails. 19 | 20 | - Note: See [Last.fm's track.unlove documentation](https://www.last.fm/api/show/track.unlove) for more information. 21 | */ 22 | func unloveTrack(track: String, artist: String) async throws { 23 | guard let sessionKey else { throw SBKClientError.missingSessionKey } 24 | 25 | let service = TrackUnloveService( 26 | track: track, 27 | artist: artist, 28 | apiKey: apiKey, 29 | secretKey: secret, 30 | sessionKey: sessionKey 31 | ) 32 | 33 | _ = try await service.start() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/Track/UpdateNowPlaying/SBKManager+TrackUpdateNowPlayingService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+TrackUpdateNowPlayingService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 11/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Notifies Last.fm that a user has started listening to a track. 13 | 14 | - Parameters: 15 | - artist: The artist name. (Required) 16 | - track: The track name. (Required) 17 | - album: The album name. (Optional) 18 | - trackNumber: The track number of the track on the album. (Optional) 19 | - context: Sub-client version. (Optional) 20 | - mbid: The MusicBrainz Track ID. (Optional) 21 | - duration: The length of the track in seconds. (Optional) 22 | - albumArtist: The album artist, if different from the track artist. (Optional) 23 | 24 | - Throws: An error of type `SBKClientError` or `SBKError` if there is an issue with the API request or decoding the response. 25 | 26 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/track.updateNowPlaying). 27 | */ 28 | func updateNowPlaying( 29 | artist: String, 30 | track: String, 31 | album: String? = nil, 32 | trackNumber: Int? = nil, 33 | context: String? = nil, 34 | mbid: String? = nil, 35 | duration: Int? = nil, 36 | albumArtist: String? = nil 37 | ) async throws -> SBKNowPlayingCorrected { 38 | guard let sessionKey else { throw SBKClientError.missingSessionKey } 39 | 40 | let service = TrackUpdateNowPlayingService( 41 | artist: artist, 42 | track: track, 43 | album: album, 44 | trackNumber: trackNumber, 45 | context: context, 46 | mbid: mbid, 47 | duration: duration, 48 | albumArtist: albumArtist, 49 | apiKey: apiKey, 50 | secretKey: secret, 51 | sessionKey: sessionKey 52 | ) 53 | let response = try await service.start() 54 | return SBKNowPlayingCorrected(response: response) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetFriends/SBKManager+UserGetFriendsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+UserGetFriendsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 15/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get friends of a user on Last.fm. 13 | 14 | - Parameters: 15 | - user: The username of the user. 16 | - recentTracks: Whether to include information about the user's recent tracks. Default value is `false`. 17 | - limit: The maximum number of friends to retrieve. Default value is 50. 18 | - page: The page number of the results to retrieve. Default value is 1. 19 | 20 | - Returns: An asynchronous task that returns an array of ``SBKUser``. 21 | 22 | - Throws: An error of type ``SBKError`` if the operation fails. 23 | 24 | - Note: See [Last.fm's user.getFriends documentation](https://www.last.fm/api/show/user.getFriends) for more information. 25 | */ 26 | func getFriends(for user: String, recentTracks: Bool = false, limit: Int = 50, page: Int = 1) async throws -> [SBKUser] { 27 | let service = UserGetFriendsService( 28 | user: user, 29 | recentTracks: recentTracks, 30 | limit: limit, 31 | page: page, 32 | apiKey: apiKey, 33 | secretKey: secret 34 | ) 35 | 36 | let response = try await service.start() 37 | let responseList = response.friends.friends 38 | return responseList.map { SBKUser(from: $0)} 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetInfo/SBKManager+UserGetInfoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+UserGetInfoService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 25/04/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Retrieves information about a Last.fm user. 13 | 14 | - Parameters: 15 | - username: The username of the user to retrieve information for. 16 | 17 | - Returns: A ``SBKUser`` object representing the user information. 18 | 19 | - Throws: An error of type ``SBKClientError`` or ``SBKError`` if there is an issue with the API request or decoding the response. 20 | 21 | - Note: For more information, see the [Last.fm API documentation](https://www.last.fm/api/show/user.getInfo). 22 | */ 23 | func getInfo(forUser username: String? = nil) async throws -> SBKUser { 24 | let service = UserGetInfoService(username: username, apiKey: apiKey, secretKey: secret, sessionKey: sessionKey) 25 | let response = try await service.start() 26 | let user = SBKUser(from: response.user) 27 | return user 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetLovedTracks/SBKManager+UserGetLovedTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+UserGetLovedTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the loved tracks of a user on Last.fm. 13 | 14 | - Parameters: 15 | - user: The username of the user. 16 | - limit: The maximum number of loved tracks to retrieve. Default value is 50. 17 | - page: The page number of the results to retrieve. Default value is 1. 18 | 19 | - Returns: An asynchronous task that returns an instance of ``SBKLovedTracks`` 20 | 21 | - Throws: An error of type `SBKError` if the operation fails. 22 | 23 | - Note: See [Last.fm's user.getLovedTracks documentation](https://www.last.fm/api/show/user.getLovedTracks) for more information. 24 | */ 25 | @discardableResult 26 | func getLovedTracks(fromUser user: String, limit: Int = 50, page: Int = 1) async throws -> SBKLovedTracks { 27 | let service = UserGetLovedTracksService( 28 | user: user, 29 | limit: limit, 30 | page: page, 31 | apiKey: apiKey, 32 | secretKey: secret 33 | ) 34 | 35 | let response = try await service.start() 36 | return SBKLovedTracks(from: response) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetRecentTracks/SBKManager+UserGetRecentTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+GetRecentTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the recent tracks of a user on Last.fm. 13 | 14 | - Parameters: 15 | - user: The username of the user. 16 | - limit: The number of results to fetch per page. Default value is 50. 17 | - page: The page number to fetch. Default value is 1. 18 | - startDate: The start date of the range for which to retrieve tracks. Default value is nil. 19 | - extended: Includes extended data in each artist, and whether or not the user has loved each track. Default value is false. 20 | - endDate: The end date of the range for which to retrieve tracks. Default value is nil. 21 | 22 | - Returns: An asynchronous task that returns an ``SBKSearchResult`` object containing the recent tracks. 23 | 24 | - Throws: An error of type ``SBKError`` if the operation fails. 25 | 26 | - Note: See [Last.fm's user.getRecentTracks documentation](https://www.last.fm/api/show/user.getRecentTracks) for more information. 27 | */ 28 | func getRecentTracks( 29 | fromUser user: String, 30 | limit: Int = 50, 31 | page: Int = 1, 32 | startDate: Date? = nil, 33 | extended: Bool = false, 34 | endDate: Date? = nil 35 | ) async throws -> SBKSearchResult { 36 | let service = UserGetRecentTracksService( 37 | user: user, 38 | limit: limit, 39 | page: page, 40 | from: startDate, 41 | extended: extended, 42 | to: endDate, 43 | apiKey: apiKey, 44 | secretKey: secret 45 | ) 46 | 47 | let response = try await service.start() 48 | let attributes = response.recenttracks.attributes 49 | guard let page = Int(attributes.page), 50 | let perPage = Int(attributes.perPage), 51 | let totalPages = Int(attributes.totalPages), 52 | let total = Int(attributes.total) else { 53 | throw SBKClientError.failedToDecodeResponse 54 | } 55 | 56 | return .init(results: response.recenttracks.tracks, 57 | searchTerms: [.user: attributes.user], 58 | page: page, 59 | perPage: perPage, 60 | totalPages: totalPages, 61 | total: total) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetTopAlbums/SBKManager+UserGetTopAlbumsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+GetTopAlbumsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the top albums of a user on Last.fm. 13 | 14 | - Parameters: 15 | - user: The username of the user. 16 | - period: The time period over which to retrieve top albums for. Possible values: overall, 7day, 1month, 3month, 6month, 12month. Default value is ``SBKSearchPeriod/overall``. 17 | - limit: The number of results to fetch per page. Default value is 50. 18 | - page: The page number to fetch. Default value is 1. 19 | 20 | - Returns: An asynchronous task that returns an ``SBKSearchResult`` object containing the top albums. 21 | 22 | - Throws: An error of type ``SBKError`` if the operation fails. 23 | 24 | - Note: See [Last.fm's user.getTopAlbums documentation](https://www.last.fm/api/show/user.getTopAlbums) for more information. 25 | */ 26 | func getTopAlbums(forUser user: String, period: SBKSearchPeriod = .overall, limit: Int = 50, page: Int = 1) async throws -> SBKSearchResult { 27 | let service = UserGetTopAlbumsService( 28 | user: user, 29 | period: period.rawValue, 30 | limit: limit, 31 | page: page, 32 | apiKey: apiKey, 33 | secretKey: secret 34 | ) 35 | 36 | let response = try await service.start() 37 | let attributes = response.topAlbums.attributes 38 | guard let page = Int(attributes.page), 39 | let perPage = Int(attributes.perPage), 40 | let totalPages = Int(attributes.totalPages), 41 | let total = Int(attributes.total) else { 42 | throw SBKClientError.failedToDecodeResponse 43 | } 44 | 45 | return .init(results: response.topAlbums.albums, 46 | searchTerms: [.user: attributes.user], 47 | page: page, 48 | perPage: perPage, 49 | totalPages: totalPages, 50 | total: total) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetTopArtists/SBKManager+UserGetTopArtistsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+UserGetTopArtistsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the top artists of a user on Last.fm. 13 | 14 | - Parameters: 15 | - user: The username of the user. 16 | - period: The time period over which to retrieve top artists for. Possible values: overall, 7day, 1month, 3month, 6month, 12month. Default value is `overall`. 17 | - limit: The number of results to fetch per page. Default value is 50. 18 | - page: The page number to fetch. Default value is 1. 19 | 20 | - Returns: An asynchronous task that returns a ``SBKSearchResult`` object containing a list of ``SBKArtist`` results. 21 | 22 | - Throws: An error of type ``SBKError`` if the operation fails. 23 | 24 | - Note: See [Last.fm's user.getTopArtists documentation](https://www.last.fm/api/show/user.getTopArtists) for more information. 25 | */ 26 | func getTopArtists(forUser user: String, period: SBKSearchPeriod = .overall, limit: Int = 50, page: Int = 1) async throws -> SBKSearchResult { 27 | let service = UserGetTopArtistsService( 28 | user: user, 29 | period: period.rawValue, 30 | limit: limit, 31 | page: page, 32 | apiKey: apiKey, 33 | secretKey: secret 34 | ) 35 | 36 | let response = try await service.start() 37 | let attributes = response.topArtists.attributes 38 | guard let page = Int(attributes.page), 39 | let perPage = Int(attributes.perPage), 40 | let totalPages = Int(attributes.totalPages), 41 | let total = Int(attributes.total) else { 42 | throw SBKClientError.failedToDecodeResponse 43 | } 44 | 45 | return .init(results: response.topArtists.artists, 46 | searchTerms: [.user: attributes.user], 47 | page: page, 48 | perPage: perPage, 49 | totalPages: totalPages, 50 | total: total) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetTopTags/SBKManager+UserGetTopTagsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+UserGetTopTagsService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the top tags of a user on Last.fm asynchronously. 13 | 14 | - Parameters: 15 | - user: The username of the user. 16 | - limit: Limit the number of tags returned. Default value is `nil`. 17 | 18 | - Returns: An asynchronous task that returns an ``SBKSearchResult`` object containing an array of ``SBKTag`` objects. 19 | 20 | - Throws: An error of type ``SBKError`` if the operation fails. 21 | 22 | - Note: See [Last.fm's user.getTopTags documentation](https://www.last.fm/api/show/user.getTopTags) for more information. 23 | */ 24 | func getTopTags(forUser user: String, limit: Int? = nil) async throws -> SBKSearchResult { 25 | let service = UserGetTopTagsService( 26 | user: user, 27 | limit: limit, 28 | apiKey: apiKey, 29 | secretKey: secret 30 | ) 31 | 32 | let response = try await service.start() 33 | let attributes = response.topTags.attributes 34 | 35 | return .init(results: response.topTags.tags, 36 | searchTerms: [.user: attributes.user], 37 | page: 1, 38 | perPage: limit ?? response.topTags.tags.count, 39 | totalPages: 1, 40 | total: response.topTags.tags.count) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Methods/User/GetTopTracks/SBKManager+UserGetTopTracksService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKManager+UserGetTopTracksService.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension SBKManager { 11 | /** 12 | Get the top tracks of a user on Last.fm asynchronously. 13 | 14 | - Parameters: 15 | - user: The username of the user. 16 | - period: The time period over which to retrieve top tracks for. Possible values: overall, 7day, 1month, 3month, 6month, 12month. Default value is `overall`. 17 | - limit: The number of results to fetch per page. Default value is `50`. 18 | - page: The page number to fetch. Default value is `1`. 19 | 20 | - Returns: An asynchronous task that returns a ``SBKSearchResult`` object with a list of ``SBKTrack`` 21 | 22 | - Throws: An error of type ``SBKError`` if the operation fails. 23 | 24 | - Note: See [Last.fm's user.getTopTracks documentation](https://www.last.fm/api/show/user.getTopTracks) for more information. 25 | */ 26 | func getTopTracks(forUser user: String, period: SBKSearchPeriod = .overall, limit: Int = 50, page: Int = 1) async throws -> SBKSearchResult { 27 | let service = UserGetTopTracksService( 28 | user: user, 29 | period: period.rawValue, 30 | limit: limit, 31 | page: page, 32 | apiKey: apiKey, 33 | secretKey: secret 34 | ) 35 | 36 | let response = try await service.start() 37 | let attributes = response.topTracks.attributes 38 | guard let page = Int(attributes.page), 39 | let perPage = Int(attributes.perPage), 40 | let totalPages = Int(attributes.totalPages), 41 | let total = Int(attributes.total) else { 42 | throw SBKClientError.failedToDecodeResponse 43 | } 44 | 45 | return .init(results: response.topTracks.tracks, 46 | searchTerms: [.user: attributes.user], 47 | page: page, 48 | perPage: perPage, 49 | totalPages: totalPages, 50 | total: total) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKAlbumSearchMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKAlbumSearchMethod.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the different methods available for searching an album on Last.fm. 11 | public enum SBKAlbumSearchMethod: Sendable { 12 | /// Search for an album by its name and artist. 13 | /// 14 | /// - Parameters: 15 | /// - album: The name of the album to search for. 16 | /// - artist: The name of the artist who created the album. 17 | case albumArtist(album: String, artist: String) 18 | 19 | /// Search for an album by its MusicBrainz ID. 20 | /// 21 | /// - Parameter id: The MusicBrainz ID of the album. 22 | case musicBrainzID(id: String) 23 | } 24 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKArtistSearchMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKArtistSearchMethod.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 22/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the different methods available for searching an artist on Last.fm. 11 | public enum SBKArtistSearchMethod: Sendable { 12 | /// Search for an artist by their name. 13 | /// 14 | /// - Parameter artist: The name of the artist to search for. 15 | case artistName(_ artist: String) 16 | 17 | /// Search for an artist by their MusicBrainz ID. 18 | /// 19 | /// - Parameter id: The MusicBrainz ID of the artist. 20 | case musicBrainzID(_ id: String) 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKImage.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | Represents an image that may be returned from a Last.fm API call. 12 | */ 13 | public struct SBKImage: Sendable { 14 | /// The URL for the small-sized image. 15 | public var small: URL? 16 | 17 | /// The URL for the medium-sized image. 18 | public var medium: URL? 19 | 20 | /// The URL for the large-sized image. 21 | public var large: URL? 22 | 23 | /// The URL for the extra-large-sized image. 24 | public var extraLarge: URL? 25 | 26 | /// The URL for the mega-sized image. 27 | public var mega: URL? 28 | 29 | /// Returns the URL of the largest available size among small, medium, large, extra-large, and mega. 30 | /// 31 | /// The `largestSize` property prioritizes larger sizes in descending order, returning the first non-nil URL found. 32 | /// 33 | /// - Returns: The URL of the largest available size, or `nil` if no size is available. 34 | public var largestSize: URL? { 35 | let sizes = [mega, extraLarge, large, medium, small] 36 | let largestImage = sizes.first { $0 != nil } 37 | guard let url = largestImage else { return nil } 38 | return url 39 | } 40 | 41 | internal init?(response: [SBKImageResponse]?) { 42 | guard let response else { return nil } 43 | let smallString = response.first(where: { $0.size == "small" })?.text 44 | self.small = URL(optionalString: smallString) 45 | let mediumString = response.first(where: { $0.size == "medium" })?.text 46 | self.medium = URL(optionalString: mediumString) 47 | let largeString = response.first(where: { $0.size == "large" })?.text 48 | self.large = URL(optionalString: largeString) 49 | let extraLargeString = response.first(where: { $0.size == "extralarge" })?.text 50 | self.extraLarge = URL(optionalString: extraLargeString) 51 | let megaString = response.first(where: { $0.size == "mega" })?.text 52 | self.mega = URL(optionalString: megaString) 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKLovedTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKLovedTrack.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 02/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents information about a track on an user's Loved Tracks list. 11 | public struct SBKLovedTrack: Sendable { 12 | /// The track loved by the user. 13 | public let track: SBKTrack 14 | /// The date the song has been added to the Loved Tracks list. 15 | public let date: Date? 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKLovedTracks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKLovedTracks.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents information about an user's Loved Tracks list. 11 | public struct SBKLovedTracks: Sendable { 12 | /// The attributes of the current query 13 | public let searchAttributes: SBKSearchAttributes 14 | /// The list of tracks in the list. 15 | public let tracks: [SBKLovedTrack] 16 | 17 | internal init(from response: _UserGetLovedTracksResponse) { 18 | let responseAttributes = response.lovedtracks.attributes 19 | self.searchAttributes = SBKSearchAttributes( 20 | term: responseAttributes.user, 21 | totalPages: responseAttributes.total, 22 | page: responseAttributes.page, 23 | perPage: responseAttributes.perPage, 24 | total: responseAttributes.total 25 | ) 26 | 27 | let responseTrackList = response.lovedtracks.track 28 | self.tracks = responseTrackList.map { 29 | let track = SBKTrack( 30 | name: $0.name, 31 | mbid: $0.mbid, 32 | playcount: nil, 33 | listeners: nil, 34 | duration: nil, 35 | artist: $0.artist, 36 | url: URL(string: $0.url), 37 | imageList: $0.image 38 | ) 39 | 40 | var date: Date? = nil 41 | 42 | if let timeInterval = TimeInterval($0.date.timestamp) { 43 | date = Date(timeIntervalSince1970: timeInterval) 44 | } 45 | 46 | return SBKLovedTrack(track: track, date: date) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKNowPlayingCorrected.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKNowPlayingCorrected.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 11/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | A struct representing the corrected information after a track update now playing request. 12 | */ 13 | public struct SBKNowPlayingCorrected: Sendable { 14 | /** 15 | Enum representing the types of corrected information. 16 | */ 17 | public enum SBKNowPlayingInfo: Sendable { 18 | case track 19 | case artist 20 | case album 21 | case albumArtist 22 | } 23 | 24 | /// Dictionary containing the corrected information. 25 | public let correctedInformation: [SBKNowPlayingInfo: String] 26 | 27 | /** 28 | Initializes a `SBKNowPlayingCorrected` instance with the provided response. 29 | 30 | - Parameter response: The response containing the corrected information. 31 | */ 32 | init(response: SBKTrackUpdateNowPlayingResponse) { 33 | var corrected: [SBKNowPlayingInfo: String] = [:] 34 | 35 | if response.nowPlaying.track.corrected == "1" { 36 | corrected[.track] = response.nowPlaying.track.text 37 | } 38 | 39 | if response.nowPlaying.artist.corrected == "1" { 40 | corrected[.artist] = response.nowPlaying.artist.text 41 | } 42 | 43 | if response.nowPlaying.album.corrected == "1" { 44 | corrected[.album] = response.nowPlaying.album.text 45 | } 46 | 47 | if response.nowPlaying.albumArtist.corrected == "1" { 48 | corrected[.albumArtist] = response.nowPlaying.albumArtist.text 49 | } 50 | 51 | self.correctedInformation = corrected 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKScrobbleResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKScrobbleResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the response from a scrobble request to Last.fm. 11 | public struct SBKScrobbleResponse: Sendable { 12 | /// The results for each track that was attempted to be scrobbled. 13 | public let results: [SBKScrobbleResult] 14 | /// The number of tracks that were successfully scrobbled. 15 | public let acceptedCount: Int 16 | /// The number of tracks that were ignored (failed to scrobble). 17 | public let ignoredCount: Int 18 | 19 | /// Indicates whether all tracks were successfully scrobbled. 20 | public var isCompletelySuccessful: Bool { 21 | return ignoredCount == 0 22 | } 23 | 24 | /// A list of tracks that were successfully scrobbled. 25 | public var acceptedScrobbles: [SBKScrobbleResult] { 26 | return results.filter { $0.isAccepted } 27 | } 28 | 29 | /// A list of tracks that failed to scrobble. 30 | public var ignoredScrobbles: [SBKScrobbleResult] { 31 | return results.filter { !$0.isAccepted } 32 | } 33 | 34 | internal init(list: SBKScrobbleList) { 35 | self.results = list.scrobbles.scrobbles.enumerated().map { (index, sbkScrobble) in 36 | let track = SBKTrackToScrobble( 37 | artist: sbkScrobble.artist.text ?? "", 38 | track: sbkScrobble.track.text ?? "", 39 | timestamp: Date(timeIntervalSince1970: TimeInterval(sbkScrobble.timestamp) ?? 0), 40 | album: sbkScrobble.album.text 41 | ) 42 | 43 | let isAccepted = sbkScrobble.ignoredMessage.code == "0" 44 | let error = isAccepted ? nil : SBKScrobbleError(code: sbkScrobble.ignoredMessage.code) 45 | 46 | return SBKScrobbleResult( 47 | track: track, 48 | isAccepted: isAccepted, 49 | error: error, 50 | correctedTrack: sbkScrobble.track.corrected == "1" ? sbkScrobble.track.text : nil, 51 | correctedArtist: sbkScrobble.artist.corrected == "1" ? sbkScrobble.artist.text : nil, 52 | correctedAlbum: sbkScrobble.album.corrected == "1" ? sbkScrobble.album.text : nil, 53 | correctedAlbumArtist: sbkScrobble.albumArtist.corrected == "1" ? sbkScrobble.albumArtist.text : nil 54 | ) 55 | } 56 | 57 | self.acceptedCount = list.scrobbles.attr.accepted 58 | self.ignoredCount = list.scrobbles.attr.ignored 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKScrobbleResponseAttribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKScrobbleResponseAttribute.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the fields of metadata of a song that may be corrected when attempting to scrobble it. 11 | public enum SBKScrobbleResponseAttribute { 12 | /// The artist associated with the song 13 | case artist 14 | /// The title of the song 15 | case title 16 | /// The album associated with the song 17 | case album 18 | /// The artist of the album associated with the song 19 | case albumArtist 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKScrobbleResponseTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKScrobbleResult.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the result of a scrobble attempt for a single track. 11 | public struct SBKScrobbleResult: Sendable, Equatable { 12 | /// The track that was attempted to be scrobbled. 13 | public let track: SBKTrackToScrobble 14 | /// Indicates whether the scrobble was accepted by Last.fm. 15 | public let isAccepted: Bool 16 | /// The error that occurred during scrobbling, if any. 17 | public let error: SBKScrobbleError? 18 | /// The corrected track name, if Last.fm made a correction. 19 | public let correctedTrack: String? 20 | /// The corrected artist name, if Last.fm made a correction. 21 | public let correctedArtist: String? 22 | /// The corrected album name, if Last.fm made a correction. 23 | public let correctedAlbum: String? 24 | /// The corrected album artist name, if Last.fm made a correction. 25 | public let correctedAlbumArtist: String? 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKScrobbleResponseTrackStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKScrobbleResponseTrackStatus.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the scrobble status of a song, indicating whether it has been successfully scrobbled or ignored. 11 | public enum SBKScrobbleResponseTrackStatus { 12 | /// The song has been scrobbled successfully. 13 | case accepted 14 | /// The song has been ignored by the scrobble service. 15 | /// - Parameter message: A message describing the cause for ignoring the song, if available. 16 | case ignored(message: String?) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKScrobbledTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKScrobbledTrack.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 26/08/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct SBKScrobbledTrack: Decodable, Sendable { 11 | public let track: SBKTrack 12 | public let date: Date? 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case name 16 | case artist 17 | case mbid 18 | case url 19 | case date 20 | } 21 | 22 | public init(from decoder: Decoder) throws { 23 | let container: KeyedDecodingContainer = try decoder.container(keyedBy: SBKScrobbledTrack.CodingKeys.self) 24 | 25 | let name = try container.decode(String.self, forKey: .name) 26 | let artist = try container.decode(SBKArtist.self, forKey: .artist) 27 | let url = try container.decode(String.self, forKey: .url) 28 | let mbid = try container.decodeIfPresent(String.self, forKey: .mbid) 29 | self.track = .init( 30 | name: name, 31 | mbid: mbid, 32 | artist: artist, 33 | url: URL(string: url) 34 | ) 35 | let timestamp = try container.decode(SBKTimestamp.self, forKey: .date) 36 | if let timeInterval = TimeInterval(timestamp.timestamp) { 37 | self.date = Date(timeIntervalSince1970: timeInterval) 38 | } else { 39 | self.date = nil 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKSearchAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSearchAttributes.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents search attributes used in Last.fm search queries 11 | public struct SBKSearchAttributes: Sendable { 12 | /// The search term used in the query. 13 | public let term: String 14 | /// The total number of pages available. 15 | public let totalPages: String 16 | /// The current page number. 17 | public let page: String 18 | /// The number of results per page. 19 | public let perPage: String 20 | /// The total number of results found. 21 | public let total: String 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKSearchPeriod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSearchPeriod.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 20/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the time periods available for Last.fm chart and statistic queries. 11 | public enum SBKSearchPeriod: String, Sendable { 12 | /// Represents all-time statistics. 13 | case overall 14 | 15 | /// Represents statistics for the last 7 days. 16 | case sevenDays = "7day" 17 | 18 | /// Represents statistics for the last month. 19 | case oneMonth = "1month" 20 | 21 | /// Represents statistics for the last 3 months. 22 | case threeMonths = "3month" 23 | 24 | /// Represents statistics for the last 6 months. 25 | case sixMonths = "6month" 26 | 27 | /// Represents statistics for the last 12 months. 28 | case twelveMonths = "12month" 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKSearchResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSearchResult.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 02/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the result of a Last.fm search. 11 | public struct SBKSearchResult: Sendable { 12 | /// The list of results retrieved from the search. 13 | public var results: [Model] 14 | /// The dictionary containing search terms and their corresponding values. 15 | public var searchTerms: [SBKSearchTerm: String] 16 | /// The current page number. 17 | public var page: Int 18 | /// The number of results per page. 19 | public var perPage: Int 20 | /// The total number of pages available. 21 | public var totalPages: Int 22 | /// The total number of results found. 23 | public var total: Int 24 | } 25 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKSearchTerm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSearchTerm.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 02/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the search terms available for Last.fm searches. 11 | public enum SBKSearchTerm: Sendable { 12 | case artist 13 | case user 14 | case tag 15 | case album 16 | case track 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKSessionResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSessionResponse.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SBKSessionResponse: Sendable, Decodable { 11 | let info: SBKSessionResponseInfo 12 | 13 | enum CodingKeys: String, CodingKey { 14 | case info = "session" 15 | } 16 | } 17 | 18 | /// Represents the information retrieved after a successful authentication with Last.fm services. 19 | public final class SBKSessionResponseInfo: Sendable, Decodable { 20 | /// The username associated with the authenticated account. 21 | public let name: String 22 | /// The session token generated for the current user session. 23 | public let key: String 24 | /// Indicates whether the user is subscribed to Last.fm Pro. 25 | public let isPro: Bool 26 | 27 | enum CodingKeys: CodingKey { 28 | case name 29 | case key 30 | case subscriber 31 | } 32 | 33 | public required init(from decoder: Decoder) throws { 34 | let container: KeyedDecodingContainer = try decoder.container(keyedBy: SBKSessionResponseInfo.CodingKeys.self) 35 | 36 | self.name = try container.decode(String.self, forKey: SBKSessionResponseInfo.CodingKeys.name) 37 | self.key = try container.decode(String.self, forKey: SBKSessionResponseInfo.CodingKeys.key) 38 | let subscriber = try container.decode(Int.self, forKey: SBKSessionResponseInfo.CodingKeys.subscriber) 39 | self.isPro = Bool(truncating: subscriber as NSNumber) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKSimilarArtist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSimilarArtist.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 16/06/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents an artist that is similar to the one returned by the Last.fm API. 11 | public struct SBKSimilarArtist: Decodable, Sendable { 12 | internal var name: String 13 | internal var musicBrainzID: String? 14 | internal var url: String 15 | internal var matchString: String 16 | internal var imageResponse: [SBKImageResponse]? 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case name 20 | case musicBrainzID = "mbid" 21 | case matchString = "match" 22 | case url 23 | case imageResponse = "image" 24 | } 25 | 26 | /// The artist that this similar artist represents 27 | public var artist: SBKArtist { 28 | return SBKArtist(name: name, 29 | musicBrainzID: musicBrainzID, 30 | url: url, 31 | image: imageResponse) 32 | } 33 | 34 | /// The match value for the similarity between the original artist and the similar artist, as a percentage (from 0.0 to 1.0). 35 | public var match: Double? { 36 | return Double(matchString) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKSimilarTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKSimilarTrack.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 29/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents a track that is similar to another track, as determined by Last.fm. 11 | public struct SBKSimilarTrack: Sendable { 12 | /// The track that this similar track represents. 13 | public var track: SBKTrack 14 | 15 | /// The match value for the similarity between the original track and this similar track. 16 | /// 17 | /// This value is a percentage represented as a decimal between 0.0 and 100.0 18 | /// A higher value indicates a stronger similarity. 19 | public var match: Double? 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKTrackToScrobble.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKTrackToScrobble.swift 3 | // ScrobbleKit 4 | // 5 | // Created by Tomas Martins on 04/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents a track to be scrobbled to Last.fm. 11 | public struct SBKTrackToScrobble: Sendable { 12 | /// The artist name. 13 | public let artist: String 14 | /// The track name. 15 | public let track: String 16 | /// The time the track started playing, in UTC. 17 | public let timestamp: Date 18 | /// The album name (optional). 19 | public let album: String? 20 | /// The album artist - if this differs from the track artist (optional). 21 | public let albumArtist: String? 22 | /// The track number of the track on the album (optional). 23 | public let trackNumber: Int? 24 | /// The length of the track in seconds (optional). 25 | public let duration: Int? 26 | /// Whether the user chose this song (optional). If not specified, assumes the user chose the song. 27 | public let chosenByUser: Bool? 28 | /// The MusicBrainz Track ID (optional). 29 | public let mbid: String? 30 | 31 | /// Initializes a new track to be scrobbled. 32 | /// - Parameters: 33 | /// - artist: The artist name. 34 | /// - track: The track name. 35 | /// - timestamp: The time the track started playing, in UTC. 36 | /// - album: The album name (optional). 37 | /// - albumArtist: The album artist, if different from the track artist (optional). 38 | /// - trackNumber: The track number on the album (optional). 39 | /// - duration: The length of the track in seconds (optional). 40 | /// - chosenByUser: Whether the user chose this song (optional). 41 | /// - mbid: The MusicBrainz Track ID (optional). 42 | public init( 43 | artist: String, 44 | track: String, 45 | timestamp: Date = Date(), 46 | album: String? = nil, 47 | albumArtist: String? = nil, 48 | trackNumber: Int? = nil, 49 | duration: Int? = nil, 50 | chosenByUser: Bool? = nil, 51 | mbid: String? = nil 52 | ) { 53 | self.artist = artist 54 | self.track = track 55 | self.timestamp = timestamp 56 | self.album = album 57 | self.albumArtist = albumArtist 58 | self.trackNumber = trackNumber 59 | self.duration = duration 60 | self.chosenByUser = chosenByUser 61 | self.mbid = mbid 62 | } 63 | } 64 | 65 | extension SBKTrackToScrobble: Equatable, Hashable { 66 | public static func == (lhs: SBKTrackToScrobble, rhs: SBKTrackToScrobble) -> Bool { 67 | return lhs.artist == rhs.artist && 68 | lhs.track == rhs.track && 69 | lhs.timestamp == rhs.timestamp 70 | } 71 | 72 | public func hash(into hasher: inout Hasher) { 73 | hasher.combine(artist) 74 | hasher.combine(track) 75 | hasher.combine(timestamp) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/Public/Models/SBKWiki.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBKWiki.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 19/02/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | A model representing a wiki entry. 12 | */ 13 | public struct SBKWiki: Sendable, Decodable { 14 | 15 | /// The summary of the wiki entry. 16 | public let summary: String 17 | 18 | /// The content of the wiki entry. 19 | public let content: String 20 | 21 | /// The date and time the wiki entry was published, or `nil` if the date couldn't be parsed. 22 | public var published: Date? { 23 | guard let publishedString else { return nil } 24 | let dateFormatter = DateFormatter() 25 | dateFormatter.dateFormat = "dd MMM yyyy, HH:mm" 26 | return dateFormatter.date(from: publishedString) 27 | } 28 | 29 | private let publishedString: String? 30 | 31 | enum CodingKeys: String, CodingKey { 32 | case summary 33 | case content 34 | case publishedString = "published" 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Sources/ScrobbleKit/ScrobbleKit.docc/Authentication.md: -------------------------------------------------------------------------------- 1 | # Authenticating an User 2 | 3 | For services requiring authentication, discover how to sign in with a Last.fm account using ScrobbleKit. 4 | 5 | ## Overview 6 | 7 | Certain ScrobbleKit services, such as scrobbling a song to a Last.fm profile, necessitate authentication from a valid Last.fm account. To perform these actions, a valid token generated by Last.fm is required, and ScrobbleKit can assist in generating one. 8 | 9 | ### Generating a Session Token 10 | 11 | Use the ``SBKManager/startSession(username:password:)-76puy`` method to create a new token by providing the Last.fm account's username and password. 12 | 13 | - Important: Last.fm's API does not support authentication via email; you must provide the account's username under the `username` parameter. 14 | 15 | ```swift 16 | let manager = SBKManager(apiKey: API_KEY, secret: SECRET_KEY) 17 | 18 | do { 19 | try await manager.startSession(username: USERNAME, password: PASSWORD) 20 | } catch { 21 | // Handle sign-in error 22 | } 23 | ``` 24 | 25 | Upon successful authentication, the ``SBKManager`` instance will automatically set the generated token for subsequent requests. However, it is recommended that your application securely stores the generated token, allowing it to access authenticated services without requiring users to repeatedly authenticate with their Last.fm account. 26 | 27 | Retrieve the session token from the ``SBKSessionResponseInfo`` instance returned by the ``SBKManager/startSession(username:password:)-76puy`` method, using the ``SBKSessionResponseInfo/key`` property: 28 | 29 | ```swift 30 | do { 31 | let response = try await manager.startSession(username: USERNAME, secret: PASSWORD) 32 | let token = response.key 33 | } catch { 34 | // Handle error 35 | } 36 | ``` 37 | 38 | ### Using an Existing Session Token 39 | 40 | If you already have a valid session token, provide it to the ``SBKManager/setSessionKey(_:)`` method: 41 | 42 | ```swift 43 | manager.setSessionKey(SESSION_KEY) 44 | ``` 45 | 46 | This action also saves the session token to the manager instance for future use. 47 | -------------------------------------------------------------------------------- /Tests/ScrobbleKitTests/ScrobbleKitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrobbleKitTests.swift 3 | // 4 | // 5 | // Created by Tomas Martins on 28/12/23. 6 | // 7 | 8 | import XCTest 9 | @testable import ScrobbleKit 10 | 11 | final class ScrobbleKitTests: XCTestCase { 12 | var manager: SBKManager! 13 | 14 | override func setUp() { 15 | super.setUp() 16 | manager = SBKManager(apiKey: "test_api_key", secret: "test_secret_key") 17 | } 18 | 19 | override func tearDown() { 20 | manager = nil 21 | super.tearDown() 22 | } 23 | 24 | func testInitialization() { 25 | XCTAssertNotNil(manager) 26 | XCTAssertEqual(manager.apiKey, "test_api_key") 27 | XCTAssertEqual(manager.secret, "test_secret_key") 28 | } 29 | 30 | func testSetSessionKey() { 31 | let sessionKey = "test_session_key" 32 | manager.setSessionKey(sessionKey) 33 | XCTAssertEqual(manager.sessionKey, sessionKey) 34 | } 35 | } 36 | --------------------------------------------------------------------------------