Source code for sni.discord.bot

"""
Discord Bot management. The bot requires the ``bot`` scope, and the following
permissions:
* ``Manage Roles``
* ``Change Nickname``
* ``Manage Nicknames``
* ``Send Messages``
* ``Manage Messages``
which corresponds to the the permission integer ``469772288``. Therefore, the
invitation link for the bot should look like this: ``https://discord.com/api/oauth2/authorize?client_id=<bot_id>&permissions=469772288&scope=bot``.
See also:
    `Discord developer portal <https://discord.com/developers/applications>`_, `Creating a Bot Account <https://discordpy.readthedocs.io/en/latest/discord.html#discord-intro>`_
"""

import asyncio
import logging
from threading import Thread

from apscheduler.jobstores.redis import RedisJobStore
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from discord.ext.commands import Bot
import discord

from sni.db.redis import new_redis_connection
from sni.conf import CONFIGURATION as conf
import sni.utils as utils

JOBS_KEY: str = "scheduler:discord:jobs"
"""The redis key for the job list"""

RUN_TIMES_KEY: str = "scheduler:discord:run_times"
"""The redis key for the job run times"""

bot = Bot(command_prefix="!", description="SeAT Navy Issue Discord Bot")

scheduler = AsyncIOScheduler(
    event_loop=bot.loop,
    job_defaults={
        "coalesce": True,
        "executor": "default",
        "jitter": 60,
        "jobstore": "discord",
        "max_instances": 3,
        "misfire_grace_time": None,
    },
    jobstores={
        "discord": RedisJobStore(
            db=conf.redis.database,
            host=conf.redis.host,
            jobs_key=JOBS_KEY,
            port=conf.redis.port,
            run_times_key=RUN_TIMES_KEY,
        ),
    },
    timezone=utils.utc,
)


[docs]def get_guild() -> discord.Guild: """ Returns a guild handler corresponding to the ``discord.server_id`` setting. """ return bot.get_guild(conf.discord.server_id)
[docs]def get_member(user_id: int) -> discord.Member: """ Gets a guild member by its user ID. """ return get_guild().get_member(user_id)
[docs]async def log(message: str): """ Sends a message on the logging channel. If configuration key ``discord.log_channel_id`` is ``None``, does't do anything. """ log_channel_id = conf.discord.log_channel_id if log_channel_id is None: return log_channel = bot.get_channel(log_channel_id) await log_channel.send(message)
[docs]def start_bot(): """ Runs the discord client in a different thread. """ async def _start(): await bot.start(conf.discord.token.get_secret_value()) logging.info("Starting Discord client") bot.loop.create_task(_start()) thread = Thread( args=(bot.loop,), daemon=True, name="discord_client", target=asyncio.BaseEventLoop.run_forever, ) thread.start()
[docs]def start_scheduler() -> None: """ Clears the job store and starts the scheduler. """ redis = new_redis_connection() redis.delete(JOBS_KEY, RUN_TIMES_KEY) scheduler.start()
[docs]def stop_scheduler() -> None: """ Stops the scheduler and cleans up things """ scheduler.shutdown()