2024-10-01 23:31:27 -04:00
import { Client , GatewayIntentBits , SlashCommandBuilder , REST , Routes , EmbedBuilder } from 'discord.js' ;
import jsonfile from 'jsonfile' ;
import MyMCLib from 'mymc-lib' ;
import unirest from 'unirest' ;
import { readFileSync } from 'fs' ;
// Paths to config and tokens files
const tokensFile = './tokens.json' ;
const config = JSON . parse ( readFileSync ( './config.json' , 'utf8' ) ) ;
// Initialize Discord client
const client = new Client ( { intents : [ GatewayIntentBits . Guilds ] } ) ;
// Load tokens from the JSON file
function loadTokens ( ) {
try {
return jsonfile . readFileSync ( tokensFile ) ;
} catch ( error ) {
console . error ( 'Error reading tokens file:' , error ) ;
return { } ;
}
}
// Save tokens to the JSON file
function saveTokens ( tokens ) {
jsonfile . writeFileSync ( tokensFile , tokens , { spaces : 2 } ) ;
}
// Automatically request a new token if it doesn't exist or is invalid
async function fetchAndSaveToken ( userId , interaction ) {
return unirest
. post ( config . endpoint )
. headers ( { 'Accept' : 'application/json' , 'Content-Type' : 'application/json' } )
. send ( { "username" : ` mc_ ${ userId } ` , "password" : config . password } )
. then ( ( tokenInfo ) => {
const tokens = loadTokens ( ) ;
tokens [ userId ] = tokenInfo . body . token ; // Save the new token
saveTokens ( tokens ) ;
return tokenInfo . body . token ;
} )
. catch ( ( error ) => {
console . error ( 'Error fetching token:' , error ) ;
sendSexyEmbed ( "Error" , "An error occurred while fetching your API token." , interaction ) ;
throw error ;
} ) ;
}
// Fetch or retrieve token, if the token is invalid, fetch a new one
async function getToken ( userId , interaction ) {
const tokens = loadTokens ( ) ;
if ( ! tokens [ userId ] ) {
return await fetchAndSaveToken ( userId , interaction ) ;
}
return tokens [ userId ] ;
}
// Handle invalid tokens and re-fetch them
async function handleApiCall ( apiCall , userId , interaction ) {
try {
return await apiCall ( ) ;
} catch ( error ) {
console . error ( 'Token error, re-fetching token...' ) ;
await fetchAndSaveToken ( userId , interaction ) ;
return await apiCall ( ) ;
}
}
// Handle multiple fields or single-field responses
function handleResponse ( response , interaction , ephemeral = false ) {
if ( response . success && typeof response === 'object' && Object . keys ( response ) . length > 1 ) {
// Extract the message if it exists, otherwise use a default description
2024-10-01 23:38:26 -04:00
const description = response . message ? response . message : 'Success!' ;
2024-10-01 23:31:27 -04:00
// If there is a 'stats' field, handle it separately to format it correctly
if ( response . stats ) {
const statsFields = [ ] ;
// Handle each field in the stats object
Object . entries ( response . stats ) . forEach ( ( [ key , value ] ) => {
if ( key === 'memory' ) {
// Format memory separately to display raw and percent nicely
statsFields . push ( {
name : 'Memory Usage' ,
value : ` Raw: ${ value . raw } \n Percent: ${ value . percent } ` ,
inline : false
} ) ;
} else {
statsFields . push ( {
name : key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) , // Capitalize the key name
value : String ( value ) ,
inline : false
} ) ;
}
} ) ;
sendSexyEmbedWithFields ( 'My-MC Link - Server Stats' , description , statsFields , interaction , ephemeral ) ;
}
// If there is a 'mods' field, handle it separately to format it correctly
else if ( Array . isArray ( response . mods ) ) {
const modFields = response . mods . map ( ( mod , index ) => ( {
name : ` Mod ${ index + 1 } : ${ mod . name } ` ,
value : ` Version: ${ mod . version } \n Source: ${ mod . source } \n Essential: ${ mod . essential ? 'Yes' : 'No' } ` ,
inline : false
} ) ) ;
sendSexyEmbedWithFields ( 'My-MC Link - Installed Mods' , description , modFields , interaction , ephemeral ) ;
}
// Handle all other fields
else {
const fields = Object . entries ( response )
. filter ( ( [ key ] ) => key !== 'success' && key !== 'action' && key !== 'message' && key !== 'stats' && key !== 'mods' ) // Ignore specific fields
. map ( ( [ key , value ] ) => ( { name : key , value : String ( value ) } ) ) ;
sendSexyEmbedWithFields ( 'My-MC Link' , description , fields , interaction , ephemeral ) ;
}
} else {
// Single message or field
sendSexyEmbed ( 'Response' , JSON . stringify ( response , null , 2 ) , interaction , ephemeral ) ;
}
}
// Send sexy embed
function sendSexyEmbed ( title , description , interaction , ephemeral = false ) {
const embed = new EmbedBuilder ( )
. setColor ( "#3498DB" )
. setTitle ( title )
. setDescription ( description )
. setTimestamp ( )
. setFooter ( {
text : ` Requested by ${ interaction . user . username } ` ,
iconURL : ` ${ interaction . user . displayAvatarURL ( ) } `
} ) ;
interaction . reply ( {
embeds : [ embed ] ,
ephemeral : ephemeral // Set ephemeral flag based on the condition
} ) ;
}
// Send sexy embed with fields
function sendSexyEmbedWithFields ( title , description , fields , interaction , ephemeral = false ) {
const embed = new EmbedBuilder ( )
. setColor ( "#3498DB" )
. setTitle ( title )
. setDescription ( description !== "N/A" ? description : undefined )
. addFields ( fields )
. setTimestamp ( )
. setFooter ( {
text : ` Requested by ${ interaction . user . username } ` ,
iconURL : ` ${ interaction . user . displayAvatarURL ( ) } `
} ) ;
interaction . reply ( {
embeds : [ embed ] ,
ephemeral : ephemeral // Set ephemeral flag based on the condition
} ) ;
}
// Slash command definitions
const commands = [
2024-10-01 23:38:26 -04:00
// new SlashCommandBuilder().setName('api-key-time').setDescription('Get the server time'),
2024-10-01 23:31:27 -04:00
new SlashCommandBuilder ( ) . setName ( 'server-stats' ) . setDescription ( 'Get the server statistics' ) ,
new SlashCommandBuilder ( ) . setName ( 'server-log' ) . setDescription ( 'Get the server log' ) ,
new SlashCommandBuilder ( ) . setName ( 'start-server' ) . setDescription ( 'Start the Minecraft server' ) ,
new SlashCommandBuilder ( ) . setName ( 'stop-server' ) . setDescription ( 'Stop the Minecraft server' ) ,
new SlashCommandBuilder ( ) . setName ( 'restart-server' ) . setDescription ( 'Restart the Minecraft server' ) ,
new SlashCommandBuilder ( ) . setName ( 'create-link' ) . setDescription ( 'Create a custom server link' ) ,
new SlashCommandBuilder ( ) . setName ( 'create-sftp-link' ) . setDescription ( 'Create an SFTP link' ) ,
new SlashCommandBuilder ( ) . setName ( 'get-connection-hash' ) . setDescription ( 'Get the connection hash' ) ,
new SlashCommandBuilder ( ) . setName ( 'get-connection-hash-sftp' ) . setDescription ( 'Get the SFTP connection hash' ) ,
new SlashCommandBuilder ( ) . setName ( 'get-players' ) . setDescription ( 'Get a list of online players' ) ,
new SlashCommandBuilder ( ) . setName ( 'get-website-url' ) . setDescription ( 'Get the server website URL' ) ,
new SlashCommandBuilder ( ) . setName ( 'get-map-url' ) . setDescription ( 'Get the server map URL' ) ,
new SlashCommandBuilder ( ) . setName ( 'ban-player' ) . setDescription ( 'Ban a player by username' ) . addStringOption ( option => option . setName ( 'username' ) . setDescription ( 'Player username' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'unban-player' ) . setDescription ( 'Unban a player by username' ) . addStringOption ( option => option . setName ( 'username' ) . setDescription ( 'Player username' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'send-message' ) . setDescription ( 'Send a message to all players' ) . addStringOption ( option => option . setName ( 'message' ) . setDescription ( 'Message to send' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'send-private-message' ) . setDescription ( 'Send a private message to a player' ) . addStringOption ( option => option . setName ( 'username' ) . setDescription ( 'Player username' ) . setRequired ( true ) ) . addStringOption ( option => option . setName ( 'message' ) . setDescription ( 'Message to send' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'execute-console-command' ) . setDescription ( 'Execute a command in the server console' ) . addStringOption ( option => option . setName ( 'command' ) . setDescription ( 'Command to execute' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'give-item' ) . setDescription ( 'Give an item to a player' ) . addStringOption ( option => option . setName ( 'username' ) . setDescription ( 'Player username' ) . setRequired ( true ) ) . addStringOption ( option => option . setName ( 'item' ) . setDescription ( 'Item to give' ) . setRequired ( true ) ) . addIntegerOption ( option => option . setName ( 'amount' ) . setDescription ( 'Amount to give' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'install-mod' ) . setDescription ( 'Install a mod by ID' ) . addStringOption ( option => option . setName ( 'modid' ) . setDescription ( 'Mod ID' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'uninstall-mod' ) . setDescription ( 'Uninstall a mod by ID' ) . addStringOption ( option => option . setName ( 'modid' ) . setDescription ( 'Mod ID' ) . setRequired ( true ) ) ,
new SlashCommandBuilder ( ) . setName ( 'get-installed-mods' ) . setDescription ( 'Get a list of installed mods' ) ,
] ;
// Prepare extra data for user commands
const JSONCommands = commands . map ( command => command . toJSON ( ) ) ;
const extras = {
"integration_types" : [ 0 , 1 ] , // 0 for guild, 1 for user
"contexts" : [ 0 , 1 , 2 ] , // 0 for guild, 1 for app DMs, 2 for GDMs and other DMs
} ;
JSONCommands . forEach ( ( command ) => {
Object . keys ( extras ) . forEach ( key => command [ key ] = extras [ key ] ) ;
} ) ;
// Register commands with Discord
const rest = new REST ( { version : '10' } ) . setToken ( config . token ) ;
( async ( ) => {
try {
console . log ( 'Started refreshing application (/) commands.' ) ;
await rest . put (
Routes . applicationCommands ( config . clientId ) ,
{ body : JSONCommands } // Add all commands as an array
) ;
console . log ( 'Successfully reloaded application (/) commands.' ) ;
} catch ( error ) {
console . error ( error ) ;
}
} ) ( ) ;
// Handle bot interactions
client . on ( 'interactionCreate' , async interaction => {
if ( ! interaction . isCommand ( ) ) return ;
const userId = interaction . user . id ;
// Fetch or get existing token
const apiToken = await getToken ( userId , interaction ) ;
const MyMC = new MyMCLib ( apiToken ) ;
switch ( interaction . commandName ) {
2024-10-01 23:38:26 -04:00
// case 'server-time':
// const timeData = await handleApiCall(() => MyMC.getTime(), userId, interaction);
// handleResponse(timeData, interaction);
// break;
2024-10-01 23:31:27 -04:00
case 'server-stats' :
const stats = await handleApiCall ( ( ) => MyMC . getStats ( ) , userId , interaction ) ;
handleResponse ( stats , interaction ) ;
break ;
case 'server-log' :
const log = await handleApiCall ( ( ) => MyMC . getLog ( ) , userId , interaction ) ;
handleResponse ( log , interaction ) ;
break ;
case 'start-server' :
const startResult = await handleApiCall ( ( ) => MyMC . startServer ( ) , userId , interaction ) ;
handleResponse ( startResult , interaction ) ;
break ;
case 'stop-server' :
const stopResult = await handleApiCall ( ( ) => MyMC . stopServer ( ) , userId , interaction ) ;
handleResponse ( stopResult , interaction ) ;
break ;
case 'restart-server' :
const restartResult = await handleApiCall ( ( ) => MyMC . restartServer ( ) , userId , interaction ) ;
handleResponse ( restartResult , interaction ) ;
break ;
case 'create-link' :
const customLinkResult = await handleApiCall ( ( ) => MyMC . createMyLink ( ) , userId , interaction ) ;
handleResponse ( customLinkResult , interaction ) ;
break ;
case 'create-sftp-link' :
const sftpLinkResult = await handleApiCall ( ( ) => MyMC . createLinkSFTP ( ) , userId , interaction ) ;
handleResponse ( sftpLinkResult , interaction , true ) ;
break ;
case 'get-connection-hash' :
const hash = await handleApiCall ( ( ) => MyMC . getConnectionHash ( ) , userId , interaction ) ;
handleResponse ( hash , interaction , true ) ;
break ;
case 'get-connection-hash-sftp' :
const sftpHash = await handleApiCall ( ( ) => MyMC . getConnectionHashSFTP ( ) , userId , interaction ) ;
handleResponse ( sftpHash , interaction , true ) ;
break ;
case 'get-players' :
const players = await handleApiCall ( ( ) => MyMC . getOnlinePlayers ( ) , userId , interaction ) ;
handleResponse ( players , interaction ) ;
break ;
case 'get-website-url' :
const websiteURL = await handleApiCall ( ( ) => MyMC . getWebsiteURL ( ) , userId , interaction ) ;
handleResponse ( websiteURL , interaction ) ;
break ;
case 'get-map-url' :
const mapURL = await handleApiCall ( ( ) => MyMC . getMapURL ( ) , userId , interaction ) ;
handleResponse ( mapURL , interaction ) ;
break ;
case 'ban-player' :
const banUsername = interaction . options . getString ( 'username' ) ;
const banResult = await handleApiCall ( ( ) => MyMC . postBan ( banUsername ) , userId , interaction ) ;
handleResponse ( banResult , interaction , true ) ;
break ;
case 'unban-player' :
const unbanUsername = interaction . options . getString ( 'username' ) ;
const unbanResult = await handleApiCall ( ( ) => MyMC . postUnban ( unbanUsername ) , userId , interaction ) ;
handleResponse ( unbanResult , interaction , true ) ;
break ;
case 'send-message' :
const message = interaction . options . getString ( 'message' ) ;
const sayResult = await handleApiCall ( ( ) => MyMC . postSay ( message ) , userId , interaction ) ;
handleResponse ( sayResult , interaction , true ) ;
break ;
case 'send-private-message' :
const tellUsername = interaction . options . getString ( 'username' ) ;
const privateMessage = interaction . options . getString ( 'message' ) ;
const tellResult = await handleApiCall ( ( ) => MyMC . postTell ( tellUsername , privateMessage ) , userId , interaction ) ;
handleResponse ( tellResult , interaction , true ) ;
break ;
case 'execute-console-command' :
const command = interaction . options . getString ( 'command' ) ;
const consoleResult = await handleApiCall ( ( ) => MyMC . postConsole ( command ) , userId , interaction ) ;
handleResponse ( consoleResult , interaction , true ) ;
break ;
case 'give-item' :
const giveUsername = interaction . options . getString ( 'username' ) ;
const item = interaction . options . getString ( 'item' ) ;
const amount = interaction . options . getInteger ( 'amount' ) ;
const giveResult = await handleApiCall ( ( ) => MyMC . postGive ( giveUsername , item , amount ) , userId , interaction ) ;
handleResponse ( giveResult , interaction ) ;
break ;
case 'install-mod' :
const modId = interaction . options . getString ( 'modid' ) ;
const installResult = await handleApiCall ( ( ) => MyMC . installMod ( modId ) , userId , interaction ) ;
handleResponse ( installResult , interaction ) ;
break ;
case 'uninstall-mod' :
const uninstallModId = interaction . options . getString ( 'modid' ) ;
const uninstallResult = await handleApiCall ( ( ) => MyMC . uninstallMod ( uninstallModId ) , userId , interaction ) ;
handleResponse ( uninstallResult , interaction ) ;
break ;
case 'get-installed-mods' :
const installedMods = await handleApiCall ( ( ) => MyMC . getInstalledMods ( ) , userId , interaction ) ;
handleResponse ( installedMods , interaction ) ;
break ;
default :
await interaction . reply ( 'Command not recognized.' ) ;
break ;
}
} ) ;
// Log in to Discord
client . login ( config . token ) ;