Пример #1
0
	async def guild_stats(self, ctx):
		cutoff = datetime.datetime.utcnow() - datetime.timedelta(weeks=4)
		e = discord.Embed(title='Page stats')
		# no transaction because maybe doing a lot of COUNTing would require table wide locks
		# to maintain consistency (dunno, just a hunch)
		async with self.bot.pool.acquire() as conn:
			connection.set(conn)
			page_count = await self.db.page_count(ctx.guild.id)
			revisions_count = await self.db.revisions_count(ctx.guild.id)
			total_page_uses = await self.db.total_page_uses(ctx.guild.id, cutoff=cutoff)
			e.description = f'{page_count} pages, {revisions_count} revisions, {total_page_uses} recent page uses'

			first_place = ord('🥇')

			top_pages = await self.db.top_pages(ctx.guild.id, cutoff=cutoff)
			if top_pages:
				value = '\n'.join(
					f'{chr(first_place + i)} {page.title} ({page.count} recent uses)'
					for i, page in enumerate(top_pages))
			else:
				value = 'No recent page uses.'

			e.add_field(name='Top pages', inline=False, value=value)

			top_editors = await self.db.top_editors(ctx.guild.id, cutoff=cutoff)
			if top_editors:
				value = '\n'.join(
					f'{chr(first_place + i)} <@{editor.id}> ({editor.count} revisions)'
					for i, editor in enumerate(top_editors))
			else:
				value = 'No recent page edits.'

			e.add_field(name='Top editors', inline=False, value=value)

		await ctx.send(embed=e)
Пример #2
0
    async def set_timer(self,
                        ctx,
                        channel: typing.Optional[discord.TextChannel] = None,
                        *,
                        expiry: ShortTime):
        """Set the disappearing messages timer for the given channel or the current one.

		Messages sent in that channel will be deleted after the given amount of time.
		You must have the Manage Channels permission on that channel in order to set the disappearing messages
		timer.
		"""
        channel = channel or ctx.channel
        if not channel.permissions_for(ctx.author).manage_channels:
            raise commands.MissingPermissions(['manage_channels'])

        # for consistency with already having a timer, also delete the invoking message
        # even when no timer is set
        async with self.to_keep_locks[channel.id]:
            self.to_keep[channel.id].add(ctx.message.id)
            await self.db.create_timer(ctx.message, expiry)

        async with self.bot.pool.acquire() as conn, conn.transaction():
            connection.set(conn)
            await self.db.set_expiry(channel, expiry)

            emoji = self.bot.config['timer_change_emoji']
            async with self.to_keep_locks[channel.id]:
                m = await channel.send(
                    f'{emoji} {ctx.author.mention} set the disappearing message timer to '
                    f'**{absolute_natural_timedelta(expiry.total_seconds())}**.'
                )
                self.to_keep[channel.id].add(m.id)
            await self.db.set_last_timer_change(channel, m.id)
Пример #3
0
	async def compare(self, ctx, revision_id_1: int, revision_id_2: int):
		"""Compares two page revisions by their ID.

		To get the revision ID you can use the history command.
		The revisions will always be compared from oldest to newest, regardless of the order you specify.
		You need the "edit pages" permission to use this command.
		"""
		if revision_id_1 == revision_id_2:
			await ctx.send('Provided revision IDs must be distinct.')
			return

		async with self.bot.pool.acquire() as conn:
			connection.set(conn)
			try:
				old, new = await self.db.get_individual_revisions(ctx.guild.id, (revision_id_1, revision_id_2))
			except ValueError:
				await ctx.send(
					'One or more provided revision IDs were invalid. '
					f'Use the {ctx.prefix}history command to get valid revision IDs.')
				return
			await self.db.check_permissions(ctx.author, Permissions.edit, new.current_title)

		with contextlib.suppress(discord.NotFound):
			old.author = await utils.fetch_member(ctx.guild, old.author_id)

		with contextlib.suppress(discord.NotFound):
			new.author = await utils.fetch_member(ctx.guild, new.author_id)

		await TextPages(ctx, self.diff(old, new), prefix='', suffix='').begin()
Пример #4
0
	async def raw(self, ctx, *, title: clean_content):
		"""Shows the raw contents of a page.

		This is with markdown escaped, which is useful for editing.
		"""
		async with self.bot.pool.acquire() as conn, conn.transaction():
			connection.set(conn)
			page = await self.db.get_page(ctx.author, title)
			await self.db.log_page_use(ctx.guild.id, title)

		# replace emojis with their names for mobile users, since on android at least, copying a message
		# with emojis in it copies just the name, not the name and colons
		# we also don't want the user to see the raw <:name:1234> form because they can't send that directly
		escaped = self.emoji_escape_regex.sub(r'\1', page.content)
		if len(escaped) > 2000:
			# in this case we don't want to send the fully escaped version
			# since there is no markdown in a plaintext file
			await ctx.send(discord.File(io.StringIO(escaped), page.title + '.md'))
		else:
			# escape_markdown messes up emojis for mobile users
			escaped2 = self.emoji_remove_escaped_underscores_regex.sub(
				lambda m: m[0].replace(r'\_', '_'), discord.utils.escape_markdown(escaped))
			if len(escaped2) > 2000:
				await ctx.send(file=discord.File(io.StringIO(escaped), page.title + '.md'))
			await ctx.send(escaped2)
Пример #5
0
	async def fileraw(self, ctx, *, title: clean_content):
		"""Shows the raw contents of a page in a file attachment."""
		async with self.bot.pool.acquire() as conn, conn.transaction():
			connection.set(conn)
			page = await self.db.get_page(ctx.author, title)
			await self.db.log_page_use(ctx.guild.id, title)

		escaped = self.emoji_escape_regex.sub(r'\1', page.content)
		await ctx.send(file=discord.File(io.StringIO(escaped), page.title + '.md'))
Пример #6
0
	async def page(self, ctx, *, title: clean_content):
		"""Shows you the contents of the page requested."""
		async with self.bot.pool.acquire() as conn, conn.transaction():
			connection.set(conn)
			page = await self.db.get_page(ctx.author, title)
			await self.db.log_page_use(ctx.guild.id, title)
		# it's kind of confusing to show a warning and an error, so only show the deprecation warning in the success
		# case
		if ctx.invoked_with in ctx.command.aliases:
			await ctx.send(
				f'⚠️ __{ctx.prefix}show__ and __{ctx.prefix}view__ will be removed in the future. '
				f'Please use __{ctx.prefix}page__ instead.'
			)
		await ctx.send(page.content)
Пример #7
0
	async def coderaw(self, ctx, *, title: clean_content):
		"""Shows the raw contents of a page in a code block.

		This is for some tricky markdown that is hard to show outside of a code block, like ">" at the end of a link.
		"""
		async with self.bot.pool.acquire() as conn, conn.transaction():
			connection.set(conn)
			page = await self.db.get_page(ctx.author, title)
			await self.db.log_page_use(ctx.guild.id, title)

		emoji_escaped = self.emoji_escape_regex.sub(r'\1', page.content)
		code_blocked = utils.code_block(utils.escape_code_blocks(emoji_escaped))
		if len(code_blocked) > 2000:
			await ctx.send(file=discord.File(io.StringIO(emoji_escaped), page.title + '.md'))
		else:
			await ctx.send(code_blocked)
Пример #8
0
    async def _dispatch_timers(self):
        try:
            while not self.bot.is_closed():
                # for some reason this is necessary, even with @optional_connection
                async with self.bot.pool.acquire() as conn:
                    connection.set(conn)
                    timer = self.current_timer = await self._wait_for_active_timer(
                    )

                await timer.sleep_until_complete()
                await self._handle_timer(timer)
        except (OSError, discord.ConnectionClosed,
                asyncpg.PostgresConnectionError,
                asyncpg.InterfaceError) as exc:
            logger.warning('Timer dispatching restarting due to %r', exc)
            self.task.cancel()
            self.task = self.bot.loop.create_task(self._dispatch_timers())
Пример #9
0
	async def revert(self, ctx, title: clean_content, revision_id: int):
		"""Reverts a page to a previous revision ID.
		To get the revision ID, you can use the history command.
		"""
		async with self.bot.pool.acquire() as conn, conn.transaction(isolation='serializable'):
			connection.set(conn)
			try:
				revision = await self.db.get_revision(ctx.guild.id, revision_id)
			except ValueError:
				await ctx.send(f'Error: revision not found. Try using the {ctx.prefix}history command to find revisions.')
				return

			if revision.current_title.lower() != title.lower():
				await ctx.send('Error: This revision is for another page.')
				return

			await self.db.revise_page(ctx.author, title, revision.content)

		await ctx.message.add_reaction(self.bot.config['success_emojis'][True])
Пример #10
0
    async def delete_timer(self, ctx, channel: discord.TextChannel = None):
        """Delete the disappearing messages timer for the given channel or the current one.

		Messages sent in that channel will no longer be deleted.
		You must have the Manage Channels permission on that channel in order to delete the disappearing messages
		timer.
		"""
        channel = channel or ctx.channel
        if not channel.permissions_for(ctx.author).manage_channels:
            raise commands.MissingPermissions(['manage_channels'])

        async with self.bot.pool.acquire() as conn, conn.transaction():
            connection.set(conn)
            await self.db.delete_expiry(channel)
            await self.db.delete_last_timer_change(channel.id)

        async with self.to_keep_locks[channel.id]:
            emoji = self.bot.config['timer_disable_emoji']
            m = await channel.send(
                f'{emoji} {ctx.author.mention} disabled disappearing messages.'
            )
            self.to_keep[channel.id].add(m.id)
Пример #11
0
    async def bind(self, ctx, target: OwnMessageOrChannel, *,
                   title: clean_content):
        """Bind a message to a page. Whenever the page is edited, the message will be edited too.

		You can supply either a message that the bot has sent, or a #channel mention.
		Messages can be provided by ID, channel_id-message_id (obtained via shift clicking on "Copy ID"),
		or by a link to the message.

		If a binding already exists for the given message, it will be updated.
		"""
        if isinstance(target, discord.TextChannel):
            async with self.bot.pool.acquire() as conn, conn.transaction():
                connection.set(conn)
                # I really don't like this design as it requires me to look up the page by title three times
                # Probably some more thought has to go into the separation of concerns between
                # the DB cogs and the Commands cogs.
                await self.wiki_db.check_permissions(
                    ctx.author, Permissions.manage_bindings, title)
                page = await self.wiki_db.get_page(ctx.author, title)

                try:
                    message = await target.send(page.content)
                except discord.Forbidden:
                    raise commands.UserInputError(
                        "I can't send messages to that channel.")

                await self.db.bind(ctx.author,
                                   message,
                                   title,
                                   check_permissions=False)
        else:
            page = await self.db.bind(ctx.author, target, title)
            try:
                await target.edit(content=page.content)
            except discord.Forbidden:
                raise commands.UserInput("I can't edit that message.")

        await ctx.message.add_reaction(self.bot.config['success_emojis'][True])
Пример #12
0
	async def history(self, ctx, *, title: clean_content):
		"""Shows the revisions of a particular page"""

		async with self.bot.pool.acquire() as conn:
			connection.set(conn)
			page = await self.db.resolve_page(ctx.author, title)
			if page.alias:
				await ctx.send(f'“{page.alias}” is an alias. Try {ctx.prefix}{ctx.invoked_with} {page.target}.')
				return

			revisions = [x async for x in self.db.get_page_revisions(ctx.author, title)]

		if not revisions:
			raise errors.PageNotFoundError(title)

		async def set_author(revision):
			revision.author = await utils.fetch_member(ctx.guild, revision.author_id)

		await asyncio.gather(
			*(asyncio.create_task(set_author(revision)) for revision in revisions),
			return_exceptions=True,
		)

		await Pages(ctx, entries=list(map(self.revision_summary, revisions)), numbered=False).begin()
Пример #13
0
	async def page_stats(self, ctx, title):
		cutoff = datetime.datetime.utcnow() - datetime.timedelta(weeks=4)

		e = discord.Embed(title=f'Stats for “{title}”')
		async with self.bot.pool.acquire() as conn:
			connection.set(conn)
			page = await self.db.get_page(ctx.author, title, partial=True)
			if page.alias:  # TODO try and defer this HTTP call till after conn is closed
				await ctx.send(f'That page is an alias. Try {ctx.prefix}{ctx.invoked_with} {page.original}.')
				return

			top_editors = await self.db.top_page_editors(ctx.guild.id, title)
			revisions_count = await self.db.page_revisions_count(ctx.guild.id, title)
			usage_count = await self.db.page_uses(ctx.guild.id, title, cutoff=cutoff)

		e = discord.Embed(title=f'Stats for {page.original}')
		e.description = f'{revisions_count} all time revisions, {usage_count} recent uses'

		first_place = ord('🥇')
		e.add_field(name='Top editors', inline=False, value='\n'.join(
			f'{chr(first_place + i)} <@{editor.id}> authored {editor.rank:.2%} ({editor.count}) revisions recently'
			for i, editor in enumerate(top_editors)))

		await ctx.send(embed=e)
Пример #14
0
 async def unmark(self, context, *positions: str.upper):
     async with self.bot.pool.acquire() as conn:
         connection.set(conn)
         await self.db.unmark(context.author.id, positions)
         await self.send_board(context, _('Your new bingo board:'), await
                               self.db.get_board(context.author.id))