Merge branch 'precommit' into 'main'

`pre-commit` for J.A.R.V.I.S.

See merge request stark-industries/j.a.r.v.i.s.!27
This commit is contained in:
Zeva Rose 2021-08-07 02:29:13 +00:00
commit 84ec50791a
53 changed files with 1003 additions and 1250 deletions

View file

@ -35,9 +35,3 @@ repos:
- flake8-bandit~=2.1 - flake8-bandit~=2.1
- flake8-docstrings~=1.5 - flake8-docstrings~=1.5
args: [--max-line-length=120, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204] args: [--max-line-length=120, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.910
hooks:
- id: mypy
args: [--install-types, --non-interactive]

View file

@ -1,6 +1,8 @@
"""Main J.A.R.V.I.S. package."""
import asyncio import asyncio
import logging
from pathlib import Path from pathlib import Path
from typing import Union from typing import Optional
from discord import Intents from discord import Intents
from discord.ext import commands from discord.ext import commands
@ -9,9 +11,21 @@ from discord_slash import SlashCommand
from mongoengine import connect from mongoengine import connect
from psutil import Process from psutil import Process
from jarvis import logo, tasks, utils from jarvis import logo # noqa: F401
from jarvis import tasks
from jarvis import utils
from jarvis.config import get_config from jarvis.config import get_config
from jarvis.events import guild, member, message from jarvis.events import guild
from jarvis.events import member
from jarvis.events import message
jconfig = get_config()
logger = logging.getLogger("discord")
logger.setLevel(logging.getLevelName(jconfig.log_level))
file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w")
file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s"))
logger.addHandler(file_handler)
if asyncio.get_event_loop().is_closed(): if asyncio.get_event_loop().is_closed():
asyncio.set_event_loop(asyncio.new_event_loop()) asyncio.set_event_loop(asyncio.new_event_loop())
@ -20,7 +34,6 @@ intents = Intents.default()
intents.members = True intents.members = True
restart_ctx = None restart_ctx = None
jconfig = get_config()
jarvis = commands.Bot( jarvis = commands.Bot(
command_prefix=utils.get_prefix, command_prefix=utils.get_prefix,
@ -31,11 +44,12 @@ jarvis = commands.Bot(
slash = SlashCommand(jarvis, sync_commands=True, sync_on_cog_reload=True) slash = SlashCommand(jarvis, sync_commands=True, sync_on_cog_reload=True)
jarvis_self = Process() jarvis_self = Process()
__version__ = "1.10.3" __version__ = "1.10.4"
@jarvis.event @jarvis.event
async def on_ready(): async def on_ready() -> None:
"""d.py on_ready override."""
global restart_ctx global restart_ctx
print(" Logged in as {0.user}".format(jarvis)) print(" Logged in as {0.user}".format(jarvis))
print(" Connected to {} guild(s)".format(len(jarvis.guilds))) print(" Connected to {} guild(s)".format(len(jarvis.guilds)))
@ -47,9 +61,7 @@ async def on_ready():
if "guild" in restart_ctx: if "guild" in restart_ctx:
guild = find(lambda x: x.id == restart_ctx["guild"], jarvis.guilds) guild = find(lambda x: x.id == restart_ctx["guild"], jarvis.guilds)
if guild: if guild:
channel = find( channel = find(lambda x: x.id == restart_ctx["channel"], guild.channels)
lambda x: x.id == restart_ctx["channel"], guild.channels
)
elif "user" in restart_ctx: elif "user" in restart_ctx:
channel = jarvis.get_user(restart_ctx["user"]) channel = jarvis.get_user(restart_ctx["user"])
if channel: if channel:
@ -57,7 +69,8 @@ async def on_ready():
restart_ctx = None restart_ctx = None
def run(ctx=None): def run(ctx: dict = None) -> Optional[dict]:
"""Run J.A.R.V.I.S."""
global restart_ctx global restart_ctx
if ctx: if ctx:
restart_ctx = ctx restart_ctx = ctx
@ -78,9 +91,7 @@ def run(ctx=None):
jarvis.load_extension(extension) jarvis.load_extension(extension)
print( print(
" https://discord.com/api/oauth2/authorize?client_id=" " https://discord.com/api/oauth2/authorize?client_id="
+ "{}&permissions=8&scope=bot%20applications.commands".format( + "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) # noqa: W503
jconfig.client_id
)
) )
jarvis.max_messages = jconfig.max_messages jarvis.max_messages = jconfig.max_messages
@ -88,7 +99,7 @@ def run(ctx=None):
# Add event listeners # Add event listeners
if jconfig.events: if jconfig.events:
listeners = [ _ = [
guild.GuildEventHandler(jarvis), guild.GuildEventHandler(jarvis),
member.MemberEventHandler(jarvis), member.MemberEventHandler(jarvis),
message.MessageEventHandler(jarvis), message.MessageEventHandler(jarvis),

View file

@ -1,16 +1,18 @@
from jarvis.cogs.admin import ( """J.A.R.V.I.S. Admin Cogs."""
ban, from discord.ext.commands import Bot
kick,
lock, from jarvis.cogs.admin import ban
lockdown, from jarvis.cogs.admin import kick
mute, from jarvis.cogs.admin import lock
purge, from jarvis.cogs.admin import lockdown
roleping, from jarvis.cogs.admin import mute
warning, from jarvis.cogs.admin import purge
) from jarvis.cogs.admin import roleping
from jarvis.cogs.admin import warning
def setup(bot): def setup(bot: Bot) -> None:
"""Add admin cogs to J.A.R.V.I.S."""
bot.add_cog(ban.BanCog(bot)) bot.add_cog(ban.BanCog(bot))
bot.add_cog(kick.KickCog(bot)) bot.add_cog(kick.KickCog(bot))
bot.add_cog(lock.LockCog(bot)) bot.add_cog(lock.LockCog(bot))

View file

@ -1,15 +1,20 @@
"""J.A.R.V.I.S. BanCog."""
import re import re
from datetime import datetime, timedelta from datetime import datetime
from datetime import timedelta
from ButtonPaginator import Paginator from ButtonPaginator import Paginator
from discord import User from discord import User
from discord.ext import commands from discord.ext import commands
from discord.utils import find from discord.utils import find
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.model import ButtonStyle from discord_slash.model import ButtonStyle
from discord_slash.utils.manage_commands import create_choice, create_option from discord_slash.utils.manage_commands import create_choice
from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Ban, Unban from jarvis.db.models import Ban
from jarvis.db.models import Unban
from jarvis.utils import build_embed from jarvis.utils import build_embed
from jarvis.utils.cachecog import CacheCog from jarvis.utils.cachecog import CacheCog
from jarvis.utils.field import Field from jarvis.utils.field import Field
@ -17,6 +22,8 @@ from jarvis.utils.permissions import admin_or_permissions
class BanCog(CacheCog): class BanCog(CacheCog):
"""J.A.R.V.I.S. BanCog."""
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
super().__init__(bot) super().__init__(bot)
@ -28,7 +35,8 @@ class BanCog(CacheCog):
duration: int, duration: int,
active: bool, active: bool,
fields: list, fields: list,
): ) -> None:
"""Apply a Discord ban."""
await ctx.guild.ban(user, reason=reason) await ctx.guild.ban(user, reason=reason)
_ = Ban( _ = Ban(
user=user.id, user=user.id,
@ -57,9 +65,8 @@ class BanCog(CacheCog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
async def discord_apply_unban( async def discord_apply_unban(self, ctx: SlashContext, user: User, reason: str) -> None:
self, ctx: SlashContext, user: User, reason: str """Apply a Discord unban."""
):
await ctx.guild.unban(user, reason=reason) await ctx.guild.unban(user, reason=reason)
_ = Unban( _ = Unban(
user=user.id, user=user.id,
@ -126,7 +133,7 @@ class BanCog(CacheCog):
reason: str = None, reason: str = None,
type: str = "perm", type: str = "perm",
duration: int = 4, duration: int = 4,
): ) -> None:
if not user or user == ctx.author: if not user or user == ctx.author:
await ctx.send("You cannot ban yourself.", hidden=True) await ctx.send("You cannot ban yourself.", hidden=True)
return return
@ -134,22 +141,16 @@ class BanCog(CacheCog):
await ctx.send("I'm afraid I can't let you do that", hidden=True) await ctx.send("I'm afraid I can't let you do that", hidden=True)
return return
if type == "temp" and duration < 0: if type == "temp" and duration < 0:
await ctx.send( await ctx.send("You cannot set a temp ban to < 0 hours.", hidden=True)
"You cannot set a temp ban to < 0 hours.", hidden=True
)
return return
elif type == "temp" and duration > 744: elif type == "temp" and duration > 744:
await ctx.send( await ctx.send("You cannot set a temp ban to > 1 month", hidden=True)
"You cannot set a temp ban to > 1 month", hidden=True
)
return return
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", hidden=True)
return return
if not reason: if not reason:
reason = ( reason = "Mr. Stark is displeased with your presence. Please leave."
"Mr. Stark is displeased with your presence. Please leave."
)
await ctx.defer() await ctx.defer()
@ -158,10 +159,7 @@ class BanCog(CacheCog):
mtype = "perma" mtype = "perma"
guild_name = ctx.guild.name guild_name = ctx.guild.name
user_message = ( user_message = f"You have been {mtype}banned from {guild_name}." + f" Reason:\n{reason}"
f"You have been {mtype}banned from {guild_name}."
+ f" Reason:\n{reason}"
)
if mtype == "temp": if mtype == "temp":
user_message += f"\nDuration: {duration} hours" user_message += f"\nDuration: {duration} hours"
@ -202,9 +200,7 @@ class BanCog(CacheCog):
if type == "soft": if type == "soft":
active = False active = False
await self.discord_apply_ban( await self.discord_apply_ban(ctx, reason, user, duration, active, fields)
ctx, reason, user, duration, active, fields
)
@cog_ext.cog_slash( @cog_ext.cog_slash(
name="unban", name="unban",
@ -230,7 +226,7 @@ class BanCog(CacheCog):
ctx: SlashContext, ctx: SlashContext,
user: str, user: str,
reason: str, reason: str,
): ) -> None:
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", hidden=True)
return return
@ -251,29 +247,18 @@ class BanCog(CacheCog):
user, discrim = user.split("#") user, discrim = user.split("#")
if discrim: if discrim:
discord_ban_info = find( discord_ban_info = find(
lambda x: x.user.name == user lambda x: x.user.name == user and x.user.discriminator == discrim,
and x.user.discriminator == discrim,
bans, bans,
) )
else: else:
results = [ results = [x for x in filter(lambda x: x.user.name == user, bans)]
x for x in filter(lambda x: x.user.name == user, bans)
]
if results: if results:
if len(results) > 1: if len(results) > 1:
active_bans = [] active_bans = []
for ban in bans: for ban in bans:
active_bans.append( active_bans.append("{0} ({1}): {2}".format(ban.user.name, ban.user.id, ban.reason))
"{0} ({1}): {2}".format( ab_message = "\n".join(active_bans)
ban.user.name, ban.user.id, ban.reason message = f"More than one result. Please use one of the following IDs:\n```{ab_message}\n```"
)
)
message = (
"More than one result. "
+ "Please use one of the following IDs:\n```"
+ "\n".join(active_bans)
+ "\n```"
)
await ctx.send(message) await ctx.send(message)
return return
else: else:
@ -284,9 +269,7 @@ class BanCog(CacheCog):
# We take advantage of the previous checks to save CPU cycles # We take advantage of the previous checks to save CPU cycles
if not discord_ban_info: if not discord_ban_info:
if isinstance(user, int): if isinstance(user, int):
database_ban_info = Ban.objects( database_ban_info = Ban.objects(guild=ctx.guild.id, user=user, active=True).first()
guild=ctx.guild.id, user=user, active=True
).first()
else: else:
search = { search = {
"guild": ctx.guild.id, "guild": ctx.guild.id,
@ -303,13 +286,9 @@ class BanCog(CacheCog):
elif discord_ban_info: elif discord_ban_info:
await self.discord_apply_unban(ctx, discord_ban_info.user, reason) await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
else: else:
discord_ban_info = find( discord_ban_info = find(lambda x: x.user.id == database_ban_info["id"], bans)
lambda x: x.user.id == database_ban_info["id"], bans
)
if discord_ban_info: if discord_ban_info:
await self.discord_apply_unban( await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
ctx, discord_ban_info.user, reason
)
else: else:
database_ban_info.active = False database_ban_info.active = False
database_ban_info.save() database_ban_info.save()
@ -321,10 +300,7 @@ class BanCog(CacheCog):
admin=ctx.author.id, admin=ctx.author.id,
reason=reason, reason=reason,
).save() ).save()
await ctx.send( await ctx.send("Unable to find user in Discord, " + "but removed entry from database.")
"Unable to find user in Discord, "
+ "but removed entry from database."
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="bans", base="bans",
@ -356,16 +332,13 @@ class BanCog(CacheCog):
], ],
) )
@admin_or_permissions(ban_members=True) @admin_or_permissions(ban_members=True)
async def _bans_list( async def _bans_list(self, ctx: SlashContext, type: int = 0, active: int = 1) -> None:
self, ctx: SlashContext, type: int = 0, active: int = 1
):
active = bool(active) active = bool(active)
exists = self.check_cache(ctx, type=type, active=active) exists = self.check_cache(ctx, type=type, active=active)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " f"Please use existing interaction: {exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -385,10 +358,12 @@ class BanCog(CacheCog):
fields.append( fields.append(
Field( Field(
name=f"Username: {ban.username}#{ban.discrim}", name=f"Username: {ban.username}#{ban.discrim}",
value=f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n" value=(
+ f"User ID: {ban.user}\n" f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
+ f"Reason: {ban.reason}\n" f"User ID: {ban.user}\n"
+ f"Type: {ban.type}\n\u200b", f"Reason: {ban.reason}\n"
f"Type: {ban.type}\n\u200b"
),
inline=False, inline=False,
) )
) )
@ -399,12 +374,13 @@ class BanCog(CacheCog):
if ban.user.id not in db_bans: if ban.user.id not in db_bans:
fields.append( fields.append(
Field( Field(
name=f"Username: {ban.user.name}#" name=f"Username: {ban.user.name}#" + f"{ban.user.discriminator}",
+ f"{ban.user.discriminator}", value=(
value="Date: [unknown]\n" f"Date: [unknown]\n"
+ f"User ID: {ban.user.id}\n" f"User ID: {ban.user.id}\n"
+ f"Reason: {ban.reason}\n" f"Reason: {ban.reason}\n"
+ "Type: manual\n\u200b", "Type: manual\n\u200b"
),
inline=False, inline=False,
) )
) )
@ -426,9 +402,7 @@ class BanCog(CacheCog):
pages.append(embed) pages.append(embed)
else: else:
for i in range(0, len(bans), 5): for i in range(0, len(bans), 5):
embed = build_embed( embed = build_embed(title=title, description="", fields=fields[i : i + 5]) # noqa: E203
title=title, description="", fields=fields[i : i + 5]
)
embed.set_thumbnail(url=ctx.guild.icon_url) embed.set_thumbnail(url=ctx.guild.icon_url)
pages.append(embed) pages.append(embed)

View file

@ -1,5 +1,8 @@
"""J.A.R.V.I.S. KickCog."""
from discord import User from discord import User
from discord_slash import SlashContext, cog_ext from discord.ext.commands import Bot
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Kick from jarvis.db.models import Kick
@ -10,7 +13,9 @@ from jarvis.utils.permissions import admin_or_permissions
class KickCog(CacheCog): class KickCog(CacheCog):
def __init__(self, bot): """J.A.R.V.I.S. KickCog."""
def __init__(self, bot: Bot):
super().__init__(bot) super().__init__(bot)
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -32,7 +37,7 @@ class KickCog(CacheCog):
], ],
) )
@admin_or_permissions(kick_members=True) @admin_or_permissions(kick_members=True)
async def _kick(self, ctx: SlashContext, user: User, reason=None): async def _kick(self, ctx: SlashContext, user: User, reason: str = None) -> None:
if not user or user == ctx.author: if not user or user == ctx.author:
await ctx.send("You cannot kick yourself.", hidden=True) await ctx.send("You cannot kick yourself.", hidden=True)
return return
@ -43,9 +48,7 @@ class KickCog(CacheCog):
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", hidden=True)
return return
if not reason: if not reason:
reason = ( reason = "Mr. Stark is displeased with your presence. Please leave."
"Mr. Stark is displeased with your presence. Please leave."
)
guild_name = ctx.guild.name guild_name = ctx.guild.name
embed = build_embed( embed = build_embed(
title=f"You have been kicked from {guild_name}", title=f"You have been kicked from {guild_name}",

View file

@ -1,8 +1,14 @@
"""J.A.R.V.I.S. LockCog."""
from contextlib import suppress
from typing import Union from typing import Union
from discord import Role, TextChannel, User, VoiceChannel from discord import Role
from discord.ext import commands from discord import TextChannel
from discord_slash import SlashContext, cog_ext from discord import User
from discord import VoiceChannel
from discord.ext.commands import Bot
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Lock from jarvis.db.models import Lock
@ -11,7 +17,9 @@ from jarvis.utils.permissions import admin_or_permissions
class LockCog(CacheCog): class LockCog(CacheCog):
def __init__(self, bot: commands.Bot): """J.A.R.V.I.S. LockCog."""
def __init__(self, bot: Bot):
super().__init__(bot) super().__init__(bot)
async def _lock_channel( async def _lock_channel(
@ -20,8 +28,8 @@ class LockCog(CacheCog):
role: Role, role: Role,
admin: User, admin: User,
reason: str, reason: str,
allow_send=False, allow_send: bool = False,
): ) -> None:
overrides = channel.overwrites_for(role) overrides = channel.overwrites_for(role)
if isinstance(channel, TextChannel): if isinstance(channel, TextChannel):
overrides.send_messages = allow_send overrides.send_messages = allow_send
@ -34,7 +42,7 @@ class LockCog(CacheCog):
channel: Union[TextChannel, VoiceChannel], channel: Union[TextChannel, VoiceChannel],
role: Role, role: Role,
admin: User, admin: User,
): ) -> None:
overrides = channel.overwrites_for(role) overrides = channel.overwrites_for(role)
if isinstance(channel, TextChannel): if isinstance(channel, TextChannel):
overrides.send_messages = None overrides.send_messages = None
@ -73,7 +81,7 @@ class LockCog(CacheCog):
reason: str, reason: str,
duration: int = 10, duration: int = 10,
channel: Union[TextChannel, VoiceChannel] = None, channel: Union[TextChannel, VoiceChannel] = None,
): ) -> None:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
if duration <= 0: if duration <= 0:
await ctx.send("Duration must be > 0", hidden=True) await ctx.send("Duration must be > 0", hidden=True)
@ -87,10 +95,8 @@ class LockCog(CacheCog):
if not channel: if not channel:
channel = ctx.channel channel = ctx.channel
for role in ctx.guild.roles: for role in ctx.guild.roles:
try: with suppress(Exception):
await self._lock_channel(channel, role, ctx.author, reason) await self._lock_channel(channel, role, ctx.author, reason)
except Exception:
continue # Just continue on error
_ = Lock( _ = Lock(
channel=channel.id, channel=channel.id,
guild=ctx.guild.id, guild=ctx.guild.id,
@ -117,20 +123,16 @@ class LockCog(CacheCog):
self, self,
ctx: SlashContext, ctx: SlashContext,
channel: Union[TextChannel, VoiceChannel] = None, channel: Union[TextChannel, VoiceChannel] = None,
): ) -> None:
if not channel: if not channel:
channel = ctx.channel channel = ctx.channel
lock = Lock.objects( lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first()
guild=ctx.guild.id, channel=channel.id, active=True
).first()
if not lock: if not lock:
await ctx.send(f"{channel.mention} not locked.", hidden=True) await ctx.send(f"{channel.mention} not locked.", hidden=True)
return return
for role in ctx.guild.roles: for role in ctx.guild.roles:
try: with suppress(Exception):
await self._unlock_channel(channel, role, ctx.author) await self._unlock_channel(channel, role, ctx.author)
except Exception:
continue # Just continue on error
lock.active = False lock.active = False
lock.save() lock.save()
await ctx.send(f"{channel.mention} unlocked") await ctx.send(f"{channel.mention} unlocked")

View file

@ -1,7 +1,10 @@
"""J.A.R.V.I.S. LockdownCog."""
from contextlib import suppress
from datetime import datetime from datetime import datetime
from discord.ext import commands from discord.ext import commands
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Lock from jarvis.db.models import Lock
@ -10,6 +13,8 @@ from jarvis.utils.permissions import admin_or_permissions
class LockdownCog(CacheCog): class LockdownCog(CacheCog):
"""J.A.R.V.I.S. LockdownCog."""
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
super().__init__(bot) super().__init__(bot)
@ -38,7 +43,7 @@ class LockdownCog(CacheCog):
ctx: SlashContext, ctx: SlashContext,
reason: str, reason: str,
duration: int = 10, duration: int = 10,
): ) -> None:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
if duration <= 0: if duration <= 0:
await ctx.send("Duration must be > 0", hidden=True) await ctx.send("Duration must be > 0", hidden=True)
@ -51,10 +56,8 @@ class LockdownCog(CacheCog):
updates = [] updates = []
for channel in channels: for channel in channels:
for role in roles: for role in roles:
try: with suppress(Exception):
await self._lock_channel(channel, role, ctx.author, reason) await self._lock_channel(channel, role, ctx.author, reason)
except Exception:
continue # Just continue on error
updates.append( updates.append(
Lock( Lock(
channel=channel.id, channel=channel.id,
@ -79,7 +82,7 @@ class LockdownCog(CacheCog):
async def _lockdown_end( async def _lockdown_end(
self, self,
ctx: SlashContext, ctx: SlashContext,
): ) -> None:
channels = ctx.guild.channels channels = ctx.guild.channels
roles = ctx.guild.roles roles = ctx.guild.roles
update = False update = False
@ -90,13 +93,9 @@ class LockdownCog(CacheCog):
await ctx.defer() await ctx.defer()
for channel in channels: for channel in channels:
for role in roles: for role in roles:
try: with suppress(Exception):
await self._unlock_channel(channel, role, ctx.author) await self._unlock_channel(channel, role, ctx.author)
except Exception:
continue # Just continue on error
update = True update = True
if update: if update:
Lock.objects(guild=ctx.guild.id, active=True).update( Lock.objects(guild=ctx.guild.id, active=True).update(set__active=False)
set__active=False
)
await ctx.send("Server unlocked") await ctx.send("Server unlocked")

View file

@ -1,17 +1,22 @@
"""J.A.R.V.I.S. MuteCog."""
from discord import Member from discord import Member
from discord.ext import commands from discord.ext import commands
from discord.utils import get from discord.utils import get
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Mute, Setting from jarvis.db.models import Mute
from jarvis.db.models import Setting
from jarvis.utils import build_embed from jarvis.utils import build_embed
from jarvis.utils.field import Field from jarvis.utils.field import Field
from jarvis.utils.permissions import admin_or_permissions from jarvis.utils.permissions import admin_or_permissions
class MuteCog(commands.Cog): class MuteCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. MuteCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -39,9 +44,7 @@ class MuteCog(commands.Cog):
], ],
) )
@admin_or_permissions(mute_members=True) @admin_or_permissions(mute_members=True)
async def _mute( async def _mute(self, ctx: SlashContext, user: Member, reason: str, duration: int = 30) -> None:
self, ctx: SlashContext, user: Member, reason: str, duration: int = 30
):
if user == ctx.author: if user == ctx.author:
await ctx.send("You cannot mute yourself.", hidden=True) await ctx.send("You cannot mute yourself.", hidden=True)
return return
@ -51,13 +54,10 @@ class MuteCog(commands.Cog):
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", hidden=True)
return return
mute_setting = Setting.objects( mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first()
guild=ctx.guild.id, setting="mute"
).first()
if not mute_setting: if not mute_setting:
await ctx.send( await ctx.send(
"Please configure a mute role " "Please configure a mute role with /settings mute <role> first",
+ "with /settings mute <role> first",
hidden=True, hidden=True,
) )
return return
@ -103,14 +103,11 @@ class MuteCog(commands.Cog):
], ],
) )
@admin_or_permissions(mute_members=True) @admin_or_permissions(mute_members=True)
async def _unmute(self, ctx: SlashContext, user: Member): async def _unmute(self, ctx: SlashContext, user: Member) -> None:
mute_setting = Setting.objects( mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first()
guild=ctx.guild.id, setting="mute"
).first()
if not mute_setting: if not mute_setting:
await ctx.send( await ctx.send(
"Please configure a mute role with " "Please configure a mute role with /settings mute <role> first.",
+ "/settings mute <role> first.",
hidden=True, hidden=True,
) )
return return
@ -122,9 +119,7 @@ class MuteCog(commands.Cog):
await ctx.send("User is not muted.", hidden=True) await ctx.send("User is not muted.", hidden=True)
return return
_ = Mute.objects(guild=ctx.guild.id, user=user.id).update( _ = Mute.objects(guild=ctx.guild.id, user=user.id).update(set__active=False)
set__active=False
)
embed = build_embed( embed = build_embed(
title="User Unmuted", title="User Unmuted",
description=f"{user.mention} has been unmuted", description=f"{user.mention} has been unmuted",

View file

@ -1,14 +1,19 @@
"""J.A.R.V.I.S. PurgeCog."""
from discord import TextChannel from discord import TextChannel
from discord.ext import commands from discord.ext import commands
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Autopurge, Purge from jarvis.db.models import Autopurge
from jarvis.db.models import Purge
from jarvis.utils.permissions import admin_or_permissions from jarvis.utils.permissions import admin_or_permissions
class PurgeCog(commands.Cog): class PurgeCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. PurgeCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -24,7 +29,7 @@ class PurgeCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_messages=True) @admin_or_permissions(manage_messages=True)
async def _purge(self, ctx: SlashContext, amount: int = 10): async def _purge(self, ctx: SlashContext, amount: int = 10) -> None:
if amount < 1: if amount < 1:
await ctx.send("Amount must be >= 1", hidden=True) await ctx.send("Amount must be >= 1", hidden=True)
return return
@ -61,9 +66,7 @@ class PurgeCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_messages=True) @admin_or_permissions(manage_messages=True)
async def _autopurge_add( async def _autopurge_add(self, ctx: SlashContext, channel: TextChannel, delay: int = 30) -> None:
self, ctx: SlashContext, channel: TextChannel, delay: int = 30
):
if not isinstance(channel, TextChannel): if not isinstance(channel, TextChannel):
await ctx.send("Channel must be a TextChannel", hidden=True) await ctx.send("Channel must be a TextChannel", hidden=True)
return return
@ -73,9 +76,7 @@ class PurgeCog(commands.Cog):
elif delay > 300: elif delay > 300:
await ctx.send("Delay must be < 5 minutes", hidden=True) await ctx.send("Delay must be < 5 minutes", hidden=True)
return return
autopurge = Autopurge.objects( autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first()
guild=ctx.guild.id, channel=channel.id
).first()
if autopurge: if autopurge:
await ctx.send("Autopurge already exists.", hidden=True) await ctx.send("Autopurge already exists.", hidden=True)
return return
@ -85,10 +86,7 @@ class PurgeCog(commands.Cog):
admin=ctx.author.id, admin=ctx.author.id,
delay=delay, delay=delay,
).save() ).save()
await ctx.send( await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds")
f"Autopurge set up on {channel.mention}, "
+ f"delay is {delay} seconds"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="autopurge", base="autopurge",
@ -104,7 +102,7 @@ class PurgeCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_messages=True) @admin_or_permissions(manage_messages=True)
async def _autopurge_remove(self, ctx: SlashContext, channel: TextChannel): async def _autopurge_remove(self, ctx: SlashContext, channel: TextChannel) -> None:
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
if not autopurge: if not autopurge:
await ctx.send("Autopurge does not exist.", hidden=True) await ctx.send("Autopurge does not exist.", hidden=True)
@ -132,15 +130,11 @@ class PurgeCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_messages=True) @admin_or_permissions(manage_messages=True)
async def _autopurge_update( async def _autopurge_update(self, ctx: SlashContext, channel: TextChannel, delay: int) -> None:
self, ctx: SlashContext, channel: TextChannel, delay: int
):
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
if not autopurge: if not autopurge:
await ctx.send("Autopurge does not exist.", hidden=True) await ctx.send("Autopurge does not exist.", hidden=True)
return return
autopurge.delay = delay autopurge.delay = delay
autopurge.save() autopurge.save()
await ctx.send( await ctx.send(f"Autopurge delay updated to {delay} seconds on {channel.mention}.")
f"Autopurge delay updated to {delay} seconds on {channel.mention}."
)

View file

@ -1,8 +1,13 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. RolepingCog."""
from datetime import datetime
from datetime import timedelta
from ButtonPaginator import Paginator from ButtonPaginator import Paginator
from discord import Member, Role from discord import Member
from discord_slash import SlashContext, cog_ext from discord import Role
from discord.ext.commands import Bot
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.model import ButtonStyle from discord_slash.model import ButtonStyle
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
@ -14,7 +19,9 @@ from jarvis.utils.permissions import admin_or_permissions
class RolepingCog(CacheCog): class RolepingCog(CacheCog):
def __init__(self, bot): """J.A.R.V.I.S. RolepingCog."""
def __init__(self, bot: Bot):
super().__init__(bot) super().__init__(bot)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -31,12 +38,10 @@ class RolepingCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _roleping_add(self, ctx: SlashContext, role: Role): async def _roleping_add(self, ctx: SlashContext, role: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first()
if roleping: if roleping:
await ctx.send( await ctx.send(f"Role `{role.name}` already in roleping.", hidden=True)
f"Role `{role.name}` already in roleping.", hidden=True
)
return return
_ = Roleping( _ = Roleping(
role=role.id, role=role.id,
@ -61,7 +66,7 @@ class RolepingCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _roleping_remove(self, ctx: SlashContext, role: Role): async def _roleping_remove(self, ctx: SlashContext, role: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id) roleping = Roleping.objects(guild=ctx.guild.id, role=role.id)
if not roleping: if not roleping:
await ctx.send("Roleping does not exist", hidden=True) await ctx.send("Roleping does not exist", hidden=True)
@ -75,13 +80,12 @@ class RolepingCog(CacheCog):
name="list", name="list",
description="List all blocklisted roles", description="List all blocklisted roles",
) )
async def _roleping_list(self, ctx: SlashContext): async def _roleping_list(self, ctx: SlashContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " f"Please use existing interaction: {exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -94,18 +98,9 @@ class RolepingCog(CacheCog):
embeds = [] embeds = []
for roleping in rolepings: for roleping in rolepings:
role = ctx.guild.get_role(roleping.role) role = ctx.guild.get_role(roleping.role)
bypass_roles = list( bypass_roles = list(filter(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles))
filter( bypass_roles = [r.mention or "||`[redacted]`||" for r in bypass_roles]
lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles bypass_users = [ctx.guild.get_member(u).mention or "||`[redacted]`||" for u in roleping.bypass["users"]]
)
)
bypass_roles = [
r.mention or "||`[redacted]`||" for r in bypass_roles
]
bypass_users = [
ctx.guild.get_member(u).mention or "||`[redacted]`||"
for u in roleping.bypass["users"]
]
bypass_roles = bypass_roles or ["None"] bypass_roles = bypass_roles or ["None"]
bypass_users = bypass_users or ["None"] bypass_users = bypass_users or ["None"]
embed = build_embed( embed = build_embed(
@ -115,9 +110,7 @@ class RolepingCog(CacheCog):
fields=[ fields=[
Field( Field(
name="Created At", name="Created At",
value=roleping.created_at.strftime( value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"),
"%a, %b %d, %Y %I:%M %p"
),
inline=False, inline=False,
), ),
Field(name="Active", value=str(roleping.active)), Field(name="Active", value=str(roleping.active)),
@ -136,12 +129,8 @@ class RolepingCog(CacheCog):
if not admin: if not admin:
admin = self.bot.user admin = self.bot.user
embed.set_author( embed.set_author(name=admin.nick or admin.name, icon_url=admin.avatar_url)
name=admin.nick or admin.name, icon_url=admin.avatar_url embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}")
)
embed.set_footer(
text=f"{admin.name}#{admin.discriminator} | {admin.id}"
)
embeds.append(embed) embeds.append(embed)
@ -191,14 +180,10 @@ class RolepingCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _roleping_bypass_user( async def _roleping_bypass_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
self, ctx: SlashContext, user: Member, rping: Role
):
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send( await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
f"Roleping not configured for {rping.mention}", hidden=True
)
return return
if user.id in roleping.bypass["users"]: if user.id in roleping.bypass["users"]:
@ -207,29 +192,23 @@ class RolepingCog(CacheCog):
if len(roleping.bypass["users"]) == 10: if len(roleping.bypass["users"]) == 10:
await ctx.send( await ctx.send(
"Already have 10 users in bypass. " "Already have 10 users in bypass. Please consider using roles for roleping bypass",
"Please consider using roles for roleping bypass",
hidden=True, hidden=True,
) )
return return
matching_role = list( matching_role = list(filter(lambda x: x.id in roleping.bypass["roles"], user.roles))
filter(lambda x: x.id in roleping.bypass["roles"], user.roles)
)
if matching_role: if matching_role:
await ctx.send( await ctx.send(
f"{user.mention} already has bypass " f"{user.mention} already has bypass via {matching_role[0].mention}",
f"via {matching_role[0].mention}",
hidden=True, hidden=True,
) )
return return
roleping.bypass["users"].append(user.id) roleping.bypass["users"].append(user.id)
roleping.save() roleping.save()
await ctx.send( await ctx.send(f"{user.nick or user.name} user bypass added for `{rping.name}`")
f"{user.nick or user.name} user bypass added for `{rping.name}`"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="roleping", base="roleping",
@ -254,14 +233,10 @@ class RolepingCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _roleping_bypass_role( async def _roleping_bypass_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
self, ctx: SlashContext, role: Role, rping: Role
):
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send( await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
f"Roleping not configured for {rping.mention}", hidden=True
)
return return
if role.id in roleping.bypass["roles"]: if role.id in roleping.bypass["roles"]:
@ -270,8 +245,7 @@ class RolepingCog(CacheCog):
if len(roleping.bypass["roles"]) == 10: if len(roleping.bypass["roles"]) == 10:
await ctx.send( await ctx.send(
"Already have 10 roles in bypass. " "Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass",
"Please consider consolidating roles for roleping bypass",
hidden=True, hidden=True,
) )
return return
@ -303,14 +277,10 @@ class RolepingCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _roleping_restore_user( async def _roleping_restore_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
self, ctx: SlashContext, user: Member, rping: Role
):
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send( await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
f"Roleping not configured for {rping.mention}", hidden=True
)
return return
if user.id not in roleping.bypass["users"]: if user.id not in roleping.bypass["users"]:
@ -319,9 +289,7 @@ class RolepingCog(CacheCog):
roleping.bypass["users"].delete(user.id) roleping.bypass["users"].delete(user.id)
roleping.save() roleping.save()
await ctx.send( await ctx.send(f"{user.nick or user.name} user bypass removed for `{rping.name}`")
f"{user.nick or user.name} user bypass removed for `{rping.name}`"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="roleping", base="roleping",
@ -346,14 +314,10 @@ class RolepingCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _roleping_restore_role( async def _roleping_restore_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
self, ctx: SlashContext, role: Role, rping: Role
):
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send( await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
f"Roleping not configured for {rping.mention}", hidden=True
)
return return
if role.id in roleping.bypass["roles"]: if role.id in roleping.bypass["roles"]:
@ -362,8 +326,7 @@ class RolepingCog(CacheCog):
if len(roleping.bypass["roles"]) == 10: if len(roleping.bypass["roles"]) == 10:
await ctx.send( await ctx.send(
"Already have 10 roles in bypass. " "Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass",
"Please consider consolidating roles for roleping bypass",
hidden=True, hidden=True,
) )
return return

View file

@ -1,10 +1,15 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. WarningCog."""
from datetime import datetime
from datetime import timedelta
from ButtonPaginator import Paginator from ButtonPaginator import Paginator
from discord import User from discord import User
from discord_slash import SlashContext, cog_ext from discord.ext.commands import Bot
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.model import ButtonStyle from discord_slash.model import ButtonStyle
from discord_slash.utils.manage_commands import create_choice, create_option from discord_slash.utils.manage_commands import create_choice
from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Warning from jarvis.db.models import Warning
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -14,7 +19,9 @@ from jarvis.utils.permissions import admin_or_permissions
class WarningCog(CacheCog): class WarningCog(CacheCog):
def __init__(self, bot): """J.A.R.V.I.S. WarningCog."""
def __init__(self, bot: Bot):
super().__init__(bot) super().__init__(bot)
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -42,9 +49,7 @@ class WarningCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _warn( async def _warn(self, ctx: SlashContext, user: User, reason: str, duration: int = 24) -> None:
self, ctx: SlashContext, user: User, reason: str, duration: int = 24
):
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", hidden=True)
return return
@ -100,14 +105,13 @@ class WarningCog(CacheCog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _warnings(self, ctx: SlashContext, user: User, active: bool = 1): async def _warnings(self, ctx: SlashContext, user: User, active: bool = 1) -> None:
active = bool(active) active = bool(active)
exists = self.check_cache(ctx, user_id=user.id, active=active) exists = self.check_cache(ctx, user_id=user.id, active=active)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " f"Please use existing interaction: {exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -115,9 +119,7 @@ class WarningCog(CacheCog):
user=user.id, user=user.id,
guild=ctx.guild.id, guild=ctx.guild.id,
).order_by("-created_at") ).order_by("-created_at")
active_warns = Warning.objects( active_warns = Warning.objects(user=user.id, guild=ctx.guild.id, active=True).order_by("-created_at")
user=user.id, guild=ctx.guild.id, active=True
).order_by("-created_at")
pages = [] pages = []
if active: if active:
@ -139,30 +141,23 @@ class WarningCog(CacheCog):
admin_name = f"{admin.name}#{admin.discriminator}" admin_name = f"{admin.name}#{admin.discriminator}"
fields.append( fields.append(
Field( Field(
name=warn.created_at.strftime( name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"),
"%Y-%m-%d %H:%M:%S UTC" value=f"{warn.reason}\nAdmin: {admin_name}\n\u200b",
),
value=f"{warn.reason}\n"
+ f"Admin: {admin_name}\n"
+ "\u200b",
inline=False, inline=False,
) )
) )
for i in range(0, len(fields), 5): for i in range(0, len(fields), 5):
embed = build_embed( embed = build_embed(
title="Warnings", title="Warnings",
description=f"{warnings.count()} total | " description=f"{warnings.count()} total | {active_warns.count()} currently active",
+ f"{active_warns.count()} currently active", fields=fields[i : i + 5], # noqa: E203
fields=fields[i : i + 5],
) )
embed.set_author( embed.set_author(
name=user.name + "#" + user.discriminator, name=user.name + "#" + user.discriminator,
icon_url=user.avatar_url, icon_url=user.avatar_url,
) )
embed.set_thumbnail(url=ctx.guild.icon_url) embed.set_thumbnail(url=ctx.guild.icon_url)
embed.set_footer( embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
text=f"{user.name}#{user.discriminator} | {user.id}"
)
pages.append(embed) pages.append(embed)
else: else:
fields = [] fields = []
@ -179,9 +174,8 @@ class WarningCog(CacheCog):
for i in range(0, len(fields), 5): for i in range(0, len(fields), 5):
embed = build_embed( embed = build_embed(
title="Warnings", title="Warnings",
description=f"{warnings.count()} total | " description=f"{warnings.count()} total | {active_warns.count()} currently active",
+ f"{active_warns.count()} currently active", fields=fields[i : i + 5], # noqa: E203
fields=fields[i : i + 5],
) )
embed.set_author( embed.set_author(
name=user.name + "#" + user.discriminator, name=user.name + "#" + user.discriminator,

View file

@ -1,9 +1,11 @@
"""J.A.R.V.I.S. Autoreact Cog."""
import re import re
from discord import TextChannel from discord import TextChannel
from discord.ext import commands from discord.ext import commands
from discord.utils import find from discord.utils import find
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.data.unicode import emoji_list from jarvis.data.unicode import emoji_list
@ -12,7 +14,9 @@ from jarvis.utils.permissions import admin_or_permissions
class AutoReactCog(commands.Cog): class AutoReactCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. Autoreact Cog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") self.custom_emote = re.compile(r"^<:\w+:(\d+)>$")
@ -30,17 +34,13 @@ class AutoReactCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _autoreact_create(self, ctx: SlashContext, channel: TextChannel): async def _autoreact_create(self, ctx: SlashContext, channel: TextChannel) -> None:
if not isinstance(channel, TextChannel): if not isinstance(channel, TextChannel):
await ctx.send("Channel must be a text channel", hidden=True) await ctx.send("Channel must be a text channel", hidden=True)
return return
exists = Autoreact.objects( exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
guild=ctx.guild.id, channel=channel.id
).first()
if exists: if exists:
await ctx.send( await ctx.send(f"Autoreact already exists for {channel.mention}.", hidden=True)
f"Autoreact already exists for {channel.mention}.", hidden=True
)
return return
_ = Autoreact( _ = Autoreact(
@ -65,16 +65,12 @@ class AutoReactCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _autoreact_delete(self, ctx, channel: TextChannel): async def _autoreact_delete(self, ctx: SlashContext, channel: TextChannel) -> None:
exists = Autoreact.objects( exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete()
guild=ctx.guild.id, channel=channel.id
).delete()
if exists: if exists:
await ctx.send(f"Autoreact removed from {channel.mention}") await ctx.send(f"Autoreact removed from {channel.mention}")
else: else:
await ctx.send( await ctx.send(f"Autoreact not found on {channel.mention}", hidden=True)
f"Autoreact not found on {channel.mention}", hidden=True
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="autoreact", base="autoreact",
@ -96,32 +92,24 @@ class AutoReactCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _autoreact_add(self, ctx, channel: TextChannel, emote: str): async def _autoreact_add(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None:
await ctx.defer() await ctx.defer()
custom_emoji = self.custom_emote.match(emote) custom_emoji = self.custom_emote.match(emote)
standard_emoji = emote in emoji_list standard_emoji = emote in emoji_list
if not custom_emoji and not standard_emoji: if not custom_emoji and not standard_emoji:
await ctx.send( await ctx.send(
"Please use either an emote from this server" "Please use either an emote from this server or a unicode emoji.",
+ " or a unicode emoji.",
hidden=True, hidden=True,
) )
return return
if custom_emoji: if custom_emoji:
emoji_id = int(custom_emoji.group(1)) emoji_id = int(custom_emoji.group(1))
if not find(lambda x: x.id == emoji_id, ctx.guild.emojis): if not find(lambda x: x.id == emoji_id, ctx.guild.emojis):
await ctx.send( await ctx.send("Please use a custom emote from this server.", hidden=True)
"Please use a custom emote from this server.", hidden=True
)
return return
exists = Autoreact.objects( exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
guild=ctx.guild.id, channel=channel.id
).first()
if not exists: if not exists:
await ctx.send( await ctx.send(f"Please create autoreact first with /autoreact create {channel.mention}")
"Please create autoreact first with "
+ f"/autoreact create {channel.mention}"
)
return return
if emote in exists.reactions: if emote in exists.reactions:
await ctx.send( await ctx.send(
@ -131,8 +119,7 @@ class AutoReactCog(commands.Cog):
return return
if len(exists.reactions) >= 5: if len(exists.reactions) >= 5:
await ctx.send( await ctx.send(
"Max number of reactions hit. " "Max number of reactions hit. Remove a different one to add this one",
+ "Remove a different one to add this one",
hidden=True, hidden=True,
) )
return return
@ -160,14 +147,11 @@ class AutoReactCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _autoreact_remove(self, ctx, channel: TextChannel, emote: str): async def _autoreact_remove(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None:
exists = Autoreact.objects( exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
guild=ctx.guild.id, channel=channel.id
).first()
if not exists: if not exists:
await ctx.send( await ctx.send(
"Please create autoreact first with " f"Please create autoreact first with /autoreact create {channel.mention}",
+ f"/autoreact create {channel.mention}",
hidden=True, hidden=True,
) )
return return
@ -195,27 +179,22 @@ class AutoReactCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _autoreact_list(self, ctx, channel: TextChannel): async def _autoreact_list(self, ctx: SlashContext, channel: TextChannel) -> None:
exists = Autoreact.objects( exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
guild=ctx.guild.id, channel=channel.id
).first()
if not exists: if not exists:
await ctx.send( await ctx.send(
"Please create autoreact first with " f"Please create autoreact first with /autoreact create {channel.mention}",
+ f"/autoreact create {channel.mention}",
hidden=True, hidden=True,
) )
return return
message = "" message = ""
if len(exists.reactions) > 0: if len(exists.reactions) > 0:
message = ( message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(exists.reactions)
f"Current active autoreacts on {channel.mention}:\n"
+ "\n".join(exists.reactions)
)
else: else:
message = f"No reactions set on {channel.mention}" message = f"No reactions set on {channel.mention}"
await ctx.send(message) await ctx.send(message)
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add AutoReactCog to J.A.R.V.I.S."""
bot.add_cog(AutoReactCog(bot)) bot.add_cog(AutoReactCog(bot))

View file

@ -1,14 +1,16 @@
"""J.A.R.V.I.S. Complete the Code 2 Cog."""
import re import re
from datetime import datetime, timedelta from datetime import datetime
from datetime import timedelta
import aiohttp import aiohttp
import pymongo
from ButtonPaginator import Paginator from ButtonPaginator import Paginator
from discord import Member, User from discord import Member
from discord import User
from discord.commands.ext import Bot
from discord.ext import commands from discord.ext import commands
from discord.ext.tasks import loop from discord_slash import cog_ext
from discord.utils import find from discord_slash import SlashContext
from discord_slash import SlashContext, cog_ext
from discord_slash.model import ButtonStyle from discord_slash.model import ButtonStyle
from jarvis.db.models import Guess from jarvis.db.models import Guess
@ -26,11 +28,16 @@ invites = re.compile(
class CTCCog(CacheCog): class CTCCog(CacheCog):
def __init__(self, bot): """J.A.R.V.I.S. Complete the Code 2 Cog."""
def __init__(self, bot: Bot):
super().__init__(bot) super().__init__(bot)
self._session = aiohttp.ClientSession() self._session = aiohttp.ClientSession()
self.url = "https://completethecodetwo.cards/pw" self.url = "https://completethecodetwo.cards/pw"
def __del__(self):
self._session.close()
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="ctc2", base="ctc2",
name="about", name="about",
@ -38,7 +45,7 @@ class CTCCog(CacheCog):
guild_ids=guild_ids, guild_ids=guild_ids,
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _about(self, ctx): async def _about(self, ctx: SlashContext) -> None:
await ctx.send("See https://completethecode.com for more information") await ctx.send("See https://completethecode.com for more information")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -48,25 +55,22 @@ class CTCCog(CacheCog):
guild_ids=guild_ids, guild_ids=guild_ids,
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _pw(self, ctx: SlashContext, guess: str): async def _pw(self, ctx: SlashContext, guess: str) -> None:
if len(guess) > 800: if len(guess) > 800:
await ctx.send( await ctx.send(
"Listen here, dipshit. Don't be like " "Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses < 800 characters.",
+ "<@256110768724901889>. Make your guesses < 800 characters.",
hidden=True, hidden=True,
) )
return return
elif not valid.fullmatch(guess): elif not valid.fullmatch(guess):
await ctx.send( await ctx.send(
"Listen here, dipshit. Don't be like " "Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses *readable*.",
+ "<@256110768724901889>. Make your guesses *readable*.",
hidden=True, hidden=True,
) )
return return
elif invites.search(guess): elif invites.search(guess):
await ctx.send( await ctx.send(
"Listen here, dipshit. " "Listen here, dipshit. No using this to bypass sending invite links.",
+ "No using this to bypass sending invite links.",
hidden=True, hidden=True,
) )
return return
@ -77,9 +81,7 @@ class CTCCog(CacheCog):
result = await self._session.post(self.url, data=guess) result = await self._session.post(self.url, data=guess)
correct = False correct = False
if 200 <= result.status < 400: if 200 <= result.status < 400:
await ctx.send( await ctx.send(f"{ctx.author.mention} got it! Password is {guess}!")
f"{ctx.author.mention} got it! Password is {guess}!"
)
correct = True correct = True
else: else:
await ctx.send("Nope.", hidden=True) await ctx.send("Nope.", hidden=True)
@ -92,13 +94,12 @@ class CTCCog(CacheCog):
guild_ids=guild_ids, guild_ids=guild_ids,
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _guesses(self, ctx: SlashContext): async def _guesses(self, ctx: SlashContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " f"Please use existing interaction: {exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -127,7 +128,7 @@ class CTCCog(CacheCog):
embed = build_embed( embed = build_embed(
title="completethecodetwo.cards guesses", title="completethecodetwo.cards guesses",
description=f"{len(fields)} guesses so far", description=f"{len(fields)} guesses so far",
fields=fields[i : i + 5], fields=fields[i : i + 5], # noqa: E203
url="https://completethecodetwo.cards", url="https://completethecodetwo.cards",
) )
embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png") embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png")
@ -161,5 +162,6 @@ class CTCCog(CacheCog):
await paginator.start() await paginator.start()
def setup(bot): def setup(bot: Bot) -> None:
"""Add CTCCog to J.A.R.V.I.S."""
bot.add_cog(CTCCog(bot)) bot.add_cog(CTCCog(bot))

View file

@ -1,8 +1,10 @@
"""J.A.R.V.I.S. dbrand cog."""
import re import re
import aiohttp import aiohttp
from discord.ext import commands from discord.ext import commands
from discord_slash import cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.config import get_config from jarvis.config import get_config
@ -20,7 +22,7 @@ class DbrandCog(commands.Cog):
Mostly support functions. Credit @cpixl for the shipping API Mostly support functions. Credit @cpixl for the shipping API
""" """
def __init__(self, bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.base_url = "https://dbrand.com/" self.base_url = "https://dbrand.com/"
self._session = aiohttp.ClientSession() self._session = aiohttp.ClientSession()
@ -28,6 +30,9 @@ class DbrandCog(commands.Cog):
self.api_url = get_config().urls["dbrand_shipping"] self.api_url = get_config().urls["dbrand_shipping"]
self.cache = {} self.cache = {}
def __del__(self):
self._session.close()
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="db", base="db",
name="skin", name="skin",
@ -35,7 +40,7 @@ class DbrandCog(commands.Cog):
description="See what skins are available", description="See what skins are available",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _skin(self, ctx): async def _skin(self, ctx: SlashContext) -> None:
await ctx.send(self.base_url + "shop/skins") await ctx.send(self.base_url + "shop/skins")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -45,7 +50,7 @@ class DbrandCog(commands.Cog):
description="Get some robot camo. Make Tony Stark proud", description="Get some robot camo. Make Tony Stark proud",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _camo(self, ctx): async def _camo(self, ctx: SlashContext) -> None:
await ctx.send(self.base_url + "shop/special-edition/robot-camo") await ctx.send(self.base_url + "shop/special-edition/robot-camo")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -55,7 +60,7 @@ class DbrandCog(commands.Cog):
description="See devices with Grip support", description="See devices with Grip support",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _grip(self, ctx): async def _grip(self, ctx: SlashContext) -> None:
await ctx.send(self.base_url + "shop/grip/#grip-devices") await ctx.send(self.base_url + "shop/grip/#grip-devices")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -65,10 +70,8 @@ class DbrandCog(commands.Cog):
description="Contact support", description="Contact support",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _contact(self, ctx): async def _contact(self, ctx: SlashContext) -> None:
await ctx.send( await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
"Contact dbrand support here: " + self.base_url + "contact"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="db", base="db",
@ -77,10 +80,8 @@ class DbrandCog(commands.Cog):
description="Contact support", description="Contact support",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _support(self, ctx): async def _support(self, ctx: SlashContext) -> None:
await ctx.send( await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
"Contact dbrand support here: " + self.base_url + "contact"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="db", base="db",
@ -89,7 +90,7 @@ class DbrandCog(commands.Cog):
description="Get your order status", description="Get your order status",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _orderstat(self, ctx): async def _orderstat(self, ctx: SlashContext) -> None:
await ctx.send(self.base_url + "order-status") await ctx.send(self.base_url + "order-status")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -99,7 +100,7 @@ class DbrandCog(commands.Cog):
description="Get your order status", description="Get your order status",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _orders(self, ctx): async def _orders(self, ctx: SlashContext) -> None:
await ctx.send(self.base_url + "order-status") await ctx.send(self.base_url + "order-status")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -109,7 +110,7 @@ class DbrandCog(commands.Cog):
description="dbrand status", description="dbrand status",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _status(self, ctx): async def _status(self, ctx: SlashContext) -> None:
await ctx.send(self.base_url + "status") await ctx.send(self.base_url + "status")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -119,7 +120,7 @@ class DbrandCog(commands.Cog):
description="Give us your money!", description="Give us your money!",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _buy(self, ctx): async def _buy(self, ctx: SlashContext) -> None:
await ctx.send("Give us your money! " + self.base_url + "shop") await ctx.send("Give us your money! " + self.base_url + "shop")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -129,10 +130,8 @@ class DbrandCog(commands.Cog):
description="(not) extortion", description="(not) extortion",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _extort(self, ctx): async def _extort(self, ctx: SlashContext) -> None:
await ctx.send( await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion")
"Be (not) extorted here: " + self.base_url + "not-extortion"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="db", base="db",
@ -141,10 +140,8 @@ class DbrandCog(commands.Cog):
guild_ids=guild_ids, guild_ids=guild_ids,
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _wallpapers(self, ctx): async def _wallpapers(self, ctx: SlashContext) -> None:
await ctx.send( await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers")
"Get robot camo wallpapers here: https://db.io/wallpapers"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="db", base="db",
@ -155,8 +152,7 @@ class DbrandCog(commands.Cog):
( (
create_option( create_option(
name="search", name="search",
description="Country search query (2 character code, " description="Country search query (2 character code, country name, emoji)",
+ "country name, emoji)",
option_type=3, option_type=3,
required=True, required=True,
) )
@ -164,7 +160,7 @@ class DbrandCog(commands.Cog):
], ],
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _shipping(self, ctx, *, search: str): async def _shipping(self, ctx: SlashContext, search: str) -> None:
await ctx.defer() await ctx.defer()
if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE): if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
if re.match( if re.match(
@ -174,9 +170,7 @@ class DbrandCog(commands.Cog):
): ):
# Magic number, subtract from flag char to get ascii char # Magic number, subtract from flag char to get ascii char
uni2ascii = 127365 uni2ascii = 127365
search = chr(ord(search[0]) - uni2ascii) + chr( search = chr(ord(search[0]) - uni2ascii) + chr(ord(search[1]) - uni2ascii)
ord(search[1]) - uni2ascii
)
elif search == "🏳️": elif search == "🏳️":
search = "fr" search = "fr"
else: else:
@ -184,11 +178,7 @@ class DbrandCog(commands.Cog):
await ctx.send("Please use text to search for shipping.") await ctx.send("Please use text to search for shipping.")
return return
if len(search) > 2: if len(search) > 2:
matches = [ matches = [x["code"] for x in shipping_lookup if search.lower() in x["country"]]
x["code"]
for x in shipping_lookup
if search.lower() in x["country"]
]
if len(matches) > 0: if len(matches) > 0:
search = matches[0] search = matches[0]
dest = search.lower() dest = search.lower()
@ -202,17 +192,11 @@ class DbrandCog(commands.Cog):
data = None data = None
self.cache[dest] = data self.cache[dest] = data
fields = None fields = None
if ( if data is not None and data["is_valid"] and data["shipping_available"]:
data is not None
and data["is_valid"]
and data["shipping_available"]
):
fields = [] fields = []
fields.append(Field(data["short-name"], data["time-title"])) fields.append(Field(data["short-name"], data["time-title"]))
for service in data["shipping_services_available"][1:]: for service in data["shipping_services_available"][1:]:
service_data = await self._session.get( service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
self.api_url + dest + "/" + service["url"]
)
if service_data.status > 400: if service_data.status > 400:
continue continue
service_data = await service_data.json() service_data = await service_data.json()
@ -222,22 +206,13 @@ class DbrandCog(commands.Cog):
service_data["time-title"], service_data["time-title"],
) )
) )
country = "-".join( country = "-".join(x for x in data["country"].split(" ") if x != "the")
x for x in data["country"].split(" ") if x != "the"
)
country_urlsafe = country.replace("-", "%20") country_urlsafe = country.replace("-", "%20")
description = ( description = (
"Click the link above to " f"Click the link above to see shipping time to {data['country']}."
+ "see shipping time to {data['country']}." "\n[View all shipping destinations](https://dbrand.com/shipping)"
) " | [Check shipping status]"
description += ( f"(https://dbrand.com/status#main-content:~:text={country_urlsafe})"
"\n[View all shipping destinations]"
+ "(https://dbrand.com/shipping)"
)
description += " | [Check shipping status]"
description += (
"(https://dbrand.com/status"
+ f"#main-content:~:text={country_urlsafe})"
) )
embed = build_embed( embed = build_embed(
title="Shipping to {}".format(data["country"]), title="Shipping to {}".format(data["country"]),
@ -255,8 +230,9 @@ class DbrandCog(commands.Cog):
elif not data["is_valid"]: elif not data["is_valid"]:
embed = build_embed( embed = build_embed(
title="Check Shipping Times", title="Check Shipping Times",
description="Country not found.\nYou can [view all shipping " description=(
+ "destinations here](https://dbrand.com/shipping)", "Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)"
),
fields=[], fields=[],
url="https://dbrand.com/shipping", url="https://dbrand.com/shipping",
color="#FFBB00", color="#FFBB00",
@ -270,9 +246,11 @@ class DbrandCog(commands.Cog):
elif not data["shipping_available"]: elif not data["shipping_available"]:
embed = build_embed( embed = build_embed(
title="Shipping to {}".format(data["country"]), title="Shipping to {}".format(data["country"]),
description="No shipping available.\nTime to move to a country" description=(
+ " that has shipping available.\nYou can [find a new country " "No shipping available.\nTime to move to a country"
+ "to live in here](https://dbrand.com/shipping)", " that has shipping available.\nYou can [find a new country "
"to live in here](https://dbrand.com/shipping)"
),
fields=[], fields=[],
url="https://dbrand.com/shipping", url="https://dbrand.com/shipping",
color="#FFBB00", color="#FFBB00",
@ -285,5 +263,6 @@ class DbrandCog(commands.Cog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add dbrandcog to J.A.R.V.I.S."""
bot.add_cog(DbrandCog(bot)) bot.add_cog(DbrandCog(bot))

View file

@ -1,42 +1,34 @@
"""J.A.R.V.I.S. Developer Cog."""
import base64 import base64
import hashlib import hashlib
import re import re
import subprocess import subprocess # noqa: S404
import uuid as uuidpy import uuid as uuidpy
from typing import Any
from typing import Union
import ulid as ulidpy import ulid as ulidpy
from bson import ObjectId from bson import ObjectId
from discord.ext import commands from discord.ext import commands
from discord_slash import cog_ext from discord_slash import cog_ext
from discord_slash.utils.manage_commands import create_choice, create_option from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_choice
from discord_slash.utils.manage_commands import create_option
from jarvis.utils import build_embed, convert_bytesize from jarvis.utils import build_embed
from jarvis.utils import convert_bytesize
from jarvis.utils.field import Field from jarvis.utils.field import Field
supported_hashes = { supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x}
x for x in hashlib.algorithms_guaranteed if "shake" not in x
}
OID_VERIFY = re.compile(r"^([1-9][0-9]{0,3}|0)(\.([1-9][0-9]{0,3}|0)){5,13}$") OID_VERIFY = re.compile(r"^([1-9][0-9]{0,3}|0)(\.([1-9][0-9]{0,3}|0)){5,13}$")
URL_VERIFY = re.compile( URL_VERIFY = re.compile(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]"
+ r"|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
)
DN_VERIFY = re.compile( DN_VERIFY = re.compile(
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)" r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$"
+ r"?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)"
+ r"?(?P<domain>(?:DC=[^,]+,?)+)$"
) )
ULID_VERIFY = re.compile(r"^[0-9a-z]{26}$", re.IGNORECASE) ULID_VERIFY = re.compile(r"^[0-9a-z]{26}$", re.IGNORECASE)
UUID_VERIFY = re.compile( UUID_VERIFY = re.compile(
( r"[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$",
"[a-f0-9]{8}-"
+ "[a-f0-9]{4}-"
+ "[1-5]"
+ "[a-f0-9]{3}-"
+ "[89ab][a-f0-9]{3}-"
+ "[a-f0-9]{12}$"
),
re.IGNORECASE, re.IGNORECASE,
) )
@ -48,9 +40,8 @@ invites = re.compile(
UUID_GET = {3: uuidpy.uuid3, 5: uuidpy.uuid5} UUID_GET = {3: uuidpy.uuid3, 5: uuidpy.uuid5}
def hash_obj(hash, data, text: bool = True) -> str: def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str:
""" """Hash data with hash object.
Hash data with hash object
Data can be text or binary Data can be text or binary
""" """
@ -60,14 +51,16 @@ def hash_obj(hash, data, text: bool = True) -> str:
BSIZE = 65536 BSIZE = 65536
block_idx = 0 block_idx = 0
while block_idx * BSIZE < len(data): while block_idx * BSIZE < len(data):
block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)] block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)] # noqa: E203
hash.update(block) hash.update(block)
block_idx += 1 block_idx += 1
return hash.hexdigest() return hash.hexdigest()
class DevCog(commands.Cog): class DevCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. Developer Cog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -79,9 +72,7 @@ class DevCog(commands.Cog):
description="Hash method", description="Hash method",
option_type=3, option_type=3,
required=True, required=True,
choices=[ choices=[create_choice(name=x, value=x) for x in supported_hashes],
create_choice(name=x, value=x) for x in supported_hashes
],
), ),
create_option( create_option(
name="data", name="data",
@ -92,7 +83,7 @@ class DevCog(commands.Cog):
], ],
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _hash(self, ctx, method: str, data: str): async def _hash(self, ctx: SlashContext, method: str, data: str) -> None:
if not data: if not data:
await ctx.send( await ctx.send(
"No data to hash", "No data to hash",
@ -111,9 +102,7 @@ class DevCog(commands.Cog):
Field("Hash", f"`{hex}`", False), Field("Hash", f"`{hex}`", False),
] ]
embed = build_embed( embed = build_embed(title=title, description=description, fields=fields)
title=title, description=description, fields=fields
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -125,9 +114,7 @@ class DevCog(commands.Cog):
description="UUID version", description="UUID version",
option_type=3, option_type=3,
required=True, required=True,
choices=[ choices=[create_choice(name=x, value=x) for x in ["3", "4", "5"]],
create_choice(name=x, value=x) for x in ["3", "4", "5"]
],
), ),
create_option( create_option(
name="data", name="data",
@ -137,7 +124,7 @@ class DevCog(commands.Cog):
), ),
], ],
) )
async def _uuid(self, ctx, version: str, data: str = None): async def _uuid(self, ctx: SlashContext, version: str, data: str = None) -> None:
version = int(version) version = int(version)
if version in [3, 5] and not data: if version in [3, 5] and not data:
await ctx.send(f"UUID{version} requires data.", hidden=True) await ctx.send(f"UUID{version} requires data.", hidden=True)
@ -161,8 +148,7 @@ class DevCog(commands.Cog):
description="Generate an ObjectID", description="Generate an ObjectID",
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _objectid(self, ctx): async def _objectid(self, ctx: SlashContext) -> None:
"""Generates new bson.ObjectId"""
await ctx.send(f"ObjectId: `{str(ObjectId())}`") await ctx.send(f"ObjectId: `{str(ObjectId())}`")
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -170,8 +156,7 @@ class DevCog(commands.Cog):
description="Generate a ULID", description="Generate a ULID",
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _ulid(self, ctx): async def _ulid(self, ctx: SlashContext) -> None:
"""Generates a new ULID"""
await ctx.send(f"ULID: `{ulidpy.new().str}`") await ctx.send(f"ULID: `{ulidpy.new().str}`")
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -179,8 +164,7 @@ class DevCog(commands.Cog):
description="Convert a UUID to a ULID", description="Convert a UUID to a ULID",
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _uuid2ulid(self, ctx: commands.Context, uuid): async def _uuid2ulid(self, ctx: SlashContext, uuid: str) -> None:
"""Converts a UUID to a ULID"""
if UUID_VERIFY.match(uuid): if UUID_VERIFY.match(uuid):
u = ulidpy.parse(uuid) u = ulidpy.parse(uuid)
await ctx.send(f"ULID: `{u.str}`") await ctx.send(f"ULID: `{u.str}`")
@ -192,8 +176,7 @@ class DevCog(commands.Cog):
description="Convert a ULID to a UUID", description="Convert a ULID to a UUID",
) )
@commands.cooldown(1, 2, commands.BucketType.user) @commands.cooldown(1, 2, commands.BucketType.user)
async def _ulid2uuid(self, ctx: commands.Context, ulid): async def _ulid2uuid(self, ctx: SlashContext, ulid: str) -> None:
"""Converts a ULID to a UUID"""
if ULID_VERIFY.match(ulid): if ULID_VERIFY.match(ulid):
ulid = ulidpy.parse(ulid) ulid = ulidpy.parse(ulid)
await ctx.send(f"UUID: `{ulid.uuid}`") await ctx.send(f"UUID: `{ulid.uuid}`")
@ -211,9 +194,7 @@ class DevCog(commands.Cog):
description="Encode method", description="Encode method",
option_type=3, option_type=3,
required=True, required=True,
choices=[ choices=[create_choice(name=x, value=x) for x in base64_methods],
create_choice(name=x, value=x) for x in base64_methods
],
), ),
create_option( create_option(
name="data", name="data",
@ -223,8 +204,7 @@ class DevCog(commands.Cog):
), ),
], ],
) )
async def _encode(self, ctx, method: str, data: str): async def _encode(self, ctx: SlashContext, method: str, data: str) -> None:
"""Encodes text with specified encoding method"""
method = getattr(base64, method + "encode") method = getattr(base64, method + "encode")
await ctx.send(f"`{method(data.encode('UTF-8')).decode('UTF-8')}`") await ctx.send(f"`{method(data.encode('UTF-8')).decode('UTF-8')}`")
@ -237,9 +217,7 @@ class DevCog(commands.Cog):
description="Decode method", description="Decode method",
option_type=3, option_type=3,
required=True, required=True,
choices=[ choices=[create_choice(name=x, value=x) for x in base64_methods],
create_choice(name=x, value=x) for x in base64_methods
],
), ),
create_option( create_option(
name="data", name="data",
@ -249,8 +227,7 @@ class DevCog(commands.Cog):
), ),
], ],
) )
async def _decode(self, ctx, method: str, data: str): async def _decode(self, ctx: SlashContext, method: str, data: str) -> None:
"""Decodes text with specified encoding method"""
method = getattr(base64, method + "decode") method = getattr(base64, method + "decode")
decoded = method(data.encode("UTF-8")).decode("UTF-8") decoded = method(data.encode("UTF-8")).decode("UTF-8")
if invites.search(decoded): if invites.search(decoded):
@ -266,12 +243,11 @@ class DevCog(commands.Cog):
description="Get J.A.R.V.I.S. lines of code", description="Get J.A.R.V.I.S. lines of code",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _cloc(self, ctx): async def _cloc(self, ctx: SlashContext) -> None:
output = subprocess.check_output( output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8") # noqa: S603, S607
["tokei", "-C", "--sort", "code"]
).decode("UTF-8")
await ctx.send(f"```\n{output}\n```") await ctx.send(f"```\n{output}\n```")
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add DevCog to J.A.R.V.I.S."""
bot.add_cog(DevCog(bot)) bot.add_cog(DevCog(bot))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. error handling cog."""
from discord.ext import commands from discord.ext import commands
from discord_slash import SlashContext from discord_slash import SlashContext
@ -5,36 +6,36 @@ from jarvis import slash
class ErrorHandlerCog(commands.Cog): class ErrorHandlerCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. error handling cog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@commands.Cog.listener() @commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error): async def on_command_error(self, ctx: commands.Context, error: Exception) -> None:
"""d.py on_command_error override."""
if isinstance(error, commands.errors.MissingPermissions): if isinstance(error, commands.errors.MissingPermissions):
await ctx.send("I'm afraid I can't let you do that.") await ctx.send("I'm afraid I can't let you do that.")
elif isinstance(error, commands.errors.CommandNotFound): elif isinstance(error, commands.errors.CommandNotFound):
return return
elif isinstance(error, commands.errors.CommandOnCooldown): elif isinstance(error, commands.errors.CommandOnCooldown):
await ctx.send( await ctx.send(
"Command on cooldown. " "Command on cooldown. " + f"Please wait {error.retry_after:0.2f}s before trying again",
+ f"Please wait {error.retry_after:0.2f}s before trying again",
) )
else: else:
await ctx.send(f"Error processing command:\n```{error}```") await ctx.send(f"Error processing command:\n```{error}```")
ctx.command.reset_cooldown(ctx) ctx.command.reset_cooldown(ctx)
@commands.Cog.listener() @commands.Cog.listener()
async def on_slash_command_error(self, ctx: SlashContext, error): async def on_slash_command_error(self, ctx: SlashContext, error: Exception) -> None:
if isinstance(error, commands.errors.MissingPermissions) or isinstance( """discord_slash on_slash_command_error override."""
error, commands.errors.CheckFailure if isinstance(error, commands.errors.MissingPermissions) or isinstance(error, commands.errors.CheckFailure):
):
await ctx.send("I'm afraid I can't let you do that.", hidden=True) await ctx.send("I'm afraid I can't let you do that.", hidden=True)
elif isinstance(error, commands.errors.CommandNotFound): elif isinstance(error, commands.errors.CommandNotFound):
return return
elif isinstance(error, commands.errors.CommandOnCooldown): elif isinstance(error, commands.errors.CommandOnCooldown):
await ctx.send( await ctx.send(
"Command on cooldown. " "Command on cooldown. " + f"Please wait {error.retry_after:0.2f}s before trying again",
+ f"Please wait {error.retry_after:0.2f}s before trying again",
hidden=True, hidden=True,
) )
else: else:
@ -45,5 +46,6 @@ class ErrorHandlerCog(commands.Cog):
slash.commands[ctx.command].reset_cooldown(ctx) slash.commands[ctx.command].reset_cooldown(ctx)
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add ErrorHandlerCog to J.A.R.V.I.S."""
bot.add_cog(ErrorHandlerCog(bot)) bot.add_cog(ErrorHandlerCog(bot))

View file

@ -1,13 +1,16 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. GitLab Cog."""
from datetime import datetime
from datetime import timedelta
import gitlab import gitlab
from ButtonPaginator import Paginator from ButtonPaginator import Paginator
from discord import Embed
from discord.ext import commands from discord.ext import commands
from discord.ext.tasks import loop from discord_slash import cog_ext
from discord.utils import find from discord_slash import SlashContext
from discord_slash import SlashContext, cog_ext
from discord_slash.model import ButtonStyle from discord_slash.model import ButtonStyle
from discord_slash.utils.manage_commands import create_choice, create_option from discord_slash.utils.manage_commands import create_choice
from discord_slash.utils.manage_commands import create_option
from jarvis.config import get_config from jarvis.config import get_config
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -18,12 +21,12 @@ guild_ids = [862402786116763668]
class GitlabCog(CacheCog): class GitlabCog(CacheCog):
def __init__(self, bot): """J.A.R.V.I.S. GitLab Cog."""
def __init__(self, bot: commands.Bot):
super().__init__(bot) super().__init__(bot)
config = get_config() config = get_config()
self._gitlab = gitlab.Gitlab( self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token)
"https://git.zevaryx.com", private_token=config.gitlab_token
)
# J.A.R.V.I.S. GitLab ID is 29 # J.A.R.V.I.S. GitLab ID is 29
self.project = self._gitlab.projects.get(29) self.project = self._gitlab.projects.get(29)
@ -32,13 +35,9 @@ class GitlabCog(CacheCog):
name="issue", name="issue",
description="Get an issue from GitLab", description="Get an issue from GitLab",
guild_ids=guild_ids, guild_ids=guild_ids,
options=[ options=[create_option(name="id", description="Issue ID", option_type=4, required=True)],
create_option(
name="id", description="Issue ID", option_type=4, required=True
)
],
) )
async def _issue(self, ctx: SlashContext, id: int): async def _issue(self, ctx: SlashContext, id: int) -> None:
try: try:
issue = self.project.issues.get(int(id)) issue = self.project.issues.get(int(id))
except gitlab.exceptions.GitlabGetError: except gitlab.exceptions.GitlabGetError:
@ -50,9 +49,7 @@ class GitlabCog(CacheCog):
else: else:
assignee = "None" assignee = "None"
created_at = datetime.strptime( created_at = datetime.strptime(issue.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
issue.created_at, "%Y-%m-%dT%H:%M:%S.%fZ"
).strftime("%Y-%m-%d %H:%M:%S UTC")
labels = issue.labels labels = issue.labels
if labels: if labels:
@ -61,25 +58,20 @@ class GitlabCog(CacheCog):
labels = "None" labels = "None"
fields = [ fields = [
Field( Field(name="State", value=issue.state[0].upper() + issue.state[1:]),
name="State", value=issue.state[0].upper() + issue.state[1:]
),
Field(name="Assignee", value=assignee), Field(name="Assignee", value=assignee),
Field(name="Labels", value=labels), Field(name="Labels", value=labels),
] ]
color = self.project.labels.get(issue.labels[0]).color color = self.project.labels.get(issue.labels[0]).color
fields.append(Field(name="Created At", value=created_at)) fields.append(Field(name="Created At", value=created_at))
if issue.state == "closed": if issue.state == "closed":
closed_at = datetime.strptime( closed_at = datetime.strptime(issue.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
issue.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ"
).strftime("%Y-%m-%d %H:%M:%S UTC")
fields.append(Field(name="Closed At", value=closed_at)) fields.append(Field(name="Closed At", value=closed_at))
if issue.milestone: if issue.milestone:
fields.append( fields.append(
Field( Field(
name="Milestone", name="Milestone",
value=f"[{issue.milestone['title']}]" value=f"[{issue.milestone['title']}]({issue.milestone['web_url']})",
+ f"({issue.milestone['web_url']})",
inline=False, inline=False,
) )
) )
@ -97,10 +89,7 @@ class GitlabCog(CacheCog):
icon_url=issue.author["avatar_url"], icon_url=issue.author["avatar_url"],
url=issue.author["web_url"], url=issue.author["web_url"],
) )
embed.set_thumbnail( embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
url="https://about.gitlab.com/images"
+ "/press/logo/png/gitlab-icon-rgb.png"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -117,16 +106,14 @@ class GitlabCog(CacheCog):
) )
], ],
) )
async def _milestone(self, ctx: SlashContext, id: int): async def _milestone(self, ctx: SlashContext, id: int) -> None:
try: try:
milestone = self.project.milestones.get(int(id)) milestone = self.project.milestones.get(int(id))
except gitlab.exceptions.GitlabGetError: except gitlab.exceptions.GitlabGetError:
await ctx.send("Milestone does not exist.", hidden=True) await ctx.send("Milestone does not exist.", hidden=True)
return return
created_at = datetime.strptime( created_at = datetime.strptime(milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ"
).strftime("%Y-%m-%d %H:%M:%S UTC")
fields = [ fields = [
Field( Field(
@ -139,9 +126,9 @@ class GitlabCog(CacheCog):
] ]
if milestone.updated_at: if milestone.updated_at:
updated_at = datetime.strptime( updated_at = datetime.strptime(milestone.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
milestone.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ" "%Y-%m-%d %H:%M:%S UTC"
).strftime("%Y-%m-%d %H:%M:%S UTC") )
fields.append(Field(name="Updated At", value=updated_at)) fields.append(Field(name="Updated At", value=updated_at))
if len(milestone.title) > 200: if len(milestone.title) > 200:
@ -157,13 +144,9 @@ class GitlabCog(CacheCog):
embed.set_author( embed.set_author(
name="J.A.R.V.I.S.", name="J.A.R.V.I.S.",
url="https://git.zevaryx.com/jarvis", url="https://git.zevaryx.com/jarvis",
icon_url="https://git.zevaryx.com/uploads/-" icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png",
+ "/system/user/avatar/11/avatar.png",
)
embed.set_thumbnail(
url="https://about.gitlab.com/images"
+ "/press/logo/png/gitlab-icon-rgb.png"
) )
embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
await ctx.send(embed=embed) await ctx.send(embed=embed)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -180,7 +163,7 @@ class GitlabCog(CacheCog):
) )
], ],
) )
async def _mergerequest(self, ctx: SlashContext, id: int): async def _mergerequest(self, ctx: SlashContext, id: int) -> None:
try: try:
mr = self.project.mergerequests.get(int(id)) mr = self.project.mergerequests.get(int(id))
except gitlab.exceptions.GitlabGetError: except gitlab.exceptions.GitlabGetError:
@ -192,9 +175,7 @@ class GitlabCog(CacheCog):
else: else:
assignee = "None" assignee = "None"
created_at = datetime.strptime( created_at = datetime.strptime(mr.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
mr.created_at, "%Y-%m-%dT%H:%M:%S.%fZ"
).strftime("%Y-%m-%d %H:%M:%S UTC")
labels = mr.labels labels = mr.labels
if labels: if labels:
@ -213,21 +194,16 @@ class GitlabCog(CacheCog):
color = "#00FFEE" color = "#00FFEE"
fields.append(Field(name="Created At", value=created_at)) fields.append(Field(name="Created At", value=created_at))
if mr.state == "merged": if mr.state == "merged":
merged_at = datetime.strptime( merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ"
).strftime("%Y-%m-%d %H:%M:%S UTC")
fields.append(Field(name="Merged At", value=merged_at)) fields.append(Field(name="Merged At", value=merged_at))
elif mr.state == "closed": elif mr.state == "closed":
closed_at = datetime.strptime( closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ"
).strftime("%Y-%m-%d %H:%M:%S UTC")
fields.append(Field(name="Closed At", value=closed_at)) fields.append(Field(name="Closed At", value=closed_at))
if mr.milestone: if mr.milestone:
fields.append( fields.append(
Field( Field(
name="Milestone", name="Milestone",
value=f"[{mr.milestone['title']}]" value=f"[{mr.milestone['title']}]({mr.milestone['web_url']})",
+ f"({mr.milestone['web_url']})",
inline=False, inline=False,
) )
) )
@ -245,13 +221,11 @@ class GitlabCog(CacheCog):
icon_url=mr.author["avatar_url"], icon_url=mr.author["avatar_url"],
url=mr.author["web_url"], url=mr.author["web_url"],
) )
embed.set_thumbnail( embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
url="https://about.gitlab.com/images"
+ "/press/logo/png/gitlab-icon-rgb.png"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
def build_embed_page(self, api_list: list, t_state: str, name: str): def build_embed_page(self, api_list: list, t_state: str, name: str) -> Embed:
"""Build an embed page for the paginator."""
title = "" title = ""
if t_state: if t_state:
title = f"{t_state} " title = f"{t_state} "
@ -261,8 +235,7 @@ class GitlabCog(CacheCog):
fields.append( fields.append(
Field( Field(
name=f"[#{item.iid}] {item.title}", name=f"[#{item.iid}] {item.title}",
value=item.description value=item.description + f"\n\n[View this {name}]({item.web_url})",
+ f"\n\n[View this {name}]({item.web_url})",
inline=False, inline=False,
) )
) )
@ -271,19 +244,14 @@ class GitlabCog(CacheCog):
title=title, title=title,
description="", description="",
fields=fields, fields=fields,
url="https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./" url=f"https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./{name.replace(' ', '_')}s",
+ f"{name.replace(' ', '_')}s",
) )
embed.set_author( embed.set_author(
name="J.A.R.V.I.S.", name="J.A.R.V.I.S.",
url="https://git.zevaryx.com/jarvis", url="https://git.zevaryx.com/jarvis",
icon_url="https://git.zevaryx.com/uploads/-" icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png",
+ "/system/user/avatar/11/avatar.png",
)
embed.set_thumbnail(
url="https://about.gitlab.com/images"
+ "/press/logo/png/gitlab-icon-rgb.png"
) )
embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
return embed return embed
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -305,13 +273,12 @@ class GitlabCog(CacheCog):
) )
], ],
) )
async def _issues(self, ctx: SlashContext, state: str = "opened"): async def _issues(self, ctx: SlashContext, state: str = "opened") -> None:
exists = self.check_cache(ctx, state=state) exists = self.check_cache(ctx, state=state)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -347,11 +314,7 @@ class GitlabCog(CacheCog):
pages = [] pages = []
t_state = t_state[0].upper() + t_state[1:] t_state = t_state[0].upper() + t_state[1:]
for i in range(0, len(issues), 5): for i in range(0, len(issues), 5):
pages.append( pages.append(self.build_embed_page(issues[i : i + 5], t_state=t_state, name="issue")) # noqa: E203
self.build_embed_page(
issues[i : i + 5], t_state=t_state, name="issue"
)
)
paginator = Paginator( paginator = Paginator(
bot=self.bot, bot=self.bot,
@ -397,13 +360,12 @@ class GitlabCog(CacheCog):
) )
], ],
) )
async def _mergerequests(self, ctx: SlashContext, state: str = "opened"): async def _mergerequests(self, ctx: SlashContext, state: str = "opened") -> None:
exists = self.check_cache(ctx, state=state) exists = self.check_cache(ctx, state=state)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -439,11 +401,7 @@ class GitlabCog(CacheCog):
pages = [] pages = []
t_state = t_state[0].upper() + t_state[1:] t_state = t_state[0].upper() + t_state[1:]
for i in range(0, len(merges), 5): for i in range(0, len(merges), 5):
pages.append( pages.append(self.build_embed_page(merges[i : i + 5], t_state=t_state, name="merge request")) # noqa: E203
self.build_embed_page(
merges[i : i + 5], t_state=t_state, name="merge request"
)
)
paginator = Paginator( paginator = Paginator(
bot=self.bot, bot=self.bot,
@ -475,13 +433,12 @@ class GitlabCog(CacheCog):
description="Get open issues from GitLab", description="Get open issues from GitLab",
guild_ids=guild_ids, guild_ids=guild_ids,
) )
async def _milestones(self, ctx: SlashContext): async def _milestones(self, ctx: SlashContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " f"Please use existing interaction: {exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -509,11 +466,7 @@ class GitlabCog(CacheCog):
pages = [] pages = []
for i in range(0, len(milestones), 5): for i in range(0, len(milestones), 5):
pages.append( pages.append(self.build_embed_page(milestones[i : i + 5], t_state=None, name="milestone")) # noqa: E203
self.build_embed_page(
milestones[i : i + 5], t_state=None, name="milestone"
)
)
paginator = Paginator( paginator = Paginator(
bot=self.bot, bot=self.bot,
@ -539,6 +492,7 @@ class GitlabCog(CacheCog):
await paginator.start() await paginator.start()
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists."""
if get_config().gitlab_token: if get_config().gitlab_token:
bot.add_cog(GitlabCog(bot)) bot.add_cog(GitlabCog(bot))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. image processing cog."""
import re import re
from io import BytesIO from io import BytesIO
@ -7,7 +8,9 @@ import numpy as np
from discord import File from discord import File
from discord.ext import commands from discord.ext import commands
from jarvis.utils import build_embed, convert_bytesize, unconvert_bytesize from jarvis.utils import build_embed
from jarvis.utils import convert_bytesize
from jarvis.utils import unconvert_bytesize
from jarvis.utils.field import Field from jarvis.utils.field import Field
@ -18,38 +21,31 @@ class ImageCog(commands.Cog):
May be categorized under util later May be categorized under util later
""" """
def __init__(self, bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self._session = aiohttp.ClientSession() self._session = aiohttp.ClientSession()
self.tgt_match = re.compile( self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE)
r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE
)
async def _resize(self, ctx, target: str, url: str = None): def __del__(self):
self._session.close()
async def _resize(self, ctx: commands.Context, target: str, url: str = None) -> None:
if not target: if not target:
await ctx.send("Missing target size, i.e. 200KB.") await ctx.send("Missing target size, i.e. 200KB.")
return return
tgt = self.tgt_match.match(target) tgt = self.tgt_match.match(target)
if not tgt: if not tgt:
await ctx.send( await ctx.send(f"Invalid target format ({target}). Expected format like 200KB")
"Invalid target format ({}).".format(target)
+ " Expected format like 200KB"
)
return return
tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1]) tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1])
if tgt_size > unconvert_bytesize(8, "MB"): if tgt_size > unconvert_bytesize(8, "MB"):
await ctx.send( await ctx.send("Target too large to send. Please make target < 8MB")
"Target too large to send. Please make target < 8MB"
)
return return
file = None file = None
filename = None filename = None
if ( if ctx.message.attachments is not None and len(ctx.message.attachments) > 0:
ctx.message.attachments is not None
and len(ctx.message.attachments) > 0
):
file = await ctx.message.attachments[0].read() file = await ctx.message.attachments[0].read()
filename = ctx.message.attachments[0].filename filename = ctx.message.attachments[0].filename
elif url is not None: elif url is not None:
@ -69,9 +65,7 @@ class ImageCog(commands.Cog):
accuracy = 0.0 accuracy = 0.0
# TODO: Optimize to not run multiple times # TODO: Optimize to not run multiple times
while len(file) > tgt_size or ( while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < 0.65):
len(file) <= tgt_size and accuracy < 0.65
):
old_file = file old_file = file
buffer = np.frombuffer(file, dtype=np.uint8) buffer = np.frombuffer(file, dtype=np.uint8)
@ -105,9 +99,10 @@ class ImageCog(commands.Cog):
@commands.command(name="resize", help="Resize an image") @commands.command(name="resize", help="Resize an image")
@commands.cooldown(1, 60, commands.BucketType.user) @commands.cooldown(1, 60, commands.BucketType.user)
async def _resize_pref(self, ctx, target: str, url: str = None): async def _resize_pref(self, ctx: commands.Context, target: str, url: str = None) -> None:
await self._resize(ctx, target, url) await self._resize(ctx, target, url)
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add ImageCog to J.A.R.V.I.S."""
bot.add_cog(ImageCog(bot)) bot.add_cog(ImageCog(bot))

View file

@ -1,13 +1,14 @@
"""J.A.R.V.I.S. Jokes module."""
import html import html
import re import re
import traceback import traceback
from datetime import datetime from datetime import datetime
from random import randint from secrets import randint
from discord.ext import commands from discord.ext import commands
from discord_slash import cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
import jarvis
from jarvis.db.models import Joke from jarvis.db.models import Joke
from jarvis.utils import build_embed from jarvis.utils import build_embed
from jarvis.utils.field import Field from jarvis.utils.field import Field
@ -20,11 +21,17 @@ class JokeCog(commands.Cog):
May adapt over time to create jokes using machine learning May adapt over time to create jokes using machine learning
""" """
def __init__(self, bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
# TODO: Make this a command group with subcommands # TODO: Make this a command group with subcommands
async def _joke(self, ctx, id: str = None): @cog_ext.cog_slash(
name="joke",
description="Hear a joke",
)
@commands.cooldown(1, 10, commands.BucketType.channel)
async def _joke(self, ctx: SlashContext, id: str = None) -> None:
"""Get a joke from the database."""
try: try:
if randint(1, 100_000) == 5779 and id is None: if randint(1, 100_000) == 5779 and id is None:
await ctx.send(f"<@{ctx.message.author.id}>") await ctx.send(f"<@{ctx.message.author.id}>")
@ -44,20 +51,14 @@ class JokeCog(commands.Cog):
result = Joke.objects().aggregate(pipeline).next() result = Joke.objects().aggregate(pipeline).next()
if result is None: if result is None:
await ctx.send( await ctx.send("Humor module failed. Please try again later.", hidden=True)
"Humor module failed. Please try again later.", hidden=True
)
return return
emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"]) emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"])
for match in emotes: for match in emotes:
result["body"] = result["body"].replace( result["body"] = result["body"].replace(match, html.unescape(match))
match, html.unescape(match)
)
emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["title"]) emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["title"])
for match in emotes: for match in emotes:
result["title"] = result["title"].replace( result["title"] = result["title"].replace(match, html.unescape(match))
match, html.unescape(match)
)
body_chunks = [] body_chunks = []
body = "" body = ""
@ -105,19 +106,10 @@ class JokeCog(commands.Cog):
) )
await ctx.send(embed=embed) await ctx.send(embed=embed)
except Exception: except Exception:
await ctx.send( await ctx.send("Encountered error:\n```\n" + traceback.format_exc() + "\n```")
"Encountered error:\n```\n" + traceback.format_exc() + "\n```"
)
# await ctx.send(f"**{result['title']}**\n\n{result['body']}") # await ctx.send(f"**{result['title']}**\n\n{result['body']}")
@cog_ext.cog_slash(
name="joke",
description="Hear a joke",
)
@commands.cooldown(1, 10, commands.BucketType.channel)
async def _joke_slash(self, ctx, id: str = None):
await self._joke(ctx, id)
def setup(bot: commands.Bot) -> None:
def setup(bot): """Add JokeCog to J.A.R.V.I.S."""
bot.add_cog(JokeCog(bot)) bot.add_cog(JokeCog(bot))

View file

@ -1,7 +1,13 @@
from jarvis.cogs.modlog import command, member, message """J.A.R.V.I.S. Modlog Cogs."""
from discord.ext.commands import Bot
from jarvis.cogs.modlog import command
from jarvis.cogs.modlog import member
from jarvis.cogs.modlog import message
def setup(bot): def setup(bot: Bot) -> None:
"""Add modlog cogs to J.A.R.V.I.S."""
bot.add_cog(command.ModlogCommandCog(bot)) bot.add_cog(command.ModlogCommandCog(bot))
bot.add_cog(member.ModlogMemberCog(bot)) bot.add_cog(member.ModlogMemberCog(bot))
bot.add_cog(message.ModlogMessageCog(bot)) bot.add_cog(message.ModlogMessageCog(bot))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. ModlogCommandCog."""
from discord import DMChannel from discord import DMChannel
from discord.ext import commands from discord.ext import commands
from discord_slash import SlashContext from discord_slash import SlashContext
@ -8,24 +9,23 @@ from jarvis.utils.field import Field
class ModlogCommandCog(commands.Cog): class ModlogCommandCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. ModlogCommandCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@commands.Cog.listener() @commands.Cog.listener()
async def on_slash_command(self, ctx: SlashContext): async def on_slash_command(self, ctx: SlashContext) -> None:
"""Process on_slash_command events."""
if not isinstance(ctx.channel, DMChannel) and ctx.name not in ["pw"]: if not isinstance(ctx.channel, DMChannel) and ctx.name not in ["pw"]:
modlog = Setting.objects( modlog = Setting.objects(guild=ctx.guild.id, setting="modlog").first()
guild=ctx.guild.id, setting="modlog"
).first()
if modlog: if modlog:
channel = ctx.guild.get_channel(modlog.value) channel = ctx.guild.get_channel(modlog.value)
fields = [ fields = [
Field("Command", ctx.name), Field("Command", ctx.name),
] ]
if ctx.kwargs: if ctx.kwargs:
kwargs_string = " ".join( kwargs_string = " ".join(f"{k}: {str(ctx.kwargs[k])}" for k in ctx.kwargs)
f"{k}: {str(ctx.kwargs[k])}" for k in ctx.kwargs
)
fields.append( fields.append(
Field( Field(
"Keyword Args", "Keyword Args",
@ -45,8 +45,5 @@ class ModlogCommandCog(commands.Cog):
name=ctx.author.name, name=ctx.author.name,
icon_url=ctx.author.avatar_url, icon_url=ctx.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
text=f"{ctx.author.name}#{ctx.author.discriminator}"
+ f" | {ctx.author.id}"
)
await channel.send(embed=embed) await channel.send(embed=embed)

View file

@ -1,24 +1,34 @@
"""J.A.R.V.I.S. ModlogMemberCog."""
import asyncio import asyncio
from datetime import datetime, timedelta from datetime import datetime
from datetime import timedelta
import discord import discord
from discord.ext import commands from discord.ext import commands
from discord.utils import find from discord.utils import find
from jarvis.cogs.modlog.utils import get_latest_log, modlog_embed from jarvis.cogs.modlog.utils import get_latest_log
from jarvis.cogs.modlog.utils import modlog_embed
from jarvis.config import get_config from jarvis.config import get_config
from jarvis.db.models import Ban, Kick, Mute, Setting, Unban from jarvis.db.models import Ban
from jarvis.db.models import Kick
from jarvis.db.models import Mute
from jarvis.db.models import Setting
from jarvis.db.models import Unban
from jarvis.utils import build_embed from jarvis.utils import build_embed
from jarvis.utils.field import Field from jarvis.utils.field import Field
class ModlogMemberCog(commands.Cog): class ModlogMemberCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. ModlogMemberCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.cache = [] self.cache = []
@commands.Cog.listener() @commands.Cog.listener()
async def on_member_ban(self, guild: discord.Guild, user: discord.User): async def on_member_ban(self, guild: discord.Guild, user: discord.User) -> None:
"""Process on_member_ban events."""
modlog = Setting.objects(guild=guild.id, setting="modlog").first() modlog = Setting.objects(guild=guild.id, setting="modlog").first()
if modlog: if modlog:
channel = guild.get_channel(modlog.value) channel = guild.get_channel(modlog.value)
@ -55,7 +65,8 @@ class ModlogMemberCog(commands.Cog):
await channel.send(embed=embed) await channel.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_member_unban(self, guild: discord.Guild, user: discord.User): async def on_member_unban(self, guild: discord.Guild, user: discord.User) -> None:
"""Process on_member_unban events."""
modlog = Setting.objects(guild=guild.id, setting="modlog").first() modlog = Setting.objects(guild=guild.id, setting="modlog").first()
if modlog: if modlog:
channel = guild.get_channel(modlog.value) channel = guild.get_channel(modlog.value)
@ -90,7 +101,8 @@ class ModlogMemberCog(commands.Cog):
await channel.send(embed=embed) await channel.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_member_remove(self, user: discord.Member): async def on_member_remove(self, user: discord.Member) -> None:
"""Process on_member_remove events."""
modlog = Setting.objects(guild=user.guild.id, setting="modlog").first() modlog = Setting.objects(guild=user.guild.id, setting="modlog").first()
if modlog: if modlog:
channel = user.guild.get_channel(modlog.value) channel = user.guild.get_channel(modlog.value)
@ -134,7 +146,8 @@ class ModlogMemberCog(commands.Cog):
await channel.send(embed=embed) await channel.send(embed=embed)
async def process_mute(self, before, after) -> discord.Embed: async def process_mute(self, before: discord.Member, after: discord.Member) -> discord.Embed:
"""Process mute event."""
await asyncio.sleep(0.5) # Need to wait for audit log await asyncio.sleep(0.5) # Need to wait for audit log
auditlog = await before.guild.audit_logs( auditlog = await before.guild.audit_logs(
limit=50, limit=50,
@ -165,7 +178,8 @@ class ModlogMemberCog(commands.Cog):
desc=f"{before.mention} was muted", desc=f"{before.mention} was muted",
) )
async def process_unmute(self, before, after) -> discord.Embed: async def process_unmute(self, before: discord.Member, after: discord.Member) -> discord.Embed:
"""Process unmute event."""
await asyncio.sleep(0.5) # Need to wait for audit log await asyncio.sleep(0.5) # Need to wait for audit log
auditlog = await before.guild.audit_logs( auditlog = await before.guild.audit_logs(
limit=50, limit=50,
@ -196,7 +210,8 @@ class ModlogMemberCog(commands.Cog):
desc=f"{before.mention} was muted", desc=f"{before.mention} was muted",
) )
async def process_verify(self, before, after) -> discord.Embed: async def process_verify(self, before: discord.Member, after: discord.Member) -> discord.Embed:
"""Process verification event."""
await asyncio.sleep(0.5) # Need to wait for audit log await asyncio.sleep(0.5) # Need to wait for audit log
auditlog = await before.guild.audit_logs( auditlog = await before.guild.audit_logs(
limit=50, limit=50,
@ -214,7 +229,8 @@ class ModlogMemberCog(commands.Cog):
desc=f"{before.mention} was verified", desc=f"{before.mention} was verified",
) )
async def process_rolechange(self, before, after) -> discord.Embed: async def process_rolechange(self, before: discord.Member, after: discord.Member) -> discord.Embed:
"""Process rolechange event."""
await asyncio.sleep(0.5) # Need to wait for audit log await asyncio.sleep(0.5) # Need to wait for audit log
auditlog = await before.guild.audit_logs( auditlog = await before.guild.audit_logs(
limit=50, limit=50,
@ -243,50 +259,34 @@ class ModlogMemberCog(commands.Cog):
) )
@commands.Cog.listener() @commands.Cog.listener()
async def on_member_update( async def on_member_update(self, before: discord.Member, after: discord.Member) -> None:
self, before: discord.Member, after: discord.Member """Process on_member_update events.
):
Caches events due to double-send bug
"""
h = hash(hash(before) * hash(after)) h = hash(hash(before) * hash(after))
if h not in self.cache: if h not in self.cache:
self.cache.append(h) self.cache.append(h)
else: else:
return return
modlog = Setting.objects( modlog = Setting.objects(guild=before.guild.id, setting="modlog").first()
guild=before.guild.id, setting="modlog"
).first()
if modlog: if modlog:
channel = after.guild.get_channel(modlog.value) channel = after.guild.get_channel(modlog.value)
await asyncio.sleep(0.5) # Need to wait for audit log await asyncio.sleep(0.5) # Need to wait for audit log
embed = None embed = None
mute = Setting.objects( mute = Setting.objects(guild=before.guild.id, setting="mute").first()
guild=before.guild.id, setting="mute" verified = Setting.objects(guild=before.guild.id, setting="verified").first()
).first()
verified = Setting.objects(
guild=before.guild.id, setting="verified"
).first()
mute_role = None mute_role = None
verified_role = None verified_role = None
if mute: if mute:
mute_role = before.guild.get_role(mute.value) mute_role = before.guild.get_role(mute.value)
if verified: if verified:
verified_role = before.guild.get_role(verified.value) verified_role = before.guild.get_role(verified.value)
if ( if mute and mute_role in after.roles and mute_role not in before.roles:
mute
and mute_role in after.roles
and mute_role not in before.roles
):
embed = await self.process_mute(before, after) embed = await self.process_mute(before, after)
elif ( elif mute and mute_role in before.roles and mute_role not in after.roles:
mute
and mute_role in before.roles
and mute_role not in after.roles
):
embed = await self.process_unmute(before, after) embed = await self.process_unmute(before, after)
elif ( elif verified and verified_role not in before.roles and verified_role in after.roles:
verified
and verified_role not in before.roles
and verified_role in after.roles
):
embed = await self.process_verify(before, after) embed = await self.process_verify(before, after)
elif before.nick != after.nick: elif before.nick != after.nick:
auditlog = await before.guild.audit_logs( auditlog = await before.guild.audit_logs(
@ -301,21 +301,18 @@ class ModlogMemberCog(commands.Cog):
fields = [ fields = [
Field( Field(
name="Before", name="Before",
value=f"{bname} ({before.name}" value=f"{bname} ({before.name}#{before.discriminator})",
+ f"#{before.discriminator})",
), ),
Field( Field(
name="After", name="After",
value=f"{aname} ({after.name}" value=f"{aname} ({after.name}#{after.discriminator})",
+ f"#{after.discriminator})",
), ),
] ]
if log.user.id != before.id: if log.user.id != before.id:
fields.append( fields.append(
Field( Field(
name="Moderator", name="Moderator",
value=f"{log.user.mention} ({log.user.name}" value=f"{log.user.mention} ({log.user.name}#{log.user.discriminator})",
+ f"#{log.user.discriminator})",
) )
) )
if log.reason: if log.reason:
@ -333,9 +330,7 @@ class ModlogMemberCog(commands.Cog):
name=f"{after.name}", name=f"{after.name}",
icon_url=after.avatar_url, icon_url=after.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{after.name}#{after.discriminator} | {after.id}")
text=f"{after.name}#{after.discriminator} | {after.id}"
)
elif len(before.roles) != len(after.roles): elif len(before.roles) != len(after.roles):
# TODO: User got a new role # TODO: User got a new role
embed = await self.process_rolechange(before, after) embed = await self.process_rolechange(before, after)

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. ModlogMessageCog."""
import discord import discord
from discord.ext import commands from discord.ext import commands
@ -7,17 +8,16 @@ from jarvis.utils.field import Field
class ModlogMessageCog(commands.Cog): class ModlogMessageCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. ModlogMessageCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@commands.Cog.listener() @commands.Cog.listener()
async def on_message_edit( async def on_message_edit(self, before: discord.Message, after: discord.Message) -> None:
self, before: discord.Message, after: discord.Message """Process on_message_edit events."""
):
if not before.author.bot: if not before.author.bot:
modlog = Setting.objects( modlog = Setting.objects(guild=after.guild.id, setting="modlog").first()
guild=after.guild.id, setting="modlog"
).first()
if modlog: if modlog:
if before.content == after.content or before.content is None: if before.content == after.content or before.content is None:
return return
@ -47,26 +47,18 @@ class ModlogMessageCog(commands.Cog):
icon_url=before.author.avatar_url, icon_url=before.author.avatar_url,
url=after.jump_url, url=after.jump_url,
) )
embed.set_footer( embed.set_footer(text=f"{before.author.name}#{before.author.discriminator} | {before.author.id}")
text=f"{before.author.name}#{before.author.discriminator}"
+ f" | {before.author.id}"
)
await channel.send(embed=embed) await channel.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_message_delete(self, message: discord.Message): async def on_message_delete(self, message: discord.Message) -> None:
modlog = Setting.objects( """Process on_message_delete events."""
guild=message.guild.id, setting="modlog" modlog = Setting.objects(guild=message.guild.id, setting="modlog").first()
).first()
if modlog: if modlog:
fields = [ fields = [Field("Original Message", message.content or "N/A", False)]
Field("Original Message", message.content or "N/A", False)
]
if message.attachments: if message.attachments:
value = "\n".join( value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments])
[f"[{x.filename}]({x.url})" for x in message.attachments]
)
fields.append( fields.append(
Field( Field(
name="Attachments", name="Attachments",
@ -76,9 +68,7 @@ class ModlogMessageCog(commands.Cog):
) )
if message.stickers: if message.stickers:
value = "\n".join( value = "\n".join([f"[{x.name}]({x.image_url})" for x in message.stickers])
[f"[{x.name}]({x.image_url})" for x in message.stickers]
)
fields.append( fields.append(
Field( Field(
name="Stickers", name="Stickers",
@ -110,8 +100,5 @@ class ModlogMessageCog(commands.Cog):
icon_url=message.author.avatar_url, icon_url=message.author.avatar_url,
url=message.jump_url, url=message.jump_url,
) )
embed.set_footer( embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
text=f"{message.author.name}#{message.author.discriminator}"
+ f" | {message.author.id}"
)
await channel.send(embed=embed) await channel.send(embed=embed)

View file

@ -1,6 +1,11 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. Modlog Cog Utilities."""
from datetime import datetime
from datetime import timedelta
from typing import List
import discord import discord
from discord import AuditLogEntry
from discord import Member
from discord.utils import find from discord.utils import find
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -14,11 +19,11 @@ def modlog_embed(
title: str, title: str,
desc: str, desc: str,
) -> discord.Embed: ) -> discord.Embed:
"""Get modlog embed."""
fields = [ fields = [
Field( Field(
name="Moderator", name="Moderator",
value=f"{admin.mention} ({admin.name}" value=f"{admin.mention} ({admin.name}#{admin.discriminator})",
+ f"#{admin.discriminator})",
), ),
] ]
if log.reason: if log.reason:
@ -34,13 +39,12 @@ def modlog_embed(
name=f"{member.name}", name=f"{member.name}",
icon_url=member.avatar_url, icon_url=member.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}")
text=f"{member.name}#{member.discriminator} | {member.id}"
)
return embed return embed
def get_latest_log(auditlog, target): def get_latest_log(auditlog: List[AuditLogEntry], target: Member) -> AuditLogEntry:
"""Filter AuditLog to get latest entry."""
before = datetime.utcnow() - timedelta(seconds=10) before = datetime.utcnow() - timedelta(seconds=10)
return find( return find(
lambda x: x.target.id == target.id and x.created_at > before, lambda x: x.target.id == target.id and x.created_at > before,

View file

@ -1,11 +1,14 @@
"""J.A.R.V.I.S. Owner Cog."""
import os import os
import sys import sys
import traceback import traceback
from inspect import getsource from inspect import getsource
from time import time from time import time
from typing import Any
import discord import discord
from discord import DMChannel, User from discord import DMChannel
from discord import User
from discord.ext import commands from discord.ext import commands
import jarvis import jarvis
@ -17,23 +20,20 @@ from jarvis.utils.permissions import user_is_bot_admin
class OwnerCog(commands.Cog): class OwnerCog(commands.Cog):
""" """
J.A.R.V.I.S. management cog J.A.R.V.I.S. management cog.
Used by admins to control core J.A.R.V.I.S. systems Used by admins to control core J.A.R.V.I.S. systems
""" """
def __init__(self, bot): def __init__(self, bot: commands.Cog):
self.bot = bot self.bot = bot
self.admins = Config.objects(key="admins").first() self.admins = Config.objects(key="admins").first()
@commands.command(name="load", hidden=True) @commands.command(name="load", hidden=True)
@user_is_bot_admin() @user_is_bot_admin()
async def _load_cog(self, ctx, *, cog: str): async def _load_cog(self, ctx: commands.Context, *, cog: str) -> None:
info = await self.bot.application_info() info = await self.bot.application_info()
if ( if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
ctx.message.author == info.owner
or ctx.message.author.id in self.admins.value
):
try: try:
if "jarvis.cogs." not in cog: if "jarvis.cogs." not in cog:
cog = "jarvis.cogs." + cog.split(".")[-1] cog = "jarvis.cogs." + cog.split(".")[-1]
@ -41,9 +41,7 @@ class OwnerCog(commands.Cog):
except commands.errors.ExtensionAlreadyLoaded: except commands.errors.ExtensionAlreadyLoaded:
await ctx.send(f"Cog `{cog}` already loaded") await ctx.send(f"Cog `{cog}` already loaded")
except Exception as e: except Exception as e:
await ctx.send( await ctx.send(f"Failed to load new cog `{cog}`: {type(e).name} - {e}")
f"Failed to load new cog `{cog}`: {type(e).name} - {e}"
)
else: else:
await ctx.send(f"Successfully loaded new cog `{cog}`") await ctx.send(f"Successfully loaded new cog `{cog}`")
else: else:
@ -51,15 +49,12 @@ class OwnerCog(commands.Cog):
@commands.command(name="unload", hidden=True) @commands.command(name="unload", hidden=True)
@user_is_bot_admin() @user_is_bot_admin()
async def _unload_cog(self, ctx, *, cog: str): async def _unload_cog(self, ctx: commands.Context, *, cog: str) -> None:
if cog in ["jarvis.cogs.owner", "owner"]: if cog in ["jarvis.cogs.owner", "owner"]:
await ctx.send("Cannot unload `owner` cog") await ctx.send("Cannot unload `owner` cog")
return return
info = await self.bot.application_info() info = await self.bot.application_info()
if ( if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
ctx.message.author == info.owner
or ctx.message.author.id in self.admins.value
):
try: try:
if "jarvis.cogs." not in cog: if "jarvis.cogs." not in cog:
cog = "jarvis.cogs." + cog.split(".")[-1] cog = "jarvis.cogs." + cog.split(".")[-1]
@ -67,9 +62,7 @@ class OwnerCog(commands.Cog):
except commands.errors.ExtensionNotLoaded: except commands.errors.ExtensionNotLoaded:
await ctx.send(f"Cog `{cog}` not loaded") await ctx.send(f"Cog `{cog}` not loaded")
except Exception as e: except Exception as e:
await ctx.send( await ctx.send(f"Failed to unload cog `{cog}` {type(e).__name__} - {e}")
f"Failed to unload cog `{cog}` {type(e).__name__} - {e}"
)
else: else:
await ctx.send(f"Successfully unloaded cog `{cog}`") await ctx.send(f"Successfully unloaded cog `{cog}`")
else: else:
@ -77,15 +70,12 @@ class OwnerCog(commands.Cog):
@commands.command(name="reload", hidden=True) @commands.command(name="reload", hidden=True)
@user_is_bot_admin() @user_is_bot_admin()
async def _cog_reload(self, ctx, *, cog: str): async def _cog_reload(self, ctx: commands.Context, *, cog: str) -> None:
if cog in ["jarvis.cogs.owner", "owner"]: if cog in ["jarvis.cogs.owner", "owner"]:
await ctx.send("Cannot reload `owner` cog") await ctx.send("Cannot reload `owner` cog")
return return
info = await self.bot.application_info() info = await self.bot.application_info()
if ( if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
ctx.message.author == info.owner
or ctx.message.author.id in self.admins.value
):
try: try:
if "jarvis.cogs." not in cog: if "jarvis.cogs." not in cog:
cog = "jarvis.cogs." + cog.split(".")[-1] cog = "jarvis.cogs." + cog.split(".")[-1]
@ -95,9 +85,7 @@ class OwnerCog(commands.Cog):
pass pass
self.bot.unload_extension(cog) self.bot.unload_extension(cog)
except Exception as e: except Exception as e:
await ctx.send( await ctx.send(f"Failed to reload cog `{cog}` {type(e).__name__} - {e}")
f"Failed to reload cog `{cog}` {type(e).__name__} - {e}"
)
else: else:
await ctx.send(f"Successfully reloaded cog `{cog}`") await ctx.send(f"Successfully reloaded cog `{cog}`")
else: else:
@ -105,21 +93,15 @@ class OwnerCog(commands.Cog):
@commands.group(name="system", hidden=True, pass_context=True) @commands.group(name="system", hidden=True, pass_context=True)
@user_is_bot_admin() @user_is_bot_admin()
async def _system(self, ctx): async def _system(self, ctx: commands.Context) -> None:
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send( await ctx.send("Usage: `system <subcommand>`\n" + "Subcommands: `restart`, `update`")
"Usage: `system <subcommand>`\n"
+ "Subcommands: `restart`, `update`"
)
@_system.command(name="restart", hidden=True) @_system.command(name="restart", hidden=True)
@user_is_bot_admin() @user_is_bot_admin()
async def _restart(self, ctx): async def _restart(self, ctx: commands.Context) -> None:
info = await self.bot.application_info() info = await self.bot.application_info()
if ( if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
ctx.message.author == info.owner
or ctx.message.author.id in self.admins.value
):
await ctx.send("Restarting core systems...") await ctx.send("Restarting core systems...")
if isinstance(ctx.channel, discord.channel.DMChannel): if isinstance(ctx.channel, discord.channel.DMChannel):
jarvis.restart_ctx = { jarvis.restart_ctx = {
@ -137,12 +119,9 @@ class OwnerCog(commands.Cog):
@_system.command(name="update", hidden=True) @_system.command(name="update", hidden=True)
@user_is_bot_admin() @user_is_bot_admin()
async def _update(self, ctx): async def _update(self, ctx: commands.Context) -> None:
info = await self.bot.application_info() info = await self.bot.application_info()
if ( if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
ctx.message.author == info.owner
or ctx.message.author.id in self.admins.value
):
await ctx.send("Updating core systems...") await ctx.send("Updating core systems...")
status = update() status = update()
if status == 0: if status == 0:
@ -161,43 +140,36 @@ class OwnerCog(commands.Cog):
elif status == 1: elif status == 1:
await ctx.send("Core systems already up to date.") await ctx.send("Core systems already up to date.")
elif status == 2: elif status == 2:
await ctx.send( await ctx.send("Core system update available, but core is dirty.")
"Core system update available, but core is dirty."
)
else: else:
await ctx.send("I'm afraid I can't let you do that") await ctx.send("I'm afraid I can't let you do that")
@_system.command(name="refresh", hidden=True) @_system.command(name="refresh", hidden=True)
@user_is_bot_admin() @user_is_bot_admin()
async def _refresh(self, ctx): async def _refresh(self, ctx: commands.Context) -> None:
reload_config() reload_config()
await ctx.send("System refreshed") await ctx.send("System refreshed")
@commands.group(name="admin", hidden=True, pass_context=True) @commands.group(name="admin", hidden=True, pass_context=True)
@commands.is_owner() @commands.is_owner()
async def _admin(self, ctx): async def _admin(self, ctx: commands.Context) -> None:
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send( await ctx.send("Usage: `admin <subcommand>`\n" + "Subcommands: `add`, `remove`")
"Usage: `admin <subcommand>`\n"
+ "Subcommands: `add`, `remove`"
)
@_admin.command(name="add", hidden=True) @_admin.command(name="add", hidden=True)
@commands.is_owner() @commands.is_owner()
async def _add(self, ctx, user: User): async def _add(self, ctx: commands.Context, user: User) -> None:
if user.id in self.admins.value: if user.id in self.admins.value:
await ctx.send(f"{user.mention} is already an admin.") await ctx.send(f"{user.mention} is already an admin.")
return return
self.admins.value.append(user.id) self.admins.value.append(user.id)
self.admins.save() self.admins.save()
reload_config() reload_config()
await ctx.send( await ctx.send(f"{user.mention} is now an admin. Use this power carefully.")
f"{user.mention} is now an admin. Use this power carefully."
)
@_admin.command(name="remove", hidden=True) @_admin.command(name="remove", hidden=True)
@commands.is_owner() @commands.is_owner()
async def _remove(self, ctx, user: User): async def _remove(self, ctx: commands.Context, user: User) -> None:
if user.id not in self.admins.value: if user.id not in self.admins.value:
await ctx.send(f"{user.mention} is not an admin.") await ctx.send(f"{user.mention} is not an admin.")
return return
@ -206,14 +178,12 @@ class OwnerCog(commands.Cog):
reload_config() reload_config()
await ctx.send(f"{user.mention} is no longer an admin.") await ctx.send(f"{user.mention} is no longer an admin.")
def resolve_variable(self, variable): def resolve_variable(self, variable: Any) -> Any:
"""Resolve a variable from eval."""
if hasattr(variable, "__iter__"): if hasattr(variable, "__iter__"):
var_length = len(list(variable)) var_length = len(list(variable))
if (var_length > 100) and (not isinstance(variable, str)): if (var_length > 100) and (not isinstance(variable, str)):
return ( return f"<a {type(variable).__name__} iterable " + f"with more than 100 values ({var_length})>"
f"<a {type(variable).__name__} iterable "
+ f"with more than 100 values ({var_length})>"
)
elif not var_length: elif not var_length:
return f"<an empty {type(variable).__name__} iterable>" return f"<an empty {type(variable).__name__} iterable>"
@ -222,24 +192,19 @@ class OwnerCog(commands.Cog):
return ( return (
variable variable
if (len(f"{variable}") <= 1000) if (len(f"{variable}") <= 1000)
else f"<a long {type(variable).__name__} object " else f"<a long {type(variable).__name__} object " + f"with the length of {len(f'{variable}'):,}>"
+ f"with the length of {len(f'{variable}'):,}>"
) )
def prepare(self, string): def prepare(self, string: str) -> str:
arr = ( """Prepare string for eval."""
string.strip("```") arr = string.strip("```").replace("py\n", "").replace("python\n", "").split("\n")
.replace("py\n", "")
.replace("python\n", "")
.split("\n")
)
if not arr[::-1][0].replace(" ", "").startswith("return"): if not arr[::-1][0].replace(" ", "").startswith("return"):
arr[len(arr) - 1] = "return " + arr[::-1][0] arr[len(arr) - 1] = "return " + arr[::-1][0]
return "".join(f"\n\t{i}" for i in arr) return "".join(f"\n\t{i}" for i in arr)
@commands.command(pass_context=True, aliases=["eval", "exec", "evaluate"]) @commands.command(pass_context=True, aliases=["eval", "exec", "evaluate"])
@user_is_bot_admin() @user_is_bot_admin()
async def _eval(self, ctx, *, code: str): async def _eval(self, ctx: commands.Context, *, code: str) -> None:
if not isinstance(ctx.message.channel, DMChannel): if not isinstance(ctx.message.channel, DMChannel):
return return
code = self.prepare(code) code = self.prepare(code)
@ -254,13 +219,13 @@ class OwnerCog(commands.Cog):
} }
try: try:
exec( exec( # noqa: S102
f"async def func():{code}", f"async def func():{code}",
globals().update(args), globals().update(args),
locals(), locals(),
) )
a = time() a = time()
response = await eval("func()", globals().update(args), locals()) response = await eval("func()", globals().update(args), locals()) # noqa: S307
if response is None or isinstance(response, discord.Message): if response is None or isinstance(response, discord.Message):
del args, code del args, code
return return
@ -269,8 +234,7 @@ class OwnerCog(commands.Cog):
response = response.replace("`", "") response = response.replace("`", "")
await ctx.send( await ctx.send(
f"```py\n{self.resolve_variable(response)}```" f"```py\n{self.resolve_variable(response)}```\n`{type(response).__name__} | {(time() - a) / 1000} ms`"
+ f"`{type(response).__name__} | {(time() - a) / 1000} ms`"
) )
except Exception: except Exception:
await ctx.send(f"Error occurred:```\n{traceback.format_exc()}```") await ctx.send(f"Error occurred:```\n{traceback.format_exc()}```")
@ -278,5 +242,6 @@ class OwnerCog(commands.Cog):
del args, code del args, code
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add OwnerCog to J.A.R.V.I.S."""
bot.add_cog(OwnerCog(bot)) bot.add_cog(OwnerCog(bot))

View file

@ -1,20 +1,22 @@
"""J.A.R.V.I.S. Remind Me Cog."""
import asyncio import asyncio
import re import re
from datetime import datetime, timedelta from datetime import datetime
from datetime import timedelta
from typing import List
from typing import Optional from typing import Optional
from bson import ObjectId from bson import ObjectId
from discord.ext import commands from discord import Embed
from discord.ext.commands import Bot
from discord.ext.tasks import loop from discord.ext.tasks import loop
from discord.utils import find from discord_slash import cog_ext
from discord_slash import SlashContext, cog_ext from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from discord_slash.utils.manage_components import ( from discord_slash.utils.manage_components import create_actionrow
create_actionrow, from discord_slash.utils.manage_components import create_select
create_select, from discord_slash.utils.manage_components import create_select_option
create_select_option, from discord_slash.utils.manage_components import wait_for_component
wait_for_component,
)
from jarvis.db.models import Reminder from jarvis.db.models import Reminder
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -29,7 +31,9 @@ invites = re.compile(
class RemindmeCog(CacheCog): class RemindmeCog(CacheCog):
def __init__(self, bot): """J.A.R.V.I.S. Remind Me Cog."""
def __init__(self, bot: Bot):
super().__init__(bot) super().__init__(bot)
self._remind.start() self._remind.start()
@ -77,7 +81,7 @@ class RemindmeCog(CacheCog):
days: Optional[int] = 0, days: Optional[int] = 0,
hours: Optional[int] = 0, hours: Optional[int] = 0,
minutes: Optional[int] = 0, minutes: Optional[int] = 0,
): ) -> None:
if len(message) > 100: if len(message) > 100:
await ctx.send("Reminder cannot be > 100 characters.", hidden=True) await ctx.send("Reminder cannot be > 100 characters.", hidden=True)
return return
@ -88,9 +92,7 @@ class RemindmeCog(CacheCog):
) )
return return
elif not valid.fullmatch(message): elif not valid.fullmatch(message):
await ctx.send( await ctx.send("Hey, you should probably make this readable", hidden=True)
"Hey, you should probably make this readable", hidden=True
)
return return
if not any([weeks, days, hours, minutes]): if not any([weeks, days, hours, minutes]):
@ -107,28 +109,22 @@ class RemindmeCog(CacheCog):
return return
elif days and days > 6: elif days and days > 6:
await ctx.send( await ctx.send("Use weeks instead of 7+ days, please.", hidden=True)
"Use weeks instead of 7+ days, please.", hidden=True
)
return return
elif hours and hours > 23: elif hours and hours > 23:
await ctx.send( await ctx.send("Use days instead of 24+ hours, please.", hidden=True)
"Use days instead of 24+ hours, please.", hidden=True
)
return return
elif minutes and minutes > 59: elif minutes and minutes > 59:
await ctx.send( await ctx.send("Use hours instead of 59+ minutes, please.", hidden=True)
"Use hours instead of 59+ minutes, please.", hidden=True
)
return return
reminders = Reminder.objects(user=ctx.author.id, active=True).count() reminders = Reminder.objects(user=ctx.author.id, active=True).count()
if reminders >= 5: if reminders >= 5:
await ctx.send( await ctx.send(
"You already have 5 (or more) active reminders. " "You already have 5 (or more) active reminders. "
+ "Please either remove an old one, or wait for one to pass", "Please either remove an old one, or wait for one to pass",
hidden=True, hidden=True,
) )
return return
@ -170,7 +166,8 @@ class RemindmeCog(CacheCog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
async def get_reminders_embed(self, ctx, reminders): async def get_reminders_embed(self, ctx: SlashContext, reminders: List[Reminder]) -> Embed:
"""Build embed for paginator."""
fields = [] fields = []
for reminder in reminders: for reminder in reminders:
fields.append( fields.append(
@ -183,7 +180,7 @@ class RemindmeCog(CacheCog):
embed = build_embed( embed = build_embed(
title=f"{len(reminders)} Active Reminder(s)", title=f"{len(reminders)} Active Reminder(s)",
description="All active reminders for " + f"{ctx.author.mention}", description=f"All active reminders for {ctx.author.mention}",
fields=fields, fields=fields,
) )
@ -200,13 +197,12 @@ class RemindmeCog(CacheCog):
name="list", name="list",
description="List reminders for a user", description="List reminders for a user",
) )
async def _list(self, ctx: SlashContext): async def _list(self, ctx: SlashContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(hidden=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " f"Please use existing interaction: {exists['paginator']._message.jump_url}",
+ f"{exists['paginator']._message.jump_url}",
hidden=True, hidden=True,
) )
return return
@ -224,7 +220,7 @@ class RemindmeCog(CacheCog):
name="delete", name="delete",
description="Delete a reminder", description="Delete a reminder",
) )
async def _delete(self, ctx: SlashContext): async def _delete(self, ctx: SlashContext) -> None:
reminders = Reminder.objects(user=ctx.author.id, active=True) reminders = Reminder.objects(user=ctx.author.id, active=True)
if not reminders: if not reminders:
await ctx.send("You have no reminders set", hidden=True) await ctx.send("You have no reminders set", hidden=True)
@ -263,18 +259,14 @@ class RemindmeCog(CacheCog):
timeout=60 * 5, timeout=60 * 5,
) )
for to_delete in context.selected_options: for to_delete in context.selected_options:
_ = Reminder.objects( _ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete()
user=ctx.author.id, id=ObjectId(to_delete)
).delete()
for row in components: for row in components:
for component in row["components"]: for component in row["components"]:
component["disabled"] = True component["disabled"] = True
fields = [] fields = []
for reminder in filter( for reminder in filter(lambda x: str(x._id) in context.selected_options, reminders):
lambda x: str(x._id) in context.selected_options, reminders
):
fields.append( fields.append(
Field( Field(
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
@ -306,10 +298,8 @@ class RemindmeCog(CacheCog):
await message.edit(components=components) await message.edit(components=components)
@loop(seconds=15) @loop(seconds=15)
async def _remind(self): async def _remind(self) -> None:
reminders = Reminder.objects( reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))
remind_at__lte=datetime.utcnow() + timedelta(seconds=30)
)
for reminder in reminders: for reminder in reminders:
if reminder.remind_at <= datetime.utcnow(): if reminder.remind_at <= datetime.utcnow():
user = await self.bot.fetch_user(reminder.user) user = await self.bot.fetch_user(reminder.user)
@ -328,16 +318,15 @@ class RemindmeCog(CacheCog):
embed.set_thumbnail(url=user.avatar_url) embed.set_thumbnail(url=user.avatar_url)
try: try:
await user.send(embed=embed) await user.send(embed=embed)
except: except Exception:
guild = self.bot.fetch_guild(reminder.guild) guild = self.bot.fetch_guild(reminder.guild)
channel = ( channel = guild.get_channel(reminder.channel) if guild else None
guild.get_channel(reminder.channel) if guild else None
)
if channel: if channel:
await channel.send(f"{user.mention}", embed=embed) await channel.send(f"{user.mention}", embed=embed)
finally: finally:
reminder.delete() reminder.delete()
def setup(bot): def setup(bot: Bot) -> None:
"""Add RemindmeCog to J.A.R.V.I.S."""
bot.add_cog(RemindmeCog(bot)) bot.add_cog(RemindmeCog(bot))

View file

@ -1,6 +1,8 @@
"""J.A.R.V.I.S. Role Giver Cog."""
from discord import Role from discord import Role
from discord.ext import commands from discord.ext import commands
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Setting from jarvis.db.models import Setting
@ -10,7 +12,9 @@ from jarvis.utils.permissions import admin_or_permissions
class RolegiverCog(commands.Cog): class RolegiverCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. Role Giver Cog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -27,18 +31,14 @@ class RolegiverCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _rolegiver_add(self, ctx: SlashContext, role: Role): async def _rolegiver_add(self, ctx: SlashContext, role: Role) -> None:
setting = Setting.objects( setting = Setting.objects(guild=ctx.guild.id, setting="rolegiver").first()
guild=ctx.guild.id, setting="rolegiver"
).first()
if setting and role.id in setting.value: if setting and role.id in setting.value:
await ctx.send("Role already in rolegiver", hidden=True) await ctx.send("Role already in rolegiver", hidden=True)
return return
if not setting: if not setting:
setting = Setting( setting = Setting(guild=ctx.guild.id, setting="rolegiver", value=[])
guild=ctx.guild.id, setting="rolegiver", value=[]
)
roles = [] roles = []
for role_id in setting.value: for role_id in setting.value:
@ -67,10 +67,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url, icon_url=ctx.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
text=f"{ctx.author.name}#{ctx.author.discriminator} "
+ f"| {ctx.author.id}"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -88,10 +85,8 @@ class RolegiverCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _rolegiver_remove(self, ctx: SlashContext, role: Role): async def _rolegiver_remove(self, ctx: SlashContext, role: Role) -> None:
setting = Setting.objects( setting = Setting.objects(guild=ctx.guild.id, setting="rolegiver").first()
guild=ctx.guild.id, setting="rolegiver"
).first()
if not setting or (setting and not setting.value): if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", hidden=True)
return return
@ -128,10 +123,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url, icon_url=ctx.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
text=f"{ctx.author.name}#{ctx.author.discriminator} "
+ f"| {ctx.author.id}"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -140,10 +132,8 @@ class RolegiverCog(commands.Cog):
name="list", name="list",
description="List roles rolegiver", description="List roles rolegiver",
) )
async def _rolegiver_list(self, ctx: SlashContext): async def _rolegiver_list(self, ctx: SlashContext) -> None:
setting = Setting.objects( setting = Setting.objects(guild=ctx.guild.id, setting="rolegiver").first()
guild=ctx.guild.id, setting="rolegiver"
).first()
if not setting or (setting and not setting.value): if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", hidden=True)
return return
@ -170,10 +160,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url, icon_url=ctx.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
text=f"{ctx.author.name}#{ctx.author.discriminator} "
+ f"| {ctx.author.id}"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -191,10 +178,8 @@ class RolegiverCog(commands.Cog):
], ],
) )
@commands.cooldown(1, 10, commands.BucketType.user) @commands.cooldown(1, 10, commands.BucketType.user)
async def _role_get(self, ctx: SlashContext, role: Role): async def _role_get(self, ctx: SlashContext, role: Role) -> None:
setting = Setting.objects( setting = Setting.objects(guild=ctx.guild.id, setting="rolegiver").first()
guild=ctx.guild.id, setting="rolegiver"
).first()
if not setting or (setting and not setting.value): if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", hidden=True)
return return
@ -230,10 +215,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url, icon_url=ctx.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
text=f"{ctx.author.name}#{ctx.author.discriminator} "
+ f"| {ctx.author.id}"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -251,10 +233,8 @@ class RolegiverCog(commands.Cog):
], ],
) )
@commands.cooldown(1, 10, commands.BucketType.user) @commands.cooldown(1, 10, commands.BucketType.user)
async def _role_forfeit(self, ctx: SlashContext, role: Role): async def _role_forfeit(self, ctx: SlashContext, role: Role) -> None:
setting = Setting.objects( setting = Setting.objects(guild=ctx.guild.id, setting="rolegiver").first()
guild=ctx.guild.id, setting="rolegiver"
).first()
if not setting or (setting and not setting.value): if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", hidden=True)
return return
@ -290,13 +270,11 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url, icon_url=ctx.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
text=f"{ctx.author.name}#{ctx.author.discriminator} "
+ f"| {ctx.author.id}"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add RolegiverCog to J.A.R.V.I.S."""
bot.add_cog(RolegiverCog(bot)) bot.add_cog(RolegiverCog(bot))

View file

@ -1,7 +1,12 @@
from discord import Role, TextChannel """J.A.R.V.I.S. Settings Management Cog."""
from typing import Any
from discord import Role
from discord import TextChannel
from discord.ext import commands from discord.ext import commands
from discord.utils import find from discord.utils import find
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from jarvis.db.models import Setting from jarvis.db.models import Setting
@ -11,10 +16,13 @@ from jarvis.utils.permissions import admin_or_permissions
class SettingsCog(commands.Cog): class SettingsCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. Settings Management Cog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
def update_settings(self, setting, value, guild): def update_settings(self, setting: str, value: Any, guild: int) -> bool:
"""Update a guild setting."""
setting = Setting.objects(setting=setting, guild=guild).first() setting = Setting.objects(setting=setting, guild=guild).first()
if not setting: if not setting:
setting = Setting(setting=setting, guild=guild, value=value) setting = Setting(setting=setting, guild=guild, value=value)
@ -22,7 +30,8 @@ class SettingsCog(commands.Cog):
return updated is not None return updated is not None
def delete_settings(self, setting, guild): def delete_settings(self, setting: str, guild: int) -> bool:
"""Delete a guild setting."""
return Setting.objects(setting=setting, guild=guild).delete() return Setting.objects(setting=setting, guild=guild).delete()
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -42,7 +51,7 @@ class SettingsCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _set_mute(self, ctx, role: Role): async def _set_mute(self, ctx: SlashContext, role: Role) -> None:
await ctx.defer() await ctx.defer()
self.update_settings("mute", role.id, ctx.guild.id) self.update_settings("mute", role.id, ctx.guild.id)
await ctx.send(f"Settings applied. New mute role is `{role.name}`") await ctx.send(f"Settings applied. New mute role is `{role.name}`")
@ -62,14 +71,12 @@ class SettingsCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _set_modlog(self, ctx, channel: TextChannel): async def _set_modlog(self, ctx: SlashContext, channel: TextChannel) -> None:
if not isinstance(channel, TextChannel): if not isinstance(channel, TextChannel):
await ctx.send("Channel must be a TextChannel", hidden=True) await ctx.send("Channel must be a TextChannel", hidden=True)
return return
self.update_settings("modlog", channel.id, ctx.guild.id) self.update_settings("modlog", channel.id, ctx.guild.id)
await ctx.send( await ctx.send(f"Settings applied. New modlog channel is {channel.mention}")
f"Settings applied. New modlog channel is {channel.mention}"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="settings", base="settings",
@ -86,14 +93,12 @@ class SettingsCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _set_userlog(self, ctx, channel: TextChannel): async def _set_userlog(self, ctx: SlashContext, channel: TextChannel) -> None:
if not isinstance(channel, TextChannel): if not isinstance(channel, TextChannel):
await ctx.send("Channel must be a TextChannel", hidden=True) await ctx.send("Channel must be a TextChannel", hidden=True)
return return
self.update_settings("userlog", channel.id, ctx.guild.id) self.update_settings("userlog", channel.id, ctx.guild.id)
await ctx.send( await ctx.send(f"Settings applied. New userlog channel is {channel.mention}")
f"Settings applied. New userlog channel is {channel.mention}"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="settings", base="settings",
@ -110,7 +115,7 @@ class SettingsCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _set_massmention(self, ctx, amount: int): async def _set_massmention(self, ctx: SlashContext, amount: int) -> None:
await ctx.defer() await ctx.defer()
self.update_settings("massmention", amount, ctx.guild.id) self.update_settings("massmention", amount, ctx.guild.id)
await ctx.send(f"Settings applied. New massmention limit is {amount}") await ctx.send(f"Settings applied. New massmention limit is {amount}")
@ -130,7 +135,7 @@ class SettingsCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _set_verified(self, ctx, role: Role): async def _set_verified(self, ctx: SlashContext, role: Role) -> None:
await ctx.defer() await ctx.defer()
self.update_settings("verified", role.id, ctx.guild.id) self.update_settings("verified", role.id, ctx.guild.id)
await ctx.send(f"Settings applied. New verified role is `{role.name}`") await ctx.send(f"Settings applied. New verified role is `{role.name}`")
@ -150,12 +155,10 @@ class SettingsCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _set_unverified(self, ctx, role: Role): async def _set_unverified(self, ctx: SlashContext, role: Role) -> None:
await ctx.defer() await ctx.defer()
self.update_settings("unverified", role.id, ctx.guild.id) self.update_settings("unverified", role.id, ctx.guild.id)
await ctx.send( await ctx.send(f"Settings applied. New unverified role is `{role.name}`")
f"Settings applied. New unverified role is `{role.name}`"
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="settings", base="settings",
@ -165,7 +168,7 @@ class SettingsCog(commands.Cog):
description="Unset mute role", description="Unset mute role",
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _unset_mute(self, ctx): async def _unset_mute(self, ctx: SlashContext) -> None:
await ctx.defer() await ctx.defer()
self.delete_settings("mute", ctx.guild.id) self.delete_settings("mute", ctx.guild.id)
await ctx.send("Setting removed.") await ctx.send("Setting removed.")
@ -177,7 +180,7 @@ class SettingsCog(commands.Cog):
description="Unset modlog channel", description="Unset modlog channel",
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _unset_modlog(self, ctx): async def _unset_modlog(self, ctx: SlashContext) -> None:
self.delete_settings("modlog", ctx.guild.id) self.delete_settings("modlog", ctx.guild.id)
await ctx.send("Setting removed.") await ctx.send("Setting removed.")
@ -188,7 +191,7 @@ class SettingsCog(commands.Cog):
description="Unset userlog channel", description="Unset userlog channel",
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _unset_userlog(self, ctx): async def _unset_userlog(self, ctx: SlashContext) -> None:
self.delete_settings("userlog", ctx.guild.id) self.delete_settings("userlog", ctx.guild.id)
await ctx.send("Setting removed.") await ctx.send("Setting removed.")
@ -199,7 +202,7 @@ class SettingsCog(commands.Cog):
description="Unet massmention amount", description="Unet massmention amount",
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _massmention(self, ctx): async def _massmention(self, ctx: SlashContext) -> None:
await ctx.defer() await ctx.defer()
self.delete_settings("massmention", ctx.guild.id) self.delete_settings("massmention", ctx.guild.id)
await ctx.send("Setting removed.") await ctx.send("Setting removed.")
@ -211,7 +214,7 @@ class SettingsCog(commands.Cog):
description="Unset verified role", description="Unset verified role",
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _verified(self, ctx): async def _verified(self, ctx: SlashContext) -> None:
await ctx.defer() await ctx.defer()
self.delete_settings("verified", ctx.guild.id) self.delete_settings("verified", ctx.guild.id)
await ctx.send("Setting removed.") await ctx.send("Setting removed.")
@ -223,16 +226,14 @@ class SettingsCog(commands.Cog):
description="Unset unverified role", description="Unset unverified role",
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _unverified(self, ctx): async def _unverified(self, ctx: SlashContext) -> None:
await ctx.defer() await ctx.defer()
self.delete_settings("unverified", ctx.guild.id) self.delete_settings("unverified", ctx.guild.id)
await ctx.send("Setting removed.") await ctx.send("Setting removed.")
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(base="settings", name="view", description="View settings")
base="settings", name="view", description="View settings"
)
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _view(self, ctx: SlashContext): async def _view(self, ctx: SlashContext) -> None:
settings = Setting.objects(guild=ctx.guild.id) settings = Setting.objects(guild=ctx.guild.id)
fields = [] fields = []
@ -260,20 +261,17 @@ class SettingsCog(commands.Cog):
value += "\n||`[redacted]`||" value += "\n||`[redacted]`||"
fields.append(Field(name=setting.setting, value=value or "N/A")) fields.append(Field(name=setting.setting, value=value or "N/A"))
embed = build_embed( embed = build_embed(title="Current Settings", description="", fields=fields)
title="Current Settings", description="", fields=fields
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(base="settings", name="clear", description="Clear all settings")
base="settings", name="clear", description="Clear all settings"
)
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _clear(self, ctx: SlashContext): async def _clear(self, ctx: SlashContext) -> None:
deleted = Setting.objects(guild=ctx.guild.id).delete() deleted = Setting.objects(guild=ctx.guild.id).delete()
await ctx.send(f"Guild settings cleared: `{deleted is not None}`") await ctx.send(f"Guild settings cleared: `{deleted is not None}`")
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add SettingsCog to J.A.R.V.I.S."""
bot.add_cog(SettingsCog(bot)) bot.add_cog(SettingsCog(bot))

View file

@ -1,16 +1,17 @@
"""J.A.R.V.I.S. Starboard Cog."""
from discord import TextChannel from discord import TextChannel
from discord.ext import commands from discord.ext import commands
from discord.utils import find from discord.utils import find
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option from discord_slash.utils.manage_commands import create_option
from discord_slash.utils.manage_components import ( from discord_slash.utils.manage_components import create_actionrow
create_actionrow, from discord_slash.utils.manage_components import create_select
create_select, from discord_slash.utils.manage_components import create_select_option
create_select_option, from discord_slash.utils.manage_components import wait_for_component
wait_for_component,
)
from jarvis.db.models import Star, Starboard from jarvis.db.models import Star
from jarvis.db.models import Starboard
from jarvis.utils import build_embed from jarvis.utils import build_embed
from jarvis.utils.permissions import admin_or_permissions from jarvis.utils.permissions import admin_or_permissions
@ -24,7 +25,9 @@ supported_images = [
class StarboardCog(commands.Cog): class StarboardCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. Starboard Cog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
@ -33,7 +36,7 @@ class StarboardCog(commands.Cog):
description="Lists all Starboards", description="Lists all Starboards",
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _list(self, ctx): async def _list(self, ctx: SlashContext) -> None:
starboards = Starboard.objects(guild=ctx.guild.id) starboards = Starboard.objects(guild=ctx.guild.id)
if starboards != []: if starboards != []:
message = "Available Starboards:\n" message = "Available Starboards:\n"
@ -57,7 +60,7 @@ class StarboardCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _create(self, ctx, channel: TextChannel): async def _create(self, ctx: SlashContext, channel: TextChannel) -> None:
if channel not in ctx.guild.channels: if channel not in ctx.guild.channels:
await ctx.send( await ctx.send(
"Channel not in guild. Choose an existing channel.", "Channel not in guild. Choose an existing channel.",
@ -68,13 +71,9 @@ class StarboardCog(commands.Cog):
await ctx.send("Channel must be a TextChannel", hidden=True) await ctx.send("Channel must be a TextChannel", hidden=True)
return return
exists = Starboard.objects( exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first()
channel=channel.id, guild=ctx.guild.id
).first()
if exists: if exists:
await ctx.send( await ctx.send(f"Starboard already exists at {channel.mention}.", hidden=True)
f"Starboard already exists at {channel.mention}.", hidden=True
)
return return
count = Starboard.objects(guild=ctx.guild.id).count() count = Starboard.objects(guild=ctx.guild.id).count()
@ -87,9 +86,7 @@ class StarboardCog(commands.Cog):
channel=channel.id, channel=channel.id,
admin=ctx.author.id, admin=ctx.author.id,
).save() ).save()
await ctx.send( await ctx.send(f"Starboard created. Check it out at {channel.mention}.")
f"Starboard created. Check it out at {channel.mention}."
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="starboard", base="starboard",
@ -105,19 +102,13 @@ class StarboardCog(commands.Cog):
], ],
) )
@admin_or_permissions(manage_guild=True) @admin_or_permissions(manage_guild=True)
async def _delete(self, ctx, channel: TextChannel): async def _delete(self, ctx: SlashContext, channel: TextChannel) -> None:
deleted = Starboard.objects( deleted = Starboard.objects(channel=channel.id, guild=ctx.guild.id).delete()
channel=channel.id, guild=ctx.guild.id
).delete()
if deleted: if deleted:
_ = Star.objects(starboard=channel.id).delete() _ = Star.objects(starboard=channel.id).delete()
await ctx.send( await ctx.send(f"Starboard deleted from {channel.mention}.", hidden=True)
f"Starboard deleted from {channel.mention}.", hidden=True
)
else: else:
await ctx.send( await ctx.send(f"Starboard not found in {channel.mention}.", hidden=True)
f"Starboard not found in {channel.mention}.", hidden=True
)
@cog_ext.cog_subcommand( @cog_ext.cog_subcommand(
base="star", base="star",
@ -132,8 +123,7 @@ class StarboardCog(commands.Cog):
), ),
create_option( create_option(
name="channel", name="channel",
description="Channel that has the message, " description="Channel that has the message, required if different than command message",
+ "required if different than command message",
option_type=7, option_type=7,
required=False, required=False,
), ),
@ -145,7 +135,7 @@ class StarboardCog(commands.Cog):
ctx: SlashContext, ctx: SlashContext,
message: str, message: str,
channel: TextChannel = None, channel: TextChannel = None,
): ) -> None:
if not channel: if not channel:
channel = ctx.channel channel = ctx.channel
starboards = Starboard.objects(guild=ctx.guild.id) starboards = Starboard.objects(guild=ctx.guild.id)
@ -156,14 +146,9 @@ class StarboardCog(commands.Cog):
await ctx.defer() await ctx.defer()
channel_list = [] channel_list = []
for starboard in starboards: for starboard in starboards:
channel_list.append( channel_list.append(find(lambda x: x.id == starboard.channel, ctx.guild.channels))
find(lambda x: x.id == starboard.channel, ctx.guild.channels)
)
select_channels = [ select_channels = [create_select_option(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list)]
create_select_option(label=x.name, value=str(idx))
for idx, x in enumerate(channel_list)
]
select = create_select( select = create_select(
options=select_channels, options=select_channels,
@ -173,9 +158,7 @@ class StarboardCog(commands.Cog):
components = [create_actionrow(select)] components = [create_actionrow(select)]
msg = await ctx.send( msg = await ctx.send(content="Choose a starboard", components=components)
content="Choose a starboard", components=components
)
com_ctx = await wait_for_component( com_ctx = await wait_for_component(
self.bot, self.bot,
@ -205,9 +188,7 @@ class StarboardCog(commands.Cog):
) )
return return
count = Star.objects( count = Star.objects(guild=message.guild.id, starboard=starboard.id).count()
guild=message.guild.id, starboard=starboard.id
).count()
content = message.content content = message.content
attachments = message.attachments attachments = message.attachments
@ -232,9 +213,7 @@ class StarboardCog(commands.Cog):
url=message.jump_url, url=message.jump_url,
icon_url=message.author.avatar_url, icon_url=message.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=message.guild.name + " | " + message.channel.name)
text=message.guild.name + " | " + message.channel.name
)
if image_url: if image_url:
embed.set_image(url=image_url) embed.set_image(url=image_url)
@ -254,8 +233,7 @@ class StarboardCog(commands.Cog):
components[0]["components"][0]["disabled"] = True components[0]["components"][0]["disabled"] = True
await com_ctx.edit_origin( await com_ctx.edit_origin(
content="Message saved to Starboard.\n" content=f"Message saved to Starboard.\nSee it in {starboard.mention}",
+ f"See it in {starboard.mention}",
components=components, components=components,
) )
@ -284,17 +262,14 @@ class StarboardCog(commands.Cog):
ctx: SlashContext, ctx: SlashContext,
id: int, id: int,
starboard: TextChannel, starboard: TextChannel,
): ) -> None:
if not isinstance(starboard, TextChannel): if not isinstance(starboard, TextChannel):
await ctx.send("Channel must be a TextChannel", hidden=True) await ctx.send("Channel must be a TextChannel", hidden=True)
return return
exists = Starboard.objects( exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first()
channel=starboard.id, guild=ctx.guild.id
).first()
if not exists: if not exists:
await ctx.send( await ctx.send(
f"Starboard does not exist in {starboard.mention}. " f"Starboard does not exist in {starboard.mention}. Please create it first",
+ "Please create it first",
hidden=True, hidden=True,
) )
return return
@ -319,5 +294,6 @@ class StarboardCog(commands.Cog):
await ctx.send(f"Star {id} deleted") await ctx.send(f"Star {id} deleted")
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add StarboardCog to J.A.R.V.I.S."""
bot.add_cog(StarboardCog(bot)) bot.add_cog(StarboardCog(bot))

View file

@ -1,19 +1,31 @@
"""J.A.R.V.I.S. Utility Cog."""
import re import re
import secrets import secrets
import string import string
from io import BytesIO from io import BytesIO
from discord import File, Guild, Role, User from discord import File
from discord import Guild
from discord import Role
from discord import User
from discord.ext import commands from discord.ext import commands
from discord_slash import SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash.utils.manage_commands import create_choice, create_option from discord_slash import SlashContext
from PIL import Image, ImageDraw from discord_slash.utils.manage_commands import create_choice
from discord_slash.utils.manage_commands import create_option
from PIL import Image
from PIL import ImageDraw
import jarvis import jarvis
from jarvis import jarvis_self, logo from jarvis import jarvis_self
from jarvis import logo
from jarvis.config import get_config from jarvis.config import get_config
from jarvis.data.robotcamo import emotes, hk, names from jarvis.data.robotcamo import emotes
from jarvis.utils import build_embed, convert_bytesize, get_repo_hash from jarvis.data.robotcamo import hk
from jarvis.data.robotcamo import names
from jarvis.utils import build_embed
from jarvis.utils import convert_bytesize
from jarvis.utils import get_repo_hash
from jarvis.utils.field import Field from jarvis.utils.field import Field
@ -24,7 +36,7 @@ class UtilCog(commands.Cog):
Mostly system utility functions, but may change over time Mostly system utility functions, but may change over time
""" """
def __init__(self, bot): def __init__(self, bot: commands.Cog):
self.bot = bot self.bot = bot
self.config = get_config() self.config = get_config()
@ -33,7 +45,7 @@ class UtilCog(commands.Cog):
description="Retrieve J.A.R.V.I.S. status", description="Retrieve J.A.R.V.I.S. status",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _status(self, ctx): async def _status(self, ctx: SlashContext) -> None:
title = "J.A.R.V.I.S. Status" title = "J.A.R.V.I.S. Status"
desc = "All systems online" desc = "All systems online"
color = "#98CCDA" color = "#98CCDA"
@ -49,9 +61,7 @@ class UtilCog(commands.Cog):
fields.append(Field("PID", jarvis_self.pid)) fields.append(Field("PID", jarvis_self.pid))
fields.append(Field("Version", jarvis.__version__, False)) fields.append(Field("Version", jarvis.__version__, False))
fields.append(Field("Git Hash", get_repo_hash(), False)) fields.append(Field("Git Hash", get_repo_hash(), False))
embed = build_embed( embed = build_embed(title=title, description=desc, fields=fields, color=color)
title=title, description=desc, fields=fields, color=color
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -59,12 +69,12 @@ class UtilCog(commands.Cog):
description="Get the current logo", description="Get the current logo",
) )
@commands.cooldown(1, 30, commands.BucketType.channel) @commands.cooldown(1, 30, commands.BucketType.channel)
async def _logo(self, ctx): async def _logo(self, ctx: SlashContext) -> None:
lo = logo.get_logo(self.config.logo) lo = logo.get_logo(self.config.logo)
await ctx.send(f"```\n{lo}\n```") await ctx.send(f"```\n{lo}\n```")
@cog_ext.cog_slash(name="rchk", description="Robot Camo HK416") @cog_ext.cog_slash(name="rchk", description="Robot Camo HK416")
async def _rchk(self, ctx: SlashContext): async def _rchk(self, ctx: SlashContext) -> None:
await ctx.send(content=hk) await ctx.send(content=hk)
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -79,11 +89,9 @@ class UtilCog(commands.Cog):
) )
], ],
) )
async def _rcauto(self, ctx: SlashContext, text: str): async def _rcauto(self, ctx: SlashContext, text: str) -> None:
to_send = "" to_send = ""
if len(text) == 1 and not re.match( if len(text) == 1 and not re.match(r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()):
r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()
):
await ctx.send("Please use ASCII characters.", hidden=True) await ctx.send("Please use ASCII characters.", hidden=True)
return return
for letter in text.upper(): for letter in text.upper():
@ -113,18 +121,14 @@ class UtilCog(commands.Cog):
], ],
) )
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _avatar(self, ctx, user: User = None): async def _avatar(self, ctx: SlashContext, user: User = None) -> None:
if not user: if not user:
user = ctx.author user = ctx.author
avatar = user.avatar_url avatar = user.avatar_url
embed = build_embed( embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
title="Avatar", description="", fields=[], color="#00FFEE"
)
embed.set_image(url=avatar) embed.set_image(url=avatar)
embed.set_author( embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=avatar)
name=f"{user.name}#{user.discriminator}", icon_url=avatar
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -139,7 +143,7 @@ class UtilCog(commands.Cog):
) )
], ],
) )
async def _roleinfo(self, ctx: SlashContext, role: Role): async def _roleinfo(self, ctx: SlashContext, role: Role) -> None:
fields = [ fields = [
Field(name="ID", value=role.id), Field(name="ID", value=role.id),
@ -148,9 +152,7 @@ class UtilCog(commands.Cog):
Field(name="Mention", value=f"`{role.mention}`"), Field(name="Mention", value=f"`{role.mention}`"),
Field(name="Hoisted", value="Yes" if role.hoist else "No"), Field(name="Hoisted", value="Yes" if role.hoist else "No"),
Field(name="Position", value=str(role.position)), Field(name="Position", value=str(role.position)),
Field( Field(name="Mentionable", value="Yes" if role.mentionable else "No"),
name="Mentionable", value="Yes" if role.mentionable else "No"
),
] ]
embed = build_embed( embed = build_embed(
@ -192,7 +194,7 @@ class UtilCog(commands.Cog):
) )
], ],
) )
async def _userinfo(self, ctx: SlashContext, user: User = None): async def _userinfo(self, ctx: SlashContext, user: User = None) -> None:
if not user: if not user:
user = ctx.author user = ctx.author
user_roles = user.roles user_roles = user.roles
@ -210,9 +212,7 @@ class UtilCog(commands.Cog):
), ),
Field( Field(
name=f"Roles [{len(user_roles)}]", name=f"Roles [{len(user_roles)}]",
value=" ".join([x.mention for x in user_roles]) value=" ".join([x.mention for x in user_roles]) if user_roles else "None",
if user_roles
else "None",
inline=False, inline=False,
), ),
] ]
@ -224,23 +224,17 @@ class UtilCog(commands.Cog):
color=str(user_roles[0].color) if user_roles else "#FF0000", color=str(user_roles[0].color) if user_roles else "#FF0000",
) )
embed.set_author( embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=user.avatar_url)
name=f"{user.name}#{user.discriminator}", icon_url=user.avatar_url
)
embed.set_thumbnail(url=user.avatar_url) embed.set_thumbnail(url=user.avatar_url)
embed.set_footer(text=f"ID: {user.id}") embed.set_footer(text=f"ID: {user.id}")
await ctx.send(embed=embed) await ctx.send(embed=embed)
@cog_ext.cog_slash(name="serverinfo", description="Get server info") @cog_ext.cog_slash(name="serverinfo", description="Get server info")
async def _server_info(self, ctx: SlashContext): async def _server_info(self, ctx: SlashContext) -> None:
guild: Guild = ctx.guild guild: Guild = ctx.guild
owner = ( owner = f"{guild.owner.name}#{guild.owner.discriminator}" if guild.owner else "||`[redacted]`||"
f"{guild.owner.name}#{guild.owner.discriminator}"
if guild.owner
else "||`[redacted]`||"
)
region = guild.region region = guild.region
categories = len(guild.categories) categories = len(guild.categories)
@ -260,13 +254,9 @@ class UtilCog(commands.Cog):
Field(name="Roles", value=roles), Field(name="Roles", value=roles),
] ]
if len(role_list) < 1024: if len(role_list) < 1024:
fields.append( fields.append(Field(name="Role List", value=role_list, inline=False))
Field(name="Role List", value=role_list, inline=False)
)
embed = build_embed( embed = build_embed(title="", description="", fields=fields, timestamp=guild.created_at)
title="", description="", fields=fields, timestamp=guild.created_at
)
embed.set_author(name=guild.name, icon_url=guild.icon_url) embed.set_author(name=guild.name, icon_url=guild.icon_url)
embed.set_thumbnail(url=guild.icon_url) embed.set_thumbnail(url=guild.icon_url)
@ -302,13 +292,9 @@ class UtilCog(commands.Cog):
], ],
) )
@commands.cooldown(1, 15, type=commands.BucketType.user) @commands.cooldown(1, 15, type=commands.BucketType.user)
async def _pw_gen( async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3) -> None:
self, ctx: SlashContext, length: int = 32, chars: int = 3
):
if length > 256: if length > 256:
await ctx.send( await ctx.send("Please limit password to 256 characters", hidden=True)
"Please limit password to 256 characters", hidden=True
)
return return
choices = [ choices = [
string.ascii_letters, string.ascii_letters,
@ -320,11 +306,12 @@ class UtilCog(commands.Cog):
pw = "".join(secrets.choice(choices[chars]) for i in range(length)) pw = "".join(secrets.choice(choices[chars]) for i in range(length))
await ctx.send( await ctx.send(
f"Generated password:\n`{pw}`\n\n" f"Generated password:\n`{pw}`\n\n"
+ '**WARNING: Once you press "Dismiss Message", ' '**WARNING: Once you press "Dismiss Message", '
+ "*the password is lost forever***", "*the password is lost forever***",
hidden=True, hidden=True,
) )
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add UtilCog to J.A.R.V.I.S."""
bot.add_cog(UtilCog(bot)) bot.add_cog(UtilCog(bot))

View file

@ -1,14 +1,18 @@
from random import randint """J.A.R.V.I.S. Verify Cog."""
from secrets import randint
from discord.ext import commands from discord.ext import commands
from discord_slash import ComponentContext, SlashContext, cog_ext from discord_slash import cog_ext
from discord_slash import ComponentContext
from discord_slash import SlashContext
from discord_slash.model import ButtonStyle from discord_slash.model import ButtonStyle
from discord_slash.utils import manage_components from discord_slash.utils import manage_components
from jarvis.db.models import Setting from jarvis.db.models import Setting
def create_layout(): def create_layout() -> list:
"""Create verify component layout."""
buttons = [] buttons = []
yes = randint(0, 2) yes = randint(0, 2)
for i in range(3): for i in range(3):
@ -27,7 +31,9 @@ def create_layout():
class VerifyCog(commands.Cog): class VerifyCog(commands.Cog):
def __init__(self, bot): """J.A.R.V.I.S. Verify Cog."""
def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@cog_ext.cog_slash( @cog_ext.cog_slash(
@ -35,27 +41,24 @@ class VerifyCog(commands.Cog):
description="Verify that you've read the rules", description="Verify that you've read the rules",
) )
@commands.cooldown(1, 15, commands.BucketType.user) @commands.cooldown(1, 15, commands.BucketType.user)
async def _verify(self, ctx: SlashContext): async def _verify(self, ctx: SlashContext) -> None:
await ctx.defer() await ctx.defer()
role = Setting.objects(guild=ctx.guild.id, setting="verified").first() role = Setting.objects(guild=ctx.guild.id, setting="verified").first()
if not role: if not role:
await ctx.send( await ctx.send("This guild has not enabled verification", delete_after=5)
"This guild has not enabled verification", delete_after=5
)
return return
if ctx.guild.get_role(role.value) in ctx.author.roles: if ctx.guild.get_role(role.value) in ctx.author.roles:
await ctx.send("You are already verified.", delete_after=5) await ctx.send("You are already verified.", delete_after=5)
return return
components = create_layout() components = create_layout()
message = await ctx.send( message = await ctx.send(
content=f"{ctx.author.mention}, " content=f"{ctx.author.mention}, please press the button that says `YES`.",
+ "please press the button that says `YES`.",
components=components, components=components,
) )
await message.delete(delay=15) await message.delete(delay=15)
@cog_ext.cog_component(components=create_layout()) @cog_ext.cog_component(components=create_layout())
async def _process(self, ctx: ComponentContext): async def _process(self, ctx: ComponentContext) -> None:
await ctx.defer(edit_origin=True) await ctx.defer(edit_origin=True)
try: try:
if ctx.author.id != ctx.origin_message.mentions[0].id: if ctx.author.id != ctx.origin_message.mentions[0].id:
@ -68,33 +71,24 @@ class VerifyCog(commands.Cog):
for c in components: for c in components:
for c2 in c["components"]: for c2 in c["components"]:
c2["disabled"] = True c2["disabled"] = True
setting = Setting.objects( setting = Setting.objects(guild=ctx.guild.id, setting="verified").first()
guild=ctx.guild.id, setting="verified"
).first()
role = ctx.guild.get_role(setting.value) role = ctx.guild.get_role(setting.value)
await ctx.author.add_roles(role, reason="Verification passed") await ctx.author.add_roles(role, reason="Verification passed")
setting = Setting.objects( setting = Setting.objects(guild=ctx.guild.id, setting="unverified").first()
guild=ctx.guild.id, setting="unverified"
).first()
if setting: if setting:
role = ctx.guild.get_role(setting.value) role = ctx.guild.get_role(setting.value)
await ctx.author.remove_roles( await ctx.author.remove_roles(role, reason="Verification passed")
role, reason="Verification passed"
)
await ctx.edit_origin( await ctx.edit_origin(
content=f"Welcome, {ctx.author.mention}. " content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.",
+ "Please enjoy your stay.", components=manage_components.spread_to_rows(*components, max_in_row=5),
components=manage_components.spread_to_rows(
*components, max_in_row=5
),
) )
await ctx.origin_message.delete(delay=5) await ctx.origin_message.delete(delay=5)
else: else:
await ctx.edit_origin( await ctx.edit_origin(
content=f"{ctx.author.mention}, incorrect. " content=f"{ctx.author.mention}, incorrect. Please press the button that says `YES`",
+ "Please press the button that says `YES`",
) )
def setup(bot): def setup(bot: commands.Bot) -> None:
"""Add VerifyCog to J.A.R.V.I.S."""
bot.add_cog(VerifyCog(bot)) bot.add_cog(VerifyCog(bot))

View file

@ -1,3 +1,4 @@
"""Load the config for J.A.R.V.I.S."""
from yaml import load from yaml import load
from jarvis.db.models import Config as DBConfig from jarvis.db.models import Config as DBConfig
@ -9,7 +10,10 @@ except ImportError:
class Config(object): class Config(object):
def __new__(cls, *args, **kwargs): """Config singleton object for J.A.R.V.I.S."""
def __new__(cls, *args: list, **kwargs: dict):
"""Get the singleton config, or creates a new one."""
it = cls.__dict__.get("it") it = cls.__dict__.get("it")
if it is not None: if it is not None:
return it return it
@ -24,33 +28,39 @@ class Config(object):
logo: str, logo: str,
mongo: dict, mongo: dict,
urls: dict, urls: dict,
log_level: str = "WARNING",
cogs: list = None, cogs: list = None,
events: bool = True, events: bool = True,
gitlab_token: str = None, gitlab_token: str = None,
max_messages: int = 1000, max_messages: int = 1000,
): ) -> None:
"""Initialize the config object."""
self.token = token self.token = token
self.client_id = client_id self.client_id = client_id
self.logo = logo self.logo = logo
self.mongo = mongo self.mongo = mongo
self.urls = urls self.urls = urls
self.log_level = log_level
self.cogs = cogs self.cogs = cogs
self.events = events self.events = events
self.max_messages = max_messages self.max_messages = max_messages
self.gitlab_token = gitlab_token self.gitlab_token = gitlab_token
def get_db_config(self): def get_db_config(self) -> None:
"""Load the database config objects."""
config = DBConfig.objects() config = DBConfig.objects()
for item in config: for item in config:
setattr(self, item.key, item.value) setattr(self, item.key, item.value)
@classmethod @classmethod
def from_yaml(cls, y): def from_yaml(cls, y: dict) -> "Config":
"""Load the yaml config file."""
instance = cls(**y) instance = cls(**y)
return instance return instance
def get_config(path: str = "config.yaml") -> Config: def get_config(path: str = "config.yaml") -> Config:
"""Get the config from the specified yaml file."""
if Config.__dict__.get("it"): if Config.__dict__.get("it"):
return Config() return Config()
with open(path) as f: with open(path) as f:
@ -59,6 +69,7 @@ def get_config(path: str = "config.yaml") -> Config:
return Config.from_yaml(y) return Config.from_yaml(y)
def reload_config(): def reload_config() -> None:
"""Force reload of the config singleton on next call."""
if "it" in Config.__dict__: if "it" in Config.__dict__:
Config.__dict__.pop("it") Config.__dict__.pop("it")

View file

@ -1,3 +1,4 @@
"""dbrand-specific data."""
shipping_lookup = [ shipping_lookup = [
{"country": "afghanistan", "code": "AF"}, {"country": "afghanistan", "code": "AF"},
{"country": "albania", "code": "AL"}, {"country": "albania", "code": "AL"},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,4 @@
"""Robot Camo emote lookups."""
emotes = { emotes = {
"A": 852317928572715038, "A": 852317928572715038,
"B": 852317954975727679, "B": 852317954975727679,

View file

@ -1,3 +1,4 @@
"""Unicode emoji data."""
import json import json
from os import getcwd from os import getcwd
from os import sep as s from os import sep as s

View file

@ -1,23 +1,26 @@
"""J.A.R.V.I.S. database object for mongoengine."""
from datetime import datetime from datetime import datetime
from mongoengine import Document from mongoengine import Document
from mongoengine.fields import ( from mongoengine.fields import BooleanField
BooleanField, from mongoengine.fields import DateTimeField
DateTimeField, from mongoengine.fields import DictField
DictField, from mongoengine.fields import DynamicField
DynamicField, from mongoengine.fields import IntField
IntField, from mongoengine.fields import ListField
ListField, from mongoengine.fields import LongField
LongField, from mongoengine.fields import StringField
StringField,
)
class SnowflakeField(LongField): class SnowflakeField(LongField):
"""Snowflake LongField Override."""
pass pass
class Autopurge(Document): class Autopurge(Document):
"""Autopurge database object."""
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
channel = SnowflakeField(required=True) channel = SnowflakeField(required=True)
delay = IntField(min_value=1, max_value=300, default=30) delay = IntField(min_value=1, max_value=300, default=30)
@ -28,6 +31,8 @@ class Autopurge(Document):
class Autoreact(Document): class Autoreact(Document):
"""Autoreact database object."""
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
channel = SnowflakeField(required=True) channel = SnowflakeField(required=True)
reactions = ListField(field=StringField()) reactions = ListField(field=StringField())
@ -38,6 +43,8 @@ class Autoreact(Document):
class Ban(Document): class Ban(Document):
"""Ban database object."""
active = BooleanField(default=True) active = BooleanField(default=True)
admin = SnowflakeField(required=True) admin = SnowflakeField(required=True)
user = SnowflakeField(required=True) user = SnowflakeField(required=True)
@ -53,6 +60,8 @@ class Ban(Document):
class Config(Document): class Config(Document):
"""Config database object."""
key = StringField(required=True) key = StringField(required=True)
value = DynamicField(required=True) value = DynamicField(required=True)
@ -60,6 +69,8 @@ class Config(Document):
class Guess(Document): class Guess(Document):
"""Guess database object."""
correct = BooleanField(default=False) correct = BooleanField(default=False)
guess = StringField(max_length=800, required=True) guess = StringField(max_length=800, required=True)
user = SnowflakeField(required=True) user = SnowflakeField(required=True)
@ -68,6 +79,8 @@ class Guess(Document):
class Joke(Document): class Joke(Document):
"""Joke database object."""
rid = StringField() rid = StringField()
body = StringField() body = StringField()
title = StringField() title = StringField()
@ -79,6 +92,8 @@ class Joke(Document):
class Kick(Document): class Kick(Document):
"""Kick database object."""
admin = SnowflakeField(required=True) admin = SnowflakeField(required=True)
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
reason = StringField(max_length=100, required=True) reason = StringField(max_length=100, required=True)
@ -89,6 +104,8 @@ class Kick(Document):
class Lock(Document): class Lock(Document):
"""Lock database object."""
active = BooleanField(default=True) active = BooleanField(default=True)
admin = SnowflakeField(required=True) admin = SnowflakeField(required=True)
channel = SnowflakeField(required=True) channel = SnowflakeField(required=True)
@ -101,6 +118,8 @@ class Lock(Document):
class Mute(Document): class Mute(Document):
"""Mute database object."""
active = BooleanField(default=True) active = BooleanField(default=True)
user = SnowflakeField(required=True) user = SnowflakeField(required=True)
admin = SnowflakeField(required=True) admin = SnowflakeField(required=True)
@ -113,6 +132,8 @@ class Mute(Document):
class Purge(Document): class Purge(Document):
"""Purge database object."""
admin = SnowflakeField(required=True) admin = SnowflakeField(required=True)
channel = SnowflakeField(required=True) channel = SnowflakeField(required=True)
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
@ -123,6 +144,8 @@ class Purge(Document):
class Reminder(Document): class Reminder(Document):
"""Reminder database object."""
active = BooleanField(default=True) active = BooleanField(default=True)
user = SnowflakeField(required=True) user = SnowflakeField(required=True)
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
@ -135,6 +158,8 @@ class Reminder(Document):
class Roleping(Document): class Roleping(Document):
"""Roleping database object."""
active = BooleanField(default=True) active = BooleanField(default=True)
role = SnowflakeField(required=True) role = SnowflakeField(required=True)
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
@ -146,6 +171,8 @@ class Roleping(Document):
class Setting(Document): class Setting(Document):
"""Setting database object."""
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
setting = StringField(required=True) setting = StringField(required=True)
value = DynamicField() value = DynamicField()
@ -154,6 +181,8 @@ class Setting(Document):
class Star(Document): class Star(Document):
"""Star database object."""
active = BooleanField(default=True) active = BooleanField(default=True)
index = IntField(required=True) index = IntField(required=True)
message = SnowflakeField(required=True) message = SnowflakeField(required=True)
@ -168,6 +197,8 @@ class Star(Document):
class Starboard(Document): class Starboard(Document):
"""Starboard database object."""
channel = SnowflakeField(required=True) channel = SnowflakeField(required=True)
guild = SnowflakeField(required=True) guild = SnowflakeField(required=True)
admin = SnowflakeField(required=True) admin = SnowflakeField(required=True)
@ -177,6 +208,8 @@ class Starboard(Document):
class Unban(Document): class Unban(Document):
"""Unban database object."""
user = SnowflakeField(required=True) user = SnowflakeField(required=True)
username = StringField(required=True) username = StringField(required=True)
discrim = IntField(min_value=1, max_value=9999, required=True) discrim = IntField(min_value=1, max_value=9999, required=True)
@ -189,6 +222,8 @@ class Unban(Document):
class Warning(Document): class Warning(Document):
"""Warning database object."""
active = BooleanField(default=True) active = BooleanField(default=True)
admin = SnowflakeField(required=True) admin = SnowflakeField(required=True)
user = SnowflakeField(required=True) user = SnowflakeField(required=True)

View file

@ -1,29 +1,33 @@
"""J.A.R.V.I.S. guild event handler."""
import asyncio import asyncio
from discord import Guild
from discord.ext.commands import Bot
from discord.utils import find from discord.utils import find
from jarvis.db.models import Setting from jarvis.db.models import Setting
class GuildEventHandler(object): class GuildEventHandler(object):
def __init__(self, bot): """J.A.R.V.I.S. guild event handler."""
def __init__(self, bot: Bot):
self.bot = bot self.bot = bot
self.bot.add_listener(self.on_guild_join) self.bot.add_listener(self.on_guild_join)
async def on_guild_join(self, guild): async def on_guild_join(self, guild: Guild) -> None:
"""Handle on_guild_join event."""
general = find(lambda x: x.name == "general", guild.channels) general = find(lambda x: x.name == "general", guild.channels)
if general and general.permissions_for(guild.me).send_messages: if general and general.permissions_for(guild.me).send_messages:
user = self.bot.user user = self.bot.user
await general.send( await general.send(
f"Allow me to introduce myself. I am {user.mention}, a virtual " f"Allow me to introduce myself. I am {user.mention}, a virtual "
+ "artificial intelligence, and I'm here to assist you with a " "artificial intelligence, and I'm here to assist you with a "
+ "variety of tasks as best I can, " "variety of tasks as best I can, "
+ "24 hours a day, seven days a week." "24 hours a day, seven days a week."
) )
await asyncio.sleep(1) await asyncio.sleep(1)
await general.send( await general.send("Importing all preferences from home interface...")
"Importing all preferences from home interface..."
)
# Set some default settings # Set some default settings
_ = Setting(guild=guild.id, setting="massmention", value=5).save() _ = Setting(guild=guild.id, setting="massmention", value=5).save()

View file

@ -1,28 +1,28 @@
"""J.A.R.V.I.S. Member event handler."""
from discord import Member from discord import Member
from discord.ext.commands import Bot
from jarvis.db.models import Mute, Setting from jarvis.db.models import Mute
from jarvis.db.models import Setting
class MemberEventHandler(object): class MemberEventHandler(object):
def __init__(self, bot): """J.A.R.V.I.S. Member event handler."""
def __init__(self, bot: Bot):
self.bot = bot self.bot = bot
self.bot.add_listener(self.on_member_join) self.bot.add_listener(self.on_member_join)
async def on_member_join(self, user: Member): async def on_member_join(self, user: Member) -> None:
"""Handle on_member_join event."""
guild = user.guild guild = user.guild
mute = Mute.objects(guild=guild.id, user=user.id, active=True).first() mute = Mute.objects(guild=guild.id, user=user.id, active=True).first()
if mute: if mute:
mute_role = Setting.objects(guild=guild.id, setting="mute").first() mute_role = Setting.objects(guild=guild.id, setting="mute").first()
role = guild.get_role(mute_role.value) role = guild.get_role(mute_role.value)
await user.add_roles( await user.add_roles(role, reason="User is still muted from prior mute")
role, reason="User is still muted from prior mute" unverified = Setting.objects(guild=guild.id, setting="unverified").first()
)
unverified = Setting.objects(
guild=guild.id, setting="unverified"
).first()
if unverified: if unverified:
role = guild.get_role(unverified.value) role = guild.get_role(unverified.value)
if role not in user.roles: if role not in user.roles:
await user.add_roles( await user.add_roles(role, reason="User just joined and is unverified")
role, reason="User just joined and is unverified"
)

View file

@ -1,10 +1,17 @@
"""J.A.R.V.I.S. Message event handler."""
import re import re
from discord import DMChannel, Message from discord import DMChannel
from discord import Message
from discord.ext.commands import Bot
from discord.utils import find from discord.utils import find
from jarvis.config import get_config from jarvis.config import get_config
from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning from jarvis.db.models import Autopurge
from jarvis.db.models import Autoreact
from jarvis.db.models import Roleping
from jarvis.db.models import Setting
from jarvis.db.models import Warning
from jarvis.utils import build_embed from jarvis.utils import build_embed
from jarvis.utils.field import Field from jarvis.utils.field import Field
@ -15,19 +22,21 @@ invites = re.compile(
class MessageEventHandler(object): class MessageEventHandler(object):
def __init__(self, bot): """J.A.R.V.I.S. Message event handler."""
def __init__(self, bot: Bot):
self.bot = bot self.bot = bot
self.bot.add_listener(self.on_message) self.bot.add_listener(self.on_message)
self.bot.add_listener(self.on_message_edit) self.bot.add_listener(self.on_message_edit)
async def autopurge(self, message: Message): async def autopurge(self, message: Message) -> None:
autopurge = Autopurge.objects( """Handle autopurge events."""
guild=message.guild.id, channel=message.channel.id autopurge = Autopurge.objects(guild=message.guild.id, channel=message.channel.id).first()
).first()
if autopurge: if autopurge:
await message.delete(delay=autopurge.delay) await message.delete(delay=autopurge.delay)
async def autoreact(self, message: Message): async def autoreact(self, message: Message) -> None:
"""Handle autoreact events."""
autoreact = Autoreact.objects( autoreact = Autoreact.objects(
guild=message.guild.id, guild=message.guild.id,
channel=message.channel.id, channel=message.channel.id,
@ -36,15 +45,13 @@ class MessageEventHandler(object):
for reaction in autoreact.reactions: for reaction in autoreact.reactions:
await message.add_reaction(reaction) await message.add_reaction(reaction)
async def checks(self, message: Message): async def checks(self, message: Message) -> None:
"""Other message checks."""
# #tech # #tech
channel = find( channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions)
lambda x: x.id == 599068193339736096, message.channel_mentions
)
if channel and message.author.id == 293795462752894976: if channel and message.author.id == 293795462752894976:
await channel.send( await channel.send(
content="https://cdn.discordapp.com/attachments/" content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif"
+ "664621130044407838/805218508866453554/tech.gif"
) )
content = re.sub(r"\s+", "", message.content) content = re.sub(r"\s+", "", message.content)
match = invites.search(content) match = invites.search(content)
@ -78,29 +85,23 @@ class MessageEventHandler(object):
fields=fields, fields=fields,
) )
embed.set_author( embed.set_author(
name=message.author.nick name=message.author.nick if message.author.nick else message.author.name,
if message.author.nick
else message.author.name,
icon_url=message.author.avatar_url, icon_url=message.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
text=f"{message.author.name}#"
+ f"{message.author.discriminator} "
+ f"| {message.author.id}"
)
await message.channel.send(embed=embed) await message.channel.send(embed=embed)
async def massmention(self, message: Message): async def massmention(self, message: Message) -> None:
"""Handle massmention events."""
massmention = Setting.objects( massmention = Setting.objects(
guild=message.guild.id, guild=message.guild.id,
setting="massmention", setting="massmention",
).first() ).first()
if ( if (
massmention massmention
and massmention.value > 0 and massmention.value > 0 # noqa: W503
and len(message.mentions) and len(message.mentions) - (1 if message.author in message.mentions else 0) # noqa: W503
- (1 if message.author in message.mentions else 0) > massmention.value # noqa: W503
> massmention.value
): ):
_ = Warning( _ = Warning(
active=True, active=True,
@ -117,18 +118,14 @@ class MessageEventHandler(object):
fields=fields, fields=fields,
) )
embed.set_author( embed.set_author(
name=message.author.nick name=message.author.nick if message.author.nick else message.author.name,
if message.author.nick
else message.author.name,
icon_url=message.author.avatar_url, icon_url=message.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
text=f"{message.author.name}#{message.author.discriminator} "
+ f"| {message.author.id}"
)
await message.channel.send(embed=embed) await message.channel.send(embed=embed)
async def roleping(self, message: Message): async def roleping(self, message: Message) -> None:
"""Handle roleping events."""
rolepings = Roleping.objects(guild=message.guild.id, active=True) rolepings = Roleping.objects(guild=message.guild.id, active=True)
if not rolepings: if not rolepings:
@ -152,9 +149,7 @@ class MessageEventHandler(object):
role_in_rolepings = list(filter(lambda x: x in roleping_ids, roles)) role_in_rolepings = list(filter(lambda x: x in roleping_ids, roles))
# Check if the user has the role, so they are allowed to ping it # Check if the user has the role, so they are allowed to ping it
user_missing_role = any( user_missing_role = any(x.id not in roleping_ids for x in message.author.roles)
x.id not in roleping_ids for x in message.author.roles
)
# Admins can ping whoever # Admins can ping whoever
user_is_admin = message.author.guild_permissions.administrator user_is_admin = message.author.guild_permissions.administrator
@ -165,19 +160,11 @@ class MessageEventHandler(object):
if message.author.id in roleping.bypass["users"]: if message.author.id in roleping.bypass["users"]:
user_has_bypass = True user_has_bypass = True
break break
if any( if any(role.id in roleping.bypass["roles"] for role in message.author.roles):
role.id in roleping.bypass["roles"]
for role in message.author.roles
):
user_has_bypass = True user_has_bypass = True
break break
if ( if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass:
role_in_rolepings
and user_missing_role
and not user_is_admin
and not user_has_bypass
):
_ = Warning( _ = Warning(
active=True, active=True,
admin=get_config().client_id, admin=get_config().client_id,
@ -199,30 +186,26 @@ class MessageEventHandler(object):
fields=fields, fields=fields,
) )
embed.set_author( embed.set_author(
name=message.author.nick name=message.author.nick if message.author.nick else message.author.name,
if message.author.nick
else message.author.name,
icon_url=message.author.avatar_url, icon_url=message.author.avatar_url,
) )
embed.set_footer( embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
text=f"{message.author.name}#{message.author.discriminator} "
+ f"| {message.author.id}"
)
await message.channel.send(embed=embed) await message.channel.send(embed=embed)
async def on_message(self, message: Message): async def on_message(self, message: Message) -> None:
if ( """Handle on_message event. Calls other event handlers."""
not isinstance(message.channel, DMChannel) if not isinstance(message.channel, DMChannel) and not message.author.bot:
and not message.author.bot
):
await self.autoreact(message) await self.autoreact(message)
await self.massmention(message) await self.massmention(message)
await self.roleping(message) await self.roleping(message)
await self.autopurge(message) await self.autopurge(message)
await self.checks(message) await self.checks(message)
async def on_message_edit(self, before: Message, after: Message): async def on_message_edit(self, before: Message, after: Message) -> None:
"""Handle on_message_edit event. Calls other event handlers."""
if not isinstance(after.channel, DMChannel) and not after.author.bot: if not isinstance(after.channel, DMChannel) and not after.author.bot:
await self.massmention(after) await self.massmention(after)
await self.roleping(after) await self.roleping(after)
await self.checks(after) await self.checks(after)
await self.roleping(after)
await self.checks(after)

View file

@ -1,3 +1,5 @@
"""Logos for J.A.R.V.I.S."""
logo_doom = r""" logo_doom = r"""
___ ___ ______ _ _ _____ _____ ___ ___ ______ _ _ _____ _____
|_ | / _ \ | ___ \ | | | | |_ _| / ___| |_ | / _ \ | ___ \ | | | | |_ _| / ___|
@ -82,7 +84,7 @@ logo_alligator = r"""
#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+#
##### ### ### ### ### ### ### ### ### ### ########### ### ######## ### ##### ### ### ### ### ### ### ### ### ### ########### ### ######## ###
""" """ # noqa: E501
logo_alligator2 = r""" logo_alligator2 = r"""
@ -97,7 +99,8 @@ logo_alligator2 = r"""
""" """
def get_logo(lo): def get_logo(lo: str) -> str:
"""Get a logo."""
if "logo_" not in lo: if "logo_" not in lo:
lo = "logo_" + lo lo = "logo_" + lo
return globals()[lo] if lo in globals() else logo_alligator2 return globals()[lo] if lo in globals() else logo_alligator2

View file

@ -1,7 +1,12 @@
from jarvis.tasks import unban, unlock, unmute, unwarn """J.A.R.V.I.S. background task handlers."""
from jarvis.tasks import unban
from jarvis.tasks import unlock
from jarvis.tasks import unmute
from jarvis.tasks import unwarn
def init(): def init() -> None:
"""Start the background task handlers."""
unban.unban.start() unban.unban.start()
unlock.unlock.start() unlock.unlock.start()
unmute.unmute.start() unmute.unmute.start()

View file

@ -1,22 +1,24 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. unban background task handler."""
from datetime import datetime
from datetime import timedelta
from discord.ext.tasks import loop from discord.ext.tasks import loop
import jarvis import jarvis
from jarvis.config import get_config from jarvis.config import get_config
from jarvis.db.models import Ban, Unban from jarvis.db.models import Ban
from jarvis.db.models import Unban
jarvis_id = get_config().client_id jarvis_id = get_config().client_id
@loop(minutes=10) @loop(minutes=10)
async def unban(): async def unban() -> None:
"""J.A.R.V.I.S. unban background task."""
bans = Ban.objects(type="temp", active=True) bans = Ban.objects(type="temp", active=True)
unbans = [] unbans = []
for ban in bans: for ban in bans:
if ban.created_at + timedelta( if ban.created_at + timedelta(hours=ban.duration) < datetime.utcnow() + timedelta(minutes=10):
hours=ban.duration
) < datetime.utcnow() + timedelta(minutes=10):
guild = await jarvis.jarvis.fetch_guild(ban.guild) guild = await jarvis.jarvis.fetch_guild(ban.guild)
user = await jarvis.jarvis.fetch_user(ban.user) user = await jarvis.jarvis.fetch_user(ban.user)
if user: if user:

View file

@ -1,4 +1,6 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. unlock background task handler."""
from datetime import datetime
from datetime import timedelta
from discord.ext.tasks import loop from discord.ext.tasks import loop
@ -7,13 +9,11 @@ from jarvis.db.models import Lock
@loop(minutes=1) @loop(minutes=1)
async def unlock(): async def unlock() -> None:
"""J.A.R.V.I.S. unlock background task."""
locks = Lock.objects(active=True) locks = Lock.objects(active=True)
for lock in locks: for lock in locks:
if ( if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow():
lock.created_at + timedelta(minutes=lock.duration)
< datetime.utcnow()
):
guild = await jarvis.jarvis.fetch_guild(lock.guild) guild = await jarvis.jarvis.fetch_guild(lock.guild)
channel = await jarvis.jarvis.fetch_channel(lock.channel) channel = await jarvis.jarvis.fetch_channel(lock.channel)
if channel: if channel:
@ -21,8 +21,6 @@ async def unlock():
for role in roles: for role in roles:
overrides = channel.overwrites_for(role) overrides = channel.overwrites_for(role)
overrides.send_messages = None overrides.send_messages = None
await channel.set_permissions( await channel.set_permissions(role, overwrite=overrides, reason="Lock expired")
role, overwrite=overrides, reason="Lock expired"
)
lock.active = False lock.active = False
lock.save() lock.save()

View file

@ -1,23 +1,22 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. unmute background task handler."""
from datetime import datetime
from datetime import timedelta
from discord.ext.tasks import loop from discord.ext.tasks import loop
import jarvis import jarvis
from jarvis.db.models import Mute, Setting from jarvis.db.models import Mute
from jarvis.db.models import Setting
@loop(minutes=1) @loop(minutes=1)
async def unmute(): async def unmute() -> None:
"""J.A.R.V.I.S. unmute background task."""
mutes = Mute.objects(duration__gt=0, active=True) mutes = Mute.objects(duration__gt=0, active=True)
mute_roles = Setting.objects(setting="mute") mute_roles = Setting.objects(setting="mute")
for mute in mutes: for mute in mutes:
if ( if mute.created_at + timedelta(minutes=mute.duration) < datetime.utcnow():
mute.created_at + timedelta(minutes=mute.duration) mute_role = [x.value for x in mute_roles if x.guild == mute.guild][0]
< datetime.utcnow()
):
mute_role = [x.value for x in mute_roles if x.guild == mute.guild][
0
]
guild = await jarvis.jarvis.fetch_guild(mute.guild) guild = await jarvis.jarvis.fetch_guild(mute.guild)
role = guild.get_role(mute_role) role = guild.get_role(mute_role)
user = await guild.fetch_member(mute.user) user = await guild.fetch_member(mute.user)

View file

@ -1,18 +1,17 @@
from datetime import datetime, timedelta """J.A.R.V.I.S. unwarn background task handler."""
from datetime import datetime
from datetime import timedelta
from discord.ext.tasks import loop from discord.ext.tasks import loop
import jarvis
from jarvis.db.models import Warning from jarvis.db.models import Warning
@loop(hours=1) @loop(hours=1)
async def unwarn(): async def unwarn() -> None:
"""J.A.R.V.I.S. unwarn background task."""
warns = Warning.objects(active=True) warns = Warning.objects(active=True)
for warn in warns: for warn in warns:
if ( if warn.created_at + timedelta(hours=warn.duration) < datetime.utcnow():
warn.created_at + timedelta(hours=warn.duration)
< datetime.utcnow()
):
warn.active = False warn.active = False
warn.save() warn.save()

View file

@ -1,8 +1,11 @@
"""J.A.R.V.I.S. Utility Functions."""
from datetime import datetime from datetime import datetime
from pkgutil import iter_modules from pkgutil import iter_modules
import git import git
from discord import Color, Embed from discord import Color
from discord import Embed
from discord import Message
from discord.ext import commands from discord.ext import commands
import jarvis.cogs import jarvis.cogs
@ -12,17 +15,19 @@ from jarvis.config import get_config
__all__ = ["field", "db", "cachecog", "permissions"] __all__ = ["field", "db", "cachecog", "permissions"]
def convert_bytesize(bytes: int) -> str: def convert_bytesize(b: int) -> str:
bytes = float(bytes) """Convert bytes amount to human readable."""
b = float(b)
sizes = ["B", "KB", "MB", "GB", "TB", "PB"] sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
size = 0 size = 0
while bytes >= 1024 and size < len(sizes) - 1: while b >= 1024 and size < len(sizes) - 1:
bytes = bytes / 1024 b = b / 1024
size += 1 size += 1
return "{:0.3f} {}".format(bytes, sizes[size]) return "{:0.3f} {}".format(b, sizes[size])
def unconvert_bytesize(size, ending: str): def unconvert_bytesize(size: int, ending: str) -> int:
"""Convert human readable to bytes."""
ending = ending.upper() ending = ending.upper()
sizes = ["B", "KB", "MB", "GB", "TB", "PB"] sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
if ending == "B": if ending == "B":
@ -31,7 +36,8 @@ def unconvert_bytesize(size, ending: str):
return round(size * (1024 ** sizes.index(ending))) return round(size * (1024 ** sizes.index(ending)))
def get_prefix(bot, message): def get_prefix(bot: commands.Bot, message: Message) -> list:
"""Get bot prefixes."""
prefixes = ["!", "-", "%"] prefixes = ["!", "-", "%"]
# if not message.guild: # if not message.guild:
# return "?" # return "?"
@ -39,13 +45,15 @@ def get_prefix(bot, message):
return commands.when_mentioned_or(*prefixes)(bot, message) return commands.when_mentioned_or(*prefixes)(bot, message)
def get_extensions(path=jarvis.cogs.__path__) -> list: def get_extensions(path: str = jarvis.cogs.__path__) -> list:
"""Get J.A.R.V.I.S. cogs."""
config = get_config() config = get_config()
vals = config.cogs or [x.name for x in iter_modules(path)] vals = config.cogs or [x.name for x in iter_modules(path)]
return ["jarvis.cogs.{}".format(x) for x in vals] return ["jarvis.cogs.{}".format(x) for x in vals]
def parse_color_hex(hex: str) -> Color: def parse_color_hex(hex: str) -> Color:
"""Convert a hex color to a d.py Color."""
hex = hex.lstrip("#") hex = hex.lstrip("#")
rgb = tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4)) # noqa: E203 rgb = tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4)) # noqa: E203
return Color.from_rgb(*rgb) return Color.from_rgb(*rgb)
@ -57,8 +65,9 @@ def build_embed(
fields: list, fields: list,
color: str = "#FF0000", color: str = "#FF0000",
timestamp: datetime = None, timestamp: datetime = None,
**kwargs, **kwargs: dict,
) -> Embed: ) -> Embed:
"""Embed builder utility function."""
if not timestamp: if not timestamp:
timestamp = datetime.utcnow() timestamp = datetime.utcnow()
embed = Embed( embed = Embed(
@ -73,7 +82,8 @@ def build_embed(
return embed return embed
def update(): def update() -> int:
"""J.A.R.V.I.S. update utility."""
repo = git.Repo(".") repo = git.Repo(".")
dirty = repo.is_dirty() dirty = repo.is_dirty()
current_hash = repo.head.object.hexsha current_hash = repo.head.object.hexsha
@ -87,6 +97,7 @@ def update():
return 1 return 1
def get_repo_hash(): def get_repo_hash() -> str:
"""J.A.R.V.I.S. current branch hash."""
repo = git.Repo(".") repo = git.Repo(".")
return repo.head.object.hexsha return repo.head.object.hexsha

View file

@ -1,4 +1,6 @@
from datetime import datetime, timedelta """Cog wrapper for command caching."""
from datetime import datetime
from datetime import timedelta
from discord.ext import commands from discord.ext import commands
from discord.ext.tasks import loop from discord.ext.tasks import loop
@ -7,27 +9,28 @@ from discord_slash import SlashContext
class CacheCog(commands.Cog): class CacheCog(commands.Cog):
"""Cog wrapper for command caching."""
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.cache = {} self.cache = {}
self._expire_interaction.start() self._expire_interaction.start()
def check_cache(self, ctx: SlashContext, **kwargs): def check_cache(self, ctx: SlashContext, **kwargs: dict) -> dict:
"""Check the cache."""
if not kwargs: if not kwargs:
kwargs = {} kwargs = {}
return find( return find(
lambda x: x["command"] == ctx.subcommand_name lambda x: x["command"] == ctx.subcommand_name # noqa: W503
and x["user"] == ctx.author.id and x["user"] == ctx.author.id # noqa: W503
and x["guild"] == ctx.guild.id and x["guild"] == ctx.guild.id # noqa: W503
and all(x[k] == v for k, v in kwargs.items()), and all(x[k] == v for k, v in kwargs.items()), # noqa: W503
self.cache.values(), self.cache.values(),
) )
@loop(minutes=1) @loop(minutes=1)
async def _expire_interaction(self): async def _expire_interaction(self) -> None:
keys = list(self.cache.keys()) keys = list(self.cache.keys())
for key in keys: for key in keys:
if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta( if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1):
minutes=1
):
del self.cache[key] del self.cache[key]

View file

@ -1,12 +1,16 @@
"""Embed field helper."""
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
@dataclass @dataclass
class Field: class Field:
"""Embed Field."""
name: Any name: Any
value: Any value: Any
inline: bool = True inline: bool = True
def to_dict(self): def to_dict(self) -> dict:
"""Convert Field to d.py field dict."""
return {"name": self.name, "value": self.value, "inline": self.inline} return {"name": self.name, "value": self.value, "inline": self.inline}

View file

@ -1,10 +1,14 @@
"""Permissions wrappers."""
from discord.ext import commands from discord.ext import commands
from jarvis.config import get_config from jarvis.config import get_config
def user_is_bot_admin(): def user_is_bot_admin() -> bool:
def predicate(ctx): """Check if a user is a J.A.R.V.I.S. admin."""
def predicate(ctx: commands.Context) -> bool:
"""Command check predicate."""
if getattr(get_config(), "admins", None): if getattr(get_config(), "admins", None):
return ctx.author.id in get_config().admins return ctx.author.id in get_config().admins
else: else:
@ -13,12 +17,12 @@ def user_is_bot_admin():
return commands.check(predicate) return commands.check(predicate)
def admin_or_permissions(**perms): def admin_or_permissions(**perms: dict) -> bool:
"""Check if a user is an admin or has other perms."""
original = commands.has_permissions(**perms).predicate original = commands.has_permissions(**perms).predicate
async def extended_check(ctx): async def extended_check(ctx: commands.Context) -> bool:
return await commands.has_permissions(administrator=True).predicate( """Extended check predicate.""" # noqa: D401
ctx return await commands.has_permissions(administrator=True).predicate(ctx) or await original(ctx)
) or await original(ctx)
return commands.check(extended_check) return commands.check(extended_check)

View file

@ -1,12 +1,12 @@
discord-py>=1.7, <2
psutil>=5.8, <6
GitPython>=3.1, <4
PyYaml>=5.4, <6
discord-py-slash-command>=2.3.2, <3
pymongo>=3.12.0, <4
opencv-python>=4.5, <5
ButtonPaginator>=0.0.3 ButtonPaginator>=0.0.3
Pillow>=8.2.0, <9 discord-py>=1.7, <2
python-gitlab>=2.9.0, <3 discord-py-slash-command>=2.3.2, <3
ulid-py>=1.1.0, <2 GitPython>=3.1, <4
mongoengine>=0.23, <1 mongoengine>=0.23, <1
opencv-python>=4.5, <5
Pillow>=8.2.0, <9
psutil>=5.8, <6
pymongo>=3.12.0, <4
python-gitlab>=2.9.0, <3
PyYaml>=5.4, <6
ulid-py>=1.1.0, <2

5
run.py
View file

@ -1,6 +1,9 @@
#!/bin/python3 #!/bin/python3
# flake8: noqa
from importlib import reload as ireload from importlib import reload as ireload
from multiprocessing import Process, Value, freeze_support from multiprocessing import freeze_support
from multiprocessing import Process
from multiprocessing import Value
from pathlib import Path from pathlib import Path
from time import sleep from time import sleep