├── .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 |
--------------------------------------------------------------------------------