Mise à jour du bot à une version de JDA plus récente, et ajout de commentaires

This commit is contained in:
MysaaJava 2024-01-12 15:58:00 +01:00
parent c456837148
commit 6721dd4f6d
Signed by: Mysaa
GPG Key ID: DBA23608F23F5A10
27 changed files with 4191 additions and 1880 deletions

View File

@ -13,11 +13,13 @@ apply plugin: 'java'
compileJava.options.encoding = 'UTF-8'
// In this section you declare where to find the dependencies of your project
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
maven {
name 'm2-dv8tion'
url 'https://m2.dv8tion.net/releases'
}
}
sourceSets {
@ -31,7 +33,7 @@ sourceSets {
}
task apiJar(type: Jar) {
archiveName = "JuliabotAPI.jar"
archiveFileName = "JuliabotAPI.jar"
group 'build'
description "Fait un jar avec juste l'api"
from(sourceSets.main.output) {
@ -40,7 +42,7 @@ task apiJar(type: Jar) {
}
task internalddonJar(type: Jar) {
archiveName = "JuliabotInternaddon_beta.jar"
archiveFileName = "JuliabotInternaddon_beta.jar"
group 'build'
description "Fait un jar avec juste l'addon interne"
from(sourceSets.main.output) {
@ -51,36 +53,29 @@ task internalddonJar(type: Jar) {
task execute(type:JavaExec) {
group 'execution'
description "Compile et execute le fichier jar"
main = 'com.bernard.juliabot.Julia'
mainClass = 'com.bernard.juliabot.Julia'
classpath = sourceSets.main.runtimeClasspath
}
task copyDependencies(type: Copy) {
group 'build'
from configurations.compile
from configurations.default
into 'dependencies'
}
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:23.0'
implementation 'org.slf4j:slf4j-nop:1.7.25'
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
implementation 'net.dv8tion:JDA:4.4.0_352'
compile 'org.slf4j:slf4j-nop:1.7.25'
implementation 'com.thedeanda:lorem:2.1'
compile 'net.dv8tion:JDA:4.2.0_189'
implementation 'commons-io:commons-io:2.6'
compile 'com.thedeanda:lorem:2.1'
implementation group: 'org.postgresql', name: 'postgresql', version: '42.2.22'
compile 'commons-io:commons-io:2.6'
// https://mvnrepository.com/artifact/mysql/mysql-connector-java
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.21'
implementation 'com.github.mpkorstanje:simmetrics-core:4.1.1'
}

View File

@ -0,0 +1,89 @@
package com.bernard.juliabot;
import static com.bernard.juliabot.api.Julia.erreur;
import java.lang.reflect.Method;
import com.bernard.juliabot.api.CCommande;
/**
* Objet utilitaire représentant l'appel à une commande, qui suivera l'execution de la
* commande et permet de les gérer dans des threads séparés.
* @author mysaa
*
*/
public class AppelACommande {
Method called;
CCommande argument;
String name;
String cname;
Object caller;
Thread commandThread;
Throwable exitValue = null;
volatile boolean running;
volatile boolean started;
/**
* Crée l'objet en spécifiant simplement ses paramètres.
* @param called La méthode qu'il faut appeler pour executer la commande.
* @param argument L'argument à cette méthode, «comment» la commande doit être lancée.
* @param name Le nom de la commande tel qu'il a été demandé.
* @param cname Le nom de la commande l'identifiant, sans alias.
* @param caller L'objet sur lequel doit être invoquée la commande.
*/
public AppelACommande(Method called, CCommande argument, String name, String cname, Object caller) {
this.called = called;
this.argument = argument;
this.name = name;
this.cname = cname;
this.caller = caller;
this.running = true;
}
/**
* Définit le therad d'appel de la fonction.
*/
public void setThread(Thread commandThread) {
this.commandThread = commandThread;
}
/**
* Définit la valeur de retour du programme (un Throwable en java)
* @param t
*/
public void setExitValue(Throwable t) {
this.exitValue = t;
}
/**
* impose la terminaison de l'execution de la commande.
*/
public void termine() {
running = false;
}
/**
* Teste si la commande est en cours de résolution
*/
public boolean isRunning() {
return running;
}
/**
* Met le thread courant en pause jusqu'à ce que la commande soit résolue.
*/
public void waitFin() {
while(running) {
try {
Thread.sleep(42);
} catch (InterruptedException e) {
erreur("On s'est fait intérrompre !",e);
}
}
}
}

View File

@ -1,59 +0,0 @@
package com.bernard.juliabot;
import java.lang.reflect.Method;
import com.bernard.juliabot.api.CCommande;
public class CommandCalled {
Method called;
CCommande argument;
String name;
String cname;
Object caller;
Thread commandThread;
Throwable exitValue = null;
volatile boolean running;
volatile boolean started;
public CommandCalled(Method called, CCommande argument, String name, String cname, Object caller) {
this.called = called;
this.argument = argument;
this.name = name;
this.cname = cname;
this.caller = caller;
this.running = true;
}
public void setThread(Thread commandThread) {
this.commandThread = commandThread;
}
public void setExitValue(Throwable t) {
this.exitValue = t;
}
public void ended() {
running = false;
}
public boolean isRunning() {
return running;
}
public void waitForExecution() {
while(running) {
try {
Thread.sleep(42);
} catch (InterruptedException e) {
System.err.println("Icapacité de pauser le thread : ");
e.printStackTrace();
}
}
}
}

View File

@ -6,25 +6,25 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.bernard.juliabot.api.CCommande;
import com.bernard.juliabot.api.Command;
import com.bernard.juliabot.api.Commande;
//TODO la javadoc
public class CommandeSurchargee {
String name;
Command command;
String nom;
Commande commande;
Map<Set<Class<?>>,Method> versions = new HashMap<>();
public CommandeSurchargee(Command c) {
this.name = c.name();
this.command = c;
public CommandeSurchargee(Commande c) {
this.nom = c.name();
this.commande = c;
}
public Method getExecutor(CCommande commande) {
public Method getExecuteur(CCommande commande) {
Set<Class<?>> critargs = checkFiliature(Stream.of(commande.getClass()).collect(Collectors.toSet()));
System.out.println("////////"+critargs);
return versions.get(versions.keySet().stream().filter(cr -> critargs.containsAll(cr)).findAny().orElse(null));
}
@ -77,11 +77,15 @@ public class CommandeSurchargee {
@Override
public String toString() {
return "CommandeSurchargee [name=" + name + ", versions=" + versions + "]";
return "CommandeSurchargee [name=" + nom + ", versions=" + versions + "]";
}
public String getDesc() {
return command.description();
return commande.description();
}
public Commande getCommande() {
return commande;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
package com.bernard.juliabot;
import static com.bernard.juliabot.api.Julia.erreur;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -16,38 +18,88 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.bernard.juliabot.Julia.Laboratory;
import com.bernard.juliabot.api.Command;
import com.bernard.juliabot.api.Discord;
import com.bernard.juliabot.api.JuLIAddon;
import com.bernard.juliabot.Julia.Laboratoire;
import com.bernard.juliabot.api.Commande;
import com.bernard.juliabot.api.EventDiscord;
import com.bernard.juliabot.api.Juliaddon;
/**
* Un objet de cette classe représente un juliaddon, hors contexte de chargement.
* @author mysaa
*
*/
public class JuliaAddon {
/**
* Un pattern qui permettre d'extraire le paquet dans lequel est un fichier .class
*/
private static final Pattern pkgFromClassName = Pattern.compile("^(\\w+(\\.\\w+){2,})\\.\\w+(\\$\\w+)*$");
/*
* Un pattern permettant d'extraire la version d'un juliaddon d'un fichier.
*/
private static final Pattern jarNameParser = Pattern.compile("^([^_]+)_([^_]+)(_(.+))?.jar$");
/**
* Le pkg de l'addon.
*/
String pkg;
/**
* La version de l'addon.
*/
String version;
JarFile jarFile;
long lastModified;
Set<JarEntry> loaderEntries;
/**
* Le fichier jar duquel a été chargé cet addon
*/
JarFile fichierJar;
/**
* Le timestamp de dernière modification du fichier jar au chargement de l'addon, afin de pouvoir tester
* plus rapidement si il est à jour avec le fichier jar associé.
*/
long derniereModif;
/**
* Liste des entrées du fichier jar à contenant l'addon, trèèèèès utile dans le cas d'un jar avec plusieurs addons.
*/
Set<JarEntry> entreesDuLoader;
Map<Character,JuliaClassLoader> classLoaders;
JuliaClassLoader unassignedClassLoader = null;
/**
* Liste les classloaders associés à chaque laboratoire dans lequel est chargé cet addon.
*/
Map<Laboratoire,JuliaClassLoader> classLoaders;
/**
* Un classloader non assigné, qui est créé afin de récupérer des informations sur l'addon à sa création, et qui
* peut être réutilisé pour un laboratoire.
*/
JuliaClassLoader classLoaderNonAssigne = null;
JuLIAddon addon;
/**
* L'annotation à l'origine de l'addon.
*/
Juliaddon addon;
/**
* La classe de cet annotation.
*/
Class<?> addonClass;
public JuliaAddon(JarFile jar) throws JarWithMultipleAddonsException {
/**
* Crée un addon à partir de toutes les entrées d'un fichier jar.
* @param jar Le fichier jar à lire.
* @throws JarAuxMultiplesAddonsException Si plusieurs addons sont présents dans le fichier jar.
*/
public JuliaAddon(JarFile jar) throws JarAuxMultiplesAddonsException {
this(jar,jar.stream().collect(Collectors.toSet()));
}
public JuliaAddon(JarFile jar,Set<JarEntry> entriesToRead) throws JarWithMultipleAddonsException {
jarFile = jar;
loaderEntries = entriesToRead;
lastModified = new File(jar.getName()).lastModified();
/**
* Crée un addon à partir des entrées jar spécifiées.
* @param jar Le fichier à lire.
* @param entreesALire Les entrées à lire.
* @throws JarAuxMultiplesAddonsException Si plusieurs addons sont dans ces entrées.
*/
public JuliaAddon(JarFile jar,Set<JarEntry> entreesALire) throws JarAuxMultiplesAddonsException {
fichierJar = jar;
entreesDuLoader = entreesALire;
derniereModif = new File(jar.getName()).lastModified();
classLoaders = new HashMap<>();
//Parsing du nom du fichier
@ -61,59 +113,117 @@ public class JuliaAddon {
//Sets addon,pkg,version(si redefini) et addonClass
unassignedClassLoader = new JuliaClassLoader(jar, entriesToRead,new DummyLaboratory());
classLoaderNonAssigne = new JuliaClassLoader(jar, entreesALire,new LaboFactice());
}
public synchronized JuliaClassLoader newClassLoader(Laboratory laboratory) {
if(unassignedClassLoader==null)
/**
* Crée un classloader pour le laboratoire donné.
* @param laboratoire le laboratoire qui sera associé au classloader.
* @return Un classloader qui peut charger ce Juliaddon.
*/
public synchronized JuliaClassLoader nouveauClassLoader(Laboratoire laboratoire) {
if(classLoaderNonAssigne==null)
try {
JuliaClassLoader cl = new JuliaClassLoader(jarFile, loaderEntries,laboratory);
classLoaders.put(laboratory.laboratory, cl);
JuliaClassLoader cl = new JuliaClassLoader(fichierJar, entreesDuLoader,laboratoire);
classLoaders.put(laboratoire, cl);
return cl;
} catch (JarWithMultipleAddonsException e) {
System.err.println("Cette erreur n'est pas censée arriver, alerte rouge");
} catch (JarAuxMultiplesAddonsException e) {
erreur("Cette erreur n'est pas censée arriver, alerte rouge");
System.exit(685533990);
}
JuliaClassLoader cl = unassignedClassLoader;
unassignedClassLoader = null;
cl.laboratory = laboratory;
classLoaders.put(laboratory.laboratory, cl);
JuliaClassLoader cl = classLoaderNonAssigne;
classLoaderNonAssigne = null;
cl.laboratoire = laboratoire;
classLoaders.put(laboratoire, cl);
return cl;
}
public String getName() {
return addon.name();
/**
* Récupère le nom de cet addon, tel que spécifié par le développeur.
*/
public String getNom() {
return addon.nom();
}
/**
* Renvoie la version de cet addon.
*/
public String getVersion() {
return version;
}
/**
* Renvoie le pkg de cet addon (qu'on peut voir comme son ID)
*/
public String getPkg() {
return pkg;
}
public JarFile getFichierJar() {
return fichierJar;
}
public long getDerniereModif() {
return derniereModif;
}
public JuliaClassLoader getLoader(Laboratoire labo) {
return classLoaders.get(labo);
}
/**
* Un classloader prévu pour lire des Juliaddons depuis des entrées de fichier jar.
* @author mysaa
*
*/
public class JuliaClassLoader extends ClassLoader {
Map<String,Class<?>> loadedClasses;
Set<JarEntry> entries;
/**
* Enregistres les classes déjà chargées pour un chargement plus rapide.
*/
Map<String,Class<?>> classesChargees;
/**
* L'ensemble des entrées que ce classloader à «le droit» de lire.
*/
Set<JarEntry> entrees;
/**
* Le fichier jar associé à ce classloader.
*/
JarFile jar;
Laboratory laboratory = null;
/**
* Le laboratoire associé à ce classloader.
*/
Laboratoire laboratoire = null;
HashSet<Method> registeredCommands;
HashSet<Method> registeredEvents;
/**
* Liste les commandes enregistrées par ce classloader.
*/
Set<Method> commandesEnregistrees;
/**
* Liste les events enregistrées par ce classloader.
*/
Set<Method> eventsEnregistrees;
public JuliaClassLoader(JarFile jar,Set<JarEntry> entriesToRead,Laboratory labo) throws JarWithMultipleAddonsException {
/**
* Crée un classloader pour un certain jeu d'entrées jar dans un laboratoire.
* @param jar le fichier jar duquel lire les entrées.
* @param entreesALire Les entrées à lire pour ce classloader
* @param labo Le laboratoire dans lequel est plongé ce classloader.
* @throws JarAuxMultiplesAddonsException Si plusieurs addons sont trouvés dans le jar. Un classloader, un addon.
*/
public JuliaClassLoader(JarFile jar,Set<JarEntry> entreesALire,Laboratoire labo) throws JarAuxMultiplesAddonsException {
super();
entries = entriesToRead;
entrees = entreesALire;
this.jar = jar;
loadedClasses = new HashMap<>();
this.laboratory = labo;
classesChargees = new HashMap<>();
this.laboratoire = labo;
Set<Class<?>> readClasses = new HashSet<>();
Map<String,JarEntry> juliaddons = new HashMap<>();
for(JarEntry entry : entriesToRead) {
for(JarEntry entry : entreesALire) {
if(entry.isDirectory())
continue;
if(!entry.getName().endsWith(".class"))
@ -130,37 +240,37 @@ public class JuliaAddon {
readClasses.add(clazz);
//Instantiated class
if(clazz.isAnnotationPresent(JuLIAddon.class)) {
if(clazz.isAnnotationPresent(Juliaddon.class)) {
//Declare package and new addon
Matcher m = pkgFromClassName.matcher(clazz.getName());
if(!m.matches())
throw new IllegalArgumentException("Le nom de la classe est invalide pour un Juliaddon (il faut au moins trois de profondeur dans le package) : "+clazz.getName());
String pkg = m.group(1);
if(pkg==null)
throw new IllegalArgumentException("Imposible de récupérer le pkg.");
if(juliaddons.containsKey(pkg))
throw new IllegalArgumentException("Il existe deja un juliaddon dans le package "+pkg+" , d<><64>placez "+clazz.getName()+" ou "+juliaddons.get(pkg).getName());
throw new IllegalArgumentException("Il existe deja un juliaddon dans le package "+pkg+" , déplacez "+clazz.getName()+" ou "+juliaddons.get(pkg).getName());
juliaddons.put(pkg, entry);
//If overwritten, will JarWithMultipleAddonsException
JuliaAddon.this.addon = clazz.getAnnotation(JuLIAddon.class);
JuliaAddon.this.addon = clazz.getAnnotation(Juliaddon.class);
JuliaAddon.this.pkg = pkg;
JuliaAddon.this.version = addon.version().isEmpty()?version:addon.version();
JuliaAddon.this.addonClass = clazz;
}
}catch (ClassFormatError e) {
System.err.println("Impossible de lire cette classe, son format est incorrect ... veuillez recompiler le jar");
e.printStackTrace();
erreur("Impossible de lire cette classe, son format est incorrect ... veuillez recompiler le jar",e);
continue;
}catch(SecurityException e) {
System.err.println("Vous essayez de mettre cette classe dans un package interdit ! Honte a vous");
e.printStackTrace();
erreur("Vous essayez de mettre cette classe dans un package interdit ! Honte a vous",e);
continue;
} catch (IllegalArgumentException e) {
System.err.println("Le fichier "+entry.getName()+" ne d<><64>cris pas un Juliaddon valide, il n'a donc pas <20><>t<EFBFBD><74> charg<72><67>, mais faites attention !");
e.printStackTrace();
erreur("Le fichier "+entry.getName()+" ne décris pas un Juliaddon valide, il n'a donc pas été chargé, mais faites attention !",e);
continue;
}
}
if(juliaddons.size()<1)
throw new IllegalStateException("Aucun addon présent dans le fichier !");
//Toutes les classes du Jar ont été lues : Si plusieurs addons, on rentre
//dans le if pour lever l'exception, sinon, on initialise le JuliaClassLoader
if(juliaddons.size() > 1) {
@ -170,7 +280,7 @@ public class JuliaAddon {
for (String pkg : juliaddons.keySet())
addonsEntriesVector.put(pkg, new HashSet<>());
eLoop : for(JarEntry entry : entriesToRead) {
eLoop : for(JarEntry entry : entreesALire) {
if(entry.isDirectory())
continue;
if(!entry.getName().endsWith(".class"))
@ -187,26 +297,28 @@ public class JuliaAddon {
}
}
//Si le code atteint ici, le JarEntry n'appartient a aucun pkg valide
System.err.println("Ce jar contiens plusieurs juliaddons. Nichtsdestotroz le fichier "+entry.getName()+" n'a pas de Juliaddon dans son package ou au dessus ... je ne charge donc pas la classe (attention, ca peux ammener a des ClassNotFoundException !!!)");
erreur("Ce jar contiens plusieurs juliaddons. "
+ "Nichtsdestotroz le fichier "+entry.getName()+" n'a pas de Juliaddon dans son package ou au dessus."
+ "je ne charge donc pas la classe (attention, ça peux ammener a des ClassNotFoundException !)");
continue;
}
addonsEntries = new HashSet<>(addonsEntriesVector.values());
throw new JarWithMultipleAddonsException(addonsEntries);
throw new JarAuxMultiplesAddonsException(addonsEntries);
}
// Initialisation du JuliaClassloader
//Les parametres addon, pkg,version et addonCLass ont d<EFBFBD><EFBFBD>j<EFBFBD><EFBFBD> <EFBFBD><EFBFBD>t<EFBFBD><EFBFBD> initialis<EFBFBD><EFBFBD>s lors de l'it<EFBFBD><EFBFBD>ration des fichiers du jar
// Les paramètres addon, pkg,version et addonCLass ont déjà été initialisés lors de l'itération des fichiers du jar
}
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if(loadedClasses.containsKey(name))
return loadedClasses.get(name);
if(classesChargees.containsKey(name))
return classesChargees.get(name);
///Recherche d'une entry qui collerai
String fileName = name.replaceAll("\\.", "/")+".class";
Optional<JarEntry> oentry = entries.stream().filter(je -> je.getName().equals(fileName)).findAny();
Optional<JarEntry> oentry = entrees.stream().filter(je -> je.getName().equals(fileName)).findAny();
if(oentry.isPresent()) {
try {
JarEntry entry = oentry.get();
@ -215,19 +327,18 @@ public class JuliaAddon {
reader.read(classData);
reader.close();
Class<?> clazz = defineClass(name,classData,0,(int) entry.getSize());
loadedClasses.put(name, clazz);
classesChargees.put(name, clazz);
return clazz;
} catch (IOException e) {
System.err.println("Impossible de lire les donn<6E><6E>es du fichier "+oentry.get().getName()+" dans le jar "+jar.getName()+" je peux vous dire adieu je pense");
e.printStackTrace();
erreur("Impossible de lire les données du fichier "+oentry.get().getName()+" dans le jar "+jar.getName()+" je peux vous dire adieu je pense",e);
}
}
JuliaClassLoader cl = laboratory.findClassLoader(name);
JuliaClassLoader cl = laboratoire.trouveLeClassLoader(name);
if(cl != null)
try {
return cl.loadClass(name, resolve);
}catch(ClassNotFoundException e) {
throw new ClassNotFoundException("J'ai délégué le chargement de la classe mais j'aurais pas du ... cette classe ne doit pas exister dans l'addon "+cl.getAddon().getName(),e);
throw new ClassNotFoundException("J'ai délégué le chargement de la classe mais j'aurais pas du ... cette classe ne doit pas exister dans l'addon "+cl.getAddon().getNom(),e);
}
else
try {
@ -238,73 +349,113 @@ public class JuliaAddon {
}
public void registerCommandsAndEvents() {
registeredCommands = new HashSet<>();
registeredEvents = new HashSet<>();
/**
* Enregistre dans le laboratoire toutes les commandes et
* les events présents dans les classes de ce classloader
*/
public void enregistrerCommadesEtEvents() {
commandesEnregistrees = new HashSet<>();
eventsEnregistrees = new HashSet<>();
Set<Method> toWatch = new HashSet<>();
Collections.addAll(toWatch, JuliaAddon.this.addonClass.getMethods());
for (Class<?> clazz : JuliaAddon.this.addon.searchPath())
for (Class<?> clazz : JuliaAddon.this.addon.filles())
try {
Collections.addAll(toWatch, loadClass(clazz.getName()).getMethods());//loadClass(clazz.getName()) poir eviter les references cassees par les classloaders
} catch (SecurityException | ClassNotFoundException e) {
e.printStackTrace();
System.err.println("L'addon "+JuliaAddon.this.addonClass.getName()+" spécifie la classe "+clazz.getName());
erreur("L'addon "+JuliaAddon.this.addonClass.getName()+" spécifie la classe "+clazz.getName(),e);
}
for(Method m : toWatch) {
Command c = m.getAnnotation(Command.class);
Discord d = m.getAnnotation(Discord.class);
Commande c = m.getAnnotation(Commande.class);
EventDiscord d = m.getAnnotation(EventDiscord.class);
if(c != null) {
registeredCommands.add(m);
laboratory.registerCommand(m, c);
commandesEnregistrees.add(m);
laboratoire.enregistrerCommande(m, c);
}else if(d != null) {
registeredEvents.add(m);
laboratory.registerEvent(m, d);
eventsEnregistrees.add(m);
laboratoire.enregistrerEvent(m, d);
}
}
}
public void unregisterCommandsAndEvents() {
for(Method m : registeredCommands) {
Command c = m.getAnnotation(Command.class);
registeredCommands.add(m);
laboratory.unregisterCommand(m, c);
/**
* Désenregistre du laboratoire associé toutes les commandes et les
* events présents dans les classes de ce classloader.
*/
public void desenregistrerCommandesEtEvents() {
for(Method m : commandesEnregistrees) {
Commande c = m.getAnnotation(Commande.class);
commandesEnregistrees.add(m);
laboratoire.desenregistrerCommande(c);
}
for(Method m : registeredEvents) {
Discord d = m.getAnnotation(Discord.class);
registeredEvents.add(m);
laboratory.unregisterEvent(m, d);
for(Method m : eventsEnregistrees) {
eventsEnregistrees.add(m);
laboratoire.desenregistrerEvent(m);
}
registeredCommands.clear();
registeredEvents.clear();
commandesEnregistrees.clear();
eventsEnregistrees.clear();
}
/**
* Récupère l'addon pour lequel est créé ce classloader.
*/
public JuliaAddon getAddon() {
return JuliaAddon.this;
}
public Set<Method> getCommandesEnregistrees() {
return commandesEnregistrees;
}
public static final class JarWithMultipleAddonsException extends Exception{
public Set<Method> getEventsEnregistrees() {
return eventsEnregistrees;
}
}
/**
* Une exception trahissant plusieurs addons dans un jeu d'entrées jar.
*
* L'exception contient des données qui devraient suffire à sélectionner de manière
* plus judicieuse les entrées afin d'eviter le problème.
* @author mysaa
*
*/
public static final class JarAuxMultiplesAddonsException extends Exception{
private static final long serialVersionUID = -7557959851687207287L;
/**
* Partition des entrées qui devrait séparer correctement les addons
*/
Set<Set<JarEntry>> addonsEntries;
public JarWithMultipleAddonsException(Set<Set<JarEntry>> addonsEntries) {
public JarAuxMultiplesAddonsException(Set<Set<JarEntry>> addonsEntries) {
this.addonsEntries = addonsEntries;
}
public Set<Set<JarEntry>> getAddonsEntries() {
public Set<Set<JarEntry>> entreesDeLAddon() {
return addonsEntries;
}
}
public static final class DummyLaboratory extends Julia.Laboratory{
/**
* Un laboratoire factice prévu pour observer les fichier jar sans les enregistrer nul part.
* @author mysaa
*
*/
public static final class LaboFactice extends Julia.Laboratoire{
public DummyLaboratory() {
Julia.theJulia().super(Character.MIN_VALUE);
public LaboFactice() {
Julia.theJulia().super("");
}
}
@Override
public String toString() {
return pkg + ":" + version;
}
}

View File

@ -1,60 +0,0 @@
package com.bernard.juliabot;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.MessageChannel;
public class JuliaErrPrintStream extends OutputStream {
StringBuilder tas = new StringBuilder(2000);// Les 2000 caractères de Discord
Lock cadenas;
MessageChannel logChannel;
public static final int maxlength = 1900;
public JuliaErrPrintStream(MessageChannel logChan) {
this.cadenas = new ReentrantLock();
this.logChannel = logChan;
}
@Override
public void write(int i) throws IOException {
tas.append((char)(i));
}
@Override
public void flush() {
}
public void flushMessage() {
if(tas.length()==0)return;
this.cadenas.lock();
String str = tas.toString();
for(int i = 0; i*maxlength < tas.length();i++) {
MessageBuilder builder = new MessageBuilder();
builder.appendCodeBlock(str.substring(i*maxlength, Math.min((i+1)*maxlength,tas.length())), "");
logChannel.sendMessage(builder.build()).queue();
}
tas = new StringBuilder();
this.cadenas.unlock();
}
PrintStream ps = null;
public PrintStream printStream() {
if(ps == null)
ps = new PrintStream(this);
return ps;
}
}

View File

@ -0,0 +1,184 @@
package com.bernard.juliabot;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.MessageChannel;
/**
* Stream de caractères dirigé vers un canal discord.
*
* Les utilisateurs doivent appller flushMessage() afin que soit envoyé le message.
* @author mysaa
*
*/
public class PrintStreamDiscord extends OutputStream {
/**
* Un buffer stoquant les prochains caractères à afficher.
*/
StringBuilder tas = new StringBuilder(2000);// Les 2000 caractères de Discord
/**
* Cadenas bloquant le write pendant la création du message discord.
*/
Lock cadenas;
/**
* Le channel dans lequel envoyer tous les caractères.
*/
MessageChannel logChannel;
/**
* La longueur maximale d'un message sur discord.
*/
public static final int maxlength = 1900;
/**
* Crée un canal dirigé vers le canal spécifié.
* @param logChan Un channel discord pour lequel julia à un accès en écriture.
*/
public PrintStreamDiscord(MessageChannel logChan) {
this.cadenas = new ReentrantLock();
this.logChannel = logChan;
}
@Override
public void write(int i) throws IOException {
cadenas.lock();
try {
tas.append((char)(i));
} finally {cadenas.unlock();}
}
@Override
public void flush() {
}
/**
* Envoie le buffer dans le salon discord.
*/
public synchronized void flushMessage() {
if(tas.length()==0)return;
this.cadenas.lock();
String str = tas.toString();
for(int i = 0; i*maxlength < tas.length();i++) {
MessageBuilder builder = new MessageBuilder();
builder.appendCodeBlock(str.substring(i*maxlength, Math.min((i+1)*maxlength,tas.length())), "");
logChannel.sendMessage(builder.build()).queue();
}
tas = new StringBuilder();
this.cadenas.unlock();
}
/**
* Une instance de printstream sauvegardée.
*/
PrintStream ps = null;
/**
* Crée si besoin une seule fois un PrintStream écrivant dans cet OutputStream.
*/
public PrintStream printStream() {
if(ps == null)
ps = new PrintStream(this);
return ps;
}
/**
* Envoie un message découpé en blocs dans le canal spécifié.
* Le message sera découpé en élements d'à peu près la taille max de message.
* @param message Le message à écrire, potentiellement long.
* @param logChannel Le canal de sortie.
*/
public static void logErrMessage(String message, MessageChannel logChannel) {
for(int i = 0; i*maxlength < message.length();i++) {
MessageBuilder builder = new MessageBuilder();
builder.appendCodeLine(message.substring(i*maxlength, Math.min((i+1)*maxlength,message.length())));
logChannel.sendMessage(builder.build()).queue();
builder = new MessageBuilder();
}
}
/**
* Envoie le message découpé en blocs dans le canal d'erreur de ce PrintStream.
* @param message Le message, potentiellement long, à afficher.
*/
public void errMessage(String message) {
logErrMessage(message, logChannel);
}
/**
* Log l'erreur d'entrée dans le canal spécifié. Le message est affiché en premier en texte
* brut, puis le Throwable est ensuite affiché dans un ou plusieurs blocs suivant sa taille.
* @param message Le message décrivant le contexte de l'erreur
* @param error L'erreur en elle-même
* @param logChannel Le canal dans lequel afficher l'erreur.
*/
public static void logErrMessageAndThrowable(String message, Throwable error, MessageChannel logChannel) {
StringWriter sw = new StringWriter();
error.printStackTrace(new PrintWriter(sw));
String errMessage = sw.toString();
if(errMessage.length()+message.length()+1<maxlength) {
MessageBuilder builder = new MessageBuilder();
builder.appendCodeLine(message);
builder.append('\n');
builder.appendCodeBlock(errMessage, "");
logChannel.sendMessage(builder.build()).queue();
}else {
Julia.theJulia().realErr().println("Leeeeeee Bricoleur");
// On doit faire du découpage.
String[] nommes = errMessage.split("\n");
MessageBuilder builder = new MessageBuilder();
builder.appendCodeLine(message);
builder.append('\n');
int depassement = message.length()+1;
StringBuilder reste = new StringBuilder();
for(String nm : nommes) {
if(depassement+nm.length()>=maxlength) {
// On envoie maintenant
builder.appendCodeBlock(reste.toString(), "");
logChannel.sendMessage(builder.build()).queue();
builder = new MessageBuilder();
reste = new StringBuilder();
depassement = 0;
}
if(nm.length() >= maxlength) { // Reste est nécessairement vide
// Pas le choix que d'envoyer avec découpage dégueulasse.
for(int i = 0; i*maxlength < nm.length();i++) {
builder.appendCodeBlock(nm.substring(i*maxlength, Math.min((i+1)*maxlength,nm.length())), "");
logChannel.sendMessage(builder.build()).queue();
builder = new MessageBuilder();
}
}else {
reste.append(nm+"\n");
depassement += nm.length();
}
}
builder.appendCodeBlock(reste.toString(), "");
logChannel.sendMessage(builder.build()).queue();
}
}
/**
* Log l'erreur d'entrée dans le canal de ce PrintStream. Le message est affiché en premier en texte
* brut, puis le Throwable est ensuite affiché dans un ou plusieurs blocs suivant sa taille.
* @param message Le message décrivant le contexte de l'erreur.
* @param error L'erreur en elle-même.
*/
public void errMessageAndThrowable(String message, Throwable error) {
logErrMessageAndThrowable(message, error, logChannel);
}
}

View File

@ -0,0 +1,93 @@
package com.bernard.juliabot.api;
import java.util.*;
/**
* Décrit des arguments bash d'une commande type bash.
*
* La commande est séparée en nommes, arguments et flags, ces deux derniers étant
* indépendant de leur position.
*
* Exemple: commande nomme1 nomme2 --flag1 --flag2 --arg1=val1 nomme3 --arg2=val2 --flag3
* @author mysaa
*
*/
public class ArgumentsComplexes {
List<String> nommes;
Map<String,String> arguments;
Set<String> flags;
/**
* Initialise cet argument avec les paramètres indiqués.
* @param nommes Les nommes, ordonnés de la commande
* @param arguments Map qui à chaque argument associe sa valeur.
* @param flags Ensemble non ordonné des flags.
*/
public ArgumentsComplexes(List<String> nommes, Map<String, String> arguments, Set<String> flags) {
this.nommes = nommes;
this.arguments = arguments;
this.flags = flags;
}
/**
* Renvoie la liste ordonnée des nommes.
* @return Liste des nommes.
*/
public List<String> getNommes() {
return Collections.unmodifiableList(nommes);
}
/**
* Renvoie une map qui à chaque argument spécifié associe sa valeur.
* @return La map des arguments.
*/
public Map<String, String> getArguments() {
return Collections.unmodifiableMap(arguments);
}
/**
* Renvoie l'ensemble des flags spécifiés.
* @return L'ensemble des flags.
*/
public Set<String> getFlags() {
return Collections.unmodifiableSet(flags);
}
/**
* Renvoie true si et seulement si le flag donné en paramètre a été spécifié.
* @param flag le flag a tester
* @return true ssi flag est spécifié.
*/
public boolean hasFlag(String flag) {
return flags.contains(flag);
}
/**
* Renvoie la valeur associée à l'argument «name», et null si l'argument n'a pas été spécifié.
* @param name le nom de l'argument.
* @return La valeur de l'argument ou null.
*/
public String getArgument(String name) {
return arguments.get(name);
}
/**
* Renvoie le n-ième nomme de la commande, le 0ème nomme correspondant typiquement au nom
* de la commande elle-même.
* @param pos L'indice du nomme.
* @return La valeur du nomme.
*/
public String getNomme(int pos) {
return nommes.get(pos);
}
/**
* Renvoie le nombre de nommes de la commande, utile pour tester si tous ont été spécifiés.
* @return Le nombre de nommes.
*/
public int getNommeCount() {
return nommes.size();
}
}

View File

@ -1,9 +1,16 @@
package com.bernard.juliabot.api;
import com.bernard.juliabot.Julia.Laboratory;
/**
* Une super-interface décrivant les capacités d'une requête recue.
* @author mysaa
*
*/
public interface CCommande {
public Laboratory getLabo();
/**
* L'ID du laboratoire dans lequel a été lancé la commande
* @return La chaine de caractère de l'ID
*/
public String getLaboId();
}

View File

@ -0,0 +1,60 @@
package com.bernard.juliabot.api;
import java.time.OffsetDateTime;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
/**
* Une CCommande décrivant une requète/commande envoyée depuis un message Discord.
* @author mysaa
*
*/
public interface CCommandeDiscord extends CCommandeString {
/**
* Renvoie le message qui a créé cette requête, tel que récupéré par la JDA.
*
* La consistance du message n'est pas assurée.
* @return Le message à l'origine de la requête.
*/
public Message getMessage();
/**
* Renvoie le canal dans lequel la requête a été formulée, tel que récupéré du JDA.
*
* La consistance du channel n'est pas assurée.
* @return Le canal de formulation de la requête.
*/
public MessageChannel getChannel();
/**
* Renvoie l'utilisateur ayant formulé cette requête, tel que récupéré par la JDA.
*
* La consistance de l'user n'est pas assurée.
* @return L'utilisateur à l'origine de la requête.
*/
public User getUser();
/**
* Renvoie la JDA qui a détécté l'évenement.
*
* @return La JDA initiante.
*/
public JDA getJDA();
/**
* Renvoie le contenu original du message ayant créé la requête.
* @return Le texte retiré de ses effets de style (markdown).
*/
public String getContentStripped();
/**
* Renvoie la date de récéption du message.
* @return La date de récéption du message.
*/
public OffsetDateTime getPostDate();
}

View File

@ -0,0 +1,23 @@
package com.bernard.juliabot.api;
/**
* Une CCommande décrite par une chaine de caractères.
* @author mysaa
*
*/
public interface CCommandeString extends CCommande{
/**
* La chaine de caractères décrivant la commande.
* @return La chaine sus-nommée.
*/
public String getStringCommand();
/**
* Renvoie les arguments complèxes associés à cette chaine de caractères.
* L'objet renvoyé peut être toujours le même ou généré à chaque fois.
* @return Les arguments complexes récupérés de la chaine de la commande.
*/
public ArgumentsComplexes getArguments();
}

View File

@ -1,15 +0,0 @@
package com.bernard.juliabot.api;
import java.lang.annotation.*;
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Command {
public String name();
public String[] aliases() default {};
public String description();
public String synopsis() default "";
public boolean admin();//TODO a remplacer par les permissions
}

View File

@ -1,45 +0,0 @@
package com.bernard.juliabot.api;
import java.util.*;
public class CommandArguments {
List<String> nommes;
Map<String,String> arguments;
Set<String> flags;
public CommandArguments(List<String> nommes, Map<String, String> arguments, Set<String> flags) {
this.nommes = nommes;
this.arguments = arguments;
this.flags = flags;
}
public List<String> getNommes() {
return nommes;
}
public Map<String, String> getArguments() {
return arguments;
}
public Set<String> getFlags() {
return flags;
}
public boolean hasTag(String tag) {
return flags.contains(tag);
}
public String getArgument(String name) {
return arguments.get(name);
}
public String getNomme(int pos) {
return nommes.get(pos);
}
public int getNommeCount() {
return nommes.size();
}
}

View File

@ -0,0 +1,41 @@
package com.bernard.juliabot.api;
import java.lang.annotation.*;
/**
* Annotation à appliquer à une méthode afin qu'elle réagisse aux commandes
* envoyées sur discord ou via le sysin.
*
* Tous les arguments de la méthodes doivent étendre
* {@link com.bernard.juliabot.api.CCommande}.
* @author mysaa
*
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Commande {
/**
* Le nom de la commande. Doit être à peu prêt unique, les nom plus
* «communs» doivent être mis dans les alias.
*/
public String name();
/**
* Tableau des alias possible de la commande, c'est moins grâve si
* il y a des doublons.
*/
public String[] alias() default {};
/**
* Une description d'une ligne environ de la commande, explique son effet.
*/
public String description();
/**
* Le synopsis de la commande dans un style proche des pages man.
*/
public String synopsis() default "";
/**
* true si la commande ne peut être éxecutée que par des admins.
*/
public boolean admin() default false;//TODO a remplacer par les permissions
}

View File

@ -1,11 +0,0 @@
package com.bernard.juliabot.api;
import java.lang.annotation.*;
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Discord {
public String description();
}

View File

@ -1,24 +0,0 @@
package com.bernard.juliabot.api;
import java.time.OffsetDateTime;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
public interface DiscordCCommande extends StringCCommande {
public Message getMessage();
public MessageChannel getChannel();
public User getUser();
public JDA getJDA();
public String getContentStripped();
public OffsetDateTime getPostDate();
}

View File

@ -0,0 +1,20 @@
package com.bernard.juliabot.api;
import java.lang.annotation.*;
/**
* Annotation à appliquer à une méthode pour indiquer qu'elle doit
* être appelée lorsque la JDA recoit des évenements (et que ces évenements sont
* bien destinés au bon laboratoire).
*
* Les méthodes doivent avoir un unique argument qui étende
* {@link net.dv8tion.jda.api.events.GenericEvent}.
* @author mysaa
*
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventDiscord {
public String description();
}

View File

@ -0,0 +1,69 @@
package com.bernard.juliabot.api;
import java.sql.Connection;
import com.bernard.juliabot.PrintStreamDiscord;
import static com.bernard.juliabot.Julia.theJulia;
import net.dv8tion.jda.api.JDA;
/**
* Anciennement appelée Trukilie, permet d'interragir avec Julia avec uniquement
* des appels statics, et donc depuis n'importe dans le programme.
* @author mysaa
*
*/
public class Julia {
/**
* Accès au JDA initialisé par Julia.
* @return La JDA.
*/
public static JDA jda() {
return theJulia().getJda();
}
/**
* Récupère la connection SQL à la base de données.
*
* Prévu pour que les juliaddons accèdent à leurs données.
* @return Une connection normalement ouverte.
*/
public static Connection juliaBDD() {
return theJulia().getJuliaBDD();
}
/**
* Envoie un message dans sysout et dans le canal «status» du Discord
* @param message Le message à envoyer, optimalement d'une seule ligne et assez court.
*/
public static void status(String message) {
System.out.println(message);
PrintStreamDiscord.logErrMessage(message, theJulia().statusTextChannel());
}
/**
* Envoie un message dans le syserr et dans le canal d'erreur du Discord
* @param message Le message décrivant l'erreur, optimalement d'une seule ligne et assez court.
*/
public static void erreur(String message) {
theJulia().realErr().println(message);
if(theJulia().getSyserr() != null)
theJulia().getSyserr().errMessage(message);
}
/**
* Envoie un message d'erreur avec un stacktrace dans le syserr et dans le canal d'erreur du Discord.
* @param message Le message décrivant l'erreur, optimalement d'une seule ligne et assez court.
* @param error L'erreur qui sera écrite à la suite du message, avec son stacktrace.
*/
public static void erreur(String message, Throwable error) {
theJulia().realErr().println(message);
error.printStackTrace(theJulia().realErr());
theJulia().getSyserr().errMessageAndThrowable(message, error);
}
}

View File

@ -1,40 +0,0 @@
package com.bernard.juliabot.api;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.bernard.juliabot.Julia;
public class JuliaConfig {
public static final String get(String name) {
try {
PreparedStatement st = Julia.theJulia().getJuliaDatabase().prepareStatement("SELECT * FROM juliaConfig WHERE strID=?");
st.setString(1, name);
ResultSet r = st.executeQuery();
if(r.getFetchSize() == 0)
throw new IllegalArgumentException("La configuration "+name+" n'existe pas ... veuillez la rentrer avant de la demander");
String value = r.getString("value");
return value;
} catch (SQLException e) {
e.printStackTrace();
throw new IllegalStateException("Impossible de faire des requetes à la bdd de julia. Est-elle initialisée au moins ?");
}
}
public static final void set(String name, String value) {
try {
PreparedStatement st = Julia.theJulia().getJuliaDatabase().prepareStatement("UPDATE juliaConfig SET value=? WHERE strID=?");
st.setString(1, value);
st.setString(2, value);
int r = st.executeUpdate();
if(r == 0)
throw new IllegalArgumentException("La configuration "+name+" n'a pas pu être modifiée ... existe-elle ?");
} catch (SQLException e) {
e.printStackTrace();
throw new IllegalStateException("Impossible de faire des requetes à la bdd de julia. Est-elle initialisée au moins ?");
}
}
}

View File

@ -7,22 +7,31 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Annotation à appliquer à une classe principale d'un Juliaddon.
* @author mysaa
*
*/
@Documented
@Retention(RUNTIME)
@Target(TYPE)
public @interface JuLIAddon {
public @interface Juliaddon {
/**
* Le nom de l'addon
*/
String name();
String nom();
/**
* La version de l'addon
*
* Par défaut: La version spécifiée dans le nom du fichier de l'addon sera utilisé.
*/
String version() default "";
/**
* Différentes classes, en plus de celle , dans laquelle chercher des méthodes définissant des événements ou des commandes
*
* Par défaut: Aucune fille, donc uniquement cette classe.
*/
Class<?>[] searchPath() default {};
Class<?>[] filles() default {};
/**
* La liste des personnes ayant dévellopé cet addon
*/

View File

@ -1,9 +0,0 @@
package com.bernard.juliabot.api;
public interface StringCCommande extends CCommande{
public String getStringCommand();
public CommandArguments getArguments();
}

View File

@ -1,19 +0,0 @@
package com.bernard.juliabot.api;
import java.sql.Connection;
import com.bernard.juliabot.Julia;
import net.dv8tion.jda.api.JDA;
public class Trukilie {
public static JDA jda() {
return Julia.theJulia().getJda();
}
public static Connection juliaDB() {
return Julia.theJulia().getJuliaDatabase();
}
}

View File

@ -1,60 +1,92 @@
package com.bernard.juliabot.internaddon;
import static com.bernard.juliabot.api.Julia.erreur;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import com.bernard.juliabot.CommandCalled;
import org.simmetrics.StringDistance;
import org.simmetrics.metrics.StringDistances;
import com.bernard.juliabot.AppelACommande;
import com.bernard.juliabot.CommandeSurchargee;
import com.bernard.juliabot.Julia;
import com.bernard.juliabot.Julia.Laboratory;
import com.bernard.juliabot.Julia.Laboratoire;
import com.bernard.juliabot.JuliaAddon;
import com.bernard.juliabot.api.Command;
import com.bernard.juliabot.api.CommandArguments;
import com.bernard.juliabot.api.Discord;
import com.bernard.juliabot.api.DiscordCCommande;
import com.bernard.juliabot.api.JuLIAddon;
import com.bernard.juliabot.api.StringCCommande;
import com.bernard.juliabot.JuliaAddon.JuliaClassLoader;
import com.bernard.juliabot.api.ArgumentsComplexes;
import com.bernard.juliabot.api.CCommandeDiscord;
import com.bernard.juliabot.api.CCommandeString;
import com.bernard.juliabot.api.Commande;
import com.bernard.juliabot.api.EventDiscord;
import com.bernard.juliabot.api.Juliaddon;
import com.thedeanda.lorem.LoremIpsum;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import net.dv8tion.jda.internal.utils.tuple.Pair;
@JuLIAddon(name="internaddon", devs = "Bernard", version="beta")
/**
* Cet addon est chargé de force dans chaque laboratoire. Il est celui qui permet de charger/décharger
* les autres addons et surtout de comprendre les commandes depuis les events reçus.
*
* Cet addon est fait pour être le seul qui accède directement aux méthodes de {@link com.bernard.juliabot.Julia}, sans
* passer par {@link com.bernard.juilabot.api.Julia}.
* @author mysaa
*/
@Juliaddon(nom="internaddon", devs = "Bernard", version="beta")
public class Internaddon {
public static final String COMMANDEUR = "!!";
@Discord(description = "LE catcheur des commandes discords. Les messages recus sont lus puis executes ici si ce sont des commandes")
public void getCommand(MessageReceivedEvent e) {
/**
* Le catcheur d'evnts qui reconnais les commandes et demande à JuL'IA de les éxecuter.
*/
@EventDiscord(description = "LE catcheur des commandes discords. Les messages recus sont lus puis executes ici si ce sont des commandes")
public void getCommande(MessageReceivedEvent e) {
if(e.getMessage().getContentRaw().startsWith(COMMANDEUR)) {
String name = e.getMessage().getContentRaw().split(" ")[0].substring(COMMANDEUR.length());
String nom = e.getMessage().getContentRaw().split(" ")[0].substring(COMMANDEUR.length());
Laboratory labo = Julia.theJulia().getLaboratoires().get(Julia.theJulia().getLecouteur().getLabo(e));//Récupére le labo de l'évent
Laboratoire labo = Julia.theJulia().getLecouteur().getLabo(e);//Récupére le labo de l'évent
InternalddonCCommande ccommande = new InternalddonCCommande(e,labo);
try {
CommandCalled called = labo.executeCommand(name, ccommande);
System.out.println(called);
AppelACommande appelee = labo.executer(nom, ccommande);
System.out.println(appelee);
}catch(IllegalArgumentException ex) {
// La commande n'existe pas
e.getChannel().sendMessage("Moi pas connaitre `"+name+"`. Vous apprendre moi ?").queue();
e.getChannel().sendMessage("Moi pas connaitre `"+nom+"`. Vous apprendre moi ?").queue();
}
}
}
@Command(name = "julia", aliases="juju", description = "Répond à l'appel", admin = true)
public void julia(DiscordCCommande commande) {
/**
* Une commande pour tester le ping de julia.
*/
@Commande(name = "julia", alias="juju", synopsis = "!!julia", description = "Répond à l'appel", admin = true)
public void julia(CCommandeDiscord commande) {
User user = commande.getUser();
MessageChannel channel = commande.getChannel();
String[] julias = new String[]{"Présent !", "Présente !", "Présentbleu !", "Présentavion !", "Présent(e) !", "Présent(bleu) !",
@ -66,62 +98,186 @@ public class Internaddon {
channel.sendMessage(out).queue();
}
@Command(name = "help", aliases= {"ausecours","?","allahuakbar"}, description = "Laissez-vous aider par le meilleur bot du monde", admin = true)
public void help(DiscordCCommande commande) {
StringBuilder s = new StringBuilder();
s.append("```");
public static final DateFormat addonFileTimeDf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.FRANCE);
Laboratory l = commande.getLabo();
s.append("Vous étes actuellement dans le laboratoire '"+l.getL()+"'\n");
s.append("Liste des addons chargés :\n");
for(JuliaAddon e : l.getLoadedAddons().values())
s.append("\t- "+e.getName()+" en version "+e.getVersion()+"\n");
s.append("\nListe des commandes chargées :\n");
for(String c : l.getLoadedCommands().keySet()) {
s.append("\t- "+c);
String collected = l.getAliases().entrySet().stream()
.filter(e->e.getValue().equals(c))
.map(Map.Entry::getKey)
.collect(Collectors.joining(", "));
if(!collected.isEmpty())
s.append(" a.k.a "+collected);
if(l.getLoadedCommands().get(c).getDesc() != null)
s.append(" :\n\t\t"+l.getLoadedCommands().get(c).getDesc());
s.append('\n');
/**
* Liste les addons chargés ainsi que les commandes enregistrées.
*/
@Commande(name = "aide", alias= {"help","ausecours","?","allahuakbar"}, synopsis = "!!help [commandName | pkg] --labo=str", description = "Laissez-vous aider par le meilleur bot du monde", admin = true)
public void aide(CCommandeDiscord commande) {
ArgumentsComplexes args = commande.getArguments();
String lab = args.getArguments().getOrDefault("labo", commande.getLaboId());
Laboratoire l = Julia.theJulia().getLaboratoires().get(lab);
EmbedBuilder msg = new EmbedBuilder();
if(args.getNommeCount() == 1) {
// On veut décrire un laboratoire.
msg.setTitle(String.format("Description du laboratoire «%s»", l.getL()));
for(JuliaAddon e : l.getAddonsCharges()) {
String commandes = e.getLoader(l)
.getCommandesEnregistrees()
.stream()
.map(m -> m.getAnnotation(Commande.class))
.map(c -> c.name())
.collect(Collectors.joining("; "));
msg.addField(
String.format("%s en version %s (%s)",e.getNom(),e.getVersion(),e.getPkg()),
String.format("Liste des commandes disponibles: %s", commandes),
false);
}
s.append("```");
} else if (args.getNommeCount() == 2) {
// Compréhension du premier argument das cet ordre:
// pkg > lastPkgName > commandName > alias > addon
commande.getChannel().sendMessage(s).queue();
String nom = args.getNomme(1);
String pkg = null;
CommandeSurchargee cs = null;
Set<String> pkgs = Julia.theJulia().pkgSet();
if(pkgs.contains(nom))
pkg=nom; // C'est déjà un pkg donc bon
else {
Optional<String> ops = pkgs.stream()
.filter(p -> p.toLowerCase(Locale.FRENCH).endsWith("."+nom.toLowerCase(Locale.FRENCH)))
.findAny();
if(ops.isPresent())
pkg = ops.get();
else {
// On teste une commande OU un alias.
cs = l.commandeAssociee(nom);
if(cs == null)
pkg = comprendreAddon(nom);
}
}
// On a «parse» le premier argument.
if(pkg != null) {
// On veut décrire un package.
String pkgf = pkg;
Optional<JuliaAddon> addonO = l.getAddonsCharges()
.stream()
.filter(a -> a.getPkg().equals(pkgf))
.findAny();
if(addonO.isEmpty()) {
// L'addon n'est pas chargé dans le laboratoire.
repondre(commande, "Vous voulez de l'aide sur l'internaddon "+pkg+" mais "
+ "il n'est pas chargé dans ce laboratoire ...").complete();
return;
}
JuliaAddon addon = addonO.get();
msg.setTitle(String.format("Description du juliaddon %s (%s)",addon.getNom(),addon.getPkg()));
msg.setDescription(String.format(
"Cet addon est chargé dans le laboratoire %s en version %s. "
+ "Il est extrait du fichier %s chargé pour la dernière fois %s",
l.getL(),addon.getVersion(),addon.getFichierJar().getName(), addonFileTimeDf.format(Date.from(Instant.ofEpochMilli(addon.getDerniereModif())))));
JuliaClassLoader loader = addon.getLoader(l);
for(Method m : loader.getEventsEnregistrees()) {
EventDiscord ed = m.getAnnotation(EventDiscord.class);
if(ed==null) {
erreur("Inconsistance dans le classLoader de l'addon "+addon+" : "
+ "la méthode "+m.getName()+" est enregistrée comme event.");
repondre(commande, "Une erreur de classes est survenue. Contactez un développeur du Julia.");
return;
}
@Command(name = "blabla", aliases= {}, description = "Pour vous prouver que je parle", admin = true)
public void blabla(DiscordCCommande commande) {
msg.addField(
m.getName() ,
ed.description(),
false);
}
msg.addBlankField(false);
for(Method m : loader.getCommandesEnregistrees()) {
Commande ed = m.getAnnotation(Commande.class);
if(ed==null) {
erreur("Inconsistance dans le classLoader de l'addon "+addon+" : "
+ "la méthode "+m.getName()+" est enregistrée comme commande");
repondre(commande, "Une erreur de classes est survenue. Contactez un développeur du Julia.");
return;
}
msg.addField(
(ed.alias().length==0)
? String.format("Commande %s", ed.name())
: String.format("Commande %s (alias %s)", ed.name(),String.join(" ou ",ed.alias())) ,
ed.description(),
false);
}
} else if (cs != null){
// On veut décrire une commande.
Method executeur = cs.getExecuteur(commande);
if(! (executeur.getDeclaringClass().getClassLoader() instanceof JuliaClassLoader)) {
repondre(commande, "Impossible de récupérer l'addon correspondant à cette commande !");
return;
}
JuliaAddon addon = ((JuliaClassLoader)executeur.getDeclaringClass().getClassLoader()).getAddon();
msg.setTitle("Commande "+cs.getCommande().name());
msg.setDescription(cs.getDesc());
msg.addField("Synopsis",cs.getCommande().synopsis()
+ (cs.getCommande().admin()?"\nCette commande requiert les droits d'administrateur.":""),false);
msg.addField("Addon",
String.format("Cette commande est décrite par l'addon %s en version %s décrit "
+ "dans le fichier %s à la date %s. La méthode à executer est la méthode %s "
+ "de la classe %s.", addon.getNom(),addon.getVersion(),addon.getFichierJar().getName(),
addonFileTimeDf.format(Date.from(Instant.ofEpochMilli(addon.getDerniereModif()))),executeur.getName(),executeur.getDeclaringClass().getName()),false);
if(cs.getCommande().alias().length != 0)
msg.addField("Aliases", String.join(", ", cs.getCommande().alias()), false);
} else {
// On a pas compris ce qui devait être décrit.
repondre(commande, "Je n'ai pas compris ce sur quoi je devais vous aider ...").complete();
return;
}
}
//msg.setFooter("Réponse à "+authorName(commande));// -> C'est moche
repondre(commande, msg.build()).complete();
}
/**
* Fait que julia publie un gros bloc de texte de type Lorem ipsum.
*/
@Commande(name = "blabla", alias= {}, description = "Pour vous prouver que je parle", admin = true)//TODO ajouter des options peut-être.
public void blabla(CCommandeDiscord commande) {
Message m = commande.getMessage();
m.getChannel().sendMessage("```"+LoremIpsum.getInstance().getWords(150, 200)+"```").queue();
m.delete();
}
@Command(name = "tuerBebePhoque", aliases= {}, description = "Pour votre plaisir", admin = true)
public void tuerBebePhoque(DiscordCCommande commande) {
/**
* Renvoie la photo d'un bébé phoque mort pour soulager les développeurs stréssés.
*/
@Commande(name = "tuerBebePhoque", alias= {}, description = "Pour votre plaisir", admin = true)//TODO remplir un peu cette méthode.
public void tuerBebePhoque(CCommandeDiscord commande) {
Message m = commande.getMessage();
m.getChannel().sendMessage("http://toni.mi.free.fr/vanhebdo/actions/phoques/im_phoque_coupGourdinAssomme_Sang.jpg").queue();
}
@Discord(description = "Arthur, par-ce que vous n'étes pas qu'une caricature")
public void shutUp(MessageReceivedEvent e) {
if(e.getAuthor().getIdLong() == 187677269626585089L) {//Yukimyo = L'Orchidoclaste
e.getChannel().sendMessage(e.getAuthor().getAsMention() +" dis du caca").queue();
}
}
@Command(name = "args", description = "Décompose les messages à la Discord'", admin = true)
public void args(DiscordCCommande commande) {
CommandArguments ca = commande.getArguments();
/**
* Commande de test de la décomposition des commandes. //XXX: À supprimer
* @see ArgumentsComplexes
*/
@Commande(name = "args", description = "Décompose les messages à la Discord'", admin = true)
public void args(CCommandeDiscord commande) {
ArgumentsComplexes ca = commande.getArguments();// Réparer la commande.
StringBuilder s = new StringBuilder();
s.append("```"+commande.getStringCommand()+"```");
@ -135,8 +291,11 @@ public class Internaddon {
}
@Command(name="guilds", description = "Liste les guildes disponibles",admin=true)
public void guilds(DiscordCCommande commande) {
/**
* Liste les guildes auquel Julia à accès.
*/
@Commande(name="guildes", alias= {"guilds"}, description = "Liste les guildes disponibles",admin=true)
public void guildes(CCommandeDiscord commande) {
MessageBuilder mb = new MessageBuilder();
mb.append("Liste des serveurs :\n");
Julia.theJulia().getJda().getGuilds()
@ -146,64 +305,234 @@ public class Internaddon {
commande.getChannel().sendMessage(mb.build()).complete();
}
@Command(name="update",description = "Met a jour le dossier des addons",admin=true)
public void update(StringCCommande commande) {
/**
* Met à jour la liste des addons disponibles, en lisant le dossier juliaddons.
*/
@Commande(name="maj", alias= {"update","miseAJour"},description = "Met a jour le dossier des addons",admin=true)
public void maj(CCommandeString commande) {
Julia.theJulia().update();
Julia.theJulia().addonsMaj();
if(CCommandeDiscord.class.isInstance(commande.getClass()))
repondre((CCommandeDiscord)commande, "La liste des addons disponibles a été mise à jour.");
}
@Command(name="load",description = "Charge l'addon dans ce laboratoire",admin=true)
public void load(DiscordCCommande commande) {
/**
* Charge l'addon spécifié en ligne de commande dans le laboratoire d'appel.
*/
@Commande(name="charger", alias= {"load"}, description = "Charge l'addon dans ce laboratoire",admin=true)
public void charger(CCommandeDiscord commande) {
CommandArguments cc = commande.getArguments();
ArgumentsComplexes cc = commande.getArguments();
String addonName = cc.getNomme(1);
String version = cc.getNomme(2);
String lab = cc.getArguments().getOrDefault("labo", commande.getLaboId());
Laboratory l = commande.getLabo();
if(cc.getNommeCount()<2) {
repondre(commande, "Il vous faut préciser l'addon que vous voulez charger !").complete();
return;
}
String addonPkg = comprendreAddon(cc.getNomme(1));
if(addonPkg == null) {
repondre(commande, String.format("Je ne trouve pas d'addon correspondant à «%s» ...", cc.getNomme(1))).complete();
return;
}
String version = null;
if(cc.getNommeCount()<3)
version = Julia.theJulia().latest(addonPkg);
else
version = cc.getNomme(2);
if(! Julia.theJulia().checkVersion(addonPkg,version)) {
repondre(commande, "Je ne trouve pas la version "+version+" pour l'addon "+addonPkg).complete();
return;
}
Laboratoire l = Julia.theJulia().getLaboratoires().get(lab);
String out = "Terminé avec succés";
try {
l.loadAddon(addonName, version);
l.loadAddon(addonPkg, version);
}catch (Exception e) {
out = "```"+e.getClass().toString()+"\n"+e.getMessage()+"```";
}
commande.getChannel().sendMessage(out);
repondre(commande, "Impossible de charger l'addon spécifié ! Contactez un admin julia.").complete();
erreur("Tentative de chargement depuis l'internaddon", e);
return;
}
@Command(name="unload",description = "Décharge l'addon de ce laboratoire",admin=true)
public void unload(DiscordCCommande commande) {
repondre(commande, String.format("L'addon %s a été chargé dans sa version %s dans le laboratoire %s", addonPkg, version, lab)).complete();
CommandArguments cc = commande.getArguments();
}
String addonName = cc.getNomme(1);
/**
* Décharge l'addon spécifié du laboratoire d'appel.
*/
@Commande(name="decharger", alias= {"unload"}, description = "Décharge l'addon de ce laboratoire",admin=true)
public void decharger(CCommandeDiscord commande) {
ArgumentsComplexes cc = commande.getArguments();
String lab = cc.getArguments().getOrDefault("labo", commande.getLaboId());
if(cc.getNommeCount()<2) {
repondre(commande, "Il vous faut préciser l'addon que vous voulez charger !").complete();
return;
}
String addonPkg = comprendreAddon(cc.getNomme(1));
Laboratoire l = Julia.theJulia().getLaboratoires().get(lab);
Laboratory l = commande.getLabo();
String out = "Terminé avec succés";
try {
l.unloadAddon(addonName);
l.dechargerAddon(addonPkg);
}catch (Exception e) {
out = "```"+e.getClass().toString()+"\n"+e.getMessage()+"```";
repondre(commande, "Impossible de décharger l'addon spécifié ! Contactez un admin julia.").complete();
erreur("Tentative de déchargement depuis l'internaddon", e);
return;
}
commande.getChannel().sendMessage(out);
repondre(commande, String.format("L'addon %s a été déchargé du laboratoire %s", addonPkg, lab)).complete();
}
@Command(name="caca",description = "Déclanche une exception ... Faut bien tester, hein !",admin=true)
public void caca(DiscordCCommande commande) {
/**
* Décharge puis recharge l'addon du laboratoire spécifié.
*/
@Commande(name="recharger", alias= {"reload"}, description = "Recharge l'addon de ce laboratoire",admin=true)
public void recharger(CCommandeDiscord commande) {
ArgumentsComplexes cc = commande.getArguments();
String lab = cc.getArguments().getOrDefault("labo", commande.getLaboId());
if(cc.getNommeCount()<2) {
repondre(commande, "Il vous faut préciser l'addon que vous voulez charger !").complete();
return;
}
String addonPkg = comprendreAddon(cc.getNomme(1));
Laboratoire l = Julia.theJulia().getLaboratoires().get(lab);
try {
l.rechargeAddon(addonPkg, null);
}catch (Exception e) {
repondre(commande, "Impossible de recharger l'addon spécifié ! Contactez un admin julia.").complete();
erreur("Tentative de rechargement depuis l'internaddon", e);
return;
}
repondre(commande, String.format("L'addon %s a été rechargé dans le laboratoire %s", addonPkg, lab)).complete();
}
/**
* Une commande qui lance une IllegalStateException à des fins de tests.
*/
//XXX: À supprimer ou cacher
@Commande(name="caca",description = "Déclanche une exception ... Faut bien tester, hein !",admin=true)
public void caca(CCommandeDiscord commande) {
throw new IllegalStateException("Nan mais tout va bien ... je fait ce qu'on m'a demandé ^^");
}
StringDistance addonMetric = StringDistances.levenshtein();
final float maxAcceptableDistance = 8.0f;
/**
* Renvoie le pkg d'un addon disponible qui correspond le mieux au nom spécifié.
* @param nom Le nom, typiquement donné par l'utilisateur.
* @return Un pkg d'un internaddon, normalement chargeable.
*/
public String comprendreAddon(String nom) {
Set<String> pkgs = Julia.theJulia().pkgSet();
if(pkgs.contains(nom))
return nom; // C'est déjà un pkg donc bon
Optional<String> ops = pkgs.stream().filter(p -> p.endsWith("."+nom)).findAny();
if(ops.isPresent())
return ops.get();
public class InternalddonCCommande implements DiscordCCommande,StringCCommande{
List<Pair<String, Float>> listProches = pkgs.stream()
.map(s -> Pair.of(s, addonMetric.distance(s.substring(s.lastIndexOf(".")+1), nom)))
.filter(e -> e.getRight() < maxAcceptableDistance)
.sorted((a,b) -> Float.compare(a.getRight(), b.getRight()))
.collect(Collectors.toList());
if(listProches.size()>1)
if(listProches.get(0).getRight() < listProches.get(1).getRight())
return listProches.get(0).getLeft();
else
return null;
else if(listProches.size()==1)
return listProches.get(0).getLeft();
else
return null;
}
/**
* Fait répondre à Julia sa réponse du message source
* @param commande La ccommande de requête
* @param message Le message à renvoyer
* @return L'action d'envoi de la réponse.
*/
public MessageAction repondre(CCommandeDiscord commande, MessageEmbed message) {
return commande.getMessage().replyEmbeds(message);
}
/**
* Fait répondre à Julia sa réponse du message source
* @param commande La ccommande de requête
* @param message Le message à renvoyer
* @return L'action d'envoi de la réponse.
*/
public MessageAction repondre(CCommandeDiscord commande, Message message) {
return commande.getMessage().reply(message);
}
/**
* Fait répondre à Julia sa réponse du message source
* @param commande La ccommande de requête
* @param message Le contenu du message à renvoyer
* @return L'action d'envoi de la réponse.
*/
public MessageAction repondre(CCommandeDiscord commande, String message) {
return commande.getMessage().reply(message).mentionRepliedUser(true);
}
public String authorName(CCommandeDiscord commande) {
if(commande.getMessage().isFromGuild())
return ((GuildChannel)commande.getChannel()).getGuild().retrieveMember(commande.getUser()).complete().getEffectiveName();
else
return commande.getUser().getName();
}
/**
* Une simple implémentation de {@link CCommandeDiscord} et de {@link CCommandeString} afin d'appeler
* les commandes depuis les events jda.
* @author mysaa
*
*/
public class InternalddonCCommande implements CCommandeDiscord,CCommandeString{
Message m;
Laboratory labo;
Laboratoire labo;
public InternalddonCCommande(MessageReceivedEvent e,Laboratory labo) {
/**
* Crée une simple commande.
* @param e L'évenement trahissant la récéption de la commande.
* @param labo Le laboratoire associé à la commande.
*/
public InternalddonCCommande(MessageReceivedEvent e,Laboratoire labo) {
m = e.getMessage();
this.labo = labo;
}
@ -214,7 +543,7 @@ public class Internaddon {
}
@Override
public CommandArguments getArguments() {
public ArgumentsComplexes getArguments() {
return parseCommandArguments(getStringCommand());
}
@ -249,14 +578,20 @@ public class Internaddon {
}
@Override
public Laboratory getLabo() {
return labo;
public String getLaboId() {
return labo.getL();
}
}
public static CommandArguments parseCommandArguments(String raw) {
/**
* Comprend une ligne de commande, en séparant les nommes, flags et arguments en
* un seul objet.
* @param raw La ligne de commande à parser
* @return L'objet arguments complexes contenant toutes les données séparées.
*/
public static ArgumentsComplexes parseCommandArguments(String raw) {
List<String> nommes = new ArrayList<String>();
Map<String,String> arguments = new HashMap<>();
@ -328,7 +663,7 @@ public class Internaddon {
}
return new CommandArguments(nommes, arguments, flags);
return new ArgumentsComplexes(nommes, arguments, flags);
}

View File

@ -1,36 +0,0 @@
package com.bernard.juliabot.internaddon;
import java.io.IOException;
import java.io.OutputStream;
import java.util.function.Consumer;
import net.dv8tion.jda.api.entities.Message;
public class UnstableMessage extends OutputStream{
MConsumer thisConsumer = new MConsumer();
public UnstableMessage(Message m) {
// TODO Auto-generated constructor stub
m.editMessage(""/* a préciser */).queue(thisConsumer, (e) -> {
});
}
@Override
public void write(int arg0) throws IOException {
// TODO Auto-generated method stub
}
public class MConsumer implements Consumer<Message>{
@Override
public void accept(Message arg0) {
// TODO Auto-generated method stub
}
}
}

8
upload.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
gradle jar
gradle internalddon
gradle apiJar
scp -r -P 27182 -4 build/libs/Juliabot.jar root@bernard.com.de:/srv/julia/
scp -r -P 27182 -4 build/libs/JuliabotInternaddon_beta.jar root@bernard.com.de:/srv/julia/juliaddons/