From e2ee24898f0b3c8200d71e647377a2e7fdd280d4 Mon Sep 17 00:00:00 2001 From: tommy Date: Mon, 23 Jun 2025 23:57:34 -0400 Subject: [PATCH] Make user slash command work --- src/Modules/HttpRequest.lua | 27 +++-- src/Modules/StrafesNET.lua | 166 ++++++++++++++++++++++++++++-- src/SlashCommands/User.lua | 199 +++++++++++++++++------------------- 3 files changed, 263 insertions(+), 129 deletions(-) diff --git a/src/Modules/HttpRequest.lua b/src/Modules/HttpRequest.lua index eedfe00..8d3f4fb 100644 --- a/src/Modules/HttpRequest.lua +++ b/src/Modules/HttpRequest.lua @@ -14,7 +14,7 @@ local function QueryParams(Params) -- {Name = Value, ...} for ParamName, ParamValue in next, Params do if ParamValue ~= nil then - QueryString = QueryString .. ParamName .. "=" .. ParamValue .. "&" + QueryString = QueryString .. tostring(ParamName) .. "=" .. tostring(ParamValue) .. "&" end end @@ -22,10 +22,11 @@ local function QueryParams(Params) -- {Name = Value, ...} end local function CreateHeaders(Headers) -- {Name = Value, ...} + if not Headers then return {} end local RequestHeaders = {} for HeaderName, HeaderValue in next, Headers do - RequestHeaders[#RequestHeaders + 1] = { HeaderName, HeaderValue } + RequestHeaders[#RequestHeaders + 1] = { tostring(HeaderName), tostring(HeaderValue) } end return RequestHeaders @@ -49,7 +50,7 @@ local function NormalizeHeaders(Response) end end -local function Request(Method, Url, Params, Headers, Callback) +local function Request(Method, Url, Params, RequestHeaders, RequestBody, Callback) if not METHODS[Method] then error("[HTTP] Method " .. Method .. " is not supported.") end @@ -58,23 +59,27 @@ local function Request(Method, Url, Params, Headers, Callback) error("[HTTP] Url is not a string") end - local QueryString = QueryParams(Params) -- at worse (I think), this is an empty string (which cannot mess up the request) + if type(RequestBody) == "table" then + RequestBody = json.encode(RequestBody) + end - local RequestHeaders = CreateHeaders(Headers) -- At worse, this will just be an empty table (which cannot mess up the request) + local QueryString = QueryParams(Params) -- at worse (I think), this is an empty string (which cannot mess up the request) + + local FormattedHeaders = CreateHeaders(RequestHeaders) -- At worse, this will just be an empty table (which cannot mess up the request) local RequestUrl = Url .. QueryString print(RequestUrl) if Callback and type(Callback) == "function" then return coroutine.wrap(function() - local Response, Body = HTTPRequest(Method, RequestUrl, RequestHeaders) - NormalizeHeaders(Response) - Callback(Response, TryDecodeJson(Body)) + local Headers, Body = HTTPRequest(Method, RequestUrl, FormattedHeaders, RequestBody) + NormalizeHeaders(Headers) + Callback(Headers, TryDecodeJson(Body)) end) else - local Response, Body = HTTPRequest(Method, RequestUrl, RequestHeaders) - NormalizeHeaders(Response) - return Response, TryDecodeJson(Body) + local Headers, Body = HTTPRequest(Method, RequestUrl, FormattedHeaders, RequestBody) + NormalizeHeaders(Headers) + return Headers, TryDecodeJson(Body) end end diff --git a/src/Modules/StrafesNET.lua b/src/Modules/StrafesNET.lua index 1cdbb9c..3b785c8 100644 --- a/src/Modules/StrafesNET.lua +++ b/src/Modules/StrafesNET.lua @@ -7,9 +7,41 @@ local Headers = { ["X-API-Key"] = APIKeys.StrafesNET } -local API_URL = "https://api.strafes.net/api/v1/" +function L1Copy(t, b) + b = b or {} + for x, y in next, t do b[x] = y end + return b +end -local API_ENDPOINTS = { +local STRAFESNET_API_URL = "https://api.strafes.net/api/v1/" +local FIVEMAN_API_URL = 'https://api.fiveman1.net/v1/' +local ROBLOX_API_URL = 'https://users.roblox.com/v1/' +local ROBLOX_BADGES_API = 'https://badges.roblox.com/v1/' +local ROBLOX_PRESENCE_URL = 'https://presence.roblox.com/v1/' +local ROBLOX_THUMBNAIL_URL = 'https://thumbnails.roblox.com/v1/' +local ROBLOX_INVENTORY_API = 'https://inventory.roblox.com/v1/' +local ROBLOX_GROUPS_ROLES_URL = 'https://groups.roblox.com/v2/users/%s/groups/roles' + +ROBLOX_THUMBNAIL_SIZES = { + [48] = '48x48', + [50] = '50x50', + [60] = '60x60', + [75] = '75x75', + [100] = '100x100', + [110] = '110x110', + [150] = '150x150', + [180] = '180x180', + [352] = '352x352', + [420] = '420x420', + [720] = '720x720' +} +ROBLOX_THUMBNAIL_TYPES = { + AVATAR = 'avatar', + BUST = 'avatar-bust', + HEADSHOT = 'avatar-headshot' +} + +local STRAFESNET_API_ENDPOINTS = { MAPS = { LIST = "map", GET = "map/%d" @@ -33,19 +65,19 @@ local API_ENDPOINTS = { local StrafesNET = {} function StrafesNET.ListMaps(GameId, PageSize, PageNumber) - local RequestUrl = API_URL .. API_ENDPOINTS.MAPS.LIST + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.MAPS.LIST local Params = { game_id = GameId, page_size = PageSize or 10, page_number = PageNumber or 1 } return Request("GET", RequestUrl, Params, Headers) end function StrafesNET.GetMap(MapId) - local RequestUrl = API_URL .. API_ENDPOINTS.MAPS.GET:format(MapId) + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.MAPS.GET:format(MapId) local Params = { id = MapId } return Request("GET", RequestUrl, Params, Headers) end function StrafesNET.ListRanks(GameId, ModeId, StyleId, SortBy, PageSize, PageNumber) - local RequestUrl = API_URL .. API_ENDPOINTS.RANKS.LIST + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.RANKS.LIST local Params = { gameId = GameId, modeId = ModeId, @@ -58,7 +90,7 @@ function StrafesNET.ListRanks(GameId, ModeId, StyleId, SortBy, PageSize, PageNum end function StrafesNET.ListTimes(UserId, MapId, GameId, ModeId, StyleId, SortBy, PageSize, PageNumber) - local RequestUrl = API_URL .. API_ENDPOINTS.TIMES.LIST + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.TIMES.LIST local Params = { user_id = UserId, map_id = MapId, @@ -73,12 +105,12 @@ function StrafesNET.ListTimes(UserId, MapId, GameId, ModeId, StyleId, SortBy, Pa end function StrafesNET.GetTime(TimeId) - local RequestUrl = API_URL .. API_ENDPOINTS.TIMES.GET:format(TimeId) + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.TIMES.GET:format(TimeId) return Request("GET", RequestUrl, nil, Headers) end function StrafesNET.ListUsers(StateId, PageSize, PageNumber) - local RequestUrl = API_URL .. API_ENDPOINTS.USERS.LIST + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.USERS.LIST local Params = { state_id = StateId, page_size = PageSize or 10, @@ -88,12 +120,12 @@ function StrafesNET.ListUsers(StateId, PageSize, PageNumber) end function StrafesNET.GetUser(UserId) - local RequestUrl = API_URL .. API_ENDPOINTS.USERS.GET:format(UserId) + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.USERS.GET:format(UserId) return Request("GET", RequestUrl, nil, Headers) end function StrafesNET.GetUserRank(UserId, GameId, ModeId, StyleId) - local RequestUrl = API_URL .. API_ENDPOINTS.USERS.RANKS.GET:format(UserId) + local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.USERS.RANKS.GET:format(UserId) local Params = { game_id = GameId, mode_id = ModeId, @@ -102,4 +134,118 @@ function StrafesNET.GetUserRank(UserId, GameId, ModeId, StyleId) return Request("GET", RequestUrl, Params, Headers) end +function StrafesNET.GetRobloxInfoFromUserId(USER_ID) + if not USER_ID then return 'empty id' end + return Request("GET", ROBLOX_API_URL .. "users/" .. USER_ID) +end + +function StrafesNET.GetRobloxInfoFromUsername(USERNAME) + if not USERNAME then return 'empty username' end + if #USERNAME > 32 then return 'Username too long' end + + local headers, body = Request("POST", ROBLOX_API_URL .. "usernames/users", nil, + { ["Content-Type"] = "application/json" }, { usernames = { USERNAME } }) + if not body or not body.data or not body.data[1] then + return 'Username \'' .. USERNAME .. '\' not found.' + end + + return StrafesNET.GetRobloxInfoFromUserId(body.data[1].id) +end + +function StrafesNET.GetRobloxInfoFromDiscordId(DISCORD_ID) + if not DISCORD_ID then return 'empty id' end + -- table.foreach(DISCORD_ID, print) + local headers, body = Request("GET", FIVEMAN_API_URL .. "users/" .. DISCORD_ID) + if headers.status == "error" then return headers.messages end + + return Request("GET", ROBLOX_API_URL .. "users/" .. body.result.robloxId) +end + +function StrafesNET.GetUserFromAny(user, message) + local str = user:match('^["\'](.+)[\'"]$') + local num = user:match('^(%d+)$') + + if str then + local roblox_user = StrafesNET.GetRobloxInfoFromUsername(str) + if not roblox_user.id then return 'User not found' end + return roblox_user + elseif num then + local roblox_user = StrafesNET.GetRobloxInfoFromUserId(user) + if not roblox_user.id then return 'Invalid user id' end + return roblox_user + elseif user == 'me' then + local me = message.author + local roblox_user = StrafesNET.GetRobloxInfoFromDiscordId(me.id) + if not roblox_user.id then + return + 'You are not registered with the fiveman1 api, use !link with the rbhop bot to link your roblox account' + end + return roblox_user + elseif user:match('<@%d+>') then + local user_id = user:match('<@(%d+)>') + local member = message.guild:getMember(user_id) + local roblox_user = StrafesNET.GetRobloxInfoFromDiscordId(member.id) + if not roblox_user.id then + return + 'User is not registered with the fiveman1 api, use !link with the rbhop bot to link your roblox account' + end + return roblox_user + else + local roblox_user = StrafesNET.GetRobloxInfoFromUsername(user) + if not roblox_user.id then return 'User not found' end + return roblox_user + end +end + +function StrafesNET.GetUserOnlineStatus(USER_ID) + if not USER_ID then return 'empty id' end + + local presence = Request("POST", ROBLOX_PRESENCE_URL .. "presence/users", { userIds = { USER_ID } }).userPresences + [1] + + local last_online = Request("POST", ROBLOX_PRESENCE_URL .. "presence/last-online", { userIds = { USER_ID } }) + .lastOnlineTimestamps[1] + + L1Copy(last_online, presence) + return presence +end + +function StrafesNET.GetUserUsernameHistory(USER_ID) + if not USER_ID then return 'empty id' end + return Request("GET", ROBLOX_API_URL .. "users/" .. USER_ID .. "/username-history", + { limit = 50, sortOrder = 'Desc' }) +end + +function StrafesNET.GetBadgesAwardedDates(USER_ID, BADGE_LIST) + if not USER_ID then return 'empty id' end + return Request("GET", ROBLOX_BADGES_API .. "users/" .. USER_ID .. "/badges/awarded-dates", + { badgeIds = table.concat(BADGE_LIST, ",") }) +end + +function StrafesNET.GetVerificationItemID(USER_ID) + if not USER_ID then return 'empty id' end + + local headers1, body1 = Request("GET", ROBLOX_INVENTORY_API .. "users/" .. USER_ID .. "/items/Asset/102611803") + if body1.errors then return body1 end + + local headers2, body2 = Request("GET", ROBLOX_INVENTORY_API .. "users/" .. USER_ID .. "/items/Asset/1567446") + if body2.errors then return body2 end + + local data = {} + if body2.data and body2.data[1] then data[#data + 1] = body2.data[1] end + if body1.data and body1.data[1] then data[#data + 1] = body1.data[1] end + + return { data = data } +end + +function StrafesNET.GetUserThumbnail(USER_ID, TYPE, SIZE) + if not USER_ID then return 'empty id' end + + local _TYPE = ROBLOX_THUMBNAIL_TYPES[TYPE] or "avatar" + local _SIZE = ROBLOX_THUMBNAIL_SIZES[SIZE] or "180x180" + + return Request("GET", ROBLOX_THUMBNAIL_URL .. "users/" .. _TYPE, + { userIds = USER_ID, size = _SIZE, format = "Png", isCircular = false }) +end + return StrafesNET diff --git a/src/SlashCommands/User.lua b/src/SlashCommands/User.lua index 8bdb21f..e99e7dd 100644 --- a/src/SlashCommands/User.lua +++ b/src/SlashCommands/User.lua @@ -4,7 +4,7 @@ local Discordia = require('discordia') local Date = Discordia.Date Discordia.extensions() -local API = require('../Modules/strafes_net.lua') +local StrafesNET = require('../Modules/StrafesNET.lua') local UserCommand = SlashCommandTools.slashCommand('user', 'Looks up specified user on Roblox') @@ -17,181 +17,164 @@ UserCommand:addOption(UserIdOption) UserCommand:addOption(MemberOption) Badges = { - '275640532', --Bhop, pre-group - '363928432', --Surf, pre-group + '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', + [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) +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] + return Date.fromISO(ymd .. "T00:00:00")[1] end -local function leftpad(s,n,p) - return string.rep(p,n-#tostring(s))..s +local function leftpad(s, n, p) + return string.rep(p, n - #tostring(s)) .. s end local function ToYMD(seconds) - return "" + return "" end local IDToDate = { --Terrible ranges but it's all we have -- {1000000000, FromYMD("2006-01-01")}, --I guess? -- {1864564055, FromYMD("2008-08-04")}, - {1228821079, FromYMD("2013-02-07")}, -- asomstephano12344 (mass scanning near sign removal date) - {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")}, - {241580400713, FromYMD("2025-02-16")}, - {244356127782, FromYMD("2025-05-18")}, + { 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") }, + { 241580400713, FromYMD("2025-02-16") }, } --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) + return math.floor(i1 + (i2 - i1) * m) end -local function GuessDateFromAssetID(InstanceID, AssetID) - local note = "" - if AssetID == 1567446 then - note = " (Verification Sign)" - end +local function GuessDateFromAssetID(AssetID) for i = #IDToDate, 1, -1 do --Newest to oldest - local ID,Time = unpack(IDToDate[i]) - if ID < InstanceID then - if not IDToDate[i+1] then - -- Screw it we ball, just do unjustified interpolation - local ID1, Time1 = unpack(IDToDate[#IDToDate-1]) - local ID2, Time2 = unpack(IDToDate[#IDToDate]) - return "Around "..ToYMD(linterp(Time1, Time2, (InstanceID-ID1)/(ID2-ID1)))..note + 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, (InstanceID-ID)/(ParentID-ID)))..note + local ParentID, ParentTime = unpack(IDToDate[i + 1]) + return "Around " .. ToYMD(linterp(Time, ParentTime, (AssetID - ID) / (ParentID - ID))) end end - -- Screw it we ball, just do unjustified interpolation - local ID1, Time1 = unpack(IDToDate[1]) - local ID2, Time2 = unpack(IDToDate[2]) - return "Around "..ToYMD(linterp(Time1, Time2, (InstanceID-ID1)/(ID2-ID1)))..note + 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 user_info + if Args then + local username = Args.username + local user_id = Args.user_id + local member = Args.member + if username then + _, user_info = StrafesNET.GetRobloxInfoFromUsername(username) + elseif user_id then + _, user_info = StrafesNET.GetRobloxInfoFromUserId(user_id) + elseif member then + _, user_info = StrafesNET.GetRobloxInfoFromDiscordId(member.id) + end + else + local user = Interaction.member or Interaction.user + if user then + _, user_info = StrafesNET.GetRobloxInfoFromDiscordId(user.id) + end + end if not user_info.id then return error("User not found") end - local description = user_info.description=='' and 'This user has no description' or user_info.description - -- table.foreach(user_info,print) + local description = user_info.description == '' and 'This user has no description' or user_info.description local created = tostring(Date.fromISO(user_info.created):toSeconds()) local current = Date():toSeconds() - local accountAge = round((current-created)/86400) + 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 usernameHistoryHeaders, usernameHistoryBody = StrafesNET.GetUserUsernameHistory(id) + local usernameHistory = usernameHistoryBody.data or {} local usernameHistoryTable = {} - for index,usernameObj in next,usernameHistory do - table.insert(usernameHistoryTable,usernameObj.name) + for index, usernameObj in next, usernameHistory do + table.insert(usernameHistoryTable, usernameObj.name) end - local usernameHistoryString = table.concat(usernameHistoryTable,', ') + local usernameHistoryString = table.concat(usernameHistoryTable, ', ') - local onlineStatus_info = {lastLocation="Unknown", lastOnline=0, userPresenceType=-1} + local onlineStatus_info = { 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 = 0--Date.fromISO(onlineStatus_info.lastOnline):toSeconds() + if onlineStatus_info.userPresenceType == 2 then LastLocation = "Ingame" end + local LastOnline = 0 --Date.fromISO(onlineStatus_info.lastOnline):toSeconds() - local verificationAssetId = API:GetVerificationItemID(id) + local verificationAssetId = StrafesNET.GetVerificationItemID(id) local verificationDate = "Not verified" if verificationAssetId.errors then verificationDate = "Failed to fetch" elseif verificationAssetId.data[1] then - verificationDate = "" - for i, data in next, verificationAssetId.data do - verificationDate = verificationDate .. GuessDateFromAssetID(data.instanceId, data.id) - if i ~= #verificationAssetId.data then - verificationDate = verificationDate .. "\n" - end - end + verificationDate = GuessDateFromAssetID(verificationAssetId.data[1].instanceId) end - local badgeRequest = API:GetBadgesAwardedDates(id,Badges) + local _, badgeRequest = StrafesNET.GetBadgesAwardedDates(id, Badges) local badgeData = badgeRequest.data -- local badgesDates = {} - local firstBadge,firstBadgeDate = 0,math.huge - for _,badge in next,badgeData do + 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 + if firstBadgeDate > awardedDate then + firstBadge = badgeId + firstBadgeDate = awardedDate end -- badgesDates[badgeId]=awardedDate end - local userThumbnail = API:GetUserThumbnail(id).data[1] + local userThumbnailHeaders, userThumbnailBody = StrafesNET.GetUserThumbnail(id) + local userThumbnail = userThumbnailBody.data[1] local embed = { - title = displayName..' (@'..name..')', - url = 'https://roblox.com/users/'..id..'/profile', + 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}, + { 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}) + 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}) + Interaction:reply({ embed = embed }) end return { - Command = UserCommand, - Callback = Callback -} \ No newline at end of file + Command = UserCommand, + Callback = Callback +}