refactor: me.zacharias.chat → me.neurodock, org rename cleanup

- Renamed package from me.zacharias.chat to me.neurodock across all 8 modules
- Updated Gradle group from me.zacharias.neurodock to me.neurodock
- Updated README and other files to reflect new Gitea org URL (Chat_things → neurodock)

Dev note: 95 files touched. The Great Refactor is complete, long may it rest.
This commit is contained in:
2026-05-27 23:34:22 +02:00
parent 88c593c47d
commit f50c04c828
97 changed files with 301 additions and 315 deletions
@@ -0,0 +1,11 @@
package me.neurodock.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.neurodock.genius;
import me.neurodock.ollama.OllamaFunctionTool;
public abstract class GeniusEndpointTool extends OllamaFunctionTool {
protected GeniusTools geniusToolsInstance;
public GeniusEndpointTool(GeniusTools geniusTools) {
super();
this.geniusToolsInstance = geniusTools;
}
}
@@ -0,0 +1,126 @@
package me.neurodock.genius;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import me.neurodock.core.Core;
import me.neurodock.ollama.OllamaFunctionTool;
import me.neurodock.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.neurodock.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,69 @@
package me.neurodock.genius.endpoints;
import me.neurodock.ollama.OllamaFunctionArgument;
import me.neurodock.ollama.OllamaPerameter;
import me.neurodock.ollama.OllamaToolResponse;
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
import me.neurodock.genius.GeniusEndpoint;
import me.neurodock.genius.GeniusEndpointTool;
import me.neurodock.genius.GeniusTools;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Map;
import static me.neurodock.ollama.OllamaPerameter.OllamaPerameterBuilder.Type.STRING;
@GeniusEndpoint
public class FindSong extends GeniusEndpointTool {
public FindSong(GeniusTools geniusTools) {
super(geniusTools);
}
@Override
public @NotNull String name() {
return "findsong";
}
@Override
public String description() {
return "Finds a song by title from the Genius API.";
}
@Override
public @NotNull OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("title", STRING, "The title, artitst, and song_id of the song to find.", true)
.build();
}
@Override
public @NotNull OllamaToolResponse 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 OllamaToolResponse(this.name(), responseData.toString());
}
}
@@ -0,0 +1,126 @@
package me.neurodock.genius.endpoints;
import me.neurodock.ollama.OllamaFunctionArgument;
import me.neurodock.ollama.OllamaPerameter;
import me.neurodock.ollama.OllamaToolResponse;
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
import me.neurodock.genius.GeniusEndpoint;
import me.neurodock.genius.GeniusEndpointTool;
import me.neurodock.genius.GeniusTools;
import org.jetbrains.annotations.NotNull;
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 static me.neurodock.core.Core.writeLog;
/**
* This class is an Ollama tool that fetches the lyrics of a song by its ID from the Genius API.
* It extends the GeniusEndpointTool to utilize the GeniusTools instance for API calls and caching.
* <p>
* <h1>Legal Notice</h1>
* <p>
* I (the developer) do NOT own the rights to the lyrics fetched by this tool, nor do I endorse scraping lyrics from Genius.com,
* which may violate their Terms of Service.
* </p>
* <p>
* I disclaim any responsibility or liability for how this tool is used.
* Use this tool at your own risk, and please respect all applicable copyright laws and third-party terms.
* </p>
*/
@GeniusEndpoint
public class GetLyrics extends GeniusEndpointTool {
public GetLyrics(GeniusTools geniusTools) {
super(geniusTools);
}
@Override
public @NotNull 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 @NotNull 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 @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
if(!( args[0].value() instanceof Integer)) {
throw new OllamaToolErrorException(this.name(), "The song_id must be an integer.");
}
String lyricsStr = geniusToolsInstance.hasCache((int) args[0].value());
if(lyricsStr != null)
{
return new OllamaToolResponse(this.name(), lyricsStr.trim());
}
JSONObject obj = geniusToolsInstance.getGeniusEndpoint("/songs/" + args[0].value(), null);
String lyrics_path = obj.getJSONObject("response").getJSONObject("song").getString("url");
try {
// WARNING: This request scrapes the lyrics from the Genius website.
// Which is against their Terms of Service. Use responsibly
Document doc = Jsoup.connect(lyrics_path)
.userAgent("insomnia/11.1.0") // TODO: replace with somthing else then insomnia! since we in no way support what ever Insomnia's User-Agent says we do
.header("host", "genius.com")
.header("accept", "*/*")
.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 OllamaToolResponse(this.name(), lyrics.toString().trim());
}catch (Exception ex)
{
ex.printStackTrace();
throw new OllamaToolErrorException(this.name(), "Failed to fetch lyrics.");
}
}
}