]>
Commit | Line | Data |
---|---|---|
85d0536f | 1 | import Foundation |
3a398bc0 | 2 | import SwiftSoup |
85d0536f | 3 | |
d852b84e | 4 | // Given a track, attempts to fetch the lyrics from lyricswiki |
a968bff7 BB |
5 | class LyricsEngine { |
6 | ||
6e949673 | 7 | private let clientToken = <GENIUS_CLIENT_TOKEN> |
1b8bdf0f | 8 | |
d852b84e | 9 | // URL of the API endpoint to use |
1b8bdf0f | 10 | private let apiURL = "https://api.genius.com/search" |
d852b84e BB |
11 | |
12 | // Method used to call the API | |
85d0536f | 13 | private let apiMethod = "GET" |
d852b84e | 14 | |
d852b84e | 15 | // The track we'll be looking for |
85d0536f BB |
16 | private let track: Track |
17 | ||
18 | // Fetches the lyrics and returns if found | |
a968bff7 | 19 | var lyrics: String? { |
d852b84e | 20 | |
1263f62c | 21 | var lyrics: String? |
85d0536f | 22 | |
d852b84e BB |
23 | // Encode the track artist and name and finish building the API call URL |
24 | ||
1263f62c | 25 | if let artist = track.artist.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { |
3a398bc0 RBR |
26 | if let name = track.name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { |
27 | ||
28 | if let url = URL(string: "\(apiURL)?access_token=\(clientToken)&q=\(artist)%20\(name)") { | |
85d0536f | 29 | |
1263f62c | 30 | // We'll lock until the async call is finished |
85d0536f | 31 | |
1263f62c BB |
32 | var requestFinished = false |
33 | let asyncLock = NSCondition() | |
34 | asyncLock.lock() | |
85d0536f | 35 | |
1263f62c | 36 | // Call the API and unlock when you're done |
85d0536f | 37 | |
1b8bdf0f | 38 | searchLyricsUsingAPI(withURL: url, completionHandler: {lyricsResult -> Void in |
d0705bff BB |
39 | lyrics = lyricsResult |
40 | requestFinished = true | |
41 | asyncLock.signal() | |
1263f62c BB |
42 | }) |
43 | ||
44 | while !requestFinished { | |
45 | asyncLock.wait() | |
85d0536f | 46 | } |
1263f62c | 47 | asyncLock.unlock() |
85d0536f | 48 | } |
a968bff7 | 49 | } |
a968bff7 | 50 | } |
1263f62c BB |
51 | |
52 | return lyrics | |
a968bff7 BB |
53 | } |
54 | ||
d852b84e | 55 | // Initializes with a track |
a968bff7 BB |
56 | init(withTrack targetTrack: Track) { |
57 | ||
58 | track = targetTrack | |
59 | } | |
85d0536f | 60 | |
d852b84e BB |
61 | // Fetch the lyrics URL from the API, triggers the request to fetch the |
62 | // lyrics page | |
1b8bdf0f | 63 | private func searchLyricsUsingAPI(withURL url: URL, completionHandler: @escaping (String?) -> Void) { |
85d0536f BB |
64 | |
65 | var apiRequest = URLRequest(url: url) | |
66 | apiRequest.httpMethod = "GET" | |
67 | ||
1263f62c | 68 | let task = URLSession.shared.dataTask(with: apiRequest, completionHandler: {data, _, _ -> Void in |
85d0536f BB |
69 | |
70 | // If the response is parseable JSON, and has a url, we'll look for | |
71 | // the lyrics in there | |
72 | ||
73 | if let data = data { | |
1263f62c BB |
74 | if let jsonResponse = try? JSONSerialization.jsonObject(with: data) { |
75 | if let jsonResponse = jsonResponse as? [String: Any] { | |
4adf8d51 RBR |
76 | if let response = jsonResponse["response"] as? [String: Any] { |
77 | if let hits = response["hits"] as? [[String: Any]] { | |
78 | let filteredHits = hits.filter { $0["type"] as? String == "song" } | |
79 | if filteredHits.count > 0 { | |
80 | let firstHit = hits[0] | |
3a398bc0 RBR |
81 | if let firstHitData = firstHit["result"] as? [String: Any] { |
82 | if let lyricsUrlString = firstHitData["url"] as? String { | |
83 | if let lyricsUrl = URL(string: lyricsUrlString) { | |
84 | ||
85 | // At this point we have a valid wiki url | |
86 | self.fetchLyricsFromPage( | |
87 | withURL: lyricsUrl, | |
88 | completionHandler: completionHandler | |
89 | ) | |
90 | return | |
91 | } | |
1b8bdf0f RBR |
92 | } |
93 | } | |
94 | } | |
1263f62c | 95 | } |
85d0536f BB |
96 | } |
97 | } | |
98 | } | |
99 | } | |
100 | ||
101 | completionHandler(nil) | |
102 | }) | |
103 | task.resume() | |
104 | } | |
105 | ||
d852b84e | 106 | // Fetch the lyrics from the page and send it to the parser |
85d0536f BB |
107 | private func fetchLyricsFromPage(withURL url: URL, completionHandler: @escaping (String?) -> Void) { |
108 | ||
109 | var pageRequest = URLRequest(url: url) | |
110 | pageRequest.httpMethod = "GET" | |
111 | ||
1263f62c | 112 | let task = URLSession.shared.dataTask(with: pageRequest, completionHandler: {data, _, _ -> Void in |
85d0536f BB |
113 | |
114 | // If the response is parseable JSON, and has a url, we'll look for | |
115 | // the lyrics in there | |
116 | ||
117 | if let data = data { | |
118 | if let htmlBody = String(data: data, encoding: String.Encoding.utf8) { | |
119 | self.parseHtmlBody(htmlBody, completionHandler: completionHandler) | |
120 | return | |
121 | } | |
122 | } | |
123 | ||
124 | completionHandler(nil) | |
125 | }) | |
126 | task.resume() | |
127 | } | |
128 | ||
d852b84e | 129 | // Parses the wiki to find the lyrics, decodes the lyrics object |
85d0536f BB |
130 | private func parseHtmlBody(_ body: String, completionHandler: @escaping (String?) -> Void) { |
131 | ||
3a398bc0 RBR |
132 | do { |
133 | let document: Document = try SwiftSoup.parse(body) | |
134 | let lyricsBox = try document.select("div[data-lyrics-container=\"true\"]") | |
135 | try lyricsBox.select("br").after("\\n") | |
136 | let lyrics = try lyricsBox.text() | |
137 | completionHandler(lyrics.replacingOccurrences(of: "\\n", with: "\r\n")) | |
138 | } catch { | |
139 | completionHandler(nil) | |
85d0536f | 140 | } |
85d0536f | 141 | } |
a968bff7 | 142 | } |