Switch to LF line endings

This commit is contained in:
archfan 2019-08-29 21:54:08 -04:00 committed by GitHub
parent 054970daa2
commit 813701c5db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 428 additions and 430 deletions

25
API.md
View File

@ -1,13 +1,12 @@
## API ## API
`discordmt` offers a simple API which other mods can use to listen to and send messages with Discord. `discordmt` offers a simple API which other mods can use to listen to and send messages with Discord.
It does not expose the command interface or logins to the API, and `discord.register_on_message` events will *not* recieve login information. It does not expose the command interface or logins to the API, and `discord.register_on_message` events will *not* recieve login information.
### `discord.send(message [, optional id])` ### `discord.send(message)`
Sends `message` to Discord. Sends `message` to Discord.
This function makes an HTTP request; therefore the sending of large volumes of data might be better grouped into a single request. **Do note that Discord limits messages to 2,000 characters, and the relay automatically cuts off messages.** This function makes an HTTP request; therefore the sending of large volumes of data might be better grouped into a single request. **Do note that Discord limits messages to 2,000 characters, and the relay automatically cuts off messages.**
The optional `id` parameter specifies a specific Discord user to send the message to.
### `discord.register_on_message(function(name, message))`
### `discord.register_on_message(function(name, message))` Adds a function to `discord.registered_on_messages`, which are called every time a message is received from Discord, excluding logins. `name` is by default the Discord username of the user who sent the message (excluding the discriminator) and `message` is the message content. This function should be called on startup.
Adds a function to `discord.registered_on_messages`, which are called every time a message is received from Discord, excluding logins. `name` is by default the Discord username of the user who sent the message (excluding the discriminator) and `message` is the message content. This function should be called on startup.

View File

@ -1,21 +1,21 @@
MIT License MIT License
Copyright (c) 2019 archfan7411 Copyright (c) 2019 archfan7411
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

118
README.md
View File

@ -1,59 +1,59 @@
## Minetest-Discord Relay `[discordmt]` ## Minetest-Discord Relay `[discordmt]`
A feature-filled Discord relay for Minetest, supporting: A feature-filled Discord relay for Minetest, supporting:
- Relaying server chat to Discord, and Discord chat to the server - Relaying server chat to Discord, and Discord chat to the server
- Allowing anyone to get the server status via a command - Allowing anyone to get the server status via a command
- Logging into the server from Discord *(configurable)* - Logging into the server from Discord *(configurable)*
- Running commands from Discord *(configurable)* - Running commands from Discord *(configurable)*
- A simple API - A simple API
## Great! How do I use it? ## Great! How do I use it?
Easy! `discordmt` works by running a Python program which converses with a serverside mod using HTTP requests. Don't have or want Python? No problem! Binaries are also available. Easy! `discordmt` works by running a Python program which converses with a serverside mod using HTTP requests. Don't have or want Python? No problem! Binaries are also available.
If you want to run the source, however, Python 3.6.3+, `aiohttp` 3.5+ and `discord.py` 1.2.0+ are required. If you want to run the source, however, Python 3.6.3+, `aiohttp` 3.5+ and `discord.py` 1.2.0+ are required.
### Basic setup ### Basic setup
1. Download the mod and binary executable (or the source code and its dependencies.) 1. Download the mod and binary executable (or the source code and its dependencies.)
2. Create an application at the [Discord Developer Dashboard](https://discordapp.com/developers/applications/) and enable it as a bot (in the Bot tab.) 2. Create an application at the [Discord Developer Dashboard](https://discordapp.com/developers/applications/) and enable it as a bot (in the Bot tab.)
3. Copy the token from your newly-created bot, and use it to finish setting up `relay.conf`. 3. Copy the token from your newly-created bot, and use it to finish setting up `relay.conf`.
Example `relay.conf`: *(The token shown below has been regenerated)* Example `relay.conf`: *(The token shown below has been regenerated)*
``` ```
[BOT] [BOT]
token = NjEwODk0MDU4ODY4NzAzMjMz.XVL5dA.8j8d2XN8_5UwRheG91P2XksYDoM token = NjEwODk0MDU4ODY4NzAzMjMz.XVL5dA.8j8d2XN8_5UwRheG91P2XksYDoM
command_prefix = ! command_prefix = !
[RELAY] [RELAY]
port = 8080 port = 8080
channel_id = 576585506658189332 channel_id = 576585506658189332
allow_logins = true allow_logins = true
clean_invites = true clean_invites = true
use_nicknames = true use_nicknames = true
``` ```
4. Set `discord.port` in your `minetest.conf` to match the port you used in `relay.conf`. You may also set `discord.text_color` to a hex color string if you'd like to color relayed messages from Discord. 4. Set `discord.port` in your `minetest.conf` to match the port you used in `relay.conf`. You may also set `discord.text_color` to a hex color string if you'd like to color relayed messages from Discord.
Example `minetest.conf` excerpt: Example `minetest.conf` excerpt:
``` ```
discord.port = 8080 discord.port = 8080
discord.text_color = #a7a7a7 discord.text_color = #a7a7a7
``` ```
*(Side note: The port must be set in both `relay.conf` and `minetest.conf` because users may decide to run the relay in a different location than the mod, or to run multiple relays/servers at once.)* *(Side note: The port must be set in both `relay.conf` and `minetest.conf` because users may decide to run the relay in a different location than the mod, or to run multiple relays/servers at once.)*
5. Run the relay and, when you're ready, the Minetest server. The relay may be left up even when the server goes down, or may run continuously between several server restarts, for maximum convenience. 5. Run the relay and, when you're ready, the Minetest server. The relay may be left up even when the server goes down, or may run continuously between several server restarts, for maximum convenience.
## Frequently Asked Questions ## Frequently Asked Questions
**Q: I just want a normal relay. Can I disable logins?** **Q: I just want a normal relay. Can I disable logins?**
A: Yep! Just set `allow_logins = false` in `relay.conf`. A: Yep! Just set `allow_logins = false` in `relay.conf`.
**Q: Do I need to re-login after a server restart, like with the IRC mod?** **Q: Do I need to re-login after a server restart, like with the IRC mod?**
A: Nope, logins persist as long as the relay is up. A: Nope, logins persist as long as the relay is up.
**Q: I'm getting an HTTP error - it says the server can't be found?** **Q: I'm getting an HTTP error - it says the server can't be found?**
A: Make sure the relay is running and that you've configured the correct port in both `minetest.conf` and `relay.conf`. A: Make sure the relay is running and that you've configured the correct port in both `minetest.conf` and `relay.conf`.

280
init.lua
View File

@ -1,140 +1,140 @@
local http = minetest.request_http_api() local http = minetest.request_http_api()
local settings = minetest.settings local settings = minetest.settings
local port = settings:get('discord.port') or 8080 local port = settings:get('discord.port') or 8080
local timeout = 10 local timeout = 10
discord = {} discord = {}
discord.text_colorization = settings:get('discord.text_color') or '#ffffff' discord.text_colorization = settings:get('discord.text_color') or '#ffffff'
discord.registered_on_messages = {} discord.registered_on_messages = {}
discord.register_on_message = function(func) discord.register_on_message = function(func)
table.insert(discord.registered_on_messages, func) table.insert(discord.registered_on_messages, func)
end end
local old_chat_send_all = minetest.chat_send_all local old_chat_send_all = minetest.chat_send_all
discord.handle_response = function(response) discord.handle_response = function(response)
local data = response.data local data = response.data
if data == '' or data == nil then if data == '' or data == nil then
return return
end end
local data = minetest.parse_json(response.data) local data = minetest.parse_json(response.data)
if not data then if not data then
return return
end end
if data.messages then if data.messages then
for _, message in pairs(data.messages) do for _, message in pairs(data.messages) do
for _, func in pairs(discord.registered_on_messages) do for _, func in pairs(discord.registered_on_messages) do
func(message.author, message.content) func(message.author, message.content)
end end
local msg = ('<%s@Discord> %s'):format(message.author, message.content) local msg = ('<%s@Discord> %s'):format(message.author, message.content)
old_chat_send_all(minetest.colorize(discord.text_colorization, msg)) old_chat_send_all(minetest.colorize(discord.text_colorization, msg))
minetest.log('[Discord] Message: '..msg) minetest.log('[Discord] Message: '..msg)
end end
end end
if data.commands then if data.commands then
local commands = minetest.registered_chatcommands local commands = minetest.registered_chatcommands
for _, v in pairs(data.commands) do for _, v in pairs(data.commands) do
if commands[v.command] then if commands[v.command] then
if minetest.get_ban_description(v.name) ~= '' then if minetest.get_ban_description(v.name) ~= '' then
discord.send('You cannot run commands because you are banned.', v.context or nil) discord.send('You cannot run commands because you are banned.', v.context or nil)
return return
end end
-- Check player privileges -- Check player privileges
local required_privs = commands[v.command].privs or {} local required_privs = commands[v.command].privs or {}
local player_privs = minetest.get_player_privs(v.name) local player_privs = minetest.get_player_privs(v.name)
for priv, value in pairs(required_privs) do for priv, value in pairs(required_privs) do
if player_privs[priv] ~= value then if player_privs[priv] ~= value then
discord.send('Insufficient privileges.', v.context or nil) discord.send('Insufficient privileges.', v.context or nil)
return return
end end
end end
local old_chat_send_player = minetest.chat_send_player local old_chat_send_player = minetest.chat_send_player
minetest.chat_send_player = function(name, message) minetest.chat_send_player = function(name, message)
old_chat_send_player(name, message) old_chat_send_player(name, message)
if name == v.name then if name == v.name then
discord.send(message, v.context or nil) discord.send(message, v.context or nil)
end end
end end
success, ret_val = commands[v.command].func(v.name, v.params or '') success, ret_val = commands[v.command].func(v.name, v.params or '')
if ret_val then if ret_val then
discord.send(ret_val, v.context or nil) discord.send(ret_val, v.context or nil)
end end
minetest.chat_send_player = old_chat_send_player minetest.chat_send_player = old_chat_send_player
else else
discord.send(('Command not found: `%s`'):format(v.command), v.context or nil) discord.send(('Command not found: `%s`'):format(v.command), v.context or nil)
end end
end end
end end
if data.logins then if data.logins then
local auth = minetest.get_auth_handler() local auth = minetest.get_auth_handler()
for _, v in pairs(data.logins) do for _, v in pairs(data.logins) do
local authdata = auth.get_auth(v.username) local authdata = auth.get_auth(v.username)
local result = false local result = false
if authdata then if authdata then
result = minetest.check_password_entry(v.username, authdata.password, v.password) result = minetest.check_password_entry(v.username, authdata.password, v.password)
end end
local request = { local request = {
type = 'DISCORD_LOGIN_RESULT', type = 'DISCORD_LOGIN_RESULT',
user_id = v.user_id, user_id = v.user_id,
username = v.username, username = v.username,
success = result success = result
} }
http.fetch({ http.fetch({
url = 'localhost:'..tostring(port), url = 'localhost:'..tostring(port),
timeout = timeout, timeout = timeout,
post_data = minetest.write_json(request) post_data = minetest.write_json(request)
}, discord.handle_response) }, discord.handle_response)
end end
end end
end end
discord.send = function(message, id) discord.send = function(message, id)
local data = { local data = {
type = 'DISCORD-RELAY-MESSAGE', type = 'DISCORD-RELAY-MESSAGE',
content = minetest.strip_colors(message) content = minetest.strip_colors(message)
} }
if id then if id then
data['context'] = id data['context'] = id
end end
http.fetch({ http.fetch({
url = 'localhost:'..tostring(port), url = 'localhost:'..tostring(port),
timeout = timeout, timeout = timeout,
post_data = minetest.write_json(data) post_data = minetest.write_json(data)
}, function(_) end) }, function(_) end)
end end
minetest.chat_send_all = function(message) minetest.chat_send_all = function(message)
old_chat_send_all(message) old_chat_send_all(message)
discord.send(message) discord.send(message)
end end
minetest.register_on_chat_message(function(name, message) minetest.register_on_chat_message(function(name, message)
discord.send(('<%s> %s'):format(name, message)) discord.send(('<%s> %s'):format(name, message))
end) end)
local timer = 0 local timer = 0
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)
if dtime then if dtime then
timer = timer + dtime timer = timer + dtime
if timer > 0.2 then if timer > 0.2 then
http.fetch({ http.fetch({
url = 'localhost:'..tostring(port), url = 'localhost:'..tostring(port),
timeout = timeout, timeout = timeout,
post_data = minetest.write_json({ post_data = minetest.write_json({
type = 'DISCORD-REQUEST-DATA' type = 'DISCORD-REQUEST-DATA'
}) })
}, discord.handle_response) }, discord.handle_response)
timer = 0 timer = 0
end end
end end
end) end)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
discord.send('*** Server shutting down...') discord.send('*** Server shutting down...')
end) end)
discord.send('*** Server started!') discord.send('*** Server started!')

View File

@ -1,9 +1,9 @@
[BOT] [BOT]
token = <Discord bot token goes here> token = <Discord bot token goes here>
command_prefix = ! command_prefix = !
[RELAY] [RELAY]
port = 8080 port = 8080
channel_id = <Discord channel ID goes here> channel_id = <Discord channel ID goes here>
allow_logins = true allow_logins = true
clean_invites = true clean_invites = true
use_nicknames = true use_nicknames = true

377
server.py
View File

@ -1,189 +1,188 @@
#!/usr/bin/env python3 import sys
import sys from aiohttp import web
from aiohttp import web import aiohttp
import aiohttp import discord
import discord from discord.ext import commands
from discord.ext import commands import asyncio
import asyncio import json
import json import time
import time import configparser
import configparser
config = configparser.ConfigParser()
config = configparser.ConfigParser()
config.read('relay.conf')
config.read('relay.conf')
class Queue():
class Queue(): def __init__(self):
def __init__(self): self.queue = []
self.queue = [] def add(self, item):
def add(self, item): self.queue.append(item)
self.queue.append(item) def get(self):
def get(self): if len(self.queue) >=1:
if len(self.queue) >=1: item = self.queue[0]
item = self.queue[0] del self.queue[0]
del self.queue[0] return item
return item else:
else: return None
return None def get_all(self):
def get_all(self): items = self.queue
items = self.queue self.queue = []
self.queue = [] return items
return items def isEmpty(self):
def isEmpty(self): return len(self.queue) == 0
return len(self.queue) == 0
def clean_invites(string):
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 not ('discordapp.com/invite' in word)])
outgoing_msgs = Queue()
outgoing_msgs = Queue() command_queue = Queue()
command_queue = Queue() login_queue = Queue()
login_queue = Queue()
prefix = config['BOT']['command_prefix']
prefix = config['BOT']['command_prefix']
bot = commands.Bot(command_prefix=prefix, help_command=None)
bot = commands.Bot(command_prefix=prefix, help_command=None)
channel_id = int(config['RELAY']['channel_id'])
channel_id = int(config['RELAY']['channel_id'])
connected = False
connected = False
port = int(config['RELAY']['port'])
port = int(config['RELAY']['port']) token = config['BOT']['token']
token = config['BOT']['token'] logins_allowed = True if config['RELAY']['allow_logins'] == 'true' else False
logins_allowed = True if config['RELAY']['allow_logins'] == 'true' else False do_clean_invites = True if config['RELAY']['clean_invites'] == '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
do_use_nicknames = True if config['RELAY']['use_nicknames'] == 'true' else False
last_request = 0
last_request = 0
channel = None
channel = None authenticated_users = {}
authenticated_users = {}
def check_timeout():
def check_timeout(): return time.time() - last_request <= 1
return time.time() - last_request <= 1
async def handle(request):
async def handle(request): global last_request
global last_request last_request = time.time()
last_request = time.time() text = await request.text()
text = await request.text() try:
try: data = json.loads(text)
data = json.loads(text) if data['type'] == 'DISCORD-RELAY-MESSAGE':
if data['type'] == 'DISCORD-RELAY-MESSAGE': msg = discord.utils.escape_mentions(data['content'])[0:2000]
msg = discord.utils.escape_mentions(data['content'])[0:2000] if 'context' in data.keys():
if 'context' in data.keys(): id = int(data['context'])
id = int(data['context']) user = bot.get_user(id)
user = bot.get_user(id) if user is not None:
if user is not None: await user.send(msg)
await user.send(msg) else:
else: print('unyay')
print('unyay') else:
else: await channel.send(msg)
await channel.send(msg) return web.Response(text = 'Acknowledged') # discord.send should NOT block extensively on the Lua side
return web.Response(text = 'Acknowledged') # discord.send should NOT block extensively on the Lua side if data['type'] == 'DISCORD_LOGIN_RESULT':
if data['type'] == 'DISCORD_LOGIN_RESULT': user = bot.get_user(int(data['user_id']))
user = bot.get_user(int(data['user_id'])) if user is not None:
if user is not None: if data['success'] is True:
if data['success'] is True: authenticated_users[int(data['user_id'])] = data['username']
authenticated_users[int(data['user_id'])] = data['username'] await user.send('Login successful.')
await user.send('Login successful.') else:
else: await user.send('Login failed.')
await user.send('Login failed.') except:
except: pass
pass response = json.dumps({
response = json.dumps({ 'messages' : outgoing_msgs.get_all(),
'messages' : outgoing_msgs.get_all(), 'commands' : command_queue.get_all(),
'commands' : command_queue.get_all(), 'logins' : login_queue.get_all()
'logins' : login_queue.get_all() })
}) return web.Response(text = response)
return web.Response(text = response)
app = web.Application()
app = web.Application() app.add_routes([web.get('/', handle),
app.add_routes([web.get('/', handle), web.post('/', handle)])
web.post('/', handle)])
@bot.event
@bot.event async def on_ready():
async def on_ready(): global connected
global connected if not connected:
if not connected: connected = True
connected = True global channel
global channel channel = await bot.fetch_channel(channel_id)
channel = await bot.fetch_channel(channel_id)
@bot.event
@bot.event async def on_message(message):
async def on_message(message): global outgoing_msgs
global outgoing_msgs if check_timeout():
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 = {
msg = { 'author': message.author.name if not do_use_nicknames else message.author.display_name,
'author': message.author.name if not do_use_nicknames else message.author.display_name, 'content': message.content.replace('\n', '/')
'content': message.content.replace('\n', '/') }
} if do_clean_invites:
if do_clean_invites: msg['content'] = clean_invites(msg['content'])
msg['content'] = clean_invites(msg['content']) if msg['content'] != '':
if msg['content'] != '': outgoing_msgs.add(msg)
outgoing_msgs.add(msg)
await bot.process_commands(message)
await bot.process_commands(message)
@bot.command(help='Runs an ingame command from Discord.')
@bot.command(help='Runs an ingame command from Discord.') async def cmd(ctx, command, *, args=''):
async def cmd(ctx, command, *, args=''): 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
return if ctx.author.id not in authenticated_users.keys():
if ctx.author.id not in authenticated_users.keys(): await ctx.send('Not logged in.')
await ctx.send('Not logged in.') return
return command = {
command = { 'name': authenticated_users[ctx.author.id],
'name': authenticated_users[ctx.author.id], 'command': command,
'command': command, 'params': args.replace('\n', '')
'params': args.replace('\n', '') }
} if ctx.guild is None:
if ctx.guild is None: command['context'] = str(ctx.author.id)
command['context'] = str(ctx.author.id) command_queue.add(command)
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=''):
async def login(ctx, username, password=''): if not check_timeout() or not logins_allowed:
if not check_timeout() or not logins_allowed: return
return if ctx.guild is not None:
if ctx.guild is not None: await ctx.send(ctx.author.mention+' You\'ve quite possibly just leaked your password; 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; it is advised that you change it at once.\n*This message will be automatically deleted*', delete_after = 10) return
return login_queue.add({
login_queue.add({ 'username' : username,
'username' : username, 'password' : password,
'password' : password, 'user_id' : str(ctx.author.id)
'user_id' : str(ctx.author.id) })
})
@bot.command(help='Lists connected players and server information.')
@bot.command(help='Lists connected players and server information.') async def status(ctx, *, args=None):
async def status(ctx, *, args=None): if not check_timeout():
if not check_timeout(): return
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
return data = {
data = { 'name': 'discord_relay',
'name': 'discord_relay', 'command': 'status',
'command': 'status', 'params': '',
'params': '', }
} if ctx.guild is None:
if ctx.guild is None: data['context'] = str(ctx.author.id)
data['context'] = str(ctx.author.id) command_queue.add(data)
command_queue.add(data)
async def runServer():
async def runServer(): runner = web.AppRunner(app)
runner = web.AppRunner(app) await runner.setup()
await runner.setup() site = web.TCPSite(runner, 'localhost', port)
site = web.TCPSite(runner, 'localhost', port) await site.start()
await site.start()
async def runBot():
async def runBot(): await bot.login(token)
await bot.login(token) await bot.connect()
await bot.connect()
try:
try: print('='*37+'\nStarting relay. Press Ctrl-C to exit.\n'+'='*37)
print('='*37+'\nStarting relay. Press Ctrl-C to exit.\n'+'='*37) loop = asyncio.get_event_loop()
loop = asyncio.get_event_loop() futures = asyncio.gather(runBot(), runServer())
futures = asyncio.gather(runBot(), runServer()) loop.run_until_complete(futures)
loop.run_until_complete(futures)
except (KeyboardInterrupt, SystemExit):
except (KeyboardInterrupt, SystemExit): sys.exit()
sys.exit()

View File

@ -1,2 +1,2 @@
discord.port (Port to run the relay on.) int 8080 discord.port (Port to run the relay on.) int 8080
discord.text_color (Custom color for relayed messages.) string #ffffff discord.text_color (Custom color for relayed messages.) string #ffffff