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,108 @@
package me.neurodock.mal.api;
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 me.zacharias.chat.ollama.*;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
public class MALAPITool {
public final String Client_ID;
public final String Client_Secret;
public final String BaseURL = "https://api.myanimelist.net/v2";
public final OllamaFunctionTools MALAPITools;
public MALAPITool() {
super();
try {
JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/malapi.json")));
this.Client_ID = obj.getString("client_id");
this.Client_Secret = obj.getString("client_secret");
} catch (IOException e) {
throw new RuntimeException(e);
}
OllamaFunctionTools.OllamaFunctionToolsBuilder builder = new OllamaFunctionTools.OllamaFunctionToolsBuilder();
try(ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("me.zacharias.chat.mal.api.endpoints").scan()) {
ClassInfoList endpoints = scanResult.getClassesWithAnnotation(MALEndpoint.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()));
MALEndpointTool tool = (MALEndpointTool) clazz.getDeclaredConstructor(MALAPITool.class).newInstance(MALAPITool.this);
builder.addTool(tool, Core.Source.INTERNAL);
}
}
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
this.MALAPITools = builder.build();
}
public OllamaFunctionTools getOllamaTools() {
return this.MALAPITools;
}
public JSONObject APIRequest(String endpoint, HashMap<String, String> params) {
try {
String urlParams = ParameterStringBuilder.getParamsString(params);
URL url = new URI(this.BaseURL + endpoint + "?" + urlParams).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("X-MAL-CLIENT-ID", this.Client_ID);
connection.setDoOutput(true);
connection.setConnectTimeout(80 * 1000);
int responseCode = connection.getResponseCode();
// HTTP_OK or 200 response code generally means that the server ran successfully without any errors
StringBuilder response = new StringBuilder();
// Read response content
// connection.getInputStream() purpose is to obtain an input stream for reading the server's response.
try (
BufferedReader reader = new BufferedReader( new InputStreamReader( connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line); // Adds every line to response till the end of file.
}
}
if (responseCode == HttpURLConnection.HTTP_OK) {
connection.disconnect();
return new JSONObject(response.toString());
}
else {
connection.disconnect();
System.err.println("Error: HTTP Response code - " + responseCode + "\n"+response.toString());
throw new RuntimeException("HTTP Response code - " + responseCode);
}
}catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error: " + e.getMessage());
}
}
}
@@ -0,0 +1,11 @@
package me.neurodock.mal.api;
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 MALEndpoint {
}
@@ -0,0 +1,11 @@
package me.neurodock.mal.api;
import me.neurodock.ollama.OllamaFunctionTool;
public abstract class MALEndpointTool extends OllamaFunctionTool {
protected MALAPITool MALAPIToolInstance;
public MALEndpointTool(MALAPITool malAPITool) {
super();
this.MALAPIToolInstance = malAPITool;
}
}
@@ -0,0 +1,27 @@
package me.neurodock.mal.api;
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,86 @@
package me.neurodock.mal.api.endpoints;
import me.neurodock.mal.api.MALAPITool;
import me.neurodock.mal.api.MALEndpoint;
import me.neurodock.mal.api.MALEndpointTool;
import me.neurodock.ollama.OllamaFunctionArgument;
import me.neurodock.ollama.OllamaPerameter;
import me.neurodock.ollama.OllamaToolResponse;
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
@MALEndpoint
public class GetAnimeDetails extends MALEndpointTool {
public GetAnimeDetails(MALAPITool malAPITool) {
super(malAPITool);
}
@Override
public @NotNull String name() {
return "get_anime_details";
}
@Override
public String description() {
return "Gets the details of an anime from the MAL API";
}
@Override
public @NotNull OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("id", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The id of the anime", true)
.addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return, defaults to [\"id\", \"title\", \"synopsis\", \"genres\"]")
.build();
}
@Override
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
int id = -1;
String[] fields = null;
for (OllamaFunctionArgument arg : args) {
switch (arg.argument()) {
case "id":
id = (Integer) arg.value();
break;
case "fields":
JSONArray jsonArray = (JSONArray) arg.value();
for (int i = 0; i < jsonArray.length(); i++) {
if (jsonArray.get(i) instanceof String) {
if (fields == null) {
fields = new String[jsonArray.length()];
}
fields[i] = jsonArray.getString(i);
} else {
throw new OllamaToolErrorException(this.name(), "The fields must be a string array");
}
}
break;
}
}
if (id == -1) {
throw new OllamaToolErrorException(this.name(), "The id must be specified");
}
if (fields == null) {
fields = new String[]{
"id",
"title",
"synopsis",
"genres"
};
}
HashMap<String, String> map = new HashMap<>();
map.put("fields", String.join(",", fields));
JSONObject obj = MALAPIToolInstance.APIRequest("/anime/"+id, map);
return new OllamaToolResponse(this.name(), obj.toString());
}
}
@@ -0,0 +1,88 @@
package me.neurodock.mal.api.endpoints;
import me.neurodock.mal.api.MALAPITool;
import me.neurodock.mal.api.MALEndpoint;
import me.neurodock.mal.api.MALEndpointTool;
import me.neurodock.ollama.OllamaFunctionArgument;
import me.neurodock.ollama.OllamaPerameter;
import me.neurodock.ollama.OllamaToolResponse;
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
@MALEndpoint
public class GetAnimeList extends MALEndpointTool {
public GetAnimeList(MALAPITool malAPITool) {
super(malAPITool);
}
@Override
public @NotNull String name() {
return "get_anime_list";
}
@Override
public String description() {
return "Gets a list of anime from the MAL API";
}
@Override
public @NotNull OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("query", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The query to search for",true)
.addProperty("offset", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The offset to start from")
.addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return")
.build();
}
@Override
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
String query = "";
int offset = 0;
String[] fields = null;
for (OllamaFunctionArgument arg : args) {
switch (arg.argument()) {
case "query":
query = (String)arg.value();
break;
case "offset":
offset = (Integer) arg.value();
break;
case "fields":
JSONArray jsonArray = (JSONArray) arg.value();
if(!jsonArray.isEmpty()) {
fields = new String[jsonArray.length()];
for (int i = 0; i < jsonArray.length(); i++) {
if (jsonArray.get(i) instanceof String) {
fields[i] = jsonArray.getString(i);
} else {
throw new OllamaToolErrorException(this.name(), "The fields must be a string array");
}
}
}
break;
}
}
if (query.isEmpty()) {
throw new OllamaToolErrorException(this.name(), "Query cannot be empty");
}
HashMap<String, String> params = new HashMap<>();
params.put("q", query);
if (offset > 0) {
params.put("offset", String.valueOf(offset));
}
if (fields != null) {
params.put("fields", String.join(",", fields));
}
JSONObject response = MALAPIToolInstance.APIRequest("/anime", params);
return new OllamaToolResponse(this.name(), response.toString());
}
}
@@ -0,0 +1,86 @@
package me.neurodock.mal.api.endpoints;
import me.neurodock.mal.api.MALAPITool;
import me.neurodock.mal.api.MALEndpoint;
import me.neurodock.mal.api.MALEndpointTool;
import me.neurodock.ollama.OllamaFunctionArgument;
import me.neurodock.ollama.OllamaPerameter;
import me.neurodock.ollama.OllamaToolResponse;
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
@MALEndpoint
public class GetManagDetails extends MALEndpointTool {
public GetManagDetails(MALAPITool malAPITool) {
super(malAPITool);
}
@Override
public @NotNull String name() {
return "get_manag_details";
}
@Override
public String description() {
return "Gets the details of an manga from the MAL API";
}
@Override
public @NotNull OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("id", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The id of the manga", true)
.addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return, defaults to [\"id\", \"title\", \"synopsis\", \"genres\"]")
.build();
}
@Override
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
int id = -1;
String[] fields = null;
for (OllamaFunctionArgument arg : args) {
switch (arg.argument()) {
case "id":
id = (Integer) arg.value();
break;
case "fields":
JSONArray jsonArray = (JSONArray) arg.value();
for (int i = 0; i < jsonArray.length(); i++) {
if (jsonArray.get(i) instanceof String) {
if (fields == null) {
fields = new String[jsonArray.length()];
}
fields[i] = jsonArray.getString(i);
} else {
throw new OllamaToolErrorException(this.name(), "The fields must be a string array");
}
}
break;
}
}
if (id == -1) {
throw new OllamaToolErrorException(this.name(), "The id must be specified");
}
if (fields == null) {
fields = new String[]{
"id",
"title",
"synopsis",
"genres"
};
}
HashMap<String, String> map = new HashMap<>();
map.put("fields", String.join(",", fields));
JSONObject obj = MALAPIToolInstance.APIRequest("/manga/"+id, map);
return new OllamaToolResponse(this.name(), obj.toString());
}
}
@@ -0,0 +1,88 @@
package me.neurodock.mal.api.endpoints;
import me.neurodock.mal.api.MALAPITool;
import me.neurodock.mal.api.MALEndpoint;
import me.neurodock.mal.api.MALEndpointTool;
import me.neurodock.ollama.OllamaFunctionArgument;
import me.neurodock.ollama.OllamaPerameter;
import me.neurodock.ollama.OllamaToolResponse;
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
@MALEndpoint
public class GetMangaList extends MALEndpointTool {
public GetMangaList(MALAPITool malAPITool) {
super(malAPITool);
}
@Override
public @NotNull String name() {
return "get_manga_list";
}
@Override
public String description() {
return "Gets a list of manga from the MAL API";
}
@Override
public @NotNull OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("query", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The query to search for",true)
.addProperty("offset", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The offset to start from")
.addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return")
.build();
}
@Override
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
String query = "";
int offset = 0;
String[] fields = null;
for (OllamaFunctionArgument arg : args) {
switch (arg.argument()) {
case "query":
query = (String)arg.value();
break;
case "offset":
offset = (Integer) arg.value();
break;
case "fields":
JSONArray jsonArray = (JSONArray) arg.value();
if(!jsonArray.isEmpty()) {
fields = new String[jsonArray.length()];
for (int i = 0; i < jsonArray.length(); i++) {
if (jsonArray.get(i) instanceof String) {
fields[i] = jsonArray.getString(i);
} else {
throw new OllamaToolErrorException(this.name(), "The fields must be a string array");
}
}
}
break;
}
}
if (query.isEmpty()) {
throw new OllamaToolErrorException(this.name(), "Query cannot be empty");
}
HashMap<String, String> params = new HashMap<>();
params.put("q", query);
if (offset > 0) {
params.put("offset", String.valueOf(offset));
}
if (fields != null) {
params.put("fields", String.join(",", fields));
}
JSONObject response = MALAPIToolInstance.APIRequest("/manga", params);
return new OllamaToolResponse(this.name(), response.toString());
}
}