Public Repository for the Magicbane Shadowbane Emulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

407 lines
15 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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);
}
}