示例#1
0
 def __init__(self, bot):
     self.bot = bot  # type: commands.Bot
     self.config = get_kaztron_config()
     self.ch_output = discord.Object(
         self.config.get("discord", "channel_output"))
     self.ch_log = discord.Object(self.config.get('modnotes',
                                                  'channel_log'))
示例#2
0
def check_admin(ctx: commands.Context):
    """
    Check if the sender of a command is an admin (as defined by the
    roles in the "discord" -> "admin_roles" config).
    """
    config = get_kaztron_config()
    return check_role(config.get("discord", "admin_roles", []), ctx.message)
示例#3
0
 def __init__(self, bot):
     self.bot = bot
     self.config = get_kaztron_config()
     self.dest_output = discord.Object(
         id=self.config.get('discord', 'channel_output'))
     self.dest_welcome = discord.Object(
         id=self.config.get("welcome", "channel_welcome"))
示例#4
0
def format_date(d: Union[datetime, date]) -> str:
    """
    Format a datetime object as a date (as specified in config).

    :param d: The date or datetime object to format.
    :return:
    """
    return d.strftime(get_kaztron_config().get('core', 'date_format'))
示例#5
0
 def __init__(self, bot):
     self.bot = bot
     self.config = get_kaztron_config()
     self.ch_request = discord.Object(
         self.config.get('core', 'channel_request'))
     self.dest_output = discord.Object(
         id=self.config.get('discord', 'channel_output'))
     self.name = self.config.get("core", "name", "KazTron")
示例#6
0
 def __init__(self, bot):
     self.bot = bot
     self.config = get_kaztron_config()
     self.distinguish_map = self.config.get("modtools", "distinguish_map",
                                            {})
     self.wb_images = self.config.get("modtools", "wb_images", [])
     self.ch_output = discord.Object(
         self.config.get("discord", "channel_output"))
示例#7
0
 def __init__(self, bot):
     self.bot = bot  # type: discord.Client
     self.config = get_kaztron_config()
     self.dest_output = discord.Object(id=self.config.get('discord', 'channel_output'))
     self.voice_channel_ids = self.config.get('role_man', 'channels_voice')
     self.role_voice_name = self.config.get('role_man', 'role_voice')
     self.role_voice = None
     self.voice_feature = False
示例#8
0
def format_datetime(dt: datetime, seconds=False) -> str:
    """
    Format a datetime object as a datetime (as specified in config).
    :param dt: The datetime object to format.
    :param seconds: Whether or not to display seconds (this determines which config format to use).
    :return:
    """
    format_key = 'datetime_format' if not seconds else 'datetime_seconds_format'
    return dt.strftime(get_kaztron_config().get('core', format_key))
示例#9
0
 def static_init(cls):
     """
     Executes one-time class setup. Called on KazCog __init__ to verify that setup.
     """
     if cls._config is None:
         cls._config = get_kaztron_config()
         cls._ch_out_id = cls._config.get("discord", "channel_output")
         cls._ch_test_id = cls._config.get("discord", "channel_test")
     if cls._state is None:
         cls._state = get_runtime_config()
示例#10
0
 def __init__(self, bot):
     self.bot = bot
     self.config = get_kaztron_config()
     try:
         self.filter_cfg = get_runtime_config()
     except OSError as e:
         logger.error(str(e))
         raise RuntimeError("Failed to load runtime config") from e
     self._make_default_config()
     self._load_filter_rules()
     self.dest_output = None
     self.dest_warning = None
     self.dest_current = None
示例#11
0
    def __init__(self, parser: CoreHelpParser, bot: commands.Bot):
        self.parser = parser
        self.bot = bot
        self.output = None  # type: list
        self.commands = None  # type: list
        self.cog = None  # type: KazCog
        self.section = []
        self.context = None  # type: commands.Context

        config = get_kaztron_config()
        config.set_section_view('help', HelpSectionView)
        self.config: HelpSectionView = config.help
        self.name = config.core.name
示例#12
0
def in_channels_cfg(config_section: str,
                    config_name: str,
                    allow_pm=False,
                    delete_on_fail=False,
                    *,
                    include_mods=False,
                    include_admins=False,
                    include_bot=False,
                    check_id=CheckId.C_LIST):
    """
    Command check decorator. Only allow this command to be run in specific channels (as specified
    from the config).

    The configuration can point to either a single channel ID string or a list of channel ID
    strings.
l
    :param config_section: The KaztronConfig section to access
    :param config_name: The KaztronConfig key containing the list of channel IDs
    :param allow_pm: Allow this command in PMs, as well as the configured channels
    :param delete_on_fail: If this check fails, delete the original message. This option does not
        delete the message itself, but throws an UnauthorizedChannelDelete error to allow an
        ``on_command_error`` handler to take appropriate action.
    :param include_mods: Also include mod channels.
    :param include_admin: Also include admin channels.
    :param include_bot: Also include bot and test channels.

    :raise UnauthorizedChannelError:
    :raise UnauthorizedChannelDelete:
    """
    config = get_kaztron_config()
    config_channels = config.get(config_section, config_name)

    if isinstance(config_channels, str):
        config_channels = [config_channels]

    if include_mods:
        config_channels.extend(config.get('discord', 'mod_channels'))
    if include_admins:
        config_channels.extend(config.get('discord', 'admin_channels'))
    if include_bot:
        config_channels.append(config.get('discord', 'channel_test'))
        config_channels.append(config.get('discord', 'channel_public'))

    return in_channels(config_channels,
                       allow_pm,
                       delete_on_fail,
                       check_id=check_id)
示例#13
0
 def __init__(self, bot):
     self.bot = bot
     self.config = get_kaztron_config()
     try:
         self.filter_cfg = get_runtime_config()
     except OSError as e:
         logger.error(str(e))
         raise RuntimeError("Failed to load runtime config") from e
     self.filter_cfg.set_defaults('filter',
                                  warn=[],
                                  delete=[],
                                  channel=self.config.get(
                                      'filter', 'channel_warning'))
     self._load_filter_rules()
     self.dest_output = None
     self.dest_warning = None
     self.dest_current = None
示例#14
0
def admin_only():
    """
    Command check decorator. Only allow admins to execute this command (as defined by the
    roles in the "discord" -> "admin_roles" config).
    """
    config = get_kaztron_config()

    def predicate(ctx: commands.Context):
        if check_role(config.get("discord", "admin_roles", []), ctx.message):
            logger.info("Validated {!s} as bot administrator".format(
                ctx.message.author))
            return True
        else:
            raise AdminOnlyError("Only administrators may use this command.",
                                 ctx)

    return commands.check(predicate)
示例#15
0
def in_channels_cfg(config_section: str, config_name: str, allow_pm=False):
    """
    Command check decorator. Only allow this command to be run in specific channels (as specified
    from the config).
    """
    config = get_kaztron_config()

    def predicate(ctx: commands.Context):
        pm_exemption = allow_pm and ctx.message.channel.is_private
        if ctx.message.channel.id in config.get(config_section,
                                                config_name) or pm_exemption:
            logger.info("Validated command in allowed channel {!s}".format(
                ctx.message.channel))
            return True
        else:
            raise UnauthorizedChannelError("Command not allowed in channel.",
                                           ctx)

    return commands.check(predicate)
示例#16
0
    def __init__(self, bot):
        self.bot = bot
        self.config = get_kaztron_config()
        try:
            self.db = get_runtime_config()
        except OSError as e:
            logger.error(str(e))
            raise RuntimeError("Failed to load runtime config") from e

        self.db.set_defaults('spotlight', current=-1, queue=[])

        self.dest_output = None
        self.dest_spotlight = None
        self.role_audience_name = self.config.get('spotlight', 'audience_role')
        self.role_host_name = self.config.get('spotlight', 'host_role')

        self.user_agent = self.config.get("core", "name")
        self.gsheet_id = self.config.get("spotlight", "spreadsheet_id")
        self.gsheet_range = self.config.get("spotlight", "spreadsheet_range")
        self.applications = []
        self.applications_last_refresh = 0

        self.current_app_index = int(self.db.get('spotlight', 'current', -1))
        self.queue_data = deque(self.db.get('spotlight', 'queue', []))
示例#17
0
class DiceCog:
    config = get_kaztron_config()
    ch_allowed_list = (config.get('dice', 'channel_dice'),
                       config.get("discord", "channel_test"),
                       config.get("discord", "channel_output"))

    def __init__(self, bot):
        self.bot = bot
        self.ch_dice = None

    async def on_ready(self):
        self.ch_dice = self.bot.get_channel(
            self.config.get('dice', 'channel_dice'))
        if self.ch_dice is None:
            raise ValueError("Channel {} not found".format(
                self.config.get('dice', 'channel_dice')))

    @commands.command(pass_context=True, ignore_extra=False, aliases=['rolls'])
    @in_channels(ch_allowed_list)
    async def roll(self, ctx, dice: str):
        """
        Rolls dice.

        Rolls a <sides>-sided die <num> times, and reports the rolls and total.

        Example: `.rolls 3d6` rolls three six-sided dice.
        """
        logger.info("roll: {}".format(message_log_str(ctx.message)))

        try:
            num_rolls, num_sides = map(int, dice.split('d'))
        except ValueError:
            err_msg = "Invalid format: {}".format(message_log_str(ctx.message))
            logger.warning("rolls(): " + err_msg)
            await self.bot.say('Invalid format. Please enter `.rolls XdY`, '
                               'where X and Y are positive whole numbers.')
            return

        if num_rolls <= 0:
            logger.warning("rolls(): arguments out of range")
            await self.bot.say("You have to roll at least 1 die.")
        elif num_sides <= 1:
            logger.warning("rolls(): arguments out of range")
            await self.bot.say("Dice must have at least 2 sides.")
        elif num_sides > 100 or num_rolls > 100:
            logger.warning("rolls(): arguments out of range")
            await self.bot.say(
                "The limit for dice number and sides is 100 each.")
        else:
            result = [random.randint(1, num_sides) for _ in range(num_rolls)]
            total = sum(result)
            await self.bot.say("{!s}\n**Sum:** {:d}".format(result, total))
            logger.info("Rolled dice: {:d}d{:d} = {!r} (sum={})".format(
                num_rolls, num_sides, result, total))

    @commands.command(pass_context=True, ignore_extra=False)
    @in_channels(ch_allowed_list)
    async def rollf(self, ctx):
        """
        Rolls four dice for the FATE tabletop roleplaying game system.

        Arguments: None
        """
        logger.info("roll: {}".format(message_log_str(ctx.message)))
        dice = (-1, -1, 0, 0, 1, 1)
        str_map = {-1: '-', 0: '0', 1: '+'}
        roll_results = [random.choice(dice) for _ in range(4)]
        total = sum(roll_results)
        rolls_str = [str_map[roll] for roll in roll_results]
        await self.bot.say("{!s}\n**Sum:** {:d}".format(rolls_str, total))
        logger.info("Rolled FATE dice: {!r} (sum={})".format(rolls_str, total))

    @roll.error
    @rollf.error
    async def roll_on_error(self, exc, ctx):
        cmd_string = message_log_str(ctx.message)
        if isinstance(exc, commands.CommandInvokeError):
            root_exc = exc.__cause__ if exc.__cause__ is not None else exc

            if isinstance(root_exc, errors.UnauthorizedChannelError):
                logger.error("Unauthorized use of command in #{1}: {0}".format(
                    cmd_string, ctx.message.channel.name))
                await self.bot.send_message(
                    ctx.message.channel,
                    "Sorry, this command can only be used in {}".format(
                        self.ch_dice.mention))

            else:
                core_cog = self.bot.get_cog("CoreCog")
                await core_cog.on_command_error(exc, ctx, force=True
                                                )  # Other errors can bubble up
        else:
            core_cog = self.bot.get_cog("CoreCog")
            await core_cog.on_command_error(exc, ctx, force=True
                                            )  # Other errors can bubble up
示例#18
0
class DiceCog(KazCog):
    config = get_kaztron_config()
    ch_allowed_list = (config.get('dice', 'channel_dice'),
                       config.get("discord", "channel_test"),
                       config.get("discord", "channel_output"))
    MAX_CHOICES = 20

    def __init__(self, bot):
        super().__init__(bot)
        self.ch_dice = None

    async def on_ready(self):
        self.ch_dice = self.validate_channel(
            self.config.get('dice', 'channel_dice'))
        await super().on_ready()

    @commands.command(pass_context=True, ignore_extra=False, aliases=['rolls'])
    @in_channels(ch_allowed_list)
    async def roll(self, ctx, dice: str):
        """
        Rolls dice.

        Rolls a <sides>-sided die <num> times, and reports the rolls and total.

        Example: `.rolls 3d6` rolls three six-sided dice.
        """
        logger.info("roll: {}".format(message_log_str(ctx.message)))

        try:
            num_rolls, num_sides = map(int, dice.split('d'))
        except ValueError:
            err_msg = "Invalid format: {}".format(message_log_str(ctx.message))
            logger.warning("rolls(): " + err_msg)
            await self.bot.say('Invalid format. Please enter `.rolls XdY`, '
                               'where X and Y are positive whole numbers.')
            return

        if num_rolls <= 0:
            logger.warning("rolls(): arguments out of range")
            await self.bot.say("You have to roll at least 1 die.")
        elif num_sides <= 1:
            logger.warning("rolls(): arguments out of range")
            await self.bot.say("Dice must have at least 2 sides.")
        elif num_sides > 100 or num_rolls > 100:
            logger.warning("rolls(): arguments out of range")
            await self.bot.say(
                "The limit for dice number and sides is 100 each.")
        else:
            result = [random.randint(1, num_sides) for _ in range(num_rolls)]
            total = sum(result)
            await self.bot.say("{!s}\n**Sum:** {:d}".format(result, total))
            logger.info("Rolled dice: {:d}d{:d} = {!r} (sum={})".format(
                num_rolls, num_sides, result, total))

    @commands.command(pass_context=True, ignore_extra=False)
    @in_channels(ch_allowed_list)
    async def rollf(self, ctx):
        """
        Rolls four dice for the FATE tabletop roleplaying game system.

        Arguments: None
        """
        logger.info("roll: {}".format(message_log_str(ctx.message)))
        dice = (-1, -1, 0, 0, 1, 1)
        str_map = {-1: '-', 0: '0', 1: '+'}
        roll_results = [random.choice(dice) for _ in range(4)]
        total = sum(roll_results)
        rolls_str = [str_map[roll] for roll in roll_results]
        await self.bot.say("[{}]\n**Sum:** {:d}".format(
            ' '.join(rolls_str), total))
        logger.info("Rolled FATE dice: {!r} (sum={})".format(rolls_str, total))

    @commands.command(pass_context=True, ignore_extra=False, no_pm=False)
    async def choose(self, ctx, *, choices: str):
        """
        Need some help making a decision? Let the bot choose for you!

        Arguments:
        * choices - Two or more choices, separated by commas `,`.

        Examples:
        `.choose a, b, c`
        """
        logger.info("choose: {}".format(message_log_str(ctx.message)))
        choices = list(map(str.strip, choices.split(",")))
        if "" in choices:
            logger.warning("choose(): argument empty")
            await self.bot.say("I cannot decide if there's an empty choice.")
        elif len(choices) < 2:
            logger.warning("choose(): arguments out of range")
            await self.bot.say("I need something to choose from.")
        elif len(choices) > self.MAX_CHOICES:
            logger.warning("choose(): arguments out of range")
            await self.bot.say("I don't know, that's too much to choose from! "
                               "I can't handle more than {:d} choices!".format(
                                   self.MAX_CHOICES))
        else:
            r = random.randint(0, len(choices) - 1)
            await self.bot.say(choices[r])

    @roll.error
    @rollf.error
    async def roll_on_error(self, exc, ctx):
        cmd_string = message_log_str(ctx.message)
        if isinstance(exc, commands.CommandInvokeError):
            root_exc = exc.__cause__ if exc.__cause__ is not None else exc

            if isinstance(root_exc, errors.UnauthorizedChannelError):
                logger.error("Unauthorized use of command in #{1}: {0}".format(
                    cmd_string, ctx.message.channel.name))
                await self.bot.send_message(
                    ctx.message.channel,
                    "Sorry, this command can only be used in {}".format(
                        channel_mention(self.ch_allowed_list[0])))

            else:
                core_cog = self.bot.get_cog("CoreCog")
                await core_cog.on_command_error(exc, ctx, force=True
                                                )  # Other errors can bubble up
        else:
            core_cog = self.bot.get_cog("CoreCog")
            await core_cog.on_command_error(exc, ctx, force=True
                                            )  # Other errors can bubble up
示例#19
0
async def main():
    add_application_path()

    import sys
    from urllib.parse import urlparse, parse_qs

    import kaztron
    from kaztron.driver.reddit import RedditLoginManager
    from kaztron.config import get_kaztron_config, get_runtime_config

    config = get_kaztron_config()
    state = get_runtime_config()
    kaztron.KazCog.static_init(config, state)

    rlm = RedditLoginManager()

    try:
        cmd = sys.argv[1].lower()
    except IndexError:
        cmd = None

    if not cmd or cmd == 'login':
        print(f"KazTron {kaztron.__version__}: Reddit authorization tool\n\n"
              "This tool allows you to authorize KazTron on a Reddit account, in order to make use "
              "functionality which requires Reddit access.\n")

        scopes = rlm.get_extension_scopes()

        print('')
        print("Go to this URL to authorize a Reddit account:")
        print(rlm.get_authorization_url(scopes) + "\n")
        print("You will be redirected to a URL (it might fail to load, that's OK).\n")
        response_url = input("Paste the URL here: ")

        response_parts = urlparse(response_url)
        query_vars = parse_qs(response_parts.query)

        await rlm.authorize(query_vars['state'][0], query_vars['code'][0])

        print("Done.")
        exit(0)

    elif cmd == 'logout':
        user = None
        try:
            user = sys.argv[2]
        except IndexError:
            print("Must specify a username to log out.")
            exit(2)
        try:
            await rlm.logout(user)
        except KeyError:
            print(f"User '{user}' not logged in.")
            exit(3)
        print(f"User '{user}' has been logged out. Note that this app's authorization will remain "
              "on the user's account and must be removed via the Reddit website.")
        exit(0)

    elif cmd == 'clear':
        await rlm.clear()
        print(f"All user sessions cleared. Note that this app's authorization will remain "
              "on user accounts and must be removed via the Reddit website.")
        exit(0)

    else:
        print("Usage: ./reddit_auth.py <login|logout <username>|clear>\n")
        exit(0)
示例#20
0
class RedditLoginManager:
    """
    Manages login credentials and OAuth flow for Reddit, and provides instances of the PRAW Reddit
    object that allows Reddit access.

    See `tools/reddit_auth.py` for a usage example.

    KazTron bot extensions can declare required Reddit scopes by defining a callable
    `get_reddit_scopes` at the MODULE level. This callable must return a list of scopes as strings,
    and must work upon being imported (i.e. without a call to `setup()` or an actual cog/bot
    initialisation).
    """
    _global_config = get_kaztron_config()
    _global_state = get_runtime_config()
    _global_config.set_section_view('reddit', RedditConfig)
    _global_state.set_section_view('reddit', RedditState)
    _config = _global_config.get_section('reddit')  # type: RedditConfig
    _config.set_defaults(refresh_uri='http://localhost:8080')
    _state = _global_state.get_section('reddit')  # type: RedditState
    _state.set_defaults(refresh_tokens={}, last_auth_state=None)

    def __init__(self):
        # lazy cache of reddit instance objects
        self.reddit_cache = {u: None
                             for u in self.users
                             }  # type: Dict[str, praw.Reddit]

    @property
    def users(self) -> Sequence[str]:
        return tuple(u for u in self._state.refresh_tokens.keys())

    @property
    def refresh_tokens(self) -> MutableMapping[str, Optional[str]]:
        """ Return a dict of username -> refresh_token """
        return self._state.refresh_tokens

    def get_reddit(self, username: str = None) -> apraw.Reddit:
        """
        Get a :class:`praw.Reddit` instance for the specified user.

        :param username: Name of user account to work with. If None, first logged-in user currently
            configured.
        :return: A :class:`praw.Reddit` instance.
        :raise KeyError: User is not currently logged in.
        """
        if username is None:  # if no username specified, set to the first username
            username = self.users[0]
        if self.reddit_cache.get(username, None) is None:
            self.reddit_cache[username] = apraw.Reddit(
                client_id=self._config.client_id,
                client_secret=self._config.client_secret,
                refresh_token=self.refresh_tokens[username],
                user_agent=self._config.user_agent)
        return self.reddit_cache[username]

    def get_anonymous_reddit(self):
        return apraw.Reddit(client_id=self._config.client_id,
                            client_secret=self._config.client_secret,
                            redirect_uri=self._config.refresh_uri,
                            user_agent=self._config.user_agent)

    def get_extension_scopes(self) -> Set[str]:
        """
        Get reddit scopes required from extensions enabled in the configuration file.

         :raises ImportError: An extension specified in the configuration does not exist.
         """
        import importlib
        scopes = {'identity'}
        for ext_name in self._global_config.get('core', 'extensions'):
            lib = importlib.import_module('kaztron.cog.' + ext_name)
            try:
                ext_scopes = lib.get_reddit_scopes()
                scopes.update(ext_scopes)
                logger.info(
                    f"Extension {ext_name} defines scopes: {ext_scopes!s}")
            except AttributeError:
                logger.warning(
                    f"Extension {ext_name} does not define get_reddit_scopes()"
                )
        return scopes

    def get_authorization_url(self,
                              scopes: Iterable[str] = ('identity', ),
                              permanent=True) -> str:
        """
        Get the URL a reddit account owner can use to authorize the bot to log in.

        This will invalidate any previously issued authorisation URLs.

        :param scopes: Reddit API authorisation scopes to request.
        :param permanent: Whether the authorisation should be permanent or temporary (time-limited).
        :return: URL
        """
        with self._state as state:
            state.last_auth_state = secrets.token_urlsafe(16)
        return self.get_anonymous_reddit().auth.url(
            scopes, self._state.last_auth_state,
            'permanent' if permanent else 'temporary')

    async def authorize(self, state: str, code: str) -> apraw.Reddit:
        """
        Once a user has permitted the application, complete the authorization.
        :raise ValueError: State does not match state of last issued authorization URL: the
            authorization may not be one that this application initiated.
        """
        logger.info("Checking authorisation...")
        if state != self._state.last_auth_state:
            raise ValueError(
                "State does not match stored state: authorization may not have been "
                "initiated by this application.")
        reddit = self.get_anonymous_reddit()
        refresh_token = await reddit.auth.authorize(code)
        with self._state as state:
            name = (await reddit.user.me()).name
            state.refresh_tokens[name] = refresh_token
            self.reddit_cache[name] = reddit
        logger.info(f"Authorised user {name}")
        return reddit

    async def logout(self, username: str):
        """
        Doesn't really logout (not meaningful server-side), but clears stored authorization
        information for that user.
        :raises KeyError: User not logged in.
        """
        del self.reddit_cache[username]
        del self._state.refresh_tokens[username]
        with self._state as state:
            state.refresh_tokens = self._state.refresh_tokens  # force marking as modified
        logger.info(f"Logged out user {username}")

    async def clear(self):
        """ Clear all login information for all reddit users. """
        logger.debug("Logging out all known users: {}".format(', '.join(
            self.users)))
        self.reddit_cache.clear()
        with self._state as state:
            state.refresh_tokens = {}
        logger.info("Logged out all users")
示例#21
0
def run(loop: asyncio.AbstractEventLoop):
    """
    Run the bot once.
    """
    config = get_kaztron_config()
    state = get_runtime_config()
    kaztron.KazCog.static_init(config, state)

    # custom help formatters
    kaz_help_parser = CoreHelpParser({
        'name': config.core.get('name')
    })

    # create bot instance (+ some custom hacks)
    client = commands.Bot(
        command_prefix='.',
        formatter=DiscordHelpFormatter(kaz_help_parser, show_check_failure=True),
        description='This an automated bot for the /r/worldbuilding discord server',
        pm_help=True)
    apply_patches(client)

    # KazTron-specific extension classes
    client.scheduler = Scheduler(client)
    client.kaz_help_parser = kaz_help_parser

    # Load core extension (core + rolemanager)
    client.load_extension("kaztron.core")

    # Load extensions
    startup_extensions = config.get("core", "extensions")
    for extension in startup_extensions:
        logger.debug("Loading extension: {}".format(extension))
        # noinspection PyBroadException
        try:
            client.load_extension("kaztron.cog." + extension)
        except Exception:
            logger.exception('Failed to load extension {}'.format(extension))
            sys.exit(ErrorCodes.EXTENSION_LOAD)

    # noinspection PyBroadException
    try:
        loop.run_until_complete(client.login(config.get("discord", "token")))
        loop.run_until_complete(client.connect())
    except KeyboardInterrupt:
        logger.info("Interrupted by user")
        logger.debug("Waiting for client to close...")
        loop.run_until_complete(client.close())
        logger.info("Client closed.")
        sys.exit(ErrorCodes.OK)
    except Exception:
        logger.exception("Uncaught exception during bot execution")
        logger.debug("Waiting for client to close...")
        loop.run_until_complete(client.close())
        logger.info("Client closed.")

        # Let the external retry reboot the bot - attempt recovery from errors
        # sys.exit(ErrorCodes.ERROR)
        return
    finally:
        logger.debug("Cancelling pending tasks...")
        # BEGIN CONTRIB
        # Modified from code from discord.py.
        #
        # Source: https://github.com/Rapptz/discord.py/blob/
        # 09bd2f4de7cccbd5d33f61e5257e1d4dc96b5caa/discord/client.py#L517
        #
        # Original code Copyright (c) 2015-2016 Rapptz. MIT licence.
        pending = asyncio.Task.all_tasks(loop=loop)
        gathered = asyncio.gather(*pending, loop=loop, return_exceptions=True)
        # noinspection PyBroadException
        try:
            gathered.cancel()
            loop.run_until_complete(gathered)
            gathered.exception()
        except Exception:
            pass
        # END CONTRIB
        KazCog.state.write()
示例#22
0
import sys
import logging

import kaztron
from kaztron import runner
from kaztron.config import get_kaztron_config
from kaztron.logging import setup_logging

# In the loving memory of my time as a moderator of r/worldbuilding network
# To the future dev, this whole thing is a mess that somehow works. Sorry for the inconvenience.
# (Assuming this is from Kazandaki -- Laogeodritt)

# load configuration
try:
    config = get_kaztron_config(kaztron.cfg_defaults)
except OSError as e:
    print(str(e), file=sys.stderr)
    sys.exit(runner.ErrorCodes.CFG_FILE)


def stop_daemon():
    import os
    import signal
    # noinspection PyPackageRequirements
    from daemon import pidfile
    print("Reading pidfile...")
    pidf = pidfile.TimeoutPIDLockFile(config.get('core', 'daemon_pidfile'))
    pid = pidf.read_pid()
    print("Stopping KazTron (PID={:d})...".format(pid))
    os.kill(pid, signal.SIGINT)
示例#23
0
def run(loop: asyncio.AbstractEventLoop):
    """
    Run the bot once.
    """
    config = get_kaztron_config()
    client = commands.Bot(command_prefix='.',
        description='This an automated bot for the /r/worldbuilding discord server',
        pm_help=True)
    patch_smart_quotes_hack(client)

    # Load extensions
    startup_extensions = config.get("core", "extensions")
    client.load_extension("kaztron.cog.core")
    for extension in startup_extensions:
        logger.debug("Loading extension: {}".format(extension))
        # noinspection PyBroadException
        try:
            client.load_extension("kaztron.cog." + extension)
        except Exception:
            logger.exception('Failed to load extension {}'.format(extension))
            sys.exit(ErrorCodes.EXTENSION_LOAD)

    # noinspection PyBroadException
    try:
        loop.run_until_complete(client.login(config.get("discord", "token")))
        loop.run_until_complete(client.connect())
    except KeyboardInterrupt:
        logger.info("Interrupted by user")
        logger.debug("Waiting for client to close...")
        loop.run_until_complete(client.close())
        logger.info("Client closed.")
        sys.exit(ErrorCodes.OK)
    except Exception:
        logger.exception("Uncaught exception during bot execution")
        logger.debug("Waiting for client to close...")
        loop.run_until_complete(client.close())
        logger.info("Client closed.")

        # Let the external retry reboot the bot - attempt recovery from errors
        # sys.exit(ErrorCodes.ERROR)
        return
    finally:
        logger.debug("Cancelling pending tasks...")
        # BEGIN CONTRIB
        # Modified from code from discord.py.
        #
        # Source: https://github.com/Rapptz/discord.py/blob/
        # 09bd2f4de7cccbd5d33f61e5257e1d4dc96b5caa/discord/client.py#L517
        #
        # Original code Copyright (c) 2015-2016 Rapptz. MIT licence.
        pending = asyncio.Task.all_tasks(loop=loop)
        gathered = asyncio.gather(*pending, loop=loop)
        # noinspection PyBroadException
        try:
            gathered.cancel()
            loop.run_until_complete(gathered)
            gathered.exception()
        except Exception:
            pass
        # END CONTRIB
        KazCog._state.write()