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
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); |
|
} |
|
}
|
|
|