| @ -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') | |||