Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Add basic nonsense generator #24

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/org/moss/discord/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class Constants {

public static final String CHANNEL_STARBOARD = ""; // TODO: fill these in once created
public static final String CHANNEL_MODLOG = "430895774075846656";
public static final String CHANNEL_NONSENSE = "";

// Roles

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/moss/discord/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.javacord.api.DiscordApiBuilder;
import org.moss.discord.commands.BStatsCommand;
import org.moss.discord.listeners.ModLogListeners;
import org.moss.discord.listeners.NonsenseListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -36,6 +37,7 @@ public static void main(String[] args) {
commandHandler.registerCommand(new BStatsCommand());

api.addListener(new ModLogListeners(api));
api.addMessageCreateListener(new NonsenseListener(api));
}

}
122 changes: 122 additions & 0 deletions src/main/java/org/moss/discord/fun/Nonsense.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.moss.discord.fun;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.moss.discord.util.WordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.moss.discord.util.WordUtil.*;

public class Nonsense {
private static final int order = 3;
private static final String start = "B";
private static final String end = "Z";

private static final ExecutorService executor = Executors.newFixedThreadPool(16);
private static final Logger logger = LoggerFactory.getLogger(Nonsense.class);
private static final Random random = new Random();

private AtomicInteger linesProcessed;
private AtomicInteger nodesProcessed;

private ConcurrentMap<String, ConcurrentMap<String, AtomicInteger>> nodes = new ConcurrentHashMap<>();
private CompletableFuture<ConcurrentMap<String, ConcurrentMap<String, AtomicInteger>>> ready = new CompletableFuture<>();

public Nonsense(String corpus) {
CompletableFuture<Void>[] tasks = addToChain(corpus);

CompletableFuture.allOf(tasks).thenRunAsync(() -> {
ready.complete(nodes);
}, executor);
}

@SuppressWarnings("unchecked")
public CompletableFuture<Void>[] addToChain(String corpus) {
List<String> lines = splitLines(corpus);
linesProcessed = new AtomicInteger(0);
nodesProcessed = new AtomicInteger(0);

CompletableFuture<Void>[] tasks = (CompletableFuture<Void>[]) lines.stream()
.map(this::addControlChars)
.map(WordUtil::splitWords)
.map(WordUtil::removeEmpty)
.map(words -> CompletableFuture.runAsync(() -> parseLine(words), executor))
.toArray(CompletableFuture[]::new);

return tasks;
}

public void parseLine(List<String> words) {
for (int i = order; i < words.size(); i++) {
String prefix = joinWords(words.subList(i - order, i));
String suffix = words.get(i);

nodes.putIfAbsent(prefix, new ConcurrentHashMap<>());

ConcurrentMap<String, AtomicInteger> node = nodes.get(prefix);
node.putIfAbsent(suffix, new AtomicInteger(0));

node.get(suffix).incrementAndGet();
nodesProcessed.incrementAndGet();
}
int lines = linesProcessed.incrementAndGet();
if (lines % 1000 == 0) logger.info("Parsed {} lines and {} nodes", lines, nodesProcessed.get());
}

private String addControlChars(String original) {
return getInitialPrefixNode() + original + " " + end; // Add initial prefix and terminator
}

public String generateNonsense() {
return predictNext(getInitialPrefixNode()).replace(getInitialPrefixNode(), "");
}

private String predictNext(String sentence) {
List<String> words = removeEmpty(new ArrayList<>(splitWords(sentence)));
String prefix = joinWords(words.subList(words.size() - order, words.size()));
Map<String, AtomicInteger> suffixMap = nodes.get(prefix);

if (suffixMap == null) return sentence;

List<String> suffixes = new ArrayList<>();
suffixMap.forEach((suffix, chance) -> {
for (int i = 0; i < chance.get(); i++) {
suffixes.add(suffix);
}
});

String suffix = suffixes.get(random.nextInt(suffixes.size()));
if (suffix.equals(end)) return joinWords(words);

words.add(suffix);
String result = joinWords(words);
//logger.info("'{}' + '{}' -> '{}'", prefix, suffix, result);
return predictNext(result);
}

public CompletableFuture<ConcurrentMap<String, ConcurrentMap<String, AtomicInteger>>> getReady() {
return ready;
}

private String getInitialPrefixNode() {
String node = start;

for (int i = 1; i < order; i++) {
node = node + " " + start;
}

return node;
}

}
80 changes: 80 additions & 0 deletions src/main/java/org/moss/discord/listeners/NonsenseListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.moss.discord.listeners;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

import org.javacord.api.DiscordApi;
import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message;
import org.javacord.api.event.message.MessageCreateEvent;
import org.javacord.api.listener.message.MessageCreateListener;
import org.javacord.api.util.DiscordRegexPattern;
import org.moss.discord.Constants;
import org.moss.discord.fun.Nonsense;

public class NonsenseListener implements MessageCreateListener {
private static File corpusFile = new File("./corpus.txt");

private DiscordApi api;
private Nonsense nonsense;

private boolean ready = false;

public NonsenseListener(DiscordApi discordApi) {
api = discordApi;

String corpus = "";

try {
Scanner scanner = new Scanner(corpusFile);
scanner.useDelimiter("\\Z");
corpus = scanner.next()
.replaceAll("[“”]", "\"")
.replaceAll("[‘’]", "\'");
scanner.close();
} catch (FileNotFoundException ignored) {}

nonsense = new Nonsense(corpus);

nonsense.getReady().whenCompleteAsync((chain, throwable) -> {
if (throwable == null) ready = true;
});
}

@Override
public void onMessageCreate(MessageCreateEvent event) {
api.getMessageById(event.getMessageId(), event.getChannel()).thenAccept(this::handleMessage);
}

private void handleMessage(Message message) {
TextChannel channel = message.getChannel();
if (!ready
|| !channel.getIdAsString().equals(Constants.CHANNEL_NONSENSE)
|| message.getAuthor().isYourself()) return;

if (message.getMentionedUsers().contains(api.getYourself())) {
String response = message.getAuthor().asUser().get().getMentionTag() + " " + nonsense.generateNonsense();
channel.sendMessage(response);
} else {
storeMessage(message.getContent());
}
}

private void storeMessage(String message) {
String filtered = message.replaceAll(DiscordRegexPattern.CHANNEL_MENTION.pattern(), "")
.replaceAll(DiscordRegexPattern.CUSTOM_EMOJI.pattern(), "")
.replaceAll(DiscordRegexPattern.ROLE_MENTION.pattern(), "")
.replaceAll(DiscordRegexPattern.USER_MENTION.pattern(), "");

try {
FileWriter fw = new FileWriter(corpusFile, true);
fw.append("\n" + filtered);
fw.close();
} catch (IOException ignored) {}

nonsense.addToChain(filtered);
}
}
43 changes: 43 additions & 0 deletions src/main/java/org/moss/discord/util/WordUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.moss.discord.util;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class WordUtil {

public static List<String> splitLines(String input) {
return Arrays.asList(input.split("\\r?\\n"));
}

public static List<String> splitWords(String input) {
return Arrays.asList(input.split("\\b"));
}

public static List<String> removeEmpty(List<String> strings) {
return strings.stream()
.map(String::trim)
.filter(s -> !s.equalsIgnoreCase(""))
.collect(Collectors.toList());
}

public static String joinWords(List<String> words) {
Iterator<String> i = removeEmpty(words).iterator();
if (!i.hasNext()) return "";

String result = i.next();

while (i.hasNext()) {
String next = i.next();
result = result
+ (Pattern.matches("\\p{Punct}", next) ? "" : " ") + next;
}

return result;
}



}