#2 Hier ein Vorschlag für einen neuen CommmandDistributor: Umsetzung nicht als Enum sondern als HashMap. So muss kein riesiges switch-case vernwendet werden und das aussuche der Befehlsklassen ist durch die HashMap optimiert.

Closed
yannic wants to merge 0 commits from new_command_distributor into master
  1. +1
    -3
      .gitignore
  2. +3
    -6
      app/build.gradle
  3. +1
    -1
      app/src/main/java/de/yannicpunktdee/yoshibot/audio/AudioPlayerListener.java
  4. +14
    -43
      app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommand.java
  5. +5
    -10
      app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandContext.java
  6. +36
    -68
      app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandDistributor.java
  7. +1
    -1
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/BonkCommand.java
  8. +1
    -1
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PatCommand.java
  9. +19
    -85
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/PlayCommand.java
  10. +48
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/SauceCommand.java
  11. +2
    -3
      app/src/main/java/de/yannicpunktdee/yoshibot/command/commands/SayCommand.java
  12. +16
    -23
      app/src/main/java/de/yannicpunktdee/yoshibot/listeners/DiscordEventListener.java
  13. +5
    -5
      app/src/main/java/de/yannicpunktdee/yoshibot/main/Main.java
  14. +34
    -46
      app/src/main/java/de/yannicpunktdee/yoshibot/main/YoshiBot.java
  15. +8
    -8
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/Logger.java
  16. +0
    -7
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/Provider.java
  17. +40
    -63
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/Resources.java
  18. +156
    -0
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/SauceProvider.java
  19. +0
    -136
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/StatusProvider.java
  20. +0
    -98
      app/src/main/java/de/yannicpunktdee/yoshibot/utils/StatusProviderFactory.java
  21. +0
    -2
      rsc/.gitignore
  22. +2
    -12
      rsc/Config.properties
  23. +1
    -2
      rsc/activities.txt
  24. +0
    -0
      rsc/audio/.gitkeep
  25. +1
    -21
      rsc/byebyes.txt
  26. +0
    -3
      rsc/comebacks.txt
  27. +0
    -4
      rsc/departs.txt
  28. +0
    -13
      rsc/greetings.txt
  29. +0
    -20
      rsc/mcservers.json
  30. +52
    -0
      rsc/sauceConfig.json

+ 1
- 3
.gitignore View File

@ -151,7 +151,6 @@ cmake-build-*/
# IntelliJ # IntelliJ
out/ out/
.classpath
# mpeltonen/sbt-idea plugin # mpeltonen/sbt-idea plugin
.idea_modules/ .idea_modules/
@ -191,10 +190,9 @@ gradle-app.setting
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties # gradle/wrapper/gradle-wrapper.properties
updateYoshiBot.sh
.classpath
# Resources # Resources
rsc/audio/* rsc/audio/*
!rsc/audio/.gitkeep !rsc/audio/.gitkeep
rsc/PrivateJdaBuilderString.txt rsc/PrivateJdaBuilderString.txt
rsc/RedditCredentials.properties

+ 3
- 6
app/build.gradle View File

@ -30,9 +30,6 @@ dependencies {
implementation 'com.sedmelluq:lavaplayer:1.3.73' implementation 'com.sedmelluq:lavaplayer:1.3.73'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.0' 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' compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16' annotationProcessor 'org.projectlombok:lombok:1.18.16'
@ -45,11 +42,11 @@ application {
mainClass = "$mainClassName" mainClass = "$mainClassName"
} }
jar {
manifest {
jar{
manifest{
attributes "Main-Class": "$mainClassName" attributes "Main-Class": "$mainClassName"
} }
from { from {
configurations.runtimeClasspath.findAll({ !it.path.endsWith(".pom") }).collect { it.isDirectory() ? it : zipTree(it) }
configurations.runtimeClasspath.findAll({!it.path.endsWith(".pom")}).collect { it.isDirectory() ? it : zipTree(it) }
} }
} }

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

@ -9,7 +9,7 @@ import net.dv8tion.jda.api.managers.AudioManager;
public class AudioPlayerListener implements AudioEventListener { public class AudioPlayerListener implements AudioEventListener {
private final AudioManager audioManager;
private AudioManager audioManager;
@Getter @Getter
private static boolean isPlayingTrack = false; private static boolean isPlayingTrack = false;


+ 14
- 43
app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommand.java View File

@ -9,15 +9,11 @@ import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.VoiceChannel; import net.dv8tion.jda.api.entities.VoiceChannel;
import java.awt.*; import java.awt.*;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
/** /**
* Abstrakte Superklasse für alle Kommandos. * Abstrakte Superklasse für alle Kommandos.
@ -60,28 +56,25 @@ public abstract class YoshiCommand {
return true; return true;
} }
protected final void sendMessage(String message) {
protected final void sendMessage(String message){
EmbedBuilder eb = new EmbedBuilder(); EmbedBuilder eb = new EmbedBuilder();
eb.setColor(Color.pink); eb.setColor(Color.pink);
eb.setDescription(message); eb.setDescription(message);
context.getEvent().getTextChannel().sendMessage(eb.build()).queue(); context.getEvent().getTextChannel().sendMessage(eb.build()).queue();
} }
protected final void sendFile(File file, String description) {
protected final void sendFile(File file, String description){
EmbedBuilder eb = new EmbedBuilder(); EmbedBuilder eb = new EmbedBuilder();
eb.setColor(Color.pink); eb.setColor(Color.pink);
if (description != null) eb.setDescription(description);
if(description != null) eb.setDescription(description);
context.getEvent().getTextChannel().sendFile(file).queue(); context.getEvent().getTextChannel().sendFile(file).queue();
} }
protected final void sendInfoMessage(String message) {
protected final void sendInfoMessage(String message){
EmbedBuilder eb = new EmbedBuilder(); EmbedBuilder eb = new EmbedBuilder();
eb.setTitle("INFO"); eb.setTitle("INFO");
eb.setColor(Color.blue); eb.setColor(Color.blue);
eb.setDescription(message); eb.setDescription(message);
context.getEvent().getTextChannel().sendMessage(eb.build()).queue(); context.getEvent().getTextChannel().sendMessage(eb.build()).queue();
} }
protected final void sendErrorMessage(String message) { protected final void sendErrorMessage(String message) {
EmbedBuilder eb = new EmbedBuilder(); EmbedBuilder eb = new EmbedBuilder();
eb.setTitle("ERROR"); eb.setTitle("ERROR");
@ -89,14 +82,13 @@ public abstract class YoshiCommand {
eb.setDescription(message); eb.setDescription(message);
context.getEvent().getTextChannel().sendMessage(eb.build()).queue(); context.getEvent().getTextChannel().sendMessage(eb.build()).queue();
} }
protected final void sendCustomMessage(MessageEmbed messageEmbed) { protected final void sendCustomMessage(MessageEmbed messageEmbed) {
context.getEvent().getTextChannel().sendMessage(messageEmbed).queue(); context.getEvent().getTextChannel().sendMessage(messageEmbed).queue();
} }
protected File downloadAttachmentToFile(String directoryPath, String name) { protected File downloadAttachmentToFile(String directoryPath, String name) {
if (directoryPath == null) directoryPath = Resources.getEnsuredTempPath();
if (directoryPath == null) directoryPath = Resources.getTempPath();
if (name == null) name = UUID.randomUUID().toString(); if (name == null) name = UUID.randomUUID().toString();
if (!(new File(directoryPath)).isDirectory()) { if (!(new File(directoryPath)).isDirectory()) {
@ -128,46 +120,25 @@ public abstract class YoshiCommand {
return null; 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; return file;
} }
protected VoiceChannel getVoiceChannelByParam() {
protected VoiceChannel getVoiceChannelByParam(){
VoiceChannel vc; 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(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)) { if (!(channels.size() > 0)) {
sendErrorMessage("Der Kanalname konnte nicht gefunden werden."); sendErrorMessage("Der Kanalname konnte nicht gefunden werden.");
return null; return null;
} }
vc = channels.get(0); vc = channels.get(0);
} else {
try {
}else {
try{
vc = context.getEvent().getMember().getVoiceState().getChannel(); 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.");
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 null;
} }
} }


+ 5
- 10
app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandContext.java View File

@ -2,7 +2,6 @@ package de.yannicpunktdee.yoshibot.command;
import java.util.*; import java.util.*;
import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor.Action;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;;
/** /**
@ -97,7 +96,7 @@ public class YoshiCommandContext {
/** /**
* Die im Eingabesting spezifizierte Aktion. * Die im Eingabesting spezifizierte Aktion.
*/ */
private Action action;
private String action;
/** /**
* Eine Map, die die Key-Werte der Argumente (ohne Bindestrich) auf dessen Werte abbildet. * Eine Map, die die Key-Werte der Argumente (ohne Bindestrich) auf dessen Werte abbildet.
*/ */
@ -154,7 +153,7 @@ public class YoshiCommandContext {
case READING_ACTION: case READING_ACTION:
if (!Character.isWhitespace(currentChar)) continue; if (!Character.isWhitespace(currentChar)) continue;
try { try {
action = Action.valueOf(argumentsString.substring(startPos, position).toUpperCase());
action = argumentsString.substring(startPos, position).toLowerCase();
readingState = ReadingState.INTERMEDIATE; readingState = ReadingState.INTERMEDIATE;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
state = State.UNKNOWN_ACTION; state = State.UNKNOWN_ACTION;
@ -209,7 +208,7 @@ public class YoshiCommandContext {
return; return;
case VERIFYING: case VERIFYING:
if (argumentsString.equals(PREFIX)) { if (argumentsString.equals(PREFIX)) {
action = Action.HELP;
action = YoshiCommandDistributor.COMMAND_HELP;
state = State.OK; state = State.OK;
} else { } else {
state = State.NO_COMMAND; state = State.NO_COMMAND;
@ -217,7 +216,7 @@ public class YoshiCommandContext {
return; return;
case READING_ACTION: case READING_ACTION:
try { try {
action = Action.valueOf(argumentsString.substring(startPos).toUpperCase());
action = argumentsString.substring(startPos).toLowerCase();
readingState = ReadingState.INTERMEDIATE; readingState = ReadingState.INTERMEDIATE;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
state = State.UNKNOWN_ACTION; state = State.UNKNOWN_ACTION;
@ -255,7 +254,7 @@ public class YoshiCommandContext {
/** /**
* Gibt die im Kommando spezifizierte Aktion zurück. null, wenn status fehlerhaft oder kein Kommando. * Gibt die im Kommando spezifizierte Aktion zurück. null, wenn status fehlerhaft oder kein Kommando.
*/ */
public Action getAction() {
public String getAction() {
return action; return action;
} }
@ -266,10 +265,6 @@ public class YoshiCommandContext {
return !arguments.isEmpty(); 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. * Prüft, ob alle Key-Werte in der Argumentenliste vorhanden sind.
* *


+ 36
- 68
app/src/main/java/de/yannicpunktdee/yoshibot/command/YoshiCommandDistributor.java View File

@ -2,6 +2,12 @@ package de.yannicpunktdee.yoshibot.command;
import de.yannicpunktdee.yoshibot.command.commands.*; import de.yannicpunktdee.yoshibot.command.commands.*;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
/** /**
* Unterscheidet nach der spezifizierten Action welche YoshiCommand-Kindklasse zum Ausführen des Kommandos verwendet * Unterscheidet nach der spezifizierten Action welche YoshiCommand-Kindklasse zum Ausführen des Kommandos verwendet
* wird. * wird.
@ -10,6 +16,29 @@ import de.yannicpunktdee.yoshibot.command.commands.*;
*/ */
public class YoshiCommandDistributor { public class YoshiCommandDistributor {
public static final String COMMAND_HELP = "help";
public static final String COMMAND_JOKE = "joke";
public static final String COMMAND_SAY = "say";
public static final String COMMAND_PLAY = "play";
public static final String COMMAND_SAUCE = "sauce";
public static final String COMMAND_PAT = "pat";
public static final String COMMAND_BONK = "bonk";
public static final String COMMAND_WIKIPEDIA = "wikipedia";
private static final Map<String, Consumer<YoshiCommandContext>> commands = new HashMap<>();
public static void init() {
commands.put(COMMAND_HELP, HelpCommand::new);
commands.put(COMMAND_JOKE, JokeCommand::new);
commands.put(COMMAND_SAY, SayCommand::new);
commands.put(COMMAND_PLAY, PlayCommand::new);
commands.put(COMMAND_SAUCE, SauceCommand::new);
commands.put(COMMAND_PAT, PatCommand::new);
commands.put(COMMAND_BONK, BonkCommand::new);
commands.put(COMMAND_WIKIPEDIA, WikipediaCommand::new);
}
/** /**
* Führt das jeweils zuständige Kommando aus. * Führt das jeweils zuständige Kommando aus.
* *
@ -36,76 +65,15 @@ public class YoshiCommandDistributor {
break; 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 (!commands.containsKey(context.getAction())) {
context.getEvent().getTextChannel().sendMessage("Diese Aktion existiert nicht.").queue();
return;
} }
if (command != null) command.execute();
try {
commands.get(context.getAction()).accept(context);
} catch (Exception e) {
context.getEvent().getTextChannel().sendMessage("Konnte Aktion nicht zuordnen.").queue();
} }
/**
* 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
} }
} }

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

@ -24,7 +24,7 @@ public class BonkCommand extends YoshiCommand {
public boolean execute() { public boolean execute() {
if(!super.execute()) return false; if(!super.execute()) return false;
File outFile = new File(Resources.getEnsuredTempPath() + UUID.randomUUID().toString() + ".gif");
File outFile = new File(Resources.getTempPath() + UUID.randomUUID().toString() + ".gif");
try { try {
BufferedImage inPicture = ImageIO.read(downloadAttachmentToFile(null, null)); BufferedImage inPicture = ImageIO.read(downloadAttachmentToFile(null, null));


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

@ -24,7 +24,7 @@ public class PatCommand extends YoshiCommand {
public boolean execute() { public boolean execute() {
if (!super.execute()) return false; if (!super.execute()) return false;
File outFile = new File(Resources.getEnsuredTempPath() + UUID.randomUUID().toString() + ".gif");
File outFile = new File(Resources.getTempPath() + UUID.randomUUID().toString() + ".gif");
try { try {
BufferedImage inPicture= ImageIO.read(downloadAttachmentToFile(null, null)); BufferedImage inPicture= ImageIO.read(downloadAttachmentToFile(null, null));


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

@ -5,25 +5,15 @@ import de.yannicpunktdee.yoshibot.command.YoshiCommandContext;
import de.yannicpunktdee.yoshibot.main.YoshiBot; import de.yannicpunktdee.yoshibot.main.YoshiBot;
import de.yannicpunktdee.yoshibot.utils.Resources; import de.yannicpunktdee.yoshibot.utils.Resources;
import net.dv8tion.jda.api.EmbedBuilder; 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 net.dv8tion.jda.api.entities.VoiceChannel;
import org.apache.commons.text.similarity.JaccardDistance;
import java.awt.Color;
import java.awt.*;
import java.io.File; import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
public class PlayCommand extends YoshiCommand { public class PlayCommand extends YoshiCommand {
protected String[] requiredArguments = {"name"}; protected String[] requiredArguments = {"name"};
private static final Map<User, Long> cooldownChecker = new HashMap<>();
private static final int SECOND_DELAY = 5;
public PlayCommand(YoshiCommandContext context) { public PlayCommand(YoshiCommandContext context) {
super(context); super(context);
@ -34,96 +24,40 @@ public class PlayCommand extends YoshiCommand {
public boolean execute() { public boolean execute() {
if (!super.execute()) return false; 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;
}
if (context.containsArguments(new String[]{"add"})) {
File download = downloadAttachmentToFile(Resources.getAudioPath(), context.getArgument("name"));
if(download.isFile()) sendInfoMessage("Audio erfolgreich hinzugefügt.");
else sendErrorMessage("Audio konnte nicht hinzugefügt werden.");
} else if (context.containsArguments(new String[]{"list"})) {
File audioDirectory = new File(Resources.getAudioPath());
StringBuilder sb = new StringBuilder();
for (File f : audioDirectory.listFiles()) {
String fName = f.getName();
if (!fName.endsWith(".opus")) continue;
sb.append(fName.substring(0, fName.lastIndexOf(".opus")));
sb.append("\n");
} }
private boolean listSounds() {
EmbedBuilder eb = new EmbedBuilder(); EmbedBuilder eb = new EmbedBuilder();
eb.setTitle("Es sind folgende Audios verf\u00fcgbar:"); eb.setTitle("Es sind folgende Audios verf\u00fcgbar:");
eb.setColor(Color.cyan); eb.setColor(Color.cyan);
eb.setDescription(String.join("\n", getAllFiles()));
eb.setDescription(sb.toString());
sendCustomMessage(eb.build()); 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()) {
} else {
File file = new File(Resources.getPathToAudioFile(context.getArgument("name")));
if(!file.isFile()){
sendErrorMessage(String.format("Konnte keine Audiodatei namens '%s.opus' finden!", sendErrorMessage(String.format("Konnte keine Audiodatei namens '%s.opus' finden!",
context.getArgument("name"))); context.getArgument("name")));
return false; return false;
} }
VoiceChannel vc = getVoiceChannelByParam(); VoiceChannel vc = getVoiceChannelByParam();
if (vc == null) {
if(vc == null){
sendErrorMessage("Konnte keinen Audiochannel auswählen."); sendErrorMessage("Konnte keinen Audiochannel auswählen.");
return false; 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); 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));
return true;
} }
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());
}
} }

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

@ -0,0 +1,48 @@
package de.yannicpunktdee.yoshibot.command.commands;
import de.yannicpunktdee.yoshibot.command.YoshiCommand;
import de.yannicpunktdee.yoshibot.command.YoshiCommandContext;
import de.yannicpunktdee.yoshibot.utils.SauceProvider;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.util.Arrays;
import java.util.List;
public class SauceCommand 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 SauceCommand(YoshiCommandContext context) {
super(context);
}
/**
* {@inheritDoc}
*/
@Override
public boolean execute() {
if (!super.execute()) return false;
if (!context.getEvent().getTextChannel().isNSFW()) {
sendErrorMessage("Dieser Kanal is nix gut, weil vong nsfw her. Geh woanders hin du kek");
return true;
}
List<String> arguments = Arrays.asList(context.getArgument("tags").split(" "));
try {
arguments.stream().map(Integer::parseInt).map(this::byIndex).forEach(this::sendCustomMessage);
} catch (Exception e) {
sendCustomMessage(byTags(arguments));
}
return true;
}
private MessageEmbed byIndex(int index) {
return SauceProvider.getSauce(index);
}
private MessageEmbed byTags(List<String> tags){
return SauceProvider.getRandomSauce(String.join(" ", tags));
}
}

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

@ -8,6 +8,7 @@ import net.dv8tion.jda.api.entities.VoiceChannel;
public class SayCommand extends YoshiCommand { public class SayCommand extends YoshiCommand {
protected final String[] requiredArguments = {"text", "channel"}; protected final String[] requiredArguments = {"text", "channel"};
@ -18,9 +19,7 @@ public class SayCommand extends YoshiCommand {
@Override @Override
public boolean execute() { public boolean execute() {
if (!super.execute()) return false; 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()); return YoshiBot.getInstance().sayTTS(context.getArgument("text"), getVoiceChannelByParam());
} }


+ 16
- 23
app/src/main/java/de/yannicpunktdee/yoshibot/listeners/DiscordEventListener.java View File

@ -5,7 +5,6 @@ import de.yannicpunktdee.yoshibot.command.YoshiCommandContext;
import de.yannicpunktdee.yoshibot.main.YoshiBot; import de.yannicpunktdee.yoshibot.main.YoshiBot;
import de.yannicpunktdee.yoshibot.utils.Resources; import de.yannicpunktdee.yoshibot.utils.Resources;
import net.dv8tion.jda.api.entities.ChannelType; 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.GuildVoiceJoinEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMoveEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMoveEvent;
@ -49,48 +48,42 @@ public class DiscordEventListener extends ListenerAdapter {
@Override @Override
public void onGuildVoiceJoin(@NotNull GuildVoiceJoinEvent event) { public void onGuildVoiceJoin(@NotNull GuildVoiceJoinEvent event) {
super.onGuildVoiceJoin(event); super.onGuildVoiceJoin(event);
if (event.getMember().getUser().isBot()) return; if (event.getMember().getUser().isBot()) return;
if (Resources.isGreetings_and_byebyes_on()) {
if(!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers();
if (Resources.isGreetings_and_byebyes_on()){
String nameToPlay = event.getMember().getNickname(); String nameToPlay = event.getMember().getNickname();
if (nameToPlay == null) nameToPlay = event.getMember().getUser().getName();
if(nameToPlay == null) nameToPlay = event.getMember().getUser().getName();
YoshiBot.getInstance().sayTTS(Resources.getRandomGreeting(nameToPlay), event.getChannelJoined());
YoshiBot.getInstance().sayTTS(
Resources.getRandomGreeting(nameToPlay),
event.getMember().getVoiceState().getChannel());
} }
if (!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers();
} }
@Override @Override
public void onGuildVoiceMove(@NotNull GuildVoiceMoveEvent event) { 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();
if(!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers();
} }
@Override @Override
public void onGuildVoiceLeave(@NotNull GuildVoiceLeaveEvent event) { public void onGuildVoiceLeave(@NotNull GuildVoiceLeaveEvent event) {
super.onGuildVoiceLeave(event); super.onGuildVoiceLeave(event);
if (event.getMember().getUser().isBot() || event.getChannelLeft().getMembers().size() == 0) return;
if (Resources.isGreetings_and_byebyes_on()) {
if (event.getMember().getUser().isBot()) return;
if(!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers();
if (Resources.isGreetings_and_byebyes_on()) {
String nameToPlay = event.getMember().getNickname(); String nameToPlay = event.getMember().getNickname();
nameToPlay = nameToPlay == null ? event.getMember().getUser().getName() : nameToPlay; nameToPlay = nameToPlay == null ? event.getMember().getUser().getName() : nameToPlay;
YoshiBot.getInstance().sayTTS(Resources.getRandomByebye(nameToPlay), event.getChannelLeft());
YoshiBot.getInstance().sayTTS(
Resources.getRandomByebye(nameToPlay),
event.getChannelLeft());
} }
if (!AudioPlayerListener.isPlayingTrack()) YoshiBot.getInstance().joinVoiceChannelWithMostMembers();
} }
} }

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

@ -7,30 +7,30 @@ import java.net.URISyntaxException;
/** /**
* Main-Klasse und Startpunkt für die Bot-Applikation. * Main-Klasse und Startpunkt für die Bot-Applikation.
*
* @author Yannic Link * @author Yannic Link
*/ */
public class Main { public class Main {
/** /**
* Eintrittspunkt für die Applikation. Erzeugen eines neuen Yoshi-Bots und Starten. * Eintrittspunkt für die Applikation. Erzeugen eines neuen Yoshi-Bots und Starten.
*
* @param args Aufrufargumente. Werden später zum Konfigurieren genutzt. * @param args Aufrufargumente. Werden später zum Konfigurieren genutzt.
*
* @throws URISyntaxException * @throws URISyntaxException
*/ */
public static void main(String[] args) throws URISyntaxException { public static void main(String[] args) throws URISyntaxException {
YoshiBot yoshiBot = YoshiBot.getInstance(); YoshiBot yoshiBot = YoshiBot.getInstance();
if (!yoshiBot.init((args.length > 0) ? args[0] : null)) {
if(!yoshiBot.init((args.length > 0)? args[0] : null)){
Logger.logError("Es ist ein Fehler beim Initialisieren der Ressourcen aufgetreten."); Logger.logError("Es ist ein Fehler beim Initialisieren der Ressourcen aufgetreten.");
return; return;
} }
Logger.logInfo("Ressourcen erfolgreich initialisiert. Starte Yoshi Bot ..."); Logger.logInfo("Ressourcen erfolgreich initialisiert. Starte Yoshi Bot ...");
try {
yoshiBot.start(); yoshiBot.start();
}catch(LoginException e) {
System.err.println("Es ist ein Fehler beim Login aufgetreten.");
}
} }
} }

+ 34
- 46
app/src/main/java/de/yannicpunktdee/yoshibot/main/YoshiBot.java View File

@ -12,7 +12,9 @@ import de.yannicpunktdee.yoshibot.command.YoshiCommandContext;
import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor; import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor;
import de.yannicpunktdee.yoshibot.listeners.CommandLine; import de.yannicpunktdee.yoshibot.listeners.CommandLine;
import de.yannicpunktdee.yoshibot.listeners.DiscordEventListener; import de.yannicpunktdee.yoshibot.listeners.DiscordEventListener;
import de.yannicpunktdee.yoshibot.utils.*;
import de.yannicpunktdee.yoshibot.utils.Logger;
import de.yannicpunktdee.yoshibot.utils.Resources;
import de.yannicpunktdee.yoshibot.utils.SauceProvider;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
@ -20,19 +22,20 @@ import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.PermissionOverride;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.VoiceChannel; import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import javax.security.auth.login.LoginException;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; 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 * Repräsentiert einen Yoshi-Bot. Der Bot initialisiert alle Ressourcen und schaltet sich in der startYoshiBot-Methode
@ -68,20 +71,21 @@ public final class YoshiBot {
@Getter @Getter
private final Random random = new Random(); 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. * Initialisiert alle dynamisch hinzugefügten und statischen Ressourcen. Startet aber nicht den Bot selbst.
*/ */
public boolean init(String configPath) { public boolean init(String configPath) {
YoshiCommandDistributor.init();
return Resources.init(configPath); return Resources.init(configPath);
} }
/** /**
* Startet den Bot und schaltet ihn online. Beginnt auf Konsoleneingaben für administrative Zwecke zu lauschen. * Startet den Bot und schaltet ihn online. Beginnt auf Konsoleneingaben für administrative Zwecke zu lauschen.
**/
*
* @throws LoginException Falls das Token ungültig ist.
*/
@SneakyThrows @SneakyThrows
public void start() {
public void start() throws LoginException {
System.out.println("Starte YoshiBot."); System.out.println("Starte YoshiBot.");
jdaBuilder = JDABuilder.createDefault(Resources.getJda_builder_string()); jdaBuilder = JDABuilder.createDefault(Resources.getJda_builder_string());
@ -110,25 +114,19 @@ public final class YoshiBot {
jdaBuilder.setStatus(OnlineStatus.ONLINE); jdaBuilder.setStatus(OnlineStatus.ONLINE);
Logger.logInfo("YoshiBot online.");
System.out.println("YoshiBot online.");
commandLineThread = new CommandLine(); commandLineThread = new CommandLine();
commandLineThread.start(); commandLineThread.start();
StatusProviderFactory.createStatusProviders(Resources.getMcserver_config_file(), allProvides);
//SauceProvider.init(300);
new SauceProvider(300);
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(YoshiBot::setRandomActivity, 0, 10, TimeUnit.HOURS); Executors.newScheduledThreadPool(1).scheduleAtFixedRate(YoshiBot::setRandomActivity, 0, 10, TimeUnit.HOURS);
Runtime.getRuntime().addShutdownHook(new Thread(() -> YoshiBot.getInstance().stop()));
joinVoiceChannelWithMostMembers(); joinVoiceChannelWithMostMembers();
} }
public synchronized void stop() { public synchronized void stop() {
allProvides.forEach(Provider::onStop);
commandLineThread.stopCommandLine(); commandLineThread.stopCommandLine();
System.out.println("Beende YoshiBot ..."); System.out.println("Beende YoshiBot ...");
jdaBuilder.setStatus(OnlineStatus.OFFLINE); jdaBuilder.setStatus(OnlineStatus.OFFLINE);
@ -162,44 +160,34 @@ public final class YoshiBot {
Logger.logInfo("Setze Aktivität auf " + activity); Logger.logInfo("Setze Aktivität auf " + activity);
} }
public synchronized void joinVoiceChannel(VoiceChannel vc) {
if (vc == null) {
public synchronized void joinVoiceChannel(VoiceChannel vc){
if(vc == null) {
guild.getAudioManager().closeAudioConnection(); guild.getAudioManager().closeAudioConnection();
return; return;
} }
if (guild.getAudioManager().getConnectedChannel() != null &&
if(guild.getAudioManager().getConnectedChannel() != null &&
vc.getIdLong() == guild.getAudioManager().getConnectedChannel().getIdLong()) return; vc.getIdLong() == guild.getAudioManager().getConnectedChannel().getIdLong()) return;
try {
guild.getAudioManager().openAudioConnection(vc); 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())));
public void joinVoiceChannelWithMostMembers(){
VoiceChannel maxVoiceChannel = null;
int maxMembers = 0;
for(VoiceChannel vc : guild.getVoiceChannels()){
int membersInChannel = 0;
for(Member m : vc.getMembers())
if(!m.getUser().isBot()) membersInChannel++;
if(membersInChannel > maxMembers){
maxVoiceChannel = vc;
maxMembers = membersInChannel;
}
} }
joinVoiceChannel(channel);
return channel;
if(maxMembers < 1) joinVoiceChannel(null);
else if(maxMembers > 0 && maxVoiceChannel != null)
joinVoiceChannel(maxVoiceChannel);
} }
public boolean playSound(File file, VoiceChannel vc) {
public boolean playSound(File file, VoiceChannel vc){
if (!file.isFile()) return false; if (!file.isFile()) return false;
joinVoiceChannel(vc); joinVoiceChannel(vc);
@ -210,7 +198,7 @@ public final class YoshiBot {
} }
public boolean sayTTS(String text, VoiceChannel vc) { public boolean sayTTS(String text, VoiceChannel vc) {
String path = Resources.getEnsuredTempPath() + UUID.randomUUID() + ".opus";
String path = Resources.getTempPath() + UUID.randomUUID() + ".opus";
try { try {
ProcessBuilder pb = new ProcessBuilder( ProcessBuilder pb = new ProcessBuilder(


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

@ -9,35 +9,35 @@ public final class Logger {
public static void logDebug(String message){ public static void logDebug(String message){
System.out.printf("%s[%tT: Yoshi::DEBUG] %s%s%n",
System.out.println(String.format("%s[%tT: Yoshi::DEBUG] %s%s",
ANSI_GREEN, ANSI_GREEN,
System.currentTimeMillis(), System.currentTimeMillis(),
message, message,
ANSI_RESET);
ANSI_RESET));
} }
public static void logInfo(String message){ public static void logInfo(String message){
System.out.printf("%s[%tT: Yoshi::INFO] %s%s%n",
System.out.println(String.format("%s[%tT: Yoshi::INFO] %s%s",
ANSI_BLUE, ANSI_BLUE,
System.currentTimeMillis(), System.currentTimeMillis(),
message, message,
ANSI_RESET);
ANSI_RESET));
} }
public static void logWarning(String message){ public static void logWarning(String message){
System.out.printf("%s[%tT: Yoshi::WARNING] %s%s%n",
System.out.println(String.format("%s[%tT: Yoshi::WARNING] %s%s",
ANSI_YELLOW, ANSI_YELLOW,
System.currentTimeMillis(), System.currentTimeMillis(),
message, message,
ANSI_RESET);
ANSI_RESET));
} }
public static void logError(String message){ public static void logError(String message){
System.err.printf("%s[%tT: Yoshi::ERROR] %s%s%n",
System.err.println(String.format("%s[%tT: Yoshi::ERROR] %s%s",
ANSI_YELLOW, ANSI_YELLOW,
System.currentTimeMillis(), System.currentTimeMillis(),
message, message,
ANSI_RESET);
ANSI_RESET));
} }
} }

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

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

+ 40
- 63
app/src/main/java/de/yannicpunktdee/yoshibot/utils/Resources.java View File

@ -8,7 +8,6 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -19,32 +18,53 @@ import java.util.stream.StreamSupport;
public final class Resources { public final class Resources {
@Getter @Getter
private static boolean greetings_and_byebyes_on;
private static String resourcePath;
@Getter
private static String configPath;
@Getter
private static String audioPath;
private static String tempPath;
@Getter
private static String activitiesPath;
@Getter
private static String greetingsPath;
@Getter
private static String byebyesPath;
@Getter
private static String sauceConfigPath;
@Getter
private static String ttsPath;
@Getter
private static String patPngPath;
@Getter @Getter
private static int statusUpdate;
private static String imagePath;
@Getter
private static String bonkPngPath;
@Getter @Getter
private static long guild_id;
private static String jda_builder_string;
@Getter @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 Long guild_id;
private static List<String> greetings, byebyes, departs, comebacks;
@Getter
private static boolean greetings_and_byebyes_on;
private static List<String> greetings;
private static List<String> byebyes;
@Getter @Getter
private static String[] restrict_commands_to_channel, filteredTags;
private static String[] restrict_commands_to_channel;
@Getter
private static String[] filteredTags;
@Getter @Getter
private static final Map<String, List<String>> feedDetails = new HashMap<>(); private static final Map<String, List<String>> feedDetails = new HashMap<>();
private static Properties propertiesFile; private static Properties propertiesFile;
public static boolean init(String resourcePathArg) {
public synchronized static boolean init(String resourcePathArg) {
boolean isOk = initResources(resourcePathArg); boolean isOk = initResources(resourcePathArg);
if (isOk) isOk = initConfig(); if (isOk) isOk = initConfig();
if (isOk) isOk = initAudio(); if (isOk) isOk = initAudio();
@ -60,7 +80,6 @@ public final class Resources {
if (isOk) isOk = initPatPngPath(); if (isOk) isOk = initPatPngPath();
if (isOk) isOk = initImages(); if (isOk) isOk = initImages();
if (isOk) isOk = initBonkPngPath(); if (isOk) isOk = initBonkPngPath();
if (isOk) isOk = initStatusMessage();
if (isOk) Logger.logInfo("Die Konfigurationen wurden erfolgreich geladen."); if (isOk) Logger.logInfo("Die Konfigurationen wurden erfolgreich geladen.");
else Logger.logError("Die Konfiguration konnte nicht geladen werden"); else Logger.logError("Die Konfiguration konnte nicht geladen werden");
@ -92,18 +111,12 @@ public final class Resources {
} }
private static boolean initAudio() { private static boolean initAudio() {
return (audioPath = verifyExists(resourcePath + "audio/", File::isDirectory)) != null;
audioPath = verifyExists(resourcePath + "audio/", File::isDirectory);
return audioPath != null;
} }
public static String getPathToAudioFile(String name) { 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 "";
}
return audioPath + name + ".opus";
} }
private static boolean initTemp() { private static boolean initTemp() {
@ -125,13 +138,10 @@ public final class Resources {
} }
} }
public static String getEnsuredTempPath() {
if (tempPath == null) {
initTemp();
}
public static String getTempPath(){
File tempDir = new File(tempPath); File tempDir = new File(tempPath);
if (!tempDir.isDirectory())
if (!tempDir.mkdir()) throw new Error("Could not make Temp directory");
if(!tempDir.isDirectory())
if(!tempDir.mkdir()) return null;
return tempPath; return tempPath;
} }
@ -189,12 +199,12 @@ public final class Resources {
@SneakyThrows @SneakyThrows
private static boolean initGreetingsAndByebyes() { private static boolean initGreetingsAndByebyes() {
greetingsPath = verifyExists(resourcePath + "greetings.txt", File::isFile); greetingsPath = verifyExists(resourcePath + "greetings.txt", File::isFile);
if (greetingsPath == null) { if (greetingsPath == null) {
return false; return false;
} }
byebyesPath = verifyExists(resourcePath + "byebyes.txt", File::isFile); 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")) { if (propertiesFile.containsKey("greetings_and_byebyes_on")) {
greetings_and_byebyes_on = Boolean.parseBoolean(propertiesFile.getProperty("greetings_and_byebyes_on")); greetings_and_byebyes_on = Boolean.parseBoolean(propertiesFile.getProperty("greetings_and_byebyes_on"));
@ -203,8 +213,6 @@ public final class Resources {
greetings = Files.readAllLines(Paths.get(greetingsPath)); greetings = Files.readAllLines(Paths.get(greetingsPath));
byebyes = Files.readAllLines(Paths.get(byebyesPath)); byebyes = Files.readAllLines(Paths.get(byebyesPath));
comebacks = Files.readAllLines(Paths.get(comebacksPath));
departs = Files.readAllLines(Paths.get(departsPath));
return true; return true;
} }
@ -216,10 +224,6 @@ public final class Resources {
return greetings_and_byebyes_on ? String.format(getRandomFrom(byebyes), name) : null; 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) { private static String getRandomFrom(List<String> pool) {
return pool.get(YoshiBot.getInstance().getRandom().nextInt(pool.size())); return pool.get(YoshiBot.getInstance().getRandom().nextInt(pool.size()));
} }
@ -278,7 +282,7 @@ public final class Resources {
private static String verifyExists(String filename, Function<File, Boolean> checkIsValidFile) { private static String verifyExists(String filename, Function<File, Boolean> checkIsValidFile) {
String[] split = filename.split("/"); String[] split = filename.split("/");
Logger.logDebug(String.format("Versuche %s zu finden.", split[split.length - 1]));
Logger.logInfo(String.format("Versuche %s zu finden.", split[split.length - 1]));
if (checkIsValidFile.apply(new File(filename))) { if (checkIsValidFile.apply(new File(filename))) {
return filename; return filename;
} else { } else {
@ -287,31 +291,4 @@ public final class Resources {
} }
} }
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), "");
}
} }

+ 156
- 0
app/src/main/java/de/yannicpunktdee/yoshibot/utils/SauceProvider.java View File

@ -0,0 +1,156 @@
package de.yannicpunktdee.yoshibot.utils;
import de.yannicpunktdee.yoshibot.main.YoshiBot;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.TextChannel;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.*;
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 SauceProvider {
private static final String BASE_URL = "https://r34-json.herokuapp.com/";
private int lastKnownSauce = -1;
private boolean isSauceInit = false;
private static MessageEmbed notFoundEmbed = null;
public SauceProvider(int timer) {
lastKnownSauce = this.getNewestIndex();
ScheduledExecutorService sauceScheduler = Executors.newScheduledThreadPool(1);
sauceScheduler.scheduleAtFixedRate(this::provideSauce, 0, timer, TimeUnit.SECONDS);
new Thread(this::initSauceProviding).start();
}
public SauceProvider(int timer, int lastKnownSauce) {
this(timer);
this.lastKnownSauce = lastKnownSauce;
}
public static MessageEmbed getSauce(int index) {
String url = BASE_URL + "posts?id=" + index;
JSONObject base = getParsedSauceData(url);
if (base.getInt("count") == 0) {
return getNotFoundEmbed();
}
JSONObject post = base.getJSONArray("posts").getJSONObject(0);
return makeStringFromJson(post);
}
public static MessageEmbed getRandomSauce(String tags) {
tags = tagsForRest(tags);
tags += "+" + String.join("+", Resources.getFilteredTags());
Random rand = YoshiBot.getInstance().getRandom();
String url = BASE_URL + "posts?tags=" + String.join("+", tags);
Logger.logInfo("Soße angefordert für tags " + tags);
JSONObject baseObj = getParsedSauceData(url);
int amount = baseObj.getInt("count");
if (amount == 0) {
return getNotFoundEmbed();
}
int selectedIndex = rand.nextInt(amount);
int page = (selectedIndex / 100) % 100;
int pageIndex = selectedIndex % 100;
String pagedUrl = url + "&pid=" + page;
JSONObject post = Objects.requireNonNull(getParsedSauceData(pagedUrl)).getJSONArray("posts")
.getJSONObject(pageIndex);
return makeStringFromJson(post);
}
private static MessageEmbed makeStringFromJson(JSONObject post) {
EmbedBuilder eb = new EmbedBuilder();
eb.setTitle("Soße").setDescription("URL: " + post.getString("file_url").substring(42));
eb.addField("ID", post.getString("id"), false);
eb.addField("Tags", "`" + post.getJSONArray("tags").join("` `") + "`", false);
eb.setImage(post.getString("file_url").substring(42));
return eb.build();
}
private void provideSauce() {
if (!isSauceInit) return;
for (Map.Entry<String, List<String>> feed : Resources.getFeedDetails().entrySet()) {
String url = BASE_URL + "posts?tags=" + String.join("+", feed.getValue())
+ "+" + String.join("+", Resources.getFilteredTags());
JSONArray posts = getParsedSauceData(url).getJSONArray("posts");
assert posts != null;
List<JSONObject> postsInternal =
StreamSupport.stream(posts.spliterator(), false)
.map(obj -> (JSONObject) obj)
.filter(obj -> obj.getInt("id") > lastKnownSauce)
.collect(Collectors.toList());
Collections.reverse(postsInternal);
YoshiBot yoshiBot = YoshiBot.getInstance();
for (JSONObject post : postsInternal) {
List<TextChannel> channels = yoshiBot.jda.getTextChannelsByName(feed.getKey(), true);
if (channels.size() == 0) {
Logger.logError("Kein Kanal mit dem Namen " + feed.getKey() + "gefunden");
break;
} else if (!channels.get(0).isNSFW()) {
Logger.logError("Kanal " + feed.getKey() + " ist nicht als NSFW markiert!");
break;
}
channels.get(0).sendMessage(makeStringFromJson(post)).queue();
}
if (postsInternal.size() > 0) {
Logger.logInfo(String.format("Found %d posts for feed '%s'", postsInternal.size(), feed.getKey()));
}
}
lastKnownSauce = this.getNewestIndex();
}
private void initSauceProviding() {
YoshiBot yoshiBot = YoshiBot.getInstance();
try {
yoshiBot.jda.awaitReady();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Map.Entry<String, List<String>> entry : Resources.getFeedDetails().entrySet()) {
List<TextChannel> channels = yoshiBot.jda.getTextChannelsByName(entry.getKey(), true);
if (channels.size() > 0) {
this.isSauceInit = true;
this.provideSauce();
} else {
Logger.logError("Konnte keine Kanaäle finden für die Soße");
}
}
}
private int getNewestIndex() {
JSONObject result = getParsedSauceData("https://r34-json.herokuapp.com/posts?limit=1&q=index");
int id = result.getJSONArray("posts").getJSONObject(0).getInt("id");
Logger.logDebug("Neuste Soßen-ID: " + id);
return id;
}
private static JSONObject getParsedSauceData(String url) {
String raw = RestHelper.getFromURL(url);
return new JSONObject(raw);
}
private static String tagsForRest(String tags) {
tags = tags.replace(" ", "+");
return tags;
}
private static MessageEmbed getNotFoundEmbed() {
if (SauceProvider.notFoundEmbed == null) {
EmbedBuilder eb = new EmbedBuilder();
eb.setTitle("Could not find any posts matching the filter!");
SauceProvider.notFoundEmbed = eb.build();
}
return SauceProvider.notFoundEmbed;
}
}

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

@ -1,136 +0,0 @@
package de.yannicpunktdee.yoshibot.utils;
import lombok.Getter;
import net.dv8tion.jda.api.EmbedBuilder;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class StatusProvider implements Provider {
private static final ScheduledExecutorService statusScheduler = Executors.newScheduledThreadPool(1);
private int lastPlayersOnline = -1;
@Getter
private final String ip;
private final String desc;
private LocalDateTime timestampLastPlayerOnline;
private final ProcessBuilder mcstatus = new ProcessBuilder();
public StatusProvider(String desc, int secondsPerTime, String ip, LocalDateTime timestamp) {
this.timestampLastPlayerOnline = timestamp;
this.desc = desc;
this.ip = ip;
this.mcstatus.command(Resources.getPath_to_mcstatus(), ip, "json");
statusScheduler.scheduleAtFixedRate(this::updateStatusMessage, 0, secondsPerTime,
TimeUnit.SECONDS);
}
@SuppressWarnings("unchecked")
public void updateStatusMessage() {
EmbedBuilder eb = new EmbedBuilder();
eb.setTitle(desc);
eb.addField("IP", ip, false);
try {
Map<String, Object> serverInfo = getPlayersOnline();
if ((boolean) serverInfo.get("online")) {
if ((boolean) serverInfo.get("Server starting")) {
eb.addField("Server still Starting", "True", false);
} else {
int newPlayersOnline = (int) serverInfo.get("playerCount");
eb.addField("Version", (String) serverInfo.get("version"), true);
eb.addField("MOTD", (String) serverInfo.get("motd"), true);
eb.addField("Spieler online", newPlayersOnline + " / " + serverInfo.get("playerMax"), false);
if (newPlayersOnline == 0) {
if (lastPlayersOnline > newPlayersOnline) {
timestampLastPlayerOnline = LocalDateTime.now();
StatusProviderFactory.updateTimestamp(this, timestampLastPlayerOnline);
}
eb.addField("Zuletzt gesehen",
StatusProviderFactory.TIME_FORMATTER.format(timestampLastPlayerOnline), false);
} else {
eb.addField("Spieler:", String.join(", ", (List<String>) serverInfo.get("playerNames")), false);
}
lastPlayersOnline = newPlayersOnline;
}
} else {
eb.addField("Offline", "", false);
}
StatusProviderFactory.updateStatus(this, eb.build());
} catch (IOException e) {
Logger.logError(e.toString());
}
}
private Map<String, Object> getPlayersOnline() throws IOException {
Process process = mcstatus.start();
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (process.exitValue() != 0) {
Logger.logError("MCStatus on IP " + ip + " exited with errorcode " + process.exitValue());
}
String output = new BufferedReader(new InputStreamReader(process.getInputStream())).lines()
.collect(Collectors.joining());
Map<String, Object> result = new HashMap<>();
if (output.startsWith("The server did not respond to the query protocol.")) {
result.put("online", false);
return result;
}
JSONObject obj = new JSONObject(output);
result.put("online", obj.getBoolean("online"));
if (!obj.getBoolean("online")) {
return result;
}
for (String key : new String[]{"player_count", "player_max", "players", "motd", "version"}) {
if (!obj.has(key)) {
result.put("Server starting", true);
return result;
}
}
result.put("Server starting", false);
result.put("playerCount", obj.getInt("player_count"));
result.put("playerMax", obj.getInt("player_max"));
result.put("playerNames", StreamSupport.stream(obj.getJSONArray("players").spliterator(), false)
.map(jsonobj -> ((JSONObject) jsonobj).getString("name")).sorted().collect(Collectors.toList()));
result.put("motd", obj.getString("motd"));
result.put("version", obj.getString("version"));
return result;
}
@Override
public void onStop() {
StatusProviderFactory.updateTimestamp(this, timestampLastPlayerOnline);
}
}

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

@ -1,98 +0,0 @@
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
- 2
rsc/.gitignore View File

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

+ 2
- 12
rsc/Config.properties View File

@ -1,14 +1,4 @@
#Sun Mar 13 19:43:48 CET 2022
status_message_vanilla=890007862456238120
status_channel=889880296168755231
guild_id=801554100814741524
# audio_source_directory=C:/Users/linky/workspace/Yoshi_Bot_Audio/
restrict_commands_to_channel=bot-muell schrein-auf-den-bot 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 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

+ 1
- 2
rsc/activities.txt View File

@ -1,7 +1,6 @@
Haare waschen Haare waschen
Kleine Menschen (Honey) verprügeln
Kleine Menschen (Vanessa) verprügeln
Im Kosovo Hexen verbennen Im Kosovo Hexen verbennen
Magnesiumcarbonat schnupfen Magnesiumcarbonat schnupfen
Offene Wunden mit Sekundenkleber verschließen Offene Wunden mit Sekundenkleber verschließen
Sich 'nen saftigen Knackarsch reinzimmern Sich 'nen saftigen Knackarsch reinzimmern
Spielt Rasputin bei Just Dance

+ 0
- 0
rsc/audio/.gitkeep View File


+ 1
- 21
rsc/byebyes.txt View File

@ -4,24 +4,4 @@ Tschö %s.
Geh kacken %s. Geh kacken %s.
%s verlässt uns unu. %s verlässt uns unu.
%s ist kurz Halle Peißen. %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..
Oh nein, oh fick, %s kann uns nicht hören, denn er/sie/ser hat Lufthülsen im Ohr!

+ 0
- 3
rsc/comebacks.txt View File

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

+ 0
- 4
rsc/departs.txt View File

@ -1,4 +0,0 @@
%s ist dann mal abwesend.
%s hat jetzt erstmal nix mehr zu sagen.
%s ist verschwunden.
%s kommt bald wieder.... vielleicht.

+ 0
- 13
rsc/greetings.txt View File

@ -6,16 +6,3 @@ Hurra hurra %s ist da.
%s was ist deine Weisheit? %s was ist deine Weisheit?
Es erscheine: %s! Es erscheine: %s!
Ja ach scheiß doch die Wand an, %s ist da! Ja ach scheiß doch die Wand an, %s ist da!
%s ist vom Rauchen zurück.
%s beehrt uns juhu tralala.
%s ist gekommen um zu kommen.
%s ich wähle dich!
Zuerst war das nichts, dann %s.
Ist es ein Flugzeug? Ist es ein Vogel? Nein es ist %s.
Hajoa schleck ma ja %s ist da.
%s betritt das Hornyjail.
Moin %s.
%s kommt für billige Unterhaltung.
%s lässt sich für 2 Euro in die Eier treten.
%s ist ein Mann / Männin von Ähre.
%s hat sich entschieden bliat zu kosten.

+ 0
- 20
rsc/mcservers.json View File

@ -1,20 +0,0 @@
{"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"
}
]}

+ 52
- 0
rsc/sauceConfig.json View File

@ -0,0 +1,52 @@
{
"tags_general_filter": [
"-anthro",
"-nonconsensual",
"-rape",
"-vore",
"-scat",
"-yiff",
"-snuff",
"-crossdressing",
"-mind_break",
"-overweight",
"-hyper",
"-udders"
],
"feeds": [
{
"channel": "snek-feed",
"tags": [
"lamia"
]
},
{
"channel": "auto-feed",
"tags": [
[
"fire_emblem",
"sword_art_online",
"monster_musume_no_iru_nichijou",
"samus_aran",
"metroid",
"palutena",
"xenoblade_chronicles",
"xenoblade_chronicles_2",
"nintendo",
"star_wars",
"nier:_automata",
"monster_girl",
"tate_no_yuusha_no_nariagari",
"zero_two_(darling_in_the_franxx)",
"re:zero_kara_hajimeru_isekai_seikatsu",
"fate_(series)",
"darling_in_the_franxx",
"dungeon_ni_deai_wo_motomeru_no_wa_machigatteiru_darou_ka",
"touhou",
"elf"
],
"breasts"
]
}
]
}

Loading…
Cancel
Save