From dc90da36dff69c0cac7df045f506cfc7b3b4ed74 Mon Sep 17 00:00:00 2001 From: tommy Date: Wed, 10 Jul 2024 15:53:13 -0400 Subject: [PATCH] Make new commands and examples --- src/MessageCommands/_Example.lua | 15 +++ src/SlashCommands/Minecraft.lua | 139 ++++++++++++++++++++ src/SlashCommands/User.lua | 175 +++++++++++++++++++++++++ src/SlashCommands/_Example.lua | 17 +++ src/UserCommands/GetProfilePicture.lua | 15 +++ 5 files changed, 361 insertions(+) create mode 100644 src/MessageCommands/_Example.lua create mode 100644 src/SlashCommands/Minecraft.lua create mode 100644 src/SlashCommands/User.lua create mode 100644 src/SlashCommands/_Example.lua create mode 100644 src/UserCommands/GetProfilePicture.lua diff --git a/src/MessageCommands/_Example.lua b/src/MessageCommands/_Example.lua new file mode 100644 index 0000000..0c51af1 --- /dev/null +++ b/src/MessageCommands/_Example.lua @@ -0,0 +1,15 @@ +local SlashCommandTools = require('discordia-slash').util.tools() + +local GetProfilePictureCommand = SlashCommandTools.messageCommand('Copy message', 'Says the same exact message (text only)') + +local function Callback(Interaction, Command, Message) + local MessageContent = Message.content + if MessageContent then + return Interaction:reply(MessageContent) + end +end + +return { + Command = GetProfilePictureCommand, + Callback = Callback +} \ No newline at end of file diff --git a/src/SlashCommands/Minecraft.lua b/src/SlashCommands/Minecraft.lua new file mode 100644 index 0000000..08910b8 --- /dev/null +++ b/src/SlashCommands/Minecraft.lua @@ -0,0 +1,139 @@ +local Discordia = require('discordia') +local json = require('json') +local http_request = require('../Modules/http.lua') +Discordia.extensions() + +local ApplicationCommandOptionTypes = Discordia.enums.appCommandOptionType + +local SlashCommandTools = require('discordia-slash').util.tools() + +local MinecraftMainCommand = SlashCommandTools.slashCommand('minecraft', 'Minecraft server related commands') + +local MinecraftStatusSubCommand = SlashCommandTools.subCommand('status', 'Get the Minecraft server status according to the preferred IP address set for this server') +local MinecraftSetIpSubCommand = SlashCommandTools.subCommand('setip', 'Set the preferred Minecraft server IP address for this server') + +local MinecraftSetIpOptions = SlashCommandTools.string('ip', 'The IP address of the server') +MinecraftSetIpOptions:setRequired(true) +MinecraftSetIpSubCommand:addOption(MinecraftSetIpOptions) + +MinecraftMainCommand:addOption(MinecraftSetIpSubCommand) +MinecraftMainCommand:addOption(MinecraftStatusSubCommand) + +local COLOURS = { + GREEN = 0x00ff00, + RED = 0xff0000 +} + +--initialize minecraft ip data +local MinecraftDataFile = io.open('minecraft_data.json', 'r') +if not MinecraftDataFile or (MinecraftDataFile and MinecraftDataFile:read('*a') == '') then + print('no such file exists! so make it') + io.open('minecraft_data.json', 'w+'):write(json.encode({})):close() +end +if MinecraftDataFile then + MinecraftDataFile:close() +end + +local SubCommandCallbacks = {} +local function Status(Interaction, Command, Args) + local GuildId = Interaction.guild and Interaction.guild.id + if not GuildId then + return Interaction:reply('You cannot use this command outside of a Discord server', true) + end + + local GlobalMinecraftData = json.decode(io.open('minecraft_data.json', 'r'):read('*a')) + if not GlobalMinecraftData then + return Interaction:reply('Could not read server data', true) + end + + local ServerMinecraftData = GlobalMinecraftData[GuildId] + if not ServerMinecraftData then + return Interaction:reply('There is no data for this Discord server', true) + end + + local ServerIPStr = ServerMinecraftData.IP..':'..ServerMinecraftData.PORT + local Response, Headers = http_request('GET', ('https://api.mcsrvstat.us/3/%s'):format(ServerIPStr)) + + local IsOnline = Response.online + local EmbedData + if IsOnline then + local MaxPlayers = Response.players.max + local OnlinePlayers = Response.players.online + local AnonymousPlayers = OnlinePlayers + local Players = {} + if OnlinePlayers>0 then + for PlayerIndex, PlayerData in next, Response.players.list do + table.insert(Players, PlayerData.name) + AnonymousPlayers = AnonymousPlayers-1 + end + else + table.insert(Players, 'No players online') + end + if AnonymousPlayers>0 then + for AnonymousPlayerIndex = 1, AnonymousPlayers do + table.insert(Players, 'Anonymous Player') + end + end + EmbedData = { + title = 'Server Status for '..ServerIPStr, + description = Response.motd.clean[1]..' ('..Response.version..')', + fields = { + {name = 'Players', value = OnlinePlayers..'/'..MaxPlayers, inline = true}, + {name = 'List of players', value = table.concat(Players, '\n'), inline = true} + }, + color = COLOURS.GREEN + } + else + EmbedData = { + title = 'Server Status for '..ServerIPStr, + description = 'Server is offline', + color = COLOURS.RED + } + end + return Interaction:reply({embed = EmbedData}) +end + +local function SetIp(Interaction, Command, Args) + local ServerIPStr = Args.ip + + local GuildId = Interaction.guild and Interaction.guild.id + if not GuildId then + return Interaction:reply('You cannot use this command outside of a Discord server') + end + + local ServerIP = ServerIPStr:match("(%d+%.%d+%.%d+%.%d+)") or ServerIPStr:match("(%w*%.?%w+%.%w+)") + if not ServerIP then + return Interaction:reply('Invalid server IP') + end + local ServerPort = ServerIPStr:match(ServerIP..':(%d+)') or 25565 + + local GuildMinecraftData = {IP = ServerIP, PORT = ServerPort} + + local GlobalMinecraftData = json.decode(io.open('minecraft_data.json','r'):read('*a')) + GlobalMinecraftData[GuildId] = GuildMinecraftData + io.open('minecraft_data.json','w+'):write(json.encode(GlobalMinecraftData)):close() + + return Interaction:reply('Successfully added `'..ServerIP..':'..ServerPort..'` for ServerId='..GuildId) +end +SubCommandCallbacks.status = Status +SubCommandCallbacks.setip = SetIp + + + +local function Callback(Interaction, Command, Args) + local SubCommandOption = Command.options[1] + if SubCommandOption.type == ApplicationCommandOptionTypes.subCommand then + local SubCommandName = SubCommandOption.name + local SubCommandCallback = SubCommandCallbacks[SubCommandName] + local SubCommandArgs = Args[SubCommandName] + if SubCommandCallback then + SubCommandCallback(Interaction, Command, SubCommandArgs) + end + end + +end + +return { + Command = MinecraftMainCommand, + Callback = Callback +} \ No newline at end of file diff --git a/src/SlashCommands/User.lua b/src/SlashCommands/User.lua new file mode 100644 index 0000000..7f4081b --- /dev/null +++ b/src/SlashCommands/User.lua @@ -0,0 +1,175 @@ +local SlashCommandTools = require('discordia-slash').util.tools() + +local Discordia = require('discordia') +local Date = Discordia.Date +Discordia.extensions() + +local API = require('../Modules/strafes_net.lua') + +local UserCommand = SlashCommandTools.slashCommand('user', 'Looks up specified user on Roblox') + +local UsernameOption = SlashCommandTools.string('username', 'Username to look up') +local UserIdOption = SlashCommandTools.integer('user_id', 'User ID to look up') +local MemberOption = SlashCommandTools.user('member', 'User to look up') + +UserCommand:addOption(UsernameOption) +UserCommand:addOption(UserIdOption) +UserCommand:addOption(MemberOption) + +Badges = { + '275640532', --Bhop, pre-group + '363928432', --Surf, pre-group + '2124614454', --Bhop, post-group + '2124615096', --Surf, post-group +} +BadgesToName = { + [275640532]='old bhop', + [363928432]='old surf', + [2124614454]='new bhop', + [2124615096]='new surf', +} +local function round(x,n) + return string.format('%.'..(n or 0)..'f',x) +end + +local function FromYMD(ymd) + return Date.fromISO(ymd.."T00:00:00")[1] +end +local function leftpad(s,n,p) + return string.rep(p,n-#tostring(s))..s +end +local function ToYMD(seconds) + return "" +end +local IDToDate = { --Terrible ranges but it's all we have + -- {1000000000, FromYMD("2006-01-01")}, --I guess? + -- {1864564055, FromYMD("2008-08-04")}, + {3800920136, FromYMD("2016-04-16")}, + {9855616205, FromYMD("2017-04-02")}, + {30361018662, FromYMD("2018-11-14")}, + {32665806459, FromYMD("2019-01-07")}, + {34758058773, FromYMD("2019-02-24")}, + {65918261258, FromYMD("2020-06-05")}, + {171994717435, FromYMD("2023-03-24")}, + {173210319088, FromYMD("2023-04-14")}, + {206368884641, FromYMD("2023-07-16")}, + {229093879745, FromYMD("2024-01-02")}, + {232802028144, FromYMD("2024-04-08")}, + {234886704167, FromYMD("2024-06-28")} +} +--We assume linear interpolation since anything more complex I can't process +local function linterp(i1, i2, m) + return math.floor(i1 + (i2-i1)*m) +end +local function GuessDateFromAssetID(AssetID) + for i = #IDToDate, 1, -1 do --Newest to oldest + local ID,Time = unpack(IDToDate[i]) + if ID < AssetID then + if not IDToDate[i+1] then + return "After "..ToYMD(Time) + end + local ParentID, ParentTime = unpack(IDToDate[i+1]) + return "Around "..ToYMD(linterp(Time, ParentTime, (AssetID-ID)/(ParentID-ID))) + end + end + return "Before "..ToYMD(IDToDate[1][2]) +end + +local function Callback(Interaction, Command, Args) + local user_info + if Args then + local username = Args.username + local user_id = Args.user_id + local member = Args.member + if username then + user_info = API:GetRobloxInfoFromUsername(username) + elseif user_id then + user_info = API:GetRobloxInfoFromUserId(user_id) + elseif member then + user_info = API:GetRobloxInfoFromDiscordId(member.id) + end + else + local user = Interaction.member or Interaction.user + if user then + user_info = API:GetRobloxInfoFromDiscordId(user.id) + end + end + + local description = user_info.description=='' and 'This user has no description' or user_info.description + -- table.foreach(user_info,print) + local created = tostring(Date.fromISO(user_info.created):toSeconds()) + local current = Date():toSeconds() + local accountAge = round((current-created)/86400) + local isBanned = user_info.isBanned + local id = user_info.id + local name = user_info.name + local displayName = user_info.displayName + + local usernameHistory = API:GetUserUsernameHistory(id).data or {} + local usernameHistoryTable = {} + for index,usernameObj in next,usernameHistory do + table.insert(usernameHistoryTable,usernameObj.name) + end + local usernameHistoryString = table.concat(usernameHistoryTable,', ') + + local onlineStatus_info = API:GetUserOnlineStatus(id) or {lastLocation="Unknown", lastOnline=0, userPresenceType=-1} + -- table.foreach(onlineStatus_info,print) + + local LastLocation = onlineStatus_info.lastLocation + if onlineStatus_info.userPresenceType==2 then LastLocation="Ingame" end + local LastOnline = Date.fromISO(onlineStatus_info.lastOnline):toSeconds() + + local verificationAssetId = API:GetVerificationItemID(id) + local verificationDate = "Not verified" + if verificationAssetId.errors then + verificationDate = "Failed to fetch" + elseif verificationAssetId.data[1] then + verificationDate = GuessDateFromAssetID(verificationAssetId.data[1].instanceId) + end + + local badgeRequest = API:GetBadgesAwardedDates(id,Badges) + local badgeData = badgeRequest.data + + -- local badgesDates = {} + + local firstBadge,firstBadgeDate = 0,math.huge + for _,badge in next,badgeData do + local badgeId = badge.badgeId + local awardedDate = tonumber(Date.fromISO(badge.awardedDate):toSeconds()) + if firstBadgeDate>awardedDate then + firstBadge=badgeId + firstBadgeDate=awardedDate + end + -- badgesDates[badgeId]=awardedDate + end + local userThumbnail = API:GetUserThumbnail(id).data[1] + + local embed = { + title = displayName..' (@'..name..')', + url = 'https://roblox.com/users/'..id..'/profile', + thumbnail = { + url = userThumbnail.imageUrl, + }, + fields = { + {name='ID',value=id,inline=true}, + {name='Account Age',value=accountAge..' days',inline=true}, + {name='Created',value='',inline=true}, + {name='Verified Email',value=verificationDate,inline=true}, + {name='Last Online',value='',inline=true}, + {name='Last Location',value=LastLocation,inline=true}, + {name='Banned',value=isBanned,inline=true}, + {name='Description',value=description,inline=false}, + {name='Username History ('..#usernameHistoryTable..(#usernameHistoryTable==50 and '*' or '')..')',value=usernameHistoryString,inline=false}, + } + } + if firstBadge and firstBadgeDate~=math.huge then + table.insert(embed.fields,{name='FQG',value=BadgesToName[firstBadge],inline=true}) + table.insert(embed.fields,{name='Joined',value='',inline=true}) + end + Interaction:reply({embed=embed}) +end + +return { + Command = UserCommand, + Callback = Callback +} \ No newline at end of file diff --git a/src/SlashCommands/_Example.lua b/src/SlashCommands/_Example.lua new file mode 100644 index 0000000..0732a66 --- /dev/null +++ b/src/SlashCommands/_Example.lua @@ -0,0 +1,17 @@ +local SlashCommandTools = require('discordia-slash').util.tools() + +local PongCommand = SlashCommandTools.slashCommand('ping', 'Replies with pong') + +local MessageOption = SlashCommandTools.string('message', 'What the bot will append to the message') + +PongCommand:addOption(MessageOption) + +local function Callback(Interaction, Command, Args) + local Message = Args.message + return Interaction:reply('Pong! '..Message) +end + +return { + Command = PongCommand, + Callback = Callback +} \ No newline at end of file diff --git a/src/UserCommands/GetProfilePicture.lua b/src/UserCommands/GetProfilePicture.lua new file mode 100644 index 0000000..0898ed1 --- /dev/null +++ b/src/UserCommands/GetProfilePicture.lua @@ -0,0 +1,15 @@ +local SlashCommandTools = require('discordia-slash').util.tools() + +local GetProfilePictureCommand = SlashCommandTools.userCommand('Get profile picture', 'Gets user avatar') + +local function Callback(Interaction, Command, Member) + local AvatarURL = Member:getAvatarURL(1024) + if AvatarURL then + return Interaction:reply(AvatarURL, true) + end +end + +return { + Command = GetProfilePictureCommand, + Callback = Callback +} \ No newline at end of file