Easy cross-server & Discord synced chat with Okwo


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

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 =generatetoken, bot will PM you two tokens, that you will need to use when connecting with guild. If you feel like they aren’t safe and want to regen them just use =generatetoken yes.
Note: Currently you have to be guild creator to run this command!

Bot's response


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)
local Connection = Okwo.ConnectClient("TOKEN ID", "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. CreateQueueConnection loads up module to manage the discord commands. It’s argument is the interval server asks for commands. Default is 5 seconds, but we will use 1 for this tutorial.

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") 

local QueueConnection = Connection.CreateQueueConnection(1)

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
	return newtab

Now we can do clearly bot things. Let’s begin with binding just action, when player writes message.
command is full object server gets. Later we automatically have it splitted with module for commandName - name of command (=g (COMMAND)), suffix - connected args (arg1 arg2 …), args - table of all arguments.

QueueConnection.AddGlobalCommand(function(command, commandName, suffix, args)
	if command.channel.id == "CHANNEL ID" then

AddGlobalCommand allows us to fetch us every message, not only specific command ( =g * instead of =g (SPECIFIC COMMAND) ).
CallShared will let us communicate with other servers to send message to others. We will name the action CHAT so it will be easier to recognize it. Let’s retreive it now. Like in the Command example, there are 4 arguments - command - full request object, commandName - how we recognize the request (CHAT in this case), suffix - connected arguments into string, args - table of arguments. We can also do so only commands from specific channel work by inserting channel id into the if condition. You can get the id, by right-click on channel and clicking Copy channel id button. If it’s not available, check if you have developer mode on in settings.

QueueConnection.AddShared("CHAT", function(command, commandName, suffix, args)
	local message = HttpService:JSONDecode(suffix)
	if message.author.discriminator == "RBLX" and Players:FindFirstChild(message.author.username) then return end
	for _, Player in pairs(Players:GetPlayers()) do
		local newmessage = DeepCopy(message)
		newmessage.author.username = TextService:FilterStringAsync(newmessage.author.username, Player.UserId):GetNonChatStringForBroadcastAsync()
		newmessage.content = TextService:FilterStringAsync(newmessage.content, Player.UserId):GetNonChatStringForBroadcastAsync()
		Event:FireClient(Player, newmessage)

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.

		msg = TextService:FilterStringAsync(msg, Player.UserId):GetNonChatStringForBroadcastAsync()
		local username = TextService:FilterStringAsync(Player.Name, Player.UserId):GetNonChatStringForBroadcastAsync()
		Connection.SendMessage("CHANNEL ID", "**"..username.."**: "..msg)
		QueueConnection.CallShared("CHAT",HttpService:JSONEncode({author = {username = username, discriminator = "RBLX"}, member = {}, content = msg}))

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")

	game.StarterGui:SetCore("ChatMakeSystemMessage", {
		Text = "[" .. message.author.username .. "#" .. message.author.discriminator .. "]: "..message.content;
		Color = message.member.admin and Color3.new(1,0,0) or nil

I’ve also added there functionality, that message color is red if person has ADMINISTRATOR permission at the Discord server.

This is my first tutorial so I’d appreciate your feedbacks. You can also post what you’d like to see in future updates of bot.
You can also join our community to stay updated!


EDIT for @Kampfkarren: We do use a module called SERVER. For clarification - it doesn’t load up into your game until you run .ConnectServer() and it bring’s limited functions for only our usage (such as client verification). You can feel safe as this person checked it all already!

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!

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.



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.


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



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