This commit is contained in:
Ocame
2024-09-04 19:50:49 +02:00
commit 418efd2f47
9 changed files with 1508 additions and 0 deletions

45
build.gradle Normal file
View File

@ -0,0 +1,45 @@
plugins {
id 'application'
id("com.github.johnrengelman.shadow") version "7.1.2"
}
application.mainClass = "de.ocame.ffdiscordbot.Main"
group 'de.ocame'
version '1.1-SNAPSHOT'
repositories {
mavenCentral()
}
sourceCompatibility = '17'
targetCompatibility = '17'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
/*
application {
mainModule = 'de.ocame.ffdiscordbot'
mainClass = 'de.ocame.ffdiscordbot.Main'
}
*/
dependencies {
implementation "net.dv8tion:JDA:5.0.1"
implementation 'org.jsoup:jsoup:1.15.3'
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'org.xerial:sqlite-jdbc:3.41.2.2'
implementation 'org.json:json:20231013'
implementation "ch.qos.logback:logback-classic:1.5.6"
// compile "org.json:json:20141113"
}
test {
useJUnitPlatform()
}

View File

@ -0,0 +1,32 @@
package de.ocame.ffdiscordbot;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ConfigLoader {
private final Properties properties;
public ConfigLoader() {
properties = new Properties();
try (FileInputStream input = new FileInputStream("ffBot.conf")) {
properties.load(input);
} catch (IOException e) {
System.err.println("Konfigurationsdatei konnte nicht geladen werden: " + e.getMessage());
e.printStackTrace();
}
}
public String getToken() {
return properties.getProperty("api.token");
}
public String getUsername() {
return properties.getProperty("ff.username");
}
public String getPassword() {
return properties.getProperty("ff.password");
}
}

View File

@ -0,0 +1,242 @@
package de.ocame.ffdiscordbot;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import java.awt.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
public class DiscordOutput {
private final List<StoryOutputText> chapterText;
public DiscordOutput() {
chapterText = new ArrayList<>();
chapterText.add(new StoryOutputText(
"Oh, wie mein Herz singt! **%autor%** hat ein neues Kapitel unserer Geschichte \"**%storyName%**\" geschrieben, " +
"und es heißt \"**%chapterName%**\". \n\nIch spüre die Aufregung in der Luft, als ob jeder Flügelschlag mich höher trägt. " +
"Die Welt leuchtet in neuen Farben, und jeder Sonnenstrahl erzählt von Abenteuern, die uns erwarten. Dieses Kapitel " +
"verspricht so viel ich kann es kaum erwarten, in die Worte zu tauchen und die Geschichten, die sie verbergen, " +
"zu entdecken!"
, "https://media.discordapp.net/stickers/1219043798101786624.webp?size=160"));
chapterText.add(new StoryOutputText(
"Aye, Text in Sicht, ihr Seeratten! **%autor%** hat ein neues Kapitel in der Geschichte \"**%storyName%**\" aufgetan, und " +
"es trägt den Namen \"**%chapterName%**\". Macht die Fässer klar, holt die besten Getränke, und besetzt die Bildschirme! \n" +
"Keiner rührt sich, bis wir jedes Wort gelesen haben, verstanden? Und hört gut hin: Wer es wagt, auch nur ein Wort " +
"zu spoilern, wird schneller gekielholt, als er \"Piratenkodex\" sagen kann! Jetzt aber volle Fahrt voraus das " +
"Abenteuer ruft!", "https://cdn.discordapp.com/attachments/542202034330271759/1272999183673987133/image.png?ex=66bd0460&is=66bbb2e0&hm=708f10708d0616ed212d0c288cfde503fb27785ddd68c1ba30bb4d3f7698acee&"));
chapterText.add(new StoryOutputText(
"Mesdames et Messieurs, heute habe ich die Ehre, Ihnen ein exquisites Gericht zu präsentieren: " +
"das \"**%chapterName%**\" à la \"**%storyName%**\", nach der feinen Art unseres geschätzten Chef-Autors \"**%autor%**\". \n" +
"Es ist ein Meisterwerk der literarischen Küche, perfekt gewürzt mit Spannung und serviert mit einer Prise Abenteuer. " +
"Lassen Sie sich von den Aromen dieser neuen Geschichte verführen und genießen Sie jeden Bissen bon appétit!",
"https://cdn.discordapp.com/attachments/542202034330271759/1272999507587629188/image.png?ex=66bd04ad&is=66bbb32d&hm=d4d7325f233c27a75305ee9622c9f87b03e1aa507acb534c2fd6c3de62d435d1&",
new Color(255, 0, 229)));
chapterText.add(new StoryOutputText(
"........................\n " +
"........................\n " +
"........................\n " +
"........................\n " +
"Na los, Grünschnabel, hör auf zu trödeln und lies endlich das neue Kapitel \"**%chapterName%**\" aus der Geschichte \"**%storyName%**\" " +
"vom \"**%autor%**\" vor! Meine Zeit ist nicht unendlich, und ich hab keine Lust, hier ewig zu warten. Also mach schon, bevor ich " +
"noch selbst anfangen muss, die Seiten zu ertasten!"
));
chapterText.add(new StoryOutputText(
"Das neue Kapitel \"**%chapterName%**\" aus der Geschichte \"**%storyName%**\" vom \"**%autor%**\" ist erschienen, und ich werde es als Erster lesen. " +
"Während die Piraten noch ihren Rum genießen, verschaffe ich mir den Wissensvorsprung. Mit jedem Satz, den ich lese, " +
"wird meine Strategie schärfer, und bald werde ich ihnen einen Schritt voraus sein. Ein kluger Kopf gewinnt schließlich immer " +
"das Spiel.",
"https://cdn.discordapp.com/attachments/542202034330271759/1273002039923965952/image.png?ex=66bd0709&is=66bbb589&hm=77130cbe420a9e941e58e68010c12cc2e53c54c288b8563fd6a1a856ffebd0df&"
));
chapterText.add(new StoryOutputText(
"hcilrütan ebah hci sad etseuen Letipak nohcs neseleg \n\n" +
"Erika Rückwerts die Worte auszuprechen ist kein Reptiloidisch!\n\n" +
"Menschen! Natürlich habe ich das neueste Kapitel schon gelesen. Kein Zweifel, es ist genauso grandios, " +
"wie ich es erwartet habe. Jede Wendung, jeder Dialog einfach meisterhaft. Und falls du es wissen willst: Es handelt sich " +
"um \"**%chapterName%**\" aus der Geschichte \"**%storyName%**\" vom Autor \"**%autor%**\". Wenn du wirklich etwas Gutes lesen willst, dann ist das genau das " +
"Richtige für dich. Aber hey, das hab ich ja gleich gewusst.",
"https://cdn.discordapp.com/attachments/542202034330271759/1273005878974349313/image.png?ex=66bd0a9c&is=66bbb91c&hm=8e7701f259b7bd84ea3b91b5c8fb63c4b8807dd297a05fc9289aead8c1978fac&",
new Color(40, 145, 6)
));
chapterText.add(new StoryOutputText(
"Ah, wie wunderbar! Endlich halte ich das neue Kapitel \"**%chapterName%**\" aus der Geschichte \"**%storyName%**\" " +
"vom \"**%autor%**\" in den Händen. Ein wahres Vergnügen für den Geist! Und bitte, meine Lieben, lasst den Kronleuchter in Ruhe. " +
"Er spendet gerade das perfekte Licht zum Lesen eine solch erhabene Lektüre verdient schließlich die besten Bedingungen."
));
chapterText.add(new StoryOutputText("Ich habe die Zukunft gesehen, und glaub mir, das neue Kapitel von \"**%chapterName%**\" aus der Geschichte " +
"\"**%storyName%**\" vom \"**%autor%**\" ist etwas ganz Besonderes. Es ist, als ob die Worte selbst eine Melodie singen, die so sanft und " +
"doch so kraftvoll ist, dass sie dich mitreißt, egal wohin sie dich führt. Jedes Wort, jede Zeile sie fließen wie ein ruhiger " +
"Fluss, der dich in eine Welt entführt, die so fesselnd und lebendig ist, dass du alles um dich herum vergisst.\n" +
"\n" +
"Es ist wie der erste Schluck eines perfekt gebrühten Tees, dieser Moment, in dem sich die Wärme in deinem Körper ausbreitet " +
"und du spürst, wie jeder Tropfen dich beruhigt und gleichzeitig belebt. Ach, Tee... Was für ein wunderbares Getränk, nicht wahr? " +
"So viel Sorgfalt und Liebe steckt in jeder Tasse, genau wie in diesen Worten, die ich gerade gelesen habe. Die Teeblätter, " +
"die mit so viel Bedacht und Geduld gepflückt werden, erinnern mich an die frischen Pflanzen im Frühling, wenn alles grünt und blüht. " +
"Sie sind voller Leben, voller Möglichkeiten, genau wie die Geschichten, die uns erzählen, wohin unser Weg führen könnte.\n" +
"\n" +
"Wie wunderschön es doch ist, in solchen einfachen Freuden Trost zu finden sei es in einem guten Buch, einer heißen Tasse Tee oder dem " +
"Anblick von Pflanzen, die in ihrer vollen Pracht stehen. Manchmal sind es gerade diese kleinen, stillen Momente, die das Leben wirklich " +
"lebenswert machen."));
}
public void newChapter(JDA jda, DataChapter chapter, DataStory story) {
newChapter(jda, chapter, story, null, null);
}
public void newChapter(JDA jda, DataChapter chapter, DataStory story, Integer version, Long tagetChannel) {
try {
String message;
EmbedBuilder outputBox = new EmbedBuilder();
outputBox.setTitle("Neues Kapitel:", chapter.link());
List<StoryOutputText> chapterTexte;
if(version != null) {
chapterTexte = SQLiteStorage.getChapterTexte(version);
}
else {
chapterTexte = SQLiteStorage.getChapterTexte(story);
}
StoryOutputText versionText = null;
if(chapterTexte.size() == 0)
return;
Random random = new Random();
versionText = chapterTexte.get(random.nextInt(chapterTexte.size()));
message = versionText.getMessage();
message = message.replace("%chapterName%", "**" + chapter.name() + "**")
.replace("%storyName%", "**" + story.name() + "**")
.replace("%autor%", "**" + story.author() + "**");
outputBox.setDescription(message);
outputBox.setColor(versionText.getColor());
Instant instant = Instant.ofEpochSecond(chapter.date());
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.of("Europe/Berlin"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yy HH:mm 'Uhr'");
outputBox.setFooter("Veröffentlicht am " + dateTime.format(formatter));
if(versionText.getImage() != null && !Objects.equals(versionText.getImage(), ""))
outputBox.setThumbnail(versionText.getImage());
Long[] channelIds = SQLiteStorage.getChannelIds();
if(tagetChannel != null) {
Objects.requireNonNull(jda.getTextChannelById(tagetChannel))
.sendMessage("Hier ist ein Text vor dem Embed <@&423156046115242005> :")
.setEmbeds(outputBox.build())
.queue();
}
else if(Objects.requireNonNull(channelIds).length == 0) {
Objects.requireNonNull(jda.getTextChannelById(542202034330271759L)).sendMessageEmbeds(outputBox.build()).queue();
}
else {
for (Long channelId : channelIds ) {
try {
if (Objects.requireNonNull(jda.getTextChannelById(channelId)).canTalk())
Objects.requireNonNull(jda.getTextChannelById(channelId)).sendMessageEmbeds(outputBox.build()).queue();
}
catch (NullPointerException e) {
//
}
}
}
}
catch (NumberFormatException e) {
//jda.getTextChannelById(channelId).sendMessage("Nope").queue();
System.out.println("problem: " + e.getMessage());
}
catch (Exception e) {
System.out.println("problem: " + e.getMessage());
}
}
public void newStory(JDA jda, DataStory story) {
newStory(jda, story, null);
}
public void newStory(JDA jda, DataStory story, Integer version) {
try {
String message;
EmbedBuilder outputBox = new EmbedBuilder();
outputBox.setTitle("Neue Gesichte:", story.link());
List<StoryOutputText> storyTexte = SQLiteStorage.getStoryTexte();
StoryOutputText versionText = null;
if(storyTexte.size() == 0)
return;
if(version != null) {
versionText = storyTexte.get(version);
}
if(version == null || versionText == null) {
Random random = new Random();
versionText = storyTexte.get(random.nextInt(storyTexte.size()));
}
message = versionText.getMessage();
message = message.replace("%storyName%", "**" + story.name() + "**")
.replace("%autor%", "**" + story.author() + "**");
outputBox.setDescription(message);
outputBox.setColor(versionText.getColor());
Instant instant = Instant.ofEpochSecond(story.date());
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.of("Europe/Berlin"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yy HH:mm 'Uhr'");
outputBox.setFooter("Veröffentlicht am " + dateTime.format(formatter));
if(versionText.getImage() != null && !Objects.equals(versionText.getImage(), ""))
outputBox.setThumbnail(versionText.getImage());
Long[] channelIds = SQLiteStorage.getChannelIds();
if(channelIds.length == 0) {
jda.getTextChannelById(542202034330271759L).sendMessageEmbeds(outputBox.build()).queue();
}
else {
for (Long channelId : channelIds ) {
try {
if (jda.getTextChannelById(channelId).canTalk())
jda.getTextChannelById(channelId).sendMessageEmbeds(outputBox.build())
.queue();
}
catch (NullPointerException e) {
//
}
}
}
}
catch (NumberFormatException e) {
//jda.getTextChannelById(channelId).sendMessage("Nope").queue();
}
catch (NullPointerException e ) {
//
}
catch (Exception e) {
System.out.println("problem: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,361 @@
package de.ocame.ffdiscordbot;
import net.dv8tion.jda.api.JDA;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FanFiktionReader {
private final HtmlFetcher fetcher;
private final DiscordOutput dco;
private final JDA jda;
private long time = 30*60;
public FanFiktionReader(JDA jda) {
fetcher = new HtmlFetcher();
SQLiteStorage.createTable();
dco = new DiscordOutput();
this.jda = jda;
}
/**
* Liest neue Ereignse von der Alert Seite aus
*/
public void checkAlerts() {
try {
String alertURL = "https://www.fanfiktion.de/?a=i";
Document alertPage = fetcher.fetchURL(alertURL);
// Login Check
Elements title = alertPage.getElementsByTag("title");
if(title.first().text().contains("keine Berechtigung!") || title.first().text().contains("Session-Error") || title.first().text().contains("Login")) {
login();
alertPage = fetcher.fetchURL(alertURL);
title = alertPage.getElementsByTag("title");
if(title.first().text().contains("keine Berechtigung!") || title.first().text().contains("Session-Error") || title.first().text().contains("Login")) {
// Fehler
return;
}
}
// Story Check
Element authorBox = alertPage.getElementById("ffcbox-alerts_author-box");
if(authorBox != null) {
Elements rows = authorBox.select("table tbody tr");
for (Element row : rows) {
checkNewStoryFromAlert(row);
}
}
// Kapitel Check
Element StoryBox = alertPage.getElementById("ffcbox-alerts_story-box");
if(StoryBox != null) {
Elements rows = StoryBox.select("table tbody tr");
for (Element row : rows) {
Elements cols = row.select("td");
if (cols.size() >= 2) {
Element link = cols.get(1).selectFirst("a");
String linkHref = (link != null) ? link.attr("href") : "No link";
String chaptertime = cols.get(6).text();
long chapterUnixtime = DateToTimestamp(chaptertime);
Element delLink = cols.last().selectFirst("a");
String delLinkHref = (delLink != null) ? delLink.attr("href") : "No link";
checkNewChapter("https://www.fanfiktion.de" + linkHref, chapterUnixtime);
// Ausführung des Links, um Inhalt aus Alert zu entfernen
try {
if(!delLinkHref.equals("No link"))
fetcher.fetchURL("https://www.fanfiktion.de" + delLinkHref);
}
catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
}
}
}
}
catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
Random random = new Random();
this.time = random.nextLong(24*60,34*60);
}
/**
* Liest neue Storys von der Alarm Seite aus
*
* @param row
*/
public void checkNewStoryFromAlert(Element row) {
Elements cols = row.select("td");
if (cols.size() >= 2) { // Ensure there are at least two columns
/*name, link, ffid, date, autor*/
String storyName = cols.get(1).text();
String storyAutor = cols.get(3).text();
String storytime = cols.get(4).text();
long storyUnixtime = DateToTimestamp(storytime);
Element link = cols.get(1).selectFirst("a");
String linkHref = (link != null) ? link.attr("href") : "No link";
String[] hrefElements = linkHref.split("/s/",2);
String storyId = hrefElements[1];
if(!SQLiteStorage.checkStory(storyId)) {
/* todo: neue Story gefunden Ausgabe */
DataStory newStory = new DataStory(storyName, "https://www.fanfiktion.de/s/" + storyId, storyId,storyUnixtime,storyAutor, 0);
SQLiteStorage.insertStorys(newStory);
dco.newStory(jda, newStory);
}
Element delLink = cols.last().selectFirst("a");
String delLinkHref = (delLink != null) ? delLink.attr("href") : "No link";
// Ausführung des Links, um Inhalt aus Alert zu entfernen
try {
if(!delLinkHref.equals("No link"))
fetcher.fetchURL("https://www.fanfiktion.de" + delLinkHref);
}
catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
}
}
/**
* Prüft neues Kapitel Anhand der Seite, das Datum wird nur aus bequemlichkeit mit übergeben, da das schneller auslesbar war
*
* @param url
* @param chapterDate
*/
public void checkNewChapter(String url, long chapterDate) {
try {
// Login Check
Document storyPage = fetcher.fetchURL(url);
Elements title = storyPage.getElementsByTag("title");
if(title.first().text().contains("keine Berechtigung!") || title.first().text().contains("Session-Error") || title.first().text().contains("Login")) {
login();
storyPage = fetcher.fetchURL(url);
title = storyPage.getElementsByTag("title");
if(title.first().text().contains("keine Berechtigung!") || title.first().text().contains("Session-Error") || title.first().text().contains("Login")) {
// Fehler
return;
}
}
// Auswahl Select für Kapitel Auswahl
Element chapterSelect = storyPage.getElementById("kA");
if(chapterSelect != null) {
Elements options = storyPage.select("option");
String chapterName = options.last().text();
String chapterNumer = options.last().attr("value");
String onChange = chapterSelect.attributes().get("onchange")
.replace("location.href = '" , "")
.replace("' + this.value + '" , chapterNumer)
.replace("'" , "");
// Regulärer Ausdruck für die ffid nach /s/
String regex = "/s/([a-zA-Z0-9]+)/";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(onChange);
String ffid = "";
if (matcher.find()) {
// Die gefundene Nummernfolge ausgeben
ffid = matcher.group(1);
} else {
// todo: fehler behandlung
}
if(SQLiteStorage.checkChapter(ffid, Long.parseLong(chapterNumer))) {
/* todo: neue Chapter gefunden Ausgabe */
if(!SQLiteStorage.checkStory(ffid)) {
readandSaveStory(storyPage);
}
DataStory ds = SQLiteStorage.getStory(ffid);
DataChapter newChapter = new DataChapter(chapterName, "https://www.fanfiktion.de" + onChange, ffid, chapterDate, Long.parseLong(chapterNumer));
SQLiteStorage.insertChapter(newChapter);
//clearChapter();
dco.newChapter(jda, newChapter, ds);
}
}
}
catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
}
/**
* List alle Infos aus einer Story Seite aus, um das relevante zu speichern
* @param storyPage
*/
public void readandSaveStory(Document storyPage) {
Elements infoBox = storyPage.select(".story-left");
String storyName = infoBox.first().getElementsByTag("h4").text();
String storyAutor = infoBox.select("a.no-wrap").first().text();
String storytime = infoBox.select(".flexicon-container.small-font.block div").first().text();
long storyUnixtime = DateToTimestamp(storytime + " | 00:00 Uhr");
String storyLink = storyPage.select("#ffcbox-story .topics a").last().attr("href");
String regex = "/s/([a-zA-Z0-9]+)/";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(storyLink);
String ffid = "";
if (matcher.find()) {
// Die gefundene Nummernfolge ausgeben
ffid = matcher.group(1);
} else {
// todo: fehler behandlung
}
if(!SQLiteStorage.checkStory(ffid)) {
DataStory newStory = new DataStory(storyName, "https://www.fanfiktion.de/s/" + ffid, ffid, storyUnixtime, storyAutor, 0);
SQLiteStorage.insertStorys(newStory);
}
}
public void outputCheapterTester(int chapterId, String chapterTextTitle, String storyTitle, String autor, Long tagetChannel) {
DataStory ds = new DataStory(storyTitle, "https://www.fanfiktion.de", "", LocalDateTime.now().toEpochSecond(ZoneOffset.UTC), autor, 0);
DataChapter dc = new DataChapter(chapterTextTitle, "https://www.fanfiktion.de", "", LocalDateTime.now().toEpochSecond(ZoneOffset.UTC), 1);
dco.newChapter(jda, dc, ds, chapterId, tagetChannel);
}
/**
* Wandelt String Zeit in Unix Timestamp um
*
* @param dateString
* @return
*/
public long DateToTimestamp(String dateString) {
//dateString = "02.08.2024 | 21:21 Uhr";
// Definiere das Datumsformat
//DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy | HH:mm 'Uhr'").withResolverStyle(ResolverStyle.STRICT);
//DateTimeFormatter formatteralternativ = DateTimeFormatter.ofPattern("dd.MM.yyyy'");
String[] patterns = {"dd.MM.yyyy | HH:mm 'Uhr'" , "dd.MM.yyyy'", "dd.MM.yyyy HH:mm'"};
LocalDateTime dateTime = parseDateTimeWithMultiplePatterns(dateString, patterns);
if(dateTime == null || dateTime.toEpochSecond(ZoneOffset.UTC) == 0) {
return 0;
}
else
return dateTime.toEpochSecond(ZoneOffset.UTC);
}
private static LocalDateTime parseDateTimeWithMultiplePatterns(String dateString, String[] patterns) {
for (String pattern : patterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return LocalDateTime.parse(dateString.trim(), formatter);
} catch (DateTimeParseException e) {
// Continue with the next pattern if parsing fails
}
}
return null; // If none of the patterns work, return null
}
/**
* Führt den Login aus
*/
public void login() {
try {
ConfigLoader configLoader = new ConfigLoader();
String loginURL = "https://www.fanfiktion.de/?a=l";
Document logintPage = fetcher.fetchURL(loginURL);
Elements nameBox = logintPage.select ("input[name='nickname']");
Elements pwBox = logintPage.select ("input[name='passwd']");
if(nameBox.isEmpty()) {
// todo: Fehler wenn Login nicht geht
}
Elements aBox = logintPage.select ("input[name='a']");
Elements uBox = logintPage.select ("input[name='u']");
String uValue = fetcher.getCookieValue("https://www.fanfiktion.de/", "u");
uValue = java.net.URLDecoder.decode(uValue, StandardCharsets.UTF_8);
String ffNickname = configLoader.getUsername();
String ffPw = configLoader.getPassword();
String postData = "a=l" // + java.net.URLEncoder.encode(aBox.attr("value"), StandardCharsets.UTF_8)
+ "&u=" + java.net.URLEncoder.encode(uValue, StandardCharsets.UTF_8)
+ "&nickname=" + java.net.URLEncoder.encode(ffNickname, StandardCharsets.UTF_8)
+ "&passwd=" + java.net.URLEncoder.encode(ffPw, StandardCharsets.UTF_8)
+ "&savelogindata=" + java.net.URLEncoder.encode("1", StandardCharsets.UTF_8);
Document loginRequest = fetcher.fetchURL("https://www.fanfiktion.de/", postData);
nameBox = loginRequest.select ("input[name='nickname']");
if(!nameBox.isEmpty()) {
// todo: Fehler wenn Login nicht geht
}
}
catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
}
public long getTime() {
return time;
}
public void clearChapter(String url) {
try {
Document storyPage = fetcher.fetchURL(url);
Elements title = storyPage.getElementsByTag("title");
if (title.first().text().contains("keine Berechtigung!")) {
login();
storyPage = fetcher.fetchURL(url);
title = storyPage.getElementsByTag("title");
if (title.first().text().contains("keine Berechtigung!")) {
// Fehler
return;
}
}
}
catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,157 @@
package de.ocame.ffdiscordbot;
import java.io.IOException;
import java.io.FileReader;
import java.io.FileWriter;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.HttpCookie;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.net.http.HttpRequest.BodyPublishers;
public class HtmlFetcher {
private final HttpClient client;
private final CookieManager cookieManager;
private static final String COOKIE_FILE = "cookies.json";
public HtmlFetcher() {
cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
client = HttpClient.newBuilder()
.cookieHandler(cookieManager)
.followRedirects(HttpClient.Redirect.ALWAYS)
.connectTimeout(Duration.ofSeconds(10)) // Verbindungs-Timeout
.build();
loadCookies();
}
public Document fetchURL(String url) throws IOException, InterruptedException, URISyntaxException {
return fetchURL(url, "");
}
public Document fetchURL(String url, String postData) throws IOException, InterruptedException, URISyntaxException {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0")
.header("Accept", "text/html")
.header("Accept-Language", "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7")
.timeout(Duration.ofSeconds(10));// Lese-Timeout
if (postData == null || postData.isEmpty()) {
requestBuilder.GET();
}
else {
// Wenn postData nicht leer ist, füge die POST-Daten hinzu
requestBuilder
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(BodyPublishers.ofString(postData));
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Print cookies if any
CookieStore cookieStore = cookieManager.getCookieStore();
List<HttpCookie> cookies = cookieStore.getCookies();
/*for (HttpCookie cookie : cookies) {
System.out.println("Cookie: " + cookie);
}*/
// Save cookies
saveCookies();
return Jsoup.parse(response.body());
}
private void saveCookies() {
CookieStore cookieStore = cookieManager.getCookieStore();
List<HttpCookie> cookies = cookieStore.getCookies();
List<SerializableCookie> serializableCookies = new ArrayList<>();
for (HttpCookie cookie : cookies) {
serializableCookies.add(new SerializableCookie(cookie));
}
try (FileWriter writer = new FileWriter(COOKIE_FILE)) {
new Gson().toJson(serializableCookies, writer);
} catch (IOException e) {
e.printStackTrace();
}
}
private void loadCookies() {
try (FileReader reader = new FileReader(COOKIE_FILE)) {
List<SerializableCookie> serializableCookies = new Gson().fromJson(reader, new TypeToken<List<SerializableCookie>>(){}.getType());
if (serializableCookies != null) {
for (SerializableCookie serializableCookie : serializableCookies) {
cookieManager.getCookieStore().add(new URI(serializableCookie.uri), serializableCookie.toHttpCookie());
}
}
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
}
private static class SerializableCookie {
private String uri;
private String name;
private String value;
private String path;
private String domain;
private long maxAge;
private boolean secure;
private boolean httpOnly;
SerializableCookie(HttpCookie cookie) {
this.uri = cookie.getDomain();
this.name = cookie.getName();
this.value = cookie.getValue();
this.path = cookie.getPath();
this.domain = cookie.getDomain();
this.maxAge = cookie.getMaxAge();
this.secure = cookie.getSecure();
this.httpOnly = cookie.isHttpOnly();
}
HttpCookie toHttpCookie() {
HttpCookie cookie = new HttpCookie(name, value);
cookie.setPath(path);
cookie.setDomain(domain);
cookie.setMaxAge(maxAge);
cookie.setSecure(secure);
cookie.setHttpOnly(httpOnly);
return cookie;
}
}
public CookieManager getCookieManager() {
return cookieManager;
}
public String getCookieValue(String uri, String cookieName) {
CookieStore cookieStore = cookieManager.getCookieStore();
List<HttpCookie> cookies = cookieStore.get(URI.create(uri));
for (HttpCookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return cookie.getValue();
}
}
return null; // Cookie wurde nicht gefunden
}
}

View File

@ -0,0 +1,71 @@
package de.ocame.ffdiscordbot;
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.requests.GatewayIntent;
import org.jsoup.nodes.Document;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class Main {
public static void main(String[] args) {
ConfigLoader configLoader = new ConfigLoader();
// Insert your bot's token here
//String token = "MTI3NzcxNTQ1NTI1MDkyMzU4MA.GUuRyh.07mjVRcI7MhLHo-Wdnytvxlb3Pts-hfRaNMaPA";
// iro
String token = configLoader.getToken();
/**/
JDA jda = JDABuilder.createDefault(token)
.enableIntents(GatewayIntent.MESSAGE_CONTENT)
.setActivity(Activity.playing("mit der Marine fangen"))
.setStatus(OnlineStatus.ONLINE)
.setAutoReconnect(true)
.build();/**/
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
}
FanFiktionReader ffr = new FanFiktionReader(jda);
// Start des Socket-Servers in einem separaten Thread
Thread socketThread = new Thread(new SocketServerRunnable(jda, ffr));
socketThread.start();
//ffr.checkAlerts();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
final long[] delay = {30 * 60};
AtomicReference<ScheduledFuture<?>> scheduledFuture = new AtomicReference<>();
Runnable task = new Runnable() {
@Override
public void run() {
//ffr.checkAlerts();
// Berechne das neue Intervall basierend auf ffr.getTime()
delay[0] = ffr.getTime();
// Plane die Aufgabe erneut mit dem neuen Intervall
scheduledFuture.get().cancel(false);
scheduledFuture.set(scheduler.schedule(this, delay[0], TimeUnit.SECONDS));
}
};
// Plane die erste Ausführung der Aufgabe
scheduledFuture.set(scheduler.schedule(task, delay[0], TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,457 @@
package de.ocame.ffdiscordbot;
import java.awt.*;
import java.sql.*;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.List;
public class SQLiteStorage {
private static final String DB_URL = "jdbc:sqlite:data.db";
private static final int MAX_RETRIES = 5;
private static final int RETRY_WAIT_TIME_MS = 1000;
public static void createTable() {
String sql = """
CREATE TABLE IF NOT EXISTS Storys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
link TEXT NOT NULL,
ffid TEXT NOT NULL,
date INTEGER NOT NULL,
tagId INTEGER NULL,
autor TEXT NOT NULL
);""";
String sqlChapter = """
CREATE TABLE IF NOT EXISTS Chapter (
id INTEGER PRIMARY KEY AUTOINCREMENT,
storyId TEXT NOT NULL,
name TEXT NOT NULL,
link TEXT NOT NULL,
date INTEGER NOT NULL,
number INTEGER NOT NULL
);""";
String sqlChapterText = """
CREATE TABLE IF NOT EXISTS ChapterText (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
chapterText TEXT NOT NULL,
color TEXT NOT NULL,
imageUrl TEXT NOT NULL,
active BOOLEAN NOT NULL DEFAULT 1
);""";
String sqlStoryText = """
CREATE TABLE IF NOT EXISTS StoryText (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
storyText TEXT NOT NULL,
color TEXT NOT NULL,
imageUrl TEXT NOT NULL,
active BOOLEAN NOT NULL DEFAULT 1
);""";
String sqlTags = """
CREATE TABLE IF NOT EXISTS Tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);
""";
String chapterTag = """
CREATE TABLE IF NOT EXISTS ChapterText_Tags (
chapterText_id INTEGER,
tag_id INTEGER,
PRIMARY KEY (chapterText_id, tag_id),
FOREIGN KEY (chapterText_id) REFERENCES ChapterText(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES Tags(id) ON DELETE CASCADE
);
""";
String options = """
CREATE TABLE IF NOT EXISTS Options (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
value TEXT
);
""";
String createIndexSQL = "CREATE INDEX IF NOT EXISTS idx_ffid ON Storys(ffid)";
String createIndexSQLChapter = "CREATE INDEX IF NOT EXISTS idx_ffid ON Chapter(ffid)";
String createIndexSQLChapter2 = "CREATE INDEX IF NOT EXISTS idx_storyId ON Chapter(storyId)";
String createIndexSQLChapterText = "CREATE INDEX IF NOT EXISTS idx_chapterText_active ON ChapterText (active);\n";
String createIndexSQLStoryText = "CREATE INDEX IF NOT EXISTS idx_storyText_active ON StoryText (active);\n";
try (Connection conn = DriverManager.getConnection(DB_URL);
Statement stmt = conn.createStatement()) {
stmt.execute(sql);
stmt.execute(createIndexSQL);
stmt.execute(sqlChapter);
stmt.execute(sqlChapterText);
stmt.execute(sqlStoryText);
stmt.execute(sqlTags);
stmt.execute(chapterTag);
stmt.execute(options);
stmt.execute(createIndexSQLChapter);
stmt.execute(createIndexSQLChapter2);
stmt.execute(createIndexSQLChapterText);
stmt.execute(createIndexSQLStoryText);
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
public static void insertStorys(DataStory record) {
String sql = "INSERT INTO Storys(name, link, ffid, date, autor) VALUES(?, ?, ?, ?, ?)";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
executeUpdateWithRetry(conn, sql,
record.name(),
record.link(),
record.ffid(),
record.date(),
record.author());
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
public static boolean checkStory(String ffid) {
String sql = "SELECT COUNT(*) FROM Storys WHERE ffid = ?";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql, ffid);
if (rs.next()) {
int count = rs.getInt(1);
return count > 0; // Gibt true zurück, wenn die ID existiert, sonst false
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return false;
}
public static boolean checkChapter(String ffid, long number) {
String sql = "SELECT COUNT(*) AS anzahl FROM Chapter WHERE storyId = ? AND number = ?";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql, ffid, number);
if (rs.next()) {
int count = rs.getInt(1);
return count > 0; // Gibt true zurück, wenn die ID existiert, sonst false
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return false;
}
public static DataStory getStory(String ffid) {
String sql = "SELECT name, link, ffid, date, autor, tagId FROM Storys WHERE ffid = ?";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql, ffid);
if (rs.next()) {
return new DataStory(
rs.getString("name"),
rs.getString("link"),
ffid,
rs.getLong("date"),
rs.getString("autor"),
rs.getInt("tagId")
);
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return null;
}
public static DataChapter getChapter(String ffid, long number) {
String sql = "SELECT storyId, name, link, date, number FROM Chapter WHERE storyId = ? AND number = ?";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql, ffid, number);
if (rs != null && rs.next()) {
return new DataChapter(
rs.getString("name"),
rs.getString("link"),
ffid,
rs.getLong("date"),
number
);
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return null;
}
public static void insertChapter(DataChapter record) {
String sql = "INSERT INTO Chapter(name, link, storyId, date, number) VALUES(?, ?, ?, ?, ?)";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
executeUpdateWithRetry(conn, sql,
record.name(),
record.link(),
record.storyId(),
record.date(),
record.number());
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] args) {
createTable();
/* DataStory record1 = new DataStory("Text 1", "https://link1.com", "Text 2");
DataStory record2 = new DataStory("Text 3", "https://link2.com", "Text 4");
// Insert data into SQLite
insertData(record1);
insertData(record2);
// Load data from SQLite
List<DataStory> loadedData = loadData();
for (DataStory record : loadedData) {
System.out.println(record);
}
}*/
}
private static int executeUpdateWithRetry(Connection conn, String sql, Object... params) throws SQLException {
int attempt = 0;
boolean success = false;
while (attempt < MAX_RETRIES && !success) {
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
setParameters(pstmt, params);
int rowsAffected = pstmt.executeUpdate();
success = true;
return rowsAffected;
} catch (SQLException e) {
handleSQLException(e, ++attempt);
}
}
throw new SQLException("Operation nach " + MAX_RETRIES + " Versuchen fehlgeschlagen.");
}
private static ResultSet executeQueryWithRetry(Connection conn, String sql, Object... params) throws SQLException {
int attempt = 0;
boolean success = false;
ResultSet rs = null;
while (attempt < MAX_RETRIES && !success) {
try {
PreparedStatement pstmt = conn.prepareStatement(sql);
setParameters(pstmt, params);
rs = pstmt.executeQuery();
success = true;
} catch (SQLException e) {
handleSQLException(e, ++attempt);
if (rs != null) {
try {
rs.close();
} catch (SQLException ex) {
System.out.println("Failed to close ResultSet: " + ex.getMessage());
}
}
}
if (!success) {
throw new SQLException("Operation nach " + MAX_RETRIES + " Versuchen fehlgeschlagen.");
}
return rs;
}
throw new SQLException("Operation nach " + MAX_RETRIES + " Versuchen fehlgeschlagen.");
}
private static void setParameters(PreparedStatement pstmt, Object... params) throws SQLException {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
}
private static void handleSQLException(SQLException e, int attempt) throws SQLException {
if (e.getMessage().contains("database is locked")) {
System.out.println("Datenbank gesperrt, versuche erneut... (Versuch " + attempt + " von " + MAX_RETRIES + ")");
try {
Thread.sleep(RETRY_WAIT_TIME_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new SQLException("Der Thread wurde unterbrochen.", ie);
}
} else {
throw e; // Andere SQL-Fehler weiterwerfen
}
}
public static Long[] getChannelIds() {
String sql = "SELECT name, value FROM \"Options\" WHERE name = \"channels\"";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql);
if (rs.next()) {
String channelOption = rs.getString("value");
if (!channelOption.isEmpty()) {
JSONArray jsonArray = new JSONArray(channelOption);
List<Long> longList = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
try {
// Versuche, den Wert in ein Long zu konvertieren
long value = jsonArray.getLong(i);
longList.add(value); // Füge den Wert der Liste hinzu, wenn es ein gültiger Long ist
} catch (Exception e) {
// Wenn der Wert kein gültiges Long ist, wird er übersprungen
//System.out.println("Überspringe ungültigen Wert: " + jsonArray.get(i));
}
}
return longList.toArray(new Long[0]);
}
return null;
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return null;
}
public static List<StoryOutputText> getChapterTexte(DataStory ds) {
String sql = """
SELECT\s
CT.id,
CT.chapterText,
CT.color,
CT.imageUrl
FROM\s
ChapterText CT
LEFT JOIN\s
ChapterText_Tags CTT\s
ON\s
CT.id = CTT.chapterText_id
LEFT JOIN\s
Tags T\s
ON\s
T.id = CTT.tag_id
WHERE
T.id = ? AND CT.active = TRUE
GROUP BY\s
CT.id;""";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql, ds.tagId());
List<StoryOutputText> outputList = new ArrayList<>();
if (rs.next()) {
outputList.add(new StoryOutputText(
rs.getString("chapterText"),
rs.getString("imageUrl"),
Color.decode(rs.getString("color")))
);
}
return outputList;
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return null;
}
public static List<StoryOutputText> getChapterTexte(int ctId) {
String sql = """
SELECT\s
CT.id,
CT.chapterText,
CT.color,
CT.imageUrl
FROM\s
ChapterText CT
WHERE
CT.id = ?
GROUP BY\s
CT.id;""";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql, ctId);
List<StoryOutputText> outputList = new ArrayList<>();
if (rs.next()) {
outputList.add(new StoryOutputText(
rs.getString("chapterText"),
rs.getString("imageUrl"),
Color.decode(rs.getString("color")))
);
}
return outputList;
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return null;
}
public static List<StoryOutputText> getStoryTexte() {
String sql = """
SELECT\s
CT.id,
CT.storyText,
CT.color,
CT.imageUrl
FROM\s
StoryText CT
WHERE
CT.active = TRUE;""";
try (Connection conn = DriverManager.getConnection(DB_URL)) {
ResultSet rs = executeQueryWithRetry(conn, sql);
List<StoryOutputText> outputList = new ArrayList<>();
if (rs.next()) {
outputList.add(new StoryOutputText(
rs.getString("storyText"),
rs.getString("imageUrl"),
Color.decode(rs.getString("color")))
);
}
return outputList;
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return null;
}
}
record DataStory(String name, String link, String ffid, long date, String author, Integer tagId) {
}
record DataChapter(String name, String link, String storyId, long date, long number) {
}

View File

@ -0,0 +1,104 @@
package de.ocame.ffdiscordbot;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.json.JSONObject;
import java.io.*;
import java.net.*;
class SocketServerRunnable implements Runnable {
private final JDA jda;
private final FanFiktionReader ffr;
public SocketServerRunnable(JDA jda, FanFiktionReader ffr) {
this.jda = jda;
this.ffr = ffr;
}
@Override
public void run() {
int port = 12345;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Socket-Server läuft auf Port " + port);
while (true) {
try (Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
String jsonMessageFromClient = in.readLine();
System.out.println("Nachricht vom Client erhalten: " + jsonMessageFromClient);
JSONObject jsonData = new JSONObject(jsonMessageFromClient);
String action = jsonData.getString("action");
JSONObject jsonResponse= new JSONObject();
Long channelId = null;
if(action.equals("channelCheck")) {
Object channelIdObj = jsonData.get("channelId");
if(channelIdObj.equals("")) {
jsonResponse.put("status", "fail");
continue;
}
if (channelIdObj instanceof Number) {
channelId = ((Number) channelIdObj).longValue();
} else if (channelIdObj instanceof String) {
String channelIdStr = (String) channelIdObj;
channelId = Long.parseLong(channelIdStr);
} else {
jsonResponse.put("status", "fail");
continue;
}
String response = "";
try {
TextChannel textchannel = jda.getTextChannelById(channelId);
if (textchannel.canTalk()) {
jsonResponse.put("status", "success")
.put("name", textchannel.getName())
.put("serverName", textchannel.getGuild().getName());
}
else {
jsonResponse.put("status", "fail");
}
} catch (NullPointerException e) {
jsonResponse.put("status", "error");
}
out.println(jsonResponse);
}
else if(action.equals("check")) {
ffr.checkAlerts();
jsonResponse.put("status", "success");
out.println(jsonResponse);
}
else if(action.equals("chapterTest")) {
String storyName = jsonData.getString("storyName");
String chapterName = jsonData.getString("chapterName");
String author = jsonData.getString("author");
Integer id = jsonData.getInt("id");
Long target = jsonData.getLong("target");
ffr.outputCheapterTester(id, chapterName, storyName, author, target);
jsonResponse.put("status", "success");
out.println(jsonResponse);
}
} catch (IOException | NullPointerException e) {
System.out.println("Fehler bei der Kommunikation mit dem Client: " + e.getMessage());
}
}
} catch (IOException e) {
System.out.println("Server konnte nicht gestartet werden: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,39 @@
package de.ocame.ffdiscordbot;
import java.awt.*;
public class StoryOutputText {
private final String message;
private final String image;
private final Color color;
public StoryOutputText(String message) {
this.message = message;
this.image = null;
this.color = new Color(36, 153, 245);
}
public StoryOutputText(String message, String image) {
this.message = message;
this.image = image;
this.color = new Color(36, 153, 245);
}
public StoryOutputText(String message, String image, Color color) {
this.message = message;
this.image = image;
this.color = color;
}
public String getMessage() {
return message;
}
public String getImage() {
return image;
}
public Color getColor() {
return color;
}
}