DisBot - Discord Bots with Roblox [No Webhook]

DisBot Module:

  • Last Update: 30/06/2025

DisBot require this:

  • Crypto Module (made by me too): For encryption and decryption. I made this because I wanted to hide SECRET KEYS, API KEYS, TOKENS, etc. Get the model here

Create a ModuleScript with this code and put it inside ServerStorage

--[[ 🎉 Dev(s)
    The_NetDev (josdev)
	
    Description:
    DisBot allows you to control Discords Bots inside a single or multiple
    servers.
    DisBot use the official Discord API and Discord Documentation and HttpService
    
    Main Functions:
    
    module:Encrypt(text) -- to encrypt/hide your bots TOKEN
    
    module:AddBot(BotName:string, BotId:string, BotToken:string)
    module:AddServer(ServerName:string, ServerId:string)
    module:LoadServerRoles(BotName:string, ServerName:string)
    module:AddChannelToServer(ServerName:string, ChannelName:string, ChannelId:string)
    module:GetDmChannel(BotName:string, DiscordUserId:string)
    
    module:ListenToChannel(BotName:string, ServerName:string, ChannelName:string, callback)
    module:StopChannelListening(ServerName:string, ChannelName:string)
    
    module:GlobalChannelListener(BotName:string, ServerName:string, ChannelName:string, callback)
    module:StopGlobalChannelListener(ServerName:string, ChannelName:string)
    
    module:ListenToDm(BotName:string, DiscordUserId:string, callback)
    module:StopDmListener(BotName:string, DiscordUserId:string)
    
    module:Message(BotName:string, ServerName:string, ChannelName:string, Message:string)
    module:Embed(BotName:string, ServerName:string, ChannelName:string, Embed:{})
    module:ReactToMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string, EmojiId:string)
    module:ReplyMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string, ReplyMessage:string)
    module:EditMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string, Payload:{})
    module:EditEmbed(BotName:string, ServerName:string, ChannelName:string, MessageId:string, EmbedIndex:number, NewEmbed:{})
    module:DeleteMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string)
    module:GetReactions(BotName:string, ServerName:string, ChannelName:string, MessageId:string, EmojiId:string)
    
    module:MessageDM(BotName:string, DiscordUserId:string, Message:string)
    module:EmbedDM(BotName:string, DiscordUserId:string, Embed:{})
    module:ReactToMessageDM(BotName:string, DiscordUserId:string, MessageId:string, EmojiId:string)
    module:ReplyMessageDM(BotName:string, DiscordUserId:string, MessageId:string, ReplyMessage:string)
    module:EditEmbedDM(BotName:string, DiscordUserId:string, MessageId:string, EmbedIndex:number, NewEmbed:{})
    module:EditEmbedDM(BotName:string, DiscordUserId:string, MessageId:string, EmbedIndex:number, NewEmbed:{})
    module:DeleteMessageDM(BotName:string, DiscordUserId:string, MessageId:string)
    module:GetReactionsDM(BotName, DiscordUserId, MessageId, Emoji)
    
    module:KickMember(BotName:string, ServerName:string, DiscordUserId:string)
    module:BanMember(BotName:string, ServerName:string, DiscordUserId:string)
    module:AssignRole(BotName:string, ServerName:string, RoleName:string, DiscordUserId:string)
	module:RemoveRole(BotName:string, ServerName:string, RoleName:string, DiscordUserId:string)
	module:GetRolesAsync(BotName:string, ServerName:string)
	module:GetRoleId(ServerName:string, RoleName:string)
    module:MentionedBot(BotName:string, MessageData:{})
    
]]

local HttpService       = game:GetService("HttpService")
local MessagingService  = game:GetService("MessagingService")

local BASE_URL          = "https://discord.com/api"
local Crypto			= require(86586092742209)

local LISTEN_CHANNEL_DELAY 			= .5
local GLOBAL_LISTEN_CHANNEL_DELAY 	= 1
local LISTEN_DM_DELAY 				= .5

local BOTNAME_REQUIRED     = "BotName is required"
local SERVERNAME_REQUIRED  = "ServerName is required"
local CHANNELNAME_REQUIRED = "ChannelName is required"
local ROLENAME_REQUIRED    = "RoleName is required"
local INVALID_BOT          = "Invalid Bot"
local INVALID_SERVER       = "Invalid Server"
local INVALID_CHANNEL      = "Invalid Server Channel"
local INVALID_USER         = "Invalid Discord User ID"

local module = {}
module._bots            = {}  -- Registered bots: [BotName] = { _token, id }
module._servers         = {}  -- Registered servers: [ServerName] = { id, _channels }
module._globalListeners = {}  -- Global listeners: [channelId] = { listening, lastMessageId, callbacks }
module._dmListeners     = {}  -- [DiscordUserId] = { listening, dmChannelId, lastMessageId, callbacks }
module._dmCache 		= {}  -- [BotName..DiscordUserId] = DmChannelId (Bot <-> User Chat Id)

-- Utility to normalize string keys (currently a placeholder)
local function FixIndex(index, value)
	if typeof(index) == "string" then
		index = string.lower(index)
	end
end

---
-- Performs an HTTP request to the Discord API.
-- Handles 204 No Content responses by returning an empty table.
-- @param BotName (string)     : Name of the registered bot.
-- @param Method (string)      : HTTP method ("GET", "POST", etc.).
-- @param Endpoint (string)    : API endpoint path (e.g. "/channels/.../messages").
-- @param Data (table or nil)  : Lua table to JSON-encode as request body.
-- @return (table or nil)      : Decoded JSON response, or empty table for no content.
function module:_request(BotName, Method, Endpoint, Data)
	local url = BASE_URL .. Endpoint
	
	local headers = {
		["Authorization"] = "Bot " .. self:Decrypt(self._bots[BotName]._token),
		["Content-Type"]  = "application/json",
	}

	local requestParams = {
		Url     = url,
		Method  = Method,
		Headers = headers,
	}

	if Method:upper() ~= "GET" and Data then
		requestParams.Body = HttpService:JSONEncode(Data)
	end

	local response = HttpService:RequestAsync(requestParams)
	-- If Discord returns No Content (204), there's nothing to decode
	if response.StatusCode == 204 or response.Body == "" then
		return {}
	end
	return HttpService:JSONDecode(response.Body)
end

---
-- Recursively applies a function to each key/value in a nested table.
-- @param tbl (table)         : The table to traverse.
-- @param func (function)     : Function to call with (key, value).
function module:_recurseTable(tbl, func)
	for index, value in pairs(tbl) do
		if type(value) == 'table' then
			self:_recurseTable(value, func)
		else
			func(index, value)
		end
	end
end

function module:Encrypt(text)
	return Crypto:encrypt(text)
end

function module:Decrypt(text)
	return Crypto:decrypt(text)
end

---------------------------------------------------------------------------------------------------------------------------------------
-- REGISTER FUNCTIONS (BOTS, SERVERS, CHANNELS)
---------------------------------------------------------------------------------------------------------------------------------------

---
-- Registers a new bot for API requests.
-- @param BotName (string)    : Unique identifier for the bot.
-- @param BotToken (string)   : Discord bot token.
-- @return (nil)
function module:AddBot(BotName:string, BotId:string, BotToken:string)
	assert(BotName, BOTNAME_REQUIRED)
	assert(BotToken, "BotToken is required")
	self._bots[BotName] = { 
		_token = BotToken,
		id = BotId,
	}
end

---
-- Registers a new game server context.
-- @param ServerName (string) : Unique name for this server.
-- @param ServerId (string)   : Arbitrary identifier for grouping.
-- @return (nil)
function module:AddServer(ServerName:string, ServerId:string)
	assert(ServerName, SERVERNAME_REQUIRED)
	assert(ServerId, "ServerId is required")
	self._servers[ServerName] = { 
		id = ServerId, 
		_channels = {} ,
		_roles = {}
	}
end

---
-- Load the server roles from Discord
-- @param BotName (string)    : Registered bot name.
-- @param ServerName (string) : Registered server name.
-- @return (nil)
function module:LoadServerRoles(BotName:string, ServerName:string)
	local Work, Result = pcall(function()
		local Roles = self:GetRolesAsync(BotName, ServerName)
		for _,roleData in pairs(Roles) do
			self._servers[ServerName]._roles[roleData.name] = roleData.id
		end
	end)
	if not Work then
		warn(Result)
	end
	return Result
end

---
-- Associates a Discord channel with a registered server.
-- @param ServerName (string) : Name of the registered server.
-- @param ChannelName (string): Local alias for the channel.
-- @param ChannelId (string)  : Discord channel snowflake.
-- @return (nil)
function module:AddChannelToServer(ServerName:string, ChannelName:string, ChannelId:string)
	assert(ServerName, SERVERNAME_REQUIRED)
	assert(ChannelName, CHANNELNAME_REQUIRED)
	assert(ChannelId, "ChannelId is required")
	if not self._servers[ServerName] then
		warn(string.format("First add the %s server with :AddServer", ServerName))
		return
	end
	self._servers[ServerName]._channels[ChannelName] = {
		id            = ChannelId,
		listening     = false,
		lastMessageId = nil,
		_callbacks    = {},
	}
end

---
-- Registers and/or gets the Direct Message Channel.
-- @param ServerName (string) 	   : Registered bot name.
-- @param DiscordUserId (string)   : Discord User Id.
-- @return string (DmChannelId)
function module:GetDmChannel(BotName:string, DiscordUserId:string)
	local cacheKey = BotName .. DiscordUserId
	if self._dmCache[cacheKey] then
		return self._dmCache[cacheKey]
	end

	local response = self:_request(BotName, "POST", "/users/@me/channels", {
		recipient_id = DiscordUserId
	})

	if response and response.id then
		self._dmCache[cacheKey] = response.id
		return response.id
	end
	return nil
end

---------------------------------------------------------------------------------------------------------------------------------------
-- CHANNEL LISTENER
---------------------------------------------------------------------------------------------------------------------------------------

---
-- Starts a simple per-server poll listener for a Discord channel.
-- @param BotName (string)    : Registered bot name.
-- @param ServerName (string) : Registered server name.
-- @param ChannelName (string): Local channel alias.
-- @param callback (function) : Invoked with the new message object on change.
-- @return (nil)
function module:ListenToChannel(BotName:string, ServerName:string, ChannelName:string, callback)
	assert(self._servers[ServerName], INVALID_SERVER)
	assert(self._servers[ServerName]._channels[ChannelName], INVALID_CHANNEL)

	local channel = self._servers[ServerName]._channels[ChannelName]
	table.insert(channel._callbacks, callback)

	if not channel.listening then
		channel.listening = true
		task.spawn(function()
			local firstRun = true
			while channel.listening do
				local success, messages = pcall(function()
					local endpoint = `/channels/{channel.id}/messages?limit=1`
					return self:_request(BotName, "GET", endpoint)
				end)
				if success and type(messages)=="table" and #messages>0 then
					local latest = messages[1]
					if firstRun then
						channel.lastMessageId = latest.id
						firstRun = false
					elseif latest.id ~= channel.lastMessageId then
						channel.lastMessageId = latest.id
						for _, cb in ipairs(channel._callbacks) do
							task.spawn(cb, latest)
						end
					end
				end
				task.wait(LISTEN_CHANNEL_DELAY)
			end
		end)
	end
end

---
-- Stops the per-server poll listener for a Discord channel.
-- @param ServerName (string) : Registered server name.
-- @param ChannelName (string): Local channel alias.
-- @return (nil)
function module:StopChannelListening(ServerName:string, ChannelName:string)
	assert(self._servers[ServerName], INVALID_SERVER)
	local channel = self._servers[ServerName]._channels[ChannelName]
	assert(channel, INVALID_CHANNEL)
	channel.listening = false
	channel._callbacks = {}
end

---------------------------------------------------------------------------------------------------------------------------------------
-- GLOBAL CHANNEL LISTENER
---------------------------------------------------------------------------------------------------------------------------------------


--- Attempts to claim the “lock” for a global listener on a given Discord channel.
-- Only servers that have already set `listening = true` will respond to the ARE_YOU_LISTENING ping.
-- @param channelId (string) : Discord channel snowflake.
-- @return (boolean)         : True if no other server responded (lock acquired), false otherwise.
function module:_claimGlobalListen(channelId)
	local topic       = "DisBot_GlobalListen_" .. channelId
	local gotResponse = false

	-- Subscribe *before* we publish
	local responder = MessagingService:SubscribeAsync(topic, function(msg)
		if msg.Data == "ARE_YOU_LISTENING" then
			-- Only reply if *we* are already listening (i.e. an existing listener)
			local existing = module._globalListeners[channelId]
			if existing and existing.listening then
				pcall(function()
					MessagingService:PublishAsync(topic, "LISTENING")
				end)
			end
		elseif msg.Data == "LISTENING" then
			gotResponse = true
		end
	end)

	-- Ping the topic
	pcall(function()
		MessagingService:PublishAsync(topic, "ARE_YOU_LISTENING")
	end)

	-- Wait briefly for any real listener to respond
	task.wait(1.5)
	responder:Disconnect()

	return not gotResponse
end

---
-- Starts a coordinated global listener for a Discord channel across all game servers.
-- Only one server will actually poll the channel; all callbacks receive new messages.
-- @param BotName (string)    : Registered bot name.
-- @param ServerName (string) : Registered server name.
-- @param ChannelName (string): Local channel alias.
-- @param callback (function) : Invoked with the new message object on change.
-- @return (nil)
function module:GlobalChannelListener(BotName:string, ServerName:string, ChannelName:string, callback)
	assert(self._bots[BotName], INVALID_BOT)
	assert(self._servers[ServerName], INVALID_SERVER)
	local channel = self._servers[ServerName]._channels[ChannelName]
	assert(channel, INVALID_CHANNEL)

	local channelId = channel.id
	module._globalListeners[channelId] = module._globalListeners[channelId] or { listening=false, lastMessageId=nil, callbacks={} }
	local listener = module._globalListeners[channelId]

	table.insert(listener.callbacks, callback)

	if listener.listening then return end
	if not module:_claimGlobalListen(channelId) then
		warn("Another server is already listening to channel: " .. channelId)
		return
	end

	listener.listening = true
	task.spawn(function()
		local firstRun = true
		while listener.listening do
			local success, messages = pcall(function()
				local endpoint = `/channels/{channelId}/messages?limit=1`
				return self:_request(BotName, "GET", endpoint)
			end)
			if success and type(messages)=="table" and #messages>0 then
				local latest = messages[1]
				if firstRun then
					listener.lastMessageId = latest.id
					firstRun = false
				elseif latest.id ~= listener.lastMessageId then
					listener.lastMessageId = latest.id
					for _, cb in ipairs(listener.callbacks) do
						task.spawn(cb, latest)
					end
				end
			end
			task.wait(GLOBAL_LISTEN_CHANNEL_DELAY)
		end
	end)
end

---
-- Stops the coordinated global listener for a Discord channel.
-- Frees the lock so another server can take over and clears callbacks.
-- @param ServerName (string) : Registered server name.
-- @param ChannelName (string): Local channel alias.
-- @return (nil)
function module:StopGlobalChannelListener(ServerName:string, ChannelName:string)
	local channel = self._servers[ServerName]._channels[ChannelName]
	if not channel then return end

	local channelId = channel.id
	local listener = module._globalListeners[channelId]
	if listener then
		listener.listening = false
		listener.callbacks  = {}
		module._globalListeners[channelId] = nil
	end
end

---------------------------------------------------------------------------------------------------------------------------------------
-- DIRECT MESSAGES LISTENER
---------------------------------------------------------------------------------------------------------------------------------------

--- Fetches (or opens) a DM channel with a user and returns the channel ID.
local function getOrCreateDmChannel(self, BotName:string, DiscordUserId:string)
	-- List DM channels
	local channels = self:_request(BotName, "GET", "/users/@me/channels")
	for _,c in ipairs(channels) do
		if c.recipients and c.recipients[1].id==DiscordUserId then
			return c.id
		end
	end
	-- Create DM channel
	local dm = self:_request(BotName, "POST", "/users/@me/channels", { recipient_id = DiscordUserId })
	return dm.id
end

--- Listens to direct messages from a specific user.
-- @param BotName (string)
-- @param DiscordUserId (string)
-- @param callback (function)
function module:ListenToDm(BotName:string, DiscordUserId:string, callback)
	assert(self._bots[BotName], INVALID_BOT)
	assert(DiscordUserId and DiscordUserId:match("^%d+$"), INVALID_USER)

	module._dmListeners[DiscordUserId] = module._dmListeners[DiscordUserId] or { listening=false, dmChannelId=nil, lastMessageId=nil, callbacks={} }
	local dm = module._dmListeners[DiscordUserId]
	table.insert(dm.callbacks, callback)

	if dm.listening then return end
	dm.listening = true

	task.spawn(function()
		dm.dmChannelId = getOrCreateDmChannel(self, BotName, DiscordUserId)
		local first=true
		while dm.listening do
			local ok,msgs = pcall(function()
				return self:_request(BotName, "GET", "/channels/"..dm.dmChannelId.."/messages?limit=1")
			end)
			if ok and #msgs>0 then
				local m=msgs[1]
				if first then dm.lastMessageId=m.id; first=false
				elseif m.id~=dm.lastMessageId then
					dm.lastMessageId=m.id
					for _,cb in ipairs(dm.callbacks) do task.spawn(cb, m) end
				end
			end
			task.wait(LISTEN_DM_DELAY)
		end
	end)

end

--- Stops listening to direct messages for a specific user.
-- @param BotName (string)
-- @param DiscordUserId (string)
function module:StopDmListener(BotName:string, DiscordUserId:string)
	local dm = self._dmListeners[DiscordUserId]
	if dm then 
		dm.listening=false
		dm.callbacks={} 
		self._dmListeners[DiscordUserId]=nil 
	end
end

---------------------------------------------------------------------------------------------------------------------------------------
-- CHANNEL MESSAGES
---------------------------------------------------------------------------------------------------------------------------------------

function module:Message(BotName:string, ServerName:string, ChannelName:string, Message:string)
	local Worked, Result = pcall(function()
		local endpoint = `/channels/{self._servers[ServerName]._channels[ChannelName].id}/messages`
		return self:_request(BotName, "POST", endpoint, { content = Message })
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:Embed(BotName:string, ServerName:string, ChannelName:string, Embed:{})
	local Worked, Result = pcall(function()
		self:_recurseTable(Embed, FixIndex)
		local endpoint = `/channels/{self._servers[ServerName]._channels[ChannelName].id}/messages`
		return self:_request(BotName, "POST", endpoint, { content = "", embeds = {Embed} })
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:ReactToMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string, EmojiId:string)
	local Worked, Result = pcall(function()
		local endpoint = "/channels/" ..
			self._servers[ServerName]._channels[ChannelName].id ..
			"/messages/" .. MessageId ..
			"/reactions/" .. EmojiId ..
			"/@me"
		return self:_request(BotName, "PUT", endpoint)
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:ReplyMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string, ReplyMessage:string)
	local Worked, Result = pcall(function()
		local endPoint  = `/channels/{self._servers[ServerName]._channels[ChannelName].id}/messages`

		local payload = {
			content = tostring(ReplyMessage),
			message_reference = {
				message_id = MessageId
			}
		}

		return self:_request(BotName, "POST",endPoint, payload)
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:EditMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string, Payload:{})
	local Worked, Result = pcall(function()
		local channel = self._servers[ServerName]._channels[ChannelName]
		local endpoint = `/channels/{channel.id}/messages/{MessageId}`
		return self:_request(BotName, "PATCH", endpoint, Payload)
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:EditEmbed(BotName:string, ServerName:string, ChannelName:string, MessageId:string, EmbedIndex:number, NewEmbed:{})
	local Worked, Result = pcall(function()
		local original = self:_request(BotName, "GET",
			`/channels/{self._servers[ServerName]._channels[ChannelName].id}/messages/{MessageId}`)

		assert(not original or type(original.embeds) ~= "table", "EditEmbed: failed to fetch original embeds")
		original.embeds[EmbedIndex] = NewEmbed

		return self:EditMessage(BotName, ServerName, ChannelName, MessageId, {
			embeds = original.embeds
		})
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:DeleteMessage(BotName:string, ServerName:string, ChannelName:string, MessageId:string)
	local Worked, Result = pcall(function()
		local channel = self._servers[ServerName]._channels[ChannelName]
		local endpoint = `/channels/{channel.id}/messages/{MessageId}`
		return self:_request(BotName, "DELETE", endpoint)
	end)
	return Worked and Result ~= nil
end

function module:GetReactions(BotName:string, ServerName:string, ChannelName:string, MessageId:string, EmojiId:string)
	local Work, Result = pcall(function()
		local channelId = self._servers[ServerName]._channels[ChannelName]

		local encodedEmoji
		if EmojiId:match("^%d+$") then
			encodedEmoji = string.format("custom%%3A%s", EmojiId)
		else
			encodedEmoji = HttpService:UrlEncode(EmojiId)
		end

		local endpoint = string.format(
			"/channels/%s/messages/%s/reactions/%s",
			channelId.id,  
			MessageId,
			encodedEmoji
		)

		return self:_request(BotName, "GET", endpoint)
	end)
	if not Work then
		warn(Result)
	end
	return Result
end


---------------------------------------------------------------------------------------------------------------------------------------
-- DIRECT MESSAGES
---------------------------------------------------------------------------------------------------------------------------------------

function module:MessageDM(BotName:string, DiscordUserId:string, Message:string)
	local Worked, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)
		return self:_request(BotName, "POST", "/channels/"..channelId.."/messages", {
			content = Message
		})
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:EmbedDM(BotName:string, DiscordUserId:string, Embed:{})
	local Worked, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)
		return self:_request(BotName, "POST", "/channels/"..channelId.."/messages", {
			embeds = {Embed}
		})
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:ReactToMessageDM(BotName:string, DiscordUserId:string, MessageId:string, EmojiId:string)
	local Worked, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)
		return self:_request(BotName, "PUT", 
			"/channels/"..channelId.."/messages/"..MessageId.."/reactions/"..EmojiId.."/@me",
			nil
		)
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:ReplyMessageDM(BotName:string, DiscordUserId:string, MessageId:string, ReplyMessage:string)
	local Worked, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)
		return self:_request(BotName, "POST", "/channels/"..channelId.."/messages", {
			message_reference = {message_id = MessageId},
			content = ReplyMessage
		})
	end)
	if not Worked then 
		warn(Result)
	end
	return Result
end

function module:EditMessageDM(BotName:string, DiscordUserId:string, MessageId:string, NewContent:string)
	local Worked, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)
		return self:_request(BotName, "PATCH", 
			"/channels/"..channelId.."/messages/"..MessageId,
			{content = NewContent}
		)
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:EditEmbedDM(BotName:string, DiscordUserId:string, MessageId:string, EmbedIndex:number, NewEmbed:{})
	local Worked, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)
		local message = self:_request(BotName, "GET", "/channels/"..channelId.."/messages/"..MessageId)

		if message and message.embeds then
			message.embeds[EmbedIndex] = NewEmbed
			return self:_request(BotName, "PATCH", 
				"/channels/"..channelId.."/messages/"..MessageId,
				{embeds = message.embeds}
			)
		end
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:DeleteMessageDM(BotName:string, DiscordUserId:string, MessageId:string)
	local Worked, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)
		if not channelId then
			error("Failed to get DM channel")
		end
		local endpoint = `/channels/{channelId}/messages/{MessageId}`
		return self:_request(BotName, "DELETE", endpoint)
	end)
	return Worked and Result ~= nil
end

function module:GetReactionsDM(BotName, DiscordUserId, MessageId, Emoji)
	local Work, Result = pcall(function()
		local channelId = self:GetDmChannel(BotName, DiscordUserId)

		local encodedEmoji
		if Emoji:match("^%d+$") then
			encodedEmoji = string.format("custom%%3A%s", Emoji)
		else
			encodedEmoji = HttpService:UrlEncode(Emoji)
		end

		local endpoint = string.format(
			"/channels/%s/messages/%s/reactions/%s",
			channelId,  
			MessageId,
			encodedEmoji
		)

		return self:_request(BotName, "GET", endpoint)
	end)
	if not Work then
		warn(Result)
	end
	return Result
end


---------------------------------------------------------------------------------------------------------------------------------------
-- SERVER ACTIONS
---------------------------------------------------------------------------------------------------------------------------------------

function module:KickMember(BotName:string, ServerName:string, DiscordUserId:string)
	local Worked,Result = pcall(function()
		local Endpoint = `/guilds/{self._servers[ServerName]}/members/{DiscordUserId}`
		return self:_request(BotName, "DELETE", Endpoint)
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:BanMember(BotName:string, ServerName:string, DiscordUserId:string)
	local Worked,Result = pcall(function()
		local Endpoint = `/guilds/{self._servers[ServerName]}/bans/{DiscordUserId}`
		return self:_request(BotName, "PUT", Endpoint)
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:AssignRole(BotName:string, ServerName:string, RoleName:string, DiscordUserId:string)
	local Worked, Result = pcall(function()
		local role = self._servers[ServerName]._roles[RoleName]
		if not role then
			warn("Role not registered:", RoleName)
			return false
		end
		local endpoint = `/guilds/{self._servers[ServerName].id}/members/{DiscordUserId}/roles/{role}` 
		return self:_request(BotName, "PUT", endpoint)
	end)
	if not Worked then
		warn(Result)
		return false
	end
	return Result
end

function module:RemoveRole(BotName:string, ServerName:string, RoleName:string, DiscordUserId:string)
	local Worked, Result = pcall(function()
		local role = self._servers[ServerName]._roles[RoleName]
		if not role then
			warn("Role not registered:", RoleName)
			return false
		end

		local endpoint = `/guilds/{self._servers[ServerName].id}/members/{DiscordUserId}/roles/{role}` 
		return self:_request(BotName, "DELETE", endpoint)
	end)
	if not Worked then
		warn(Result)
		return false
	end
	return Result
end

function module:GetRolesAsync(BotName:string, ServerName:string)
	local Worked, Result = pcall(function()
		local endpoint = `/guilds/{self._servers[ServerName].id}/roles`
		return self:_request(BotName, "GET", endpoint) or {}
	end)
	if not Worked then
		warn(Result)
	end
	return Result
end

function module:GetRoleId(ServerName:string, RoleName:string)
	local Worked, Result = pcall(function()
		if self._servers[ServerName] and self._servers[ServerName]._roles[RoleName] then
			return self._servers[ServerName]._roles[RoleName]
		end
		return nil
	end)
	if not Worked then
		warn(Result)
		return nil
	end
	return Result
end

function module:MentionedBot(BotName:string, MessageData:{})
	assert(BotName, BOTNAME_REQUIRED)
	assert(MessageData, "MessageData is required")
	assert(MessageData.mentions, "MessageData.mentions is required")
	assert(self._bots[BotName], INVALID_BOT)
	
	for _,mentionData in pairs(MessageData.mentions) do
		if self._bots[BotName].id == mentionData.id then
			return true
		end
	end
	
	return false
end

return module

19 Likes

Thanks for making this! I was looking for resources to help me.

1 Like

I didn’t know you could go this far with luau and discord bot, thought this was a generic webbook.

1 Like

Woah, are we getting discord.luau? :eyes: Although since your module needs to be required, I’m concerned over where the bot token goes. But other than that, this has blown my mind. Why not use up some extra roblox resources for your discord bot while the game is running :laughing:

You can import the module from your inventory if you want to pin the module and avoid any unwanted updates.

1 Like

It is better to use the id of the module (for the moment), because the module is equivalent to a bot, so if for example you wanted to use some of these functions with more than one Discord Bot it is better to require the id of the module than to add the same module more at once just for using these functions with more than one bot, I’m going to update this so that with one module you can use these functions to more than one Discord Bot. (And also because you would get the most updated version of the module, that would mean access to more features to use your Discord Bot through Roblox Studio)

There is no purpose for automatic feature updates if users will not be using the features or implementing them immediately; therefore, users would not be heavily impacted by importing the module at its current state rather than requiring it by id. If a user has the module in his/her inventory, that would make manually updating much easier. When dealing with sensitive information like a bot token, it is much better to import the module because you will know where your bot token is going.

1 Like

Would much rather download the module and require that instead. It would be unsafe for developers to require a black box and send a confidential API key through it.

3 Likes

This looks really good, hidden gem. One thing I would add is that when the disbot:Message() / disbot:Embed() is fired it returns the message data, so that reactions or replys can be added on to the message sent. :smile:

there is a function to reply and react to a message however i will keep that in mind

right, but you need the message id. which can be found in the data returned from :postAsync() or :requestAsync()

I recommend uploading your code to GitHub so others can contribute to the module.

Uploading to GitHub allows for an easy way to suggest ideas and submit changes.

1 Like

A few types I made, this would make it easier to for example make embeds inside of roblox.
Though honestly I wasn’t quite sure what they meant with the type “snowflake”, nor did I know how to add an int type nor ISO8601.

type embed = {
	["title"]: string?,
	["type"]: "rich" | "image" | "video" | "gifv" | "article" | "link",
	["description"]: string?,
	["url"]: string?,
	["timestamp"]: any?, -- ISO8601 
	["color"]: any?, -- INT
	["footer"]: {
		["text"]: string,
		["icon_url"]: string?,
		["proxy_icon_url"]: string?
	}?,
	["image"]: {
		["url"]: string,
		["proxy_url"]: string?,
		["height"]: number?, -- INT
		["width"]: number? -- INT
	}?,
	["thumbnail"]: {
		["url"]: string,
		["proxy_url"]: string?,
		["height"]: number?, -- INT
		["width"]: number? -- INT
	}?,
	["video"]: {
		["url"]: string?,
		["proxy_url"]: string?,
		["height"]: number?, -- INT
		["width"]: number? -- INT
	}?,
	["provider"]: {
		["name"]: string?,
		["url"]: string?
	}?,
	["author"]: {
		["name"]: string?,
		["url"]: string?,
		["icon_url"]: string?,
		["proxy_icon_url"]: string?
	}?,
	["fields"]: {[number]: {
		["name"]: string,
		["value"]: string,
		["inline"]: boolean?
	}}?
}
type activity = {
	["name"]: string,
	["type"]: number, -- INT, 0: Playing {name}, 1: Streaming {details} (https://twitch.tv/ or https://youtube.com/),
	-- 2: Listening to {name}, 3: Watching {name}, 4: {emoji} {name}, 5: Competing in {name}
	["url"]: string?, -- type = 1
	["created_at"]: number, -- UNIX, INT
	["timestamps"]: {
		["start"]: number?, 
		["end"]: number?
	}?,
	["application_id"]: any?,
	["details"]: string?,
	["state"]: string?,
	["emoji"]: {
		["name"]: string, 
		["id"]: any?, 
		["animated"]: boolean?
	}?,
	["party"]: {
		["id"]: string?,
		["size"]: {
			["current_size"]: number, 
			["max_size"]: number
		}?
	}?,
	["assets"]: {
		["large_image"]: string?, 
		["large_text"]: string?, 
		["small_image"]: string?,
		["small_text"]: string?
	},
	["secrets"]: {
		["join"]: string?,
		["spectate"]: string?,
		["match"]: string?
	}?,
	["instance"]: boolean,
	["flags"]: number, -- INT
	["buttons"]: {[number]: {
		["label"]: string,
		["url"]: string
	}?}
}
type presence = { -- Gateway update precense
	["since"]: number, -- INT
	["activities"]: {[number]: activity?},
	["status"]: "online" | "dnd" | "idle" | "invisible" | "offline",
	["afk"]: boolean
}

For example, by using:

local myEmbed: embed = {...}

it’ll give you suggestions on what should be added.

2 Likes

Sorry for bumping. But is it possible to make ephemeral messages with DisBot?

1 Like

Im pretty sure it does violate TOS.
You are essentially hosting a discord bot on roblox servers…

1 Like

No, that claim isn’t accurate. Roblox explicitly allows you to use HttpService to call external APIs (including Discord’s) from within your game (as long as you’ve turned on Allow HTTP Requests in your game’s settings) You’re not “hosting” a Discord bot on Roblox servers, you’re merely sending HTTP requests out.

I’d have to find out, but I think so

could this be used to send multiple formatted strings from an array on roblox to a discord thread?

1 Like