commit 79272efd9a8a7fd65e49fd061f8e56748b53a3c7 Author: yl60lepu Date: Thu Mar 25 00:18:50 2021 +0100 Initial Commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..00a51af --- /dev/null +++ b/.gitattributes @@ -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 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a90bfa --- /dev/null +++ b/.gitignore @@ -0,0 +1,196 @@ +# ---> 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/ + +# 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 + +.classpath + +app/src/main/resources/* +!app/src/main/resources/.gitkeep \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2f6bb63 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# YoshiBot + +Ein in Java geschriebener Discordbot, der lustige Sachen kann. + +##Einrichtung + +Ordner app/src/main/resources anlegen und darein die Config.properties anlegen. Diese sollte folgende Werte enthalten: + +- jda_builder_string -> Client Secret +- audio_source_directory -> Verzeichnis, in dem die Audio-Dateien liegen, auch unter Windows mit / anstatt \ (dieses Verzeichnis sollte natürlich existieren) +- restrict_commands_to_channel -> Textkanalname, auf dem die Botkommandos empfangen werden \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aac99de --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,38 @@ +/* + * 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' +} + +application { + // Define the main class for the application. + mainClass = 'de.yannicpunktdee.yoshibot.main.Main' +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioController.java b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioController.java new file mode 100644 index 0000000..f7bbdda --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioController.java @@ -0,0 +1,30 @@ +package de.yannicpunktdee.yoshibot.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; + +import de.yannicpunktdee.yoshibot.main.YoshiBot; +import net.dv8tion.jda.api.entities.Guild; + +public class AudioController { + + private Guild guild; + private AudioPlayer audioPlayer; + + + public AudioController(Guild guild) { + this.guild = guild; + this.audioPlayer = YoshiBot.audioPlayerManager.createPlayer(); + audioPlayer.addListener(new AudioPlayerListener(guild.getAudioManager())); + + this.guild.getAudioManager().setSendingHandler(new AudioSendHandlerImpl(audioPlayer)); + } + + public Guild getGuild() { + return guild; + } + + public AudioPlayer getAudioPlayer() { + return audioPlayer; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioControllerManager.java b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioControllerManager.java new file mode 100644 index 0000000..53cd6cb --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioControllerManager.java @@ -0,0 +1,28 @@ +package de.yannicpunktdee.yoshibot.audio; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import de.yannicpunktdee.yoshibot.main.YoshiBot; + +public class AudioControllerManager { + + public Map audioController; + + + public AudioControllerManager() { + audioController = new ConcurrentHashMap(); + } + + public AudioController getController(long guildId) { + AudioController ac = null; + if(audioController.containsKey(guildId)) + ac = audioController.get(guildId); + else { + ac = new AudioController(YoshiBot.jda.getGuildById(guildId)); + audioController.put(guildId, ac); + } + return ac; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioLoadResultHandlerImpl.java b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioLoadResultHandlerImpl.java new file mode 100644 index 0000000..9b7f3bf --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioLoadResultHandlerImpl.java @@ -0,0 +1,39 @@ +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; + +public class AudioLoadResultHandlerImpl implements AudioLoadResultHandler { + + private AudioController audioController; + + + public AudioLoadResultHandlerImpl(AudioController audioController) { + this.audioController = audioController; + } + + @Override + public void trackLoaded(AudioTrack track) { + audioController.getAudioPlayer().playTrack(track); + } + + @Override + public void playlistLoaded(AudioPlaylist playlist) { + System.out.println("Kann aktuell noch keine Playlists abspielen"); + } + + @Override + public void noMatches() { + System.out.println("Nothing found"); + } + + @Override + public void loadFailed(FriendlyException exception) { + System.out.println("Loading failed"); + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioPlayerListener.java b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioPlayerListener.java new file mode 100644 index 0000000..4eab8dd --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioPlayerListener.java @@ -0,0 +1,25 @@ +package de.yannicpunktdee.yoshibot.audio; + +import com.sedmelluq.discord.lavaplayer.player.event.AudioEvent; +import com.sedmelluq.discord.lavaplayer.player.event.AudioEventListener; + +import net.dv8tion.jda.api.managers.AudioManager; + +public class AudioPlayerListener implements AudioEventListener { + + private AudioManager audioManager; + + + public AudioPlayerListener(AudioManager audioManager) { + this.audioManager = audioManager; + } + + @Override + public void onEvent(AudioEvent event) { + if(event.player.getPlayingTrack() == null) { + event.player.stopTrack(); + audioManager.closeAudioConnection(); + } + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioSendHandlerImpl.java b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioSendHandlerImpl.java new file mode 100644 index 0000000..ac9c105 --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioSendHandlerImpl.java @@ -0,0 +1,37 @@ +package de.yannicpunktdee.yoshibot.audio; + +import java.nio.ByteBuffer; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; + +import de.yannicpunktdee.yoshibot.main.YoshiBot; +import net.dv8tion.jda.api.audio.AudioSendHandler; + +public class AudioSendHandlerImpl implements AudioSendHandler { + + private 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()); + } + + @Override + public boolean isOpus() { + return true; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommand.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommand.java new file mode 100644 index 0000000..2022e3d --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommand.java @@ -0,0 +1,43 @@ +package de.yannicpunktdee.yoshibot.command; + +/** + * 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; + + + /** + * 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)){ + sendMessage("Fehlende Argumente"); + return false; + } + return true; + } + + protected void sendMessage(String message) { + context.getEvent().getTextChannel().sendMessage(message).queue(); + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandContext.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandContext.java new file mode 100644 index 0000000..f486b47 --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandContext.java @@ -0,0 +1,288 @@ +package de.yannicpunktdee.yoshibot.command; + +import java.util.HashMap; +import java.util.Map; + +import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor.Action; +import de.yannicpunktdee.yoshibot.main.YoshiBot; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.VoiceChannel; +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 static 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 static 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 arguments; + + 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 + * @param user Der Benutzer, der das Kommando geschickt hat. + * @param channel Der Textchannel in den das Kommando geschickt wurde. + */ + public YoshiCommandContext(String argumentsString, MessageReceivedEvent event) { + this.event = event; + + argumentsString = argumentsString.trim(); + + 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(); + } + /** + * 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; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandDistributor.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandDistributor.java new file mode 100644 index 0000000..3d5743b --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandDistributor.java @@ -0,0 +1,106 @@ +package de.yannicpunktdee.yoshibot.command; + +import de.yannicpunktdee.yoshibot.command.commands.HelpCommand; +import de.yannicpunktdee.yoshibot.command.commands.JokeCommand; +import de.yannicpunktdee.yoshibot.command.commands.PlayCommand; +import de.yannicpunktdee.yoshibot.command.commands.SayCommand; +import de.yannicpunktdee.yoshibot.command.commands.StopCommand; + +/** + * 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 STOP: + command = new StopCommand(context); + break; + 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; + 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, + /** + * Fahre den Yoshi-Bot herunter (nur mit speziellen Berechtigungen m�glich). + */ + STOP, + /** + * 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, + /** + * Listet alle zugewiesenen Ressourcen auf. Mit der Option -type [all|link|audio|video] l�sst sich das Format + * der Ressource spezifizieren. + */ + LIST, + /** + * 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. + */ + DELETE + } + + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/HelpCommand.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/HelpCommand.java new file mode 100644 index 0000000..65f34d9 --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/HelpCommand.java @@ -0,0 +1,15 @@ +package de.yannicpunktdee.yoshibot.command.commands; + +import de.yannicpunktdee.yoshibot.command.YoshiCommand; +import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; + +public class HelpCommand extends YoshiCommand { + + public HelpCommand(YoshiCommandContext context) { + super(context); + } + + @Override + public boolean execute() {return true;} + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/JokeCommand.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/JokeCommand.java new file mode 100644 index 0000000..95c7e56 --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/JokeCommand.java @@ -0,0 +1,140 @@ +package de.yannicpunktdee.yoshibot.command.commands; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.Random; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.json.JSONException; +import org.json.JSONObject; + +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; + +/** + * Schickt einen zufälligen Jokus aus einer zufällig ausgewählten Quelle in den Textchannel. + * @author Yannic Link + */ +@SuppressWarnings("deprecation") +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 = new Random(); + 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) { + sendMessage("Es wurde kein channel angegeben."); + return false; + } + List channels = YoshiBot.jda.getTextChannelsByName(context.getArgument("channel"), true); + if(channels.isEmpty()) { + sendMessage("Der Kanalname konnte nicht gefunden werden."); + return false; + } + channels.get(0).sendMessage(message).queue(); + }else { + sendMessage(message); + } + return true; + } + + private String getFromURL(String url) throws IOException { + 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 null; + }finally { + if(con != null) con.disconnect(); + } + + return response.toString(); + } + + 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 | IOException 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 | IOException 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 | IOException e) { + return "Konnte keinen Jokus von \"" + url + "\" laden."; + } + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PlayCommand.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PlayCommand.java new file mode 100644 index 0000000..6e61dd3 --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PlayCommand.java @@ -0,0 +1,46 @@ +package de.yannicpunktdee.yoshibot.command.commands; + +import java.util.List; + +import de.yannicpunktdee.yoshibot.audio.AudioController; +import de.yannicpunktdee.yoshibot.audio.AudioLoadResultHandlerImpl; +import de.yannicpunktdee.yoshibot.command.YoshiCommand; +import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; +import de.yannicpunktdee.yoshibot.main.Resources; +import de.yannicpunktdee.yoshibot.main.YoshiBot; +import net.dv8tion.jda.api.entities.VoiceChannel; + +public class PlayCommand extends YoshiCommand { + + protected final String[] requiredArguments = new String[] {"channel", "name"}; + + + public PlayCommand(YoshiCommandContext context) { + super(context); + } + + @Override + public boolean execute() { + if(!super.execute()) return false; + + List channels = YoshiBot.jda.getVoiceChannelsByName(context.getArgument("channel"), true); + if(!(channels.size() > 0)) { + context.getEvent().getTextChannel().sendMessage("Der Kanalname konnte nicht gefunden werden.").queue(); + return false; + } + VoiceChannel vc = channels.get(0); + + String fileName = Resources.getAudioFilePath(context.getArgument("name")); + if(fileName == null) { + context.getEvent().getTextChannel().sendMessage("Audio konnte nicht gefunden werden.").queue(); + return false; + } + + AudioController ac = YoshiBot.audioControllerManager.getController(vc.getGuild().getIdLong()); + vc.getGuild().getAudioManager().openAudioConnection(vc); + YoshiBot.audioPlayerManager.loadItem(fileName, new AudioLoadResultHandlerImpl(ac)); + + return true; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/SayCommand.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/SayCommand.java new file mode 100644 index 0000000..3c77ec2 --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/SayCommand.java @@ -0,0 +1,15 @@ +package de.yannicpunktdee.yoshibot.command.commands; + +import de.yannicpunktdee.yoshibot.command.YoshiCommand; +import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; + +public class SayCommand extends YoshiCommand { + + public SayCommand(YoshiCommandContext context) { + super(context); + } + + @Override + public boolean execute() {return true;} + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/StopCommand.java b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/StopCommand.java new file mode 100644 index 0000000..842d20f --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/StopCommand.java @@ -0,0 +1,23 @@ +package de.yannicpunktdee.yoshibot.command.commands; + +import de.yannicpunktdee.yoshibot.command.YoshiCommand; +import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; +import de.yannicpunktdee.yoshibot.main.YoshiBot; + +public class StopCommand extends YoshiCommand { + + protected final String[] requiredArguments = {"please", "uwu"}; + + + public StopCommand(YoshiCommandContext context) { + super(context); + } + + @Override + public boolean execute() { + if(!super.execute()) return false; + YoshiBot.stop(); + return true; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandLine.java b/app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandLine.java new file mode 100644 index 0000000..2b48d3b --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandLine.java @@ -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.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."); + } + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandListener.java b/app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandListener.java new file mode 100644 index 0000000..a38ee9e --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandListener.java @@ -0,0 +1,39 @@ +package de.yannicpunktdee.yoshibot.listeners; + +import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; +import de.yannicpunktdee.yoshibot.main.Resources; +import de.yannicpunktdee.yoshibot.main.YoshiBot; +import net.dv8tion.jda.api.entities.ChannelType; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; + +/** + * 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 CommandListener extends ListenerAdapter { + + /** + * {@inheritDoc} + */ + @Override public void onMessageReceived(MessageReceivedEvent event) { + if(!event.isFromType(ChannelType.TEXT)) return; + + if(event.getAuthor().isBot()) return; + + if(Resources.getRestrictCommandsToChannel() != null + && !Resources.getRestrictCommandsToChannel().equalsIgnoreCase(event.getTextChannel().getName())) + 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); + + return; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/main/Main.java b/app/src/main/java/de/yannicpunktdee/yoshibot/main/Main.java new file mode 100644 index 0000000..4b4f12d --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/main/Main.java @@ -0,0 +1,29 @@ +package de.yannicpunktdee.yoshibot.main; + +import java.net.URISyntaxException; +import javax.security.auth.login.LoginException; + +/** + * 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 { + System.out.println("Starte Applikation.\n"); + + YoshiBot.init((args.length > 0)? args[0] : null); + + try { + YoshiBot.start(); + }catch(LoginException e) { + System.err.println("Es ist ein Fehler beim Login aufgetreten."); + } + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/main/Resources.java b/app/src/main/java/de/yannicpunktdee/yoshibot/main/Resources.java new file mode 100644 index 0000000..ece73a3 --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/main/Resources.java @@ -0,0 +1,77 @@ +package de.yannicpunktdee.yoshibot.main; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +public class Resources { + + private static final String default_propertiesFilePath = "Config.properties"; + private static String propertiesFilePath = default_propertiesFilePath; + private static Properties propertiesFile; + + private static String jda_builder_string; + + private static final String default_audio_source_directory = "rsc/audio/"; + private static String audio_source_directory = default_audio_source_directory; + + private static final String default_restrict_commands_to_channel = null; + private static String restrict_commands_to_channel = default_restrict_commands_to_channel; + + + public synchronized static void init(String pathToConfig) { + if(pathToConfig != null) propertiesFilePath = pathToConfig; + if(!(new File(propertiesFilePath)).exists()) propertiesFilePath = default_propertiesFilePath; + + propertiesFile = new Properties(); + try { + propertiesFile.load(Resources.class.getClassLoader().getResourceAsStream(propertiesFilePath)); + System.out.println("Properties-Datei erfolgreich geladen."); + } catch (IOException e) { + System.err.println("Es wurde keine Config-Datei gefunden. Benutze Standards."); + return; + } + + initJdaBuilderString(); + initAudio(); + initChannelRestrict(); + } + + private static void initJdaBuilderString() { + if(!propertiesFile.containsKey("jda_builder_string")) { + System.err.println("Es wurde kein jda_builder_string gefunden."); + YoshiBot.stop(); + } else jda_builder_string = propertiesFile.getProperty("jda_builder_string"); + } + public static String getJdaBuilderString() { + return jda_builder_string; + } + + private static void initAudio() { + if(!propertiesFile.containsKey("audio_source_directory")) return; + + String dir = propertiesFile.getProperty("audio_source_directory"); + File file = new File(dir); + if(!file.exists() || !file.isDirectory()){ + System.err.println("Das Audio-Verzeichnis wurde nicht gefunden"); + return; + } + + audio_source_directory = dir; + } + public static String getAudioFilePath(String name) { + name = audio_source_directory + (audio_source_directory.endsWith("/")? "" : "/") + name + ".opus"; + System.out.println("Dateiname: " + name); + if((new File(name)).exists()) return name; + else return null; + } + + private static void initChannelRestrict() { + if(propertiesFile.containsKey("restrict_commands_to_channel")) + restrict_commands_to_channel = propertiesFile.getProperty("restrict_commands_to_channel"); + } + public static String getRestrictCommandsToChannel() { + return restrict_commands_to_channel; + } + +} diff --git a/app/src/main/java/de/yannicpunktdee/yoshibot/main/YoshiBot.java b/app/src/main/java/de/yannicpunktdee/yoshibot/main/YoshiBot.java new file mode 100644 index 0000000..02b983b --- /dev/null +++ b/app/src/main/java/de/yannicpunktdee/yoshibot/main/YoshiBot.java @@ -0,0 +1,99 @@ +package de.yannicpunktdee.yoshibot.main; + +import javax.security.auth.login.LoginException; + +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.AudioControllerManager; +import de.yannicpunktdee.yoshibot.command.YoshiCommandContext; +import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor; +import de.yannicpunktdee.yoshibot.listeners.CommandLine; +import de.yannicpunktdee.yoshibot.listeners.CommandListener; +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; + +/** + * 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 class YoshiBot { + + private static CommandLine commandLineThread; + + /** + * Erlaubt es einige Einstellungen vor und nach der Erzeugung eines Bots vorzunehmen. + */ + public static JDABuilder jdaBuilder; + /** + * Instanz vom aktuell laufenden Bot. + */ + public static JDA jda; + /** + * LavaPlayer AudioPlayerManager. + */ + public static AudioPlayerManager audioPlayerManager; + + public static AudioControllerManager audioControllerManager; + + + /** + * Initialisiert alle dynamisch hinzugefügten und statischen Ressourcen. Startet aber nicht + * den Bot selbst. + */ + public static void init(String configPath) { + Resources.init(configPath); + } + + /** + * Startet den Bot und schaltet ihn online. Beginnt auf Konsoleneingaben für administrative + * Zwecke zu lauschen. + * @throws LoginException Falls das Token ungültig ist. + */ + public static void start() throws LoginException { + System.out.println("Starte YoshiBot."); + + jdaBuilder = JDABuilder.createDefault(Resources.getJdaBuilderString()); + + jdaBuilder.addEventListeners(new CommandListener()); + + audioPlayerManager = new DefaultAudioPlayerManager(); + audioPlayerManager.registerSourceManager(new LocalAudioSourceManager()); + AudioSourceManagers.registerRemoteSources(audioPlayerManager); + + audioControllerManager = new AudioControllerManager(); + + jda = jdaBuilder.build(); + + jdaBuilder.setActivity(Activity.playing("Haare waschen.")); + jdaBuilder.setStatus(OnlineStatus.ONLINE); + + System.out.println("YoshiBot online."); + + commandLineThread = new CommandLine(); + commandLineThread.start(); + } + + public static synchronized void stop() { + 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); + } + +} diff --git a/app/src/main/resources/.gitkeep b/app/src/main/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..442d913 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..edd903e --- /dev/null +++ b/settings.gradle @@ -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')