From 1ab160b5f640c01b870880b2f721bb84198f59a4 Mon Sep 17 00:00:00 2001 From: Mysaa Date: Mon, 24 May 2021 15:34:36 +0200 Subject: [PATCH] =?UTF-8?q?Premier=20commit=20-=20Inclusion=20dans=20le=20?= =?UTF-8?q?syst=C3=A8me=20git?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + src/com/bernard/julatex/Juliatex.java | 345 ++++++++++++++++++ src/com/bernard/julatex/JuliatexConfig.java | 123 +++++++ .../julatex/StopEverythingException.java | 10 + .../bernard/julatex/UserConfigManager.java | 134 +++++++ 5 files changed, 617 insertions(+) create mode 100644 .gitignore create mode 100644 src/com/bernard/julatex/Juliatex.java create mode 100644 src/com/bernard/julatex/JuliatexConfig.java create mode 100644 src/com/bernard/julatex/StopEverythingException.java create mode 100644 src/com/bernard/julatex/UserConfigManager.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0efb711 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.settings +.gradle +.project +.classpath +bin/ diff --git a/src/com/bernard/julatex/Juliatex.java b/src/com/bernard/julatex/Juliatex.java new file mode 100644 index 0000000..f4dd97c --- /dev/null +++ b/src/com/bernard/julatex/Juliatex.java @@ -0,0 +1,345 @@ +package com.bernard.julatex; + +import java.awt.Color; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.ProcessBuilder.Redirect; +import java.util.HashMap; +import java.util.Map; +import java.util.PrimitiveIterator.OfInt; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import com.bernard.juliabot.api.Command; +import com.bernard.juliabot.api.JuLIAddon; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.MessageBuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.User; + +@JuLIAddon(name = "juliatex", devs="Mysaa", version = "20w33a") +public class Juliatex { + + //TODO globally add a check-null parsing to every method call + + public static Map userLogModes = new HashMap<>(); + static { + UserConfigManager.retieveUserLogModes(); + } + + public static final String imageStorageRoot = "/var/julia/juliatex/images/"; + public static final String pngLatexPath = "/opt/pnglatex"; + public static final String wipText = "Je suis super motivé mais les développeurs ont préféré perdre leur temps à écrire un texte disant qu'il ont pas fait leur taff plutot que de réellement faire le contenu ... Ah ben Bravo !"; + public static final LogMode DEFAULT_LOGMODE = LogMode.RISKY; + + @Command(name = "£atex", admin = false, description = "Affiche une image a partir du latex") + public void latex(Message message, User user, MessageChannel channel) { + try { + if(!userLogModes.containsKey(user.getIdLong())) + userLogModes.put(user.getIdLong(), DEFAULT_LOGMODE); + System.out.println("Ca fonctionne ? : "+user); + String formula = message.getContentRaw().substring(7); + JuliatexConfig config = UserConfigManager.getUserSelectedConfig(user); + + File f = new File(imageStorageRoot, Long.toString(hash(formula, config)) + ".png"); + String error = latexToPng(formula,f.getAbsolutePath(),config); + if(!error.isEmpty()) { + MessageBuilder errMsg = new MessageBuilder(); + errMsg.append("Bon, le latex était pas super content, voila ce qu'il m'a renvoyé"); + errMsg.appendCodeBlock(error, ""); + Message msg = errMsg.build(); + Long mid = msg.getIdLong(); + System.out.println("L'ID : "+mid); + channel.sendMessage(msg).complete().delete().completeAfter(20, TimeUnit.SECONDS); + //channel.deleteMessageById(mid).completeAfter(20, TimeUnit.SECONDS); + } + if(!f.exists()) { + channel.sendMessage("Le moteur latex m'a renvoye une image neant, du coup, je vous l'affiche dans ce message. Bon visionnage (J'imagine) !"); + return; + } + channel.sendFile(f).complete(); + } catch (IOException e) { + System.err.println("AQueFichierProbleme"); + } catch (InterruptedException e) { + System.err.println("AQueThreadProbleme"); + } catch (StopEverythingException e) { + notifyUser("Un message d'arret géneral a été lancé (surement du a une erreur) ... donc ... j'écoute. (Du moins je vous le fait croire)", user, channel); + System.err.println("Erreur générale lancée : user="+user+" ; message="+message+" ; channel="+channel); + } + + } + + @Command(name = "£atech", admin = true, description = "Règle les paramètres utilisateurs de JuL'IAtex") + public void latech(Message message, User user, MessageChannel channel) { + String[] args = parseArguments(message.getContentRaw()); + if(args.length < 2) { + notifyUser("La commende £atech prends au moins un argument (l'action � �ffectuer)", user, channel); + return; + } + switch (args[1]) { + case "add":// "configName" = "dumpedText" |||||| "configName" ["parentName"] + case "new": + case "ajouter": + case "nouveau": + + if(args.length < 3) { + notifyUser("La commande '£atech "+args[1]+"' prends au moins un argument (le nom de la nouvelle config)", user, channel); + return; + } + JuliatexConfig model = UserConfigManager.getDefaultConfig(); + switch (args.length) { + case 4: + model = UserConfigManager.getUserConfig(user, args[3]); + if(model == null){ + notifyUser("Je ne connais pas la configuration '"+args[3]+"'", user, channel); + return; + } + case 3: + if(!Regexer.configName.matcher(args[2]).matches()){ + notifyUser("'"+args[2]+"' n'est pas un nom de configuration valide (la regex c'est '"+Regexer.configName.pattern()+"' pour ceux que ca intéresse)", user, channel); + return; + } + UserConfigManager.addConfig(user, args[2],model); + break; + case 5: + + if(!args[3].equals("=")){ + notifyUser("La commende '£atech "+args[1]+"' ne s'utilise pas comme ca ! (voir '£atex help')", user, channel); + return; + } + notifyUser("Je gere pas encore les dumps ... déso pas déso", user, channel); + + break; + + default: + notifyUser("La commende '£atech "+args[0]+"' ne s'utilise pas comme ca ! (voir '£atex help')", user, channel); + return; + } + + break; + case "rm":// "configName + case "remove": + case "delete": + case "suppr":// "configName + case "supprimer": + case "enlever": + + if(args.length != 3) { + notifyUser("La commande '£atech "+args[1]+"' prends un argument (le nom de la config à supprimer)", user, channel); + return; + } + JuliatexConfig target = UserConfigManager.getUserConfig(user, args[2]); + if(target == null){ + notifyUser("Je ne connais pas la configuration '"+args[2]+"'", user, channel); + return; + } + UserConfigManager.delConfig(user, args[2]); + + break; + case "use": + case "take": // "configName" + case "utiliser": + case "prendre": + + if(args.length != 3) { + notifyUser("La commande '£atech "+args[1]+"' prends un argument (le nom de la config à utiliser)", user, channel); + return; + } + JuliatexConfig cible = UserConfigManager.getUserConfig(user, args[2]); + if(cible == null){ + notifyUser("Je ne connais pas la configuration '"+args[2]+"'", user, channel); + return; + } + UserConfigManager.useConfig(user, cible); + + break; + case "change": + case "modify": //"configName" "param"="value" "param"="value" "param"="value" + case "alter": + case "changer": + case "modifier": + case "alterer": + + notifyUser(wipText, user, channel); + + if(args.length < 3) { + notifyUser("La commande '£atech "+args[1]+"' prends au moins un argument (le nom de la config à changer)", user, channel); + return; + } + + JuliatexConfig config = UserConfigManager.getUserConfig(user, args[2]); + + + + for(int paramIndex = 3;args.length > paramIndex;paramIndex++) { + + OfInt arg = args[paramIndex].chars().iterator(); + + String field = ""; + String value = ""; + char c; + while((c = (char)(int)arg.next()) != '=') + field += c; + while(arg.hasNext()) + value += (char)(int)arg.next(); + try { + System.out.println("Set config file "+args[2]+" from \""+field+"\" with \""+ value +"\""); + config.setField(field,value); + }catch(IllegalArgumentException e) { + notifyUser(e.getMessage(), user, channel); + } + + } + try { + UserConfigManager.saveConfig(user,args[2],config,false); + } catch (IOException e) { + notifyUser("A que je n'arive pas a modifier la config, les changements ne seront valable que si JuL'IA ne redémarre pas ... contactez un développeur (même si ils doivent déjà être prévenus)", user, channel); + } + notifyUser("C'est fini !!!", user, channel); + + + break; + case "dump": // configName + case "cracher": + + notifyUser(wipText, user, channel); + + if(args.length < 3) { + notifyUser("La commande '£atech "+args[1]+"' prends au moins un argument (le nom de la config à changer)", user, channel); + return; + } + + JuliatexConfig configToDump = UserConfigManager.getUserConfig(user, args[2]); + + notifyUser("Et JuL'IA cracha '"+args[2]+"' : ```"+configToDump.toString()+"```", user, channel); + + break; + + case "lacommandedinitialisationdelaquelleilestdurdetrouverlenomauhasard": + + notifyUser("Ceci est une commande de debug ...", user, channel); + + userLogModes = new HashMap<>(); + UserConfigManager.selecteds = new HashMap<>(); + + //XXX UserConfigManager.saveSelectedConfigs(); + //XXX UserConfigManager.saveUserLogModes(); + + break; + + case "help": + case "aide": + case "ausecours": + case "?": + case "42": + case "quoi?": + + EmbedBuilder builder = new EmbedBuilder(); + builder.setTitle("Voila les commandes de £atech disponible"); + builder.addField("add|new|ajouter|nouveau","Crée une nouvelle config\n" + +"\t\t\"configName\" = \"dumpedText\"\n" + +"\t\t\"configName\" [\"parentName\"]",false); + builder.addField("rm|remove|suppr|delete|supprimer|enlever","Supprime une config\n" + +"\t\t\"configName\"",false); + builder.addField("use|take|utiliser|prendre","Sélectionne une config\n" + +"\t\t\"configName\"",false); + builder.addField("change|modify|alter|changer|modifier|alterer","Modifie une config\n" + +"\t\t\"configName\" \"param\"=\"value\" \"param\"=\"value\" \"param\"=\"value\" etc... ",false); + builder.addField("dump|cracher","Renvoie un texte décrivant la config (pour le partage)\n" + +"\t\t\"configName\"",false); + builder.addField("help|aide|ausecours|?|42|quoi?","Je suis sur que vous savez ce que ca veut dire !\n" + +"\t\t Vous voulez mettre quoi comme paramètres à un help ?!",false); + + builder.setColor(Color.getHSBColor((float)Math.random(), 1.0f, 1.0f)); + + channel.sendMessage(builder.build()).complete(); + + break; + default: + notifyUser("Je ne connais pas cette action : '"+args[0]+"' ... Je crois que tu t'es trompé", user, channel); + return; + } + } + + public static final void notifyUser(String problem,User user,MessageChannel channel) { + MessageBuilder builder = new MessageBuilder(); + builder.append(user); + builder.append(" : "); + builder.append(problem); + if(channel == null) + channel = user.openPrivateChannel().complete(); + channel.sendMessage(builder.build()).complete(); + } + + public static final String[] parseArguments(String args) { + return args.split(" ");//TODO space escape + } + + public static final String latexToPng(String formula, String outputFile, JuliatexConfig config) + throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder( + new String[] { "/opt/pnglatex", "-b", config.getBgColor(), "-B", config.getBorderColor(), "-F", + config.getFgColor(), "-f", formula, "-d", Integer.toString(config.getDpi(), 10), "-o", + outputFile, "-P", Integer.toString(config.getPadding(), 10), "-m", + Integer.toString(config.getMargin(), 10), config.getEnvironnement().isEmpty()?"":"-e", config.getEnvironnement(), config.getHeader().isEmpty()?"":"-H", + config.getHeader(), "-O", config.optimize() ? "1" : "0", config.getPackages().isEmpty()?"":"-p "+ config.getPackages() }); + System.out.println("Launching command : "+String.join(" ", pb.command())); + File tempErrFile = File.createTempFile(outputFile, "err"); + tempErrFile.createNewFile(); + pb.redirectError(tempErrFile); + pb.redirectOutput(Redirect.INHERIT); + Process p = pb.start(); + p.waitFor(); + FileInputStream fis = new FileInputStream(tempErrFile); + byte[] data = new byte[(int) tempErrFile.length()]; + fis.read(data); + fis.close(); + tempErrFile.delete(); + + return new String(data, "UTF-8"); + } + + public static boolean log(User user,String message) { + LogMode logMode = userLogModes.get(user.getIdLong()); + if(logMode == LogMode.NORMAL) { + notifyUser(message, user, null);//notify the user itself + }else if(logMode == LogMode.SAFELY) { + throw new StopEverythingException(); + } + return logMode == LogMode.RISKY; + } + + + + + + //////// FNV-1 HASH FUNCTION (très rapide,non cryprographique) //////////// + + public static final long FNV_1_START = 0xCBF29CE484222325L; + public static final long FNV_1_XOR = 0x100000001B3L; + + + public static long hash(String formula, JuliatexConfig config) { + long hash = FNV_1_START; + for (byte b : formula.getBytes()) { + hash *= FNV_1_XOR; + hash ^= b & 0xFF; + } + for (byte b : config.toString().getBytes()) { + hash *= FNV_1_XOR; + hash ^= b & 0xFF; + } + return hash; + } + + public static enum LogMode{ + RISKY,NORMAL,SAFELY; + } + + public static class Regexer { + public static final Pattern configName = Pattern.compile("[-a-zA-Z0-9_]+"); + } +} \ No newline at end of file diff --git a/src/com/bernard/julatex/JuliatexConfig.java b/src/com/bernard/julatex/JuliatexConfig.java new file mode 100644 index 0000000..11da139 --- /dev/null +++ b/src/com/bernard/julatex/JuliatexConfig.java @@ -0,0 +1,123 @@ +package com.bernard.julatex; + +import java.io.Serializable; + +public class JuliatexConfig implements Serializable{ + + private static final long serialVersionUID = 4384323400837152293L; + + int padding; + int margin; + String packages; + String bgColor,fgColor,borderColor; + int dpi; + String environnement; + String header; + boolean optimize; + + public JuliatexConfig(int padding, int margin, String packages, String bgColor, String fgColor, String borderColor, + int dpi, String environnement, String header, boolean optimize) { + super(); + this.padding = padding; + this.margin = margin; + this.packages = packages; + this.bgColor = bgColor; + this.fgColor = fgColor; + this.borderColor = borderColor; + this.dpi = dpi; + this.environnement = environnement; + this.header = header; + this.optimize = optimize; + } + + public static long getSerialversionuid() { + return serialVersionUID; + } + + public int getPadding() { + return padding; + } + + public int getMargin() { + return margin; + } + + public String getPackages() { + return packages; + } + + public String getBgColor() { + return bgColor; + } + + public String getFgColor() { + return fgColor; + } + + public String getBorderColor() { + return borderColor; + } + + public int getDpi() { + return dpi; + } + + public String getEnvironnement() { + return environnement; + } + + public String getHeader() { + return header; + } + + public boolean optimize() { + return optimize; + } + + public void setField(String field, String value) { + + switch(field) {//TDOD traduce in every language + special names + case "padding": + padding = Integer.parseUnsignedInt(value, 10); + break; + case "margin": + margin = Integer.parseUnsignedInt(value, 10); + break; + case "packages": + packages = value; + break; + case "bgColor": + bgColor = value;//TODO super color parsing (pour les trois) + break; + case "fgColor": + fgColor = value; + break; + case "borderColor": + borderColor = value; + break; + case "dpi": + dpi = Integer.parseUnsignedInt(value, 10); + break; + case "environnement": + environnement = value; + break; + case "header": + header = value; + break; + case "optimize": + if(value == "1" || value == "true" || value == "oui") + optimize = true; + else if(value == "1" || value == "true" || value == "oui") + optimize = false; + else + throw new IllegalArgumentException("La valeur de "+field+" doit etre comprehensible comme un boolean !"); + break; + default: + throw new IllegalArgumentException("Malgrès tous mes efforts, je n'arrive pas a comprendre ce qui doit etre modifié ... essayez autre chose que "+field); + } + + } + + + +} diff --git a/src/com/bernard/julatex/StopEverythingException.java b/src/com/bernard/julatex/StopEverythingException.java new file mode 100644 index 0000000..f4b9964 --- /dev/null +++ b/src/com/bernard/julatex/StopEverythingException.java @@ -0,0 +1,10 @@ +package com.bernard.julatex; + +public class StopEverythingException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 2080456590173832156L; + +} diff --git a/src/com/bernard/julatex/UserConfigManager.java b/src/com/bernard/julatex/UserConfigManager.java new file mode 100644 index 0000000..0a47df7 --- /dev/null +++ b/src/com/bernard/julatex/UserConfigManager.java @@ -0,0 +1,134 @@ +package com.bernard.julatex; + +import static com.bernard.julatex.Juliatex.log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import net.dv8tion.jda.api.entities.User; + +public class UserConfigManager { + + public static Map selecteds = new HashMap<>(); + + public static final String varFolder = "/var/julia/juliatex/"; + public static final String juliatexConfigExtension = "juliatexconfig"; + public static final String defaultConfigName = "default"; + + public static final JuliatexConfig addConfig(User user,String configName,JuliatexConfig model) { + try { + saveConfig(user, configName, model, true); + } catch (IOException e) { + e.printStackTrace(); + if(log(user,"Une erreur interne I/O est survenue, contactez les développeurs. Immediatement... C'EST UN ORDRE !!!")); + return getDefaultConfig(); + } + return getUserConfig(user, configName); + } + + public static final JuliatexConfig useConfig(User user,JuliatexConfig cible) { + selecteds.remove(user.getIdLong());//TODO voir si'il y a rien de plus propre genre un selected.replace(); + selecteds.put(user.getIdLong(),cible); + return cible; + } + + public static final void delConfig(User user,String targetName) { + File file = getUserConfigFilePath(user, targetName).toFile(); + if(file.exists()) + file.delete(); + else + log(user,"La config à supprimer n'existe pas .... C'est problêmatique !"); + } + + public static final JuliatexConfig getUserSelectedConfig(User user){ + long id = user.getIdLong(); + if(selecteds.containsKey(id)) + return selecteds.get(id); + else if(log(user,"Vous n'avez pas de config sélectionnée .... faut me dire comment vous avez fait !")) + return getDefaultConfig(); + else + return null; + } + + public static final JuliatexConfig getUserConfig(User user,String configName){ + File configFile = getUserConfigFilePath(user, configName).toFile(); + if(!configFile.exists()) + configFile = getDefaultConfigFilePath(configName).toFile(); + if(!configFile.exists()) + if(log(user,"La configuration demandée n'existe pas.... Je suis une IA qui est censé deviné votre configuration à partir du nom mais ..... la flemme !")) + return getDefaultConfig(); + else + return null; + FileInputStream fis = null; + try { + fis = new FileInputStream(configFile); + JuliatexConfig config = parseConfig(fis); + return config; + } catch (FileNotFoundException e) { + // TODO Auto-generated truc + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + }finally { + if(fis != null) + try { + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + public static final JuliatexConfig getDefaultConfig() { + return new JuliatexConfig(3, 0, "", "Transparent", "White", "", 3000, "", "", false); + } + + public static final JuliatexConfig parseConfig(InputStream is) throws IOException, ClassNotFoundException { + ObjectInputStream ois = new ObjectInputStream(is); + return (JuliatexConfig) ois.readObject(); + } + + public static final void dumpConfig(OutputStream os,JuliatexConfig config) throws IOException { + ObjectOutputStream oos = new ObjectOutputStream(os); + oos.writeObject(config); + } + + private static Path getUserConfigFilePath(User user,String configName) { + return Paths.get(varFolder, "users",Long.toString(user.getIdLong(), 10),configName+"."+juliatexConfigExtension); + } + + private static Path getDefaultConfigFilePath(String configName) { + return Paths.get(varFolder, "templates",configName+"."+juliatexConfigExtension); + } + + public static void saveConfig(User user, String configName, JuliatexConfig config,boolean createIfDontExists) throws IOException { + File saveFile = getUserConfigFilePath(user, configName).toFile(); + if(!(createIfDontExists || saveFile.exists())) + throw new IllegalArgumentException("Le fichier "+saveFile.getAbsolutePath()+" n'existe pas et ne doit pas être créé"); + saveFile.createNewFile(); + FileOutputStream fos = new FileOutputStream(saveFile); + dumpConfig(fos, config); + fos.close(); + } + + public static void retieveUserLogModes() { + // TODO Auto-generated method stub + + } + +}