Compare commits

...

14 Commits

Author SHA1 Message Date
0500c9e734 Fix FQG? 2025-07-10 23:53:41 -04:00
a1ec7c8042 new command WIP 2025-07-08 15:53:50 -04:00
af3d164148 changes + new util functions 2025-07-08 15:53:29 -04:00
16bc78f676 Add table.clear 2025-07-08 15:53:11 -04:00
923e3096be HTTP retry 2025-07-08 15:53:04 -04:00
de158b45ee Print response code 2025-07-07 14:09:57 -04:00
e2d3a57629 New endpoint: Get recent world records 2025-06-27 22:08:17 -04:00
083b8960aa Normalize param name 2025-06-27 22:08:05 -04:00
7b3ab8e115 Minecraft user command: Format + fix 2025-06-24 15:08:47 -04:00
54d588b43a User (verification date guesser): Reintroduce changes that I lost 2025-06-24 15:08:47 -04:00
f072b14735 Remove unused 2025-06-24 15:08:47 -04:00
e2ee24898f Make user slash command work 2025-06-24 15:08:47 -04:00
1c08e5fd47 QueryParams guard 2025-06-24 15:08:46 -04:00
d2eded99a1 Refactor StrafesNET module (WIP, more to come) 2025-06-24 15:08:46 -04:00
8 changed files with 550 additions and 479 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
discordia.log discordia.log
gateway.json gateway.json
apikey.lua APIKeys.lua
Token.lua Token.lua
deps deps
sensdb.lua sensdb.lua

View File

@ -1,6 +1,13 @@
local Http = require('coro-http') local Http = require('coro-http')
local HTTPRequest = Http.request local HTTPRequest = Http.request
local Timer = require("timer")
local Sleep = Timer.sleep
local function Wait(n)
return Sleep(n * 1000)
end
local json = require('json') local json = require('json')
local METHODS = { local METHODS = {
@ -9,11 +16,12 @@ local METHODS = {
} }
local function QueryParams(Params) -- {Name = Value, ...} local function QueryParams(Params) -- {Name = Value, ...}
if not Params then return "" end
local QueryString = "?" local QueryString = "?"
for ParamName, ParamValue in next, Params do for ParamName, ParamValue in next, Params do
if ParamValue ~= nil then if ParamValue ~= nil then
QueryString = QueryString .. ParamName .. "=" .. ParamValue .. "&" QueryString = QueryString .. tostring(ParamName) .. "=" .. tostring(ParamValue) .. "&"
end end
end end
@ -21,10 +29,11 @@ local function QueryParams(Params) -- {Name = Value, ...}
end end
local function CreateHeaders(Headers) -- {Name = Value, ...} local function CreateHeaders(Headers) -- {Name = Value, ...}
if not Headers then return {} end
local RequestHeaders = {} local RequestHeaders = {}
for HeaderName, HeaderValue in next, Headers do for HeaderName, HeaderValue in next, Headers do
RequestHeaders[#RequestHeaders + 1] = { HeaderName, HeaderValue } RequestHeaders[#RequestHeaders + 1] = { tostring(HeaderName), tostring(HeaderValue) }
end end
return RequestHeaders return RequestHeaders
@ -48,7 +57,7 @@ local function NormalizeHeaders(Response)
end end
end end
local function Request(Method, Url, Params, Headers, Callback) local function Request(Method, Url, Params, RequestHeaders, RequestBody, Callback, MaxRetries)
if not METHODS[Method] then if not METHODS[Method] then
error("[HTTP] Method " .. Method .. " is not supported.") error("[HTTP] Method " .. Method .. " is not supported.")
end end
@ -57,23 +66,55 @@ local function Request(Method, Url, Params, Headers, Callback)
error("[HTTP] Url is not a string") error("[HTTP] Url is not a string")
end 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 local RequestUrl = Url .. QueryString
print(RequestUrl) print(RequestUrl)
MaxRetries = MaxRetries or 10
local function DoRequest()
local Attempt = 0
local Delay = 2
while Attempt <= MaxRetries do
local Headers, Body = HTTPRequest(Method, RequestUrl, FormattedHeaders, RequestBody)
NormalizeHeaders(Headers)
print("Attempt:", Attempt + 1, "Status code:", Headers.code)
-- we will assume <400 = success i guess
if Headers.code and Headers.code < 400 then
return Headers, TryDecodeJson(Body)
end
Attempt = Attempt + 1
if Attempt > MaxRetries then
break
end
print("Request failed, retrying in " .. Delay .. " seconds...")
Wait(Delay)
Delay = Delay * 2 -- exponential back-off
end
local Headers, Body = HTTPRequest(Method, RequestUrl, FormattedHeaders, RequestBody)
NormalizeHeaders(Headers)
return Headers, TryDecodeJson(Body)
end
if Callback and type(Callback) == "function" then if Callback and type(Callback) == "function" then
return coroutine.wrap(function() return coroutine.wrap(function()
local Response, Body = HTTPRequest(Method, RequestUrl, RequestHeaders) local Headers, DecodedBody = DoRequest()
NormalizeHeaders(Response) Callback(Headers, DecodedBody)
Callback(Response, TryDecodeJson(Body))
end) end)
else else
local Response, Body = HTTPRequest(Method, RequestUrl, RequestHeaders) return DoRequest()
NormalizeHeaders(Response)
return Response, TryDecodeJson(Body)
end end
end end

310
src/Modules/StrafesNET.lua Normal file
View File

@ -0,0 +1,310 @@
local HttpRequest = require("./HttpRequest.lua")
local Request = HttpRequest.Request
local APIKeys = require("./APIKeys.lua")
local RequestHeaders = {
["Content-Type"] = "application/json",
["X-API-Key"] = APIKeys.StrafesNET
}
function L1Copy(t, b)
b = b or {}
for x, y in next, t do b[x] = y end
return b
end
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'
local GAME_IDS = {
BHOP = 1,
SURF = 2,
-- FLY_TRIALS = 5
}
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"
},
RANKS = {
LIST = "rank"
},
TIMES = {
LIST = "time",
WORLD_RECORD = {
GET = "time/worldrecord"
},
GET = "time/%d"
},
USERS = {
LIST = "user",
GET = "user/%d",
RANKS = {
GET = "user/%d/rank"
}
}
}
local RankConstants = {
Magic1 = 0.7,
Magic2 = 0.22313016
}
local StrafesNET = {}
StrafesNET.GameIds = GAME_IDS
function StrafesNET.CalculatePoints(Rank, Count)
local ExpMagic2 = math.exp(RankConstants.Magic2)
local Num1 = ExpMagic2 - 1.0
local ExpDenomExp = math.max(-700.0, -RankConstants.Magic2 * Count)
local Denom1 = 1.0 - math.exp(ExpDenomExp)
local ExpRankExp = math.max(-700.0, -RankConstants.Magic2 * Rank)
local ExpRank = math.exp(ExpRankExp)
local Part1 = RankConstants.Magic1 * (Num1 / Denom1) * ExpRank
local Part2 = (1.0 - RankConstants.Magic1) * (1.0 + 2.0 * (Count - Rank)) / (Count * Count)
return Part1 + Part2
end
function StrafesNET.CalculateSkill(Rank, Count)
local Denominator = Count - 1
if Denominator == 0 then
return 0
else
return (Count - Rank) / Denominator
end
end
function StrafesNET.ListMaps(GameId, PageSize, PageNumber)
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, RequestHeaders)
end
function StrafesNET.GetMap(MapId)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.MAPS.GET:format(MapId)
local Params = { id = MapId }
return Request("GET", RequestUrl, Params, RequestHeaders)
end
function StrafesNET.ListRanks(GameId, ModeId, StyleId, SortBy, PageSize, PageNumber)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.RANKS.LIST
local Params = {
game_id = GameId,
mode_id = ModeId,
style_id = StyleId,
sort_by = SortBy or 1,
page_size = PageSize or 10,
page_number = PageNumber or 1
}
return Request("GET", RequestUrl, Params, RequestHeaders)
end
function StrafesNET.ListTimes(MapId, GameId, ModeId, StyleId, UserId, SortBy, PageSize, PageNumber)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.TIMES.LIST
local Params = {
user_id = UserId,
map_id = MapId,
game_id = GameId,
mode_id = ModeId,
style_id = StyleId,
sort_by = SortBy or 0,
page_size = PageSize or 10,
page_number = PageNumber or 1
}
return Request("GET", RequestUrl, Params, RequestHeaders)
end
function StrafesNET.GetWorldRecords(UserId, MapId, GameId, ModeId, StyleId, PageSize, PageNumber)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.TIMES.WORLD_RECORD.GET
local Params = {
user_id = UserId,
map_id = MapId,
game_id = GameId,
mode_id = ModeId,
style_id = StyleId,
page_size = PageSize or 10,
page_number = PageNumber or 0
}
return Request("GET", RequestUrl, Params, RequestHeaders)
end
function StrafesNET.GetTime(TimeId)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.TIMES.GET:format(TimeId)
return Request("GET", RequestUrl, nil, RequestHeaders)
end
function StrafesNET.ListUsers(StateId, PageSize, PageNumber)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.USERS.LIST
local Params = {
state_id = StateId,
page_size = PageSize or 10,
page_number = PageNumber or 1,
}
return Request("GET", RequestUrl, Params, RequestHeaders)
end
function StrafesNET.GetUser(UserId)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.USERS.GET:format(UserId)
return Request("GET", RequestUrl, nil, RequestHeaders)
end
function StrafesNET.GetUserRank(UserId, GameId, ModeId, StyleId)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.USERS.RANKS.GET:format(UserId)
local Params = {
game_id = GameId,
mode_id = ModeId,
style_id = StyleId,
}
return Request("GET", RequestUrl, Params, RequestHeaders)
end
-- util stuff or something
function StrafesNET.GetMapCompletionCount(MapId, GameId, ModeId, StyleId)
local Headers, Response = StrafesNET.ListTimes(MapId, GameId, ModeId, StyleId)
if Headers.code >= 400 then
return error("HTTP Error while getting map completion count")
end
return Response.pagination.total_items
end
function StrafesNET.GetAllUserTimes(UserId, GameId, ModeId, StyleId)
local Times = {}
local CurrentPage = 1
local Headers, Response = StrafesNET.ListTimes(nil, GameId, ModeId, StyleId, UserId, 0, 100, CurrentPage)
if Headers.code >= 400 then
return error("HTTP error while getting times for something")
end
for TimeIndex, Time in next, Response.data do
Times[Time.id] = Time
end
local TotalPages = Response.pagination.total_pages
while CurrentPage < TotalPages do
CurrentPage = CurrentPage + 1
local _Headers, _Response = StrafesNET.ListTimes(nil, GameId, ModeId, StyleId, UserId, 0, 100, CurrentPage)
if _Headers.code >= 400 then
return error("HTTP error while getting times for something")
end
for _, Time in next, _Response.data do
Times[Time.id] = Time
end
end
return Times
end
function StrafesNET.GetAllMaps()
local Maps = {}
-- TODO
return Maps
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.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

@ -1,348 +0,0 @@
local http_request = require('./http.lua')
local API = {}
local API_KEY = require('./apikey.lua')
local API_HEADER = { {'Content-Type','application/json'}, { 'api-key', API_KEY }, {'secretkey', '7b320736'} }
local STRAFESNET_API_URL = 'https://api.strafes.net/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'
local ROBLOX_SENS_DB = 'http://oef.ddns.net:9017/rbhop/sens/'
local RANK_CONSTANT_A, RANK_CONSTANT_B, RANK_CONSTANT_C, RANK_CONSTANT_D, RANK_CONSTANT_E = 0.215, 0.595, 0.215, 0.215, 0.71
local t=tostring
local r=function(n,nd) return tonumber(string.format('%.' .. (nd or 0) .. 'f', n))end
local GAMES={BHOP=1,SURF=2,[1]='bhop',[2]='surf'}
local STATES={[0]='Default',[1]='Whitelisted',[2]='Blacklisted',[3]='Pending'}
local RANKS={'New (1)','Newb (2)','Bad (3)','Okay (4)','Not Bad (5)','Decent (6)','Getting There (7)','Advanced (8)','Good (9)','Great (10)','Superb (11)','Amazing (12)','Sick (13)','Master (14)','Insane (15)','Majestic (16)','Baby Jesus (17)','Jesus (18)','Half God (19)','God (20)'}
local STYLES_LIST={'Autohop','Scroll','Sideways','Half-Sideways','W-Only','A-Only','Backwards'}
local STYLES={AUTOHOP=1,SCROLL=2,SIDEWAYS=3,HALFSIDEWAYS=4,WONLY=5,AONLY=6,BACKWARDS=7}
setmetatable(STYLES,{__index=function(self,i)
if type(i)=='number' then return STYLES_LIST[i] or 'Unknown' end
if i=='a' then i='auto'elseif i=='hsw'then i='half'elseif i=='s'then i='scroll'elseif i=='sw'then i='side'elseif i=='bw'then i='back'end
for ix,v in pairs(self) do
if string.sub(ix:lower(),1,#i):find(i:lower()) then
return self[ix]
end
end
end})
setmetatable(GAMES,{__index=function(self,i)
for ix,v in pairs(self) do
if tostring(ix):lower()==i:lower() then
return self[ix]
end
end
end})
API.GAMES=GAMES
API.STYLES=STYLES
API.STYLES_LIST=STYLES_LIST
API.STATES=STATES
API.ROBLOX_LOCATION_TYPES={
[0]='Mobile Website',
[1]='Mobile In-Game',
[2]='Website',
[3]='Roblox Studio',
[4]='In-Game',
[5]='XboxApp',
[6]='TeamCreate'
}
API.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'
}
API.ROBLOX_THUMBNAIL_TYPES = {
AVATAR='avatar',
BUST='avatar-bust',
HEADSHOT='avatar-headshot'
}
-- insyri make this BTW
-- use as local err, res = parseToURLArgs(), thanks golang for this idea
function parseToURLArgs(tb) local function Err(err) return err, nil end local function Ok(res) return nil, res end if not tb then return Err('got nothing') end if type(tb) ~= 'table' then return Err('expected table, got '..type(tb)) end local str = '?' local index = 1 for key, value in pairs(tb) do if index == 1 then str = str..key..'='..t(value) else str = str..'&'..key..'='..t(value) end index = index + 1 end return Ok(str) end
-- fiveman made these (converted to lua from python)
-- function format_helper(a,b)a=tostring(a)while#a<b do a='0'..a end;return a end
-- function formatTime(a)if a>86400000 then return'>1 day'end;local c=format_helper(a%1000,3)local d=format_helper(math.floor(a/1000)%60,2)local e=format_helper(math.floor(a/(1000*60))%60,2)local f=format_helper(math.floor(a/(1000*60*60))%24,2)if f=='00'then return e..':'..d..'.'..c else return f..':'..e..':'..d end end
function formatTime(time) -- chatgpt THIS IS FOR SECONDS! NOT MILLISECONDS
local hours = math.floor(time / 3600)
local minutes = math.floor((time % 3600) / 60)
local seconds = math.floor(time % 60)
local milliseconds = math.floor(((time % 1) * 1000)+.5)
-- if hours > 0 then
-- return string.format("%02d:%02d:%02d", hours, minutes, seconds)
-- else
-- return string.format("%02d:%02d.%03d", minutes, seconds, milliseconds)
-- end
return string.format(hours and '%02d:%02d:%02d' or '%02d:%02d.%03d', hours and hours or minutes,hours and minutes or seconds, hours and seconds or milliseconds)
end
function L1Copy(t,b) b=b or {} for x,y in next,t do b[x]=y end return b end
-- [[ STRAFESNET API ]] --
-- Get rank string from rank point
function API.FormatRank(n) return RANKS[1+math.floor(n*19)] end
-- Get skill percentage from skill point
function API.FormatSkill(n) return r(n*100,3)..'%' end
function API.FormatTime(n) return formatTime(n) end
-- Time from id.
function API:GetTime(ID)
if not ID then return 'empty id' end
local response,headers = http_request('GET', STRAFESNET_API_URL..'time/'..ID, API_HEADER)
return response,headers
end
-- Time rank from id.
function API:GetTimeRank(TIME_ID)
if not TIME_ID then return 'empty id' end
local response,headers = http_request('GET', STRAFESNET_API_URL..'time/'..TIME_ID..'/rank', API_HEADER)
return response,headers
end
-- 10 recent world records.
function API:GetRecentWrs(STYLE_ID, GAME_ID, WHITELIST_FILTER)
if not STYLE_ID or not GAME_ID then return 'empty id' end
local err, res = parseToURLArgs({style=STYLE_ID, game=GAME_ID, whitelist=WHITELIST_FILTER})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'time/recent/wr'..res, API_HEADER)
return response,headers
end
-- Time by map id. Sorted in ascending order.
function API:GetMapTimes(MAP_ID, STYLE_ID, PAGE)
if not MAP_ID then return 'empty id' end
local err, res = parseToURLArgs({style=STYLE_ID, page=PAGE})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'time/map/'..MAP_ID..res, API_HEADER)
return response,headers
end
-- Get WR of map.
function API:GetMapWr(MAP_ID, STYLE_ID)
if not MAP_ID or not STYLE_ID then return 'empty id' end
local err, res = parseToURLArgs({style=STYLE_ID})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'time/map/'..MAP_ID..'/wr'..res, API_HEADER)
return response,headers
end
-- Time by user id.
function API:GetUserTimes(USER_ID, MAP_ID, STYLE_ID, GAME_ID, PAGE)
if not USER_ID then return 'empty id' end
local err, res = parseToURLArgs({map=MAP_ID, style=STYLE_ID, game=GAME_ID, page=PAGE})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'time/user/'..USER_ID..res , API_HEADER)
return response,headers
end
-- World records by user id.
function API:GetUserWrs(USER_ID,GAME_ID,STYLE_ID)
if not USER_ID or not GAME_ID or not STYLE_ID then return 'empty id' end
local err, res = parseToURLArgs({game=GAME_ID, style=STYLE_ID})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'time/user/'..USER_ID..'/wr'..res, API_HEADER)
return response,headers
end
-- User from id.
function API:GetUser(USER_ID)
if not USER_ID then return 'empty id' end
local response,headers = http_request('GET', STRAFESNET_API_URL..'user/'..USER_ID, API_HEADER)
return response,headers
end
-- Top ranked players, paged at 50 per page.
function API:GetRanks(STYLE_ID,GAME_ID,PAGE)
if not STYLE_ID or not GAME_ID then return 'empty id' end
local err, res = parseToURLArgs({style=STYLE_ID, game=GAME_ID, page=PAGE})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'rank'..res, API_HEADER)
return response,headers
end
-- Get rank of user by their id.
function API:GetRank(USER_ID,GAME_ID,STYLE_ID)
if not USER_ID or not STYLE_ID or not GAME_ID then return 'empty id' end
local err, res = parseToURLArgs({style=STYLE_ID, game=GAME_ID})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'rank/'..USER_ID..res, API_HEADER)
return response,headers
end
-- Get list of maps.
function API:GetMaps(GAME_ID,PAGE)
if not GAME_ID then return 'empty id' end
local err, res = parseToURLArgs({game=GAME_ID, page=PAGE})
if err then return err end
local response,headers = http_request('GET', STRAFESNET_API_URL..'map'..res, API_HEADER)
return response,headers
end
-- Get map by ID.
function API:GetMap(MAP_ID)
if not MAP_ID then return 'empty id' end
local response,headers = http_request('GET', STRAFESNET_API_URL..'map/'..MAP_ID, API_HEADER)
return response,headers
end
-- [[ CUSTOM ]] --
function API:GetMapCompletionCount(MAP_ID,STYLE_ID)
if not MAP_ID or not STYLE_ID then return 'empty id' end
local _,headers = self:GetMapTimes(MAP_ID,STYLE_ID)
local pages = headers['Pagination-Count']
local res,h = self:GetMapTimes(MAP_ID,STYLE_ID,pages)
if not res then
table.foreach(h,print)
end
return ((pages-1)*200)+#res
end
--cool doggo, aidan and me
function API.CalculatePoint(rank,count) --??wtf
return RANK_CONSTANT_A*(math.exp(RANK_CONSTANT_B)-1)/(1-math.exp(math.max(-700, -RANK_CONSTANT_C*count)))*math.exp(math.max(-700, -RANK_CONSTANT_D*rank))+(1-RANK_CONSTANT_E)*(1+2*(count-rank))/(count*count)
end
function API.Pad(str,n)
n = n or 20
str = tostring(str)
return str..string.rep(' ',n-#str)
end
function API.CalculateDifference(v1,v2)
return math.abs(v1-v2)
end
function API.CalculateDifferencePercent(v1,v2)
return math.abs((1-(v1/v2))*100)..'%'
end
function API:GetUserFromAny(user,message)
local str = user:match('^["\'](.+)[\'"]$')
local num = user:match('^(%d+)$')
if str then
local roblox_user=self:GetRobloxInfoFromUsername(str)
if not roblox_user.id then return 'User not found' end
return roblox_user
elseif num then
local roblox_user = self: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=self: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=self: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=self:GetRobloxInfoFromUsername(user)
if not roblox_user.id then return 'User not found' end
return roblox_user
end
return 'Something went wrong (this should generally not happen)'
end
-- [[ ROBLOX / FIVEMAN AND OTHER APIs ]] --
function API:GetRobloxInfoFromUserId(USER_ID)
if not USER_ID then return 'empty id' end
local response,headers = http_request('GET', ROBLOX_API_URL..'users/'..USER_ID, API_HEADER)
return response,headers
end
function API:GetRobloxInfoFromUsername(USERNAME)
if not USERNAME then return 'empty username' end
if #USERNAME > 32 then return 'Username too long' end
local response,headers = http_request('POST', ROBLOX_API_URL..'usernames/users', API_HEADER, {usernames={USERNAME}})
if not response.data[1] then return 'Username \''..USERNAME..'\' not found.' end
return self:GetRobloxInfoFromUserId(response.data[1].id)
end
function API:GetRobloxInfoFromDiscordId(DISCORD_ID)
if not DISCORD_ID then return 'empty id' end
local response,headers = http_request('GET', FIVEMAN_API_URL..'users/'..DISCORD_ID, API_HEADER)
if response.status=='error' then return response.messages end
local response2 = http_request('GET', ROBLOX_API_URL..'users/'..response.result.robloxId, API_HEADER)
return response2
end
function API:GetUserOnlineStatus(USER_ID)
if not USER_ID then return 'empty id' end
local response1 = http_request('POST', ROBLOX_PRESENCE_URL..'presence/users', API_HEADER, {userIds={USER_ID}}).userPresences[1] --For LastLocation
local response2 = http_request('POST', ROBLOX_PRESENCE_URL..'presence/last-online', API_HEADER, {userIds={USER_ID}}).lastOnlineTimestamps[1] --For a more accurate LastOnline
L1Copy(response2, response1)
return response1
end
function API:GetUserUsernameHistory(USER_ID)
if not USER_ID then return 'empty id' end
local err, res = parseToURLArgs({limit=50,sortOrder='Desc'})
if err then return err end
local response1 = http_request('GET', ROBLOX_API_URL..'users/'..USER_ID..'/username-history'..res,API_HEADER)
return response1
end
function API:GetBadgesAwardedDates(USER_ID,BADGE_LIST)
if not USER_ID then return 'empty id' end
local err,res = parseToURLArgs({badgeIds=table.concat(BADGE_LIST,',')})
if err then return end
local response,headers = http_request('GET',ROBLOX_BADGES_API..'users/'..USER_ID..'/badges/awarded-dates'..res)
return response,headers
end
function API:GetVerificationItemID(USER_ID)
if not USER_ID then return 'empty id' end
local response1,headers1 = http_request('GET', ROBLOX_INVENTORY_API..'users/'..USER_ID.."/items/Asset/102611803", API_HEADER)
if response1.errors then return response1,headers1 end
local response2,headers2 = http_request('GET', ROBLOX_INVENTORY_API..'users/'..USER_ID.."/items/Asset/1567446", API_HEADER)
if response2.errors then return response2,headers2 end
local data = {}
data[#data+1] = response2.data[1] -- Do the older item first if present
data[#data+1] = response1.data[1]
return {data=data},headers1
end
function API:GetUserThumbnail(USER_ID,TYPE,SIZE) -- https://thumbnails.roblox.com/v1/users/avatar?userIds=1455906620&size=180x180&format=Png&isCircular=false
if not USER_ID then return 'empty id' end
local _TYPE = self.ROBLOX_THUMBNAIL_TYPES[TYPE] or 'avatar'
local _SIZE = self.ROBLOX_THUMBNAIL_SIZES[SIZE] or '180x180'
local err, res = parseToURLArgs({userIds=USER_ID,size=_SIZE,format='Png',isCircular=false})
if err then return err end
local response,headers = http_request('GET', ROBLOX_THUMBNAIL_URL..'users/'.._TYPE..res, API_HEADER)
return response,headers
end
function API:GetGroups(USER_ID)
if not USER_ID then return 'empty id' end
local response,headers = http_request('GET',string.format(ROBLOX_GROUPS_ROLES_URL,USER_ID))
return response,headers
end
function API:GetSensWithParams(ARGUMENTS)
local hasarg = false
for _,val in next,ARGUMENTS do
if val then
hasarg=true
end
end
ARGUMENTS.format='json'
local err,res = parseToURLArgs(ARGUMENTS)
if err then return err end
local response,headers=http_request('GET',ROBLOX_SENS_DB..(hasarg and 'get' or 'get/all')..res)
return response,headers
end
return API

View File

@ -1,6 +1,7 @@
local Discordia = require('discordia') local Discordia = require('discordia')
local json = require('json') local json = require('json')
local http_request = require('../Modules/http.lua') local HttpRequest = require('../Modules/HttpRequest.lua')
local Request = HttpRequest.Request
local SubCommandHandler = require('../Modules/SubCommandHandler.lua') local SubCommandHandler = require('../Modules/SubCommandHandler.lua')
Discordia.extensions() Discordia.extensions()
@ -12,8 +13,10 @@ local MinecraftSubCommandHandler = SubCommandHandler.new()
local MinecraftMainCommand = SlashCommandTools.slashCommand('minecraft', 'Minecraft server related commands') 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 MinecraftStatusSubCommand = SlashCommandTools.subCommand('status',
local MinecraftSetIpSubCommand = SlashCommandTools.subCommand('setip', 'Set the preferred Minecraft server IP address for this server') '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') local MinecraftSetIpOptions = SlashCommandTools.string('ip', 'The IP address of the server')
MinecraftSetIpOptions:setRequired(true) MinecraftSetIpOptions:setRequired(true)
@ -53,46 +56,49 @@ MinecraftSubCommandHandler:AddSubCommand(MinecraftStatusSubCommand.name, functio
return Interaction:reply('There is no data for this Discord server', true) return Interaction:reply('There is no data for this Discord server', true)
end end
local ServerIPStr = ServerMinecraftData.IP..':'..ServerMinecraftData.PORT local ServerIPStr = ServerMinecraftData.IP .. ':' .. ServerMinecraftData.PORT
local Response, Headers = http_request('GET', ('https://api.mcsrvstat.us/3/%s'):format(ServerIPStr)) local Headers, Body = Request("GET", ('https://api.mcsrvstat.us/3/%s'):format(ServerIPStr), nil,
{ ["User-Agent"] = "tommy-bot/1.0 Main-Release" })
local IsOnline = Response.online if not Headers.code == 200 then
return error("Something went wrong")
end
local IsOnline = Body.online
local EmbedData local EmbedData
if IsOnline then if IsOnline then
local MaxPlayers = Response.players.max local MaxPlayers = Body.players.max
local OnlinePlayers = Response.players.online local OnlinePlayers = Body.players.online
local AnonymousPlayers = OnlinePlayers local AnonymousPlayers = OnlinePlayers
local Players = {} local Players = {}
if OnlinePlayers>0 then if OnlinePlayers > 0 then
for PlayerIndex, PlayerData in next, Response.players.list do for PlayerIndex, PlayerData in next, Body.players.list do
table.insert(Players, PlayerData.name) table.insert(Players, PlayerData.name)
AnonymousPlayers = AnonymousPlayers-1 AnonymousPlayers = AnonymousPlayers - 1
end end
else else
table.insert(Players, 'No players online') table.insert(Players, 'No players online')
end end
if AnonymousPlayers>0 then if AnonymousPlayers > 0 then
for AnonymousPlayerIndex = 1, AnonymousPlayers do for AnonymousPlayerIndex = 1, AnonymousPlayers do
table.insert(Players, 'Anonymous Player') table.insert(Players, 'Anonymous Player')
end end
end end
EmbedData = { EmbedData = {
title = 'Server Status for '..ServerIPStr, title = 'Server Status for ' .. ServerIPStr,
description = Response.motd.clean[1]..' ('..Response.version..')', description = Body.motd.clean[1] .. ' (' .. Body.version .. ')',
fields = { fields = {
{name = 'Players', value = OnlinePlayers..'/'..MaxPlayers, inline = true}, { name = 'Players', value = OnlinePlayers .. '/' .. MaxPlayers, inline = true },
{name = 'List of players', value = table.concat(Players, '\n'), inline = true} { name = 'List of players', value = table.concat(Players, '\n'), inline = true }
}, },
color = COLOURS.GREEN color = COLOURS.GREEN
} }
else else
EmbedData = { EmbedData = {
title = 'Server Status for '..ServerIPStr, title = 'Server Status for ' .. ServerIPStr,
description = 'Server is offline', description = 'Server is offline',
color = COLOURS.RED color = COLOURS.RED
} }
end end
return Interaction:reply({embed = EmbedData}) return Interaction:reply({ embed = EmbedData })
end) end)
MinecraftSubCommandHandler:AddSubCommand(MinecraftSetIpSubCommand.name, function(Interaction, Command, Args) MinecraftSubCommandHandler:AddSubCommand(MinecraftSetIpSubCommand.name, function(Interaction, Command, Args)
@ -107,18 +113,18 @@ MinecraftSubCommandHandler:AddSubCommand(MinecraftSetIpSubCommand.name, function
if not ServerIP then if not ServerIP then
return Interaction:reply('Invalid server IP') return Interaction:reply('Invalid server IP')
end end
local ServerPort = ServerIPStr:match(ServerIP..':(%d+)') or 25565 local ServerPort = ServerIPStr:match(ServerIP .. ':(%d+)') or 25565
local GuildMinecraftData = {IP = ServerIP, PORT = ServerPort} local GuildMinecraftData = { IP = ServerIP, PORT = ServerPort }
local GlobalMinecraftData = json.decode(io.open('minecraft_data.json','r'):read('*a')) local GlobalMinecraftData = json.decode(io.open('minecraft_data.json', 'r'):read('*a'))
GlobalMinecraftData[GuildId] = GuildMinecraftData GlobalMinecraftData[GuildId] = GuildMinecraftData
io.open('minecraft_data.json','w+'):write(json.encode(GlobalMinecraftData)):close() io.open('minecraft_data.json', 'w+'):write(json.encode(GlobalMinecraftData)):close()
return Interaction:reply('Successfully added `'..ServerIP..':'..ServerPort..'` for ServerId='..GuildId) return Interaction:reply('Successfully added `' .. ServerIP .. ':' .. ServerPort .. '` for ServerId=' .. GuildId)
end) end)
return { return {
Command = MinecraftMainCommand, Command = MinecraftMainCommand,
Callback = MinecraftSubCommandHandler:GetMainCommandCallback() Callback = MinecraftSubCommandHandler:GetMainCommandCallback()
} }

View File

@ -0,0 +1,56 @@
local SlashCommandTools = require('discordia-slash').util.tools()
local Discordia = require('discordia')
local Date = Discordia.Date
Discordia.extensions()
local StrafesNET = require('../Modules/StrafesNET.lua')
local CalculateCommand = SlashCommandTools.slashCommand('calculate', 'Calculate rank and skill points')
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')
local GameIdOption = SlashCommandTools.integer
CalculateCommand:addOption(UsernameOption)
CalculateCommand:addOption(UserIdOption)
CalculateCommand:addOption(MemberOption)
local function Callback(Interaction, Command, Args)
local UserInfo
if Args then
if Args.username then
local Headers, Response = StrafesNET.GetRobloxInfoFromUsername(Args.username)
if Headers.code < 400 then
UserInfo = Response
end
elseif Args.user_id then
local Headers, Response = StrafesNET.GetRobloxInfoFromUserId(Args.user_id)
if Headers.code < 400 then
UserInfo = Response
end
elseif Args.member then
local Headers, Response = StrafesNET.GetRobloxInfoFromDiscordId(Args.member.id)
if Headers.code < 400 then
UserInfo = Response
end
end
else
local Headers, Response = StrafesNET.GetRobloxInfoFromDiscordId((Interaction.member or Interaction.user).id)
if Headers.code < 400 then
UserInfo = Response
end
end
if UserInfo == nil then
error("SOMETHING WENT REALLY WRONG")
end
-- Add args for game/style etc and grab all times and grab all placements
end
return {
Command = CalculateCommand,
Callback = Callback
}

View File

@ -4,7 +4,7 @@ local Discordia = require('discordia')
local Date = Discordia.Date local Date = Discordia.Date
Discordia.extensions() 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') local UserCommand = SlashCommandTools.slashCommand('user', 'Looks up specified user on Roblox')
@ -17,52 +17,51 @@ UserCommand:addOption(UserIdOption)
UserCommand:addOption(MemberOption) UserCommand:addOption(MemberOption)
Badges = { Badges = {
'275640532', --Bhop, pre-group '275640532', --Bhop, pre-group
'363928432', --Surf, pre-group '363928432', --Surf, pre-group
'2124614454', --Bhop, post-group '2124614454', --Bhop, post-group
'2124615096', --Surf, post-group '2124615096', --Surf, post-group
} }
BadgesToName = { BadgesToName = {
[275640532]='old bhop', [275640532] = 'old bhop',
[363928432]='old surf', [363928432] = 'old surf',
[2124614454]='new bhop', [2124614454] = 'new bhop',
[2124615096]='new surf', [2124615096] = 'new surf',
} }
local function round(x,n) local function round(x, n)
return string.format('%.'..(n or 0)..'f',x) return string.format('%.' .. (n or 0) .. 'f', x)
end end
local function FromYMD(ymd) local function FromYMD(ymd)
return Date.fromISO(ymd.."T00:00:00")[1] return Date.fromISO(ymd .. "T00:00:00")[1]
end end
local function leftpad(s,n,p) local function leftpad(s, n, p)
return string.rep(p,n-#tostring(s))..s return string.rep(p, n - #tostring(s)) .. s
end end
local function ToYMD(seconds) local function ToYMD(seconds)
return "<t:"..seconds..":R>" return "<t:" .. seconds .. ":R>"
end end
local IDToDate = { --Terrible ranges but it's all we have local IDToDate = { --Terrible ranges but it's all we have
-- {1000000000, FromYMD("2006-01-01")}, --I guess? -- {1000000000, FromYMD("2006-01-01")}, --I guess?
-- {1864564055, FromYMD("2008-08-04")}, -- {1864564055, FromYMD("2008-08-04")},
{1228821079, FromYMD("2013-02-07")}, -- asomstephano12344 (mass scanning near sign removal date) { 1228821079, FromYMD("2013-02-07") }, -- asomstephano12344 (mass scanning near sign removal date)
{3800920136, FromYMD("2016-04-16")}, { 3800920136, FromYMD("2016-04-16") },
{9855616205, FromYMD("2017-04-02")}, { 9855616205, FromYMD("2017-04-02") },
{30361018662, FromYMD("2018-11-14")}, { 30361018662, FromYMD("2018-11-14") },
{32665806459, FromYMD("2019-01-07")}, { 32665806459, FromYMD("2019-01-07") },
{34758058773, FromYMD("2019-02-24")}, { 34758058773, FromYMD("2019-02-24") },
{65918261258, FromYMD("2020-06-05")}, { 65918261258, FromYMD("2020-06-05") },
{171994717435, FromYMD("2023-03-24")}, { 171994717435, FromYMD("2023-03-24") },
{173210319088, FromYMD("2023-04-14")}, { 173210319088, FromYMD("2023-04-14") },
{206368884641, FromYMD("2023-07-16")}, { 206368884641, FromYMD("2023-07-16") },
{229093879745, FromYMD("2024-01-02")}, { 229093879745, FromYMD("2024-01-02") },
{232802028144, FromYMD("2024-04-08")}, { 232802028144, FromYMD("2024-04-08") },
{234886704167, FromYMD("2024-06-28")}, { 234886704167, FromYMD("2024-06-28") },
{241580400713, FromYMD("2025-02-16")}, { 241580400713, FromYMD("2025-02-16") },
{244356127782, FromYMD("2025-05-18")},
} }
--We assume linear interpolation since anything more complex I can't process --We assume linear interpolation since anything more complex I can't process
local function linterp(i1, i2, m) local function linterp(i1, i2, m)
return math.floor(i1 + (i2-i1)*m) return math.floor(i1 + (i2 - i1) * m)
end end
local function GuessDateFromAssetID(InstanceID, AssetID) local function GuessDateFromAssetID(InstanceID, AssetID)
local note = "" local note = ""
@ -70,72 +69,72 @@ local function GuessDateFromAssetID(InstanceID, AssetID)
note = " (Verification Sign)" note = " (Verification Sign)"
end end
for i = #IDToDate, 1, -1 do --Newest to oldest for i = #IDToDate, 1, -1 do --Newest to oldest
local ID,Time = unpack(IDToDate[i]) local ID, Time = unpack(IDToDate[i])
if ID < InstanceID then if ID < InstanceID then
if not IDToDate[i+1] then if not IDToDate[i + 1] then
-- Screw it we ball, just do unjustified interpolation -- Screw it we ball, just do unjustified interpolation
local ID1, Time1 = unpack(IDToDate[#IDToDate-1]) local ID1, Time1 = unpack(IDToDate[#IDToDate - 1])
local ID2, Time2 = unpack(IDToDate[#IDToDate]) local ID2, Time2 = unpack(IDToDate[#IDToDate])
return "Around "..ToYMD(linterp(Time1, Time2, (InstanceID-ID1)/(ID2-ID1)))..note return "Around " .. ToYMD(linterp(Time1, Time2, (InstanceID - ID1) / (ID2 - ID1))) .. note
end end
local ParentID, ParentTime = unpack(IDToDate[i+1]) local ParentID, ParentTime = unpack(IDToDate[i + 1])
return "Around "..ToYMD(linterp(Time, ParentTime, (InstanceID-ID)/(ParentID-ID)))..note return "Around " .. ToYMD(linterp(Time, ParentTime, (InstanceID - ID) / (ParentID - ID))) .. note
end end
end end
-- Screw it we ball, just do unjustified interpolation -- Screw it we ball, just do unjustified interpolation
local ID1, Time1 = unpack(IDToDate[1]) local ID1, Time1 = unpack(IDToDate[1])
local ID2, Time2 = unpack(IDToDate[2]) local ID2, Time2 = unpack(IDToDate[2])
return "Around "..ToYMD(linterp(Time1, Time2, (InstanceID-ID1)/(ID2-ID1)))..note return "Around " .. ToYMD(linterp(Time1, Time2, (InstanceID - ID1) / (ID2 - ID1))) .. note
end end
local function Callback(Interaction, Command, Args) local function Callback(Interaction, Command, Args)
local user_info local user_info
if Args then if Args then
local username = Args.username local username = Args.username
local user_id = Args.user_id local user_id = Args.user_id
local member = Args.member local member = Args.member
if username then if username then
user_info = API:GetRobloxInfoFromUsername(username) _, user_info = StrafesNET.GetRobloxInfoFromUsername(username)
elseif user_id then elseif user_id then
user_info = API:GetRobloxInfoFromUserId(user_id) _, user_info = StrafesNET.GetRobloxInfoFromUserId(user_id)
elseif member then elseif member then
user_info = API:GetRobloxInfoFromDiscordId(member.id) _, user_info = StrafesNET.GetRobloxInfoFromDiscordId(member.id)
end end
else else
local user = Interaction.member or Interaction.user local user = Interaction.member or Interaction.user
if user then if user then
user_info = API:GetRobloxInfoFromDiscordId(user.id) _, user_info = StrafesNET.GetRobloxInfoFromDiscordId(user.id)
end end
end end
if not user_info.id then if not user_info.id then
return error("User not found") return error("User not found")
end end
local description = user_info.description=='' and 'This user has no description' or user_info.description 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 created = tostring(Date.fromISO(user_info.created):toSeconds())
local current = Date():toSeconds() local current = Date():toSeconds()
local accountAge = round((current-created)/86400) local accountAge = round((current - created) / 86400)
local isBanned = user_info.isBanned local isBanned = user_info.isBanned
local id = user_info.id local id = user_info.id
local name = user_info.name local name = user_info.name
local displayName = user_info.displayName 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 = {} local usernameHistoryTable = {}
for index,usernameObj in next,usernameHistory do for index, usernameObj in next, usernameHistory do
table.insert(usernameHistoryTable,usernameObj.name) table.insert(usernameHistoryTable, usernameObj.name)
end 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) -- table.foreach(onlineStatus_info,print)
local LastLocation = onlineStatus_info.lastLocation local LastLocation = onlineStatus_info.lastLocation
if onlineStatus_info.userPresenceType==2 then LastLocation="Ingame" end if onlineStatus_info.userPresenceType == 2 then LastLocation = "Ingame" end
local LastOnline = 0--Date.fromISO(onlineStatus_info.lastOnline):toSeconds() local LastOnline = 0 --Date.fromISO(onlineStatus_info.lastOnline):toSeconds()
local verificationAssetId = API:GetVerificationItemID(id) local verificationAssetId = StrafesNET.GetVerificationItemID(id)
local verificationDate = "Not verified" local verificationDate = "Not verified"
if verificationAssetId.errors then if verificationAssetId.errors then
verificationDate = "Failed to fetch" verificationDate = "Failed to fetch"
@ -149,49 +148,50 @@ local function Callback(Interaction, Command, Args)
end end
end end
local badgeRequest = API:GetBadgesAwardedDates(id,Badges) local _, badgeRequest = StrafesNET.GetBadgesAwardedDates(id, Badges)
local badgeData = badgeRequest.data local badgeData = badgeRequest.data
-- local badgesDates = {} -- local badgesDates = {}
local firstBadge,firstBadgeDate = 0,math.huge local firstBadge, firstBadgeDate = 0, math.huge
for _,badge in next,badgeData do for _, badge in next, badgeData do
local badgeId = badge.badgeId local badgeId = badge.badgeId
local awardedDate = tonumber(Date.fromISO(badge.awardedDate):toSeconds()) local awardedDate = tonumber(Date.fromISO(badge.awardedDate):toSeconds())
if firstBadgeDate>awardedDate then if firstBadgeDate > awardedDate then
firstBadge=badgeId firstBadge = badgeId
firstBadgeDate=awardedDate firstBadgeDate = awardedDate
end end
-- badgesDates[badgeId]=awardedDate -- badgesDates[badgeId]=awardedDate
end end
local userThumbnail = API:GetUserThumbnail(id).data[1] local userThumbnailHeaders, userThumbnailBody = StrafesNET.GetUserThumbnail(id)
local userThumbnail = userThumbnailBody.data[1]
local embed = { local embed = {
title = displayName..' (@'..name..')', title = displayName .. ' (@' .. name .. ')',
url = 'https://roblox.com/users/'..id..'/profile', url = 'https://roblox.com/users/' .. id .. '/profile',
thumbnail = { thumbnail = {
url = userThumbnail.imageUrl, url = userThumbnail.imageUrl,
}, },
fields = { fields = {
{name='ID',value=id,inline=true}, { name = 'ID', value = id, inline = true },
{name='Account Age',value=accountAge..' days',inline=true}, { name = 'Account Age', value = accountAge .. ' days', inline = true },
{name='Created',value='<t:'..round(created)..':R>',inline=true}, { name = 'Created', value = '<t:' .. round(created) .. ':R>', inline = true },
{name='Verified Email',value=verificationDate,inline=true}, { name = 'Verified Email', value = verificationDate, inline = true },
{name='Last Online',value='<t:'..round(LastOnline)..':R>',inline=true}, { name = 'Last Online', value = '<t:' .. round(LastOnline) .. ':R>', inline = true },
{name='Last Location',value=LastLocation,inline=true}, { name = 'Last Location', value = LastLocation, inline = true },
{name='Banned',value=isBanned,inline=true}, { name = 'Banned', value = isBanned, inline = true },
{name='Description',value=description,inline=false}, { name = 'Description', value = description, inline = false },
{name='Username History ('..#usernameHistoryTable..(#usernameHistoryTable==50 and '*' or '')..')',value=usernameHistoryString,inline=false}, { name = 'Username History (' .. #usernameHistoryTable .. (#usernameHistoryTable == 50 and '*' or '') .. ')', value = usernameHistoryString, inline = false },
} }
} }
if firstBadge and firstBadgeDate~=math.huge then if firstBadge and firstBadgeDate ~= math.huge then
table.insert(embed.fields,{name='FQG',value=BadgesToName[firstBadge],inline=true}) table.insert(embed.fields, { name = 'FQG', value = BadgesToName[firstBadge], inline = true })
table.insert(embed.fields,{name='Joined',value='<t:'..round(firstBadgeDate)..':R>',inline=true}) table.insert(embed.fields, { name = 'Joined', value = '<t:' .. round(firstBadgeDate) .. ':R>', inline = true })
end end
Interaction:reply({embed=embed}) Interaction:reply({ embed = embed })
end end
return { return {
Command = UserCommand, Command = UserCommand,
Callback = Callback Callback = Callback
} }

View File

@ -5,6 +5,12 @@ local CommandCollector = require('./Modules/CommandCollector.lua')
local Client = Discordia.Client():useApplicationCommands() local Client = Discordia.Client():useApplicationCommands()
Discordia.extensions() Discordia.extensions()
table.clear = function(t)
for k in pairs(t) do
t[k] = nil
end
end
local MessageCommandCollector = CommandCollector.new('Message'):Collect() local MessageCommandCollector = CommandCollector.new('Message'):Collect()
local SlashCommandCollector = CommandCollector.new('Slash'):Collect() local SlashCommandCollector = CommandCollector.new('Slash'):Collect()
local UserCommandCollector = CommandCollector.new('User'):Collect() local UserCommandCollector = CommandCollector.new('User'):Collect()