Make user slash command work

This commit is contained in:
2025-06-23 23:57:34 -04:00
parent 1c08e5fd47
commit e2ee24898f
3 changed files with 263 additions and 129 deletions

View File

@ -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
if type(RequestBody) == "table" then
RequestBody = json.encode(RequestBody)
end
local QueryString = QueryParams(Params) -- at worse (I think), this is an empty string (which cannot mess up the request)
local RequestHeaders = CreateHeaders(Headers) -- At worse, this will just be an empty table (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

View File

@ -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

View File

@ -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')
@ -44,7 +44,6 @@ 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") },
@ -58,34 +57,23 @@ local IDToDate = { --Terrible ranges but it's all we have
{ 232802028144, FromYMD("2024-04-08") },
{ 234886704167, FromYMD("2024-06-28") },
{ 241580400713, FromYMD("2025-02-16") },
{244356127782, FromYMD("2025-05-18")},
}
--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(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 ID < AssetID 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
return "After " .. ToYMD(Time)
end
local ParentID, ParentTime = unpack(IDToDate[i + 1])
return "Around "..ToYMD(linterp(Time, ParentTime, (InstanceID-ID)/(ParentID-ID)))..note
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)
@ -95,16 +83,16 @@ local function Callback(Interaction, Command, Args)
local user_id = Args.user_id
local member = Args.member
if username then
user_info = API:GetRobloxInfoFromUsername(username)
_, user_info = StrafesNET.GetRobloxInfoFromUsername(username)
elseif user_id then
user_info = API:GetRobloxInfoFromUserId(user_id)
_, user_info = StrafesNET.GetRobloxInfoFromUserId(user_id)
elseif member then
user_info = API:GetRobloxInfoFromDiscordId(member.id)
_, user_info = StrafesNET.GetRobloxInfoFromDiscordId(member.id)
end
else
local user = Interaction.member or Interaction.user
if user then
user_info = API:GetRobloxInfoFromDiscordId(user.id)
_, user_info = StrafesNET.GetRobloxInfoFromDiscordId(user.id)
end
end
if not user_info.id then
@ -112,7 +100,6 @@ local function Callback(Interaction, Command, Args)
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)
@ -121,7 +108,8 @@ local function Callback(Interaction, Command, Args)
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)
@ -135,21 +123,15 @@ local function Callback(Interaction, Command, Args)
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 = {}
@ -164,7 +146,8 @@ local function Callback(Interaction, Command, Args)
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 .. ')',