feat(command|prefix): add prefix commands (new, del, list)
known issue: `prefix list` return weird result when there is no custom prefix
This commit is contained in:
parent
d9427d1863
commit
97980e96d1
12 changed files with 1138 additions and 67 deletions
11
bot.py
11
bot.py
|
@ -28,6 +28,7 @@ l_extensions: List[str] = [
|
||||||
'cogs.utility',
|
'cogs.utility',
|
||||||
'cogs.logs',
|
'cogs.logs',
|
||||||
'cogs.poll',
|
'cogs.poll',
|
||||||
|
'cogs.help',
|
||||||
'jishaku',
|
'jishaku',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -38,8 +39,9 @@ async def _prefix_callable(bot, message: discord.message) -> list:
|
||||||
extras = []
|
extras = []
|
||||||
if str(message.guild.id) in bot.prefixes:
|
if str(message.guild.id) in bot.prefixes:
|
||||||
extras.extend(
|
extras.extend(
|
||||||
bot.prefixes.get(str(message.guild.id), "prefixes")
|
bot.prefixes.get(str(message.guild.id), "prefixes").split(
|
||||||
.split('-sep-')
|
bot.config.get("misc", "separator")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return commands.when_mentioned_or(*extras)(bot, message)
|
return commands.when_mentioned_or(*extras)(bot, message)
|
||||||
|
@ -67,7 +69,7 @@ class TuxBot(commands.AutoShardedBot):
|
||||||
self.prefixes = Config('./configs/prefixes.cfg')
|
self.prefixes = Config('./configs/prefixes.cfg')
|
||||||
self.blacklist = Config('./configs/blacklist.cfg')
|
self.blacklist = Config('./configs/blacklist.cfg')
|
||||||
|
|
||||||
self.version = Version(10, 1, 0, pre_release='a0', build=build)
|
self.version = Version(10, 1, 0, pre_release='a5', build=build)
|
||||||
|
|
||||||
for extension in l_extensions:
|
for extension in l_extensions:
|
||||||
try:
|
try:
|
||||||
|
@ -83,7 +85,8 @@ class TuxBot(commands.AutoShardedBot):
|
||||||
+ extension, exc_info=e)
|
+ extension, exc_info=e)
|
||||||
|
|
||||||
async def is_owner(self, user: discord.User) -> bool:
|
async def is_owner(self, user: discord.User) -> bool:
|
||||||
return str(user.id) in self.config.get("permissions", "owners").split(',')
|
return str(user.id) in self.config.get("permissions", "owners").split(
|
||||||
|
',')
|
||||||
|
|
||||||
async def on_socket_response(self, msg):
|
async def on_socket_response(self, msg):
|
||||||
self._prev_events.append(msg)
|
self._prev_events.append(msg)
|
||||||
|
|
169
cogs/admin.py
169
cogs/admin.py
|
@ -10,6 +10,7 @@ from discord.ext import commands
|
||||||
|
|
||||||
from bot import TuxBot
|
from bot import TuxBot
|
||||||
from .utils.lang import Texts
|
from .utils.lang import Texts
|
||||||
|
from .utils.extra import commandExtra, groupExtra
|
||||||
from .utils.models import Warn, Lang
|
from .utils.models import Warn, Lang
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -61,9 +62,10 @@ class Admin(commands.Cog):
|
||||||
|
|
||||||
return e
|
return e
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.group(name='say', invoke_without_command=True)
|
@groupExtra(name='say', invoke_without_command=True, category='admin',
|
||||||
|
description=Texts('commands').get('admin._say'))
|
||||||
async def _say(self, ctx: commands.Context, *, content: str):
|
async def _say(self, ctx: commands.Context, *, content: str):
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
try:
|
try:
|
||||||
|
@ -73,7 +75,8 @@ class Admin(commands.Cog):
|
||||||
|
|
||||||
await ctx.send(content)
|
await ctx.send(content)
|
||||||
|
|
||||||
@_say.command(name='edit')
|
@_say.command(name='edit',
|
||||||
|
description=Texts('commands').get('admin._say_edit'))
|
||||||
async def _say_edit(self, ctx: commands.Context, message_id: int, *,
|
async def _say_edit(self, ctx: commands.Context, message_id: int, *,
|
||||||
content: str):
|
content: str):
|
||||||
try:
|
try:
|
||||||
|
@ -90,7 +93,8 @@ class Admin(commands.Cog):
|
||||||
Texts('utils', ctx).get("Unable to find the message"),
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
delete_after=5)
|
delete_after=5)
|
||||||
|
|
||||||
@_say.command(name='to')
|
@_say.command(name='to',
|
||||||
|
description=Texts('commands').get('admin._say_to'))
|
||||||
async def _say_to(self, ctx: commands.Context,
|
async def _say_to(self, ctx: commands.Context,
|
||||||
channel: Union[discord.TextChannel, discord.User], *,
|
channel: Union[discord.TextChannel, discord.User], *,
|
||||||
content):
|
content):
|
||||||
|
@ -101,9 +105,10 @@ class Admin(commands.Cog):
|
||||||
|
|
||||||
await channel.send(content)
|
await channel.send(content)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='ban')
|
@commandExtra(name='ban', category='admin',
|
||||||
|
description=Texts('commands').get('admin._ban'))
|
||||||
async def _ban(self, ctx: commands.Context, user: discord.Member, *,
|
async def _ban(self, ctx: commands.Context, user: discord.Member, *,
|
||||||
reason=""):
|
reason=""):
|
||||||
try:
|
try:
|
||||||
|
@ -128,9 +133,10 @@ class Admin(commands.Cog):
|
||||||
Texts('utils', ctx).get("Unable to find the user..."),
|
Texts('utils', ctx).get("Unable to find the user..."),
|
||||||
delete_after=5)
|
delete_after=5)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='kick')
|
@commandExtra(name='kick', category='admin',
|
||||||
|
description=Texts('commands').get('admin._kick'))
|
||||||
async def _kick(self, ctx: commands.Context, user: discord.Member, *,
|
async def _kick(self, ctx: commands.Context, user: discord.Member, *,
|
||||||
reason=""):
|
reason=""):
|
||||||
try:
|
try:
|
||||||
|
@ -155,9 +161,10 @@ class Admin(commands.Cog):
|
||||||
Texts('utils', ctx).get("Unable to find the user..."),
|
Texts('utils', ctx).get("Unable to find the user..."),
|
||||||
delete_after=5)
|
delete_after=5)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='clear')
|
@commandExtra(name='clear', category='admin',
|
||||||
|
description=Texts('commands').get('admin._clear'))
|
||||||
async def _clear(self, ctx: commands.Context, count: int):
|
async def _clear(self, ctx: commands.Context, count: int):
|
||||||
try:
|
try:
|
||||||
await ctx.message.delete()
|
await ctx.message.delete()
|
||||||
|
@ -165,14 +172,16 @@ class Admin(commands.Cog):
|
||||||
except discord.errors.Forbidden:
|
except discord.errors.Forbidden:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.group(name='react')
|
@groupExtra(name='react', category='admin',
|
||||||
|
description=Texts('commands').get('admin._react'))
|
||||||
async def _react(self, ctx: commands.Context):
|
async def _react(self, ctx: commands.Context):
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
return
|
await ctx.send_help('react')
|
||||||
|
|
||||||
@_react.command(name='add')
|
@_react.command(name='add',
|
||||||
|
description=Texts('commands').get('admin._react_add'))
|
||||||
async def _react_add(self, ctx: commands.Context, message_id: int, *,
|
async def _react_add(self, ctx: commands.Context, message_id: int, *,
|
||||||
emojis: str):
|
emojis: str):
|
||||||
emojis: list = emojis.split(' ')
|
emojis: list = emojis.split(' ')
|
||||||
|
@ -188,7 +197,8 @@ class Admin(commands.Cog):
|
||||||
Texts('utils', ctx).get("Unable to find the message"),
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
delete_after=5)
|
delete_after=5)
|
||||||
|
|
||||||
@_react.command(name='clear')
|
@_react.command(name='clear',
|
||||||
|
description=Texts('commands').get('admin._react_remove'))
|
||||||
async def _react_remove(self, ctx: commands.Context, message_id: int):
|
async def _react_remove(self, ctx: commands.Context, message_id: int):
|
||||||
try:
|
try:
|
||||||
message: discord.Message = await ctx.channel.fetch_message(
|
message: discord.Message = await ctx.channel.fetch_message(
|
||||||
|
@ -199,9 +209,11 @@ class Admin(commands.Cog):
|
||||||
Texts('utils', ctx).get("Unable to find the message"),
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
delete_after=5)
|
delete_after=5)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.group(name='delete', invoke_without_command=True)
|
@groupExtra(name='delete', invoke_without_command=True,
|
||||||
|
category='admin',
|
||||||
|
description=Texts('commands').get('admin._delete'))
|
||||||
async def _delete(self, ctx: commands.Context, message_id: int):
|
async def _delete(self, ctx: commands.Context, message_id: int):
|
||||||
try:
|
try:
|
||||||
await ctx.message.delete()
|
await ctx.message.delete()
|
||||||
|
@ -217,7 +229,8 @@ class Admin(commands.Cog):
|
||||||
Texts('utils', ctx).get("Unable to find the message"),
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
delete_after=5)
|
delete_after=5)
|
||||||
|
|
||||||
@_delete.command(name='from', aliases=['to', 'in'])
|
@_delete.command(name='from', aliases=['to', 'in'],
|
||||||
|
description=Texts('commands').get('admin._delete_from'))
|
||||||
async def _delete_from(self, ctx: commands.Context,
|
async def _delete_from(self, ctx: commands.Context,
|
||||||
channel: discord.TextChannel, message_id: int):
|
channel: discord.TextChannel, message_id: int):
|
||||||
try:
|
try:
|
||||||
|
@ -234,7 +247,7 @@ class Admin(commands.Cog):
|
||||||
Texts('utils', ctx).get("Unable to find the message"),
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
delete_after=5)
|
delete_after=5)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
async def get_warn(self, ctx: commands.Context,
|
async def get_warn(self, ctx: commands.Context,
|
||||||
member: discord.Member = False):
|
member: discord.Member = False):
|
||||||
|
@ -279,7 +292,8 @@ class Admin(commands.Cog):
|
||||||
self.bot.database.session.add(warn)
|
self.bot.database.session.add(warn)
|
||||||
self.bot.database.session.commit()
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
@commands.group(name='warn', aliases=['warns'])
|
@groupExtra(name='warn', aliases=['warns'], category='admin',
|
||||||
|
description=Texts('commands').get('admin._warn'))
|
||||||
async def _warn(self, ctx: commands.Context):
|
async def _warn(self, ctx: commands.Context):
|
||||||
await ctx.trigger_typing()
|
await ctx.trigger_typing()
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
|
@ -291,7 +305,8 @@ class Admin(commands.Cog):
|
||||||
|
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
@_warn.command(name='add', aliases=['new'])
|
@_warn.command(name='add', aliases=['new'],
|
||||||
|
description=Texts('commands').get('admin._warn_new'))
|
||||||
async def _warn_new(self, ctx: commands.Context, member: discord.Member,
|
async def _warn_new(self, ctx: commands.Context, member: discord.Member,
|
||||||
*, reason="N/A"):
|
*, reason="N/A"):
|
||||||
member = await ctx.guild.fetch_member(member.id)
|
member = await ctx.guild.fetch_member(member.id)
|
||||||
|
@ -370,7 +385,8 @@ class Admin(commands.Cog):
|
||||||
f"\n**{Texts('admin', ctx).get('Reason')}:** `{reason}`"
|
f"\n**{Texts('admin', ctx).get('Reason')}:** `{reason}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
@_warn.command(name='remove', aliases=['revoke'])
|
@_warn.command(name='remove', aliases=['revoke'],
|
||||||
|
description=Texts('commands').get('admin._warn_remove'))
|
||||||
async def _warn_remove(self, ctx: commands.Context, warn_id: int):
|
async def _warn_remove(self, ctx: commands.Context, warn_id: int):
|
||||||
warn = self.bot.database.session \
|
warn = self.bot.database.session \
|
||||||
.query(Warn) \
|
.query(Warn) \
|
||||||
|
@ -382,7 +398,8 @@ class Admin(commands.Cog):
|
||||||
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
|
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
|
||||||
f" {Texts('admin', ctx).get('successfully removed')}")
|
f" {Texts('admin', ctx).get('successfully removed')}")
|
||||||
|
|
||||||
@_warn.command(name='show', aliases=['list'])
|
@_warn.command(name='show', aliases=['list'],
|
||||||
|
description=Texts('commands').get('admin._warn_show'))
|
||||||
async def _warn_show(self, ctx: commands.Context, member: discord.Member):
|
async def _warn_show(self, ctx: commands.Context, member: discord.Member):
|
||||||
warns_list, warns = await self.get_warn(ctx, member)
|
warns_list, warns = await self.get_warn(ctx, member)
|
||||||
|
|
||||||
|
@ -393,7 +410,8 @@ class Admin(commands.Cog):
|
||||||
|
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
@_warn.command(name='edit', aliases=['change'])
|
@_warn.command(name='edit', aliases=['change'],
|
||||||
|
description=Texts('commands').get('admin._warn_edit'))
|
||||||
async def _warn_edit(self, ctx: commands.Context, warn_id: int, *, reason):
|
async def _warn_edit(self, ctx: commands.Context, warn_id: int, *, reason):
|
||||||
warn = self.bot.database.session \
|
warn = self.bot.database.session \
|
||||||
.query(Warn) \
|
.query(Warn) \
|
||||||
|
@ -406,9 +424,11 @@ class Admin(commands.Cog):
|
||||||
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
|
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
|
||||||
f" {Texts('admin', ctx).get('successfully edited')}")
|
f" {Texts('admin', ctx).get('successfully edited')}")
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='language', aliases=['lang', 'langue', 'langage'])
|
@commandExtra(name='language', aliases=['lang', 'langue', 'langage'],
|
||||||
|
category='admin',
|
||||||
|
description=Texts('commands').get('admin._language'))
|
||||||
async def _language(self, ctx: commands.Context, locale: str):
|
async def _language(self, ctx: commands.Context, locale: str):
|
||||||
available = self.bot.database.session \
|
available = self.bot.database.session \
|
||||||
.query(Lang.value) \
|
.query(Lang.value) \
|
||||||
|
@ -436,6 +456,103 @@ class Admin(commands.Cog):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
Texts('admin', ctx).get('Language changed successfully'))
|
Texts('admin', ctx).get('Language changed successfully'))
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@groupExtra(name='prefix', aliases=['prefixes'], category='admin',
|
||||||
|
description=Texts('commands').get('admin._prefix'))
|
||||||
|
async def _prefix(self, ctx: commands.Context):
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
await ctx.send_help('prefix')
|
||||||
|
|
||||||
|
@_prefix.command(name='add', aliases=['set', 'new'],
|
||||||
|
description=Texts('commands').get('admin._prefix_add'))
|
||||||
|
async def _prefix_add(self, ctx: commands.Context, prefix: str):
|
||||||
|
if str(ctx.guild.id) in self.bot.prefixes:
|
||||||
|
prefixes = self.bot.prefixes.get(
|
||||||
|
str(ctx.guild.id), "prefixes"
|
||||||
|
).split(
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
)
|
||||||
|
|
||||||
|
if prefix in prefixes:
|
||||||
|
return await ctx.send(
|
||||||
|
Texts('admin', ctx).get('This prefix already exists')
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
prefixes.append(prefix)
|
||||||
|
self.bot.prefixes.set(
|
||||||
|
str(ctx.guild.id),
|
||||||
|
"prefixes",
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
.join(prefixes)
|
||||||
|
)
|
||||||
|
with open('./configs/prefixes.cfg', 'w') as configfile:
|
||||||
|
self.bot.prefixes.write(configfile)
|
||||||
|
else:
|
||||||
|
self.bot.prefixes.add_section(str(ctx.guild.id))
|
||||||
|
self.bot.prefixes.set(str(ctx.guild.id), "prefixes", prefix)
|
||||||
|
with open('./configs/prefixes.cfg', 'w') as configfile:
|
||||||
|
self.bot.prefixes.write(configfile)
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get('Prefix added successfully')
|
||||||
|
)
|
||||||
|
|
||||||
|
@_prefix.command(name='remove', aliases=['drop', 'del', 'delete'],
|
||||||
|
description=Texts('commands').get('admin._prefix_remove'))
|
||||||
|
async def _prefix_remove(self, ctx: commands.Context, prefix: str):
|
||||||
|
if str(ctx.guild.id) in self.bot.prefixes:
|
||||||
|
prefixes = self.bot.prefixes.get(
|
||||||
|
str(ctx.guild.id), "prefixes"
|
||||||
|
).split(
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
)
|
||||||
|
|
||||||
|
if prefix in prefixes:
|
||||||
|
prefixes.remove(prefix)
|
||||||
|
self.bot.prefixes.set(
|
||||||
|
str(ctx.guild.id),
|
||||||
|
"prefixes",
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
.join(prefixes)
|
||||||
|
)
|
||||||
|
with open('./configs/prefixes.cfg', 'w') as configfile:
|
||||||
|
self.bot.prefixes.write(configfile)
|
||||||
|
|
||||||
|
return await ctx.send(
|
||||||
|
Texts('admin', ctx).get('Prefix removed successfully')
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get('This prefix does not exist')
|
||||||
|
)
|
||||||
|
|
||||||
|
@_prefix.command(name='list', aliases=['show', 'all'],
|
||||||
|
description=Texts('commands').get('admin._prefix_list'))
|
||||||
|
async def _prefix_list(self, ctx: commands.Context):
|
||||||
|
extras = ['.']
|
||||||
|
if ctx.message.guild is not None:
|
||||||
|
extras = []
|
||||||
|
if str(ctx.message.guild.id) in self.bot.prefixes:
|
||||||
|
extras.extend(
|
||||||
|
self.bot.prefixes.get(str(ctx.message.guild.id),
|
||||||
|
"prefixes").split(
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
prefixes = [self.bot.user.mention]
|
||||||
|
prefixes.extend(extras)
|
||||||
|
|
||||||
|
if len(prefixes) <= 1:
|
||||||
|
text = Texts('admin', ctx)\
|
||||||
|
.get('The only prefix for this guild is :\n')
|
||||||
|
else:
|
||||||
|
text = Texts('admin', ctx)\
|
||||||
|
.get('Available prefixes for this guild are :\n')
|
||||||
|
|
||||||
|
await ctx.send(text + "\n • ".join(prefixes))
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: TuxBot):
|
def setup(bot: TuxBot):
|
||||||
bot.add_cog(Admin(bot))
|
bot.add_cog(Admin(bot))
|
||||||
|
|
|
@ -25,10 +25,9 @@ class Basics(commands.Cog):
|
||||||
|
|
||||||
return os.popen(cmd).read().strip()
|
return os.popen(cmd).read().strip()
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commandExtra(name='ping',
|
@commandExtra(name='ping', category='basics',
|
||||||
category='basics',
|
|
||||||
description=Texts('commands').get('basics._ping'))
|
description=Texts('commands').get('basics._ping'))
|
||||||
async def _ping(self, ctx: commands.Context):
|
async def _ping(self, ctx: commands.Context):
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
|
@ -45,7 +44,7 @@ class Basics(commands.Cog):
|
||||||
e.add_field(name='discordapp.com', value=f'{discordapp}ms')
|
e.add_field(name='discordapp.com', value=f'{discordapp}ms')
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fetch_info():
|
def fetch_info():
|
||||||
|
@ -67,8 +66,7 @@ class Basics(commands.Cog):
|
||||||
|
|
||||||
return total, file_amount
|
return total, file_amount
|
||||||
|
|
||||||
@commandExtra(name='info', aliases=['about'],
|
@commandExtra(name='info', aliases=['about'], category='basics',
|
||||||
category='basics',
|
|
||||||
description=Texts('commands').get('basics._info'))
|
description=Texts('commands').get('basics._info'))
|
||||||
async def _info(self, ctx: commands.Context):
|
async def _info(self, ctx: commands.Context):
|
||||||
proc = psutil.Process()
|
proc = psutil.Process()
|
||||||
|
@ -148,7 +146,7 @@ class Basics(commands.Cog):
|
||||||
|
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commandExtra(name='credits', aliases=['contributors', 'authors'],
|
@commandExtra(name='credits', aliases=['contributors', 'authors'],
|
||||||
category='basics',
|
category='basics',
|
||||||
|
|
621
cogs/help.py
Normal file
621
cogs/help.py
Normal file
|
@ -0,0 +1,621 @@
|
||||||
|
from discord.ext import commands
|
||||||
|
from .utils import checks, formats, time
|
||||||
|
from .utils.paginator import Pages
|
||||||
|
import discord
|
||||||
|
from collections import OrderedDict, deque, Counter
|
||||||
|
import os, datetime
|
||||||
|
import asyncio
|
||||||
|
import copy
|
||||||
|
import unicodedata
|
||||||
|
import inspect
|
||||||
|
import itertools
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
class Prefix(commands.Converter):
|
||||||
|
async def convert(self, ctx, argument):
|
||||||
|
user_id = ctx.bot.user.id
|
||||||
|
if argument.startswith((f'<@{user_id}>', f'<@!{user_id}>')):
|
||||||
|
raise commands.BadArgument('That is a reserved prefix already in use.')
|
||||||
|
return argument
|
||||||
|
|
||||||
|
class FetchedUser(commands.Converter):
|
||||||
|
async def convert(self, ctx, argument):
|
||||||
|
if not argument.isdigit():
|
||||||
|
raise commands.BadArgument('Not a valid user ID.')
|
||||||
|
try:
|
||||||
|
return await ctx.bot.fetch_user(argument)
|
||||||
|
except discord.NotFound:
|
||||||
|
raise commands.BadArgument('User not found.') from None
|
||||||
|
except discord.HTTPException:
|
||||||
|
raise commands.BadArgument('An error occurred while fetching the user.') from None
|
||||||
|
|
||||||
|
class HelpPaginator(Pages):
|
||||||
|
def __init__(self, help_command, ctx, entries, *, per_page=4):
|
||||||
|
super().__init__(ctx, entries=entries, per_page=per_page)
|
||||||
|
self.reaction_emojis.append(('\N{WHITE QUESTION MARK ORNAMENT}', self.show_bot_help))
|
||||||
|
self.total = len(entries)
|
||||||
|
self.help_command = help_command
|
||||||
|
self.prefix = help_command.clean_prefix
|
||||||
|
self.is_bot = False
|
||||||
|
|
||||||
|
def get_bot_page(self, page):
|
||||||
|
cog, description, commands = self.entries[page - 1]
|
||||||
|
self.title = f'{cog} Commands'
|
||||||
|
self.description = description
|
||||||
|
return commands
|
||||||
|
|
||||||
|
def prepare_embed(self, entries, page, *, first=False):
|
||||||
|
self.embed.clear_fields()
|
||||||
|
self.embed.description = self.description
|
||||||
|
self.embed.title = self.title
|
||||||
|
|
||||||
|
if self.is_bot:
|
||||||
|
value ='For more help, join the official bot support server: https://discord.gg/DWEaqMy'
|
||||||
|
self.embed.add_field(name='Support', value=value, inline=False)
|
||||||
|
|
||||||
|
self.embed.set_footer(text=f'Use "{self.prefix}help command" for more info on a command.')
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
signature = f'{entry.qualified_name} {entry.signature}'
|
||||||
|
self.embed.add_field(name=signature, value=entry.short_doc or "No help given", inline=False)
|
||||||
|
|
||||||
|
if self.maximum_pages:
|
||||||
|
self.embed.set_author(name=f'Page {page}/{self.maximum_pages} ({self.total} commands)')
|
||||||
|
|
||||||
|
async def show_help(self):
|
||||||
|
"""shows this message"""
|
||||||
|
|
||||||
|
self.embed.title = 'Paginator help'
|
||||||
|
self.embed.description = 'Hello! Welcome to the help page.'
|
||||||
|
|
||||||
|
messages = [f'{emoji} {func.__doc__}' for emoji, func in self.reaction_emojis]
|
||||||
|
self.embed.clear_fields()
|
||||||
|
self.embed.add_field(name='What are these reactions for?', value='\n'.join(messages), inline=False)
|
||||||
|
|
||||||
|
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
|
||||||
|
await self.message.edit(embed=self.embed)
|
||||||
|
|
||||||
|
async def go_back_to_current_page():
|
||||||
|
await asyncio.sleep(30.0)
|
||||||
|
await self.show_current_page()
|
||||||
|
|
||||||
|
self.bot.loop.create_task(go_back_to_current_page())
|
||||||
|
|
||||||
|
async def show_bot_help(self):
|
||||||
|
"""shows how to use the bot"""
|
||||||
|
|
||||||
|
self.embed.title = 'Using the bot'
|
||||||
|
self.embed.description = 'Hello! Welcome to the help page.'
|
||||||
|
self.embed.clear_fields()
|
||||||
|
|
||||||
|
entries = (
|
||||||
|
('<argument>', 'This means the argument is __**required**__.'),
|
||||||
|
('[argument]', 'This means the argument is __**optional**__.'),
|
||||||
|
('[A|B]', 'This means the it can be __**either A or B**__.'),
|
||||||
|
('[argument...]', 'This means you can have multiple arguments.\n' \
|
||||||
|
'Now that you know the basics, it should be noted that...\n' \
|
||||||
|
'__**You do not type in the brackets!**__')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.embed.add_field(name='How do I use this bot?', value='Reading the bot signature is pretty simple.')
|
||||||
|
|
||||||
|
for name, value in entries:
|
||||||
|
self.embed.add_field(name=name, value=value, inline=False)
|
||||||
|
|
||||||
|
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
|
||||||
|
await self.message.edit(embed=self.embed)
|
||||||
|
|
||||||
|
async def go_back_to_current_page():
|
||||||
|
await asyncio.sleep(30.0)
|
||||||
|
await self.show_current_page()
|
||||||
|
|
||||||
|
self.bot.loop.create_task(go_back_to_current_page())
|
||||||
|
|
||||||
|
class PaginatedHelpCommand(commands.HelpCommand):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(command_attrs={
|
||||||
|
'cooldown': commands.Cooldown(1, 3.0, commands.BucketType.member),
|
||||||
|
'help': 'Shows help about the bot, a command, or a category'
|
||||||
|
})
|
||||||
|
|
||||||
|
async def on_help_command_error(self, ctx, error):
|
||||||
|
if isinstance(error, commands.CommandInvokeError):
|
||||||
|
await ctx.send(str(error.original))
|
||||||
|
|
||||||
|
def get_command_signature(self, command):
|
||||||
|
parent = command.full_parent_name
|
||||||
|
if len(command.aliases) > 0:
|
||||||
|
aliases = '|'.join(command.aliases)
|
||||||
|
fmt = f'[{command.name}|{aliases}]'
|
||||||
|
if parent:
|
||||||
|
fmt = f'{parent} {fmt}'
|
||||||
|
alias = fmt
|
||||||
|
else:
|
||||||
|
alias = command.name if not parent else f'{parent} {command.name}'
|
||||||
|
return f'{alias} {command.signature}'
|
||||||
|
|
||||||
|
async def send_bot_help(self, mapping):
|
||||||
|
def key(c):
|
||||||
|
return c.cog_name or '\u200bNo Category'
|
||||||
|
|
||||||
|
bot = self.context.bot
|
||||||
|
entries = await self.filter_commands(bot.commands, sort=True, key=key)
|
||||||
|
nested_pages = []
|
||||||
|
per_page = 9
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
for cog, commands in itertools.groupby(entries, key=key):
|
||||||
|
commands = sorted(commands, key=lambda c: c.name)
|
||||||
|
if len(commands) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
total += len(commands)
|
||||||
|
actual_cog = bot.get_cog(cog)
|
||||||
|
# get the description if it exists (and the cog is valid) or return Empty embed.
|
||||||
|
description = (actual_cog and actual_cog.description) or discord.Embed.Empty
|
||||||
|
nested_pages.extend((cog, description, commands[i:i + per_page]) for i in range(0, len(commands), per_page))
|
||||||
|
|
||||||
|
# a value of 1 forces the pagination session
|
||||||
|
pages = HelpPaginator(self, self.context, nested_pages, per_page=1)
|
||||||
|
|
||||||
|
# swap the get_page implementation to work with our nested pages.
|
||||||
|
pages.get_page = pages.get_bot_page
|
||||||
|
pages.is_bot = True
|
||||||
|
pages.total = total
|
||||||
|
await self.context.release()
|
||||||
|
await pages.paginate()
|
||||||
|
|
||||||
|
async def send_cog_help(self, cog):
|
||||||
|
entries = await self.filter_commands(cog.get_commands(), sort=True)
|
||||||
|
pages = HelpPaginator(self, self.context, entries)
|
||||||
|
pages.title = f'{cog.qualified_name} Commands'
|
||||||
|
pages.description = cog.description
|
||||||
|
|
||||||
|
await self.context.release()
|
||||||
|
await pages.paginate()
|
||||||
|
|
||||||
|
def common_command_formatting(self, page_or_embed, command):
|
||||||
|
page_or_embed.title = self.get_command_signature(command)
|
||||||
|
if command.description:
|
||||||
|
page_or_embed.description = f'{command.description}\n\n{command.help}'
|
||||||
|
else:
|
||||||
|
page_or_embed.description = command.help or 'No help found...'
|
||||||
|
|
||||||
|
async def send_command_help(self, command):
|
||||||
|
# No pagination necessary for a single command.
|
||||||
|
embed = discord.Embed(colour=discord.Colour.blurple())
|
||||||
|
self.common_command_formatting(embed, command)
|
||||||
|
await self.context.send(embed=embed)
|
||||||
|
|
||||||
|
async def send_group_help(self, group):
|
||||||
|
subcommands = group.commands
|
||||||
|
if len(subcommands) == 0:
|
||||||
|
return await self.send_command_help(group)
|
||||||
|
|
||||||
|
entries = await self.filter_commands(subcommands, sort=True)
|
||||||
|
pages = HelpPaginator(self, self.context, entries)
|
||||||
|
self.common_command_formatting(pages, group)
|
||||||
|
|
||||||
|
await self.context.release()
|
||||||
|
await pages.paginate()
|
||||||
|
|
||||||
|
class Meta(commands.Cog):
|
||||||
|
"""Commands for utilities related to Discord or the Bot itself."""
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.old_help_command = bot.help_command
|
||||||
|
bot.help_command = PaginatedHelpCommand()
|
||||||
|
bot.help_command.cog = self
|
||||||
|
|
||||||
|
def cog_unload(self):
|
||||||
|
self.bot.help_command = self.old_help_command
|
||||||
|
|
||||||
|
async def cog_command_error(self, ctx, error):
|
||||||
|
if isinstance(error, commands.BadArgument):
|
||||||
|
await ctx.send(error)
|
||||||
|
|
||||||
|
@commands.command(hidden=True)
|
||||||
|
async def hello(self, ctx):
|
||||||
|
"""Displays my intro message."""
|
||||||
|
await ctx.send('Hello! I\'m a robot! Danny#0007 made me.')
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def charinfo(self, ctx, *, characters: str):
|
||||||
|
"""Shows you information about a number of characters.
|
||||||
|
|
||||||
|
Only up to 25 characters at a time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_string(c):
|
||||||
|
digit = f'{ord(c):x}'
|
||||||
|
name = unicodedata.name(c, 'Name not found.')
|
||||||
|
return f'`\\U{digit:>08}`: {name} - {c} \N{EM DASH} <http://www.fileformat.info/info/unicode/char/{digit}>'
|
||||||
|
msg = '\n'.join(map(to_string, characters))
|
||||||
|
if len(msg) > 2000:
|
||||||
|
return await ctx.send('Output too long to display.')
|
||||||
|
await ctx.send(msg)
|
||||||
|
|
||||||
|
@commands.group(name='prefix', invoke_without_command=True)
|
||||||
|
async def prefix(self, ctx):
|
||||||
|
"""Manages the server's custom prefixes.
|
||||||
|
|
||||||
|
If called without a subcommand, this will list the currently set
|
||||||
|
prefixes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
prefixes = self.bot.get_guild_prefixes(ctx.guild)
|
||||||
|
|
||||||
|
# we want to remove prefix #2, because it's the 2nd form of the mention
|
||||||
|
# and to the end user, this would end up making them confused why the
|
||||||
|
# mention is there twice
|
||||||
|
del prefixes[1]
|
||||||
|
|
||||||
|
e = discord.Embed(title='Prefixes', colour=discord.Colour.blurple())
|
||||||
|
e.set_footer(text=f'{len(prefixes)} prefixes')
|
||||||
|
e.description = '\n'.join(f'{index}. {elem}' for index, elem in enumerate(prefixes, 1))
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
@prefix.command(name='add', ignore_extra=False)
|
||||||
|
@checks.is_mod()
|
||||||
|
async def prefix_add(self, ctx, prefix: Prefix):
|
||||||
|
"""Appends a prefix to the list of custom prefixes.
|
||||||
|
|
||||||
|
Previously set prefixes are not overridden.
|
||||||
|
|
||||||
|
To have a word prefix, you should quote it and end it with
|
||||||
|
a space, e.g. "hello " to set the prefix to "hello ". This
|
||||||
|
is because Discord removes spaces when sending messages so
|
||||||
|
the spaces are not preserved.
|
||||||
|
|
||||||
|
Multi-word prefixes must be quoted also.
|
||||||
|
|
||||||
|
You must have Manage Server permission to use this command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
current_prefixes = self.bot.get_raw_guild_prefixes(ctx.guild.id)
|
||||||
|
current_prefixes.append(prefix)
|
||||||
|
try:
|
||||||
|
await self.bot.set_guild_prefixes(ctx.guild, current_prefixes)
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f'{ctx.tick(False)} {e}')
|
||||||
|
else:
|
||||||
|
await ctx.send(ctx.tick(True))
|
||||||
|
|
||||||
|
@prefix_add.error
|
||||||
|
async def prefix_add_error(self, ctx, error):
|
||||||
|
if isinstance(error, commands.TooManyArguments):
|
||||||
|
await ctx.send("You've given too many prefixes. Either quote it or only do it one by one.")
|
||||||
|
|
||||||
|
@prefix.command(name='remove', aliases=['delete'], ignore_extra=False)
|
||||||
|
@checks.is_mod()
|
||||||
|
async def prefix_remove(self, ctx, prefix: Prefix):
|
||||||
|
"""Removes a prefix from the list of custom prefixes.
|
||||||
|
|
||||||
|
This is the inverse of the 'prefix add' command. You can
|
||||||
|
use this to remove prefixes from the default set as well.
|
||||||
|
|
||||||
|
You must have Manage Server permission to use this command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
current_prefixes = self.bot.get_raw_guild_prefixes(ctx.guild.id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_prefixes.remove(prefix)
|
||||||
|
except ValueError:
|
||||||
|
return await ctx.send('I do not have this prefix registered.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.bot.set_guild_prefixes(ctx.guild, current_prefixes)
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f'{ctx.tick(False)} {e}')
|
||||||
|
else:
|
||||||
|
await ctx.send(ctx.tick(True))
|
||||||
|
|
||||||
|
@prefix.command(name='clear')
|
||||||
|
@checks.is_mod()
|
||||||
|
async def prefix_clear(self, ctx):
|
||||||
|
"""Removes all custom prefixes.
|
||||||
|
|
||||||
|
After this, the bot will listen to only mention prefixes.
|
||||||
|
|
||||||
|
You must have Manage Server permission to use this command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
await self.bot.set_guild_prefixes(ctx.guild, [])
|
||||||
|
await ctx.send(ctx.tick(True))
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def source(self, ctx, *, command: str = None):
|
||||||
|
"""Displays my full source code or for a specific command.
|
||||||
|
|
||||||
|
To display the source code of a subcommand you can separate it by
|
||||||
|
periods, e.g. tag.create for the create subcommand of the tag command
|
||||||
|
or by spaces.
|
||||||
|
"""
|
||||||
|
source_url = 'https://github.com/Rapptz/RoboDanny'
|
||||||
|
branch = 'rewrite'
|
||||||
|
if command is None:
|
||||||
|
return await ctx.send(source_url)
|
||||||
|
|
||||||
|
if command == 'help':
|
||||||
|
src = type(self.bot.help_command)
|
||||||
|
module = src.__module__
|
||||||
|
filename = inspect.getsourcefile(src)
|
||||||
|
else:
|
||||||
|
obj = self.bot.get_command(command.replace('.', ' '))
|
||||||
|
if obj is None:
|
||||||
|
return await ctx.send('Could not find command.')
|
||||||
|
|
||||||
|
# since we found the command we're looking for, presumably anyway, let's
|
||||||
|
# try to access the code itself
|
||||||
|
src = obj.callback.__code__
|
||||||
|
module = obj.callback.__module__
|
||||||
|
filename = src.co_filename
|
||||||
|
|
||||||
|
lines, firstlineno = inspect.getsourcelines(src)
|
||||||
|
if not module.startswith('discord'):
|
||||||
|
# not a built-in command
|
||||||
|
location = os.path.relpath(filename).replace('\\', '/')
|
||||||
|
else:
|
||||||
|
location = module.replace('.', '/') + '.py'
|
||||||
|
source_url = 'https://github.com/Rapptz/discord.py'
|
||||||
|
branch = 'master'
|
||||||
|
|
||||||
|
final_url = f'<{source_url}/blob/{branch}/{location}#L{firstlineno}-L{firstlineno + len(lines) - 1}>'
|
||||||
|
await ctx.send(final_url)
|
||||||
|
|
||||||
|
@commands.command(name='quit', hidden=True)
|
||||||
|
@commands.is_owner()
|
||||||
|
async def _quit(self, ctx):
|
||||||
|
"""Quits the bot."""
|
||||||
|
await self.bot.logout()
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def avatar(self, ctx, *, user: Union[discord.Member, FetchedUser] = None):
|
||||||
|
"""Shows a user's enlarged avatar (if possible)."""
|
||||||
|
embed = discord.Embed()
|
||||||
|
user = user or ctx.author
|
||||||
|
avatar = user.avatar_url_as(static_format='png')
|
||||||
|
embed.set_author(name=str(user), url=avatar)
|
||||||
|
embed.set_image(url=avatar)
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def info(self, ctx, *, user: Union[discord.Member, FetchedUser] = None):
|
||||||
|
"""Shows info about a user."""
|
||||||
|
|
||||||
|
user = user or ctx.author
|
||||||
|
if ctx.guild and isinstance(user, discord.User):
|
||||||
|
user = ctx.guild.get_member(user.id) or user
|
||||||
|
|
||||||
|
e = discord.Embed()
|
||||||
|
roles = [role.name.replace('@', '@\u200b') for role in getattr(user, 'roles', [])]
|
||||||
|
shared = sum(g.get_member(user.id) is not None for g in self.bot.guilds)
|
||||||
|
e.set_author(name=str(user))
|
||||||
|
|
||||||
|
def format_date(dt):
|
||||||
|
if dt is None:
|
||||||
|
return 'N/A'
|
||||||
|
return f'{dt:%Y-%m-%d %H:%M} ({time.human_timedelta(dt, accuracy=3)})'
|
||||||
|
|
||||||
|
e.add_field(name='ID', value=user.id, inline=False)
|
||||||
|
e.add_field(name='Servers', value=f'{shared} shared', inline=False)
|
||||||
|
e.add_field(name='Joined', value=format_date(getattr(user, 'joined_at', None)), inline=False)
|
||||||
|
e.add_field(name='Created', value=format_date(user.created_at), inline=False)
|
||||||
|
|
||||||
|
voice = getattr(user, 'voice', None)
|
||||||
|
if voice is not None:
|
||||||
|
vc = voice.channel
|
||||||
|
other_people = len(vc.members) - 1
|
||||||
|
voice = f'{vc.name} with {other_people} others' if other_people else f'{vc.name} by themselves'
|
||||||
|
e.add_field(name='Voice', value=voice, inline=False)
|
||||||
|
|
||||||
|
if roles:
|
||||||
|
e.add_field(name='Roles', value=', '.join(roles) if len(roles) < 10 else f'{len(roles)} roles', inline=False)
|
||||||
|
|
||||||
|
colour = user.colour
|
||||||
|
if colour.value:
|
||||||
|
e.colour = colour
|
||||||
|
|
||||||
|
if user.avatar:
|
||||||
|
e.set_thumbnail(url=user.avatar_url)
|
||||||
|
|
||||||
|
if isinstance(user, discord.User):
|
||||||
|
e.set_footer(text='This member is not in this server.')
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
@commands.command(aliases=['guildinfo'], usage='')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def serverinfo(self, ctx, *, guild_id: int = None):
|
||||||
|
"""Shows info about the current server."""
|
||||||
|
|
||||||
|
if guild_id is not None and await self.bot.is_owner(ctx.author):
|
||||||
|
guild = self.bot.get_guild(guild_id)
|
||||||
|
if guild is None:
|
||||||
|
return await ctx.send(f'Invalid Guild ID given.')
|
||||||
|
else:
|
||||||
|
guild = ctx.guild
|
||||||
|
|
||||||
|
roles = [role.name.replace('@', '@\u200b') for role in guild.roles]
|
||||||
|
|
||||||
|
# we're going to duck type our way here
|
||||||
|
class Secret:
|
||||||
|
pass
|
||||||
|
|
||||||
|
secret_member = Secret()
|
||||||
|
secret_member.id = 0
|
||||||
|
secret_member.roles = [guild.default_role]
|
||||||
|
|
||||||
|
# figure out what channels are 'secret'
|
||||||
|
secret = Counter()
|
||||||
|
totals = Counter()
|
||||||
|
for channel in guild.channels:
|
||||||
|
perms = channel.permissions_for(secret_member)
|
||||||
|
channel_type = type(channel)
|
||||||
|
totals[channel_type] += 1
|
||||||
|
if not perms.read_messages:
|
||||||
|
secret[channel_type] += 1
|
||||||
|
elif isinstance(channel, discord.VoiceChannel) and (not perms.connect or not perms.speak):
|
||||||
|
secret[channel_type] += 1
|
||||||
|
|
||||||
|
member_by_status = Counter(str(m.status) for m in guild.members)
|
||||||
|
|
||||||
|
e = discord.Embed()
|
||||||
|
e.title = guild.name
|
||||||
|
e.add_field(name='ID', value=guild.id)
|
||||||
|
e.add_field(name='Owner', value=guild.owner)
|
||||||
|
if guild.icon:
|
||||||
|
e.set_thumbnail(url=guild.icon_url)
|
||||||
|
|
||||||
|
channel_info = []
|
||||||
|
key_to_emoji = {
|
||||||
|
discord.TextChannel: '<:text_channel:586339098172850187>',
|
||||||
|
discord.VoiceChannel: '<:voice_channel:586339098524909604>',
|
||||||
|
}
|
||||||
|
for key, total in totals.items():
|
||||||
|
secrets = secret[key]
|
||||||
|
try:
|
||||||
|
emoji = key_to_emoji[key]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if secrets:
|
||||||
|
channel_info.append(f'{emoji} {total} ({secrets} locked)')
|
||||||
|
else:
|
||||||
|
channel_info.append(f'{emoji} {total}')
|
||||||
|
|
||||||
|
info = []
|
||||||
|
features = set(guild.features)
|
||||||
|
all_features = {
|
||||||
|
'PARTNERED': 'Partnered',
|
||||||
|
'VERIFIED': 'Verified',
|
||||||
|
'DISCOVERABLE': 'Server Discovery',
|
||||||
|
'PUBLIC': 'Server Discovery/Public',
|
||||||
|
'INVITE_SPLASH': 'Invite Splash',
|
||||||
|
'VIP_REGIONS': 'VIP Voice Servers',
|
||||||
|
'VANITY_URL': 'Vanity Invite',
|
||||||
|
'MORE_EMOJI': 'More Emoji',
|
||||||
|
'COMMERCE': 'Commerce',
|
||||||
|
'LURKABLE': 'Lurkable',
|
||||||
|
'NEWS': 'News Channels',
|
||||||
|
'ANIMATED_ICON': 'Animated Icon',
|
||||||
|
'BANNER': 'Banner'
|
||||||
|
}
|
||||||
|
|
||||||
|
for feature, label in all_features.items():
|
||||||
|
if feature in features:
|
||||||
|
info.append(f'{ctx.tick(True)}: {label}')
|
||||||
|
|
||||||
|
if info:
|
||||||
|
e.add_field(name='Features', value='\n'.join(info))
|
||||||
|
|
||||||
|
e.add_field(name='Channels', value='\n'.join(channel_info))
|
||||||
|
|
||||||
|
if guild.premium_tier != 0:
|
||||||
|
boosts = f'Level {guild.premium_tier}\n{guild.premium_subscription_count} boosts'
|
||||||
|
last_boost = max(guild.members, key=lambda m: m.premium_since or guild.created_at)
|
||||||
|
if last_boost.premium_since is not None:
|
||||||
|
boosts = f'{boosts}\nLast Boost: {last_boost} ({time.human_timedelta(last_boost.premium_since, accuracy=2)})'
|
||||||
|
e.add_field(name='Boosts', value=boosts, inline=False)
|
||||||
|
|
||||||
|
fmt = f'<:online:316856575413321728> {member_by_status["online"]} ' \
|
||||||
|
f'<:idle:316856575098880002> {member_by_status["idle"]} ' \
|
||||||
|
f'<:dnd:316856574868193281> {member_by_status["dnd"]} ' \
|
||||||
|
f'<:offline:316856575501402112> {member_by_status["offline"]}\n' \
|
||||||
|
f'Total: {guild.member_count}'
|
||||||
|
|
||||||
|
e.add_field(name='Members', value=fmt, inline=False)
|
||||||
|
|
||||||
|
# TODO: maybe chunk and stuff for top role members
|
||||||
|
# requires max-concurrency d.py check to work though.
|
||||||
|
|
||||||
|
e.add_field(name='Roles', value=', '.join(roles) if len(roles) < 10 else f'{len(roles)} roles')
|
||||||
|
e.set_footer(text='Created').timestamp = guild.created_at
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
async def say_permissions(self, ctx, member, channel):
|
||||||
|
permissions = channel.permissions_for(member)
|
||||||
|
e = discord.Embed(colour=member.colour)
|
||||||
|
allowed, denied = [], []
|
||||||
|
for name, value in permissions:
|
||||||
|
name = name.replace('_', ' ').replace('guild', 'server').title()
|
||||||
|
if value:
|
||||||
|
allowed.append(name)
|
||||||
|
else:
|
||||||
|
denied.append(name)
|
||||||
|
|
||||||
|
e.add_field(name='Allowed', value='\n'.join(allowed))
|
||||||
|
e.add_field(name='Denied', value='\n'.join(denied))
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def permissions(self, ctx, member: discord.Member = None, channel: discord.TextChannel = None):
|
||||||
|
"""Shows a member's permissions in a specific channel.
|
||||||
|
|
||||||
|
If no channel is given then it uses the current one.
|
||||||
|
|
||||||
|
You cannot use this in private messages. If no member is given then
|
||||||
|
the info returned will be yours.
|
||||||
|
"""
|
||||||
|
channel = channel or ctx.channel
|
||||||
|
if member is None:
|
||||||
|
member = ctx.author
|
||||||
|
|
||||||
|
await self.say_permissions(ctx, member, channel)
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
|
async def botpermissions(self, ctx, *, channel: discord.TextChannel = None):
|
||||||
|
"""Shows the bot's permissions in a specific channel.
|
||||||
|
|
||||||
|
If no channel is given then it uses the current one.
|
||||||
|
|
||||||
|
This is a good way of checking if the bot has the permissions needed
|
||||||
|
to execute the commands it wants to execute.
|
||||||
|
|
||||||
|
To execute this command you must have Manage Roles permission.
|
||||||
|
You cannot use this in private messages.
|
||||||
|
"""
|
||||||
|
channel = channel or ctx.channel
|
||||||
|
member = ctx.guild.me
|
||||||
|
await self.say_permissions(ctx, member, channel)
|
||||||
|
|
||||||
|
@commands.command(aliases=['invite'])
|
||||||
|
async def join(self, ctx):
|
||||||
|
"""Joins a server."""
|
||||||
|
perms = discord.Permissions.none()
|
||||||
|
perms.read_messages = True
|
||||||
|
perms.external_emojis = True
|
||||||
|
perms.send_messages = True
|
||||||
|
perms.manage_roles = True
|
||||||
|
perms.manage_channels = True
|
||||||
|
perms.ban_members = True
|
||||||
|
perms.kick_members = True
|
||||||
|
perms.manage_messages = True
|
||||||
|
perms.embed_links = True
|
||||||
|
perms.read_message_history = True
|
||||||
|
perms.attach_files = True
|
||||||
|
perms.add_reactions = True
|
||||||
|
await ctx.send(f'<{discord.utils.oauth_url(self.bot.client_id, perms)}>')
|
||||||
|
|
||||||
|
@commands.command(rest_is_raw=True, hidden=True)
|
||||||
|
@commands.is_owner()
|
||||||
|
async def echo(self, ctx, *, content):
|
||||||
|
await ctx.send(content)
|
||||||
|
|
||||||
|
@commands.command(hidden=True)
|
||||||
|
async def cud(self, ctx):
|
||||||
|
"""pls no spam"""
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
await ctx.send(3 - i)
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
await ctx.send('go')
|
||||||
|
|
||||||
|
def setup(bot):
|
||||||
|
bot.add_cog(Meta(bot))
|
11
cogs/logs.py
11
cogs/logs.py
|
@ -19,6 +19,8 @@ import psutil
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
from bot import TuxBot
|
from bot import TuxBot
|
||||||
|
from .utils import Texts
|
||||||
|
from .utils.extra import commandExtra
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -238,7 +240,8 @@ class Logs(commands.Cog):
|
||||||
msg = f'{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`'
|
msg = f'{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`'
|
||||||
await self.webhook.send(msg)
|
await self.webhook.send(msg)
|
||||||
|
|
||||||
@commands.command(name='commandstats', hidden=True)
|
@commandExtra(name='commandstats', hidden=True, category='logs',
|
||||||
|
description=Texts('commands').get('logs._commandstats'))
|
||||||
@commands.is_owner()
|
@commands.is_owner()
|
||||||
async def _commandstats(self, ctx, limit=20):
|
async def _commandstats(self, ctx, limit=20):
|
||||||
counter = self.bot.command_stats
|
counter = self.bot.command_stats
|
||||||
|
@ -253,7 +256,8 @@ class Logs(commands.Cog):
|
||||||
|
|
||||||
await ctx.send(f'```\n{output}\n```')
|
await ctx.send(f'```\n{output}\n```')
|
||||||
|
|
||||||
@commands.command(name='socketstats', hidden=True)
|
@commandExtra(name='socketstats', hidden=True, category='logs',
|
||||||
|
description=Texts('commands').get('logs._socketstats'))
|
||||||
@commands.is_owner()
|
@commands.is_owner()
|
||||||
async def _socketstats(self, ctx):
|
async def _socketstats(self, ctx):
|
||||||
delta = datetime.datetime.utcnow() - self.bot.uptime
|
delta = datetime.datetime.utcnow() - self.bot.uptime
|
||||||
|
@ -263,7 +267,8 @@ class Logs(commands.Cog):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f'{total} socket events observed ({cpm:.2f}/minute):\n{self.bot.socket_stats}')
|
f'{total} socket events observed ({cpm:.2f}/minute):\n{self.bot.socket_stats}')
|
||||||
|
|
||||||
@commands.command(name='uptime')
|
@commandExtra(name='uptime', category='logs',
|
||||||
|
description=Texts('commands').get('logs._uptime'))
|
||||||
async def _uptime(self, ctx):
|
async def _uptime(self, ctx):
|
||||||
"""Tells you how long the bot has been up for."""
|
"""Tells you how long the bot has been up for."""
|
||||||
uptime = humanize.naturaltime(
|
uptime = humanize.naturaltime(
|
||||||
|
|
|
@ -72,7 +72,7 @@ class Polls(commands.Cog):
|
||||||
|
|
||||||
await self.update_poll(poll.id)
|
await self.update_poll(poll.id)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
async def create_poll(self, ctx: commands.Context, poll: str, anonymous):
|
async def create_poll(self, ctx: commands.Context, poll: str, anonymous):
|
||||||
question = (poll.split('|')[0]).strip()
|
question = (poll.split('|')[0]).strip()
|
||||||
|
@ -92,7 +92,7 @@ class Polls(commands.Cog):
|
||||||
)
|
)
|
||||||
for i, response in enumerate(responses):
|
for i, response in enumerate(responses):
|
||||||
e.add_field(
|
e.add_field(
|
||||||
name=f"{emotes[i]} __{response.capitalize()}__",
|
name=f"__```{emotes[i]} - {response.capitalize()}```__",
|
||||||
value="**0** vote"
|
value="**0** vote"
|
||||||
)
|
)
|
||||||
e.set_footer(text=f"ID: #{poll_row.id}")
|
e.set_footer(text=f"ID: #{poll_row.id}")
|
||||||
|
@ -184,10 +184,9 @@ class Polls(commands.Cog):
|
||||||
description=Texts('commands').get('poll._poll'))
|
description=Texts('commands').get('poll._poll'))
|
||||||
async def _poll(self, ctx: commands.Context):
|
async def _poll(self, ctx: commands.Context):
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
pass
|
await ctx.send_help('sondage')
|
||||||
|
|
||||||
@_poll.group(name='create', aliases=['new', 'nouveau'],
|
@_poll.group(name='create', aliases=['new', 'nouveau'],
|
||||||
category='poll',
|
|
||||||
description=Texts('commands').get('poll._poll_create'))
|
description=Texts('commands').get('poll._poll_create'))
|
||||||
async def _poll_create(self, ctx: commands.Context, *, poll: str):
|
async def _poll_create(self, ctx: commands.Context, *, poll: str):
|
||||||
is_anonymous = '--anonyme' in poll
|
is_anonymous = '--anonyme' in poll
|
||||||
|
|
|
@ -7,7 +7,10 @@ from bot import TuxBot
|
||||||
import socket
|
import socket
|
||||||
from socket import AF_INET6
|
from socket import AF_INET6
|
||||||
|
|
||||||
|
from .admin import Admin
|
||||||
|
|
||||||
from .utils.lang import Texts
|
from .utils.lang import Texts
|
||||||
|
from .utils.extra import commandExtra
|
||||||
|
|
||||||
|
|
||||||
class Utility(commands.Cog):
|
class Utility(commands.Cog):
|
||||||
|
@ -15,9 +18,10 @@ class Utility(commands.Cog):
|
||||||
def __init__(self, bot: TuxBot):
|
def __init__(self, bot: TuxBot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='iplocalise')
|
@commandExtra(name='iplocalise', category='utility',
|
||||||
|
description=Texts('commands').get('utility._iplocalise'))
|
||||||
async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''):
|
async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''):
|
||||||
addr = re.sub(r'http(s?)://', '', addr)
|
addr = re.sub(r'http(s?)://', '', addr)
|
||||||
addr = addr[:-1] if addr.endswith('/') else addr
|
addr = addr[:-1] if addr.endswith('/') else addr
|
||||||
|
@ -79,9 +83,10 @@ class Utility(commands.Cog):
|
||||||
f"{Texts('utility', ctx).get('Cannot connect to host')} {addr}"
|
f"{Texts('utility', ctx).get('Cannot connect to host')} {addr}"
|
||||||
)
|
)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='getheaders')
|
@commandExtra(name='getheaders', category='utility',
|
||||||
|
description=Texts('commands').get('utility._getheaders'))
|
||||||
async def _getheaders(self, ctx: commands.Context, addr: str):
|
async def _getheaders(self, ctx: commands.Context, addr: str):
|
||||||
if (addr.startswith('http') or addr.startswith('ftp')) is not True:
|
if (addr.startswith('http') or addr.startswith('ftp')) is not True:
|
||||||
addr = f"http://{addr}"
|
addr = f"http://{addr}"
|
||||||
|
@ -109,9 +114,11 @@ class Utility(commands.Cog):
|
||||||
f"{Texts('utility', ctx).get('Cannot connect to host')} {addr}"
|
f"{Texts('utility', ctx).get('Cannot connect to host')} {addr}"
|
||||||
)
|
)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='git', aliases=['sources', 'source', 'github'])
|
@commandExtra(name='git', aliases=['sources', 'source', 'github'],
|
||||||
|
category='utility',
|
||||||
|
description=Texts('commands').get('utility._git'))
|
||||||
async def _git(self, ctx):
|
async def _git(self, ctx):
|
||||||
e = discord.Embed(
|
e = discord.Embed(
|
||||||
title=Texts('utility', ctx).get('git repo'),
|
title=Texts('utility', ctx).get('git repo'),
|
||||||
|
@ -124,9 +131,10 @@ class Utility(commands.Cog):
|
||||||
)
|
)
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
###########################################################################
|
||||||
|
|
||||||
@commands.command(name='quote')
|
@commandExtra(name='quote', category='utility',
|
||||||
|
description=Texts('commands').get('utility._quote'))
|
||||||
async def _quote(self, ctx, message_id: discord.Message):
|
async def _quote(self, ctx, message_id: discord.Message):
|
||||||
e = discord.Embed(
|
e = discord.Embed(
|
||||||
colour=message_id.author.colour,
|
colour=message_id.author.colour,
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config(configparser.RawConfigParser):
|
||||||
__slots__ = ('name', '_db')
|
__slots__ = ('name', '_db')
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
|
super().__init__()
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
self._db: configparser.ConfigParser = configparser.ConfigParser()
|
self._db = super()
|
||||||
self._db.read(self.name)
|
self._db.read(self.name)
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
return item in self._db
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
return self._db[item]
|
|
||||||
|
|
||||||
def all(self) -> list:
|
def all(self) -> list:
|
||||||
return self._db.sections()
|
return self._db.sections()
|
||||||
|
|
||||||
def get(self, *args, **kwargs) -> str:
|
|
||||||
return self._db.get(*args, **kwargs)
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from discord.ext import commands
|
||||||
class commandsPlus(commands.Command):
|
class commandsPlus(commands.Command):
|
||||||
def __init__(self, func, **kwargs):
|
def __init__(self, func, **kwargs):
|
||||||
super().__init__(func, **kwargs)
|
super().__init__(func, **kwargs)
|
||||||
self.category = kwargs.pop("category")
|
self.category = kwargs.get("category", 'other')
|
||||||
|
|
||||||
|
|
||||||
def commandExtra(*args, **kwargs):
|
def commandExtra(*args, **kwargs):
|
||||||
|
@ -14,7 +14,7 @@ def commandExtra(*args, **kwargs):
|
||||||
class GroupPlus(commands.Group):
|
class GroupPlus(commands.Group):
|
||||||
def __init__(self, func, **kwargs):
|
def __init__(self, func, **kwargs):
|
||||||
super().__init__(func, **kwargs)
|
super().__init__(func, **kwargs)
|
||||||
self.category = kwargs.pop("category")
|
self.category = kwargs.get("category", 'other')
|
||||||
|
|
||||||
|
|
||||||
def groupExtra(*args, **kwargs):
|
def groupExtra(*args, **kwargs):
|
||||||
|
|
316
cogs/utils/paginator.py
Normal file
316
cogs/utils/paginator.py
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
import asyncio
|
||||||
|
import discord
|
||||||
|
from discord.ext.commands import Paginator as CommandPaginator
|
||||||
|
|
||||||
|
|
||||||
|
class CannotPaginate(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Pages:
|
||||||
|
"""Implements a paginator that queries the user for the
|
||||||
|
pagination interface.
|
||||||
|
|
||||||
|
Pages are 1-index based, not 0-index based.
|
||||||
|
|
||||||
|
If the user does not reply within 2 minutes then the pagination
|
||||||
|
interface exits automatically.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
ctx: Context
|
||||||
|
The context of the command.
|
||||||
|
entries: List[str]
|
||||||
|
A list of entries to paginate.
|
||||||
|
per_page: int
|
||||||
|
How many entries show up per page.
|
||||||
|
show_entry_count: bool
|
||||||
|
Whether to show an entry count in the footer.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
embed: discord.Embed
|
||||||
|
The embed object that is being used to send pagination info.
|
||||||
|
Feel free to modify this externally. Only the description,
|
||||||
|
footer fields, and colour are internally modified.
|
||||||
|
permissions: discord.Permissions
|
||||||
|
Our permissions for the channel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True):
|
||||||
|
self.bot = ctx.bot
|
||||||
|
self.entries = entries
|
||||||
|
self.message = ctx.message
|
||||||
|
self.channel = ctx.channel
|
||||||
|
self.author = ctx.author
|
||||||
|
self.per_page = per_page
|
||||||
|
pages, left_over = divmod(len(self.entries), self.per_page)
|
||||||
|
if left_over:
|
||||||
|
pages += 1
|
||||||
|
self.maximum_pages = pages
|
||||||
|
self.embed = discord.Embed(colour=discord.Colour.blurple())
|
||||||
|
self.paginating = len(entries) > per_page
|
||||||
|
self.show_entry_count = show_entry_count
|
||||||
|
self.reaction_emojis = [
|
||||||
|
('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}',
|
||||||
|
self.first_page),
|
||||||
|
('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
|
||||||
|
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
|
||||||
|
('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}',
|
||||||
|
self.last_page),
|
||||||
|
('\N{INPUT SYMBOL FOR NUMBERS}', self.numbered_page),
|
||||||
|
('\N{BLACK SQUARE FOR STOP}', self.stop_pages),
|
||||||
|
('\N{INFORMATION SOURCE}', self.show_help),
|
||||||
|
]
|
||||||
|
|
||||||
|
if ctx.guild is not None:
|
||||||
|
self.permissions = self.channel.permissions_for(ctx.guild.me)
|
||||||
|
else:
|
||||||
|
self.permissions = self.channel.permissions_for(ctx.bot.user)
|
||||||
|
|
||||||
|
if not self.permissions.embed_links:
|
||||||
|
raise CannotPaginate('Bot does not have embed links permission.')
|
||||||
|
|
||||||
|
if not self.permissions.send_messages:
|
||||||
|
raise CannotPaginate('Bot cannot send messages.')
|
||||||
|
|
||||||
|
if self.paginating:
|
||||||
|
# verify we can actually use the pagination session
|
||||||
|
if not self.permissions.add_reactions:
|
||||||
|
raise CannotPaginate(
|
||||||
|
'Bot does not have add reactions permission.')
|
||||||
|
|
||||||
|
if not self.permissions.read_message_history:
|
||||||
|
raise CannotPaginate(
|
||||||
|
'Bot does not have Read Message History permission.')
|
||||||
|
|
||||||
|
def get_page(self, page):
|
||||||
|
base = (page - 1) * self.per_page
|
||||||
|
return self.entries[base:base + self.per_page]
|
||||||
|
|
||||||
|
def get_content(self, entries, page, *, first=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_embed(self, entries, page, *, first=False):
|
||||||
|
self.prepare_embed(entries, page, first=first)
|
||||||
|
return self.embed
|
||||||
|
|
||||||
|
def prepare_embed(self, entries, page, *, first=False):
|
||||||
|
p = []
|
||||||
|
for index, entry in enumerate(entries,
|
||||||
|
1 + ((page - 1) * self.per_page)):
|
||||||
|
p.append(f'{index}. {entry}')
|
||||||
|
|
||||||
|
if self.maximum_pages > 1:
|
||||||
|
if self.show_entry_count:
|
||||||
|
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
|
||||||
|
else:
|
||||||
|
text = f'Page {page}/{self.maximum_pages}'
|
||||||
|
|
||||||
|
self.embed.set_footer(text=text)
|
||||||
|
|
||||||
|
if self.paginating and first:
|
||||||
|
p.append('')
|
||||||
|
p.append(
|
||||||
|
'Confused? React with \N{INFORMATION SOURCE} for more info.')
|
||||||
|
|
||||||
|
self.embed.description = '\n'.join(p)
|
||||||
|
|
||||||
|
async def show_page(self, page, *, first=False):
|
||||||
|
self.current_page = page
|
||||||
|
entries = self.get_page(page)
|
||||||
|
content = self.get_content(entries, page, first=first)
|
||||||
|
embed = self.get_embed(entries, page, first=first)
|
||||||
|
|
||||||
|
if not self.paginating:
|
||||||
|
return await self.channel.send(content=content, embed=embed)
|
||||||
|
|
||||||
|
if not first:
|
||||||
|
await self.message.edit(content=content, embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.message = await self.channel.send(content=content, embed=embed)
|
||||||
|
for (reaction, _) in self.reaction_emojis:
|
||||||
|
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
|
||||||
|
# no |<< or >>| buttons if we only have two pages
|
||||||
|
# we can't forbid it if someone ends up using it but remove
|
||||||
|
# it from the default set
|
||||||
|
continue
|
||||||
|
|
||||||
|
await self.message.add_reaction(reaction)
|
||||||
|
|
||||||
|
async def checked_show_page(self, page):
|
||||||
|
if page != 0 and page <= self.maximum_pages:
|
||||||
|
await self.show_page(page)
|
||||||
|
|
||||||
|
async def first_page(self):
|
||||||
|
"""goes to the first page"""
|
||||||
|
await self.show_page(1)
|
||||||
|
|
||||||
|
async def last_page(self):
|
||||||
|
"""goes to the last page"""
|
||||||
|
await self.show_page(self.maximum_pages)
|
||||||
|
|
||||||
|
async def next_page(self):
|
||||||
|
"""goes to the next page"""
|
||||||
|
await self.checked_show_page(self.current_page + 1)
|
||||||
|
|
||||||
|
async def previous_page(self):
|
||||||
|
"""goes to the previous page"""
|
||||||
|
await self.checked_show_page(self.current_page - 1)
|
||||||
|
|
||||||
|
async def show_current_page(self):
|
||||||
|
if self.paginating:
|
||||||
|
await self.show_page(self.current_page)
|
||||||
|
|
||||||
|
async def numbered_page(self):
|
||||||
|
"""lets you type a page number to go to"""
|
||||||
|
to_delete = []
|
||||||
|
to_delete.append(
|
||||||
|
await self.channel.send('What page do you want to go to?'))
|
||||||
|
|
||||||
|
def message_check(m):
|
||||||
|
return m.author == self.author and \
|
||||||
|
self.channel == m.channel and \
|
||||||
|
m.content.isdigit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = await self.bot.wait_for('message', check=message_check,
|
||||||
|
timeout=30.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
to_delete.append(await self.channel.send('Took too long.'))
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
else:
|
||||||
|
page = int(msg.content)
|
||||||
|
to_delete.append(msg)
|
||||||
|
if page != 0 and page <= self.maximum_pages:
|
||||||
|
await self.show_page(page)
|
||||||
|
else:
|
||||||
|
to_delete.append(await self.channel.send(
|
||||||
|
f'Invalid page given. ({page}/{self.maximum_pages})'))
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.channel.delete_messages(to_delete)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def show_help(self):
|
||||||
|
"""shows this message"""
|
||||||
|
messages = ['Welcome to the interactive paginator!\n']
|
||||||
|
messages.append(
|
||||||
|
'This interactively allows you to see pages of text by navigating with ' \
|
||||||
|
'reactions. They are as follows:\n')
|
||||||
|
|
||||||
|
for (emoji, func) in self.reaction_emojis:
|
||||||
|
messages.append(f'{emoji} {func.__doc__}')
|
||||||
|
|
||||||
|
embed = self.embed.copy()
|
||||||
|
embed.clear_fields()
|
||||||
|
embed.description = '\n'.join(messages)
|
||||||
|
embed.set_footer(
|
||||||
|
text=f'We were on page {self.current_page} before this message.')
|
||||||
|
await self.message.edit(content=None, embed=embed)
|
||||||
|
|
||||||
|
async def go_back_to_current_page():
|
||||||
|
await asyncio.sleep(60.0)
|
||||||
|
await self.show_current_page()
|
||||||
|
|
||||||
|
self.bot.loop.create_task(go_back_to_current_page())
|
||||||
|
|
||||||
|
async def stop_pages(self):
|
||||||
|
"""stops the interactive pagination session"""
|
||||||
|
await self.message.delete()
|
||||||
|
self.paginating = False
|
||||||
|
|
||||||
|
def react_check(self, payload):
|
||||||
|
if payload.user_id != self.author.id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if payload.message_id != self.message.id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
to_check = str(payload.emoji)
|
||||||
|
for (emoji, func) in self.reaction_emojis:
|
||||||
|
if to_check == emoji:
|
||||||
|
self.match = func
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def paginate(self):
|
||||||
|
"""Actually paginate the entries and run the interactive loop if necessary."""
|
||||||
|
first_page = self.show_page(1, first=True)
|
||||||
|
if not self.paginating:
|
||||||
|
await first_page
|
||||||
|
else:
|
||||||
|
# allow us to react to reactions right away if we're paginating
|
||||||
|
self.bot.loop.create_task(first_page)
|
||||||
|
|
||||||
|
while self.paginating:
|
||||||
|
try:
|
||||||
|
payload = await self.bot.wait_for('raw_reaction_add',
|
||||||
|
check=self.react_check,
|
||||||
|
timeout=120.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
self.paginating = False
|
||||||
|
try:
|
||||||
|
await self.message.clear_reactions()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.message.remove_reaction(payload.emoji,
|
||||||
|
discord.Object(
|
||||||
|
id=payload.user_id))
|
||||||
|
except:
|
||||||
|
pass # can't remove it so don't bother doing so
|
||||||
|
|
||||||
|
await self.match()
|
||||||
|
|
||||||
|
|
||||||
|
class FieldPages(Pages):
|
||||||
|
"""Similar to Pages except entries should be a list of
|
||||||
|
tuples having (key, value) to show as embed fields instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def prepare_embed(self, entries, page, *, first=False):
|
||||||
|
self.embed.clear_fields()
|
||||||
|
self.embed.description = discord.Embed.Empty
|
||||||
|
|
||||||
|
for key, value in entries:
|
||||||
|
self.embed.add_field(name=key, value=value, inline=False)
|
||||||
|
|
||||||
|
if self.maximum_pages > 1:
|
||||||
|
if self.show_entry_count:
|
||||||
|
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
|
||||||
|
else:
|
||||||
|
text = f'Page {page}/{self.maximum_pages}'
|
||||||
|
|
||||||
|
self.embed.set_footer(text=text)
|
||||||
|
|
||||||
|
|
||||||
|
class TextPages(Pages):
|
||||||
|
"""Uses a commands.Paginator internally to paginate some text."""
|
||||||
|
|
||||||
|
def __init__(self, ctx, text, *, prefix='```', suffix='```',
|
||||||
|
max_size=2000):
|
||||||
|
paginator = CommandPaginator(prefix=prefix, suffix=suffix,
|
||||||
|
max_size=max_size - 200)
|
||||||
|
for line in text.split('\n'):
|
||||||
|
paginator.add_line(line)
|
||||||
|
|
||||||
|
super().__init__(ctx, entries=paginator.pages, per_page=1,
|
||||||
|
show_entry_count=False)
|
||||||
|
|
||||||
|
def get_page(self, page):
|
||||||
|
return self.entries[page - 1]
|
||||||
|
|
||||||
|
def get_embed(self, entries, page, *, first=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_content(self, entry, page, *, first=False):
|
||||||
|
if self.maximum_pages > 1:
|
||||||
|
return f'{entry}\nPage {page}/{self.maximum_pages}'
|
||||||
|
return entry
|
|
@ -1,2 +1,12 @@
|
||||||
[280805240977227776]
|
[280805240977227776]
|
||||||
prefixes = b.
|
prefixes = b1.|Imo07fZY9ogan7ank1n3UERg|b2.
|
||||||
|
|
||||||
|
[303633056944881686]
|
||||||
|
prefixes = b1.
|
||||||
|
|
||||||
|
[373881878471770112]
|
||||||
|
prefixes = b1.
|
||||||
|
|
||||||
|
[336642139381301249]
|
||||||
|
prefixes =
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
humanize
|
||||||
discord.py[voice]
|
discord.py[voice]
|
||||||
jishaku
|
jishaku
|
||||||
gitpython
|
gitpython
|
||||||
|
@ -6,3 +7,4 @@ psycopg2
|
||||||
configparser
|
configparser
|
||||||
psutil
|
psutil
|
||||||
tcp_latency
|
tcp_latency
|
||||||
|
yarl
|
Loading…
Reference in a new issue