Reorganized code to be more object-oriented.

This commit is contained in:
2022-07-06 10:05:38 -04:00
parent e4abb0d84d
commit 3aa3b64e1c
11 changed files with 121 additions and 74 deletions
@@ -11,32 +11,25 @@ package dev.logal.crabstero;
import dev.logal.crabstero.listeners.*; import dev.logal.crabstero.listeners.*;
import org.javacord.api.DiscordApi; import org.javacord.api.DiscordApi;
import org.javacord.api.DiscordApiBuilder; import org.javacord.api.DiscordApiBuilder;
import org.javacord.api.Javacord;
import org.javacord.api.entity.intent.Intent; import org.javacord.api.entity.intent.Intent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPoolConfig;
import java.util.concurrent.*; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public final class Crabstero { public final class Crabstero {
public static final int maximumMessagesPerChannel = 50000;
private static final Logger logger = LoggerFactory.getLogger(Crabstero.class); private static final Logger logger = LoggerFactory.getLogger(Crabstero.class);
private static final String token = System.getenv("TOKEN"); private final DiscordApi discordApi;
private static final JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), System.getenv("REDIS_HOST")); private final JedisPool jedisPool;
private final ScheduledExecutorService workerPool;
private static final ScheduledExecutorService workerPool = Executors.newScheduledThreadPool(4, new CrabsteroThreadFactory()); public Crabstero(final String token, final String redisHost, final int redisPort) {
this.jedisPool = new JedisPool(new JedisPoolConfig(), redisHost, redisPort);
private Crabstero() { this.workerPool = Executors.newScheduledThreadPool(4, new CrabsteroThreadFactory());
throw new UnsupportedOperationException();
}
public static void main(final String[] arguments) {
logger.info(Javacord.USER_AGENT);
final DiscordApiBuilder builder = new DiscordApiBuilder(); final DiscordApiBuilder builder = new DiscordApiBuilder();
@@ -48,28 +41,37 @@ public final class Crabstero {
builder.setIntents(Intent.GUILDS, Intent.GUILD_MESSAGES); builder.setIntents(Intent.GUILDS, Intent.GUILD_MESSAGES);
builder.addListener(new MessageCreate()); builder.addListener(new MessageCreate(this));
builder.addListener(new RoleChangePermissions()); builder.addListener(new RoleChangePermissions(this));
builder.addListener(new ServerBecomesAvailable()); builder.addListener(new ServerBecomesAvailable(this));
builder.addListener(new ServerChannelChangeOverwrittenPermissions()); builder.addListener(new ServerChannelChangeOverwrittenPermissions(this));
builder.addListener(new ServerJoin()); builder.addListener(new ServerJoin(this));
builder.addListener(new UserRoleAdd()); builder.addListener(new UserRoleAdd(this));
builder.setRecommendedTotalShards(); builder.setRecommendedTotalShards();
final DiscordApi api = builder.login().join(); this.discordApi = builder.login().join();
api.setMessageCacheSize(0, 1); this.discordApi.setMessageCacheSize(0, 1);
} }
public static Future<?> submitTask(final Runnable task) { public static void main(final String[] arguments) {
return workerPool.submit(task); final String token = System.getenv("TOKEN");
final String redisHost = System.getenv("REDIS_HOST");
logger.info("Starting Crabstero...");
new Crabstero(token, redisHost, 6379);
logger.info("Crabstero started!");
} }
public static ScheduledFuture<?> scheduleTask(final Runnable task, final long delay, final TimeUnit unit) { public DiscordApi getDiscordApi() {
return workerPool.schedule(task, delay, unit); return this.discordApi;
} }
public static Jedis getJedis() { public JedisPool getJedisPool() {
return jedisPool.getResource(); return this.jedisPool;
}
public ScheduledExecutorService getWorkerPool() {
return this.workerPool;
} }
} }
@@ -11,7 +11,7 @@ package dev.logal.crabstero;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
public final class CrabsteroThreadFactory implements ThreadFactory { public final class CrabsteroThreadFactory implements ThreadFactory {
private static int threadNumber = 1; private int threadNumber = 1;
@Override @Override
public Thread newThread(final Runnable runnable) { public Thread newThread(final Runnable runnable) {
@@ -8,6 +8,7 @@
package dev.logal.crabstero.listeners; package dev.logal.crabstero.listeners;
import dev.logal.crabstero.Crabstero;
import dev.logal.crabstero.utils.MarkovChainMessages; import dev.logal.crabstero.utils.MarkovChainMessages;
import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.Message;
@@ -17,6 +18,12 @@ import org.javacord.api.event.message.MessageCreateEvent;
import org.javacord.api.listener.message.MessageCreateListener; import org.javacord.api.listener.message.MessageCreateListener;
public final class MessageCreate implements MessageCreateListener { public final class MessageCreate implements MessageCreateListener {
private final MarkovChainMessages markovChainMessages;
public MessageCreate(final Crabstero crabstero) {
this.markovChainMessages = new MarkovChainMessages(crabstero);
}
@Override @Override
public void onMessageCreate(final MessageCreateEvent event) { public void onMessageCreate(final MessageCreateEvent event) {
// Is this message being created outside of a server text channel or a server thread channel? // Is this message being created outside of a server text channel or a server thread channel?
@@ -34,13 +41,13 @@ public final class MessageCreate implements MessageCreateListener {
// Is this message mentioning Crabstero? // Is this message mentioning Crabstero?
final Message message = event.getMessage(); final Message message = event.getMessage();
if (message.getMentionedUsers().contains(event.getApi().getYourself())) { if (message.getMentionedUsers().contains(event.getApi().getYourself())) {
MarkovChainMessages.replyToMessage(message); // Yes. Respond to it. this.markovChainMessages.replyToMessage(message); // Yes. Respond to it.
return; // Prevent further processing of the message. return; // Prevent further processing of the message.
} }
// Is this message a normal message outside of a server thread channel? // Is this message a normal message outside of a server thread channel?
if (message.getType() == MessageType.NORMAL && channel.asServerThreadChannel().isEmpty()) { if (message.getType() == MessageType.NORMAL && channel.asServerThreadChannel().isEmpty()) {
MarkovChainMessages.ingestMessage(message); // Yes. Ingest the message. this.markovChainMessages.ingestMessage(message); // Yes. Ingest the message.
} }
} }
} }
@@ -14,11 +14,17 @@ import org.javacord.api.event.server.role.RoleChangePermissionsEvent;
import org.javacord.api.listener.server.role.RoleChangePermissionsListener; import org.javacord.api.listener.server.role.RoleChangePermissionsListener;
public final class RoleChangePermissions implements RoleChangePermissionsListener { public final class RoleChangePermissions implements RoleChangePermissionsListener {
private final Crabstero crabstero;
public RoleChangePermissions(final Crabstero crabstero) {
this.crabstero = crabstero;
}
@Override @Override
public void onRoleChangePermissions(final RoleChangePermissionsEvent event) { public void onRoleChangePermissions(final RoleChangePermissionsEvent event) {
if (event.getRole().hasUser(event.getApi().getYourself())) { if (event.getRole().hasUser(event.getApi().getYourself())) {
event.getServer().getTextChannels().forEach((channel) -> { event.getServer().getTextChannels().forEach((channel) -> {
Crabstero.submitTask(new ChannelHistoryIngestionTask(channel)); this.crabstero.getWorkerPool().submit(new ChannelHistoryIngestionTask(channel, crabstero));
}); });
} }
} }
@@ -14,10 +14,16 @@ import org.javacord.api.event.server.ServerBecomesAvailableEvent;
import org.javacord.api.listener.server.ServerBecomesAvailableListener; import org.javacord.api.listener.server.ServerBecomesAvailableListener;
public final class ServerBecomesAvailable implements ServerBecomesAvailableListener { public final class ServerBecomesAvailable implements ServerBecomesAvailableListener {
private final Crabstero crabstero;
public ServerBecomesAvailable(final Crabstero crabstero) {
this.crabstero = crabstero;
}
@Override @Override
public void onServerBecomesAvailable(final ServerBecomesAvailableEvent event) { public void onServerBecomesAvailable(final ServerBecomesAvailableEvent event) {
event.getServer().getTextChannels().forEach((channel) -> { event.getServer().getTextChannels().forEach((channel) -> {
Crabstero.submitTask(new ChannelHistoryIngestionTask(channel)); this.crabstero.getWorkerPool().submit(new ChannelHistoryIngestionTask(channel, crabstero));
}); });
} }
} }
@@ -14,10 +14,16 @@ import org.javacord.api.event.channel.server.ServerChannelChangeOverwrittenPermi
import org.javacord.api.listener.channel.server.ServerChannelChangeOverwrittenPermissionsListener; import org.javacord.api.listener.channel.server.ServerChannelChangeOverwrittenPermissionsListener;
public final class ServerChannelChangeOverwrittenPermissions implements ServerChannelChangeOverwrittenPermissionsListener { public final class ServerChannelChangeOverwrittenPermissions implements ServerChannelChangeOverwrittenPermissionsListener {
private final Crabstero crabstero;
public ServerChannelChangeOverwrittenPermissions(final Crabstero crabstero) {
this.crabstero = crabstero;
}
@Override @Override
public void onServerChannelChangeOverwrittenPermissions(final ServerChannelChangeOverwrittenPermissionsEvent event) { public void onServerChannelChangeOverwrittenPermissions(final ServerChannelChangeOverwrittenPermissionsEvent event) {
event.getChannel().asServerTextChannel().ifPresent((channel) -> { event.getChannel().asServerTextChannel().ifPresent((channel) -> {
Crabstero.submitTask(new ChannelHistoryIngestionTask(channel)); this.crabstero.getWorkerPool().submit(new ChannelHistoryIngestionTask(channel, crabstero));
}); });
} }
} }
@@ -23,6 +23,12 @@ import java.awt.*;
public final class ServerJoin implements ServerJoinListener { public final class ServerJoin implements ServerJoinListener {
private static final Logger logger = LoggerFactory.getLogger(ServerJoin.class); private static final Logger logger = LoggerFactory.getLogger(ServerJoin.class);
private final Crabstero crabstero;
public ServerJoin(final Crabstero crabstero) {
this.crabstero = crabstero;
}
@Override @Override
public void onServerJoin(final ServerJoinEvent event) { public void onServerJoin(final ServerJoinEvent event) {
final Server server = event.getServer(); final Server server = event.getServer();
@@ -41,7 +47,7 @@ public final class ServerJoin implements ServerJoinListener {
}); });
server.getTextChannels().forEach((channel) -> { server.getTextChannels().forEach((channel) -> {
Crabstero.submitTask(new ChannelHistoryIngestionTask(channel)); this.crabstero.getWorkerPool().submit(new ChannelHistoryIngestionTask(channel, crabstero));
}); });
} }
} }
@@ -14,11 +14,17 @@ import org.javacord.api.event.server.role.UserRoleAddEvent;
import org.javacord.api.listener.server.role.UserRoleAddListener; import org.javacord.api.listener.server.role.UserRoleAddListener;
public final class UserRoleAdd implements UserRoleAddListener { public final class UserRoleAdd implements UserRoleAddListener {
private final Crabstero crabstero;
public UserRoleAdd(final Crabstero crabstero) {
this.crabstero = crabstero;
}
@Override @Override
public void onUserRoleAdd(final UserRoleAddEvent event) { public void onUserRoleAdd(final UserRoleAddEvent event) {
if (event.getUser().isYourself()) { if (event.getUser().isYourself()) {
event.getServer().getTextChannels().forEach((channel) -> { event.getServer().getTextChannels().forEach((channel) -> {
Crabstero.submitTask(new ChannelHistoryIngestionTask(channel)); this.crabstero.getWorkerPool().submit(new ChannelHistoryIngestionTask(channel, crabstero));
}); });
} }
} }
@@ -22,13 +22,16 @@ import java.util.stream.Stream;
public final class ChannelHistoryIngestionTask implements Runnable { public final class ChannelHistoryIngestionTask implements Runnable {
private static final String INGESTED_CHANNELS_KEY = "ingestedChannels"; private static final String INGESTED_CHANNELS_KEY = "ingestedChannels";
private static final int MAXIMUM_MESSAGES_PER_CHANNEL = 50000;
private static final Logger logger = LoggerFactory.getLogger(ChannelHistoryIngestionTask.class); private static final Logger logger = LoggerFactory.getLogger(ChannelHistoryIngestionTask.class);
private final Crabstero crabstero;
private final ServerTextChannel channel; private final ServerTextChannel channel;
public ChannelHistoryIngestionTask(final ServerTextChannel channel) { public ChannelHistoryIngestionTask(final ServerTextChannel channel, final Crabstero crabstero) {
this.channel = channel; this.channel = channel;
this.crabstero = crabstero;
} }
@Override @Override
@@ -39,7 +42,7 @@ public final class ChannelHistoryIngestionTask implements Runnable {
return; return;
} }
try (final Jedis jedis = Crabstero.getJedis()) { try (final Jedis jedis = this.crabstero.getJedisPool().getResource()) {
if (jedis.lrange(INGESTED_CHANNELS_KEY, 0, -1).contains(this.channel.getIdAsString())) { if (jedis.lrange(INGESTED_CHANNELS_KEY, 0, -1).contains(this.channel.getIdAsString())) {
return; return;
} else { } else {
@@ -49,13 +52,14 @@ public final class ChannelHistoryIngestionTask implements Runnable {
} }
} }
final MarkovChainMessages markovChainMessages = new MarkovChainMessages(this.crabstero);
try (final Stream<Message> history = this.channel.getMessagesAsStream()) { try (final Stream<Message> history = this.channel.getMessagesAsStream()) {
final Iterator<Message> iterator = history.iterator(); final Iterator<Message> iterator = history.iterator();
int i = 0; int i = 0;
while (iterator.hasNext()) { while (iterator.hasNext()) {
i++; i++;
MarkovChainMessages.ingestMessage(iterator.next()); markovChainMessages.ingestMessage(iterator.next());
if (i == Crabstero.maximumMessagesPerChannel) { if (i == MAXIMUM_MESSAGES_PER_CHANNEL) {
break; break;
} }
} }
@@ -18,15 +18,24 @@ import java.util.Random;
public final class MarkovChain { public final class MarkovChain {
private static final char DEFAULT_SENTENCE_END = '§'; private static final char DEFAULT_SENTENCE_END = '§';
private static final Random rng = new SecureRandom();
private final long id; private final long id;
private final Crabstero crabstero;
private final Random rng;
public MarkovChain(final long id) { public MarkovChain(final long id, final Crabstero crabstero) {
this.id = id; this.id = id;
this.crabstero = crabstero;
this.rng = new SecureRandom();
} }
private static final boolean isCompleteSentence(final String sentence) { public MarkovChain(final long id, final Crabstero crabstero, final Random random) {
this.id = id;
this.crabstero = crabstero;
this.rng = random;
}
private static boolean isCompleteSentence(final String sentence) {
if (sentence.isEmpty()) { if (sentence.isEmpty()) {
return false; return false;
} }
@@ -42,8 +51,8 @@ public final class MarkovChain {
final String[] sentences = paragraph.trim().replaceAll(" +", " ").replaceAll("\n", " ").split("(?<=[.!?]) "); final String[] sentences = paragraph.trim().replaceAll(" +", " ").replaceAll("\n", " ").split("(?<=[.!?]) ");
for (int i = 0; i < sentences.length; i++) { for (String sentence : sentences) {
this.ingestSentence(sentences[i]); this.ingestSentence(sentence);
} }
} }
@@ -54,7 +63,7 @@ public final class MarkovChain {
String[] words = sentence.trim().replaceAll(" +", " ").split(" "); String[] words = sentence.trim().replaceAll(" +", " ").split(" ");
try (final Jedis jedis = Crabstero.getJedis()) { try (final Jedis jedis = this.crabstero.getJedisPool().getResource()) {
final Pipeline pipeline = jedis.pipelined(); final Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < words.length - 1; i++) { for (int i = 0; i < words.length - 1; i++) {
if (i == 0) { if (i == 0) {
@@ -70,7 +79,7 @@ public final class MarkovChain {
public String generate(final int softCharacterLimit, final int hardCharacterLimit) { public String generate(final int softCharacterLimit, final int hardCharacterLimit) {
final StringBuilder newSentence = new StringBuilder(); final StringBuilder newSentence = new StringBuilder();
try (final Jedis jedis = Crabstero.getJedis()) { try (final Jedis jedis = this.crabstero.getJedisPool().getResource()) {
if (!jedis.exists(this.id + ":start")) { if (!jedis.exists(this.id + ":start")) {
this.ingestSentence("Hello world!"); this.ingestSentence("Hello world!");
} }
@@ -104,7 +113,7 @@ public final class MarkovChain {
} }
word = wordChoices.get(index); word = wordChoices.get(index);
newSentence.append(" " + word); newSentence.append(" ").append(word);
final int sentenceLength = newSentence.length(); final int sentenceLength = newSentence.length();
if (sentenceLength >= hardCharacterLimit) { if (sentenceLength >= hardCharacterLimit) {
@@ -25,22 +25,22 @@ import java.util.List;
import java.util.Random; import java.util.Random;
public class MarkovChainMessages { public class MarkovChainMessages {
private static final AllowedMentions allowedMentions; private final Crabstero crabstero;
private static final Random rng = new SecureRandom();
private final AllowedMentions allowedMentions;
private final Random rng = new SecureRandom();
public MarkovChainMessages(final Crabstero crabstero) {
this.crabstero = crabstero;
static {
final AllowedMentionsBuilder builder = new AllowedMentionsBuilder(); final AllowedMentionsBuilder builder = new AllowedMentionsBuilder();
builder.setMentionEveryoneAndHere(false); builder.setMentionEveryoneAndHere(false);
builder.setMentionRoles(false); builder.setMentionRoles(false);
builder.setMentionUsers(false); builder.setMentionUsers(false);
allowedMentions = builder.build(); this.allowedMentions = builder.build();
} }
private MarkovChainMessages() { public void replyToMessage(final Message message) {
throw new UnsupportedOperationException();
}
public static void replyToMessage(final Message message) {
final TextChannel channel = message.getChannel(); final TextChannel channel = message.getChannel();
if (!channel.canYouWrite()) { if (!channel.canYouWrite()) {
return; return;
@@ -55,20 +55,20 @@ public class MarkovChainMessages {
final MessageBuilder response = new MessageBuilder(); final MessageBuilder response = new MessageBuilder();
final MarkovChain markovChain = new MarkovChain(channelID); final MarkovChain markovChain = new MarkovChain(channelID, this.crabstero);
response.replyTo(message); response.replyTo(message);
response.setContent(markovChain.generate(750, 1000)); response.setContent(markovChain.generate(750, 1000));
if (rng.nextDouble() >= 0.95 && channel.canYouEmbedLinks()) { if (this.rng.nextDouble() >= 0.95 && channel.canYouEmbedLinks()) {
final EmbedBuilder embed = new EmbedBuilder(); final EmbedBuilder embed = new EmbedBuilder();
embed.setTitle(markovChain.generate(200, 300)); embed.setTitle(markovChain.generate(200, 300));
embed.setDescription(markovChain.generate(300, 500)); embed.setDescription(markovChain.generate(300, 500));
try (final Jedis jedis = Crabstero.getJedis()) { try (final Jedis jedis = this.crabstero.getJedisPool().getResource()) {
final List<String> embedImageURLs = jedis.lrange(channelID + ":images", 0, -1); final List<String> embedImageURLs = jedis.lrange(channelID + ":images", 0, -1);
if (embedImageURLs.size() > 0) { if (embedImageURLs.size() > 0) {
embed.setImage(embedImageURLs.get(rng.nextInt(embedImageURLs.size()))); embed.setImage(embedImageURLs.get(this.rng.nextInt(embedImageURLs.size())));
} }
} }
@@ -80,14 +80,14 @@ public class MarkovChainMessages {
response.send(channel).exceptionally(ExceptionLogger.get()); response.send(channel).exceptionally(ExceptionLogger.get());
} }
public static void ingestMessage(final Message message) { public void ingestMessage(final Message message) {
final MessageAuthor author = message.getAuthor(); final MessageAuthor author = message.getAuthor();
if (author.isBotUser() || author.isWebhook() || message.getMentionedUsers().contains(message.getApi().getYourself())) { if (author.isBotUser() || author.isWebhook() || message.getMentionedUsers().contains(message.getApi().getYourself())) {
return; return;
} }
final long channelID = message.getChannel().getId(); final long channelID = message.getChannel().getId();
final MarkovChain markovChain = new MarkovChain(channelID); final MarkovChain markovChain = new MarkovChain(channelID, this.crabstero);
markovChain.ingest(message.getContent()); markovChain.ingest(message.getContent());
@@ -96,19 +96,14 @@ public class MarkovChainMessages {
} }
} }
public static void ingestEmbed(final long channelID, final Embed embed) { public void ingestEmbed(final long channelID, final Embed embed) {
final MarkovChain markovChain = new MarkovChain(channelID); final MarkovChain markovChain = new MarkovChain(channelID, this.crabstero);
embed.getTitle().ifPresent((title) -> { embed.getTitle().ifPresent(markovChain::ingest);
markovChain.ingest(title); embed.getDescription().ifPresent(markovChain::ingest);
});
embed.getDescription().ifPresent((description) -> {
markovChain.ingest(description);
});
embed.getImage().ifPresent((image) -> { embed.getImage().ifPresent((image) -> {
try (final Jedis jedis = Crabstero.getJedis()) { try (final Jedis jedis = this.crabstero.getJedisPool().getResource()) {
jedis.lpush(channelID + ":images", image.getUrl().toString()); jedis.lpush(channelID + ":images", image.getUrl().toString());
} }
}); });