Browse Source

Initial Commit

master
yl60lepu 5 years ago
committed by Paul Glaß
commit
a43a0daa71
49 changed files with 3121 additions and 0 deletions
  1. +6
    -0
      .gitattributes
  2. +200
    -0
      .gitignore
  3. +39
    -0
      README.md
  4. +55
    -0
      app/build.gradle
  5. +33
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioLoadResultHandlerImpl.java
  6. +31
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioPlayerListener.java
  7. +39
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioSendHandlerImpl.java
  8. +177
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommand.java
  9. +305
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandContext.java
  10. +111
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandDistributor.java
  11. +59
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/BonkCommand.java
  12. +50
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/HelpCommand.java
  13. +120
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/JokeCommand.java
  14. +62
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PatCommand.java
  15. +129
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PlayCommand.java
  16. +27
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/SayCommand.java
  17. +47
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/WikipediaCommand.java
  18. +44
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandLine.java
  19. +96
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/listeners/DiscordEventListener.java
  20. +36
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/main/Main.java
  21. +244
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/main/YoshiBot.java
  22. +153
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/GifSequenceWriter.java
  23. +43
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/Logger.java
  24. +7
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/Provider.java
  25. +317
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/Resources.java
  26. +40
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/RestHelper.java
  27. +136
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/StatusProvider.java
  28. +98
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/StatusProviderFactory.java
  29. +0
    -0
      app/src/main/resources/.gitkeep
  30. BIN
      gradle/wrapper/gradle-wrapper.jar
  31. +5
    -0
      gradle/wrapper/gradle-wrapper.properties
  32. +185
    -0
      gradlew
  33. +89
    -0
      gradlew.bat
  34. +2
    -0
      rsc/.gitignore
  35. +14
    -0
      rsc/Config.properties
  36. +11
    -0
      rsc/Ordnerstruktur.txt
  37. +7
    -0
      rsc/activities.txt
  38. BIN
      rsc/bonks/bonk1.png
  39. BIN
      rsc/bonks/bonk2.png
  40. +27
    -0
      rsc/byebyes.txt
  41. +3
    -0
      rsc/comebacks.txt
  42. +4
    -0
      rsc/departs.txt
  43. +21
    -0
      rsc/greetings.txt
  44. +20
    -0
      rsc/mcservers.json
  45. BIN
      rsc/pats/pat1.png
  46. BIN
      rsc/pats/pat2.png
  47. BIN
      rsc/pats/pat3.png
  48. +18
    -0
      rsc/tts.py
  49. +11
    -0
      settings.gradle

+ 6
- 0
.gitattributes View File

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

+ 200
- 0
.gitignore View File

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

+ 39
- 0
README.md View File

@ -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.

+ 55
- 0
app/build.gradle View File

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

+ 33
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioLoadResultHandlerImpl.java View File

@ -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");
}
}

+ 31
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioPlayerListener.java View File

@ -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;
}
}

+ 39
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioSendHandlerImpl.java View File

@ -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;
}
}

+ 177
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommand.java View File

@ -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;
}
}

+ 305
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandContext.java View File

@ -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;
}
}

+ 111
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandDistributor.java View File

@ -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();
}
/**
* Enthlt alle mglichen Aktionen, die der Yoshi-Bot ausfhren 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 lsst sich der Ausgabechannel bestimmen.
* Standardmig wird die Ausgabe in den Textchannel zurckgesendet, 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] lsst 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,
/**
* Lscht die Ressource, die ber -name spezifiziert wurde. Mit -type wird der Ressourcentyp festgelegt.
*/
SAUCE,
PAT,
BONK,
WIKIPEDIA
}
}

+ 59
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/BonkCommand.java View File

@ -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;
}
}

+ 50
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/HelpCommand.java View File

@ -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;
}
}

+ 120
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/JokeCommand.java View File

@ -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.";
}
}
}

+ 62
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PatCommand.java View File

@ -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;
}
}

+ 129
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PlayCommand.java View File

@ -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());
}
}

+ 27
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/SayCommand.java View File

@ -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());
}
}

+ 47
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/WikipediaCommand.java View File

@ -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());
}
}

+ 44
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/listeners/CommandLine.java View File

@ -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.");
}
}
}

+ 96
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/listeners/DiscordEventListener.java View File

@ -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();
}
}

+ 36
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/main/Main.java View File

@ -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();
}
}

+ 244
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/main/YoshiBot.java View File

@ -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);
}
}

+ 153
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/GifSequenceWriter.java View File

@ -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);
}
}

+ 43
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/Logger.java View File

@ -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);
}
}

+ 7
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/Provider.java View File

@ -0,0 +1,7 @@
package de.yannicpunktdee.yoshibot.utils;
public interface Provider {
void onStop();
}

+ 317
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/Resources.java View File

@ -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), "");
}
}

+ 40
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/RestHelper.java View File

@ -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();
}
}

+ 136
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/StatusProvider.java View File

@ -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);
}
}

+ 98
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/StatusProviderFactory.java View File

@ -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
app/src/main/resources/.gitkeep View File


BIN
gradle/wrapper/gradle-wrapper.jar View File


+ 5
- 0
gradle/wrapper/gradle-wrapper.properties View File

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

+ 185
- 0
gradlew View File

@ -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" "$@"

+ 89
- 0
gradlew.bat View File

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

+ 2
- 0
rsc/.gitignore View File

@ -0,0 +1,2 @@
255*.txt
mcstatus.sh

+ 14
- 0
rsc/Config.properties View File

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

+ 11
- 0
rsc/Ordnerstruktur.txt View File

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

+ 7
- 0
rsc/activities.txt View File

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

BIN
rsc/bonks/bonk1.png View File

Before After
Width: 680  |  Height: 412  |  Size: 152 KiB

BIN
rsc/bonks/bonk2.png View File

Before After
Width: 680  |  Height: 412  |  Size: 146 KiB

+ 27
- 0
rsc/byebyes.txt View File

@ -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..

+ 3
- 0
rsc/comebacks.txt View File

@ -0,0 +1,3 @@
%s hat jetzt fertig.
%s ist wieder aufnahmebereit.
%s hat sich wieder erholt.

+ 4
- 0
rsc/departs.txt View File

@ -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.

+ 21
- 0
rsc/greetings.txt View File

@ -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.

+ 20
- 0
rsc/mcservers.json View File

@ -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"
}
]}

BIN
rsc/pats/pat1.png View File

Before After
Width: 3000  |  Height: 3000  |  Size: 456 KiB

BIN
rsc/pats/pat2.png View File

Before After
Width: 3000  |  Height: 3000  |  Size: 414 KiB

BIN
rsc/pats/pat3.png View File

Before After
Width: 3000  |  Height: 3000  |  Size: 409 KiB

+ 18
- 0
rsc/tts.py View File

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

+ 11
- 0
settings.gradle View File

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

Loading…
Cancel
Save