Skip to content

Commit fc62fc9

Browse files
committed
Fix Spotify get access token failed
1 parent c1b9eae commit fc62fc9

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ let package = Package(
2424
.package(url: "https://github.com/1024jp/GzipSwift", from: "5.0.0"),
2525
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess", .upToNextMajor(from: "4.0.0")),
2626
.package(url: "https://github.com/MxIris-Library-Forks/Schedule", branch: "master"),
27+
.package(url: "https://github.com/lachlanbell/SwiftOTP", from: "3.0.2"),
2728
],
2829
targets: [
2930
.target(
@@ -50,6 +51,7 @@ let package = Package(
5051
"LyricsService",
5152
.product(name: "KeychainAccess", package: "KeychainAccess"),
5253
.product(name: "Schedule", package: "Schedule"),
54+
.product(name: "SwiftOTP", package: "SwiftOTP"),
5355
]
5456
),
5557
.testTarget(

Sources/LyricsServiceUI/Controller/SpotifyLoginManager.swift

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,39 @@ import AppKit
22
import WebKit
33
import KeychainAccess
44
import Schedule
5+
import SwiftOTP
56

67
private typealias Task = _Concurrency.Task
78

9+
private enum TOTPGenerator {
10+
static func generate(serverTimeSeconds: Int) -> String? {
11+
let secretCipher = [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54]
12+
13+
var processed = [UInt8]()
14+
for (i, byte) in secretCipher.enumerated() {
15+
processed.append(UInt8(byte ^ (i % 33 + 9)))
16+
}
17+
18+
let processedStr = processed.map { String($0) }.joined()
19+
20+
guard let utf8Bytes = processedStr.data(using: .utf8) else {
21+
return nil
22+
}
23+
24+
let secretBase32 = utf8Bytes.base32EncodedString
25+
26+
guard let secretData = base32DecodeToData(secretBase32) else {
27+
return nil
28+
}
29+
30+
guard let totp = TOTP(secret: secretData, digits: 6, timeInterval: 30, algorithm: .sha1) else {
31+
return nil
32+
}
33+
34+
return totp.generate(secondsPast1970: serverTimeSeconds)
35+
}
36+
}
37+
838
struct SpotifyAccessToken: Codable {
939
let accessToken: String
1040
let accessTokenExpirationTimestampMs: TimeInterval
@@ -15,7 +45,23 @@ struct SpotifyAccessToken: Codable {
1545
}
1646

1747
static func accessToken(forCookie cookie: String) async throws -> Self {
18-
let url = URL(string: "https://open.spotify.com/get_access_token?reason=transport&productType=web_player")!
48+
struct ServerTime: Codable {
49+
var serverTime: Int
50+
}
51+
52+
enum Error: Swift.Error {
53+
case totpGenerationFailed
54+
}
55+
56+
let serverTimeRequest = URLRequest(url: .init(string: "https://open.spotify.com/server-time")!)
57+
let serverTimeData = try await URLSession.shared.data(for: serverTimeRequest).0
58+
let serverTime = try JSONDecoder().decode(ServerTime.self, from: serverTimeData).serverTime
59+
60+
guard let totp = TOTPGenerator.generate(serverTimeSeconds: serverTime) else {
61+
throw Error.totpGenerationFailed
62+
}
63+
64+
let url = URL(string: "https://open.spotify.com/get_access_token?reason=transport&productType=web_player&totpVer=5&ts=\(Int(Date().timeIntervalSince1970))&totp=\(totp)")!
1965
var request = URLRequest(url: url)
2066
request.setValue("sp_dc=\(cookie)", forHTTPHeaderField: "Cookie")
2167
let accessTokenData = try await URLSession.shared.data(for: request).0
@@ -99,7 +145,7 @@ public final class SpotifyLoginManager: NSObject, @unchecked Sendable {
99145
}
100146

101147
private let secureStorage = SecureStorage()
102-
148+
103149
private var refreshTask: Schedule.Task?
104150

105151
public var isLogin: Bool {
@@ -134,7 +180,6 @@ public final class SpotifyLoginManager: NSObject, @unchecked Sendable {
134180
}
135181
}
136182

137-
138183
private func scheduleAccessTokenRefresh(_ accessToken: SpotifyAccessToken) {
139184
refreshTask?.cancel()
140185
refreshTask = Plan.at(accessToken.expirationDate).do(queue: .global()) { [weak self] in

0 commit comments

Comments
 (0)