Easy cross-server & Discord synced chat with Okwo


#1

What is Okwo?
Okwo is a Roblox-Discord bot that allows you to connect your Discord guild with game! Currently we brought one feature to show it off!
Link to the completed project

Why is this efficient?
We base on our own features, so we are sure that they are safe. also we do not choose the exact commands or functionalities for you, so you can make them by yourself. We also don’t base only on Discord.

Beginning
First thing, what we should do is configuring our guild and bot.

Inviting bot
To invite bot you should use this link. After that, bot will be added to our guild.

Configuring guild
What we should do after inviting is making a role for our bot, granting proper permissions (reading channels and sending messages for this tutorial) and generate our tokens - codes that will keep our game safe.

If you type =token 0, you will be prompted to enter the token name and bot will PM your personal access token, that you will need to use when connecting with guild.
Note: Currently you have to be guild creator to run this command!

Bot's response

image

Configuring in-game connection
To configure in-game connection with bot services, you can use our module, which will always stay updated for bots’ possibilities. After requiring module, we need to auth so we should type:

local Okwo = require(2021571149) -- INITIALIZING OKWO 
local Client = Okwo.init("Client", token)

Scripting cross-server chat
In this step we should begin in creating event that will allow us to communicate with client in order to send messages. We create RemoteEvent then. Also we will get the service and table deep copy function to do filtering.

local token = "TOKEN"
local channelId = "CHANNEL ID HERE"
local interval = 1

-- SCRIPT
local Okwo = require(2021571149) -- INITIALIZING OKWO 
local Client = Okwo.init("Client", token)

local Event = Instance.new("RemoteEvent", game.ReplicatedStorage)
Event.Name = "DiscordChat"

local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local TextService = game:GetService("TextService") 

Now we can do the bot commands. Let’s begin with binding just action, when player writes message - setCommandEvent.

  • args - table of all arguments.
  • command - command object name of the used command (=g (COMMAND)),
  • date - timestamp, when command was sent
local token = "TOKEN"
local channelId = "CHANNEL ID HERE"
local interval = 1

-- SCRIPT
local Okwo = require(2021571149) -- INITIALIZING OKWO 
local Client = Okwo.init("Client", token)

local Event = Instance.new("RemoteEvent", game.ReplicatedStorage)
Event.Name = "DiscordChat"

local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local TextService = game:GetService("TextService") 

if channelId ~= nil then
	Client:setCommandEvent(interval, nil, function(args, command, date)
		Client:sendShared("CHAT", command)
	end, channelId)
end

setCommandEvent lets us fetch every message, not only specific command ( =g * instead of =g (SPECIFIC COMMAND) - You can also set a custom prefix to the command using =commandprefix so you’ll be able to do !(SOME COMMAND) ).

sendShared will let us communicate with other servers and send data to others. We will name the action CHAT so it will be easier to recognize it. Let’s retreive it now. There are 4 arguments:

  • interval - time between checks (it syncs with the commands),
  • sharedName - how we recognize the request (CHAT in this case),
  • data - data sent via sendShared
local token = "TOKEN"
local channelId = "CHANNEL ID HERE"
local interval = 1

-- SCRIPT
local Okwo = require(2021571149) -- INITIALIZING OKWO 
local Client = Okwo.init("Client", token)

local Event = Instance.new("RemoteEvent", game.ReplicatedStorage)
Event.Name = "DiscordChat"

local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local TextService = game:GetService("TextService") 

if channelId ~= nil then
	Client:setCommandEvent(interval, nil, function(args, command, date)
		Client:sendShared("CHAT", command)
	end, channelId)
end

function DeepCopy(tab)
	local newtab = {}
	for i, v in pairs(tab) do
		if type(i) == "table" then i = DeepCopy(i) end
		if type(v) == "table" then v = DeepCopy(v) end
		newtab[i] = v
	end
	return newtab
end

Client:setSharedEvent(interval, "CHAT", function(name, data)
	if data.author.discriminator == "RBLX" and Players:FindFirstChild(data.author.username) then return end
	for _, Player in pairs(Players:GetPlayers()) do
		local newMessage = DeepCopy(data)
		newMessage.author.username = TextService:FilterStringAsync(newMessage.author.username, Player.UserId):GetNonChatStringForBroadcastAsync()
		newMessage.content = TextService:FilterStringAsync(newMessage.content, Player.UserId):GetNonChatStringForBroadcastAsync()
		Event:FireClient(Player, newMessage)
	end
end)

AddShared allows us to bind action, when server receives the Shared request. The message was Encoded into string from JSON, before so firstly we need to Decode it. After that we have function that will check is message sent from discord or game (BELOW IS PART, WHERE WE ADD SENDING FROM GAME (CROSS-SERVER)), if from game then if the sender is in server, we prevent duplicating message.
After doing that we copy the message for every player, filter discord’s name, message and send it then with our event.

Now let’s add Cross-Server chat. We should bind to the Chatted event then. Let’s do this.

local token = "TOKEN"
local channelId = "CHANNEL ID HERE"
local interval = 1

-- SCRIPT
local Okwo = require(2021571149) -- INITIALIZING OKWO 
local Client = Okwo.init("Client", token)

local Event = Instance.new("RemoteEvent", game.ReplicatedStorage)
Event.Name = "DiscordChat"

local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local TextService = game:GetService("TextService") 

if channelId ~= nil then
	Client:setCommandEvent(interval, nil, function(args, command, date)
		Client:sendShared("CHAT", command)
	end, channelId)
end

function DeepCopy(tab)
	local newtab = {}
	for i, v in pairs(tab) do
		if type(i) == "table" then i = DeepCopy(i) end
		if type(v) == "table" then v = DeepCopy(v) end
		newtab[i] = v
	end
	return newtab
end

Client:setSharedEvent(interval, "CHAT", function(name, data)
	if data.author.discriminator == "RBLX" and Players:FindFirstChild(data.author.username) then return end
	for _, Player in pairs(Players:GetPlayers()) do
		local newMessage = DeepCopy(data)
		newMessage.author.username = TextService:FilterStringAsync(newMessage.author.username, Player.UserId):GetNonChatStringForBroadcastAsync()
		newMessage.content = TextService:FilterStringAsync(newMessage.content, Player.UserId):GetNonChatStringForBroadcastAsync()
		Event:FireClient(Player, newMessage)
	end
end)

game.Players.PlayerAdded:Connect(function(Player)
	Player.Chatted:Connect(function(msg)
		msg = TextService:FilterStringAsync(msg, Player.UserId):GetNonChatStringForBroadcastAsync()
		local username = TextService:FilterStringAsync(Player.Name, Player.UserId):GetNonChatStringForBroadcastAsync()
		Client:sendShared("CHAT", {author = {username = username, discriminator = "RBLX"}, member = {}, content = msg})
		if channelId ~= nil then 
			Client:sendMessage(channelId, "**"..username.."**: "..msg)
		end
	end)
end)

After binding we need to filter both message and username (as outgoing resources). We can send the message to Discord, to make Discord community able to read in-game messages. Next step is obviously sending Shared request. We select the “CHAT” command then and send Encoded table containing our sender username, tag (I used RBLX to let people know that person is in other server. You can change it or remove though) and message.

Last step - receiving events on Client Side
We need to listen for the event we have created and we will send the messages with :SetCore() function.

local event = game.ReplicatedStorage:WaitForChild("DiscordChat")

event.OnClientEvent:Connect(function(message)
	game.StarterGui:SetCore("ChatMakeSystemMessage", {
		Text = "[" .. message.author.username .. "#" .. message.author.discriminator .. "]: "..message.content;
	})
end)

PS: You can set the channelId to nil to disable Discord functionality.

You can also join our community to stay updated!

Join our Community Discord to also participate in early access program of the newest features - such as Roblox group management right now.

Cheers!

The place you are linked for, doesn’t work and will not to prevent spam and leaking tokens! Please be also sure to check and prevent people for example from abusing @here and @everyone!


Okwo looking for developers!
#4

Cross server isnt held on discord servers. About the connection with Discord. It is safe, efficient and doesnt overweight Discord as bot works like every bot (like tatsumaki etc), has it’s own limits and holds 99% of things on it’s own server.

Trust me. That’s really well-made. You can join our Discord to ask more precise questions.

Cheers!


#5

I assume you are either using webhooks or your own server to send requests from in-game-discord?

Also by the way Tatsumaki bot doesn’t stress Discord servers as it wouldn’t have been allowed to run. The only real way you can mess up is by adding a lot of listeners for your bot, which use a lot of memory and cause memory leaks. You guys probably didn’t add many since it’s a small bot.


#6

Own server and not that small as you probably think! :wink: Also trust me, no memory leaks there. We are running good!

EDIT: For those who doubt in our potential.

Our current usage, note that we will expand easily if we will have bigger

CPU


RAM

Funny thing also is that purely the bot takes only 1.7% - 3% of RAM and 1%-2% of CPU.


#7

Updated the tutorial to comply with the new methods and it is working again!


#8

You’re requiring a module. Is it open source? If not, You may want to take a look at this update because it will impact you.


#9

Yes, it is perfectly perfect. :smile: It is a project that exists long time, so don’t worry about mistakes. We are really professional with what we are doing.

The source: https://www.roblox.com/library/2021571149/Okwo-Connection-Module (is noted in the post)

Thanks for noticing that anyways! :smiley:


#10

This looks very cool! :smile: I’ve seen some similar concepts for other games (e.g. Minecraft) but I haven’t seen something like this on Roblox before. How fast is it when you’re limited to http requests rather than websockets?


#11

Our system queues the requests and mass-executes them every 1 second. Prevents DDoS and unheavies the limitting system. We also separate all the guilds into shards (different processes or even servers).

We are also working on to provide the best support with singleton functions and others. Commands and shareds are actually executed together so.
If you are doing requests in the background, we don’t see a need in executing them directly after receiving, that’s why we are querying them. That might change in the future to faster time, but that should not affect game functionality (who would send a request every bullet shoot? :thinking:)
image
As Ive said. They should never exceed 1 second.


#12

What’s the point in having something like this? If you had thousands of players chatting at once then surely you wouldn’t be able to communicate in-game properly.


#13

If you had thousands of players in the game, it would not be very cool, but that’s one of many possibilities. An example usage. And there are more of them to come.