Skip to content

Commit 61f8ef3

Browse files
committed
Vendor JLyrics for reliable CI builds
1 parent 00ceb4c commit 61f8ef3

File tree

4 files changed

+332
-13
lines changed

4 files changed

+332
-13
lines changed

pom.xml

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,6 @@
1717
<name>bintray</name>
1818
<url>https://jcenter.bintray.com</url>
1919
</repository>
20-
<repository>
21-
<id>jitpack.io</id>
22-
<url>https://jitpack.io</url>
23-
<snapshots>
24-
<enabled>true</enabled>
25-
<updatePolicy>always</updatePolicy>
26-
</snapshots>
27-
</repository>
2820
<repository>
2921
<id>m2.duncte123.dev</id>
3022
<name>m2-duncte123</name>
@@ -80,11 +72,6 @@
8072
<artifactId>common</artifactId>
8173
<version>1.18.0</version>
8274
</dependency>
83-
<dependency>
84-
<groupId>com.github.jagrosh</groupId>
85-
<artifactId>JLyrics</artifactId>
86-
<version>master-SNAPSHOT</version>
87-
</dependency>
8875
<dependency>
8976
<groupId>com.dunctebot</groupId>
9077
<artifactId>sourcemanagers</artifactId>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2018 John Grosh (john.a.grosh@gmail.com).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.jagrosh.jlyrics;
17+
18+
public class Lyrics
19+
{
20+
private final String title;
21+
private final String author;
22+
private final String content;
23+
private final String url;
24+
private final String source;
25+
26+
protected Lyrics(String title, String author, String content, String url, String source)
27+
{
28+
this.title = title;
29+
this.author = author;
30+
this.content = content;
31+
this.url = url;
32+
this.source = source;
33+
}
34+
35+
public String getTitle()
36+
{
37+
return title;
38+
}
39+
40+
public String getAuthor()
41+
{
42+
return author;
43+
}
44+
45+
public String getContent()
46+
{
47+
return content;
48+
}
49+
50+
public String getURL()
51+
{
52+
return url;
53+
}
54+
55+
public String getSource()
56+
{
57+
return source;
58+
}
59+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright 2018 John Grosh (john.a.grosh@gmail.com).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.jagrosh.jlyrics;
17+
18+
import com.typesafe.config.Config;
19+
import com.typesafe.config.ConfigException;
20+
import com.typesafe.config.ConfigFactory;
21+
import java.io.IOException;
22+
import java.util.HashMap;
23+
import java.util.concurrent.CompletableFuture;
24+
import java.util.concurrent.Executor;
25+
import java.util.concurrent.Executors;
26+
import java.util.regex.Matcher;
27+
import java.util.regex.Pattern;
28+
import org.json.JSONException;
29+
import org.json.JSONObject;
30+
import org.json.XML;
31+
import org.jsoup.Connection;
32+
import org.jsoup.Jsoup;
33+
import org.jsoup.nodes.Document;
34+
import org.jsoup.nodes.Document.OutputSettings;
35+
import org.jsoup.nodes.Element;
36+
import org.jsoup.safety.Safelist;
37+
38+
public class LyricsClient
39+
{
40+
private final Config config = ConfigFactory.load();
41+
private final HashMap<String, Lyrics> cache = new HashMap<>();
42+
private final OutputSettings noPrettyPrint = new OutputSettings().prettyPrint(false);
43+
private final Safelist newlineSafelist = Safelist.none().addTags("br", "p");
44+
private final Executor executor;
45+
private final String defaultSource;
46+
private final String userAgent;
47+
private final int timeout;
48+
49+
public LyricsClient()
50+
{
51+
this(null, null);
52+
}
53+
54+
public LyricsClient(String defaultSource)
55+
{
56+
this(defaultSource, null);
57+
}
58+
59+
public LyricsClient(Executor executor)
60+
{
61+
this(null, executor);
62+
}
63+
64+
public LyricsClient(String defaultSource, Executor executor)
65+
{
66+
this.defaultSource = defaultSource == null ? config.getString("lyrics.default") : defaultSource;
67+
this.userAgent = config.getString("lyrics.user-agent");
68+
this.timeout = config.getInt("lyrics.timeout");
69+
this.executor = executor == null ? Executors.newCachedThreadPool() : executor;
70+
}
71+
72+
public CompletableFuture<Lyrics> getLyrics(String search)
73+
{
74+
return getLyrics(search, defaultSource);
75+
}
76+
77+
public CompletableFuture<Lyrics> getLyrics(String search, String source)
78+
{
79+
String cacheKey = source + "||" + search;
80+
if(cache.containsKey(cacheKey))
81+
return CompletableFuture.completedFuture(cache.get(cacheKey));
82+
try
83+
{
84+
CompletableFuture<String> futureToken;
85+
boolean jsonSearch = config.getBoolean("lyrics." + source + ".search.json");
86+
String select = config.getString("lyrics." + source + ".search.select");
87+
String titleSelector = config.getString("lyrics." + source + ".parse.title");
88+
String authorSelector = config.getString("lyrics." + source + ".parse.author");
89+
String contentSelector = config.getString("lyrics." + source + ".parse.content");
90+
91+
if(config.hasPath("lyrics." + source + ".token"))
92+
futureToken = getToken(source);
93+
else
94+
futureToken = CompletableFuture.completedFuture("");
95+
96+
return futureToken.thenCompose(token -> {
97+
String searchUrl = String.format(config.getString("lyrics." + source + ".search.url"), search, token);
98+
99+
return CompletableFuture.supplyAsync(() -> {
100+
try
101+
{
102+
Document doc;
103+
Connection connection = Jsoup.connect(searchUrl).userAgent(userAgent).timeout(timeout);
104+
if(jsonSearch)
105+
{
106+
String body = connection.ignoreContentType(true).execute().body();
107+
JSONObject json = new JSONObject(body);
108+
doc = Jsoup.parse(XML.toString(json));
109+
}
110+
else
111+
doc = connection.get();
112+
113+
Element urlElement = doc.selectFirst(select);
114+
String url;
115+
if(jsonSearch)
116+
url = urlElement.text();
117+
else
118+
url = urlElement.attr("abs:href");
119+
if(url == null || url.isEmpty())
120+
return null;
121+
doc = Jsoup.connect(url).userAgent(userAgent).timeout(timeout).get();
122+
Lyrics lyrics = new Lyrics(
123+
doc.selectFirst(titleSelector).ownText(),
124+
doc.selectFirst(authorSelector).ownText(),
125+
cleanWithNewlines(doc.selectFirst(contentSelector)),
126+
url,
127+
source
128+
);
129+
cache.put(cacheKey, lyrics);
130+
return lyrics;
131+
}
132+
catch(IOException | NullPointerException | JSONException ex)
133+
{
134+
return null;
135+
}
136+
}, executor);
137+
});
138+
}
139+
catch(ConfigException ex)
140+
{
141+
throw new IllegalArgumentException(String.format("Source '%s' does not exist or is not configured correctly", source));
142+
}
143+
catch(Exception ignored)
144+
{
145+
return null;
146+
}
147+
}
148+
149+
private CompletableFuture<String> getToken(String source)
150+
{
151+
try
152+
{
153+
String tokenUrl = config.getString("lyrics." + source + ".token.url");
154+
String select = config.getString("lyrics." + source + ".token.select");
155+
boolean textSearch = config.getBoolean("lyrics." + source + ".token.text");
156+
157+
return CompletableFuture.supplyAsync(() -> {
158+
try
159+
{
160+
Pattern pattern = null;
161+
162+
if(config.hasPath("lyrics." + source + ".token.regex"))
163+
{
164+
String regexPattern = config.getString("lyrics." + source + ".token.regex");
165+
pattern = Pattern.compile(regexPattern);
166+
}
167+
168+
Connection connection = Jsoup.connect(tokenUrl).userAgent(userAgent).timeout(timeout);
169+
String body;
170+
171+
if(textSearch)
172+
{
173+
body = connection.ignoreContentType(true).execute().body();
174+
}
175+
else
176+
{
177+
Document doc = connection.get();
178+
body = doc.selectFirst(select).ownText();
179+
}
180+
181+
if(pattern != null)
182+
{
183+
Matcher matcher = pattern.matcher(body);
184+
if(matcher.find())
185+
return matcher.group();
186+
}
187+
return null;
188+
}
189+
catch(IOException | NullPointerException ex)
190+
{
191+
return null;
192+
}
193+
}, executor);
194+
}
195+
catch(ConfigException ex)
196+
{
197+
throw new IllegalArgumentException(String.format("Source '%s' does not exist or is not configured correctly", source));
198+
}
199+
catch(Exception ignored)
200+
{
201+
return null;
202+
}
203+
}
204+
205+
private String cleanWithNewlines(Element element)
206+
{
207+
return Jsoup.clean(Jsoup.clean(element.html(), newlineSafelist), "", Safelist.none(), noPrettyPrint);
208+
}
209+
}

src/main/resources/reference.conf

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,70 @@ updatealerts=true
143143
// https://github.com/jagrosh/JLyrics
144144

145145
lyrics.default = "A-Z Lyrics"
146+
lyrics {
147+
timeout = 5000
148+
user-agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0"
149+
150+
A-Z Lyrics {
151+
token {
152+
url = "https://www.azlyrics.com/geo.js"
153+
text = true
154+
select = ""
155+
regex = """(?<=\"value\",\s\").*(?=\")"""
156+
}
157+
search {
158+
url = "https://search.azlyrics.com/search.php?q=%s&x=%s"
159+
json = false
160+
select = "a[href*=/lyrics/]"
161+
}
162+
parse {
163+
title = "div.ringtone ~ b"
164+
author = "div.lyricsh b"
165+
content = "div.ringtone ~ div"
166+
}
167+
}
168+
169+
Genius {
170+
search {
171+
url = "https://genius.com/api/search?q=%s"
172+
json = true
173+
select = "result > url"
174+
}
175+
parse {
176+
title = "h1[class*=__Title] > span"
177+
author = "a[class*=__Artist]"
178+
content = "div[class^=Lyrics__Container]"
179+
}
180+
}
181+
182+
MusixMatch {
183+
search {
184+
url = "https://www.musixmatch.com/search/%s"
185+
json = false
186+
select = "a.title[href*=/lyrics/]"
187+
}
188+
parse {
189+
title = "h1"
190+
author = "h2 span a"
191+
content = "div.mxm-lyrics > span"
192+
}
193+
}
194+
195+
MusicMatch = ${lyrics.MusixMatch}
196+
197+
LyricsFreak {
198+
search {
199+
url = "https://www.lyricsfreak.com/search.php?q=%s"
200+
json = false
201+
select = "a.song[href*=.html]"
202+
}
203+
parse {
204+
title = "div#breadcrumb span > span[itemprop=title]"
205+
author = "h2.lyric-song-head a"
206+
content = "div#content"
207+
}
208+
}
209+
}
146210

147211

148212
// These settings allow you to configure custom aliases for all commands.

0 commit comments

Comments
 (0)