2018-07-30 03:48:58 +00:00
|
|
|
|
#!/usr/bin/env python3
|
2020-05-12 23:55:08 +00:00
|
|
|
|
|
|
|
|
|
# © 2018–2020 io mintz <io@mintz.cc>
|
|
|
|
|
#
|
|
|
|
|
# Emote Manager is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# Emote Manager is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
# along with Emote Manager. If not, see <https://www.gnu.org/licenses/>.
|
2018-07-30 03:48:58 +00:00
|
|
|
|
|
2020-06-03 21:21:41 +00:00
|
|
|
|
import base64
|
2018-07-30 05:15:38 +00:00
|
|
|
|
import logging
|
2018-07-30 05:26:06 +00:00
|
|
|
|
import traceback
|
2018-07-30 05:15:38 +00:00
|
|
|
|
|
2018-07-30 03:48:58 +00:00
|
|
|
|
import discord
|
2019-09-05 22:39:46 +00:00
|
|
|
|
from bot_bin.bot import Bot
|
2018-07-30 03:48:58 +00:00
|
|
|
|
from discord.ext import commands
|
2021-04-06 04:31:47 +00:00
|
|
|
|
from utils.compat import md5
|
2018-07-30 03:48:58 +00:00
|
|
|
|
|
2018-07-30 05:15:38 +00:00
|
|
|
|
logging.basicConfig(level=logging.WARNING)
|
2019-06-04 03:08:44 +00:00
|
|
|
|
logging.getLogger('discord').setLevel(logging.INFO)
|
2018-07-30 05:15:38 +00:00
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
|
|
|
2021-05-19 21:26:51 +00:00
|
|
|
|
# SelectorEventLoop on windows doesn't support subprocesses lol
|
2021-05-19 22:44:40 +00:00
|
|
|
|
import asyncio
|
|
|
|
|
import sys
|
2021-05-19 21:26:51 +00:00
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
|
loop = asyncio.ProactorEventLoop()
|
|
|
|
|
asyncio.set_event_loop(loop)
|
|
|
|
|
|
2019-09-05 22:39:46 +00:00
|
|
|
|
class Bot(Bot):
|
2019-08-30 21:46:35 +00:00
|
|
|
|
startup_extensions = (
|
|
|
|
|
'cogs.emote',
|
|
|
|
|
'cogs.meta',
|
2019-09-05 22:39:46 +00:00
|
|
|
|
'bot_bin.debug',
|
|
|
|
|
'bot_bin.misc',
|
2020-07-01 06:44:33 +00:00
|
|
|
|
'bot_bin.systemd',
|
2019-08-30 21:46:35 +00:00
|
|
|
|
'jishaku',
|
|
|
|
|
)
|
|
|
|
|
|
2018-07-30 04:34:27 +00:00
|
|
|
|
def __init__(self, **kwargs):
|
2020-07-24 19:22:35 +00:00
|
|
|
|
with open('data/config.py', encoding='utf-8') as f:
|
2019-08-30 21:46:35 +00:00
|
|
|
|
config = eval(f.read(), {})
|
2018-08-01 02:05:23 +00:00
|
|
|
|
|
2021-04-06 03:51:54 +00:00
|
|
|
|
super().__init__(config=config, setup_db=True, **kwargs)
|
2020-06-03 21:21:41 +00:00
|
|
|
|
# allow use of the bot's user ID before ready()
|
|
|
|
|
token_part0 = self.config['tokens']['discord'].partition('.')[0].encode()
|
|
|
|
|
self.user_id = int(base64.b64decode(token_part0 + b'=' * (3 - len(token_part0) % 3)))
|
2018-08-22 15:27:35 +00:00
|
|
|
|
|
2019-09-19 21:14:18 +00:00
|
|
|
|
def process_config(self):
|
2018-08-22 15:27:35 +00:00
|
|
|
|
"""Load the emojis from the config to be used when a command fails or succeeds
|
|
|
|
|
We do it this way so that they can be used anywhere instead of requiring a bot instance.
|
|
|
|
|
"""
|
2019-09-19 21:14:18 +00:00
|
|
|
|
super().process_config()
|
2018-08-22 15:27:35 +00:00
|
|
|
|
import utils.misc
|
|
|
|
|
default = ('❌', '✅')
|
|
|
|
|
utils.SUCCESS_EMOJIS = utils.misc.SUCCESS_EMOJIS = (
|
|
|
|
|
self.config.get('response_emojis', {}).get('success', default))
|
|
|
|
|
|
2021-04-06 04:00:17 +00:00
|
|
|
|
# Metrics
|
|
|
|
|
|
2021-04-06 03:51:54 +00:00
|
|
|
|
async def on_command(self, ctx):
|
2021-04-06 04:31:47 +00:00
|
|
|
|
user_id_md5 = md5(ctx.author.id.to_bytes(8, byteorder='big')).digest()
|
2021-04-06 03:51:54 +00:00
|
|
|
|
await self.pool.execute(
|
|
|
|
|
'INSERT INTO invokes (guild_id, user_id_md5, command) VALUES ($1, $2, $3)',
|
2021-04-06 04:20:06 +00:00
|
|
|
|
getattr(ctx.guild, 'id', None), user_id_md5, ctx.command.qualified_name,
|
2021-04-06 03:51:54 +00:00
|
|
|
|
)
|
|
|
|
|
|
2021-04-06 04:00:17 +00:00
|
|
|
|
# we use on_shard_ready rather than on_ready because the latter is a bit less reliable
|
|
|
|
|
async def on_shard_ready(self, shard_id):
|
|
|
|
|
await self.pool.execute(
|
|
|
|
|
"""
|
2021-04-06 04:19:37 +00:00
|
|
|
|
INSERT INTO shard_info (shard_id, guild_count, member_count)
|
|
|
|
|
VALUES ($1, $2, $3)
|
|
|
|
|
ON CONFLICT (shard_id) DO UPDATE SET
|
|
|
|
|
guild_count = EXCLUDED.guild_count,
|
|
|
|
|
member_count = EXCLUDED.member_count
|
2021-04-06 04:00:17 +00:00
|
|
|
|
""",
|
2021-06-03 08:38:50 +00:00
|
|
|
|
shard_id, *self.shard_stats(shard_id),
|
2021-04-06 04:00:17 +00:00
|
|
|
|
)
|
|
|
|
|
|
2021-06-03 08:30:26 +00:00
|
|
|
|
async def update_shard(self, guild):
|
|
|
|
|
await self.pool.execute(
|
|
|
|
|
"""
|
|
|
|
|
UPDATE shard_info
|
|
|
|
|
SET
|
|
|
|
|
guild_count = $2,
|
|
|
|
|
member_count = $3
|
|
|
|
|
WHERE shard_id = $1
|
|
|
|
|
""",
|
2021-06-03 08:38:50 +00:00
|
|
|
|
guild.shard_id, *self.shard_stats(guild.shard_id),
|
2021-06-03 08:30:26 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
on_guild_join = on_guild_remove = update_shard
|
|
|
|
|
|
2021-06-03 08:38:50 +00:00
|
|
|
|
def shard_stats(self, shard_id):
|
|
|
|
|
guilds = [guild for guild in self.guilds if guild.shard_id == shard_id]
|
|
|
|
|
guild_count = len(guilds)
|
|
|
|
|
member_count = sum(getattr(guild, 'member_count', 0) for guild in guilds)
|
|
|
|
|
return guild_count, member_count
|
|
|
|
|
|
2020-06-02 00:51:06 +00:00
|
|
|
|
def main():
|
|
|
|
|
import sys
|
|
|
|
|
|
2021-02-16 06:48:27 +00:00
|
|
|
|
if len(sys.argv) == 1:
|
|
|
|
|
shard_count = None
|
|
|
|
|
shard_ids = None
|
|
|
|
|
elif len(sys.argv) < 3:
|
|
|
|
|
print('Usage:', sys.argv[0], '[<shard count> <hyphen-separated list of shard IDs>]', file=sys.stderr)
|
2020-06-19 03:56:27 +00:00
|
|
|
|
sys.exit(1)
|
2021-02-16 06:48:27 +00:00
|
|
|
|
else:
|
|
|
|
|
shard_count = int(sys.argv[1])
|
|
|
|
|
shard_ids = list(map(int, sys.argv[2].split('-')))
|
2020-09-29 07:41:29 +00:00
|
|
|
|
|
|
|
|
|
Bot(
|
|
|
|
|
intents=discord.Intents(
|
|
|
|
|
guilds=True,
|
|
|
|
|
# we hardly need DM support but it's helpful to be able to run the help/support commands in DMs
|
|
|
|
|
messages=True,
|
|
|
|
|
# we don't need DM reactions because we don't ever paginate in DMs
|
|
|
|
|
guild_reactions=True,
|
|
|
|
|
emojis=True,
|
|
|
|
|
# everything else, including `members` and `presences`, is implicitly false.
|
|
|
|
|
),
|
2021-02-16 06:48:27 +00:00
|
|
|
|
|
|
|
|
|
# the least stateful bot you will ever see 😎
|
2020-09-29 07:41:29 +00:00
|
|
|
|
chunk_guilds_at_startup=False,
|
|
|
|
|
member_cache_flags=discord.MemberCacheFlags.none(),
|
2020-10-01 06:06:43 +00:00
|
|
|
|
# disable message cache
|
|
|
|
|
max_messages=None,
|
2020-09-29 07:41:29 +00:00
|
|
|
|
|
|
|
|
|
shard_count=shard_count,
|
|
|
|
|
shard_ids=shard_ids,
|
|
|
|
|
).run()
|
2020-06-02 00:51:06 +00:00
|
|
|
|
|
2018-07-30 03:48:58 +00:00
|
|
|
|
if __name__ == '__main__':
|
2020-06-02 00:51:06 +00:00
|
|
|
|
main()
|