Skip to content

Commit 8f36aa5

Browse files
committed
Add Spotify login UI
1 parent 3b977a4 commit 8f36aa5

File tree

6 files changed

+133
-8
lines changed

6 files changed

+133
-8
lines changed

Package.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ let package = Package(
1313
products: [
1414
.library(
1515
name: "LyricsKit",
16-
targets: ["LyricsCore", "LyricsService"]),
16+
targets: ["LyricsCore", "LyricsService", "LyricsServiceUI"]),
1717
],
1818
dependencies: [
1919
.package(url: "https://github.com/MxIris-LyricsX-Project/CXShim", .branchItem("master")),
2020
.package(url: "https://github.com/MxIris-LyricsX-Project/CXExtensions", .branchItem("master")),
2121
.package(url: "https://github.com/ddddxxx/Regex", from: "1.0.1"),
2222
.package(url: "https://github.com/MxIris-Library-Forks/SwiftCF", .branchItem("master")),
2323
.package(name: "Gzip", url: "https://github.com/1024jp/GzipSwift", from: "5.0.0"),
24+
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess", .upToNextMajor(from: "4.0.0")),
2425
],
2526
targets: [
2627
.target(
@@ -32,6 +33,14 @@ let package = Package(
3233
"LyricsCore", "CXShim", "CXExtensions", "Regex", "Gzip",
3334
]
3435
),
36+
.target(
37+
name: "LyricsServiceUI",
38+
dependencies: [
39+
"LyricsCore",
40+
"LyricsService",
41+
"KeychainAccess",
42+
]
43+
),
3544
.testTarget(
3645
name: "LyricsKitTests",
3746
dependencies: ["LyricsCore", "LyricsService"]),

Sources/LyricsService/Provider/Spotify.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@ import Foundation
44
import LyricsCore
55
import CXExtensions
66

7-
struct SpotifyAccessToken: Codable {
8-
let accessToken: String
9-
let accessTokenExpirationTimestampMs: TimeInterval
10-
let isAnonymous: Bool
11-
}
12-
137
struct SpotifySearchResponse: Codable {
148
struct Tracks: Codable {
159
struct Item: Codable {
@@ -74,7 +68,7 @@ extension LyricsProviders {
7468
typealias LyricsToken = SpotifySearchResponse.Tracks.Item
7569

7670
let accessToken: String
77-
71+
7872
init(accessToken: String) {
7973
self.accessToken = accessToken
8074
}

Sources/LyricsService/Provider/Syair.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import CXShim
1313
import CXExtensions
1414

1515
#if canImport(Darwin)
16+
import AppKit
1617

1718
private let syairSearchBaseURLString = "https://syair.info/search"
1819
private let syairLyricsBaseURL = URL(string: "https://syair.info")!
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import AppKit
2+
import WebKit
3+
4+
struct SpotifyAccessToken: Codable {
5+
let accessToken: String
6+
let accessTokenExpirationTimestampMs: TimeInterval
7+
let isAnonymous: Bool
8+
9+
static func accessToken(forCookie cookie: String) async throws -> Self {
10+
let url = URL(string: "https://open.spotify.com/get_access_token?reason=transport&productType=web_player")!
11+
var request = URLRequest(url: url)
12+
request.setValue("sp_dc=\(cookie)", forHTTPHeaderField: "Cookie")
13+
let accessTokenData = try await URLSession.shared.data(for: request).0
14+
return try JSONDecoder().decode(SpotifyAccessToken.self, from: accessTokenData)
15+
}
16+
}
17+
18+
public final class SpotifyLoginController: NSObject {
19+
public static let shared = SpotifyLoginController()
20+
21+
// var isLogin: Bool {
22+
//
23+
// }
24+
25+
26+
}
27+
28+
29+
public final class SpotifyLoginWindowController: NSWindowController {
30+
public init() {
31+
super.init(window: nil)
32+
}
33+
34+
@available(*, unavailable)
35+
public required init?(coder: NSCoder) {
36+
fatalError("init(coder:) has not been implemented")
37+
}
38+
39+
public override func loadWindow() {
40+
let window = NSWindow(contentRect: .init(x: 0, y: 0, width: 800, height: 600), styleMask: [.titled, .closable], backing: .buffered, defer: false)
41+
window.title = "Spotify Login"
42+
window.setContentSize(NSSize(width: 800, height: 600))
43+
window.center()
44+
self.window = window
45+
}
46+
47+
public override var windowNibName: NSNib.Name? { "" }
48+
49+
public override func windowDidLoad() {
50+
contentViewController = SpotifyLoginViewController()
51+
}
52+
}
53+
54+
public final class SpotifyLoginViewController: NSViewController {
55+
private let webView: WKWebView
56+
57+
private static let loginURL = URL(string: "https://accounts.spotify.com/en/login?continue=https%3A%2F%2Fopen.spotify.com%2F")!
58+
59+
private static let logoutURL = URL(string: "https://www.spotify.com/logout/")!
60+
61+
public var didLogin: ((String) -> Void)?
62+
63+
public init() {
64+
self.webView = WKWebView(frame: .zero, configuration: .init())
65+
super.init(nibName: nil, bundle: nil)
66+
}
67+
68+
@available(*, unavailable)
69+
public required init?(coder: NSCoder) {
70+
fatalError("init(coder:) has not been implemented")
71+
}
72+
73+
public override func loadView() {
74+
view = webView
75+
}
76+
77+
public override func viewDidLoad() {
78+
super.viewDidLoad()
79+
gotoLogin()
80+
}
81+
82+
public func gotoLogin() {
83+
webView.load(.init(url: Self.loginURL))
84+
}
85+
86+
public func gotoLogout() {
87+
webView.load(.init(url: Self.logoutURL))
88+
}
89+
}
90+
91+
extension SpotifyLoginViewController: WKNavigationDelegate {
92+
public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
93+
guard let url = webView.url else { return }
94+
if url.absoluteString.starts(with: "https://open.spotify.com") {
95+
Task.detached {
96+
if let cookie = await WKWebsiteDataStore.default().spotifyCookie() {
97+
await MainActor.run {
98+
self.didLogin?(cookie)
99+
}
100+
}
101+
}
102+
}
103+
}
104+
}
105+
106+
extension WKWebsiteDataStore {
107+
public func spotifyCookie() async -> String? {
108+
let cookies = await httpCookieStore.allCookies()
109+
if let temporaryCookie = cookies.first(where: { $0.name == "sp_dc" }) {
110+
return temporaryCookie.value
111+
}
112+
return nil
113+
}
114+
}
115+
116+
@available(macOS 14.0, *)
117+
#Preview {
118+
SpotifyLoginViewController()
119+
}

Sources/LyricsService/Drawing/LyricsSourceIconDrawing+Image.swift renamed to Sources/LyricsServiceUI/Drawing/LyricsSourceIconDrawing+Image.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
88
//
99

10+
import LyricsService
11+
1012
#if canImport(CoreGraphics)
1113

1214
import CoreGraphics

0 commit comments

Comments
 (0)