949 lines
53 KiB
Python
949 lines
53 KiB
Python
import discord
|
||
from discord.ext.commands import Cog
|
||
import logging
|
||
from datetime import datetime
|
||
import asyncio
|
||
import typing
|
||
from geeksbot.imports import arcon
|
||
from geeksbot.imports import utils
|
||
from geeksbot.imports import checks
|
||
|
||
rcon_log = logging.getLogger('rcon')
|
||
|
||
|
||
class Rcon(Cog):
|
||
def __init__(self, bot):
|
||
self.bot = bot
|
||
|
||
async def connect_rcon_server(self, *, name: str, ip: str, port: int, password: str,
|
||
single_packet: bool = False, force: bool = False, monitor_chat: bool = False,
|
||
server_chat_channel: int = None, server_messages_channel: int = None) \
|
||
-> typing.Union[int, str]:
|
||
|
||
if name.replace('_', ' ').title() in self.bot.connected_ark_servers.keys() \
|
||
and isinstance(self.bot.connected_ark_servers[name], arcon.ARKServer) \
|
||
and self.bot.connected_ark_servers[name].writer \
|
||
and not force:
|
||
rcon_log.info(f"{name.replace('_', ' ').title()} is already connected.")
|
||
return 1
|
||
|
||
server = arcon.ARKServer(host=ip, port=port, password=password, single_packet=single_packet,
|
||
monitor_chat=monitor_chat, server_chat_channel=server_chat_channel,
|
||
server_messages_channel=server_messages_channel)
|
||
rcon_log.info(f'{name.replace("_", " ").title()} configured.')
|
||
connected = await server.connect()
|
||
if connected == 1:
|
||
rcon_log.info(f'{name.replace("_", " ").title()} connected.')
|
||
self.bot.connected_ark_servers[name.replace('_', ' ').title()] = server
|
||
return 1
|
||
elif connected == -1:
|
||
rcon_log.info(f'{name.replace("_", " ").title()} timeout error.')
|
||
return 'Timeout Error'
|
||
elif connected == 0:
|
||
rcon_log.info(f'{name.replace("_", " ").title()} auth error.')
|
||
return 'Authentication Failed'
|
||
else:
|
||
rcon_log.info(f'{name.replace("_", " ").title()} unknown error.')
|
||
return 'Unknown Error'
|
||
|
||
async def get_rcon_server_by_name(self, *, guild_config: dict, name: str) \
|
||
-> typing.Union[dict, arcon.ARKServer, None]:
|
||
guild_servers = guild_config.get('rcon_connections')
|
||
if guild_servers:
|
||
if name == '*':
|
||
server_list = {}
|
||
for server, info in guild_servers.items():
|
||
if server == name.replace('_', ' ').title():
|
||
if server not in self.bot.connected_ark_servers.keys():
|
||
connection_result = await self.connect_rcon_server(
|
||
name=server,
|
||
ip=info['ip'],
|
||
port=info['port'],
|
||
password=info['password'],
|
||
monitor_chat=info.get('monitoring_chat', None),
|
||
server_chat_channel=info.get('game_chat_chan_id', None),
|
||
server_messages_channel=info.get('msg_chan_id', None)
|
||
)
|
||
if connection_result != 1:
|
||
return None
|
||
return self.bot.connected_ark_servers[server]
|
||
elif name == '*':
|
||
if server not in self.bot.connected_ark_servers.keys():
|
||
connection_result = await self.connect_rcon_server(
|
||
name=server,
|
||
ip=info['ip'],
|
||
port=info['port'],
|
||
password=info['password'],
|
||
monitor_chat=info.get('monitoring_chat', None),
|
||
server_chat_channel=info.get('game_chat_chan_id', None),
|
||
server_messages_channel=info.get('msg_chan_id', None)
|
||
)
|
||
if connection_result != 1:
|
||
# noinspection PyUnboundLocalVariable
|
||
server_list[server] = f'Cannot connect to the {server} server.\n{connection_result}'
|
||
try:
|
||
# noinspection PyUnboundLocalVariable
|
||
server_list[server] = self.bot.connected_ark_servers[server]
|
||
except KeyError:
|
||
rcon_log.warning(server_list[server])
|
||
del server_list[server]
|
||
else:
|
||
if name == '*':
|
||
return server_list
|
||
return None
|
||
else:
|
||
return None
|
||
|
||
@staticmethod
|
||
async def admin(bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||
player = msg.split(' ||| ')[1].split(' (')[0]
|
||
rcon_log.info(f'{player} requested admin assistance')
|
||
admin_roles = guild_config.get('admin_roles')
|
||
if admin_roles:
|
||
for role in admin_roles:
|
||
msg = '{0} {1}'.format(msg, discord.utils.get(guild.roles, id=admin_roles[role]).mention)
|
||
await server_con.server_chat_to_player_name(player, 'GeeksBot: Admin Geeks have been notified you need '
|
||
'assistance. Please be patient.')
|
||
return msg
|
||
|
||
async def dinowipe(self, bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||
player = msg.split(' ||| ')[1].split(' (')[0]
|
||
steam_ref = self.bot.fs_db.collection(f'users').where('steam_name', '==', player)
|
||
user_info = await self.bot.loop.run_in_executor(self.bot.tpe, steam_ref.get)
|
||
steamid = None
|
||
user = None
|
||
for user in user_info:
|
||
if user:
|
||
steamid = user.to_dict().get('steam_id')
|
||
break
|
||
if steamid:
|
||
if not self.bot.dino_wipe_request.get(server_name):
|
||
if user:
|
||
player = await patron.Patron.from_id(bot, steamid, discord_id=int(user.id))
|
||
rcon_log.info(f'{player.steam_name} requested a wild dino wipe')
|
||
member = guild.get_member(player.discord_id)
|
||
self.bot.loop.create_task(self.request_dinowipe(member, server_name, server_con))
|
||
else:
|
||
await server_con.server_chat_to_player_name(player,
|
||
'Sorry, an error has occurred please try again.')
|
||
else:
|
||
await server_con.server_chat_to_player_name(player, 'A Wild Dino Wipe request is already in progress.')
|
||
else:
|
||
await server_con.server_chat_to_player_name(player, 'Sorry. You are not registered to run commands via the '
|
||
'in-game chat. Please send a chat message in-game '
|
||
'$register steamid=your_steam_id and follow the '
|
||
'instructions so I can link your character with your '
|
||
'Discord account. Thanks')
|
||
return msg
|
||
|
||
@staticmethod
|
||
async def delaywipe(bot, guild: discord.Guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||
player = msg.split(' ||| ')[1].split(' (')[0]
|
||
if bot.dino_wipe_request.get(server_name):
|
||
bot.dino_wipe_request[server_name] = False
|
||
await server_con.broadcast(f'Wild Dino Wipe has been delayed by {player}. '
|
||
f'Please watch chat for timing updates')
|
||
elif bot.dino_wipe_request.get(server_name) is None:
|
||
await server_con.server_chat_to_player_name(player, 'There are no Wild Dino Wipes pending.')
|
||
else:
|
||
await server_con.server_chat_to_player_name(player, 'The dino wipe has already been delayed.')
|
||
return msg
|
||
|
||
@staticmethod
|
||
async def register(bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||
player = msg.split(' ||| ')[1].split(' (')[0]
|
||
steamid = msg.split(' ||| ')[1].split('steamid=')[1].split(' ')[0].replace('`', '').replace("'", '').strip()
|
||
rcon_log.info(f'{player} - {steamid}')
|
||
try:
|
||
int(steamid)
|
||
except ValueError:
|
||
await server_con.server_chat_to_player_name(player, 'That is not a valid SteamID')
|
||
return msg
|
||
else:
|
||
identifier = randint(10000, 99999)
|
||
prefix = guild_config.get("prefixes", bot.default_prefix)
|
||
await server_con.server_chat_to_player_name(player,
|
||
'Your SteamID has been noted, to finish registering '
|
||
'please run the following command in the discord '
|
||
f'channel. You will not be able to run commands '
|
||
f'in-game until your registration is completed... '
|
||
f'{prefix[0] if isinstance(prefix, list) else prefix}'
|
||
f'register {identifier}')
|
||
bot.pending_registrations[identifier] = (player, steamid)
|
||
return msg
|
||
|
||
@commands.command(name='register')
|
||
async def register_discord_account(self, ctx, identifier: int=None):
|
||
user_ref = self.bot.fs_db.document(f'users/{ctx.author.id}')
|
||
user_info = (await self.bot.loop.run_in_executor(self.bot.tpe, user_ref.get)).to_dict()
|
||
if user_info.get('steam_id') and user_info.get('steam_name'):
|
||
await ctx.send('You are already registered to run commands in-game')
|
||
return
|
||
|
||
if identifier not in self.bot.pending_registrations.keys():
|
||
await ctx.send('That identifier is not in the pending registrations')
|
||
return
|
||
|
||
steam_info = self.bot.pending_registrations[identifier]
|
||
|
||
await self.bot.loop.run_in_executor(self.bot.tpe, user_ref.update, {
|
||
'steam_name': steam_info[0],
|
||
'steam_id': steam_info[1]
|
||
})
|
||
await ctx.send('Registration complete. You are now authorized to run commands in-game on the ARK servers.')
|
||
del self.bot.pending_registrations[identifier]
|
||
|
||
async def create_server_chat_chan(self, guild: discord.Guild, server_name: str,
|
||
server_con: arcon.ARKServer, guild_config: dict):
|
||
rcon_log.info(f'Creating channel for {server_name}')
|
||
category = discord.utils.get(guild.categories, name='Server Chats')
|
||
if category is None:
|
||
overrides = {guild.default_role: discord.PermissionOverwrite(read_messages=False)}
|
||
category = await guild.create_category('Server Chats', overwrites=overrides)
|
||
rcon_log.info(category)
|
||
channels = guild.channels
|
||
if category:
|
||
for channel in category.channels:
|
||
if server_con.server_chat_channel == channel.id:
|
||
return channel
|
||
else:
|
||
rcon_log.info(f'Creating {server_name}')
|
||
chan = await guild.create_text_channel(f'{server_name}', category=category)
|
||
server_con.server_chat_channel = chan.id
|
||
if guild_config.get('rcon_connections'):
|
||
guild_config['rcon_connections'][server_name]['game_chat_chan_id'] = chan.id
|
||
guild_ref = self.bot.fs_db.document(f'guilds/{guild.id}')
|
||
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, guild_config)
|
||
return chan
|
||
|
||
async def _monitor_chat(self, guild, server_name, server_con, guild_config: dict):
|
||
# noinspection PyShadowingNames
|
||
async def start_monitor_chat(bot, guild, *, server_name: str,
|
||
server_con: arcon.ARKServer, guild_config: dict):
|
||
while server_con.monitor_chat:
|
||
messages = await server_con.getchat()
|
||
rcon_log.debug('Got chat from {0}.'.format(server_name))
|
||
for message in [msg.strip() for msg in messages.split('\n')
|
||
if msg.strip() != 'Server received, But no response!!']:
|
||
rcon_log.info(message)
|
||
message_out = f'```{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} ||| {message}```'
|
||
for command in self.bot.game_commands:
|
||
prefix_command = '{0}{1}'.format(self.bot.game_prefix, command)
|
||
if message.split('): ')[-1].startswith(prefix_command):
|
||
try:
|
||
func = getattr(self, command)
|
||
except AttributeError:
|
||
rcon_log.warning('Function not found "{0}"'.format(command))
|
||
else:
|
||
rcon_log.info(f'Sending to {command}')
|
||
message_out = await func(bot, guild, message_out, server_name, server_con, guild_config)
|
||
|
||
await guild.get_channel(server_con.server_chat_channel).send(message_out)
|
||
await asyncio.sleep(1)
|
||
await guild.get_channel(server_con.server_chat_channel).send('Monitoring Stopped')
|
||
|
||
rcon_log.info(server_con.server_chat_channel)
|
||
if server_con.server_chat_channel:
|
||
channel = self.bot.get_channel(server_con.server_chat_channel)
|
||
if not channel:
|
||
channel = await self.create_server_chat_chan(guild, server_name, server_con, guild_config)
|
||
else:
|
||
channel = await self.create_server_chat_chan(guild, server_name, server_con, guild_config)
|
||
rcon_log.info(channel)
|
||
self.bot.loop.create_task(start_monitor_chat(self.bot, guild, server_name=server_name,
|
||
server_con=server_con, guild_config=guild_config))
|
||
await channel.send('Started monitoring on the {0} server.'.format(server_name))
|
||
rcon_log.debug('Started monitoring on the {0} server.'.format(server_name))
|
||
|
||
@commands.command()
|
||
@commands.guild_only()
|
||
async def monitor_chat(self, ctx, *, server=None):
|
||
"""Begins monitoring the specified ARK server for chat messages and other events.
|
||
The specified server must already be in the current guild\'s configuration.
|
||
To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
|
||
The server argument is not case sensitive and if the server name has two
|
||
words it can be in one of the following forms:
|
||
first last
|
||
first_last
|
||
"first last"
|
||
To view all the valid ARK servers for this guild see list_ark_servers."""
|
||
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if server is not None:
|
||
server = server.replace('_', ' ').title()
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name=server)
|
||
if server_con:
|
||
server_con.monitor_chat = True
|
||
ctx.guild_config['rcon_connections'][server]["monitoring_chat"] = 1
|
||
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||
await self._monitor_chat(ctx.guild, server, server_con, ctx.guild_config)
|
||
await ctx.message.add_reaction('✅')
|
||
else:
|
||
await ctx.send(f'Server not found: {server}')
|
||
else:
|
||
await ctx.send(f'You must include a server in this command.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.command()
|
||
@commands.guild_only()
|
||
async def end_monitor_chat(self, ctx, *, server=None):
|
||
"""Ends chat monitoring on the specified server.
|
||
Context is the same as monitor_chat"""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if server is not None:
|
||
server = server.replace('_', ' ').title()
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name=server)
|
||
if server_con:
|
||
server_con.monitor_chat = False
|
||
else:
|
||
await ctx.send(f'{server} is not connected currently.')
|
||
if server in ctx.guild_config.get('rcon_connections', []):
|
||
ctx.guild_config['rcon_connections'][server]["monitoring_chat"] = 0
|
||
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||
else:
|
||
await ctx.send(f'Server not found in config: {server}')
|
||
else:
|
||
await ctx.send(f'You must include a server in this command.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.command()
|
||
@commands.guild_only()
|
||
async def listplayers(self, ctx, *, server_name=None):
|
||
"""Lists the players currently connected to the specified ARK server.
|
||
The specified server must already be in the current guild\'s configuration.
|
||
To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
|
||
The server argument is not case sensitive and if the server name has two
|
||
words it can be in one of the following forms:
|
||
first last
|
||
first_last
|
||
"first last"
|
||
To view all the valid ARK servers for this guild see list_ark_servers."""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
|
||
if server_name:
|
||
server_name = server_name.replace('_', ' ').title()
|
||
server: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name=server_name)
|
||
if server:
|
||
msg = await ctx.send(f'**Getting Data for the {server_name} server**')
|
||
await ctx.channel.trigger_typing()
|
||
|
||
message = await server.listplayers()
|
||
await ctx.channel.trigger_typing()
|
||
await msg.delete()
|
||
await ctx.send(f'**Players currently on the {server_name} server:**\n{message}')
|
||
else:
|
||
await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
|
||
f'"{server_name}" "ip" port "password" if you would like to get info from it.')
|
||
else:
|
||
futures = []
|
||
guild_servers: dict = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name='*'
|
||
)
|
||
if guild_servers is not None:
|
||
for name, server in guild_servers.items():
|
||
if not isinstance(server, arcon.ARKServer):
|
||
await ctx.send(f'__**{server}**__')
|
||
else:
|
||
msg = await ctx.send(f'**Getting Data for the {name} server**')
|
||
|
||
# noinspection PyShadowingNames
|
||
async def _listplayers(*, name, server_con: arcon.ARKServer, mess: discord.Message):
|
||
name = name.replace('_', ' ').title()
|
||
|
||
response = await server_con.listplayers()
|
||
await mess.edit(content=f'**Players currently on the {name} server:**\n{response}')
|
||
|
||
futures.append(_listplayers(mess=msg, server_con=server, name=name))
|
||
if futures:
|
||
asyncio.ensure_future(asyncio.gather(*futures))
|
||
else:
|
||
await ctx.send('There are no available servers for this guild.')
|
||
else:
|
||
await ctx.send('There are no available servers for this guild.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.command()
|
||
@commands.guild_only()
|
||
async def add_rcon_server(self, ctx, server, ip, port, password):
|
||
"""Adds the specified server to the current guild\'s rcon config.
|
||
All multi-word strings (<server>, <ip>, <password>) must be contained inside double quotes."""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
server = server.replace('_', ' ').title()
|
||
if ctx.guild_config.get('rcon_connections'):
|
||
if server not in ctx.guild_config['rcon_connections']:
|
||
ctx.guild_config['rcon_connections'][server] = {
|
||
'ip': ip,
|
||
'port': port,
|
||
'password': password,
|
||
'game_chat_chan_id': 0,
|
||
'msg_chan_id': 0,
|
||
'monitoring_chat': 0
|
||
}
|
||
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||
await ctx.send('{0} server has been added to my configuration.'.format(server))
|
||
else:
|
||
await ctx.send('This server name is already in my configuration. Please choose another.')
|
||
else:
|
||
ctx.guild_config['rcon_connections'] = {server: {
|
||
'ip': ip,
|
||
'port': port,
|
||
'password': password,
|
||
'game_chat_chan_id': 0,
|
||
'msg_chan_id': 0,
|
||
'monitoring_chat': 0
|
||
}}
|
||
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||
await ctx.send('{0} server has been added to my configuration.'.format(server))
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
await ctx.message.delete()
|
||
await ctx.send('Command deleted to prevent password leak.')
|
||
|
||
@commands.command()
|
||
@commands.guild_only()
|
||
async def remove_rcon_server(self, ctx, *, server: str):
|
||
"""removes the specified server from the current guild\'s rcon config."""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
server = server.replace('_', ' ').title()
|
||
if server in ctx.guild_config.get('rcon_connections', []):
|
||
del ctx.guild_config['rcon_connections'][server]
|
||
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||
await ctx.send('{0} has been removed from my configuration.'.format(server))
|
||
else:
|
||
await ctx.send('{0} is not in my configuration.'.format(server))
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@staticmethod
|
||
async def _whitelist(*, server_name: str, server_con: arcon.ARKServer,
|
||
player: patron.Patron, message: discord.Message, message_lock: asyncio.Lock):
|
||
result = await server_con.whitelist(player.steam_id)
|
||
if result == f'{player.steam_id} Allow Player To Join No Check':
|
||
with await message_lock:
|
||
message = await message.channel.get_message(message.id)
|
||
await message.edit(content=f'{message.content}\n{server_name.replace("_", " ").title()}'
|
||
f' Done!')
|
||
else:
|
||
with await message_lock:
|
||
message = await message.channel.get_message(message.id)
|
||
await message.edit(content=f'{message.content}\n{server_name.replace("_", " ").title()}'
|
||
f' Failed!')
|
||
|
||
@commands.command(name='add_whitelist')
|
||
@commands.guild_only()
|
||
async def add_whitelist(self, ctx, *, members: str=None):
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if members:
|
||
futures = []
|
||
members = members.replace(', ', '').split(',')
|
||
converter = commands.MemberConverter()
|
||
for member in members:
|
||
try:
|
||
member = await converter.convert(ctx, member)
|
||
except commands.errors.BadArgument:
|
||
try:
|
||
member = int(member)
|
||
except ValueError:
|
||
raise ValueError(f'Member {member} can\'t be found and is not a valid Steam64 ID.')
|
||
|
||
if isinstance(member, discord.Member):
|
||
player = await patron.Patron.from_name(self.bot, discord_name=member)
|
||
else:
|
||
player = await patron.Patron.from_id(self.bot, steam_id=member)
|
||
|
||
if player == -1:
|
||
await ctx.send(f'{ctx.author.mention} I Cannot find a player with a discord name/steam id of '
|
||
f'{member} in the current whitelist sheet. Did you forget to '
|
||
f'move them to the correct sheet?')
|
||
else:
|
||
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name='*')
|
||
if rcon_connections:
|
||
msg = await ctx.send(f'**Whitelisting {player.discord_name} on all servers**')
|
||
lock = asyncio.Lock()
|
||
for server_name, server_con in rcon_connections.items():
|
||
futures.append(self._whitelist(server_name=server_name, server_con=server_con,
|
||
player=player, message=msg, message_lock=lock))
|
||
|
||
if futures:
|
||
asyncio.ensure_future(asyncio.gather(*futures), loop=self.bot.loop)
|
||
else:
|
||
await ctx.send('Nothing for me to do')
|
||
return
|
||
|
||
else:
|
||
await ctx.send('I need a list of members to whitelist.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.command(name='new_patron')
|
||
@commands.guild_only()
|
||
async def register_new_patron(self, ctx, *, members: str=None):
|
||
"""Adds the included Steam 64 IDs to the running whitelist on all the ARK servers in the current guild\'s rcon config.
|
||
Steam 64 IDs should be a comma separated list of IDs.
|
||
Example: 76561198024193239,76561198024193239,76561198024193239"""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if members is not None:
|
||
async with ctx.typing():
|
||
members = members.replace(', ', ',').split(',')
|
||
converter = commands.MemberConverter()
|
||
members = [await converter.convert(ctx, m) for m in members]
|
||
futures = []
|
||
patrons = []
|
||
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name='*')
|
||
|
||
if not isinstance(members, list):
|
||
members = [members, ]
|
||
for member in members:
|
||
member: discord.Member
|
||
player = await patron.Patron.from_name(self.bot, discord_name=member)
|
||
if player == -1:
|
||
await ctx.send(f'{ctx.author.mention} I Cannot find a player with a discord name of '
|
||
f'{member.display_name} in the current whitelist sheet. Did you forget to '
|
||
f'move them to the correct sheet?')
|
||
else:
|
||
if rcon_connections:
|
||
msg = await ctx.send(f'**Whitelisting {player.discord_name} on all servers**')
|
||
lock = asyncio.Lock()
|
||
for server_name, server_con in rcon_connections.items():
|
||
futures.append(self._whitelist(server_name=server_name, server_con=server_con,
|
||
player=player, message=msg, message_lock=lock))
|
||
|
||
roles = []
|
||
patron_roles = ctx.guild_config.get('patreon_tiers')
|
||
creator_roles = ctx.guild_config.get('patreon_creators')
|
||
if creator_roles:
|
||
rcon_log.info(f'patron_of {player.patron_of}')
|
||
if 'both' in player.patron_of.casefold():
|
||
rcon_log.info('found both')
|
||
for role_id in creator_roles.values():
|
||
|
||
roles.append(ctx.guild.get_role(int(role_id)))
|
||
else:
|
||
roles.append(ctx.guild.get_role(
|
||
int(creator_roles[player.patron_of + '_Patron'])
|
||
))
|
||
if patron_roles:
|
||
roles.append(ctx.guild.get_role(
|
||
int(patron_roles[player.patreon_tier.strip().title()])
|
||
))
|
||
if roles:
|
||
role_str = '\n'.join([role.name for role in roles])
|
||
await ctx.send(f'Adding {player.discord_name} to the following roles:\n'
|
||
f'{role_str}')
|
||
await ctx.guild.get_member(int(player.discord_id)).add_roles(*roles)
|
||
|
||
patrons.append(player)
|
||
|
||
if futures:
|
||
asyncio.ensure_future(asyncio.gather(*futures), loop=self.bot.loop)
|
||
else:
|
||
await ctx.send('Nothing for me to do')
|
||
return
|
||
|
||
if patrons:
|
||
new_patron_message = ctx.guild_config.get('new_patron_message')
|
||
new_patron_channel = ctx.guild_config.get('new_patron_channel')
|
||
if new_patron_message and new_patron_channel:
|
||
channel = ctx.guild.get_channel(int(new_patron_channel))
|
||
if channel:
|
||
patron_mentions = []
|
||
for p in patrons:
|
||
member = ctx.guild.get_member(int(p.discord_id))
|
||
patron_mentions.append(member.mention)
|
||
prelude = new_patron_message.get('prelude')
|
||
message = ''
|
||
if prelude:
|
||
if len(patron_mentions) > 1:
|
||
message = prelude.get('plural')
|
||
else:
|
||
message = prelude.get('singular')
|
||
body = new_patron_message.get('body')
|
||
if body:
|
||
server_info_channel = ctx.guild.get_channel(int(
|
||
ctx.guild_config.get('server_info')
|
||
))
|
||
message += ' ' + body.format(users=', '.join(patron_mentions),
|
||
server_info=server_info_channel.mention if
|
||
server_info_channel else "server_info")
|
||
await channel.send(message)
|
||
else:
|
||
await ctx.send('I need a list of members to add roles to and whitelist.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.command()
|
||
@commands.guild_only()
|
||
async def saveworld(self, ctx, *, server=None):
|
||
"""Runs SaveWorld on the specified ARK server.
|
||
If a server is not specified it will default to running saveworld on all servers in the guild\'s config.
|
||
Will print out "World Saved" for each server when the command completes successfully."""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
|
||
# noinspection PyShadowingNames
|
||
async def _saveworld(ctx, server_name: str, server_con: arcon.ARKServer):
|
||
response = await server_con.saveworld()
|
||
if response == 'World Saved':
|
||
await ctx.send(f'{server_name} Saved')
|
||
else:
|
||
await ctx.send(f'Failed to save {server_name}')
|
||
|
||
futures = []
|
||
async with ctx.typing():
|
||
if server is None:
|
||
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name='*')
|
||
if rcon_connections:
|
||
for server_name, server_con in rcon_connections.items():
|
||
futures.append(_saveworld(ctx, server_name, server_con))
|
||
self.bot.loop.create_task(asyncio.gather(*futures))
|
||
else:
|
||
await ctx.send('There are no available servers for this guild.')
|
||
else:
|
||
server = server.replace('_', ' ').title()
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name=server
|
||
)
|
||
if server_con:
|
||
# noinspection PyTypeChecker
|
||
await _saveworld(ctx, server, server_con)
|
||
else:
|
||
await ctx.send(f'{server} is not currently in the configuration for this guild.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.group(case_insensitive=True)
|
||
async def broadcast(self, ctx):
|
||
"""Run help broadcast for more info"""
|
||
pass
|
||
|
||
@broadcast.command(name='all', aliases=['a'])
|
||
@commands.guild_only()
|
||
async def broadcast_all(self, ctx, *, message=None):
|
||
"""Sends a broadcast message to all servers in the guild config.
|
||
The message will be prefixed with the Discord name of the person running the command.
|
||
Will print "Success" for each server once the broadcast is sent."""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if message is not None:
|
||
|
||
# noinspection PyShadowingNames
|
||
async def _broadcast(*, message: str, server_con: arcon.ARKServer, server_name: str,
|
||
msg: discord.Message, message_lock: asyncio.Lock):
|
||
print(server_con.host, server_con.port)
|
||
response = await server_con.broadcast(message)
|
||
if response == 'Server received, But no response!!':
|
||
with await message_lock:
|
||
msg = await msg.channel.get_message(msg.id)
|
||
await msg.edit(content=f'{msg.content}\n{server_name} Success')
|
||
else:
|
||
with await message_lock:
|
||
msg = await msg.channel.get_message(msg.id)
|
||
await msg.edit(content=f'{msg.content}\n{server_name} Failed')
|
||
futures = []
|
||
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name='*')
|
||
if rcon_connections:
|
||
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||
msg = await ctx.send(f'Broadcasting "{message}" to all servers.')
|
||
lock = asyncio.Lock()
|
||
for server_name, server_con in rcon_connections.items():
|
||
futures.append(_broadcast(message=message, server_con=server_con, server_name=server_name,
|
||
msg=msg, message_lock=lock))
|
||
self.bot.loop.create_task(asyncio.gather(*futures))
|
||
await ctx.message.add_reaction('✅')
|
||
else:
|
||
await ctx.send('There are no available servers for this guild.')
|
||
else:
|
||
await ctx.send('You must include a message with this command.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@broadcast.command(name='server')
|
||
@commands.guild_only()
|
||
async def broadcast_server(self, ctx, server, *, message=None):
|
||
"""Sends a broadcast message to the specified server that is in the guild's config.
|
||
The message will be prefixed with the Discord name of the person running the command.
|
||
If <server> has more than one word in it's name it will either need to be surrounded
|
||
by double quotes or the words separated by _"""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if server is not None:
|
||
server = server.replace('_', ' ').title()
|
||
if message is not None:
|
||
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name=server
|
||
)
|
||
if server_con:
|
||
msg = await ctx.send(f'Broadcasting "{message}" to {server}.')
|
||
response = await server_con.broadcast(message)
|
||
if response == 'Server received, But no response!!':
|
||
await msg.add_reaction(self.bot.unicode_emojis['y'])
|
||
else:
|
||
await msg.add_reaction(self.bot.unicode_emojis['x'])
|
||
else:
|
||
await ctx.send(f'{server} is not in the config for this guild')
|
||
else:
|
||
await ctx.send('You must include a message with this command.')
|
||
else:
|
||
await ctx.send('You must include a server with this command')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.command(aliases=['servers', 'list_servers'])
|
||
@commands.guild_only()
|
||
@commands.check(checks.is_restricted_chan)
|
||
async def list_ark_servers(self, ctx):
|
||
"""Returns a list of all the ARK servers in the current guild\'s config."""
|
||
servers = ctx.guild_config.get('rcon_connections', [])
|
||
em = discord.Embed(style='rich',
|
||
title=f'__**There are currently {len(servers)} ARK servers in my config:**__',
|
||
color=discord.Colour.green()
|
||
)
|
||
if ctx.guild.icon:
|
||
em.set_thumbnail(url=f'{ctx.guild.icon_url}')
|
||
for server in servers:
|
||
description = f"""
|
||
឵ **IP:** {servers[server]['ip']}:{servers[server]['port']}
|
||
឵ **Steam Connect:** [steam://connect/{servers[server]['ip']}:{servers[server]['port']}]\
|
||
(steam://connect/{servers[server]['ip']}:{servers[server]['port']})"""
|
||
em.add_field(name=f'__***{server}***__', value=description, inline=False)
|
||
await ctx.send(embed=em)
|
||
|
||
@commands.command(name='server_chat')
|
||
@commands.guild_only()
|
||
async def send_chat_to_server(self, ctx, server: str=None, *, message: str=None):
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if server is not None:
|
||
server = server.replace('_', ' ').title()
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||
name=server)
|
||
if server_con:
|
||
if message is not None:
|
||
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||
msg = await ctx.send(f'Sending "{message}" to {server}\'s chat')
|
||
response = await server_con.serverchat(message)
|
||
if response == 'Server received, But no response!!':
|
||
await msg.add_reaction(self.bot.unicode_emojis['y'])
|
||
else:
|
||
await msg.add_reaction(self.bot.unicode_emojis['x'])
|
||
else:
|
||
await ctx.send('You must include a message with this command.')
|
||
else:
|
||
await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
|
||
f'"{server}" "ip" port "password" if you would like to get info from it.')
|
||
else:
|
||
await ctx.send('You must include a server with this command')
|
||
else:
|
||
await ctx.send('You are not authorized to run this command.')
|
||
|
||
@commands.command(name='restart_server', aliases=['restart'])
|
||
@commands.guild_only()
|
||
async def restart_rcon_server(self, ctx, message: str, server_name: str=None,
|
||
delay_time: int=15, sleep_time: int=60):
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
|
||
if server_name is None or server_name == 'all':
|
||
futures = []
|
||
rcon_servers: dict = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name='*'
|
||
)
|
||
for server_name, server_con in rcon_servers.items():
|
||
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||
futures.append(utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
|
||
message=message, delay=delay_time, sleep=sleep_time))
|
||
self.bot.loop.create_task(asyncio.gather(*futures))
|
||
|
||
elif server_name.startswith('exclude='):
|
||
futures = []
|
||
exclude_servers = server_name.split('exclude=')[1].replace('_', ' ').title().split(',')
|
||
rcon_servers: dict = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name='*'
|
||
)
|
||
for server_name, server_con in rcon_servers.items():
|
||
if server_name not in exclude_servers:
|
||
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||
futures.append(utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
|
||
message=message, delay=delay_time, sleep=sleep_time))
|
||
self.bot.loop.create_task(asyncio.gather(*futures))
|
||
|
||
else:
|
||
async with ctx.typing():
|
||
server_name = server_name.replace('_', ' ').title()
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name=server_name
|
||
)
|
||
if server_con:
|
||
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||
await utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
|
||
message=message, delay=delay_time, sleep=sleep_time)
|
||
else:
|
||
await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
|
||
f'"{server_name}" "ip" port "password" if you would like to get info from it.')
|
||
else:
|
||
await ctx.send('You are not authorized to run this command.')
|
||
|
||
# noinspection PyShadowingNames
|
||
@staticmethod
|
||
async def start_dinowipe(channel, server_name: str, server_con: arcon.ARKServer):
|
||
msg = await channel.send(f'Wild dinos will be wiped on {server_name} in 2 minutes.')
|
||
await server_con.broadcast(f'Wild dino wipe incoming in 2 minutes, expect a small amount of lag while '
|
||
f'the dinos repopulate.')
|
||
await asyncio.sleep(90)
|
||
await server_con.serverchat(f'Wild Dino Wipe in 30 seconds')
|
||
await asyncio.sleep(26)
|
||
await server_con.serverchat(f'Wild Dino Wipe in 3')
|
||
await asyncio.sleep(1)
|
||
await server_con.serverchat(f'Wild Dino Wipe in 2')
|
||
await asyncio.sleep(1)
|
||
await server_con.serverchat(f'Wild Dino Wipe in 1')
|
||
await asyncio.sleep(1)
|
||
response = await server_con.destroy_wild_dinos()
|
||
if response == 'All Wild Dinos Destroyed':
|
||
await msg.edit(content=f'Wild Dinos Wiped on {server_name}')
|
||
await server_con.serverchat(f'Wild dinos have been wiped.')
|
||
else:
|
||
await msg.edit(content=f'Failed to wipe wild dinos on {server_name}')
|
||
await server_con.serverchat(f'Wild Dino Wipe failed, Please let the Admin know of this issue')
|
||
|
||
# noinspection PyShadowingNames,PyUnresolvedReferences
|
||
async def request_dinowipe(self, requester, server_name: str, server_con: arcon.ARKServer):
|
||
self.bot.dino_wipe_request[server_name] = True
|
||
if server_con.server_messages_channel and self.bot.get_channel(int(server_con.server_messages_channel)):
|
||
channel = self.bot.get_channel(int(server_con.server_messages_channel))
|
||
else:
|
||
channel = self.bot.get_channel(server_con.server_chat_channel)
|
||
msg = False
|
||
if channel:
|
||
msg = await channel.send(f'@here {requester.mention} has requested wild dinos be wiped on the '
|
||
f'{server_name} server\n'
|
||
f'If you are in the middle of taming or have some other reason to '
|
||
f'delay the wipe please react to this message with '
|
||
f'{self.bot.unicode_emojis["x"]}.\n'
|
||
f'Doing so will delay the wipe for 10 minutes.')
|
||
await server_con.broadcast(f'{requester.display_name} has requested wild dinos be wiped.\n'
|
||
f'if you are in the middle of taming or have some other reason to delay the wipe '
|
||
f'please send a chat message containing "$delaywipe" here in-game.\n'
|
||
f'Doing so will delay the wipe for 10 minutes.')
|
||
await asyncio.sleep(5)
|
||
await server_con.serverchat('The wipe will continue in 2 minutes if there are no requests to delay. '
|
||
'Send a message containing $delaywipe to delay for 10 minutes.')
|
||
rcon_log.info('Dino Wipe messages sent')
|
||
if msg:
|
||
def check(reaction, user):
|
||
return str(reaction.emoji) == self.bot.unicode_emojis['x'] and reaction.message.id == msg.id and \
|
||
user != self.bot.user
|
||
|
||
user = None
|
||
await msg.add_reaction(self.bot.unicode_emojis['x'])
|
||
try:
|
||
reaction, user = await self.bot.wait_for('reaction_add', check=check, timeout=120)
|
||
except asyncio.TimeoutError:
|
||
rcon_log.info('Message timed out...')
|
||
if self.bot.dino_wipe_request.get(server_name):
|
||
rcon_log.info('Starting dino wipe')
|
||
await self.start_dinowipe(channel, server_name, server_con)
|
||
rcon_log.info('Wild Dino Wipe Completed')
|
||
return
|
||
else:
|
||
rcon_log.info('Dino Wipe Delayed.')
|
||
await server_con.broadcast(f'Wild Dino Wipe has been delayed by {user.display_name}. '
|
||
f'Please watch chat for timing updates')
|
||
await channel.send(f'Dino wipe has been delayed by {user.mention if user else "Game Chat"}...')
|
||
for i in range(10):
|
||
await asyncio.sleep(60)
|
||
if i >= 5 or i == 0:
|
||
await server_con.serverchat(f'The Wild Dino Wipe process will continue in {9 - i} '
|
||
f'{"minute" if 9 - i == 1 else "minutes"}')
|
||
await self.request_dinowipe(requester, server_name, server_con)
|
||
else:
|
||
rcon_log.info('No channels configured, running wipe.')
|
||
await asyncio.sleep(120)
|
||
if not self.bot.dino_wipe_request.get(server_name):
|
||
rcon_log.info('Wipe Delay requested')
|
||
for i in range(10):
|
||
await asyncio.sleep(60)
|
||
await server_con.serverchat(f'The Wino Dino Wipe process will continue in {9 - i} '
|
||
f'{"minute" if 9 - i == 1 else "minutes"}')
|
||
await self.request_dinowipe(requester, server_name, server_con)
|
||
|
||
@commands.command(name='dinowipe')
|
||
@commands.guild_only()
|
||
async def run_dino_wipe(self, ctx, *, server=None):
|
||
"""Runs DestroyWildDinos on the specified ARK server.
|
||
If a server is not specified it will default to wiping the wild dinos on all servers in the guild\'s config.
|
||
Will print out "Wild Dinos Wiped on <server name>" for each server when the command completes successfully."""
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
futures = []
|
||
async with ctx.typing():
|
||
if server is None:
|
||
rcon_connections: dict = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name='*'
|
||
)
|
||
if rcon_connections:
|
||
for server_name, server_con in rcon_connections.items():
|
||
futures.append(self.start_dinowipe(ctx.channel, server_name, server_con))
|
||
self.bot.loop.create_task(asyncio.gather(*futures))
|
||
else:
|
||
await ctx.send('There are no available servers for this guild.')
|
||
else:
|
||
server = server.replace('_', ' ').title()
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name=server
|
||
)
|
||
if server_con:
|
||
# noinspection PyTypeChecker
|
||
await self.start_dinowipe(ctx.channel, server, server_con)
|
||
else:
|
||
await ctx.send(f'{server} is not currently in the configuration for this guild.')
|
||
else:
|
||
await ctx.send(f'You are not authorized to run this command.')
|
||
|
||
@commands.command(name='run_command', aliases=['run'])
|
||
@commands.guild_only()
|
||
async def run_rcon_command(self, ctx, server=None, *, command=None):
|
||
if await checks.is_rcon_admin(self.bot, ctx):
|
||
if not server:
|
||
await ctx.send('You must include a server when running this command.')
|
||
return
|
||
async with ctx.typing():
|
||
server = server.replace('_', ' ').title()
|
||
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||
guild_config=ctx.guild_config, name=server
|
||
)
|
||
if server_con:
|
||
if not command:
|
||
await ctx.send('You must include a command to run on the server.')
|
||
return
|
||
|
||
command = command.split(' ')
|
||
|
||
if ctx.guild_config.get('allowed_rcon_commands'):
|
||
pass
|
||
|
||
response = await server_con.run_command(command=' '.join(command), multi_packet=True)
|
||
if isinstance(response, str):
|
||
await ctx.send(response)
|
||
else:
|
||
body = response.body
|
||
pag = utils.Paginator(bot=self.bot)
|
||
pag.add(body)
|
||
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
|
||
await book.create_book()
|
||
else:
|
||
await ctx.send('That server was not found in the config for this guild.')
|
||
|
||
def setup(bot):
|
||
bot.add_cog(Rcon(bot))
|