Edit on GitHub

tickets_plus.cogs.routines

Tasks that run in the background from time to time.

This is where the bot's background tasks are defined. These tasks are scheduled to run at a specific interval, and are generally used to perform some sort of maintenance or cleanup. Like cleaning up users who are no longer prevented from something.

Typical usage example:
from tickets_plus import bot
bot_instance = bot.TicketsPlusBot(...)
await bot_instance.load_extension("tickets_plus.cogs.routines")
  1"""Tasks that run in the background from time to time.
  2
  3This is where the bot's background tasks are defined. These tasks are
  4scheduled to run at a specific interval, and are generally used to
  5perform some sort of maintenance or cleanup. Like cleaning up users
  6who are no longer prevented from something.
  7
  8Typical usage example:
  9    ```py
 10    from tickets_plus import bot
 11    bot_instance = bot.TicketsPlusBot(...)
 12    await bot_instance.load_extension("tickets_plus.cogs.routines")
 13    ```
 14"""
 15# License: EPL-2.0
 16# SPDX-License-Identifier: EPL-2.0
 17# Copyright (c) 2021-present The Tickets+ Contributors
 18# This Source Code may also be made available under the following
 19# Secondary Licenses when the conditions for such availability set forth
 20# in the Eclipse Public License, v. 2.0 are satisfied: GPL-3.0-only OR
 21# If later approved by the Initial Contributor, GPL-3.0-or-later.
 22
 23from discord.ext import commands, tasks
 24
 25from tickets_plus import bot
 26from tickets_plus.database import config
 27
 28_CNFG = config.RuntimeConfig()
 29
 30
 31class Routines(commands.Cog):
 32    """Magic cog for background tasks.
 33
 34    We do the *real* magic here. This is where the background tasks
 35    are defined. These tasks are scheduled to run at a specific
 36    interval, and are generally used to perform some sort of
 37    maintenance or cleanup.
 38    """
 39
 40    def __init__(self, bot_instance: bot.TicketsPlusBot):
 41        """Initialize the cog.
 42
 43        This is called when the cog is loaded, and initializes the
 44        background tasks.
 45
 46        Args:
 47            bot_instance: The bot instance that loaded the cog.
 48        """
 49        self._bt = bot_instance
 50        self.clean_status.start()
 51        self.notify_users.start()
 52
 53    async def cog_unload(self):
 54        """Cancel all tasks when the cog is unloaded.
 55
 56        This is called when the cog is unloaded, and cancels all
 57        tasks that are running.
 58        """
 59        self.clean_status.cancel()
 60        self.notify_users.cancel()
 61
 62    @tasks.loop(seconds=_CNFG.spt_clean_usr)
 63    async def clean_status(self):
 64        """Remove all status roles from users whose status has expired.
 65
 66        This task runs every minute, and checks if any users have a
 67        status role that has expired. If so, it removes the role from
 68        the user.
 69        """
 70        async with self._bt.get_connection() as conn:
 71            rehabilitated = await conn.get_expired_members()
 72            for member in rehabilitated:
 73                gld = member.guild
 74                usr_id = member.user_id
 75                actv_guild = self._bt.get_guild(gld.guild_id)
 76                if actv_guild is None:
 77                    continue
 78                actv_member = actv_guild.get_member(usr_id)
 79                if actv_member is None:
 80                    continue
 81                rles = []
 82                if gld.helping_block:
 83                    rles.append(actv_guild.get_role(gld.helping_block))
 84                if gld.support_block:
 85                    rles.append(actv_guild.get_role(gld.support_block))
 86                await actv_member.remove_roles(*rles, reason="Status expired")
 87                member.status = 0
 88                member.status_till = None
 89            await conn.commit()
 90
 91    @clean_status.before_loop
 92    async def before_clean_status(self):
 93        """Delay the first run till the bot is ready.
 94
 95        Ensures that the bot is ready before the first run of the
 96        task.
 97        """
 98        await self._bt.wait_until_ready()
 99
100    @tasks.loop(seconds=_CNFG.spt_notif_usr)
101    async def notify_users(self):
102        """Notifies users of their tickets closing soon.
103
104        Running every 2 minutes and 30 seconds, this task checks if
105        any tickets are lacking responses,
106        (time since last message above warning threshold)
107        and if so, sends a warning message to the user.
108        """
109        async with self._bt.get_connection() as conn:
110            tickets = await conn.get_pending_tickets()
111            for ticket in tickets:
112                ticket.notified = True
113                gld = ticket.guild
114                usr_id = ticket.user_id
115                if usr_id is None:
116                    continue
117                actv_guild = self._bt.get_guild(gld.guild_id)
118                if actv_guild is None:
119                    continue
120                actv_member = actv_guild.get_member(usr_id)
121                if actv_member is None:
122                    continue
123                appnd = "Please respond soon, or it will be closed."
124                if gld.any_autoclose:
125                    appnd = (
126                        "Please respond soon, or it will be closed "
127                        # pylint: disable=line-too-long # skipcq: FLK-E501
128                        f"<t:{int((ticket.last_response + gld.any_autoclose).timestamp())}:R>.")
129                txt = (f"Your ticket <#{ticket.channel_id}> in {actv_guild.name} "
130                       f"is still open. {appnd}")
131                await actv_member.send(txt)
132            await conn.commit()
133
134    @notify_users.before_loop
135    async def before_notify_users(self):
136        """Delay the first run till the bot is ready.
137
138        Ensures that the bot is ready before the first run of the
139        task.
140        """
141        await self._bt.wait_until_ready()
142
143
144async def setup(bot_instance: bot.TicketsPlusBot):
145    """Load the cog into the bot.
146
147    This is called when the cog is loaded, and initializes the
148    cog instance.
149
150    Args:
151        bot_instance: The bot instance that loaded the cog.
152    """
153    await bot_instance.add_cog(Routines(bot_instance))
class Routines(discord.ext.commands.cog.Cog):
 32class Routines(commands.Cog):
 33    """Magic cog for background tasks.
 34
 35    We do the *real* magic here. This is where the background tasks
 36    are defined. These tasks are scheduled to run at a specific
 37    interval, and are generally used to perform some sort of
 38    maintenance or cleanup.
 39    """
 40
 41    def __init__(self, bot_instance: bot.TicketsPlusBot):
 42        """Initialize the cog.
 43
 44        This is called when the cog is loaded, and initializes the
 45        background tasks.
 46
 47        Args:
 48            bot_instance: The bot instance that loaded the cog.
 49        """
 50        self._bt = bot_instance
 51        self.clean_status.start()
 52        self.notify_users.start()
 53
 54    async def cog_unload(self):
 55        """Cancel all tasks when the cog is unloaded.
 56
 57        This is called when the cog is unloaded, and cancels all
 58        tasks that are running.
 59        """
 60        self.clean_status.cancel()
 61        self.notify_users.cancel()
 62
 63    @tasks.loop(seconds=_CNFG.spt_clean_usr)
 64    async def clean_status(self):
 65        """Remove all status roles from users whose status has expired.
 66
 67        This task runs every minute, and checks if any users have a
 68        status role that has expired. If so, it removes the role from
 69        the user.
 70        """
 71        async with self._bt.get_connection() as conn:
 72            rehabilitated = await conn.get_expired_members()
 73            for member in rehabilitated:
 74                gld = member.guild
 75                usr_id = member.user_id
 76                actv_guild = self._bt.get_guild(gld.guild_id)
 77                if actv_guild is None:
 78                    continue
 79                actv_member = actv_guild.get_member(usr_id)
 80                if actv_member is None:
 81                    continue
 82                rles = []
 83                if gld.helping_block:
 84                    rles.append(actv_guild.get_role(gld.helping_block))
 85                if gld.support_block:
 86                    rles.append(actv_guild.get_role(gld.support_block))
 87                await actv_member.remove_roles(*rles, reason="Status expired")
 88                member.status = 0
 89                member.status_till = None
 90            await conn.commit()
 91
 92    @clean_status.before_loop
 93    async def before_clean_status(self):
 94        """Delay the first run till the bot is ready.
 95
 96        Ensures that the bot is ready before the first run of the
 97        task.
 98        """
 99        await self._bt.wait_until_ready()
100
101    @tasks.loop(seconds=_CNFG.spt_notif_usr)
102    async def notify_users(self):
103        """Notifies users of their tickets closing soon.
104
105        Running every 2 minutes and 30 seconds, this task checks if
106        any tickets are lacking responses,
107        (time since last message above warning threshold)
108        and if so, sends a warning message to the user.
109        """
110        async with self._bt.get_connection() as conn:
111            tickets = await conn.get_pending_tickets()
112            for ticket in tickets:
113                ticket.notified = True
114                gld = ticket.guild
115                usr_id = ticket.user_id
116                if usr_id is None:
117                    continue
118                actv_guild = self._bt.get_guild(gld.guild_id)
119                if actv_guild is None:
120                    continue
121                actv_member = actv_guild.get_member(usr_id)
122                if actv_member is None:
123                    continue
124                appnd = "Please respond soon, or it will be closed."
125                if gld.any_autoclose:
126                    appnd = (
127                        "Please respond soon, or it will be closed "
128                        # pylint: disable=line-too-long # skipcq: FLK-E501
129                        f"<t:{int((ticket.last_response + gld.any_autoclose).timestamp())}:R>.")
130                txt = (f"Your ticket <#{ticket.channel_id}> in {actv_guild.name} "
131                       f"is still open. {appnd}")
132                await actv_member.send(txt)
133            await conn.commit()
134
135    @notify_users.before_loop
136    async def before_notify_users(self):
137        """Delay the first run till the bot is ready.
138
139        Ensures that the bot is ready before the first run of the
140        task.
141        """
142        await self._bt.wait_until_ready()

Magic cog for background tasks.

We do the real magic here. This is where the background tasks are defined. These tasks are scheduled to run at a specific interval, and are generally used to perform some sort of maintenance or cleanup.

Routines(bot_instance: tickets_plus.bot.TicketsPlusBot)
41    def __init__(self, bot_instance: bot.TicketsPlusBot):
42        """Initialize the cog.
43
44        This is called when the cog is loaded, and initializes the
45        background tasks.
46
47        Args:
48            bot_instance: The bot instance that loaded the cog.
49        """
50        self._bt = bot_instance
51        self.clean_status.start()
52        self.notify_users.start()

Initialize the cog.

This is called when the cog is loaded, and initializes the background tasks.

Arguments:
  • bot_instance: The bot instance that loaded the cog.
async def cog_unload(self):
54    async def cog_unload(self):
55        """Cancel all tasks when the cog is unloaded.
56
57        This is called when the cog is unloaded, and cancels all
58        tasks that are running.
59        """
60        self.clean_status.cancel()
61        self.notify_users.cancel()

Cancel all tasks when the cog is unloaded.

This is called when the cog is unloaded, and cancels all tasks that are running.

def clean_status(unknown):

A background task helper that abstracts the loop and reconnection logic for you.

The main interface to create this is through loop().

@clean_status.before_loop
async def before_clean_status(self):
92    @clean_status.before_loop
93    async def before_clean_status(self):
94        """Delay the first run till the bot is ready.
95
96        Ensures that the bot is ready before the first run of the
97        task.
98        """
99        await self._bt.wait_until_ready()

Delay the first run till the bot is ready.

Ensures that the bot is ready before the first run of the task.

def notify_users(unknown):

A background task helper that abstracts the loop and reconnection logic for you.

The main interface to create this is through loop().

@notify_users.before_loop
async def before_notify_users(self):
135    @notify_users.before_loop
136    async def before_notify_users(self):
137        """Delay the first run till the bot is ready.
138
139        Ensures that the bot is ready before the first run of the
140        task.
141        """
142        await self._bt.wait_until_ready()

Delay the first run till the bot is ready.

Ensures that the bot is ready before the first run of the task.

async def setup(bot_instance: tickets_plus.bot.TicketsPlusBot):
145async def setup(bot_instance: bot.TicketsPlusBot):
146    """Load the cog into the bot.
147
148    This is called when the cog is loaded, and initializes the
149    cog instance.
150
151    Args:
152        bot_instance: The bot instance that loaded the cog.
153    """
154    await bot_instance.add_cog(Routines(bot_instance))

Load the cog into the bot.

This is called when the cog is loaded, and initializes the cog instance.

Arguments:
  • bot_instance: The bot instance that loaded the cog.