Skip to content

Commit

Permalink
Use discord.py v2 and add send_every_3s setting
Browse files Browse the repository at this point in the history
  • Loading branch information
luk3yx authored and archfan7411 committed Nov 1, 2022
1 parent 5b23959 commit 00e8d5c
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 102 deletions.
25 changes: 16 additions & 9 deletions init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ discord.registered_on_messages = {}

local irc_enabled = minetest.get_modpath("irc")

discord.register_on_message = function(func)
function discord.register_on_message(func)
table.insert(discord.registered_on_messages, func)
end
end

discord.chat_send_all = minetest.chat_send_all

discord.handle_response = function(response)
-- Allow the chat message format to be customised by other mods
function discord.format_chat_message(name, msg)
return ('<%s@Discord> %s'):format(name, msg)
end

function discord.handle_response(response)
local data = response.data
if data == '' or data == nil then
return
Expand All @@ -32,7 +37,7 @@ discord.handle_response = function(response)
for _, func in pairs(discord.registered_on_messages) do
func(message.author, message.content)
end
local msg = ('<%s@Discord> %s'):format(message.author, message.content)
local msg = discord.format_chat_message(message.author, message.content)
discord.chat_send_all(minetest.colorize(discord.text_colorization, msg))
if irc_enabled then
irc.say(msg)
Expand Down Expand Up @@ -97,7 +102,7 @@ discord.handle_response = function(response)
end
end

discord.send = function(message, id)
function discord.send(message, id)
local data = {
type = 'DISCORD-RELAY-MESSAGE',
content = minetest.strip_colors(message)
Expand All @@ -112,13 +117,15 @@ discord.send = function(message, id)
}, function(_) end)
end

minetest.chat_send_all = function(message)
function minetest.chat_send_all(message)
discord.chat_send_all(message)
discord.send(message)
end

minetest.register_on_chat_message(function(name, message)
discord.send(('<%s> %s'):format(name, message))
-- Register the chat message callback after other mods load so that anything
-- that overrides chat will work correctly
minetest.after(0, minetest.register_on_chat_message, function(name, message)
discord.send(minetest.format_chat_message(name, message))
end)

local timer = 0
Expand All @@ -144,7 +151,7 @@ end)

if irc_enabled then
discord.old_irc_sendLocal = irc.sendLocal
irc.sendLocal = function(msg)
function irc.sendLocal(msg)
discord.old_irc_sendLocal(msg)
discord.send(msg)
end
Expand Down
5 changes: 4 additions & 1 deletion relay.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ port = 8080
channel_id = <Discord channel ID goes here>
allow_logins = true
clean_invites = true
use_nicknames = true
use_nicknames = true

# Uncomment to send messages to Discord every 3 seconds (for busy servers)
# send_every_3s = true
204 changes: 113 additions & 91 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,152 +5,149 @@
import discord
from discord.ext import commands
import asyncio
import collections
import json
import time
import configparser
import re
import traceback

config = configparser.ConfigParser()

config.read('relay.conf')

class Queue():

class Queue:
def __init__(self):
self.queue = []

def add(self, item):
self.queue.append(item)

def get(self):
if len(self.queue) >=1:
item = self.queue[0]
del self.queue[0]
return item
else:
return None
if len(self.queue) >= 1:
return self.queue.pop(0)

def get_all(self):
items = self.queue
self.queue = []
return items

def isEmpty(self):
return len(self.queue) == 0


def clean_invites(string):
return ' '.join([word for word in string.split() if not ('discord.gg' in word) and not ('discordapp.com/invite' in word)])
return ' '.join(word for word in string.split()
if not ('discord.gg' in word) and
'discordapp.com/invite' not in word)


outgoing_msgs = Queue()
command_queue = Queue()
login_queue = Queue()

prefix = config['BOT']['command_prefix']

bot = commands.Bot(command_prefix=prefix)
bot = commands.Bot(
command_prefix=prefix,
intents=discord.Intents(messages=True, message_content=True),
)

channel_id = int(config['RELAY']['channel_id'])

connected = False

port = int(config['RELAY']['port'])
token = config['BOT']['token']
logins_allowed = True if config['RELAY']['allow_logins'] == 'true' else False
do_clean_invites = True if config['RELAY']['clean_invites'] == 'true' else False
do_use_nicknames = True if config['RELAY']['use_nicknames'] == 'true' else False
logins_allowed = config['RELAY'].getboolean('allow_logins')
do_clean_invites = config['RELAY'].getboolean('clean_invites')
do_use_nicknames = config['RELAY'].getboolean('use_nicknames')
if config['RELAY'].getboolean('send_every_3s'):
incoming_msgs = collections.deque()
else:
incoming_msgs = None

last_request = 0

channel = None
channel = bot.get_partial_messageable(channel_id)
authenticated_users = {}


def check_timeout():
return time.time() - last_request <= 1

async def get_or_fetch_channel(id):
target_channel = bot.get_channel(id)
if target_channel is None:
target_channel = await bot.fetch_channel(id)
if target_channel is None:
print(f'Failed to fetch channel {id!r}.')

return target_channel

async def get_or_fetch_user(user_id):
user = bot.get_user(user_id)
if user is None:
user = await bot.fetch_user(user_id)
if user is None:
print(f'Failed to fetch user {user_id!r}.')
translation_re = re.compile(r'\x1b(T|F|E|\(T@[^\)]*\))')

return user

async def handle(request):
global last_request
last_request = time.time()
text = await request.text()
try:
data = json.loads(text)
data = await request.json()
if data['type'] == 'DISCORD-RELAY-MESSAGE':
msg = discord.utils.escape_mentions(data['content'])[0:2000]
r = re.compile(r'\x1b(T|F|E|\(T@[^\)]*\))')
msg = r.sub('', msg)
if 'context' in data.keys():
msg = translation_re.sub('', data['content'])
msg = discord.utils.escape_mentions(msg)[:2000]
if 'context' in data:
id = int(data['context'])
target_channel = await get_or_fetch_channel(id)
if target_channel is not None:
await target_channel.send(msg)
else:
target_channel = bot.get_partial_messageable(id)
await target_channel.send(msg)
elif incoming_msgs is None:
await channel.send(msg)
return web.Response(text = 'Acknowledged') # discord.send should NOT block extensively on the Lua side
else:
incoming_msgs.append(msg)

# discord.send should NOT block extensively on the Lua side
return web.Response(text='Acknowledged')
if data['type'] == 'DISCORD_LOGIN_RESULT':
user_id = int(data['user_id'])
user = await get_or_fetch_user(user_id)
if user is not None:
if data['success'] is True:
authenticated_users[user_id] = data['username']
await user.send('Login successful.')
else:
await user.send('Login failed.')
except:
pass
user = bot.get_partial_messageable(user_id)
if data['success'] is True:
authenticated_users[user_id] = data['username']
await user.send('Login successful.')
else:
await user.send('Login failed.')
except Exception:
traceback.print_exc()

response = json.dumps({
'messages' : outgoing_msgs.get_all(),
'commands' : command_queue.get_all(),
'logins' : login_queue.get_all()
'messages': outgoing_msgs.get_all(),
'commands': command_queue.get_all(),
'logins': login_queue.get_all()
})
return web.Response(text = response)
return web.Response(text=response)


app = web.Application()
app.add_routes([web.get('/', handle),
web.post('/', handle)])

@bot.event
async def on_ready():
global connected
if not connected:
connected = True
global channel
channel = await bot.fetch_channel(channel_id)

@bot.event
async def on_message(message):
global outgoing_msgs
if check_timeout():
if (message.channel.id == channel_id) and (message.author.id != bot.user.id):
if (message.channel.id == channel_id and
message.author.id != bot.user.id):
msg = {
'author': message.author.name if not do_use_nicknames else message.author.display_name,
'author': (message.author.display_name
if do_use_nicknames else message.author.name),
'content': message.content.replace('\n', '/')
}
if do_clean_invites:
msg['content'] = clean_invites(msg['content'])
if msg['content'] != '':
outgoing_msgs.add(msg)

await bot.process_commands(message)


@bot.command(help='Runs an ingame command from Discord.')
async def cmd(ctx, command, *, args=''):
if not check_timeout():
await ctx.send("The server currently appears to be down.")
return
if ((ctx.channel.id != channel_id) and ctx.guild is not None) or not logins_allowed:
if ((ctx.channel.id != channel_id and ctx.guild is not None) or
not logins_allowed):
return
if ctx.author.id not in authenticated_users.keys():
await ctx.send('Not logged in.')
Expand All @@ -163,32 +160,43 @@ async def cmd(ctx, command, *, args=''):
if ctx.guild is None:
command['context'] = str(ctx.author.id)
command_queue.add(command)

@bot.command(help='Logs into your ingame account from Discord so you can run commands. You should only run this command in DMs with the bot.')


@bot.command(help='Logs into your ingame account from Discord so you can run '
'commands. You should only run this command in DMs with the '
'bot.')
async def login(ctx, username, password=''):
if not logins_allowed:
return
if ctx.guild is not None:
await ctx.send(ctx.author.mention+' You\'ve quite possibly just leaked your password by using this command outside of DMs; it is advised that you change it at once.\n*This message will be automatically deleted.*', delete_after = 10)
await ctx.send(ctx.author.mention + ' You\'ve quite possibly just '
'leaked your password by using this command outside of '
'DMs; it is advised that you change it at once.\n*This '
'message will be automatically deleted.*',
delete_after=10)
try:
await ctx.message.delete()
except:
print(f"Unable to delete possible password leak by user ID {ctx.author.id} due to insufficient permissions.")
except discord.errros.Forbidden:
print(f"Unable to delete possible password leak by user ID "
f"{ctx.author.id} due to insufficient permissions.")
return
login_queue.add({
'username' : username,
'password' : password,
'user_id' : str(ctx.author.id)
'username': username,
'password': password,
'user_id': str(ctx.author.id)
})
if not check_timeout():
await ctx.send("The server currently appears to be down, but your login attempt has been added to the queue and will be executed as soon as the server returns.")
await ctx.send("The server currently appears to be down, but your "
"login attempt has been added to the queue and will be "
"executed as soon as the server returns.")


@bot.command(help='Lists connected players and server information.')
async def status(ctx, *, args=None):
if not check_timeout():
await ctx.send("The server currently appears to be down.")
return
if ((ctx.channel.id != channel_id) and ctx.guild is not None):
if ctx.channel.id != channel_id and ctx.guild is not None:
return
data = {
'name': 'discord_relay',
Expand All @@ -199,21 +207,35 @@ async def status(ctx, *, args=None):
data['context'] = str(ctx.author.id)
command_queue.add(data)

async def runServer():
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, 'localhost', port)
await site.start()

async def runBot():
await bot.login(token)
await bot.connect()
async def send_messages():
while True:
await asyncio.sleep(3)
if channel is None or not incoming_msgs:
continue

to_send = []
msglen = 0
while incoming_msgs and msglen + len(incoming_msgs[0]) < 1999:
msg = incoming_msgs.popleft()
to_send.append(msg)
msglen += len(msg) + 1

try:
print('='*37+'\nStarting relay. Press Ctrl-C to exit.\n'+'='*37)
loop = asyncio.get_event_loop()
futures = asyncio.gather(runBot(), runServer())
loop.run_until_complete(futures)
await channel.send('\n'.join(to_send))

except (KeyboardInterrupt, SystemExit):
sys.exit()

async def on_startup(app):
asyncio.create_task(bot.start(token))
if incoming_msgs is not None:
asyncio.create_task(send_messages())


app.on_startup.append(on_startup)


if __name__ == '__main__':
try:
print('='*37+'\nStarting relay. Press Ctrl-C to exit.\n'+'='*37)
web.run_app(app, host='localhost', port=port)
except KeyboardInterrupt:
pass
Loading

0 comments on commit 00e8d5c

Please sign in to comment.