| @ -0,0 +1,6 @@ | |||||
| # | |||||
| # https://help.github.com/articles/dealing-with-line-endings/ | |||||
| # | |||||
| # These are explicitly windows files and should use crlf | |||||
| *.bat text eol=crlf | |||||
| @ -0,0 +1,200 @@ | |||||
| # ---> Java | |||||
| # Compiled class file | |||||
| *.class | |||||
| # Log file | |||||
| *.log | |||||
| # BlueJ files | |||||
| *.ctxt | |||||
| .idea | |||||
| # Mobile Tools for Java (J2ME) | |||||
| .mtj.tmp/ | |||||
| # Package Files # | |||||
| *.jar | |||||
| *.war | |||||
| *.nar | |||||
| *.ear | |||||
| *.zip | |||||
| *.tar.gz | |||||
| *.rar | |||||
| # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | |||||
| hs_err_pid* | |||||
| # ---> Eclipse | |||||
| .metadata | |||||
| bin/ | |||||
| tmp/ | |||||
| *.tmp | |||||
| *.bak | |||||
| *.swp | |||||
| *~.nib | |||||
| local.properties | |||||
| .settings/ | |||||
| .loadpath | |||||
| .recommenders | |||||
| # External tool builders | |||||
| .externalToolBuilders/ | |||||
| # Locally stored "Eclipse launch configurations" | |||||
| *.launch | |||||
| # PyDev specific (Python IDE for Eclipse) | |||||
| *.pydevproject | |||||
| # CDT-specific (C/C++ Development Tooling) | |||||
| .cproject | |||||
| # CDT- autotools | |||||
| .autotools | |||||
| # Java annotation processor (APT) | |||||
| .factorypath | |||||
| # PDT-specific (PHP Development Tools) | |||||
| .buildpath | |||||
| # sbteclipse plugin | |||||
| .target | |||||
| # Tern plugin | |||||
| .tern-project | |||||
| # TeXlipse plugin | |||||
| .texlipse | |||||
| # STS (Spring Tool Suite) | |||||
| .springBeans | |||||
| # Code Recommenders | |||||
| .recommenders/ | |||||
| # Annotation Processing | |||||
| .apt_generated/ | |||||
| .apt_generated_test/ | |||||
| # Scala IDE specific (Scala & Java development for Eclipse) | |||||
| .cache-main | |||||
| .scala_dependencies | |||||
| .worksheet | |||||
| # Uncomment this line if you wish to ignore the project description file. | |||||
| # Typically, this file would be tracked if it contains build/dependency configurations: | |||||
| .project | |||||
| # ---> Maven | |||||
| target/ | |||||
| pom.xml.tag | |||||
| pom.xml.releaseBackup | |||||
| pom.xml.versionsBackup | |||||
| pom.xml.next | |||||
| release.properties | |||||
| dependency-reduced-pom.xml | |||||
| buildNumber.properties | |||||
| .mvn/timing.properties | |||||
| # https://github.com/takari/maven-wrapper#usage-without-binary-jar | |||||
| .mvn/wrapper/maven-wrapper.jar | |||||
| # ---> JetBrains | |||||
| # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider | |||||
| # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 | |||||
| # User-specific stuff | |||||
| .idea/**/workspace.xml | |||||
| .idea/**/tasks.xml | |||||
| .idea/**/usage.statistics.xml | |||||
| .idea/**/dictionaries | |||||
| .idea/**/shelf | |||||
| # Generated files | |||||
| .idea/**/contentModel.xml | |||||
| # Sensitive or high-churn files | |||||
| .idea/**/dataSources/ | |||||
| .idea/**/dataSources.ids | |||||
| .idea/**/dataSources.local.xml | |||||
| .idea/**/sqlDataSources.xml | |||||
| .idea/**/dynamic.xml | |||||
| .idea/**/uiDesigner.xml | |||||
| .idea/**/dbnavigator.xml | |||||
| # Gradle | |||||
| .idea/**/gradle.xml | |||||
| .idea/**/libraries | |||||
| # Gradle and Maven with auto-import | |||||
| # When using Gradle or Maven with auto-import, you should exclude module files, | |||||
| # since they will be recreated, and may cause churn. Uncomment if using | |||||
| # auto-import. | |||||
| .idea/artifacts | |||||
| .idea/compiler.xml | |||||
| .idea/jarRepositories.xml | |||||
| .idea/modules.xml | |||||
| .idea/*.iml | |||||
| .idea/modules | |||||
| *.iml | |||||
| *.ipr | |||||
| # CMake | |||||
| cmake-build-*/ | |||||
| # Mongo Explorer plugin | |||||
| .idea/**/mongoSettings.xml | |||||
| # File-based project format | |||||
| *.iws | |||||
| # IntelliJ | |||||
| out/ | |||||
| .classpath | |||||
| # mpeltonen/sbt-idea plugin | |||||
| .idea_modules/ | |||||
| # JIRA plugin | |||||
| atlassian-ide-plugin.xml | |||||
| # Cursive Clojure plugin | |||||
| .idea/replstate.xml | |||||
| # Crashlytics plugin (for Android Studio and IntelliJ) | |||||
| com_crashlytics_export_strings.xml | |||||
| crashlytics.properties | |||||
| crashlytics-build.properties | |||||
| fabric.properties | |||||
| # Editor-based Rest Client | |||||
| .idea/httpRequests | |||||
| # Android studio 3.1+ serialized cache file | |||||
| .idea/caches/build_file_checksums.ser | |||||
| # ---> Gradle | |||||
| .gradle | |||||
| **/build/ | |||||
| !src/**/build/ | |||||
| # Ignore Gradle GUI config | |||||
| gradle-app.setting | |||||
| # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) | |||||
| !gradle-wrapper.jar | |||||
| # Cache of project | |||||
| .gradletasknamecache | |||||
| # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 | |||||
| # gradle/wrapper/gradle-wrapper.properties | |||||
| updateYoshiBot.sh | |||||
| # Resources | |||||
| rsc/audio/* | |||||
| !rsc/audio/.gitkeep | |||||
| rsc/PrivateJdaBuilderString.txt | |||||
| rsc/RedditCredentials.properties | |||||
| @ -0,0 +1,39 @@ | |||||
| # YoshiBot # | |||||
| Ein in Java geschriebener Discordbot, der lustige Sachen kann. | |||||
| Das Git-Projekt befindet sich unter http://yannicpunktdee.de:3000/yannic/YoshiBot.git. | |||||
| ## Einrichtung ## | |||||
| Der Bot ist hauptsächlich in Java geschrieben mit Gradle als Build-Tool. | |||||
| Zusätzlich wird Python3 mit dem Paket gTTS verwendet für alles was mit Text-To-Speech zu tun hat. | |||||
| Für die Annotationen wie ```@Getter``` oder ```@SneakyThrows``` wird das Lombok-Plugin für Intellij benötigt. | |||||
| Mit Gradle sind die Bibliotheken JDA für die Discord-Schnittstelle (https://github.com/DV8FromTheWorld/JDA) und | |||||
| LavaPlayer (https://github.com/sedmelluq/lavaplayer) für Audioangelegenheiten einzubinden. Dies ist aber bereits im | |||||
| Gradle build script (```app/build.gradle```) enthalten. | |||||
| Als Entwicklungsumgebung eignet sich z.B. IntelliJ IDEA. | |||||
| Zum Einrichten einfach das Projekt mit Git clonen und in Intellij öffnen. | |||||
| Damit sich der Bot online schalten kann wird noch die Datei ```rsc/PrivateJdaBuilderString.txt``` benötigt, die den | |||||
| Geheimschlüssel der Discord-Anwendung enthält. Diesen bekommt man mitgeteilt, sobald man eine Discord-Anwendung erstellt | |||||
| über https://discord.com/developers/applications und der Bot auf den Server eingeladen wurde. | |||||
| Weiterhin muss die Guild-ID der Server-Guild in der ```Config.properties``` angepasst werden. | |||||
| Für Pfadangaben in der ```Config.properties``` achte darauf, dass diese Ordner auch existieren und dass Verzeichnisse auch unter | |||||
| Windows mit ```/``` und nicht mit ```\ ``` geschrieben werden und darauf enden. | |||||
| Nun nur noch die Main-Methode starten oder wie folgt builden und ausführen und der Bot läuft. | |||||
| **Warnung** Der Bot benutzt das temp-Verzeichnis (Ordner ```yoshibot```) des Rechners, auf dem er ausgeführt wird und legt dort evtl viele Dateien | |||||
| ab, die er selbst nicht löscht. Also entweder muss das Verzeichnis von Zeit zu Zeit geleert oder der Rechner neu gestartet werden. | |||||
| ## Build ## | |||||
| Zum Exportieren der Applikation führe den Befehl ```gradlew clean build``` auf Windows oder für Linux ```gradle clean build``` | |||||
| im Root Verzeichnis aus. Die fertige Jar liegt dann in ```app/build/libs```. Füge dieser noch im selben Verzeichnis den | |||||
| rsc-Ordner mitsamt Inhalt hinzu. Starte die Applikation mit ```java-jar app.jar``` am besten im Hintergrund oder per ```screen``` | |||||
| unter Linux, damit der Bot permanent läuft. | |||||
| @ -0,0 +1,55 @@ | |||||
| /* | |||||
| * This file was generated by the Gradle 'init' task. | |||||
| * | |||||
| * This generated file contains a sample Java application project to get you started. | |||||
| * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle | |||||
| * User Manual available at https://docs.gradle.org/6.8.3/userguide/building_java_projects.html | |||||
| */ | |||||
| plugins { | |||||
| // Apply the application plugin to add support for building a CLI application in Java. | |||||
| id 'application' | |||||
| } | |||||
| repositories { | |||||
| // Use JCenter for resolving dependencies. | |||||
| jcenter() | |||||
| } | |||||
| dependencies { | |||||
| // Use JUnit test framework. | |||||
| testImplementation 'junit:junit:4.13' | |||||
| // This dependency is used by the application. | |||||
| implementation 'com.google.guava:guava:29.0-jre' | |||||
| implementation group: 'org.json', name: 'json', version: '20210307' | |||||
| implementation 'net.dv8tion:JDA:4.2.0_247' | |||||
| implementation 'com.sedmelluq:lavaplayer:1.3.73' | |||||
| implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.0' | |||||
| implementation 'org.apache.commons:commons-text:1.9' | |||||
| implementation "net.dean.jraw:JRAW:1.1.0" | |||||
| compileOnly 'org.projectlombok:lombok:1.18.16' | |||||
| annotationProcessor 'org.projectlombok:lombok:1.18.16' | |||||
| } | |||||
| mainClassName = 'de.yannicpunktdee.yoshibot.main.Main' | |||||
| application { | |||||
| // Define the main class for the application. | |||||
| mainClass = "$mainClassName" | |||||
| } | |||||
| jar { | |||||
| manifest { | |||||
| attributes "Main-Class": "$mainClassName" | |||||
| } | |||||
| from { | |||||
| configurations.runtimeClasspath.findAll({ !it.path.endsWith(".pom") }).collect { it.isDirectory() ? it : zipTree(it) } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,33 @@ | |||||
| package de.yannicpunktdee.yoshibot.audio; | |||||
| import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; | |||||
| import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; | |||||
| import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; | |||||
| import com.sedmelluq.discord.lavaplayer.track.AudioTrack; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import de.yannicpunktdee.yoshibot.utils.Logger; | |||||
| public class AudioLoadResultHandlerImpl implements AudioLoadResultHandler { | |||||
| @Override | |||||
| public void trackLoaded(AudioTrack track) { | |||||
| YoshiBot.getInstance().audioPlayer.playTrack(track); | |||||
| } | |||||
| @Override | |||||
| public void playlistLoaded(AudioPlaylist playlist) { | |||||
| Logger.logWarning("Aktuell kann noch keine Playlist abgespielt werden."); | |||||
| } | |||||
| @Override | |||||
| public void noMatches() { | |||||
| Logger.logError("Nothing found"); | |||||
| } | |||||
| @Override | |||||
| public void loadFailed(FriendlyException exception) { | |||||
| Logger.logError("Loading failed"); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,31 @@ | |||||
| package de.yannicpunktdee.yoshibot.audio; | |||||
| import com.sedmelluq.discord.lavaplayer.player.event.AudioEvent; | |||||
| import com.sedmelluq.discord.lavaplayer.player.event.AudioEventListener; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import lombok.Getter; | |||||
| import net.dv8tion.jda.api.managers.AudioManager; | |||||
| public class AudioPlayerListener implements AudioEventListener { | |||||
| private final AudioManager audioManager; | |||||
| @Getter | |||||
| private static boolean isPlayingTrack = false; | |||||
| public AudioPlayerListener(AudioManager audioManager) { | |||||
| this.audioManager = audioManager; | |||||
| } | |||||
| @Override | |||||
| public void onEvent(AudioEvent event) { | |||||
| if(event.player.getPlayingTrack() == null) { | |||||
| event.player.stopTrack(); | |||||
| isPlayingTrack = false; | |||||
| YoshiBot.getInstance().joinVoiceChannelWithMostMembers(); | |||||
| } else isPlayingTrack = true; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,39 @@ | |||||
| package de.yannicpunktdee.yoshibot.audio; | |||||
| import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; | |||||
| import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; | |||||
| import net.dv8tion.jda.api.audio.AudioSendHandler; | |||||
| import java.nio.ByteBuffer; | |||||
| public class AudioSendHandlerImpl implements AudioSendHandler { | |||||
| private final AudioPlayer audioPlayer; | |||||
| private AudioFrame lastFrame; | |||||
| public AudioSendHandlerImpl(AudioPlayer audioPlayer) { | |||||
| this.audioPlayer = audioPlayer; | |||||
| } | |||||
| // | |||||
| @Override | |||||
| public boolean canProvide() { | |||||
| lastFrame = audioPlayer.provide(); | |||||
| return lastFrame != null; | |||||
| } | |||||
| @Override | |||||
| public ByteBuffer provide20MsAudio() { | |||||
| return ByteBuffer.wrap(lastFrame.getData()); | |||||
| } | |||||
| /** | |||||
| * {@inheritDoc} | |||||
| */ | |||||
| @Override | |||||
| public boolean isOpus() { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,177 @@ | |||||
| package de.yannicpunktdee.yoshibot.command; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import de.yannicpunktdee.yoshibot.utils.Logger; | |||||
| import de.yannicpunktdee.yoshibot.utils.Resources; | |||||
| import net.dv8tion.jda.api.EmbedBuilder; | |||||
| import net.dv8tion.jda.api.entities.Message.Attachment; | |||||
| import net.dv8tion.jda.api.entities.MessageEmbed; | |||||
| import net.dv8tion.jda.api.entities.VoiceChannel; | |||||
| import java.awt.*; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStreamReader; | |||||
| import java.util.List; | |||||
| import java.util.UUID; | |||||
| import java.util.concurrent.CompletableFuture; | |||||
| import java.util.concurrent.ExecutionException; | |||||
| import java.util.stream.Collectors; | |||||
| /** | |||||
| * Abstrakte Superklasse für alle Kommandos. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public abstract class YoshiCommand { | |||||
| protected final String[] requiredArguments = {}; | |||||
| /** | |||||
| * Der Kontext mit dem das Kommando aufgerufen wurde. | |||||
| */ | |||||
| protected YoshiCommandContext context; | |||||
| public static String resourceToDelete = null; | |||||
| /** | |||||
| * Erzeugt ein neues Kommando, führt es aber noch nicht aus. Es wird ermittelt, ob die Argumentenkombination valide | |||||
| * ist und das isOk-Flag gesetzt. Im Fehlerfall wird eine Fehleremeldung spezifiziert. | |||||
| * | |||||
| * @param context Der Kontext mit dem das Kommando aufgerufen wurde. | |||||
| */ | |||||
| public YoshiCommand(YoshiCommandContext context) { | |||||
| this.context = context; | |||||
| } | |||||
| /** | |||||
| * Führt das Kommando aus. | |||||
| * | |||||
| * @return True, wenn Ausführung erfolgreich. False, wenn Ausführung fehlgeschlagen. Fehlermeldung wird in | |||||
| * errorMessage spezifiziert. | |||||
| */ | |||||
| public boolean execute() { | |||||
| if (!context.containsArguments(requiredArguments)) { | |||||
| sendErrorMessage("Fehlende Argumente"); | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| protected final void sendMessage(String message) { | |||||
| EmbedBuilder eb = new EmbedBuilder(); | |||||
| eb.setColor(Color.pink); | |||||
| eb.setDescription(message); | |||||
| context.getEvent().getTextChannel().sendMessage(eb.build()).queue(); | |||||
| } | |||||
| protected final void sendFile(File file, String description) { | |||||
| EmbedBuilder eb = new EmbedBuilder(); | |||||
| eb.setColor(Color.pink); | |||||
| if (description != null) eb.setDescription(description); | |||||
| context.getEvent().getTextChannel().sendFile(file).queue(); | |||||
| } | |||||
| protected final void sendInfoMessage(String message) { | |||||
| EmbedBuilder eb = new EmbedBuilder(); | |||||
| eb.setTitle("INFO"); | |||||
| eb.setColor(Color.blue); | |||||
| eb.setDescription(message); | |||||
| context.getEvent().getTextChannel().sendMessage(eb.build()).queue(); | |||||
| } | |||||
| protected final void sendErrorMessage(String message) { | |||||
| EmbedBuilder eb = new EmbedBuilder(); | |||||
| eb.setTitle("ERROR"); | |||||
| eb.setColor(Color.red); | |||||
| eb.setDescription(message); | |||||
| context.getEvent().getTextChannel().sendMessage(eb.build()).queue(); | |||||
| } | |||||
| protected final void sendCustomMessage(MessageEmbed messageEmbed) { | |||||
| context.getEvent().getTextChannel().sendMessage(messageEmbed).queue(); | |||||
| } | |||||
| protected File downloadAttachmentToFile(String directoryPath, String name) { | |||||
| if (directoryPath == null) directoryPath = Resources.getEnsuredTempPath(); | |||||
| if (name == null) name = UUID.randomUUID().toString(); | |||||
| if (!(new File(directoryPath)).isDirectory()) { | |||||
| Logger.logError("Das Download-Verzeichnis wurde nicht gefunden."); | |||||
| sendErrorMessage("Der Anhang konnte nicht gedownloaded werden."); | |||||
| return null; | |||||
| } | |||||
| List<Attachment> attachments = context.getEvent().getMessage().getAttachments(); | |||||
| if (attachments.size() == 0) { | |||||
| return null; | |||||
| } | |||||
| Attachment attachment = attachments.get(0); | |||||
| File file = new File(directoryPath + name + "." + attachment.getFileExtension()); | |||||
| CompletableFuture<File> future = attachment.downloadToFile(file); | |||||
| future.exceptionally(e -> { | |||||
| sendErrorMessage("Ein Anhang konnte nicht gedownloaded werden."); | |||||
| return null; | |||||
| }); | |||||
| try { | |||||
| future.get(); | |||||
| } catch (InterruptedException | ExecutionException e) { | |||||
| sendErrorMessage("Ein Anhang konnte nicht gedownloaded werden."); | |||||
| return null; | |||||
| } | |||||
| if (!file.exists()) { | |||||
| sendErrorMessage("Ein Anhang konnte nicht gedownloaded werden."); | |||||
| return null; | |||||
| } | |||||
| if (file.getAbsolutePath().endsWith(".mp3")) { | |||||
| String newFilePath = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 3) + "opus"; | |||||
| Runtime rt = Runtime.getRuntime(); | |||||
| try { | |||||
| String command = "/usr/bin/ffmpeg -y -i " + file.getAbsolutePath() + " " + newFilePath; | |||||
| Process pr = rt.exec(command); | |||||
| String err = new BufferedReader(new InputStreamReader(pr.getErrorStream())).lines() | |||||
| .collect(Collectors.joining()); | |||||
| String out = new BufferedReader(new InputStreamReader(pr.getInputStream())).lines().collect( | |||||
| Collectors.joining()); | |||||
| int exit = pr.waitFor(); | |||||
| if (!file.delete()) { | |||||
| throw new IOException("Delete ging nich"); | |||||
| } | |||||
| return new File(newFilePath); | |||||
| } catch (IOException | InterruptedException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| return file; | |||||
| } | |||||
| protected VoiceChannel getVoiceChannelByParam() { | |||||
| VoiceChannel vc; | |||||
| if (context.containsArguments(new String[]{"channel"})) { | |||||
| if (context.getArgument("channel") == null) return null; | |||||
| List<VoiceChannel> channels = YoshiBot.getInstance().jda | |||||
| .getVoiceChannelsByName(context.getArgument("channel"), true); | |||||
| if (!(channels.size() > 0)) { | |||||
| sendErrorMessage("Der Kanalname konnte nicht gefunden werden."); | |||||
| return null; | |||||
| } | |||||
| vc = channels.get(0); | |||||
| } else { | |||||
| try { | |||||
| vc = context.getEvent().getMember().getVoiceState().getChannel(); | |||||
| if (vc == null) vc = YoshiBot.getInstance().getGuild().getAudioManager().getConnectedChannel(); | |||||
| } catch (Exception e) { | |||||
| sendErrorMessage( | |||||
| "Es konnte kein Voicekanal gefunden werden in dem die Audio-Datei abgespielt werden kann."); | |||||
| return null; | |||||
| } | |||||
| } | |||||
| return vc; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,305 @@ | |||||
| package de.yannicpunktdee.yoshibot.command; | |||||
| import java.util.*; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor.Action; | |||||
| import net.dv8tion.jda.api.events.message.MessageReceivedEvent;; | |||||
| /** | |||||
| * Parst einen Eingabestring, entscheidet ob er ein Kommando ist und zerlegt ihn in seine Bestandteile. Kommandos | |||||
| * besitzen einen Status, der Auskunft gibt, ob Fehler beim Parsen des Eingabestrings aufgetreten sind oder der String | |||||
| * gar kein Kommando war. Außerdem wird bei erfolgreich geparsten Kommandos eine Aktion festgelegt, die das Kommando | |||||
| * ausführen soll, sowie die Argumente mit welcher das Kommando ausgeführt wird. Außerdem enthalten ist eine Referenz | |||||
| * auf Ursprungs-User und -TextChannel. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public class YoshiCommandContext { | |||||
| /** | |||||
| * Repräsentiert einen Status, den eine YoshiCommand-Instanz hat, nachdem der Eingabestring eingelesen und geparst | |||||
| * wurde. Sofern der Status nicht OK ist, ist diese Instanz entweder kein Kommando oder besitzt eine fehlerhafte | |||||
| * Syntax. | |||||
| */ | |||||
| public enum State { | |||||
| /** | |||||
| * Das Kommando wurde erfolgreich geparst und besitzt keine Syntaxfehler. | |||||
| */ | |||||
| OK, | |||||
| /** | |||||
| * Der Eingabestring war kein Kommando für den Yoshi-Bot. | |||||
| */ | |||||
| NO_COMMAND, | |||||
| /** | |||||
| * Im Kommando wurde keine Aktion spezifiziert. | |||||
| */ | |||||
| NO_ACTION, | |||||
| /** | |||||
| * Die angegebene Aktion existiert nicht. | |||||
| */ | |||||
| UNKNOWN_ACTION, | |||||
| /** | |||||
| * Das Kommando hatt einen Syntaxfehler. | |||||
| */ | |||||
| BAD_SYNTAX | |||||
| } | |||||
| /** | |||||
| * Hilfskonstrukt. Beschreibt den Zustand vom Parser. | |||||
| */ | |||||
| private enum ReadingState { | |||||
| /** | |||||
| * Der Parser ist dabei festzustellen, ob es sich um ein Kommando handelt. | |||||
| */ | |||||
| VERIFYING, | |||||
| /** | |||||
| * Zwischenzustand zwischen VERIFYING und READING_ACTION. | |||||
| */ | |||||
| AFTER_VERIFY, | |||||
| /** | |||||
| * Der Parser liest die Action ein. | |||||
| */ | |||||
| READING_ACTION, | |||||
| /** | |||||
| * Zustand nach READING_ACTION, READING_VALUE oder READING_STRING). | |||||
| */ | |||||
| INTERMEDIATE, | |||||
| /** | |||||
| * Der Parser ist dabei einen Argumentenkey einzulesen. | |||||
| */ | |||||
| READING_KEY, | |||||
| /** | |||||
| * Der Parser hat gerade einen Argumentenkey eingelesen. | |||||
| */ | |||||
| AFTER_KEY, | |||||
| /** | |||||
| * Der Parser liest gerade einen Argumentenwert ein. | |||||
| */ | |||||
| READING_VALUE, | |||||
| /** | |||||
| * Der Parser liest gerade einen zusammenhängenden Argumentenwert ein. | |||||
| */ | |||||
| READING_STRING | |||||
| } | |||||
| ; | |||||
| /** | |||||
| * Das Präfix, mit dem Yoshi-Bot-Kommandos beginnen. | |||||
| */ | |||||
| public static final String PREFIX = "::yoshi"; | |||||
| /** | |||||
| * Der (Fehler-)Status, den der Parser nach Einlesen des Eingabestrings angenommen hat. | |||||
| */ | |||||
| private State state; | |||||
| /** | |||||
| * Die im Eingabesting spezifizierte Aktion. | |||||
| */ | |||||
| private Action action; | |||||
| /** | |||||
| * Eine Map, die die Key-Werte der Argumente (ohne Bindestrich) auf dessen Werte abbildet. | |||||
| */ | |||||
| private Map<String, String> arguments; | |||||
| protected List<String> argumentList; | |||||
| private MessageReceivedEvent event; | |||||
| /** | |||||
| * Erzeugt aus einem unbearbeiteten Eingabestring ein Kommando. Nach dem parsen enthält die state-Variable den | |||||
| * Endzustand des Parsers. | |||||
| * | |||||
| * @param argumentsString Ein unbearbeiteter Eingabestring | |||||
| */ | |||||
| public YoshiCommandContext(String argumentsString, MessageReceivedEvent event) { | |||||
| this.event = event; | |||||
| argumentsString = argumentsString.trim(); | |||||
| argumentList = new ArrayList<>(); | |||||
| argumentList.addAll(Arrays.asList(argumentsString.split(" "))); | |||||
| argumentList.remove(0); | |||||
| arguments = new HashMap<>(); | |||||
| ReadingState readingState = ReadingState.VERIFYING; | |||||
| String currentKey = null; | |||||
| int startPos = 0; | |||||
| int length = argumentsString.length(); | |||||
| for (int position = 0; position < length; position++) { | |||||
| char currentChar = argumentsString.charAt(position); | |||||
| switch (readingState) { | |||||
| case VERIFYING: | |||||
| if (!Character.isWhitespace(currentChar)) continue; | |||||
| if (!argumentsString.substring(0, position).equals(PREFIX)) { | |||||
| state = State.NO_COMMAND; | |||||
| return; | |||||
| } | |||||
| readingState = ReadingState.AFTER_VERIFY; | |||||
| continue; | |||||
| case AFTER_VERIFY: | |||||
| if (Character.isWhitespace(currentChar)) continue; | |||||
| if (currentChar == '-') { | |||||
| state = State.NO_ACTION; | |||||
| return; | |||||
| } | |||||
| startPos = position; | |||||
| readingState = ReadingState.READING_ACTION; | |||||
| continue; | |||||
| case READING_ACTION: | |||||
| if (!Character.isWhitespace(currentChar)) continue; | |||||
| try { | |||||
| action = Action.valueOf(argumentsString.substring(startPos, position).toUpperCase()); | |||||
| readingState = ReadingState.INTERMEDIATE; | |||||
| } catch (IllegalArgumentException e) { | |||||
| state = State.UNKNOWN_ACTION; | |||||
| return; | |||||
| } | |||||
| continue; | |||||
| case INTERMEDIATE: | |||||
| if (Character.isWhitespace(currentChar)) continue; | |||||
| if (currentChar != '-' || currentChar == '"') { | |||||
| state = State.BAD_SYNTAX; | |||||
| return; | |||||
| } | |||||
| startPos = position + 1; | |||||
| readingState = ReadingState.READING_KEY; | |||||
| continue; | |||||
| case READING_KEY: | |||||
| if (!Character.isWhitespace(currentChar)) continue; | |||||
| currentKey = argumentsString.substring(startPos, position); | |||||
| readingState = ReadingState.AFTER_KEY; | |||||
| continue; | |||||
| case AFTER_KEY: | |||||
| if (Character.isWhitespace(currentChar)) continue; | |||||
| if (currentChar == '-') { | |||||
| arguments.put(currentKey, null); | |||||
| startPos = position + 1; | |||||
| readingState = ReadingState.READING_KEY; | |||||
| } else if (currentChar == '"') { | |||||
| startPos = position + 1; | |||||
| readingState = ReadingState.READING_STRING; | |||||
| } else { | |||||
| startPos = position; | |||||
| readingState = ReadingState.READING_VALUE; | |||||
| } | |||||
| continue; | |||||
| case READING_VALUE: | |||||
| if (!Character.isWhitespace(currentChar)) continue; | |||||
| arguments.put(currentKey, argumentsString.substring(startPos, position)); | |||||
| readingState = ReadingState.INTERMEDIATE; | |||||
| continue; | |||||
| case READING_STRING: | |||||
| if (currentChar != '"') continue; | |||||
| if (argumentsString.charAt(position - 1) == '\\') continue; | |||||
| arguments.put(currentKey, argumentsString.substring(startPos, position)); | |||||
| readingState = ReadingState.INTERMEDIATE; | |||||
| continue; | |||||
| } | |||||
| } | |||||
| switch (readingState) { | |||||
| case INTERMEDIATE: | |||||
| state = State.OK; | |||||
| return; | |||||
| case VERIFYING: | |||||
| if (argumentsString.equals(PREFIX)) { | |||||
| action = Action.HELP; | |||||
| state = State.OK; | |||||
| } else { | |||||
| state = State.NO_COMMAND; | |||||
| } | |||||
| return; | |||||
| case READING_ACTION: | |||||
| try { | |||||
| action = Action.valueOf(argumentsString.substring(startPos).toUpperCase()); | |||||
| readingState = ReadingState.INTERMEDIATE; | |||||
| } catch (IllegalArgumentException e) { | |||||
| state = State.UNKNOWN_ACTION; | |||||
| return; | |||||
| } | |||||
| state = State.OK; | |||||
| return; | |||||
| case READING_KEY: | |||||
| arguments.put(argumentsString.substring(startPos), null); | |||||
| state = State.OK; | |||||
| return; | |||||
| case READING_VALUE: | |||||
| arguments.put(currentKey, argumentsString.substring(startPos)); | |||||
| state = State.OK; | |||||
| return; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Prüft, ob der Eingabestring ein valides Kommando war. | |||||
| */ | |||||
| public boolean isValid() { | |||||
| return state.equals(State.OK); | |||||
| } | |||||
| /** | |||||
| * Gibt den (Fehler-)Status des Parsers zurück. | |||||
| */ | |||||
| public State getState() { | |||||
| return state; | |||||
| } | |||||
| /** | |||||
| * Gibt die im Kommando spezifizierte Aktion zurück. null, wenn status fehlerhaft oder kein Kommando. | |||||
| */ | |||||
| public Action getAction() { | |||||
| return action; | |||||
| } | |||||
| /** | |||||
| * Prüft, ob das Kommando mit Argumenten aufgerufen wurde. | |||||
| */ | |||||
| public boolean hasArguments() { | |||||
| return !arguments.isEmpty(); | |||||
| } | |||||
| public boolean containsArgument(String arg){ | |||||
| return this.containsArguments(new String[]{arg}); | |||||
| } | |||||
| /** | |||||
| * Prüft, ob alle Key-Werte in der Argumentenliste vorhanden sind. | |||||
| * | |||||
| * @param args Liste von den auf Existenz zu überprüfenden Argumenten. | |||||
| */ | |||||
| public boolean containsArguments(String[] args) { | |||||
| for (String arg : args) { | |||||
| if (!arguments.containsKey(arg)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * Gibt den Wert eines Arguments zurück. | |||||
| * | |||||
| * @param arg Name des Arguments. | |||||
| */ | |||||
| public String getArgument(String arg) { | |||||
| if (!arguments.containsKey(arg)) return null; | |||||
| return arguments.get(arg); | |||||
| } | |||||
| public MessageReceivedEvent getEvent() { | |||||
| return event; | |||||
| } | |||||
| public List<String> getArguments() { | |||||
| return this.argumentList; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,111 @@ | |||||
| package de.yannicpunktdee.yoshibot.command; | |||||
| import de.yannicpunktdee.yoshibot.command.commands.*; | |||||
| /** | |||||
| * Unterscheidet nach der spezifizierten Action welche YoshiCommand-Kindklasse zum Ausführen des Kommandos verwendet | |||||
| * wird. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public class YoshiCommandDistributor { | |||||
| /** | |||||
| * Führt das jeweils zuständige Kommando aus. | |||||
| * | |||||
| * @param context | |||||
| */ | |||||
| public static void distribute(YoshiCommandContext context) { | |||||
| switch (context.getState()) { | |||||
| case NO_ACTION: | |||||
| context.getEvent().getTextChannel() | |||||
| .sendMessage("Im letzten Befehl wurde keine Aktion spezifiziert. Führe \"" | |||||
| + YoshiCommandContext.PREFIX + " help\" für Hilfe aus.").queue(); | |||||
| return; | |||||
| case UNKNOWN_ACTION: | |||||
| context.getEvent().getTextChannel() | |||||
| .sendMessage("Im letzten Befehl wurde eine unbekannte Aktion angegeben. Führe \"" | |||||
| + YoshiCommandContext.PREFIX + " help\" für Hilfe aus.").queue(); | |||||
| return; | |||||
| case BAD_SYNTAX: | |||||
| context.getEvent().getTextChannel().sendMessage("Der letzte Befehl hatte ein falsches Format. Führe \"" | |||||
| + YoshiCommandContext.PREFIX + " help\" für Hilfe aus.") | |||||
| .queue(); | |||||
| return; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| YoshiCommand command = null; | |||||
| switch (context.getAction()) { | |||||
| case HELP: | |||||
| command = new HelpCommand(context); | |||||
| break; | |||||
| case JOKE: | |||||
| command = new JokeCommand(context); | |||||
| break; | |||||
| case SAY: | |||||
| command = new SayCommand(context); | |||||
| break; | |||||
| case PLAY: | |||||
| command = new PlayCommand(context); | |||||
| break; | |||||
| case SAUCE: | |||||
| command = new SauceCommand(context); | |||||
| break; | |||||
| case PAT: | |||||
| command = new PatCommand(context); | |||||
| break; | |||||
| case BONK: | |||||
| command = new BonkCommand(context); | |||||
| break; | |||||
| case WIKIPEDIA: | |||||
| command = new WikipediaCommand(context); | |||||
| break; | |||||
| default: | |||||
| context.getEvent().getTextChannel().sendMessage("Dieses Kommando existiert noch nicht.").queue(); | |||||
| break; | |||||
| } | |||||
| if (command != null) command.execute(); | |||||
| } | |||||
| /** | |||||
| * Enth�lt alle m�glichen Aktionen, die der Yoshi-Bot ausf�hren kann. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public enum Action { | |||||
| /** | |||||
| * Sende eine Hilfe-Nachricht, in der die Benutzung des Yoshi-Bots dokumentiert ist. | |||||
| */ | |||||
| HELP, | |||||
| /** | |||||
| * Erzählt einen Jokus. | |||||
| */ | |||||
| JOKE, | |||||
| /** | |||||
| * Gib die Nachricht -message aus. �ber die Option -out [text|voice] wird angegeben, ob die Nachricht per | |||||
| * Textnachricht oder als Text-To-Speech ausgegeben wird. Mit -channel l�sst sich der Ausgabechannel bestimmen. | |||||
| * Standardm��ig wird die Ausgabe in den Textchannel zur�ckgesendet, aus dem das Kommando kam. | |||||
| */ | |||||
| SAY, | |||||
| /** | |||||
| * Gibt eine vorhandene Ressource -name aus. (Vorhandene Ressourcen lassen sich mit der Aktion LIST anzeigen). | |||||
| * �ber den Parameter -type [link|audio|video] l�sst sich der Typ der Ressource spezifizieren. Ein Link wird | |||||
| * �ber in den per -channel spezifizierten (default=Ursprungskanal) Textkanal geschickt. Eine Audiodatei wird | |||||
| * �ber den per -channel spezifizierten (default=Aktueller Kanal) Voice-Channel ausgegeben. Ein Video wird �ber | |||||
| * den per -channel spezifizierten (default=Aktueller Kanal) Voice-Channel abgespielt. | |||||
| */ | |||||
| PLAY, | |||||
| /** | |||||
| * L�scht die Ressource, die �ber -name spezifiziert wurde. Mit -type wird der Ressourcentyp festgelegt. | |||||
| */ | |||||
| SAUCE, | |||||
| PAT, | |||||
| BONK, | |||||
| WIKIPEDIA | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,59 @@ | |||||
| package de.yannicpunktdee.yoshibot.command.commands; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommand; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.utils.GifSequenceWriter; | |||||
| import de.yannicpunktdee.yoshibot.utils.Resources; | |||||
| import javax.imageio.ImageIO; | |||||
| import javax.imageio.stream.FileImageOutputStream; | |||||
| import javax.imageio.stream.ImageOutputStream; | |||||
| import java.awt.*; | |||||
| import java.awt.image.BufferedImage; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.util.UUID; | |||||
| public class BonkCommand extends YoshiCommand { | |||||
| public BonkCommand(YoshiCommandContext context) { | |||||
| super(context); | |||||
| } | |||||
| @Override | |||||
| public boolean execute() { | |||||
| if(!super.execute()) return false; | |||||
| File outFile = new File(Resources.getEnsuredTempPath() + UUID.randomUUID().toString() + ".gif"); | |||||
| try { | |||||
| BufferedImage inPicture = ImageIO.read(downloadAttachmentToFile(null, null)); | |||||
| BufferedImage bonk1Picture = ImageIO.read(new File(Resources.getBonkPngPath() + "bonk1.png")); | |||||
| BufferedImage bonk2Picture = ImageIO.read(new File(Resources.getBonkPngPath() + "bonk2.png")); | |||||
| BufferedImage frame1 = getOutFrame(bonk1Picture, inPicture, 615, 155, 185, 345); | |||||
| ImageOutputStream output = new FileImageOutputStream(outFile); | |||||
| GifSequenceWriter writer = new GifSequenceWriter(output, frame1.getType(), 100, true); | |||||
| writer.writeToSequence(frame1); | |||||
| writer.writeToSequence(getOutFrame(bonk2Picture, inPicture, 455, 155, 345, 345)); | |||||
| writer.close(); | |||||
| output.close(); | |||||
| } catch (IOException e) { | |||||
| sendErrorMessage("GIF konnte nicht erstellt werden."); | |||||
| return false; | |||||
| } | |||||
| sendFile(outFile, null); | |||||
| return true; | |||||
| } | |||||
| private BufferedImage getOutFrame(BufferedImage bonkDogGraphics, BufferedImage personGraphics, int x, int y, int width, int height){ | |||||
| BufferedImage outFrame = new BufferedImage(800, 500, BufferedImage.TYPE_INT_RGB); | |||||
| Graphics2D g = outFrame.createGraphics(); | |||||
| g.setColor(Color.black); | |||||
| g.drawImage(personGraphics, x, y, width, height, null); | |||||
| g.drawImage(bonkDogGraphics, 0, 0, 680, 412, null); | |||||
| return outFrame; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,50 @@ | |||||
| package de.yannicpunktdee.yoshibot.command.commands; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommand; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import net.dv8tion.jda.api.EmbedBuilder; | |||||
| public class HelpCommand extends YoshiCommand { | |||||
| public HelpCommand(YoshiCommandContext context) { | |||||
| super(context); | |||||
| } | |||||
| @Override | |||||
| public boolean execute() { | |||||
| if (!super.execute()) return false; | |||||
| EmbedBuilder eb = new EmbedBuilder(); | |||||
| eb.setTitle("So kannst du den Yohsi-Bot benutzen:"); | |||||
| eb.setDescription("W\u00e4hle einen Befehl aus der folgenden Liste und ersetze die ``hervorgehobenen`` Ausdr\u00fccke " + | |||||
| "durch die jeweiligen Werte. Enth\u00e4lt ein Wert Leerzeichen muss er in doppelten Anf\u00fchrungszeichen stehen. " + | |||||
| "Die Befehle funktionieren nur im bot-muell Textchannel und der sauce-Befehl nur im schrein-auf-den-bot.\n[Link zum Projekt](http://yannicpunktdee.de:3000/yannic/YoshiBot.git)"); | |||||
| eb.addField("::yoshi help", "Zeigt genau diesen Hilfetext an.", false); | |||||
| eb.addField("::yoshi joke", "Schreibt einen random Jokus in den bot-muell Textchannel.", false); | |||||
| eb.addField("::yoshi joke -channel ``kanal``", "Schreibt einen random Jokus in den Textchannel kanal.", false); | |||||
| eb.addField("::yoshi play -add -name ``name``", "F\u00fcgt die im **ANHANG** enthaltene Audiodatei (im .opus-" + | |||||
| "Format der Audiosammlung hinzu und gibt ihr den Namen ``name``. Dabei m\u00fcssen der Befehl und der Anhang " + | |||||
| "nat\u00fcrlich der selben Nachricht angeh\u00f6ren.", false); | |||||
| eb.addField("::yoshi play -list", "Listet alle derzeit verf\u00fcgbaren Sounds auf.", false); | |||||
| eb.addField("::yoshi play -name ``name`` -channel ``kanal``", "Spielt den Sound mit dem Namen ``name`` " + | |||||
| "in dem Voicechannel ``kanal``. Die Kanalangabe kann man auch weglassen, wenn man selbst oder der Bot " + | |||||
| "sich gerade in einem Voicechannel befindet.", false); | |||||
| eb.addField("::yoshi say -text \"``text``\" -channel ``kanal``", "Liest den Text ``text`` mittels " + | |||||
| "Google \u00dcbersetzer in dem Voicechannel ``kanal`` vor. Die Kanalangabe kann man auch weglassen, " + | |||||
| "wenn man selbst oder der Bot sich gerade in einem Voicechannel befindet.", false); | |||||
| eb.addField("::yoshi sauce -tags \"``tags``\"", "Schickt ein zuf\u00e4lliges knuspriges Bild von rule34.xxx " + | |||||
| "in den schrein-auf-den-bot gefiltert durch die ``tags`` (durch Leerzeichen getrennt). Ps.: Vergiss die " + | |||||
| "doppelten Anf\u00fchrungszeichen nicht.", false); | |||||
| eb.addField("::yoshi pat", "Macht aus dem im **ANHANG** enthaltenen Bild ein Pat-GIF und schickt es " + | |||||
| "in den bot-muell Textchannel. Dabei m\u00fcssen der Befehl und der Anhang nat\u00fcrlich der selben " + | |||||
| "Nachricht angeh\u00f6ren.", false); | |||||
| eb.addField("::yoshi bonk", "Analog zu pat, nur mit Bonk.", false); | |||||
| eb.addField("::yoshi wikipedia -name ``name`` -channel ``kanal``", "Liest die ersten S\u00e4tze des " + | |||||
| "Wikipedia-Artikels zu ``name`` im Sprachkanal ``kanal`` vor und schickt den Text in den bot-muell Textchannel." + | |||||
| "Die Kanalangabe kann man auch weglassen, wenn man selbst oder der Bot sich gerade in einem Voicechannel befindet.", false); | |||||
| sendCustomMessage(eb.build()); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,120 @@ | |||||
| package de.yannicpunktdee.yoshibot.command.commands; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommand; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import net.dv8tion.jda.api.entities.TextChannel; | |||||
| import org.json.JSONException; | |||||
| import org.json.JSONObject; | |||||
| import java.util.List; | |||||
| import java.util.Random; | |||||
| import static de.yannicpunktdee.yoshibot.utils.RestHelper.getFromURL; | |||||
| /** | |||||
| * Schickt einen zufälligen Jokus aus einer zufällig ausgewählten Quelle in den Textchannel. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public class JokeCommand extends YoshiCommand { | |||||
| /** | |||||
| * Erstellt einen neuen JokeCommand. | |||||
| */ | |||||
| public JokeCommand(YoshiCommandContext context) { | |||||
| super(context); | |||||
| } | |||||
| /** | |||||
| * {@inheritDoc} | |||||
| */ | |||||
| @Override | |||||
| public synchronized boolean execute() { | |||||
| String message = "Jokus"; | |||||
| Random random = YoshiBot.getInstance().getRandom(); | |||||
| int number = random.nextInt(3); | |||||
| switch (number) { | |||||
| case 0: | |||||
| message = jokeApi(); | |||||
| break; | |||||
| case 1: | |||||
| message = officialJokeApi(); | |||||
| break; | |||||
| case 2: | |||||
| message = chuckNorris(); | |||||
| break; | |||||
| default: | |||||
| message = "Jokus"; | |||||
| break; | |||||
| } | |||||
| if (context.containsArguments(new String[]{"channel"})) { | |||||
| String arg = context.getArgument("channel"); | |||||
| if (arg == null) { | |||||
| sendErrorMessage("Es wurde kein channel angegeben."); | |||||
| return false; | |||||
| } | |||||
| List<TextChannel> channels = YoshiBot.getInstance().jda | |||||
| .getTextChannelsByName(context.getArgument("channel"), true); | |||||
| if (channels.isEmpty()) { | |||||
| sendErrorMessage("Der Kanalname konnte nicht gefunden werden."); | |||||
| return false; | |||||
| } | |||||
| channels.get(0).sendMessage(message).queue(); | |||||
| } else { | |||||
| sendMessage(message); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| private String chuckNorris() { | |||||
| String url = "http://api.icndb.com/jokes/random"; | |||||
| JSONObject json = null; | |||||
| try { | |||||
| String raw = getFromURL(url); | |||||
| json = new JSONObject(raw); | |||||
| return json.getJSONObject("value").getString("joke"); | |||||
| } catch (JSONException e) { | |||||
| return "Konnte keinen Jokus von \"" + url + "\" laden."; | |||||
| } | |||||
| } | |||||
| private String officialJokeApi() { | |||||
| String url = "https://official-joke-api.appspot.com/jokes/random"; | |||||
| JSONObject json = null; | |||||
| try { | |||||
| String raw = getFromURL(url); | |||||
| json = new JSONObject(raw); | |||||
| String result = json.getString("setup"); | |||||
| result += " - "; | |||||
| result += json.getString("punchline"); | |||||
| return result; | |||||
| } catch (JSONException e) { | |||||
| return "Konnte keinen Jokus von \"" + url + "\" laden."; | |||||
| } | |||||
| } | |||||
| private String jokeApi() { | |||||
| String url = "https://v2.jokeapi.dev/joke/any"; | |||||
| JSONObject json = null; | |||||
| try { | |||||
| String raw = getFromURL(url); | |||||
| json = new JSONObject(raw); | |||||
| String result = json.getString("setup"); | |||||
| result += " - "; | |||||
| result += json.getString("delivery"); | |||||
| return result; | |||||
| } catch (JSONException e) { | |||||
| return "Konnte keinen Jokus von \"" + url + "\" laden."; | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,62 @@ | |||||
| package de.yannicpunktdee.yoshibot.command.commands; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommand; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.utils.GifSequenceWriter; | |||||
| import de.yannicpunktdee.yoshibot.utils.Resources; | |||||
| import javax.imageio.ImageIO; | |||||
| import javax.imageio.stream.FileImageOutputStream; | |||||
| import javax.imageio.stream.ImageOutputStream; | |||||
| import java.awt.*; | |||||
| import java.awt.image.BufferedImage; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.util.UUID; | |||||
| public class PatCommand extends YoshiCommand { | |||||
| public PatCommand(YoshiCommandContext context) { | |||||
| super(context); | |||||
| } | |||||
| @Override | |||||
| public boolean execute() { | |||||
| if (!super.execute()) return false; | |||||
| File outFile = new File(Resources.getEnsuredTempPath() + UUID.randomUUID().toString() + ".gif"); | |||||
| try { | |||||
| BufferedImage inPicture= ImageIO.read(downloadAttachmentToFile(null, null)); | |||||
| BufferedImage pat1Picture= ImageIO.read(new File(Resources.getPatPngPath() + "pat1.png")); | |||||
| BufferedImage pat2Picture= ImageIO.read(new File(Resources.getPatPngPath() + "pat2.png")); | |||||
| BufferedImage pat3Picture= ImageIO.read(new File(Resources.getPatPngPath() + "pat3.png")); | |||||
| BufferedImage frame1 = getOutFrame(pat1Picture, inPicture, 100, 100, 400, 400); | |||||
| ImageOutputStream output = new FileImageOutputStream(outFile); | |||||
| GifSequenceWriter writer = new GifSequenceWriter(output, frame1.getType(), 100, true); | |||||
| writer.writeToSequence(frame1); | |||||
| writer.writeToSequence(getOutFrame(pat2Picture, inPicture, 100, 70, 400, 430)); | |||||
| writer.writeToSequence(getOutFrame(pat3Picture, inPicture, 100, 50, 400, 450)); | |||||
| writer.close(); | |||||
| output.close(); | |||||
| } catch (IOException e) { | |||||
| sendErrorMessage("Gif konnte nicht erstellt werden,"); | |||||
| e.printStackTrace(); | |||||
| return false; | |||||
| } | |||||
| sendFile(outFile, null); | |||||
| return true; | |||||
| } | |||||
| private BufferedImage getOutFrame(BufferedImage patHandGraphics, BufferedImage personGraphics, int x, int y, int width, int height){ | |||||
| BufferedImage outFrame = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB); | |||||
| Graphics2D g = outFrame.createGraphics(); | |||||
| g.setColor(Color.black); | |||||
| g.drawImage(personGraphics, x, y, width, height, null); | |||||
| g.drawImage(patHandGraphics, 0, 0, 500, 400, null); | |||||
| return outFrame; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,129 @@ | |||||
| package de.yannicpunktdee.yoshibot.command.commands; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommand; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import de.yannicpunktdee.yoshibot.utils.Resources; | |||||
| import net.dv8tion.jda.api.EmbedBuilder; | |||||
| import net.dv8tion.jda.api.entities.Member; | |||||
| import net.dv8tion.jda.api.entities.User; | |||||
| import net.dv8tion.jda.api.entities.VoiceChannel; | |||||
| import org.apache.commons.text.similarity.JaccardDistance; | |||||
| import java.awt.Color; | |||||
| import java.io.File; | |||||
| import java.util.*; | |||||
| import java.util.stream.Collectors; | |||||
| public class PlayCommand extends YoshiCommand { | |||||
| protected String[] requiredArguments = {"name"}; | |||||
| private static final Map<User, Long> cooldownChecker = new HashMap<>(); | |||||
| private static final int SECOND_DELAY = 5; | |||||
| public PlayCommand(YoshiCommandContext context) { | |||||
| super(context); | |||||
| } | |||||
| @Override | |||||
| public boolean execute() { | |||||
| if (!super.execute()) return false; | |||||
| for (User user : cooldownChecker.keySet()) { | |||||
| Long timestamp = cooldownChecker.get(user); | |||||
| if (System.currentTimeMillis() - timestamp > SECOND_DELAY * 1000) { | |||||
| cooldownChecker.remove(user); | |||||
| } | |||||
| } | |||||
| if (context.containsArguments(new String[]{"add", "name"})) { | |||||
| return addSound(context.getArgument("name")); | |||||
| } else if (context.containsArgument("list")) { | |||||
| return listSounds(); | |||||
| } else if (context.containsArgument("name")) { | |||||
| return playSound(context.getArgument("name")); | |||||
| } else { | |||||
| context.getEvent().getMessage().getTextChannel().sendMessage("Blyat, keine Ahnung was du willst. Gib mal " + | |||||
| "Parameter").queue(); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| private boolean addSound(String filename) { | |||||
| File download = downloadAttachmentToFile(Resources.getAudioPath(), filename); | |||||
| if (download.isFile()) { | |||||
| sendInfoMessage("Audio erfolgreich hinzugefügt."); | |||||
| return true; | |||||
| } else { | |||||
| sendErrorMessage("Audio konnte nicht hinzugefügt werden."); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| private boolean listSounds() { | |||||
| EmbedBuilder eb = new EmbedBuilder(); | |||||
| eb.setTitle("Es sind folgende Audios verf\u00fcgbar:"); | |||||
| eb.setColor(Color.cyan); | |||||
| eb.setDescription(String.join("\n", getAllFiles())); | |||||
| sendCustomMessage(eb.build()); | |||||
| return true; | |||||
| } | |||||
| private boolean playSound(String requestedFile) { | |||||
| if (requestedFile == null) { | |||||
| sendErrorMessage(String.format("Konnte keine Audiodatei namens '%s.opus' finden!", | |||||
| context.getArgument("name"))); | |||||
| return false; | |||||
| } | |||||
| requestedFile = getBestMatch(requestedFile, getAllFiles()); | |||||
| File file = new File(Resources.getPathToAudioFile(requestedFile)); | |||||
| if (!file.isFile()) { | |||||
| sendErrorMessage(String.format("Konnte keine Audiodatei namens '%s.opus' finden!", | |||||
| context.getArgument("name"))); | |||||
| return false; | |||||
| } | |||||
| VoiceChannel vc = getVoiceChannelByParam(); | |||||
| if (vc == null) { | |||||
| sendErrorMessage("Konnte keinen Audiochannel auswählen."); | |||||
| return false; | |||||
| } | |||||
| User author = context.getEvent().getAuthor(); | |||||
| if (YoshiBot.getInstance().jda.getVoiceChannels().parallelStream() | |||||
| .flatMap(vcs -> vcs.getMembers().parallelStream()).map( | |||||
| Member::getUser).noneMatch(user -> user == author)) { | |||||
| if (PlayCommand.cooldownChecker.containsKey(author)) { | |||||
| sendErrorMessage( | |||||
| "Иди нахуй (geh auf Schwanz), du musst " + SECOND_DELAY + "s warten, wenn du nicht in" + | |||||
| " einem Channel bist"); | |||||
| return false; | |||||
| } | |||||
| PlayCommand.cooldownChecker.put(author, System.currentTimeMillis()); | |||||
| } | |||||
| context.getEvent().getMessage().getTextChannel() | |||||
| .sendMessage("Danke, " + context.getEvent().getMessage().getAuthor().getName() + ". Spiele '" + | |||||
| requestedFile + "' in '" + vc.getName() + "' ab").queue(); | |||||
| YoshiBot.getInstance().playSound(file, vc); | |||||
| return true; | |||||
| } | |||||
| private String getBestMatch(String word, List<String> choices) { | |||||
| Optional<String> match = choices.parallelStream().filter(word::equals).findAny(); | |||||
| return match.orElse(choices.parallelStream().min( | |||||
| Comparator.comparingDouble(file -> new JaccardDistance().apply(word, file))).orElse(null)); | |||||
| } | |||||
| private List<String> getAllFiles() { | |||||
| File audioDirectory = new File(Resources.getAudioPath()); | |||||
| return Arrays.stream(Objects.requireNonNull(audioDirectory.listFiles())) | |||||
| .map(File::getName) | |||||
| .filter(name -> name.endsWith(".opus")) | |||||
| .map(name -> name.substring(0, name.lastIndexOf("."))) | |||||
| .sorted(String::compareToIgnoreCase).collect(Collectors.toList()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,27 @@ | |||||
| package de.yannicpunktdee.yoshibot.command.commands; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommand; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import net.dv8tion.jda.api.entities.VoiceChannel; | |||||
| public class SayCommand extends YoshiCommand { | |||||
| protected final String[] requiredArguments = {"text", "channel"}; | |||||
| public SayCommand(YoshiCommandContext context) { | |||||
| super(context); | |||||
| } | |||||
| @Override | |||||
| public boolean execute() { | |||||
| if (!super.execute()) return false; | |||||
| context.getEvent().getMessage().getTextChannel().sendMessage( | |||||
| "Danke, " + context.getEvent().getMessage().getAuthor().getName() + ". Ich werde nun " + | |||||
| "abspielen:\n" + context.getArgument("text")).queue(); | |||||
| return YoshiBot.getInstance().sayTTS(context.getArgument("text"), getVoiceChannelByParam()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,47 @@ | |||||
| package de.yannicpunktdee.yoshibot.command.commands; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommand; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import de.yannicpunktdee.yoshibot.utils.Logger; | |||||
| import de.yannicpunktdee.yoshibot.utils.RestHelper; | |||||
| import org.json.JSONObject; | |||||
| import java.util.Arrays; | |||||
| import java.util.List; | |||||
| public class WikipediaCommand extends YoshiCommand { | |||||
| /** | |||||
| * Erzeugt ein neues Kommando, führt es aber noch nicht aus. Es wird ermittelt, ob die Argumentenkombination valide | |||||
| * ist und das isOk-Flag gesetzt. Im Fehlerfall wird eine Fehleremeldung spezifiziert. | |||||
| * | |||||
| * @param context Der Kontext mit dem das Kommando aufgerufen wurde. | |||||
| */ | |||||
| public WikipediaCommand(YoshiCommandContext context) { | |||||
| super(context); | |||||
| } | |||||
| @Override | |||||
| public boolean execute() { | |||||
| if (!super.execute()) return false; | |||||
| String url = "https://de.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext" + | |||||
| "&redirects&titles=" + context.getArgument("name"); | |||||
| JSONObject articleBase = new JSONObject(RestHelper.getFromURL(url)); | |||||
| JSONObject pages = articleBase.getJSONObject("query").getJSONObject("pages"); | |||||
| if (pages.has("-1")) { | |||||
| sendErrorMessage("Kein Artikel namens " + context.getArgument("name") + " gefunden!"); | |||||
| Logger.logWarning("Konnte Artikel " + context.getArgument("name") + " nicht finden!"); | |||||
| } | |||||
| assert pages.keySet().stream().findFirst().isPresent(); | |||||
| JSONObject page = pages.getJSONObject(pages.keySet().stream().findFirst().get()); | |||||
| String text = page.getString("extract"); | |||||
| sendMessage(text); | |||||
| List<String> parts = Arrays.asList(text.split(" ")); | |||||
| text = String.join(" ", parts.subList(0, Math.min(parts.size(), 50))); | |||||
| return YoshiBot.getInstance().sayTTS(text, getVoiceChannelByParam()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,44 @@ | |||||
| package de.yannicpunktdee.yoshibot.listeners; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStreamReader; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| public class CommandLine extends Thread implements Runnable { | |||||
| private BufferedReader reader; | |||||
| /** | |||||
| * Nicht manuell aufrufen. Wird einmalig in startYoshiBot in einem neuen Thread aufgerufen und reagiert | |||||
| * auf administrative Konsoleneingaben außerhalb von Discord. | |||||
| */ | |||||
| @Override public void run() { | |||||
| String line = ""; | |||||
| reader = new BufferedReader(new InputStreamReader(System.in)); | |||||
| try { | |||||
| System.out.print("> "); | |||||
| while((line = reader.readLine()) != null) { | |||||
| line = line.trim(); | |||||
| if(line.equalsIgnoreCase("exit")) { | |||||
| YoshiBot.getInstance().stop(); | |||||
| return; | |||||
| } | |||||
| System.out.print("> "); | |||||
| } | |||||
| } catch(IOException e) { | |||||
| System.err.println("Es ist eine IOException aufgetreten."); | |||||
| } | |||||
| } | |||||
| public void stopCommandLine() { | |||||
| try { | |||||
| reader.close(); | |||||
| interrupt(); | |||||
| } catch (IOException e) { | |||||
| System.err.println("Fehler beim Schließen des Readers."); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,96 @@ | |||||
| package de.yannicpunktdee.yoshibot.listeners; | |||||
| import de.yannicpunktdee.yoshibot.audio.AudioPlayerListener; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import de.yannicpunktdee.yoshibot.utils.Resources; | |||||
| import net.dv8tion.jda.api.entities.ChannelType; | |||||
| import net.dv8tion.jda.api.entities.VoiceChannel; | |||||
| import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent; | |||||
| import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent; | |||||
| import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMoveEvent; | |||||
| import net.dv8tion.jda.api.events.message.MessageReceivedEvent; | |||||
| import net.dv8tion.jda.api.hooks.ListenerAdapter; | |||||
| import org.jetbrains.annotations.NotNull; | |||||
| import java.util.Arrays; | |||||
| /** | |||||
| * Lauscht auf eingehende Nachrichten und leitet diese an die YoshiBot.executeCommand-Methode weiter, falls es sich um | |||||
| * ein Kommando handelt. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public class DiscordEventListener extends ListenerAdapter { | |||||
| /** | |||||
| * {@inheritDoc} | |||||
| */ | |||||
| @Override | |||||
| public void onMessageReceived(MessageReceivedEvent event) { | |||||
| if (!event.isFromType(ChannelType.TEXT)) return; | |||||
| if (event.getAuthor().isBot()) return; | |||||
| boolean inErlaubtemKanal = Arrays.stream(Resources.getRestrict_commands_to_channel()) | |||||
| .anyMatch(channel -> channel.equalsIgnoreCase(event.getTextChannel().getName())); | |||||
| if (Resources.getRestrict_commands_to_channel() != null && !inErlaubtemKanal) | |||||
| return; | |||||
| String raw = event.getMessage().getContentRaw().trim(); | |||||
| if (!raw.startsWith(YoshiCommandContext.PREFIX)) return; | |||||
| YoshiCommandContext context = new YoshiCommandContext(raw, event); | |||||
| if (!context.getState().equals(YoshiCommandContext.State.NO_COMMAND)) YoshiBot.executeCommand(context); | |||||
| } | |||||
| @Override | |||||
| public void onGuildVoiceJoin(@NotNull GuildVoiceJoinEvent event) { | |||||
| super.onGuildVoiceJoin(event); | |||||
| if (event.getMember().getUser().isBot()) return; | |||||
| if (Resources.isGreetings_and_byebyes_on()) { | |||||
| String nameToPlay = event.getMember().getNickname(); | |||||
| if (nameToPlay == null) nameToPlay = event.getMember().getUser().getName(); | |||||
| YoshiBot.getInstance().sayTTS(Resources.getRandomGreeting(nameToPlay), event.getChannelJoined()); | |||||
| } | |||||
| if (!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers(); | |||||
| } | |||||
| @Override | |||||
| public void onGuildVoiceMove(@NotNull GuildVoiceMoveEvent event) { | |||||
| super.onGuildVoiceMove(event); | |||||
| if (event.getMember().getUser().isBot()) return; | |||||
| VoiceChannel afkChannel = YoshiBot.getInstance().getGuild().getAfkChannel(); | |||||
| String nameToPlay = event.getMember().getNickname(); | |||||
| nameToPlay = nameToPlay == null ? event.getMember().getUser().getName() : nameToPlay; | |||||
| if (event.getChannelJoined() == afkChannel) { | |||||
| YoshiBot.getInstance().sayTTS(Resources.getRandomAfk(nameToPlay, false), event.getChannelLeft()); | |||||
| } else if (event.getChannelLeft() == afkChannel) { | |||||
| YoshiBot.getInstance().sayTTS(Resources.getRandomAfk(nameToPlay, true), event.getChannelJoined()); | |||||
| } | |||||
| if (!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers(); | |||||
| } | |||||
| @Override | |||||
| public void onGuildVoiceLeave(@NotNull GuildVoiceLeaveEvent event) { | |||||
| super.onGuildVoiceLeave(event); | |||||
| if (event.getMember().getUser().isBot() || event.getChannelLeft().getMembers().size() == 0) return; | |||||
| if (Resources.isGreetings_and_byebyes_on()) { | |||||
| String nameToPlay = event.getMember().getNickname(); | |||||
| nameToPlay = nameToPlay == null ? event.getMember().getUser().getName() : nameToPlay; | |||||
| YoshiBot.getInstance().sayTTS(Resources.getRandomByebye(nameToPlay), event.getChannelLeft()); | |||||
| } | |||||
| if (!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,36 @@ | |||||
| package de.yannicpunktdee.yoshibot.main; | |||||
| import de.yannicpunktdee.yoshibot.utils.Logger; | |||||
| import javax.security.auth.login.LoginException; | |||||
| import java.net.URISyntaxException; | |||||
| /** | |||||
| * Main-Klasse und Startpunkt für die Bot-Applikation. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public class Main { | |||||
| /** | |||||
| * Eintrittspunkt für die Applikation. Erzeugen eines neuen Yoshi-Bots und Starten. | |||||
| * | |||||
| * @param args Aufrufargumente. Werden später zum Konfigurieren genutzt. | |||||
| * | |||||
| * @throws URISyntaxException | |||||
| */ | |||||
| public static void main(String[] args) throws URISyntaxException { | |||||
| YoshiBot yoshiBot = YoshiBot.getInstance(); | |||||
| if (!yoshiBot.init((args.length > 0) ? args[0] : null)) { | |||||
| Logger.logError("Es ist ein Fehler beim Initialisieren der Ressourcen aufgetreten."); | |||||
| return; | |||||
| } | |||||
| Logger.logInfo("Ressourcen erfolgreich initialisiert. Starte Yoshi Bot ..."); | |||||
| yoshiBot.start(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,244 @@ | |||||
| package de.yannicpunktdee.yoshibot.main; | |||||
| import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; | |||||
| import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; | |||||
| import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; | |||||
| import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; | |||||
| import com.sedmelluq.discord.lavaplayer.source.local.LocalAudioSourceManager; | |||||
| import de.yannicpunktdee.yoshibot.audio.AudioLoadResultHandlerImpl; | |||||
| import de.yannicpunktdee.yoshibot.audio.AudioPlayerListener; | |||||
| import de.yannicpunktdee.yoshibot.audio.AudioSendHandlerImpl; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; | |||||
| import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor; | |||||
| import de.yannicpunktdee.yoshibot.listeners.CommandLine; | |||||
| import de.yannicpunktdee.yoshibot.listeners.DiscordEventListener; | |||||
| import de.yannicpunktdee.yoshibot.utils.*; | |||||
| import lombok.Getter; | |||||
| import lombok.SneakyThrows; | |||||
| import net.dv8tion.jda.api.JDA; | |||||
| import net.dv8tion.jda.api.JDABuilder; | |||||
| import net.dv8tion.jda.api.OnlineStatus; | |||||
| import net.dv8tion.jda.api.entities.Activity; | |||||
| import net.dv8tion.jda.api.entities.Guild; | |||||
| import net.dv8tion.jda.api.entities.PermissionOverride; | |||||
| import net.dv8tion.jda.api.entities.VoiceChannel; | |||||
| import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStreamReader; | |||||
| import java.nio.file.Files; | |||||
| import java.util.*; | |||||
| import java.util.concurrent.Executors; | |||||
| import java.util.concurrent.TimeUnit; | |||||
| import java.util.stream.Collectors; | |||||
| /** | |||||
| * Repräsentiert einen Yoshi-Bot. Der Bot initialisiert alle Ressourcen und schaltet sich in der startYoshiBot-Methode | |||||
| * online und beginnt dann zu lauschen. Parallel lauscht ein Thread auf Konsoleneingaben für administrative Zwecke, die | |||||
| * nicht über den Chat erledigt werden sollten. | |||||
| * | |||||
| * @author Yannic Link | |||||
| */ | |||||
| public final class YoshiBot { | |||||
| private CommandLine commandLineThread; | |||||
| /** | |||||
| * Erlaubt es einige Einstellungen vor und nach der Erzeugung eines Bots vorzunehmen. | |||||
| */ | |||||
| public JDABuilder jdaBuilder; | |||||
| /** | |||||
| * Instanz vom aktuell laufenden Bot. | |||||
| */ | |||||
| public JDA jda; | |||||
| /** | |||||
| * LavaPlayer AudioPlayerManager. | |||||
| */ | |||||
| public AudioPlayerManager audioPlayerManager; | |||||
| @Getter | |||||
| private Guild guild; | |||||
| public AudioPlayer audioPlayer; | |||||
| private static YoshiBot instance = null; | |||||
| @Getter | |||||
| private final Random random = new Random(); | |||||
| private final Set<Provider> allProvides = new HashSet<>(); | |||||
| /** | |||||
| * Initialisiert alle dynamisch hinzugefügten und statischen Ressourcen. Startet aber nicht den Bot selbst. | |||||
| */ | |||||
| public boolean init(String configPath) { | |||||
| return Resources.init(configPath); | |||||
| } | |||||
| /** | |||||
| * Startet den Bot und schaltet ihn online. Beginnt auf Konsoleneingaben für administrative Zwecke zu lauschen. | |||||
| **/ | |||||
| @SneakyThrows | |||||
| public void start() { | |||||
| System.out.println("Starte YoshiBot."); | |||||
| jdaBuilder = JDABuilder.createDefault(Resources.getJda_builder_string()); | |||||
| jdaBuilder.setAutoReconnect(true); | |||||
| jdaBuilder.addEventListeners(new DiscordEventListener()); | |||||
| audioPlayerManager = new DefaultAudioPlayerManager(); | |||||
| audioPlayerManager.registerSourceManager(new LocalAudioSourceManager()); | |||||
| AudioSourceManagers.registerRemoteSources(audioPlayerManager); | |||||
| jda = jdaBuilder.build(); | |||||
| try { | |||||
| jda.awaitReady(); | |||||
| } catch (InterruptedException e) { | |||||
| Logger.logError("Konnte nicht auf jda warten. Thread unterbrochen."); | |||||
| return; | |||||
| } | |||||
| jda.awaitReady(); | |||||
| guild = jda.getGuildById(Resources.getGuild_id()); | |||||
| audioPlayer = audioPlayerManager.createPlayer(); | |||||
| audioPlayer.addListener(new AudioPlayerListener(guild.getAudioManager())); | |||||
| guild.getAudioManager().setSendingHandler(new AudioSendHandlerImpl(audioPlayer)); | |||||
| jdaBuilder.setStatus(OnlineStatus.ONLINE); | |||||
| Logger.logInfo("YoshiBot online."); | |||||
| commandLineThread = new CommandLine(); | |||||
| commandLineThread.start(); | |||||
| StatusProviderFactory.createStatusProviders(Resources.getMcserver_config_file(), allProvides); | |||||
| //SauceProvider.init(300); | |||||
| Executors.newScheduledThreadPool(1).scheduleAtFixedRate(YoshiBot::setRandomActivity, 0, 10, TimeUnit.HOURS); | |||||
| Runtime.getRuntime().addShutdownHook(new Thread(() -> YoshiBot.getInstance().stop())); | |||||
| joinVoiceChannelWithMostMembers(); | |||||
| } | |||||
| public synchronized void stop() { | |||||
| allProvides.forEach(Provider::onStop); | |||||
| commandLineThread.stopCommandLine(); | |||||
| System.out.println("Beende YoshiBot ..."); | |||||
| jdaBuilder.setStatus(OnlineStatus.OFFLINE); | |||||
| jda.shutdown(); | |||||
| System.out.println("YoshiBot offline."); | |||||
| } | |||||
| /** | |||||
| * Leitet den Context an den CommandDistributor weiter. | |||||
| * | |||||
| * @param command Der Kontext für das Kommando. | |||||
| */ | |||||
| public static void executeCommand(YoshiCommandContext command) { | |||||
| YoshiCommandDistributor.distribute(command); | |||||
| } | |||||
| public static YoshiBot getInstance() { | |||||
| if (YoshiBot.instance == null) { | |||||
| YoshiBot.instance = new YoshiBot(); | |||||
| } | |||||
| return YoshiBot.instance; | |||||
| } | |||||
| @SneakyThrows | |||||
| private static void setRandomActivity() { | |||||
| YoshiBot yoshiBot = YoshiBot.getInstance(); | |||||
| yoshiBot.jda.awaitReady(); | |||||
| List<String> text = Files.readAllLines(new File(Resources.getActivitiesPath()).toPath()); | |||||
| String activity = text.get(yoshiBot.random.nextInt(text.size())); | |||||
| yoshiBot.jda.getPresence().setActivity(Activity.playing(activity)); | |||||
| Logger.logInfo("Setze Aktivität auf " + activity); | |||||
| } | |||||
| public synchronized void joinVoiceChannel(VoiceChannel vc) { | |||||
| if (vc == null) { | |||||
| guild.getAudioManager().closeAudioConnection(); | |||||
| return; | |||||
| } | |||||
| if (guild.getAudioManager().getConnectedChannel() != null && | |||||
| vc.getIdLong() == guild.getAudioManager().getConnectedChannel().getIdLong()) return; | |||||
| try { | |||||
| guild.getAudioManager().openAudioConnection(vc); | |||||
| } catch (InsufficientPermissionException e) { | |||||
| Logger.logWarning("Durfte dem VoiceChannel " + vc.getName() + " nicht beitreten."); | |||||
| } | |||||
| } | |||||
| public VoiceChannel joinVoiceChannelWithMostMembers() { | |||||
| Map<VoiceChannel, Long> vcAmount = | |||||
| guild.getVoiceChannels().stream() | |||||
| .filter(channel -> channel != guild.getAfkChannel()) | |||||
| .collect(Collectors.toMap(vc -> vc, | |||||
| vc -> vc.getMembers().stream() | |||||
| .filter(m -> !m.getUser().isBot()) | |||||
| .count())); | |||||
| VoiceChannel channel = | |||||
| vcAmount.entrySet().stream() | |||||
| .filter(vc -> vc.getValue() > 0) | |||||
| .max(Comparator.comparingLong(Map.Entry::getValue)) | |||||
| .map(Map.Entry::getKey) | |||||
| .orElse(null); | |||||
| if (channel != null) { | |||||
| PermissionOverride override = channel.getPermissionOverride( | |||||
| Objects.requireNonNull(guild.getMember(jda.getSelfUser()))); | |||||
| } | |||||
| joinVoiceChannel(channel); | |||||
| return channel; | |||||
| } | |||||
| public boolean playSound(File file, VoiceChannel vc) { | |||||
| if (!file.isFile()) return false; | |||||
| joinVoiceChannel(vc); | |||||
| audioPlayerManager.loadItem(file.getAbsolutePath(), new AudioLoadResultHandlerImpl()); | |||||
| return true; | |||||
| } | |||||
| public boolean sayTTS(String text, VoiceChannel vc) { | |||||
| String path = Resources.getEnsuredTempPath() + UUID.randomUUID() + ".opus"; | |||||
| try { | |||||
| ProcessBuilder pb = new ProcessBuilder( | |||||
| "python3", | |||||
| Resources.getTtsPath(), | |||||
| "--text", | |||||
| text, | |||||
| "--lang", | |||||
| "de", | |||||
| "--out", | |||||
| path); | |||||
| Process p = pb.start(); | |||||
| BufferedReader errorReader = new BufferedReader(new InputStreamReader(p.getErrorStream())); | |||||
| StringBuilder builder = new StringBuilder(); | |||||
| String line; | |||||
| while ((line = errorReader.readLine()) != null) { | |||||
| builder.append(line).append("\n"); | |||||
| } | |||||
| if (builder.toString().length() > 0) { | |||||
| Logger.logError(builder.toString()); | |||||
| } | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| return false; | |||||
| } | |||||
| return playSound(new File(path), vc); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,153 @@ | |||||
| package de.yannicpunktdee.yoshibot.utils; | |||||
| // | |||||
| // GifSequenceWriter.java | |||||
| // | |||||
| // Created by Elliot Kroo on 2009-04-25. | |||||
| // | |||||
| // This work is licensed under the Creative Commons Attribution 3.0 Unported | |||||
| // License. To view a copy of this license, visit | |||||
| // http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative | |||||
| // Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. | |||||
| import javax.imageio.*; | |||||
| import javax.imageio.metadata.*; | |||||
| import javax.imageio.stream.*; | |||||
| import java.awt.image.*; | |||||
| import java.io.*; | |||||
| import java.util.Iterator; | |||||
| public class GifSequenceWriter { | |||||
| protected ImageWriter gifWriter; | |||||
| protected ImageWriteParam imageWriteParam; | |||||
| protected IIOMetadata imageMetaData; | |||||
| /** | |||||
| * Creates a new GifSequenceWriter | |||||
| * | |||||
| * @param outputStream the ImageOutputStream to be written to | |||||
| * @param imageType one of the imageTypes specified in BufferedImage | |||||
| * @param timeBetweenFramesMS the time between frames in miliseconds | |||||
| * @param loopContinuously wether the gif should loop repeatedly | |||||
| * @throws IIOException if no gif ImageWriters are found | |||||
| * @author Elliot Kroo (elliot[at]kroo[dot]net) | |||||
| */ | |||||
| public GifSequenceWriter( | |||||
| ImageOutputStream outputStream, | |||||
| int imageType, | |||||
| int timeBetweenFramesMS, | |||||
| boolean loopContinuously) throws IIOException, IOException { | |||||
| // my method to create a writer | |||||
| gifWriter = getWriter(); | |||||
| imageWriteParam = gifWriter.getDefaultWriteParam(); | |||||
| ImageTypeSpecifier imageTypeSpecifier = | |||||
| ImageTypeSpecifier.createFromBufferedImageType(imageType); | |||||
| imageMetaData = | |||||
| gifWriter.getDefaultImageMetadata(imageTypeSpecifier, | |||||
| imageWriteParam); | |||||
| String metaFormatName = imageMetaData.getNativeMetadataFormatName(); | |||||
| IIOMetadataNode root = (IIOMetadataNode) | |||||
| imageMetaData.getAsTree(metaFormatName); | |||||
| IIOMetadataNode graphicsControlExtensionNode = getNode( | |||||
| root, | |||||
| "GraphicControlExtension"); | |||||
| graphicsControlExtensionNode.setAttribute("disposalMethod", "none"); | |||||
| graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE"); | |||||
| graphicsControlExtensionNode.setAttribute( | |||||
| "transparentColorFlag", | |||||
| "FALSE"); | |||||
| graphicsControlExtensionNode.setAttribute( | |||||
| "delayTime", | |||||
| Integer.toString(timeBetweenFramesMS / 10)); | |||||
| graphicsControlExtensionNode.setAttribute( | |||||
| "transparentColorIndex", | |||||
| "0"); | |||||
| IIOMetadataNode commentsNode = getNode(root, "CommentExtensions"); | |||||
| commentsNode.setAttribute("CommentExtension", "Created by MAH"); | |||||
| IIOMetadataNode appEntensionsNode = getNode( | |||||
| root, | |||||
| "ApplicationExtensions"); | |||||
| IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension"); | |||||
| child.setAttribute("applicationID", "NETSCAPE"); | |||||
| child.setAttribute("authenticationCode", "2.0"); | |||||
| int loop = loopContinuously ? 0 : 1; | |||||
| child.setUserObject(new byte[]{0x1, (byte) (loop & 0xFF), (byte) | |||||
| ((loop >> 8) & 0xFF)}); | |||||
| appEntensionsNode.appendChild(child); | |||||
| imageMetaData.setFromTree(metaFormatName, root); | |||||
| gifWriter.setOutput(outputStream); | |||||
| gifWriter.prepareWriteSequence(null); | |||||
| } | |||||
| public void writeToSequence(RenderedImage img) throws IOException { | |||||
| gifWriter.writeToSequence( | |||||
| new IIOImage( | |||||
| img, | |||||
| null, | |||||
| imageMetaData), | |||||
| imageWriteParam); | |||||
| } | |||||
| /** | |||||
| * Close this GifSequenceWriter object. This does not close the underlying | |||||
| * stream, just finishes off the GIF. | |||||
| */ | |||||
| public void close() throws IOException { | |||||
| gifWriter.endWriteSequence(); | |||||
| } | |||||
| /** | |||||
| * Returns the first available GIF ImageWriter using | |||||
| * ImageIO.getImageWritersBySuffix("gif"). | |||||
| * | |||||
| * @return a GIF ImageWriter object | |||||
| * @throws IIOException if no GIF image writers are returned | |||||
| */ | |||||
| private static ImageWriter getWriter() throws IIOException { | |||||
| Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif"); | |||||
| if (!iter.hasNext()) { | |||||
| throw new IIOException("No GIF Image Writers Exist"); | |||||
| } else { | |||||
| return iter.next(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Returns an existing child node, or creates and returns a new child node (if | |||||
| * the requested node does not exist). | |||||
| * | |||||
| * @param rootNode the <tt>IIOMetadataNode</tt> to search for the child node. | |||||
| * @param nodeName the name of the child node. | |||||
| * @return the child node, if found or a new node created with the given name. | |||||
| */ | |||||
| private static IIOMetadataNode getNode( | |||||
| IIOMetadataNode rootNode, | |||||
| String nodeName) { | |||||
| int nNodes = rootNode.getLength(); | |||||
| for (int i = 0; i < nNodes; i++) { | |||||
| if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) | |||||
| == 0) { | |||||
| return ((IIOMetadataNode) rootNode.item(i)); | |||||
| } | |||||
| } | |||||
| IIOMetadataNode node = new IIOMetadataNode(nodeName); | |||||
| rootNode.appendChild(node); | |||||
| return (node); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,43 @@ | |||||
| package de.yannicpunktdee.yoshibot.utils; | |||||
| public final class Logger { | |||||
| private static final String ANSI_RESET = "\u001B[0m"; | |||||
| private static final String ANSI_GREEN = "\u001B[32m"; | |||||
| private static final String ANSI_YELLOW = "\u001B[33m"; | |||||
| private static final String ANSI_BLUE = "\u001B[34m"; | |||||
| public static void logDebug(String message){ | |||||
| System.out.printf("%s[%tT: Yoshi::DEBUG] %s%s%n", | |||||
| ANSI_GREEN, | |||||
| System.currentTimeMillis(), | |||||
| message, | |||||
| ANSI_RESET); | |||||
| } | |||||
| public static void logInfo(String message){ | |||||
| System.out.printf("%s[%tT: Yoshi::INFO] %s%s%n", | |||||
| ANSI_BLUE, | |||||
| System.currentTimeMillis(), | |||||
| message, | |||||
| ANSI_RESET); | |||||
| } | |||||
| public static void logWarning(String message){ | |||||
| System.out.printf("%s[%tT: Yoshi::WARNING] %s%s%n", | |||||
| ANSI_YELLOW, | |||||
| System.currentTimeMillis(), | |||||
| message, | |||||
| ANSI_RESET); | |||||
| } | |||||
| public static void logError(String message){ | |||||
| System.err.printf("%s[%tT: Yoshi::ERROR] %s%s%n", | |||||
| ANSI_YELLOW, | |||||
| System.currentTimeMillis(), | |||||
| message, | |||||
| ANSI_RESET); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,7 @@ | |||||
| package de.yannicpunktdee.yoshibot.utils; | |||||
| public interface Provider { | |||||
| void onStop(); | |||||
| } | |||||
| @ -0,0 +1,317 @@ | |||||
| package de.yannicpunktdee.yoshibot.utils; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import lombok.Getter; | |||||
| import lombok.SneakyThrows; | |||||
| import org.json.JSONArray; | |||||
| import org.json.JSONObject; | |||||
| import java.io.File; | |||||
| import java.io.FileInputStream; | |||||
| import java.io.FileOutputStream; | |||||
| import java.io.IOException; | |||||
| import java.nio.file.Files; | |||||
| import java.nio.file.Paths; | |||||
| import java.util.*; | |||||
| import java.util.function.Function; | |||||
| import java.util.stream.StreamSupport; | |||||
| public final class Resources { | |||||
| @Getter | |||||
| private static boolean greetings_and_byebyes_on; | |||||
| @Getter | |||||
| private static int statusUpdate; | |||||
| @Getter | |||||
| private static long guild_id; | |||||
| @Getter | |||||
| private static String resourcePath, configPath, audioPath, tempPath, activitiesPath, greetingsPath, byebyesPath, | |||||
| sauceConfigPath, ttsPath, patPngPath, imagePath, bonkPngPath, jda_builder_string, status_channel, | |||||
| path_to_mcstatus, comebacksPath, departsPath, mcserver_config_file; | |||||
| private static List<String> greetings, byebyes, departs, comebacks; | |||||
| @Getter | |||||
| private static String[] restrict_commands_to_channel, filteredTags; | |||||
| @Getter | |||||
| private static final Map<String, List<String>> feedDetails = new HashMap<>(); | |||||
| private static Properties propertiesFile; | |||||
| public static boolean init(String resourcePathArg) { | |||||
| boolean isOk = initResources(resourcePathArg); | |||||
| if (isOk) isOk = initConfig(); | |||||
| if (isOk) isOk = initAudio(); | |||||
| if (isOk) isOk = initTemp(); | |||||
| if (isOk) isOk = initActivities(); | |||||
| if (isOk) isOk = initGreetingsAndByebyes(); | |||||
| if (isOk) isOk = initSauceConfig(); | |||||
| if (isOk) isOk = initTTS(); | |||||
| if (isOk) isOk = initJdaBuilderString(); | |||||
| if (isOk) isOk = initGuildId(); | |||||
| if (isOk) isOk = initChannelRestrict(); | |||||
| if (isOk) isOk = initTagFilter(); | |||||
| if (isOk) isOk = initPatPngPath(); | |||||
| if (isOk) isOk = initImages(); | |||||
| if (isOk) isOk = initBonkPngPath(); | |||||
| if (isOk) isOk = initStatusMessage(); | |||||
| if (isOk) Logger.logInfo("Die Konfigurationen wurden erfolgreich geladen."); | |||||
| else Logger.logError("Die Konfiguration konnte nicht geladen werden"); | |||||
| return isOk; | |||||
| } | |||||
| private static boolean initResources(String resourcePathArg) { | |||||
| Logger.logInfo("Versuche Resource-Verzeichnis zu finden."); | |||||
| resourcePath = (new File((resourcePathArg == null) ? "rsc" : resourcePathArg)).getAbsolutePath() | |||||
| .replace('\\', '/') + "/"; | |||||
| return verifyExists(resourcePath, File::isDirectory) != null; | |||||
| } | |||||
| @SneakyThrows | |||||
| private static boolean initConfig() { | |||||
| configPath = verifyExists(resourcePath + "Config.properties", File::isFile); | |||||
| if (configPath == null) { | |||||
| return false; | |||||
| } | |||||
| propertiesFile = new Properties(); | |||||
| propertiesFile.load(new FileInputStream(configPath)); | |||||
| return true; | |||||
| } | |||||
| private static boolean initAudio() { | |||||
| return (audioPath = verifyExists(resourcePath + "audio/", File::isDirectory)) != null; | |||||
| } | |||||
| public static String getPathToAudioFile(String name) { | |||||
| String filePathWithoutExtension = audioPath + name; | |||||
| if (new File(filePathWithoutExtension + ".opus").isFile()) { | |||||
| return filePathWithoutExtension + ".opus"; | |||||
| } else if (new File(filePathWithoutExtension + ".mp3").isFile()) { | |||||
| return filePathWithoutExtension + ".mp3"; | |||||
| } else { | |||||
| return ""; | |||||
| } | |||||
| } | |||||
| private static boolean initTemp() { | |||||
| Logger.logInfo("Versuche Temp-Verzeichnis zu finden."); | |||||
| String theoreticalTempPath = System.getProperty("java.io.tmpdir").replace('\\', '/') + "/yoshibot/"; | |||||
| tempPath = verifyExists(theoreticalTempPath, File::isDirectory); | |||||
| if (tempPath != null) { | |||||
| return true; | |||||
| } | |||||
| File tempDir = new File(theoreticalTempPath); | |||||
| if (tempDir.mkdir()) { | |||||
| return verifyExists(tempDir.getAbsolutePath(), File::isDirectory) != null; | |||||
| } else { | |||||
| Logger.logError("Temp-Verzeichnis konnte nicht erstellt werden."); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| public static String getEnsuredTempPath() { | |||||
| if (tempPath == null) { | |||||
| initTemp(); | |||||
| } | |||||
| File tempDir = new File(tempPath); | |||||
| if (!tempDir.isDirectory()) | |||||
| if (!tempDir.mkdir()) throw new Error("Could not make Temp directory"); | |||||
| return tempPath; | |||||
| } | |||||
| private static boolean initActivities() { | |||||
| activitiesPath = verifyExists(resourcePath + "activities.txt", File::isFile); | |||||
| return activitiesPath != null; | |||||
| } | |||||
| private static boolean initSauceConfig() { | |||||
| sauceConfigPath = verifyExists(resourcePath + "sauceConfig.json", File::isFile); | |||||
| return sauceConfigPath != null; | |||||
| } | |||||
| private static boolean initTTS() { | |||||
| ttsPath = verifyExists(resourcePath + "tts.py", File::isFile); | |||||
| return ttsPath != null; | |||||
| } | |||||
| @SneakyThrows | |||||
| private static boolean initJdaBuilderString() { | |||||
| if (verifyExists(resourcePath + "PrivateJdaBuilderString.txt", File::isFile) == null) { | |||||
| return false; | |||||
| } | |||||
| jda_builder_string = Files.readAllLines(new File(resourcePath + "PrivateJdaBuilderString.txt").toPath()).get(0); | |||||
| Logger.logInfo("jda_builder_string erfolgreich geladen"); | |||||
| return true; | |||||
| } | |||||
| private static boolean initPatPngPath() { | |||||
| patPngPath = verifyExists(resourcePath + "pats/", File::isDirectory); | |||||
| return patPngPath != null; | |||||
| } | |||||
| private static boolean initBonkPngPath() { | |||||
| bonkPngPath = verifyExists(resourcePath + "bonks/", File::isDirectory); | |||||
| return bonkPngPath != null; | |||||
| } | |||||
| private static boolean initGuildId() { | |||||
| if (!propertiesFile.containsKey("guild_id")) { | |||||
| Logger.logError("Die Config.properties benötigt das Attribut guild_id."); | |||||
| return false; | |||||
| } | |||||
| String raw = propertiesFile.getProperty("guild_id"); | |||||
| try { | |||||
| guild_id = Long.parseLong(raw); | |||||
| } catch (NumberFormatException e) { | |||||
| Logger.logError("Die angegebene guild_id ist keine Ganzzahl"); | |||||
| return false; | |||||
| } | |||||
| Logger.logInfo("guild_id erfolgreich geladen"); | |||||
| return true; | |||||
| } | |||||
| @SneakyThrows | |||||
| private static boolean initGreetingsAndByebyes() { | |||||
| greetingsPath = verifyExists(resourcePath + "greetings.txt", File::isFile); | |||||
| if (greetingsPath == null) { | |||||
| return false; | |||||
| } | |||||
| byebyesPath = verifyExists(resourcePath + "byebyes.txt", File::isFile); | |||||
| comebacksPath = verifyExists(resourcePath + "comebacks.txt", File::isFile); | |||||
| departsPath = verifyExists(resourcePath + "departs.txt", File::isFile); | |||||
| if (propertiesFile.containsKey("greetings_and_byebyes_on")) { | |||||
| greetings_and_byebyes_on = Boolean.parseBoolean(propertiesFile.getProperty("greetings_and_byebyes_on")); | |||||
| if (!greetings_and_byebyes_on) return true; | |||||
| } else greetings_and_byebyes_on = true; | |||||
| greetings = Files.readAllLines(Paths.get(greetingsPath)); | |||||
| byebyes = Files.readAllLines(Paths.get(byebyesPath)); | |||||
| comebacks = Files.readAllLines(Paths.get(comebacksPath)); | |||||
| departs = Files.readAllLines(Paths.get(departsPath)); | |||||
| return true; | |||||
| } | |||||
| public static String getRandomGreeting(String name) { | |||||
| return greetings_and_byebyes_on ? String.format(getRandomFrom(greetings), name) : null; | |||||
| } | |||||
| public static String getRandomByebye(String name) { | |||||
| return greetings_and_byebyes_on ? String.format(getRandomFrom(byebyes), name) : null; | |||||
| } | |||||
| public static String getRandomAfk(String name, boolean didComeBack) { | |||||
| return greetings_and_byebyes_on ? String.format(getRandomFrom(didComeBack ? comebacks : departs), name) : null; | |||||
| } | |||||
| private static String getRandomFrom(List<String> pool) { | |||||
| return pool.get(YoshiBot.getInstance().getRandom().nextInt(pool.size())); | |||||
| } | |||||
| private static boolean initChannelRestrict() { | |||||
| if (propertiesFile.containsKey("restrict_commands_to_channel")) | |||||
| restrict_commands_to_channel = propertiesFile.getProperty("restrict_commands_to_channel").split("\\s+"); | |||||
| return true; | |||||
| } | |||||
| private static boolean initTagFilter() { | |||||
| try { | |||||
| JSONObject configBase = new JSONObject( | |||||
| String.join("\n", | |||||
| Files.readAllLines(new File(sauceConfigPath).toPath()))); | |||||
| JSONArray filter = configBase.getJSONArray("tags_general_filter"); | |||||
| filteredTags = | |||||
| StreamSupport.stream(filter.spliterator(), false).map(i -> (String) i).toArray(String[]::new); | |||||
| JSONArray feeds = configBase.getJSONArray("feeds"); | |||||
| for (Object feedConfigObj : feeds) { | |||||
| JSONObject feedConfig = (JSONObject) feedConfigObj; | |||||
| List<String> tags = new ArrayList<>(); | |||||
| for (Object tagObj : feedConfig.getJSONArray("tags")) { | |||||
| if (tagObj instanceof String) { | |||||
| tags.add((String) tagObj); | |||||
| } else if (tagObj instanceof JSONArray) { | |||||
| StringBuilder sb = new StringBuilder().append("(%20"); | |||||
| sb.append(((JSONArray) tagObj).join("%20~%20")); | |||||
| tags.add(sb.append("%20)").toString().replace("\"", "")); | |||||
| } | |||||
| } | |||||
| feedDetails.put(feedConfig.getString("channel"), tags); | |||||
| } | |||||
| } catch (IOException e) { | |||||
| return false; | |||||
| } | |||||
| Logger.logInfo("tags_general_filter erfolgreich geladen"); | |||||
| return true; | |||||
| } | |||||
| private static boolean initImages() { | |||||
| imagePath = verifyExists(resourcePath + "image/", File::isDirectory); | |||||
| if (imagePath != null) { | |||||
| return true; | |||||
| } | |||||
| if (new File(resourcePath + "image/").mkdir()) { | |||||
| imagePath = verifyExists(resourcePath + "image/", File::isDirectory); | |||||
| Logger.logInfo("Bildordner erzeugt"); | |||||
| return true; | |||||
| } else { | |||||
| Logger.logError("Konnte Bildordner nicht erzeugen!"); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| private static String verifyExists(String filename, Function<File, Boolean> checkIsValidFile) { | |||||
| String[] split = filename.split("/"); | |||||
| Logger.logDebug(String.format("Versuche %s zu finden.", split[split.length - 1])); | |||||
| if (checkIsValidFile.apply(new File(filename))) { | |||||
| return filename; | |||||
| } else { | |||||
| Logger.logError(String.format("%s konnte nicht gefunden werden", filename)); | |||||
| return null; | |||||
| } | |||||
| } | |||||
| private static boolean initStatusMessage() { | |||||
| if (propertiesFile.containsKey("path_to_mcstatus") && | |||||
| propertiesFile.containsKey("status_channel") && | |||||
| propertiesFile.containsKey("path_to_status_json")) { | |||||
| status_channel = propertiesFile.getProperty("status_channel"); | |||||
| path_to_mcstatus = propertiesFile.getProperty("path_to_mcstatus"); | |||||
| mcserver_config_file = propertiesFile.getProperty("path_to_status_json"); | |||||
| try { | |||||
| statusUpdate = Integer.parseInt(propertiesFile.getProperty("status_update")); | |||||
| } catch (NumberFormatException e) { | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } else return false; | |||||
| } | |||||
| public static String getProperty(String key) { | |||||
| return propertiesFile.getProperty(key); | |||||
| } | |||||
| @SneakyThrows | |||||
| public synchronized static void setProperty(String key, String value) { | |||||
| propertiesFile.setProperty(key, value); | |||||
| propertiesFile.store(new FileOutputStream(configPath), ""); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,40 @@ | |||||
| package de.yannicpunktdee.yoshibot.utils; | |||||
| import lombok.SneakyThrows; | |||||
| import org.apache.commons.lang3.StringEscapeUtils; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStreamReader; | |||||
| import java.net.HttpURLConnection; | |||||
| import java.net.URL; | |||||
| public final class RestHelper { | |||||
| @SneakyThrows | |||||
| public static String getFromURL(String url) { | |||||
| StringBuilder response = new StringBuilder(""); | |||||
| HttpURLConnection con = null; | |||||
| try { | |||||
| con = (HttpURLConnection) (new URL(url)).openConnection(); | |||||
| con.setRequestMethod("GET"); | |||||
| BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream())); | |||||
| String line; | |||||
| while ((line = br.readLine()) != null) { | |||||
| response.append(StringEscapeUtils.unescapeHtml4(line)); | |||||
| } | |||||
| br.close(); | |||||
| } catch (IOException e) { | |||||
| return ""; | |||||
| } finally { | |||||
| if (con != null) con.disconnect(); | |||||
| } | |||||
| return response.toString(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,136 @@ | |||||
| package de.yannicpunktdee.yoshibot.utils; | |||||
| import lombok.Getter; | |||||
| import net.dv8tion.jda.api.EmbedBuilder; | |||||
| import org.json.JSONObject; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStreamReader; | |||||
| import java.time.LocalDateTime; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.Executors; | |||||
| import java.util.concurrent.ScheduledExecutorService; | |||||
| import java.util.concurrent.TimeUnit; | |||||
| import java.util.stream.Collectors; | |||||
| import java.util.stream.StreamSupport; | |||||
| public class StatusProvider implements Provider { | |||||
| private static final ScheduledExecutorService statusScheduler = Executors.newScheduledThreadPool(1); | |||||
| private int lastPlayersOnline = -1; | |||||
| @Getter | |||||
| private final String ip; | |||||
| private final String desc; | |||||
| private LocalDateTime timestampLastPlayerOnline; | |||||
| private final ProcessBuilder mcstatus = new ProcessBuilder(); | |||||
| public StatusProvider(String desc, int secondsPerTime, String ip, LocalDateTime timestamp) { | |||||
| this.timestampLastPlayerOnline = timestamp; | |||||
| this.desc = desc; | |||||
| this.ip = ip; | |||||
| this.mcstatus.command(Resources.getPath_to_mcstatus(), ip, "json"); | |||||
| statusScheduler.scheduleAtFixedRate(this::updateStatusMessage, 0, secondsPerTime, | |||||
| TimeUnit.SECONDS); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public void updateStatusMessage() { | |||||
| EmbedBuilder eb = new EmbedBuilder(); | |||||
| eb.setTitle(desc); | |||||
| eb.addField("IP", ip, false); | |||||
| try { | |||||
| Map<String, Object> serverInfo = getPlayersOnline(); | |||||
| if ((boolean) serverInfo.get("online")) { | |||||
| if ((boolean) serverInfo.get("Server starting")) { | |||||
| eb.addField("Server still Starting", "True", false); | |||||
| } else { | |||||
| int newPlayersOnline = (int) serverInfo.get("playerCount"); | |||||
| eb.addField("Version", (String) serverInfo.get("version"), true); | |||||
| eb.addField("MOTD", (String) serverInfo.get("motd"), true); | |||||
| eb.addField("Spieler online", newPlayersOnline + " / " + serverInfo.get("playerMax"), false); | |||||
| if (newPlayersOnline == 0) { | |||||
| if (lastPlayersOnline > newPlayersOnline) { | |||||
| timestampLastPlayerOnline = LocalDateTime.now(); | |||||
| StatusProviderFactory.updateTimestamp(this, timestampLastPlayerOnline); | |||||
| } | |||||
| eb.addField("Zuletzt gesehen", | |||||
| StatusProviderFactory.TIME_FORMATTER.format(timestampLastPlayerOnline), false); | |||||
| } else { | |||||
| eb.addField("Spieler:", String.join(", ", (List<String>) serverInfo.get("playerNames")), false); | |||||
| } | |||||
| lastPlayersOnline = newPlayersOnline; | |||||
| } | |||||
| } else { | |||||
| eb.addField("Offline", "", false); | |||||
| } | |||||
| StatusProviderFactory.updateStatus(this, eb.build()); | |||||
| } catch (IOException e) { | |||||
| Logger.logError(e.toString()); | |||||
| } | |||||
| } | |||||
| private Map<String, Object> getPlayersOnline() throws IOException { | |||||
| Process process = mcstatus.start(); | |||||
| try { | |||||
| process.waitFor(); | |||||
| } catch (InterruptedException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| if (process.exitValue() != 0) { | |||||
| Logger.logError("MCStatus on IP " + ip + " exited with errorcode " + process.exitValue()); | |||||
| } | |||||
| String output = new BufferedReader(new InputStreamReader(process.getInputStream())).lines() | |||||
| .collect(Collectors.joining()); | |||||
| Map<String, Object> result = new HashMap<>(); | |||||
| if (output.startsWith("The server did not respond to the query protocol.")) { | |||||
| result.put("online", false); | |||||
| return result; | |||||
| } | |||||
| JSONObject obj = new JSONObject(output); | |||||
| result.put("online", obj.getBoolean("online")); | |||||
| if (!obj.getBoolean("online")) { | |||||
| return result; | |||||
| } | |||||
| for (String key : new String[]{"player_count", "player_max", "players", "motd", "version"}) { | |||||
| if (!obj.has(key)) { | |||||
| result.put("Server starting", true); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| result.put("Server starting", false); | |||||
| result.put("playerCount", obj.getInt("player_count")); | |||||
| result.put("playerMax", obj.getInt("player_max")); | |||||
| result.put("playerNames", StreamSupport.stream(obj.getJSONArray("players").spliterator(), false) | |||||
| .map(jsonobj -> ((JSONObject) jsonobj).getString("name")).sorted().collect(Collectors.toList())); | |||||
| result.put("motd", obj.getString("motd")); | |||||
| result.put("version", obj.getString("version")); | |||||
| return result; | |||||
| } | |||||
| @Override | |||||
| public void onStop() { | |||||
| StatusProviderFactory.updateTimestamp(this, timestampLastPlayerOnline); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,98 @@ | |||||
| package de.yannicpunktdee.yoshibot.utils; | |||||
| import de.yannicpunktdee.yoshibot.main.YoshiBot; | |||||
| import lombok.NonNull; | |||||
| import lombok.SneakyThrows; | |||||
| import net.dv8tion.jda.api.MessageBuilder; | |||||
| import net.dv8tion.jda.api.entities.Message; | |||||
| import net.dv8tion.jda.api.entities.MessageEmbed; | |||||
| import net.dv8tion.jda.api.entities.TextChannel; | |||||
| import net.dv8tion.jda.api.exceptions.ErrorResponseException; | |||||
| import org.json.JSONArray; | |||||
| import org.json.JSONObject; | |||||
| import java.io.BufferedWriter; | |||||
| import java.io.File; | |||||
| import java.io.FileWriter; | |||||
| import java.nio.file.Files; | |||||
| import java.time.LocalDateTime; | |||||
| import java.time.format.DateTimeFormatter; | |||||
| import java.util.*; | |||||
| public abstract class StatusProviderFactory { | |||||
| public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss E, dd.MM.yyyy", | |||||
| Locale.GERMANY); | |||||
| private static final Map<StatusProvider, Long> providers = new HashMap<>(); | |||||
| private static final Map<StatusProvider, JSONObject> providerJSON = new HashMap<>(); | |||||
| private static JSONObject rootJSON; | |||||
| private static File configFile; | |||||
| @NonNull | |||||
| private static final TextChannel statusChannel = | |||||
| Objects.requireNonNull(YoshiBot.getInstance().getGuild().getTextChannelById(Resources.getStatus_channel())); | |||||
| @SneakyThrows | |||||
| public static void createStatusProviders(String configFilePath, Set<Provider> allProviders) { | |||||
| configFile = new File(configFilePath); | |||||
| rootJSON = new JSONObject(String.join("\n", Files.readAllLines(configFile.toPath()))); | |||||
| final JSONArray servers = rootJSON.getJSONArray("servers"); | |||||
| for (int i = 0; i < servers.length(); i++) { | |||||
| final JSONObject server = servers.getJSONObject(i); | |||||
| final String ip = server.getString("ip"); | |||||
| final String name = server.getString("name"); | |||||
| if (!server.has("timestamp")) { | |||||
| server.put("timestamp", TIME_FORMATTER.format(LocalDateTime.now())); | |||||
| } | |||||
| final LocalDateTime timestamp = LocalDateTime.parse(server.getString("timestamp"), TIME_FORMATTER); | |||||
| if (!server.has("message")) { | |||||
| server.put("message", -1); | |||||
| } | |||||
| final long msg = ensureMessageId(server.getLong("message")); | |||||
| server.put("message", msg); | |||||
| StatusProvider provider = new StatusProvider(name, Resources.getStatusUpdate(), ip, timestamp); | |||||
| providers.put(provider, msg); | |||||
| providerJSON.put(provider, servers.getJSONObject(i)); | |||||
| } | |||||
| allProviders.addAll(providers.keySet()); | |||||
| writeJSON(); | |||||
| } | |||||
| public static void updateStatus(StatusProvider provider, MessageEmbed msg) { | |||||
| statusChannel.editMessageById(providers.get(provider), msg).queue(); | |||||
| } | |||||
| public static void updateTimestamp(StatusProvider provider, LocalDateTime timestamp) { | |||||
| providerJSON.get(provider).put("timestamp", TIME_FORMATTER.format(timestamp)); | |||||
| writeJSON(); | |||||
| } | |||||
| @SneakyThrows | |||||
| private static void writeJSON() { | |||||
| BufferedWriter bw = new BufferedWriter(new FileWriter(configFile)); | |||||
| bw.write(rootJSON.toString(2)); | |||||
| bw.flush(); | |||||
| } | |||||
| private static long createStatusMessage() { | |||||
| Logger.logInfo("Creating new Status Message!"); | |||||
| return StatusProviderFactory.statusChannel.sendMessage( | |||||
| new MessageBuilder() | |||||
| .append("ServerInformation") | |||||
| .build()) | |||||
| .complete().getIdLong(); | |||||
| } | |||||
| private static long ensureMessageId(long messageId) { | |||||
| try { | |||||
| Message msg = StatusProviderFactory.statusChannel.retrieveMessageById(messageId).complete(); | |||||
| return msg.getIdLong(); | |||||
| } catch (ErrorResponseException e) { | |||||
| return createStatusMessage(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,5 @@ | |||||
| distributionBase=GRADLE_USER_HOME | |||||
| distributionPath=wrapper/dists | |||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip | |||||
| zipStoreBase=GRADLE_USER_HOME | |||||
| zipStorePath=wrapper/dists | |||||
| @ -0,0 +1,185 @@ | |||||
| #!/usr/bin/env sh | |||||
| # | |||||
| # Copyright 2015 the original author or authors. | |||||
| # | |||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| # you may not use this file except in compliance with the License. | |||||
| # You may obtain a copy of the License at | |||||
| # | |||||
| # https://www.apache.org/licenses/LICENSE-2.0 | |||||
| # | |||||
| # Unless required by applicable law or agreed to in writing, software | |||||
| # distributed under the License is distributed on an "AS IS" BASIS, | |||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| # See the License for the specific language governing permissions and | |||||
| # limitations under the License. | |||||
| # | |||||
| ############################################################################## | |||||
| ## | |||||
| ## Gradle start up script for UN*X | |||||
| ## | |||||
| ############################################################################## | |||||
| # Attempt to set APP_HOME | |||||
| # Resolve links: $0 may be a link | |||||
| PRG="$0" | |||||
| # Need this for relative symlinks. | |||||
| while [ -h "$PRG" ] ; do | |||||
| ls=`ls -ld "$PRG"` | |||||
| link=`expr "$ls" : '.*-> \(.*\)$'` | |||||
| if expr "$link" : '/.*' > /dev/null; then | |||||
| PRG="$link" | |||||
| else | |||||
| PRG=`dirname "$PRG"`"/$link" | |||||
| fi | |||||
| done | |||||
| SAVED="`pwd`" | |||||
| cd "`dirname \"$PRG\"`/" >/dev/null | |||||
| APP_HOME="`pwd -P`" | |||||
| cd "$SAVED" >/dev/null | |||||
| APP_NAME="Gradle" | |||||
| APP_BASE_NAME=`basename "$0"` | |||||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | |||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | |||||
| MAX_FD="maximum" | |||||
| warn () { | |||||
| echo "$*" | |||||
| } | |||||
| die () { | |||||
| echo | |||||
| echo "$*" | |||||
| echo | |||||
| exit 1 | |||||
| } | |||||
| # OS specific support (must be 'true' or 'false'). | |||||
| cygwin=false | |||||
| msys=false | |||||
| darwin=false | |||||
| nonstop=false | |||||
| case "`uname`" in | |||||
| CYGWIN* ) | |||||
| cygwin=true | |||||
| ;; | |||||
| Darwin* ) | |||||
| darwin=true | |||||
| ;; | |||||
| MINGW* ) | |||||
| msys=true | |||||
| ;; | |||||
| NONSTOP* ) | |||||
| nonstop=true | |||||
| ;; | |||||
| esac | |||||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |||||
| # Determine the Java command to use to start the JVM. | |||||
| if [ -n "$JAVA_HOME" ] ; then | |||||
| if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |||||
| # IBM's JDK on AIX uses strange locations for the executables | |||||
| JAVACMD="$JAVA_HOME/jre/sh/java" | |||||
| else | |||||
| JAVACMD="$JAVA_HOME/bin/java" | |||||
| fi | |||||
| if [ ! -x "$JAVACMD" ] ; then | |||||
| die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | |||||
| Please set the JAVA_HOME variable in your environment to match the | |||||
| location of your Java installation." | |||||
| fi | |||||
| else | |||||
| JAVACMD="java" | |||||
| which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |||||
| Please set the JAVA_HOME variable in your environment to match the | |||||
| location of your Java installation." | |||||
| fi | |||||
| # Increase the maximum file descriptors if we can. | |||||
| if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | |||||
| MAX_FD_LIMIT=`ulimit -H -n` | |||||
| if [ $? -eq 0 ] ; then | |||||
| if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | |||||
| MAX_FD="$MAX_FD_LIMIT" | |||||
| fi | |||||
| ulimit -n $MAX_FD | |||||
| if [ $? -ne 0 ] ; then | |||||
| warn "Could not set maximum file descriptor limit: $MAX_FD" | |||||
| fi | |||||
| else | |||||
| warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | |||||
| fi | |||||
| fi | |||||
| # For Darwin, add options to specify how the application appears in the dock | |||||
| if $darwin; then | |||||
| GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | |||||
| fi | |||||
| # For Cygwin or MSYS, switch paths to Windows format before running java | |||||
| if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then | |||||
| APP_HOME=`cygpath --path --mixed "$APP_HOME"` | |||||
| CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | |||||
| JAVACMD=`cygpath --unix "$JAVACMD"` | |||||
| # We build the pattern for arguments to be converted via cygpath | |||||
| ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | |||||
| SEP="" | |||||
| for dir in $ROOTDIRSRAW ; do | |||||
| ROOTDIRS="$ROOTDIRS$SEP$dir" | |||||
| SEP="|" | |||||
| done | |||||
| OURCYGPATTERN="(^($ROOTDIRS))" | |||||
| # Add a user-defined pattern to the cygpath arguments | |||||
| if [ "$GRADLE_CYGPATTERN" != "" ] ; then | |||||
| OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | |||||
| fi | |||||
| # Now convert the arguments - kludge to limit ourselves to /bin/sh | |||||
| i=0 | |||||
| for arg in "$@" ; do | |||||
| CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | |||||
| CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | |||||
| if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | |||||
| eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | |||||
| else | |||||
| eval `echo args$i`="\"$arg\"" | |||||
| fi | |||||
| i=`expr $i + 1` | |||||
| done | |||||
| case $i in | |||||
| 0) set -- ;; | |||||
| 1) set -- "$args0" ;; | |||||
| 2) set -- "$args0" "$args1" ;; | |||||
| 3) set -- "$args0" "$args1" "$args2" ;; | |||||
| 4) set -- "$args0" "$args1" "$args2" "$args3" ;; | |||||
| 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | |||||
| 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | |||||
| 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | |||||
| 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | |||||
| 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | |||||
| esac | |||||
| fi | |||||
| # Escape application args | |||||
| save () { | |||||
| for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | |||||
| echo " " | |||||
| } | |||||
| APP_ARGS=`save "$@"` | |||||
| # Collect all arguments for the java command, following the shell quoting and substitution rules | |||||
| eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | |||||
| exec "$JAVACMD" "$@" | |||||
| @ -0,0 +1,89 @@ | |||||
| @rem | |||||
| @rem Copyright 2015 the original author or authors. | |||||
| @rem | |||||
| @rem Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| @rem you may not use this file except in compliance with the License. | |||||
| @rem You may obtain a copy of the License at | |||||
| @rem | |||||
| @rem https://www.apache.org/licenses/LICENSE-2.0 | |||||
| @rem | |||||
| @rem Unless required by applicable law or agreed to in writing, software | |||||
| @rem distributed under the License is distributed on an "AS IS" BASIS, | |||||
| @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| @rem See the License for the specific language governing permissions and | |||||
| @rem limitations under the License. | |||||
| @rem | |||||
| @if "%DEBUG%" == "" @echo off | |||||
| @rem ########################################################################## | |||||
| @rem | |||||
| @rem Gradle startup script for Windows | |||||
| @rem | |||||
| @rem ########################################################################## | |||||
| @rem Set local scope for the variables with windows NT shell | |||||
| if "%OS%"=="Windows_NT" setlocal | |||||
| set DIRNAME=%~dp0 | |||||
| if "%DIRNAME%" == "" set DIRNAME=. | |||||
| set APP_BASE_NAME=%~n0 | |||||
| set APP_HOME=%DIRNAME% | |||||
| @rem Resolve any "." and ".." in APP_HOME to make it shorter. | |||||
| for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | |||||
| @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |||||
| set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | |||||
| @rem Find java.exe | |||||
| if defined JAVA_HOME goto findJavaFromJavaHome | |||||
| set JAVA_EXE=java.exe | |||||
| %JAVA_EXE% -version >NUL 2>&1 | |||||
| if "%ERRORLEVEL%" == "0" goto execute | |||||
| echo. | |||||
| echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |||||
| echo. | |||||
| echo Please set the JAVA_HOME variable in your environment to match the | |||||
| echo location of your Java installation. | |||||
| goto fail | |||||
| :findJavaFromJavaHome | |||||
| set JAVA_HOME=%JAVA_HOME:"=% | |||||
| set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |||||
| if exist "%JAVA_EXE%" goto execute | |||||
| echo. | |||||
| echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | |||||
| echo. | |||||
| echo Please set the JAVA_HOME variable in your environment to match the | |||||
| echo location of your Java installation. | |||||
| goto fail | |||||
| :execute | |||||
| @rem Setup the command line | |||||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |||||
| @rem Execute Gradle | |||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | |||||
| :end | |||||
| @rem End local scope for the variables with windows NT shell | |||||
| if "%ERRORLEVEL%"=="0" goto mainEnd | |||||
| :fail | |||||
| rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | |||||
| rem the _cmd.exe /c_ return code! | |||||
| if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | |||||
| exit /b 1 | |||||
| :mainEnd | |||||
| if "%OS%"=="Windows_NT" endlocal | |||||
| :omega | |||||
| @ -0,0 +1,2 @@ | |||||
| 255*.txt | |||||
| mcstatus.sh | |||||
| @ -0,0 +1,14 @@ | |||||
| #Sun Mar 13 19:43:48 CET 2022 | |||||
| status_message_vanilla=890007862456238120 | |||||
| status_channel=889880296168755231 | |||||
| restrict_commands_to_channel=bot-muell schrein-auf-den-bot | |||||
| status_message_eighteen_vanilla=952638126327726150 | |||||
| status_message_selina=952638127451820042 | |||||
| mc_server=85.214.148.23 | |||||
| greetings_and_byebyes_on=true | |||||
| guild_id=801554100814741524 | |||||
| status_message_modded=952638124197040139 | |||||
| status_message_kreativ=952638123324616735 | |||||
| status_update=30 | |||||
| path_to_mcstatus=/home/paul/.local/bin/mcstatus | |||||
| path_to_status_json=/home/paul/gits/YoshiBot/rsc/mcservers.json | |||||
| @ -0,0 +1,11 @@ | |||||
| rsc | |||||
| |-- .gitkeep | |||||
| |-- Ordnerstruktur.txt | |||||
| |-- Config.properties | |||||
| |-- tts.py | |||||
| |-- audio | |||||
| |-- temp | |||||
| |-- temp_1.opus | |||||
| |-- temp_2.opus | |||||
| |-- audio_1.opus | |||||
| |-- audio_2.opus | |||||
| @ -0,0 +1,7 @@ | |||||
| Haare waschen | |||||
| Kleine Menschen (Honey) verprügeln | |||||
| Im Kosovo Hexen verbennen | |||||
| Magnesiumcarbonat schnupfen | |||||
| Offene Wunden mit Sekundenkleber verschließen | |||||
| Sich 'nen saftigen Knackarsch reinzimmern | |||||
| Spielt Rasputin bei Just Dance | |||||
| @ -0,0 +1,27 @@ | |||||
| Verpiss dich %s. | |||||
| Bye bye %s. | |||||
| Tschö %s. | |||||
| Geh kacken %s. | |||||
| %s verlässt uns unu. | |||||
| %s ist kurz Halle Peißen. | |||||
| %s geht Haare waschen. | |||||
| %s ist kurz den Ofen schrubben. | |||||
| %s ist ein Verräter. | |||||
| Algengrütze, %s ist weg. | |||||
| Walfischdreck, %s ist weg. | |||||
| Ach verdammt, das Killerkaninchen hat %s erwischt. | |||||
| %s geht den Agaven-Dickbaum suchen | |||||
| %s geht Müll mit Kevin aus Oldenburg sammeln. | |||||
| %s macht nun schmutzige Sachen auf einem anderen Discord. | |||||
| %s geht nun mit Seliner telefonieren. | |||||
| %s ist ein Wichser. | |||||
| Man darf nun über %s lästern. | |||||
| %s wurde fachgerecht entsorgt. | |||||
| %s geht jetzt seine Nachbarn teebeuteln. | |||||
| Oh nein wir haben %s verloren. | |||||
| Unga bunga, wo %s? | |||||
| %s hasst jeden hier. | |||||
| %s hats erwischt. | |||||
| %s ist aus zu großer Höhe gefallen. | |||||
| %s hat versucht in Lava zu schwimmen. | |||||
| %s ist an Pauls Kartoffelsalat gestorben.. | |||||
| @ -0,0 +1,3 @@ | |||||
| %s hat jetzt fertig. | |||||
| %s ist wieder aufnahmebereit. | |||||
| %s hat sich wieder erholt. | |||||
| @ -0,0 +1,4 @@ | |||||
| %s ist dann mal abwesend. | |||||
| %s hat jetzt erstmal nix mehr zu sagen. | |||||
| %s ist verschwunden. | |||||
| %s kommt bald wieder.... vielleicht. | |||||
| @ -0,0 +1,21 @@ | |||||
| Sei gegrüßt Genosse %s. | |||||
| Uwu %s ist uns beigetreten. | |||||
| Gelobt sei %s. | |||||
| Hurra hurra %s ist da. | |||||
| %s ist gekommen um uns zu erleuchten. | |||||
| %s was ist deine Weisheit? | |||||
| Es erscheine: %s! | |||||
| Ja ach scheiß doch die Wand an, %s ist da! | |||||
| %s ist vom Rauchen zurück. | |||||
| %s beehrt uns juhu tralala. | |||||
| %s ist gekommen um zu kommen. | |||||
| %s ich wähle dich! | |||||
| Zuerst war das nichts, dann %s. | |||||
| Ist es ein Flugzeug? Ist es ein Vogel? Nein es ist %s. | |||||
| Hajoa schleck ma ja %s ist da. | |||||
| %s betritt das Hornyjail. | |||||
| Moin %s. | |||||
| %s kommt für billige Unterhaltung. | |||||
| %s lässt sich für 2 Euro in die Eier treten. | |||||
| %s ist ein Mann / Männin von Ähre. | |||||
| %s hat sich entschieden bliat zu kosten. | |||||
| @ -0,0 +1,20 @@ | |||||
| {"servers": [ | |||||
| { | |||||
| "ip": "85.214.148.23:25568", | |||||
| "name": "MCMuffing™®㋏ Inc.", | |||||
| "message": 960983446350594049, | |||||
| "timestamp": "13:43:35 Fr., 15.04.2022" | |||||
| }, | |||||
| { | |||||
| "ip": "85.214.148.23:25566", | |||||
| "name": "Enigmatica 6: Expert 1.0.0", | |||||
| "message": 960983448410013737, | |||||
| "timestamp": "21:25:08 Di., 05.04.2022" | |||||
| }, | |||||
| { | |||||
| "ip": "85.214.148.23:25570", | |||||
| "name": "Medieval Minecraft 1.16.5 v52", | |||||
| "message": 960983449961922600, | |||||
| "timestamp": "11:30:09 So., 10.04.2022" | |||||
| } | |||||
| ]} | |||||
| @ -0,0 +1,18 @@ | |||||
| import sys | |||||
| from gtts import gTTS | |||||
| import os | |||||
| import argparse | |||||
| parser = argparse.ArgumentParser() | |||||
| parser.add_argument("--text") | |||||
| parser.add_argument("--lang") | |||||
| parser.add_argument("--out") | |||||
| args = parser.parse_args() | |||||
| mytext = args.text | |||||
| language = args.lang | |||||
| output = args.out | |||||
| myobj = gTTS(text=mytext, lang=language, slow=False) | |||||
| myobj.save(output) | |||||
| @ -0,0 +1,11 @@ | |||||
| /* | |||||
| * This file was generated by the Gradle 'init' task. | |||||
| * | |||||
| * The settings file is used to specify which projects to include in your build. | |||||
| * | |||||
| * Detailed information about configuring a multi-project build in Gradle can be found | |||||
| * in the user manual at https://docs.gradle.org/6.8.3/userguide/multi_project_builds.html | |||||
| */ | |||||
| rootProject.name = 'YoshiBot' | |||||
| include('app') | |||||