コード例 #1
0
ファイル: duck_pond.py プロジェクト: Kronifer/bot
 def __init__(self, bot: Bot):
     self.bot = bot
     self.webhook_id = constants.Webhooks.duck_pond
     self.webhook = None
     self.ducked_messages = []
     scheduling.create_task(self.fetch_webhook(), event_loop=self.bot.loop)
     self.relay_lock = None
コード例 #2
0
ファイル: _cog.py プロジェクト: dolphingarlic/bot
    async def claim_channel(self, message: discord.Message) -> None:
        """
        Claim the channel in which the question `message` was sent.

        Move the channel to the In Use category and pin the `message`. Add a cooldown to the
        claimant to prevent them from asking another question. Lastly, make a new channel available.
        """
        log.info(f"Channel #{message.channel} was claimed by `{message.author.id}`.")
        await self.move_to_in_use(message.channel)
        await _cooldown.revoke_send_permissions(message.author, self.scheduler)

        await _message.pin(message)
        try:
            await _message.dm_on_open(message)
        except Exception as e:
            log.warning("Error occurred while sending DM:", exc_info=e)

        # Add user with channel for dormant check.
        await _caches.claimants.set(message.channel.id, message.author.id)

        self.bot.stats.incr("help.claimed")

        # datetime.timestamp() would assume it's local, despite d.py giving a (naïve) UTC time.
        timestamp = arrow.Arrow.fromdatetime(message.created_at).timestamp()

        await _caches.claim_times.set(message.channel.id, timestamp)
        await _caches.claimant_last_message_times.set(message.channel.id, timestamp)
        # Delete to indicate that the help session has yet to receive an answer.
        await _caches.non_claimant_last_message_times.delete(message.channel.id)

        # Removing the help channel from the dynamic message, and editing/sending that message.
        self.available_help_channels.remove(message.channel)

        # Not awaited because it may indefinitely hold the lock while waiting for a channel.
        scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}")
コード例 #3
0
    async def claim_channel(self, message: discord.Message) -> None:
        """
        Claim the channel in which the question `message` was sent.

        Move the channel to the In Use category and pin the `message`. Add a cooldown to the
        claimant to prevent them from asking another question. Lastly, make a new channel available.
        """
        log.info(
            f"Channel #{message.channel} was claimed by `{message.author.id}`."
        )
        await self.move_to_in_use(message.channel)
        await _cooldown.revoke_send_permissions(message.author, self.scheduler)

        await _message.pin(message)
        try:
            await _message.dm_on_open(message)
        except Exception as e:
            log.warning("Error occurred while sending DM:", exc_info=e)

        # Add user with channel for dormant check.
        await _caches.claimants.set(message.channel.id, message.author.id)

        self.bot.stats.incr("help.claimed")

        # Must use a timezone-aware datetime to ensure a correct POSIX timestamp.
        timestamp = datetime.now(timezone.utc).timestamp()
        await _caches.claim_times.set(message.channel.id, timestamp)

        await _caches.unanswered.set(message.channel.id, True)

        # Not awaited because it may indefinitely hold the lock while waiting for a channel.
        scheduling.create_task(self.move_to_available(),
                               name=f"help_claim_{message.id}")
コード例 #4
0
ファイル: python_news.py プロジェクト: Kronifer/bot
    def __init__(self, bot: Bot):
        self.bot = bot
        self.webhook_names = {}
        self.webhook: t.Optional[discord.Webhook] = None

        scheduling.create_task(self.get_webhook_names(), event_loop=self.bot.loop)
        scheduling.create_task(self.get_webhook_and_channel(), event_loop=self.bot.loop)
コード例 #5
0
ファイル: _scheduler.py プロジェクト: Kronifer/bot
    def __init__(self, bot: Bot, supported_infractions: t.Container[str]):
        self.bot = bot
        self.scheduler = scheduling.Scheduler(self.__class__.__name__)

        scheduling.create_task(
            self.reschedule_infractions(supported_infractions),
            event_loop=self.bot.loop)
コード例 #6
0
ファイル: _batch_parser.py プロジェクト: kwzrd/pydis-bot
    async def _parse_queue(self) -> None:
        """
        Parse all items from the queue, setting their result Markdown on the futures and sending them to redis.

        The coroutine will run as long as the queue is not empty, resetting `self._parse_task` to None when finished.
        """
        log.trace("Starting queue parsing.")
        try:
            while self._queue:
                item, soup = self._queue.pop()
                markdown = None

                if (future := self._item_futures[item]).done():
                    # Some items are present in the inventories multiple times under different symbol names,
                    # if we already parsed an equal item, we can just skip it.
                    continue

                try:
                    markdown = await bot.instance.loop.run_in_executor(None, get_symbol_markdown, soup, item)
                    if markdown is not None:
                        await doc_cache.set(item, markdown)
                    else:
                        # Don't wait for this coro as the parsing doesn't depend on anything it does.
                        scheduling.create_task(
                            self.stale_inventory_notifier.send_warning(item), name="Stale inventory warning"
                        )
                except Exception:
                    log.exception(f"Unexpected error when handling {item}")
                future.set_result(markdown)
                del self._item_futures[item]
                await asyncio.sleep(0.1)
        finally:
            self._parse_task = None
            log.trace("Finished parsing queue.")
コード例 #7
0
ファイル: _cog.py プロジェクト: Kronifer/bot
    def __init__(self, bot: Bot) -> None:
        """Instantiate repository abstraction & allow daemon to start."""
        self.bot = bot
        self.repository = BrandingRepository(bot)

        scheduling.create_task(
            self.maybe_start_daemon(),
            event_loop=self.bot.loop)  # Start depending on cache.
コード例 #8
0
ファイル: defcon.py プロジェクト: Kronifer/bot
    def __init__(self, bot: Bot):
        self.bot = bot
        self.channel = None
        self.threshold = relativedelta(days=0)
        self.expiry = None

        self.scheduler = Scheduler(self.__class__.__name__)

        scheduling.create_task(self._sync_settings(), event_loop=self.bot.loop)
コード例 #9
0
ファイル: decorators.py プロジェクト: Kronifer/bot
        async def inner(self: Cog, ctx: Context, *args, **kwargs) -> None:
            if ctx.channel.id == destination_channel:
                log.trace(
                    f"Command {ctx.command} was invoked in destination_channel, not redirecting"
                )
                await func(self, ctx, *args, **kwargs)
                return

            if bypass_roles and any(role.id in bypass_roles
                                    for role in ctx.author.roles):
                log.trace(
                    f"{ctx.author} has role to bypass output redirection")
                await func(self, ctx, *args, **kwargs)
                return

            elif channels and ctx.channel.id not in channels:
                log.trace(
                    f"{ctx.author} used {ctx.command} in a channel that can bypass output redirection"
                )
                await func(self, ctx, *args, **kwargs)
                return

            elif categories and ctx.channel.category.id not in categories:
                log.trace(
                    f"{ctx.author} used {ctx.command} in a category that can bypass output redirection"
                )
                await func(self, ctx, *args, **kwargs)
                return

            redirect_channel = ctx.guild.get_channel(destination_channel)
            old_channel = ctx.channel

            log.trace(
                f"Redirecting output of {ctx.author}'s command '{ctx.command.name}' to {redirect_channel.name}"
            )
            ctx.channel = redirect_channel

            if ping_user:
                await ctx.send(
                    f"Here's the output of your command, {ctx.author.mention}")
            scheduling.create_task(func(self, ctx, *args, **kwargs))

            message = await old_channel.send(
                f"Hey, {ctx.author.mention}, you can find the output of your command here: "
                f"{redirect_channel.mention}")
            if RedirectOutput.delete_invocation:
                await asyncio.sleep(RedirectOutput.delete_delay)

                with suppress(NotFound):
                    await message.delete()
                    log.trace(
                        "Redirect output: Deleted user redirection message")

                with suppress(NotFound):
                    await ctx.message.delete()
                    log.trace("Redirect output: Deleted invocation message")
コード例 #10
0
    def __init__(self, bot: Bot) -> None:
        """Prepare `event_lock` and schedule `crawl_task` on start-up."""
        self.bot = bot
        self.incidents_webhook = None

        scheduling.create_task(self.fetch_webhook(), event_loop=self.bot.loop)

        self.event_lock = asyncio.Lock()
        self.crawl_task = scheduling.create_task(self.crawl_incidents(),
                                                 event_loop=self.bot.loop)
コード例 #11
0
ファイル: _cog.py プロジェクト: gsdev215/bot
    def __init__(self, bot: Bot) -> None:
        self.bot = bot
        self.reviewer = Reviewer(self.__class__.__name__, bot, self)
        self.cache: Optional[defaultdict[dict]] = None
        self.api_default_params = {
            'active': 'true',
            'ordering': '-inserted_at'
        }

        self.initial_refresh_task = scheduling.create_task(
            self.refresh_cache(), event_loop=self.bot.loop)
        scheduling.create_task(self.schedule_autoreviews(),
                               event_loop=self.bot.loop)
コード例 #12
0
ファイル: modpings.py プロジェクト: Kronifer/bot
    def __init__(self, bot: Bot):
        self.bot = bot
        self._role_scheduler = Scheduler("ModPingsOnOff")
        self._modpings_scheduler = Scheduler("ModPingsSchedule")

        self.guild = None
        self.moderators_role = None

        self.modpings_schedule_task = scheduling.create_task(
            self.reschedule_modpings_schedule(), event_loop=self.bot.loop)
        self.reschedule_task = scheduling.create_task(
            self.reschedule_roles(),
            name="mod-pings-reschedule",
            event_loop=self.bot.loop,
        )
コード例 #13
0
ファイル: _cog.py プロジェクト: Kronifer/bot
    def __init__(self, bot: Bot):
        self.bot = bot
        self.scheduler = scheduling.Scheduler(self.__class__.__name__)

        self.guild: discord.Guild = None
        self.cooldown_role: discord.Role = None

        # Categories
        self.available_category: discord.CategoryChannel = None
        self.in_use_category: discord.CategoryChannel = None
        self.dormant_category: discord.CategoryChannel = None

        # Queues
        self.channel_queue: asyncio.Queue[discord.TextChannel] = None
        self.name_queue: t.Deque[str] = None

        self.last_notification: t.Optional[arrow.Arrow] = None

        self.dynamic_message: t.Optional[int] = None
        self.available_help_channels: t.Set[discord.TextChannel] = set()

        # Asyncio stuff
        self.queue_tasks: t.List[asyncio.Task] = []
        self.init_task = scheduling.create_task(self.init_cog(),
                                                event_loop=self.bot.loop)
コード例 #14
0
ファイル: _watchchannel.py プロジェクト: Kronifer/bot
    def __init__(self,
                 bot: Bot,
                 destination: int,
                 webhook_id: int,
                 api_endpoint: str,
                 api_default_params: dict,
                 logger: CustomLogger,
                 *,
                 disable_header: bool = False) -> None:
        self.bot = bot

        self.destination = destination  # E.g., Channels.big_brother_logs
        self.webhook_id = webhook_id  # E.g.,  Webhooks.big_brother
        self.api_endpoint = api_endpoint  # E.g., 'bot/infractions'
        self.api_default_params = api_default_params  # E.g., {'active': 'true', 'type': 'watch'}
        self.log = logger  # Logger of the child cog for a correct name in the logs

        self._consume_task = None
        self.watched_users = defaultdict(dict)
        self.message_queue = defaultdict(lambda: defaultdict(deque))
        self.consumption_queue = {}
        self.retries = 5
        self.retry_delay = 10
        self.channel = None
        self.webhook = None
        self.message_history = MessageHistory()
        self.disable_header = disable_header

        self._start = scheduling.create_task(self.start_watchchannel(),
                                             event_loop=self.bot.loop)
コード例 #15
0
ファイル: _batch_parser.py プロジェクト: Kronifer/bot
 def __init__(self):
     self._init_task = scheduling.create_task(
         self._init_channel(),
         name="StaleInventoryNotifier channel init",
         event_loop=bot.instance.loop,
     )
     self._warned_urls = set()
コード例 #16
0
ファイル: _cog.py プロジェクト: Kronifer/bot
    async def send_instructions(self, message: discord.Message, instructions: str) -> None:
        """
        Send an embed with `instructions` on fixing an incorrect code block in a `message`.

        The embed will be deleted automatically after 5 minutes.
        """
        log.info(f"Sending code block formatting instructions for message {message.id}.")

        embed = self.create_embed(instructions)
        bot_message = await message.channel.send(f"Hey {message.author.mention}!", embed=embed)
        self.codeblock_message_ids[message.id] = bot_message.id

        scheduling.create_task(wait_for_deletion(bot_message, (message.author.id,)), event_loop=self.bot.loop)

        # Increase amount of codeblock correction in stats
        self.bot.stats.incr("codeblock_corrections")
コード例 #17
0
ファイル: _batch_parser.py プロジェクト: kwzrd/pydis-bot
    async def get_markdown(self, doc_item: _cog.DocItem) -> Optional[str]:
        """
        Get the result Markdown of `doc_item`.

        If no symbols were fetched from `doc_item`s page before,
        the HTML has to be fetched and then all items from the page are put into the parse queue.

        Not safe to run while `self.clear` is running.
        """
        if doc_item not in self._item_futures and doc_item not in self._queue:
            self._item_futures[doc_item].user_requested = True

            async with bot.instance.http_session.get(doc_item.url) as response:
                soup = await bot.instance.loop.run_in_executor(
                    None,
                    BeautifulSoup,
                    await response.text(encoding="utf8"),
                    "lxml",
                )

            self._queue.extendleft(QueueItem(item, soup) for item in self._page_doc_items[doc_item.url])
            log.debug(f"Added items from {doc_item.url} to the parse queue.")

            if self._parse_task is None:
                self._parse_task = scheduling.create_task(self._parse_queue(), name="Queue parse")
        else:
            self._item_futures[doc_item].user_requested = True
        with suppress(ValueError):
            # If the item is not in the queue then the item is already parsed or is being parsed
            self._move_to_front(doc_item)
        return await self._item_futures[doc_item]
コード例 #18
0
ファイル: reminders.py プロジェクト: Kronifer/bot
    def ensure_valid_reminder(
            self, reminder: dict
    ) -> t.Tuple[bool, discord.User, discord.TextChannel]:
        """Ensure reminder author and channel can be fetched otherwise delete the reminder."""
        user = self.bot.get_user(reminder['author'])
        channel = self.bot.get_channel(reminder['channel_id'])
        is_valid = True
        if not user or not channel:
            is_valid = False
            log.info(
                f"Reminder {reminder['id']} invalid: "
                f"User {reminder['author']}={user}, Channel {reminder['channel_id']}={channel}."
            )
            scheduling.create_task(
                self.bot.api_client.delete(f"bot/reminders/{reminder['id']}"))

        return is_valid, user, channel
コード例 #19
0
    async def send_eval(self, ctx: Context, code: str) -> Message:
        """
        Evaluate code, format it, and send the output to the corresponding channel.

        Return the bot response.
        """
        async with ctx.typing():
            results = await self.post_eval(code)
            msg, error = self.get_results_message(results)

            if error:
                output, paste_link = error, None
            else:
                output, paste_link = await self.format_output(results["stdout"]
                                                              )

            icon = self.get_status_emoji(results)
            msg = f"{ctx.author.mention} {icon} {msg}.\n\n```\n{output}\n```"
            if paste_link:
                msg = f"{msg}\nFull output: {paste_link}"

            # Collect stats of eval fails + successes
            if icon == ":x:":
                self.bot.stats.incr("snekbox.python.fail")
            else:
                self.bot.stats.incr("snekbox.python.success")

            filter_cog = self.bot.get_cog("Filtering")
            filter_triggered = False
            if filter_cog:
                filter_triggered = await filter_cog.filter_eval(
                    msg, ctx.message)
            if filter_triggered:
                response = await ctx.send(
                    "Attempt to circumvent filter detected. Moderator team has been alerted."
                )
            else:
                response = await ctx.send(msg)
            scheduling.create_task(wait_for_deletion(response,
                                                     (ctx.author.id, )),
                                   event_loop=self.bot.loop)

            log.info(
                f"{ctx.author}'s job had a return code of {results['returncode']}"
            )
        return response
コード例 #20
0
ファイル: _watchchannel.py プロジェクト: Kronifer/bot
    async def on_message(self, msg: Message) -> None:
        """Queues up messages sent by watched users."""
        if msg.author.id in self.watched_users:
            if not self.consuming_messages:
                self._consume_task = scheduling.create_task(
                    self.consume_messages(), event_loop=self.bot.loop)

            self.log.trace(
                f"Received message: {msg.content} ({len(msg.attachments)} attachments)"
            )
            self.message_queue[msg.author.id][msg.channel.id].append(msg)
コード例 #21
0
ファイル: antispam.py プロジェクト: Kronifer/bot
    def __init__(self, bot: Bot, validation_errors: Dict[str, str]) -> None:
        self.bot = bot
        self.validation_errors = validation_errors
        role_id = AntiSpamConfig.punishment['role_id']
        self.muted_role = Object(role_id)
        self.expiration_date_converter = Duration()

        self.message_deletion_queue = dict()

        # Fetch the rule configuration with the highest rule interval.
        max_interval_config = max(AntiSpamConfig.rules.values(),
                                  key=itemgetter('interval'))
        self.max_interval = max_interval_config['interval']
        self.cache = MessageCache(AntiSpamConfig.cache_size, newest_first=True)

        scheduling.create_task(
            self.alert_on_validation_error(),
            name="AntiSpam.alert_on_validation_error",
            event_loop=self.bot.loop,
        )
コード例 #22
0
    def schedule_expiration(
            self, loop: asyncio.AbstractEventLoop,
            infraction_object: Dict[str, Union[str, int, bool]]) -> None:
        """Schedules a task to expire a temporary infraction."""
        infraction_id = infraction_object["id"]
        if infraction_id in self.scheduled_tasks:
            return

        task: asyncio.Task = create_task(
            loop, self._scheduled_expiration(infraction_object))

        self.scheduled_tasks[infraction_id] = task
コード例 #23
0
ファイル: messages.py プロジェクト: gsdev215/bot
def reaction_check(
    reaction: discord.Reaction,
    user: discord.abc.User,
    *,
    message_id: int,
    allowed_emoji: Sequence[str],
    allowed_users: Sequence[int],
    allow_mods: bool = True,
) -> bool:
    """
    Check if a reaction's emoji and author are allowed and the message is `message_id`.

    If the user is not allowed, remove the reaction. Ignore reactions made by the bot.
    If `allow_mods` is True, allow users with moderator roles even if they're not in `allowed_users`.
    """
    right_reaction = (
        user != bot.instance.user
        and reaction.message.id == message_id
        and str(reaction.emoji) in allowed_emoji
    )
    if not right_reaction:
        return False

    is_moderator = (
        allow_mods
        and any(role.id in MODERATION_ROLES for role in getattr(user, "roles", []))
    )

    if user.id in allowed_users or is_moderator:
        log.trace(f"Allowed reaction {reaction} by {user} on {reaction.message.id}.")
        return True
    else:
        log.trace(f"Removing reaction {reaction} by {user} on {reaction.message.id}: disallowed user.")
        scheduling.create_task(
            reaction.message.remove_reaction(reaction.emoji, user),
            suppressed_exceptions=(discord.HTTPException,),
            name=f"remove_reaction-{reaction}-{reaction.message.id}-{user}"
        )
        return False
コード例 #24
0
ファイル: _cog.py プロジェクト: Kronifer/bot
    async def wait_for_dormant_channel(self) -> discord.TextChannel:
        """Wait for a dormant channel to become available in the queue and return it."""
        log.trace("Waiting for a dormant channel.")

        task = scheduling.create_task(self.channel_queue.get())
        self.queue_tasks.append(task)
        channel = await task

        log.trace(
            f"Channel #{channel} ({channel.id}) finally retrieved from the queue."
        )
        self.queue_tasks.remove(task)

        return channel
コード例 #25
0
    def __init__(self, bot: Bot) -> None:
        self.bot = bot
        self._session_scheduler = Scheduler(self.__class__.__name__)

        self.session_token: Optional[
            str] = None  # session_info["session_token"]: str
        self.session_expiry: Optional[
            float] = None  # session_info["session_expiry"]: UtcPosixTimestamp
        self.headers = BASE_HEADERS

        self.exports: Dict[int, List[Dict]] = {
        }  # Saves the output of each question, so internal eval can access it

        self.init_task = scheduling.create_task(self.init_cog(),
                                                event_loop=self.bot.loop)
コード例 #26
0
    def schedule_expiration(self, loop: asyncio.AbstractEventLoop,
                            infraction_object: dict):
        """
        Schedules a task to expire a temporary infraction.
        :param loop: the asyncio event loop
        :param infraction_object: the infraction object to expire at the end of the task
        """

        infraction_id = infraction_object["id"]
        if infraction_id in self.scheduled_tasks:
            return

        task: asyncio.Task = create_task(
            loop, self._scheduled_expiration(infraction_object))

        self.scheduled_tasks[infraction_id] = task
コード例 #27
0
    def make_confirmation_task(self,
                               incident: discord.Message,
                               timeout: int = 5) -> asyncio.Task:
        """
        Create a task to wait `timeout` seconds for `incident` to be deleted.

        If `timeout` passes, this will raise `asyncio.TimeoutError`, signaling that we haven't
        been able to confirm that the message was deleted.
        """
        log.trace(
            f"Confirmation task will wait {timeout=} seconds for {incident.id=} to be deleted"
        )

        def check(payload: discord.RawReactionActionEvent) -> bool:
            return payload.message_id == incident.id

        coroutine = self.bot.wait_for(event="raw_message_delete",
                                      check=check,
                                      timeout=timeout)
        return scheduling.create_task(coroutine, event_loop=self.bot.loop)
コード例 #28
0
    def __init__(self, bot: Bot):
        # Contains URLs to documentation home pages.
        # Used to calculate inventory diffs on refreshes and to display all currently stored inventories.
        self.base_urls = {}
        self.bot = bot
        self.doc_symbols: Dict[str, DocItem] = {
        }  # Maps symbol names to objects containing their metadata.
        self.item_fetcher = _batch_parser.BatchParser()
        # Maps a conflicting symbol name to a list of the new, disambiguated names created from conflicts with the name.
        self.renamed_symbols = defaultdict(list)

        self.inventory_scheduler = Scheduler(self.__class__.__name__)

        self.refresh_event = asyncio.Event()
        self.refresh_event.set()
        self.symbol_get_event = SharedEvent()

        self.init_refresh_task = scheduling.create_task(
            self.init_refresh_inventory(),
            name="Doc inventory init",
            event_loop=self.bot.loop,
        )
コード例 #29
0
ファイル: _watchchannel.py プロジェクト: Kronifer/bot
    async def consume_messages(self, delay_consumption: bool = True) -> None:
        """Consumes the message queues to log watched users' messages."""
        if delay_consumption:
            self.log.trace(
                f"Sleeping {BigBrotherConfig.log_delay} seconds before consuming message queue"
            )
            await asyncio.sleep(BigBrotherConfig.log_delay)

        self.log.trace("Started consuming the message queue")

        # If the previous consumption Task failed, first consume the existing comsumption_queue
        if not self.consumption_queue:
            self.consumption_queue = self.message_queue.copy()
            self.message_queue.clear()

        for user_channel_queues in self.consumption_queue.values():
            for channel_queue in user_channel_queues.values():
                while channel_queue:
                    msg = channel_queue.popleft()

                    self.log.trace(
                        f"Consuming message {msg.id} ({len(msg.attachments)} attachments)"
                    )
                    await self.relay_message(msg)

        self.consumption_queue.clear()

        if self.message_queue:
            self.log.trace(
                "Channel queue not empty: Continuing consuming queues")
            self._consume_task = scheduling.create_task(
                self.consume_messages(delay_consumption=False),
                event_loop=self.bot.loop,
            )
        else:
            self.log.trace("Done consuming messages.")
コード例 #30
0
ファイル: filter_lists.py プロジェクト: Kronifer/bot
 def __init__(self, bot: Bot) -> None:
     self.bot = bot
     scheduling.create_task(self._amend_docstrings(),
                            event_loop=self.bot.loop)