Added Genius API wrapper
for fetching song lyrics from Genius Database # Disclaimer. This commit contains parts that are for educational purposes only! FindSong: - Uses Genius API to find the song queried GetLyrics: # Educational purposes only - Scrapes Genius lyrics page for the Lyrics
This commit is contained in:
24
GeniusAPI/build.gradle
Normal file
24
GeniusAPI/build.gradle
Normal file
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'me.zacharias'
|
||||
version = '1.0-SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation platform('org.junit:junit-bom:5.10.0')
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
|
||||
implementation "org.jsoup:jsoup:1.20.1"
|
||||
implementation 'io.github.classgraph:classgraph:4.8.158'
|
||||
|
||||
implementation project(":Core")
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package me.zacharias.neuro.dock.genius;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface GeniusEndpoint {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.zacharias.neuro.dock.genius;
|
||||
|
||||
import me.zacharias.chat.ollama.OllamaFunctionTool;
|
||||
|
||||
public abstract class GeniusEndpointTool extends OllamaFunctionTool {
|
||||
protected GeniusTools geniusToolsInstance;
|
||||
|
||||
public GeniusEndpointTool(GeniusTools geniusTools) {
|
||||
super();
|
||||
this.geniusToolsInstance = geniusTools;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package me.zacharias.neuro.dock.genius;
|
||||
|
||||
import com.google.common.reflect.ClassPath;
|
||||
import io.github.classgraph.ClassGraph;
|
||||
import io.github.classgraph.ClassInfo;
|
||||
import io.github.classgraph.ClassInfoList;
|
||||
import io.github.classgraph.ScanResult;
|
||||
import me.zacharias.chat.core.Core;
|
||||
import me.zacharias.chat.ollama.OllamaFunctionTool;
|
||||
import me.zacharias.chat.ollama.OllamaFunctionTools;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeniusTools {
|
||||
|
||||
public final String Client_ID;
|
||||
public final String Client_Secret;
|
||||
public final String Access_Token;
|
||||
public final String BaseURL = "https://api.genius.com";
|
||||
public final OllamaFunctionTools GeniusTools;
|
||||
public final File CacheFile = new File(Core.DATA_DIR + "/genius_cache.json");
|
||||
public JSONObject CacheData;
|
||||
|
||||
public GeniusTools() {
|
||||
super();
|
||||
try {
|
||||
JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/geniusapi.json")));
|
||||
this.Client_ID = obj.getString("client_id");
|
||||
this.Client_Secret = obj.getString("client_secret");
|
||||
this.Access_Token = obj.getString("access_token");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if(CacheFile.exists()) {
|
||||
try{
|
||||
CacheData = new JSONObject(Files.readString(CacheFile.toPath()));
|
||||
}catch (IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
if(CacheData == null)
|
||||
CacheData = new JSONObject();
|
||||
CacheFile.delete();
|
||||
}
|
||||
}
|
||||
else {
|
||||
CacheData = new JSONObject();
|
||||
}
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
try {
|
||||
Files.writeString(CacheFile.toPath(), CacheData.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}));
|
||||
|
||||
OllamaFunctionTools.OllamaFunctionToolsBuilder builder = new OllamaFunctionTools.OllamaFunctionToolsBuilder();
|
||||
|
||||
try(ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("me.zacharias.neuro.dock.genius.endpoints").scan()) {
|
||||
ClassInfoList endpoints = scanResult.getClassesWithAnnotation(GeniusEndpoint.class.getName());
|
||||
for (ClassInfo classInfo : endpoints) {
|
||||
Class<?> clazz = classInfo.loadClass();
|
||||
if (OllamaFunctionTool.class.isAssignableFrom(clazz)) {
|
||||
//System.out.println("Found endpoint: " + clazz.getName() + " With constructor: " + clazz.getDeclaredConstructors().f.getName() + " and arguments: " + Arrays.toString(clazz.getDeclaredConstructor().getParameterTypes()));
|
||||
GeniusEndpointTool tool = (GeniusEndpointTool) clazz.getDeclaredConstructor(GeniusTools.class).newInstance(this);
|
||||
builder.addTool(tool, Core.Source.INTERNAL);
|
||||
}
|
||||
}
|
||||
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
this.GeniusTools = builder.build();
|
||||
}
|
||||
|
||||
public OllamaFunctionTools getGeniusTools() {
|
||||
return this.GeniusTools;
|
||||
}
|
||||
|
||||
public JSONObject getGeniusEndpoint(String endpoint, Map<String, String> params) {
|
||||
try {
|
||||
URL url = new URL(this.BaseURL + endpoint + "?" + ParameterStringBuilder.getParamsString(params));
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Authorization", "Bearer " + this.Access_Token);
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
|
||||
if (conn.getResponseCode() != 200) {
|
||||
throw new IOException("Failed to connect to Genius API: " + conn.getResponseCode());
|
||||
}
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String inputLine;
|
||||
StringBuilder response = new StringBuilder();
|
||||
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
in.close();
|
||||
|
||||
return new JSONObject(response.toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String hasCache(int song_id) {
|
||||
if(CacheData.has(Integer.toString(song_id)))
|
||||
return CacheData.getString(Integer.toString(song_id));
|
||||
return null;
|
||||
}
|
||||
|
||||
public void cacheLyrics(int song_id, String lyrics) {
|
||||
CacheData.put(Integer.toString(song_id), lyrics);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.zacharias.neuro.dock.genius;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
public class ParameterStringBuilder {
|
||||
public static String getParamsString(Map<String, String> params)
|
||||
throws UnsupportedEncodingException {
|
||||
if(params == null)
|
||||
return "";
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
result.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
|
||||
result.append("=");
|
||||
result.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
|
||||
result.append("&");
|
||||
}
|
||||
|
||||
String resultString = result.toString();
|
||||
return !resultString.isEmpty()
|
||||
? resultString.substring(0, resultString.length() - 1)
|
||||
: resultString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package me.zacharias.neuro.dock.genius.endpoints;
|
||||
|
||||
import me.zacharias.chat.ollama.OllamaFunctionArgument;
|
||||
import me.zacharias.chat.ollama.OllamaPerameter;
|
||||
import me.zacharias.chat.ollama.OllamaToolRespnce;
|
||||
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.zacharias.neuro.dock.genius.GeniusEndpoint;
|
||||
import me.zacharias.neuro.dock.genius.GeniusEndpointTool;
|
||||
import me.zacharias.neuro.dock.genius.GeniusTools;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static me.zacharias.chat.ollama.OllamaPerameter.OllamaPerameterBuilder.Type.STRING;
|
||||
|
||||
@GeniusEndpoint
|
||||
public class FindSong extends GeniusEndpointTool {
|
||||
public FindSong(GeniusTools geniusTools) {
|
||||
super(geniusTools);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "findsong";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Finds a song by title from the Genius API.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("title", STRING, "The title, artitst, and song_id of the song to find.", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
|
||||
String title = (String) args[0].value();
|
||||
if (title == null || title.isEmpty()) {
|
||||
throw new OllamaToolErrorException(this.name(), "Title cannot be null or empty.");
|
||||
}
|
||||
|
||||
JSONObject response = this.geniusToolsInstance.getGeniusEndpoint("/search", Map.of("q", title));
|
||||
|
||||
JSONArray responseData = new JSONArray();
|
||||
|
||||
for(Object obj : response.getJSONObject("response").getJSONArray("hits")) {
|
||||
if(obj instanceof JSONObject songResult) {
|
||||
JSONObject song = songResult.getJSONObject("result");
|
||||
JSONObject songData = new JSONObject();
|
||||
songData.put("title", song.getString("title"));
|
||||
songData.put("artist", song.getString("artist_names"));
|
||||
songData.put("id", song.getInt("id"));
|
||||
responseData.put(songData);
|
||||
}
|
||||
}
|
||||
|
||||
if (responseData.isEmpty()) {
|
||||
throw new OllamaToolErrorException(this.name(), "No songs found for the given title.");
|
||||
}
|
||||
|
||||
return new OllamaToolRespnce(this.name(), responseData.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package me.zacharias.neuro.dock.genius.endpoints;
|
||||
|
||||
import me.zacharias.chat.ollama.OllamaFunctionArgument;
|
||||
import me.zacharias.chat.ollama.OllamaPerameter;
|
||||
import me.zacharias.chat.ollama.OllamaToolRespnce;
|
||||
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.zacharias.neuro.dock.genius.GeniusEndpoint;
|
||||
import me.zacharias.neuro.dock.genius.GeniusEndpointTool;
|
||||
import me.zacharias.neuro.dock.genius.GeniusTools;
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static me.zacharias.chat.core.Core.writeLog;
|
||||
|
||||
@GeniusEndpoint
|
||||
public class GetLyrics extends GeniusEndpointTool {
|
||||
public GetLyrics(GeniusTools geniusTools) {
|
||||
super(geniusTools);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "get_lyrics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Gets the lyrics of a song by its ID from the Genius API.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("song_id", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The ID of the song to get lyrics for.", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
|
||||
|
||||
String lyricsStr = geniusToolsInstance.hasCache((int) args[0].value());
|
||||
|
||||
if(lyricsStr != null)
|
||||
{
|
||||
// If we have a cached response, return it
|
||||
}
|
||||
|
||||
JSONObject obj = geniusToolsInstance.getGeniusEndpoint("/songs/" + args[0].value(), null);
|
||||
|
||||
String lyrics_path = obj.getJSONObject("response").getJSONObject("song").getString("url");
|
||||
try {
|
||||
Document doc = Jsoup.connect(lyrics_path)
|
||||
.userAgent("Mozilla/5.0")
|
||||
.get();
|
||||
|
||||
writeLog("Fetching lyrics from: " + lyrics_path);
|
||||
|
||||
Elements containers = doc.select("div[data-lyrics-container=true]");
|
||||
|
||||
StringBuilder lyrics = new StringBuilder();
|
||||
|
||||
for (Element container : containers) {
|
||||
for(Node n : container.childNodes())
|
||||
{
|
||||
if(n instanceof Element e) {
|
||||
if (e.attribute("data-exclude-from-selection") != null && e.attr("data-exclude-from-selection").equals("true")) {
|
||||
continue;
|
||||
}
|
||||
else if(e.tagName().equalsIgnoreCase("br"))
|
||||
{
|
||||
lyrics.append("\n");
|
||||
}
|
||||
else {
|
||||
//System.out.println(container.tagName());
|
||||
String s = e.text();
|
||||
lyrics.append(s.trim());
|
||||
}
|
||||
}
|
||||
else if(n instanceof TextNode tn)
|
||||
{
|
||||
String s = tn.text();
|
||||
if (!s.isBlank()) {
|
||||
lyrics.append(s.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geniusToolsInstance.cacheLyrics((int) args[0].value(), lyrics.toString());
|
||||
|
||||
return new OllamaToolRespnce(this.name(), lyrics.toString().trim());
|
||||
}catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
throw new OllamaToolErrorException(this.name(), "Failed to fetch lyrics.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user