|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2022
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
|
|
|
|
package discord;
|
|
|
|
|
|
|
|
import discord.handlers.*;
|
|
|
|
import engine.gameManager.ConfigManager;
|
|
|
|
import net.dv8tion.jda.api.JDA;
|
|
|
|
import net.dv8tion.jda.api.JDABuilder;
|
|
|
|
import net.dv8tion.jda.api.entities.*;
|
|
|
|
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
|
|
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
|
|
|
import net.dv8tion.jda.api.requests.GatewayIntent;
|
|
|
|
import net.dv8tion.jda.api.utils.MemberCachePolicy;
|
|
|
|
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
|
|
|
import org.pmw.tinylog.Configurator;
|
|
|
|
import org.pmw.tinylog.Level;
|
|
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import org.pmw.tinylog.labelers.TimestampLabeler;
|
|
|
|
import org.pmw.tinylog.policies.StartupPolicy;
|
|
|
|
import org.pmw.tinylog.writers.RollingFileWriter;
|
|
|
|
|
|
|
|
import javax.security.auth.login.LoginException;
|
|
|
|
import java.io.*;
|
|
|
|
import java.util.*;
|
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
import static discord.ChatChannel.ADMINLOG;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MagicBot is many things to Magicbane...
|
|
|
|
*
|
|
|
|
* -Project Mascot
|
|
|
|
* -Customer service and administration bot
|
|
|
|
* -Benevolent dictator
|
|
|
|
* -Investment manager.
|
|
|
|
*
|
|
|
|
* MagicBot will never beg you for money. He is a very
|
|
|
|
* responsible robot. He was varnished but never garnished.
|
|
|
|
* MagicBot does not for to overclock himself. His chips
|
|
|
|
* will therefore never overcook.
|
|
|
|
* MagicBot will never be a pitiful robot trying for to use
|
|
|
|
* you as emotional support human.
|
|
|
|
*
|
|
|
|
* MagicBot is just not that sort of robot and Magicbane
|
|
|
|
* just isn't that sort of project.
|
|
|
|
*
|
|
|
|
* MagicBot runs a Shaodowbane emulator not a Second Life emulator.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public class MagicBot extends ListenerAdapter {
|
|
|
|
|
|
|
|
public static final Pattern accountNameRegex = Pattern.compile("^[\\p{Alnum}]{6,20}$");
|
|
|
|
public static final Pattern passwordRegex = Pattern.compile("^[\\p{Alnum}]{6,20}$");
|
|
|
|
public static JDA jda;
|
|
|
|
public static Database database;
|
|
|
|
public static long discordServerID;
|
|
|
|
public static long discordRoleID;
|
|
|
|
|
|
|
|
public static Guild magicbaneDiscord;
|
|
|
|
public static Role memberRole;
|
|
|
|
public static TextChannel septicChannel;
|
|
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) throws LoginException, InterruptedException {
|
|
|
|
|
|
|
|
// Configure tinylogger
|
|
|
|
|
|
|
|
Configurator.defaultConfig()
|
|
|
|
.addWriter(new RollingFileWriter("logs/discord/magicbot.txt", 30, new TimestampLabeler(), new StartupPolicy()))
|
|
|
|
.level(Level.DEBUG)
|
|
|
|
.formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
|
|
|
|
.activate();
|
|
|
|
|
|
|
|
// Configuration Manager to the front desk
|
|
|
|
|
|
|
|
if (ConfigManager.init() == false) {
|
|
|
|
Logger.error("ABORT! Missing config entry!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure Discord essential identifiers
|
|
|
|
|
|
|
|
discordServerID = Long.parseLong(ConfigManager.MB_MAGICBOT_SERVERID.getValue());
|
|
|
|
discordRoleID = Long.parseLong(ConfigManager.MB_MAGICBOT_ROLEID.getValue());
|
|
|
|
|
|
|
|
// Configure and instance the database interface
|
|
|
|
|
|
|
|
database = new Database();
|
|
|
|
database.configureDatabase();
|
|
|
|
|
|
|
|
// Authentication token issued to MagicBot application to connect
|
|
|
|
// to Discord is stored in config. Bot is pre-invited to the Magicbane server.
|
|
|
|
|
|
|
|
// Configure and create JDA discord instance
|
|
|
|
|
|
|
|
JDABuilder jdaBuilder = JDABuilder.create(GatewayIntent.GUILD_MEMBERS, GatewayIntent.DIRECT_MESSAGES)
|
|
|
|
.setToken(ConfigManager.MB_MAGICBOT_BOTTOKEN.getValue())
|
|
|
|
.addEventListeners(new MagicBot())
|
|
|
|
.disableCache(EnumSet.of(CacheFlag.VOICE_STATE, CacheFlag.EMOTE,
|
|
|
|
CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS))
|
|
|
|
.setMemberCachePolicy(MemberCachePolicy.ALL);
|
|
|
|
|
|
|
|
jda = jdaBuilder.build();
|
|
|
|
jda.awaitReady();
|
|
|
|
|
|
|
|
// Cache guild and role values for later usage in #register
|
|
|
|
|
|
|
|
magicbaneDiscord = jda.getGuildById(discordServerID);
|
|
|
|
memberRole = magicbaneDiscord.getRoleById(discordRoleID);
|
|
|
|
|
|
|
|
// Initialize chat channel support
|
|
|
|
|
|
|
|
ChatChannel.Init();
|
|
|
|
|
|
|
|
// Background thread to send Admin Events
|
|
|
|
Runnable adminLogRunnable = () -> SendAdminLogUpdates();
|
|
|
|
|
|
|
|
ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
|
|
|
|
exec.scheduleAtFixedRate(adminLogRunnable, 0, 1, TimeUnit.MINUTES);
|
|
|
|
|
|
|
|
Logger.info("***MAGICBOT IS RUNNING***");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void sendResponse(MessageReceivedEvent event, String responseContent) {
|
|
|
|
|
|
|
|
// Send a formatted MagicBot response to a Discord user
|
|
|
|
|
|
|
|
String discordUserName;
|
|
|
|
MessageChannel channel;
|
|
|
|
|
|
|
|
// Exit if discord is offline
|
|
|
|
|
|
|
|
if (jda.getStatus().equals(JDA.Status.CONNECTED) == false)
|
|
|
|
return;
|
|
|
|
|
|
|
|
discordUserName = event.getAuthor().getName();
|
|
|
|
channel = event.getMessage().getChannel();
|
|
|
|
|
|
|
|
channel.sendMessage(
|
|
|
|
"```\n" + "Hello Player " + discordUserName + "\n\n" +
|
|
|
|
responseContent + "\n\n" +
|
|
|
|
RobotSpeak.getRobotSpeak() + "\n```").queue();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isAdminEvent(MessageReceivedEvent event) {
|
|
|
|
|
|
|
|
String discordAccountID = event.getAuthor().getId();
|
|
|
|
List<DiscordAccount> discordAccounts;
|
|
|
|
DiscordAccount discordAccount;
|
|
|
|
|
|
|
|
// Note that database errors will cause this to return false.
|
|
|
|
// After the database is offline Avail status must be set
|
|
|
|
// to true before any subsequent admin commands will function.
|
|
|
|
|
|
|
|
if (Database.online == false)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
discordAccounts = database.getDiscordAccounts(discordAccountID);
|
|
|
|
|
|
|
|
if (discordAccounts.isEmpty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
discordAccount = discordAccounts.get(0);
|
|
|
|
return (discordAccount.isDiscordAdmin == 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String generatePassword(int length) {
|
|
|
|
|
|
|
|
String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
|
|
StringBuilder passwordBuilder = new StringBuilder(length);
|
|
|
|
Random random = new Random();
|
|
|
|
|
|
|
|
// Generate alphanumeric password of a given length.
|
|
|
|
// Could not find a good method of generating a password
|
|
|
|
// based upon a given regex.
|
|
|
|
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
passwordBuilder.append(ALPHABET.charAt(random.nextInt(ALPHABET.length())));
|
|
|
|
|
|
|
|
return new String(passwordBuilder);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String readLogFile(String filePath, int lineCount) {
|
|
|
|
|
|
|
|
ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "tail -n " + lineCount + " " + filePath);
|
|
|
|
builder.redirectErrorStream(true);
|
|
|
|
Process process = null;
|
|
|
|
String line = null;
|
|
|
|
String logOutput = "";
|
|
|
|
|
|
|
|
try {
|
|
|
|
process = builder.start();
|
|
|
|
|
|
|
|
InputStream is = process.getInputStream();
|
|
|
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
|
|
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
logOutput += line + "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
Logger.error(e.toString());
|
|
|
|
return "Error while reading logfile";
|
|
|
|
}
|
|
|
|
|
|
|
|
return logOutput;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void junkbot(String command, String[] inString) {
|
|
|
|
|
|
|
|
String outString;
|
|
|
|
Writer fileWriter;
|
|
|
|
|
|
|
|
if (inString == null)
|
|
|
|
return;
|
|
|
|
;
|
|
|
|
|
|
|
|
outString = command + String.join(" ", inString);
|
|
|
|
outString += "\n";
|
|
|
|
|
|
|
|
try {
|
|
|
|
fileWriter = new BufferedWriter(new FileWriter("junkbot.txt", true));
|
|
|
|
fileWriter.append(outString);
|
|
|
|
fileWriter.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void SendAdminLogUpdates() {
|
|
|
|
HashMap<Integer, String> adminEvents = database.getAdminEvents();
|
|
|
|
|
|
|
|
for (int adminEvent : adminEvents.keySet()) {
|
|
|
|
|
|
|
|
// Set event as read
|
|
|
|
database.setAdminEventAsRead(adminEvent);
|
|
|
|
String outString =
|
|
|
|
"```\n" + "Hello Players \n\n" +
|
|
|
|
adminEvents.get(adminEvent) + "\n\n" +
|
|
|
|
RobotSpeak.getRobotSpeak() + "\n```";
|
|
|
|
|
|
|
|
if (ADMINLOG.textChannel.canTalk())
|
|
|
|
ADMINLOG.textChannel.sendMessage(outString).queue();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onMessageReceived(MessageReceivedEvent event) {
|
|
|
|
|
|
|
|
// Exit if discord is offline
|
|
|
|
|
|
|
|
if (jda.getStatus().equals(JDA.Status.CONNECTED) == false)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Early exit if message sent to us by another bot or ourselves.
|
|
|
|
|
|
|
|
if (event.getAuthor().isBot())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Extract message and origin channel from event
|
|
|
|
|
|
|
|
Message message = event.getMessage();
|
|
|
|
|
|
|
|
// Only private messages
|
|
|
|
MessageChannel channel = event.getMessage().getChannel();
|
|
|
|
|
|
|
|
if (channel.getType().equals(ChannelType.PRIVATE) == false)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Only real users
|
|
|
|
|
|
|
|
if (event.getAuthor().isBot())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Only users who have actually joined Magicbane discord.
|
|
|
|
|
|
|
|
if (magicbaneDiscord.isMember(event.getAuthor()) == false)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// getContentRaw() is an atomic getter
|
|
|
|
// getContentDisplay() is a lazy getter which modifies the content
|
|
|
|
// for e.g. console view or logging (strip discord formatting)
|
|
|
|
|
|
|
|
String content = message.getContentRaw();
|
|
|
|
String[] args = content.split(" ");
|
|
|
|
String command = args[0].toLowerCase();
|
|
|
|
|
|
|
|
if (args.length > 1)
|
|
|
|
args = Arrays.copyOfRange(args, 1, args.length);
|
|
|
|
else
|
|
|
|
args = new String[0];
|
|
|
|
|
|
|
|
switch (command) {
|
|
|
|
case "#register":
|
|
|
|
RegisterAccountHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#help":
|
|
|
|
handleHelpRequest(event);
|
|
|
|
break;
|
|
|
|
case "#account":
|
|
|
|
AccountInfoRequest.handleRequest(event);
|
|
|
|
break;
|
|
|
|
case "#password":
|
|
|
|
PasswordChangeHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#changelog":
|
|
|
|
ChatChannelHandler.handleRequest(ChatChannel.CHANGELOG, event, args);
|
|
|
|
break;
|
|
|
|
case "#general":
|
|
|
|
ChatChannelHandler.handleRequest(ChatChannel.GENERAL, event, args);
|
|
|
|
break;
|
|
|
|
case "#politics":
|
|
|
|
ChatChannelHandler.handleRequest(ChatChannel.POLITICAL, event, args);
|
|
|
|
break;
|
|
|
|
case "#announce":
|
|
|
|
ChatChannelHandler.handleRequest(ChatChannel.ANNOUNCE, event, args);
|
|
|
|
break;
|
|
|
|
case "#bug":
|
|
|
|
ChatChannelHandler.handleRequest(ChatChannel.FORTOFIX, event, args);
|
|
|
|
break;
|
|
|
|
case "#recruit":
|
|
|
|
ChatChannelHandler.handleRequest(ChatChannel.RECRUIT, event, args);
|
|
|
|
break;
|
|
|
|
case "#magicbox":
|
|
|
|
ChatChannelHandler.handleRequest(ChatChannel.MAGICBOX, event, args);
|
|
|
|
break;
|
|
|
|
case "#lookup":
|
|
|
|
LookupRequestHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#rules":
|
|
|
|
RulesRequestHandler.handleRequest(event);
|
|
|
|
break;
|
|
|
|
case "#status":
|
|
|
|
StatusRequestHandler.handleRequest(event);
|
|
|
|
break;
|
|
|
|
case "#setavail":
|
|
|
|
SetAvailHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#ban":
|
|
|
|
BanToggleHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#server":
|
|
|
|
ServerRequestHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#dev":
|
|
|
|
DevRequestHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#logs":
|
|
|
|
LogsRequestHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#flash":
|
|
|
|
FlashHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
case "#trash":
|
|
|
|
TrashRequestHandler.handleRequest(event, args);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
junkbot(command, args);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void handleHelpRequest(MessageReceivedEvent event) {
|
|
|
|
|
|
|
|
// Help is kept here in the main class instead of a handler as a
|
|
|
|
// design decision for ease of maintenance.
|
|
|
|
|
|
|
|
String helpString = "I wish for to do the following things for you, not to you!\n\n" +
|
|
|
|
"#register <name> Register account for to play Magicbane.\n" +
|
|
|
|
"#password <newpass> Change your current game password.\n" +
|
|
|
|
"#account List your account detailings.\n" +
|
|
|
|
"#rules List of MagicBane server rules.\n" +
|
|
|
|
"#status Display MagicBane server status.\n" +
|
|
|
|
"#help List of MagicBot featurings.\n\n" +
|
|
|
|
"http://magicbane.com/tinyinstaller.zip";
|
|
|
|
|
|
|
|
if (isAdminEvent(event))
|
|
|
|
helpString += "\n" +
|
|
|
|
"#lookup <name> Return accounts starting with string.\n" +
|
|
|
|
"#bug -r <text> Post to the bug channel/\n" +
|
|
|
|
"#announce -r <text> Post to the announcement channel/\n" +
|
|
|
|
"#changelog <text> Post to the Changelog channel/\n" +
|
|
|
|
"#general -r <text> Post to the general channel/\n" +
|
|
|
|
"#politics -r <text> Post to the politics channel/\n" +
|
|
|
|
"#recruit -r <text> Post to the politics channel/\n" +
|
|
|
|
"#ban ###### Toggle active status of account.\n" +
|
|
|
|
"#setavail true/false Toggle status of database access.\n" +
|
|
|
|
"#server reboot/shutdown are your options.\n" +
|
|
|
|
"#logs magicbot/world/login n (tail)\n" +
|
|
|
|
"#flash <text> Send flash message\n" +
|
|
|
|
"#dev help (list dev subcommands)\n" +
|
|
|
|
"#trash <blank>/detail/flush";
|
|
|
|
sendResponse(event, helpString);
|
|
|
|
}
|
|
|
|
}
|