1
0
forked from tommy/tommy-bot

Compare commits

9 Commits
main ... main

8 changed files with 469 additions and 513 deletions

2
.gitignore vendored

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

@ -0,0 +1,88 @@
local Http = require('coro-http')
local HTTPRequest = Http.request
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)
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)
if Callback and type(Callback) == "function" then
return coroutine.wrap(function()
local Headers, Body = HTTPRequest(Method, RequestUrl, FormattedHeaders, RequestBody)
NormalizeHeaders(Headers)
Callback(Headers, TryDecodeJson(Body))
end)
else
local Headers, Body = HTTPRequest(Method, RequestUrl, FormattedHeaders, RequestBody)
NormalizeHeaders(Headers)
return Headers, TryDecodeJson(Body)
end
end
return {
Request = Request
}

251
src/Modules/StrafesNET.lua Normal file

@ -0,0 +1,251 @@
local HttpRequest = require("./HttpRequest.lua")
local Request = HttpRequest.Request
local APIKeys = require("./APIKeys.lua")
local Headers = {
["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'
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",
GET = "time/%d"
},
USERS = {
LIST = "user",
GET = "user/%d",
RANKS = {
GET = "user/%d/rank"
}
}
}
local StrafesNET = {}
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, Headers)
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, Headers)
end
function StrafesNET.ListRanks(GameId, ModeId, StyleId, SortBy, PageSize, PageNumber)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.RANKS.LIST
local Params = {
gameId = GameId,
modeId = ModeId,
styleId = StyleId,
sort_by = SortBy or 1,
page_size = PageSize or 10,
page_number = PageNumber or 1
}
return Request("GET", RequestUrl, Params, Headers)
end
function StrafesNET.ListTimes(UserId, MapId, GameId, ModeId, StyleId, 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 1,
page_size = PageSize or 10,
page_number = PageNumber or 0
}
return Request("GET", RequestUrl, Params, Headers)
end
function StrafesNET.GetTime(TimeId)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.TIMES.GET:format(TimeId)
return Request("GET", RequestUrl, nil, Headers)
end
function StrafesNET.ListUsers(StateId, PageSize, PageNumber)
local RequestUrl = 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, Headers)
end
function StrafesNET.GetUser(UserId)
local RequestUrl = STRAFESNET_API_URL .. STRAFESNET_API_ENDPOINTS.USERS.GET:format(UserId)
return Request("GET", RequestUrl, nil, Headers)
end
function StrafesNET.GetUserRank(UserId, GameId, ModeId, StyleId)
local RequestUrl = 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, Headers)
end
function StrafesNET.GetRobloxInfoFromUserId(USER_ID)
if not USER_ID then return 'empty id' end
return Request("GET", ROBLOX_API_URL .. "users/" .. USER_ID)
end
function StrafesNET.GetRobloxInfoFromUsername(USERNAME)
if not USERNAME then return 'empty username' end
if #USERNAME > 32 then return 'Username too long' end
local headers, body = Request("POST", ROBLOX_API_URL .. "usernames/users", nil,
{ ["Content-Type"] = "application/json" }, { usernames = { USERNAME } })
if not body or not body.data or not body.data[1] then
return 'Username \'' .. USERNAME .. '\' not found.'
end
return StrafesNET.GetRobloxInfoFromUserId(body.data[1].id)
end
function StrafesNET.GetRobloxInfoFromDiscordId(DISCORD_ID)
if not DISCORD_ID then return 'empty id' end
-- table.foreach(DISCORD_ID, print)
local headers, body = Request("GET", FIVEMAN_API_URL .. "users/" .. DISCORD_ID)
if headers.status == "error" then return headers.messages end
return Request("GET", ROBLOX_API_URL .. "users/" .. body.result.robloxId)
end
function StrafesNET.GetUserFromAny(user, message)
local str = user:match('^["\'](.+)[\'"]$')
local num = user:match('^(%d+)$')
if str then
local roblox_user = StrafesNET.GetRobloxInfoFromUsername(str)
if not roblox_user.id then return 'User not found' end
return roblox_user
elseif num then
local roblox_user = StrafesNET.GetRobloxInfoFromUserId(user)
if not roblox_user.id then return 'Invalid user id' end
return roblox_user
elseif user == 'me' then
local me = message.author
local roblox_user = StrafesNET.GetRobloxInfoFromDiscordId(me.id)
if not roblox_user.id then
return
'You are not registered with the fiveman1 api, use !link with the rbhop bot to link your roblox account'
end
return roblox_user
elseif user:match('<@%d+>') then
local user_id = user:match('<@(%d+)>')
local member = message.guild:getMember(user_id)
local roblox_user = StrafesNET.GetRobloxInfoFromDiscordId(member.id)
if not roblox_user.id then
return
'User is not registered with the fiveman1 api, use !link with the rbhop bot to link your roblox account'
end
return roblox_user
else
local roblox_user = StrafesNET.GetRobloxInfoFromUsername(user)
if not roblox_user.id then return 'User not found' end
return roblox_user
end
end
function StrafesNET.GetUserOnlineStatus(USER_ID)
if not USER_ID then return 'empty id' end
local presence = Request("POST", ROBLOX_PRESENCE_URL .. "presence/users", { userIds = { USER_ID } }).userPresences
[1]
local last_online = Request("POST", ROBLOX_PRESENCE_URL .. "presence/last-online", { userIds = { USER_ID } })
.lastOnlineTimestamps[1]
L1Copy(last_online, presence)
return presence
end
function StrafesNET.GetUserUsernameHistory(USER_ID)
if not USER_ID then return 'empty id' end
return Request("GET", ROBLOX_API_URL .. "users/" .. USER_ID .. "/username-history",
{ limit = 50, sortOrder = 'Desc' })
end
function StrafesNET.GetBadgesAwardedDates(USER_ID, BADGE_LIST)
if not USER_ID then return 'empty id' end
return Request("GET", ROBLOX_BADGES_API .. "users/" .. USER_ID .. "/badges/awarded-dates",
{ badgeIds = table.concat(BADGE_LIST, ",") })
end
function StrafesNET.GetVerificationItemID(USER_ID)
if not USER_ID then return 'empty id' end
local headers1, body1 = Request("GET", ROBLOX_INVENTORY_API .. "users/" .. USER_ID .. "/items/Asset/102611803")
if body1.errors then return body1 end
local headers2, body2 = Request("GET", ROBLOX_INVENTORY_API .. "users/" .. USER_ID .. "/items/Asset/1567446")
if body2.errors then return body2 end
local data = {}
if body2.data and body2.data[1] then data[#data + 1] = body2.data[1] end
if body1.data and body1.data[1] then data[#data + 1] = body1.data[1] end
return { data = data }
end
function StrafesNET.GetUserThumbnail(USER_ID, TYPE, SIZE)
if not USER_ID then return 'empty id' end
local _TYPE = ROBLOX_THUMBNAIL_TYPES[TYPE] or "avatar"
local _SIZE = ROBLOX_THUMBNAIL_SIZES[SIZE] or "180x180"
return Request("GET", ROBLOX_THUMBNAIL_URL .. "users/" .. _TYPE,
{ userIds = USER_ID, size = _SIZE, format = "Png", isCircular = false })
end
return StrafesNET

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

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

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

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

@ -16,10 +16,10 @@ Client:on('ready', function()
end)
local function RunCallback(Callback, Interaction, Command, Args)
local Success, Return = pcall(Callback, Interaction, Command, Args)
if not Success then
Interaction:reply('Error encountered when trying to run command: '..tostring(Return), true)
end
local Success, Return = pcall(Callback, Interaction, Command, Args)
if not Success then
Interaction:reply('Error encountered when trying to run command: ' .. tostring(Return), true)
end
end
Client:on('slashCommand', function(Interaction, Command, Args)
@ -43,4 +43,4 @@ Client:on('userCommand', function(Interaction, Command, Member)
end
end)
Client:run('Bot '..Token)
Client:run('Bot ' .. Token)