Compare commits

...

31 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
5eb9f82bfe Refactor HTTP Request module 2025-06-23 23:58:21 -04:00
708a5364a9 Formatting 2025-06-23 23:58:21 -04:00
328a9ed5b7 Merge pull request 'Improvements to the user command' (#13) from 9382/tommy-bot:main into main
Reviewed-on: #13
Reviewed-by: tommy <tommy@noreply-gitea@tommyy.dev>
2025-05-19 22:57:49 +00:00
cc9b12c858 More data points 2025-05-18 11:17:30 +01:00
dce3e81219 Do interpolation beyond the dataset bounds (🤢)
Still not sure this is a good idea, but it means there's less need to update the IDToDate dataset
2025-05-18 11:13:06 +01:00
c0ffa71f39 Implement support for the Verification Sign item 2025-05-18 11:12:02 +01:00
bf3778c802 New timestamp 2025-03-13 18:16:34 -04:00
837d601f57 surely this deals with the issue 2025-01-18 16:01:20 -05:00
0e0a9dde16 Officially remove the usage of GetUserOnlineStatus
Roblox just removed the endpoint with no warning
2025-01-18 15:10:13 -05:00
b28c49f342 yikes 2024-09-27 18:00:42 -04:00
f12f2a8c4e test it i guess 2024-09-27 17:58:24 -04:00
9dd1b5e92c oh is this it 2024-09-27 17:54:40 -04:00
0777a6bf11 hold on what 2024-09-27 17:52:22 -04:00
48384ef229 this caused a crash somehow lol 2024-09-27 17:49:28 -04:00
fe49284843 BOOM 2024-09-27 17:47:38 -04:00
6d8c4355a8 what 2024-09-27 17:47:38 -04:00
280f690e6a linux binary I guess 2024-09-27 17:47:38 -04:00
13 changed files with 644 additions and 506 deletions

4
.gitignore vendored
View File

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

BIN
exes/lit Executable file

Binary file not shown.

BIN
exes/luvi Executable file

Binary file not shown.

BIN
exes/luvit Executable file

Binary file not shown.

View File

@ -30,7 +30,7 @@ function CommandCollector:Collect()
local CommandsContainerPath = self.Prefix..'Commands/' local CommandsContainerPath = self.Prefix..'Commands/'
for File in io.popen('dir "./src/'..CommandsContainerPath..'" /b'):lines() do for File in io.popen('ls ./src/'..CommandsContainerPath):lines() do
if File:sub(1, 1) ~= IGNORE_STARTING_FILE_NAME then if File:sub(1, 1) ~= IGNORE_STARTING_FILE_NAME then
local Success, Return = pcall(require, RELATIVE_PATH_TO_COMMANDS..CommandsContainerPath..File) local Success, Return = pcall(require, RELATIVE_PATH_TO_COMMANDS..CommandsContainerPath..File)
if Success then if Success then

123
src/Modules/HttpRequest.lua Normal file
View File

@ -0,0 +1,123 @@
local Http = require('coro-http')
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 METHODS = {
GET = true,
POST = true
}
local function QueryParams(Params) -- {Name = Value, ...}
if not Params then return "" end
local QueryString = "?"
for ParamName, ParamValue in next, Params do
if ParamValue ~= nil then
QueryString = QueryString .. tostring(ParamName) .. "=" .. tostring(ParamValue) .. "&"
end
end
return string.sub(QueryString, 1, -2) -- Remove last character (will always be a "&")
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] = { tostring(HeaderName), tostring(HeaderValue) }
end
return RequestHeaders
end
local function TryDecodeJson(Body)
local Success, Result = pcall(json.decode, Body)
if not Success then
return Body
end
return Result
end
local function NormalizeHeaders(Response)
for Index, Header in next, Response do
if type(Header) == "table" and #Header == 2 then
local HeaderName, HeaderValue = table.unpack(Header)
Response[HeaderName] = HeaderValue
Response[Index] = nil
end
end
end
local function Request(Method, Url, Params, RequestHeaders, RequestBody, Callback, MaxRetries)
if not METHODS[Method] then
error("[HTTP] Method " .. Method .. " is not supported.")
end
if type(Url) ~= "string" then
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 FormattedHeaders = CreateHeaders(RequestHeaders) -- at worse, this will just be an empty table (which cannot mess up the request)
local RequestUrl = Url .. QueryString
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
return coroutine.wrap(function()
local Headers, DecodedBody = DoRequest()
Callback(Headers, DecodedBody)
end)
else
return DoRequest()
end
end
return {
Request = Request
}

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,40 +0,0 @@
local http = require('coro-http')
local json = require('json')
function wait(n)local c=os.clock local t=c()while c()-t<=n do end;end
--[[
1: method
2: url
3: headers
4: body
5: options]]
local STRAFES_NET_RATELIMIMT = {
HOUR = 3000,
MINUTE = 100,
}
local remaining_timeout = 0
local function request(method,url,headers,body,options)
if type(body)=='table' then body=json.encode(body) end
local headers,body=http.request(method,url,headers,body,options)
local rbody=json.decode(body) or body
local rheaders={}
for _,t in pairs(headers) do
if type(t)=='table' then
rheaders[t[1]]=t[2]
else
rheaders[_]=t
end
end
local remaining = tonumber(rheaders['RateLimit-Remaining'])
local remaining_hour = tonumber(rheaders['X-RateLimit-Remaining-Hour'])
local reset = tonumber(rheaders['RateLimit-Reset'])
local retry_after = tonumber(rheaders['Retry-After'])
if remaining and reset then
local t = remaining==0 and reset or .38
if retry_after then t = retry_after end
wait(t)
end
return rbody,rheaders
end
-- local urlparamencode=function()
return request

View File

@ -1,342 +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 response,headers = http_request('GET', ROBLOX_INVENTORY_API..'users/'..USER_ID.."/items/Asset/102611803", API_HEADER)
return response,headers
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,162 +17,181 @@ 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")},
{3800920136, FromYMD("2016-04-16")}, { 1228821079, FromYMD("2013-02-07") }, -- asomstephano12344 (mass scanning near sign removal date)
{9855616205, FromYMD("2017-04-02")}, { 3800920136, FromYMD("2016-04-16") },
{30361018662, FromYMD("2018-11-14")}, { 9855616205, FromYMD("2017-04-02") },
{32665806459, FromYMD("2019-01-07")}, { 30361018662, FromYMD("2018-11-14") },
{34758058773, FromYMD("2019-02-24")}, { 32665806459, FromYMD("2019-01-07") },
{65918261258, FromYMD("2020-06-05")}, { 34758058773, FromYMD("2019-02-24") },
{171994717435, FromYMD("2023-03-24")}, { 65918261258, FromYMD("2020-06-05") },
{173210319088, FromYMD("2023-04-14")}, { 171994717435, FromYMD("2023-03-24") },
{206368884641, FromYMD("2023-07-16")}, { 173210319088, FromYMD("2023-04-14") },
{229093879745, FromYMD("2024-01-02")}, { 206368884641, FromYMD("2023-07-16") },
{232802028144, FromYMD("2024-04-08")}, { 229093879745, FromYMD("2024-01-02") },
{234886704167, FromYMD("2024-06-28")} { 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 --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(AssetID) local function GuessDateFromAssetID(InstanceID, AssetID)
local note = ""
if AssetID == 1567446 then
note = " (Verification Sign)"
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 < AssetID then if ID < InstanceID then
if not IDToDate[i+1] then if not IDToDate[i + 1] then
return "After "..ToYMD(Time) -- 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
end end
local ParentID, ParentTime = unpack(IDToDate[i+1]) local ParentID, ParentTime = unpack(IDToDate[i + 1])
return "Around "..ToYMD(linterp(Time, ParentTime, (AssetID-ID)/(ParentID-ID))) return "Around " .. ToYMD(linterp(Time, ParentTime, (InstanceID - ID) / (ParentID - ID))) .. note
end end
end end
return "Before "..ToYMD(IDToDate[1][2]) -- 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
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_info) 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 = API:GetUserOnlineStatus(id) or {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 = 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"
elseif verificationAssetId.data[1] then elseif verificationAssetId.data[1] then
verificationDate = GuessDateFromAssetID(verificationAssetId.data[1].instanceId) 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
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()
@ -18,7 +24,7 @@ end)
local function RunCallback(Callback, Interaction, Command, Args) local function RunCallback(Callback, Interaction, Command, Args)
local Success, Return = pcall(Callback, Interaction, Command, Args) local Success, Return = pcall(Callback, Interaction, Command, Args)
if not Success then if not Success then
Interaction:reply('Error encountered when trying to run command: '..Return, true) Interaction:reply('Error encountered when trying to run command: ' .. tostring(Return), true)
end end
end end
@ -43,4 +49,4 @@ Client:on('userCommand', function(Interaction, Command, Member)
end end
end) end)
Client:run('Bot '..Token) Client:run('Bot ' .. Token)