Pass all flake8 checks

This commit is contained in:
Zeva Rose 2021-08-06 20:24:50 -06:00
parent a87783db16
commit 93759287ca
50 changed files with 625 additions and 380 deletions

View file

@ -35,9 +35,3 @@ repos:
- flake8-bandit~=2.1
- flake8-docstrings~=1.5
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,7 +1,8 @@
"""Main J.A.R.V.I.S. package."""
import asyncio
import logging
from pathlib import Path
from typing import Union
from typing import Optional
from discord import Intents
from discord.ext import commands
@ -10,7 +11,7 @@ from discord_slash import SlashCommand
from mongoengine import connect
from psutil import Process
from jarvis import logo
from jarvis import logo # noqa: F401
from jarvis import tasks
from jarvis import utils
from jarvis.config import get_config
@ -18,6 +19,14 @@ 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():
asyncio.set_event_loop(asyncio.new_event_loop())
@ -25,7 +34,6 @@ intents = Intents.default()
intents.members = True
restart_ctx = None
jconfig = get_config()
jarvis = commands.Bot(
command_prefix=utils.get_prefix,
@ -40,7 +48,8 @@ __version__ = "1.10.3"
@jarvis.event
async def on_ready():
async def on_ready() -> None:
"""d.py on_ready override."""
global restart_ctx
print(" Logged in as {0.user}".format(jarvis))
print(" Connected to {} guild(s)".format(len(jarvis.guilds)))
@ -60,7 +69,8 @@ async def on_ready():
restart_ctx = None
def run(ctx=None):
def run(ctx: dict = None) -> Optional[dict]:
"""Run J.A.R.V.I.S."""
global restart_ctx
if ctx:
restart_ctx = ctx
@ -81,7 +91,7 @@ def run(ctx=None):
jarvis.load_extension(extension)
print(
" https://discord.com/api/oauth2/authorize?client_id="
+ "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id)
+ "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) # noqa: W503
)
jarvis.max_messages = jconfig.max_messages
@ -89,7 +99,7 @@ def run(ctx=None):
# Add event listeners
if jconfig.events:
listeners = [
_ = [
guild.GuildEventHandler(jarvis),
member.MemberEventHandler(jarvis),
message.MessageEventHandler(jarvis),

View file

@ -1,3 +1,6 @@
"""J.A.R.V.I.S. Admin Cogs."""
from discord.ext.commands import Bot
from jarvis.cogs.admin import ban
from jarvis.cogs.admin import kick
from jarvis.cogs.admin import lock
@ -8,7 +11,8 @@ 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(kick.KickCog(bot))
bot.add_cog(lock.LockCog(bot))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. BanCog."""
import re
from datetime import datetime
from datetime import timedelta
@ -21,6 +22,8 @@ from jarvis.utils.permissions import admin_or_permissions
class BanCog(CacheCog):
"""J.A.R.V.I.S. BanCog."""
def __init__(self, bot: commands.Bot):
super().__init__(bot)
@ -32,7 +35,8 @@ class BanCog(CacheCog):
duration: int,
active: bool,
fields: list,
):
) -> None:
"""Apply a Discord ban."""
await ctx.guild.ban(user, reason=reason)
_ = Ban(
user=user.id,
@ -61,7 +65,8 @@ class BanCog(CacheCog):
await ctx.send(embed=embed)
async def discord_apply_unban(self, ctx: SlashContext, user: User, reason: str):
async def discord_apply_unban(self, ctx: SlashContext, user: User, reason: str) -> None:
"""Apply a Discord unban."""
await ctx.guild.unban(user, reason=reason)
_ = Unban(
user=user.id,
@ -128,7 +133,7 @@ class BanCog(CacheCog):
reason: str = None,
type: str = "perm",
duration: int = 4,
):
) -> None:
if not user or user == ctx.author:
await ctx.send("You cannot ban yourself.", hidden=True)
return
@ -221,7 +226,7 @@ class BanCog(CacheCog):
ctx: SlashContext,
user: str,
reason: str,
):
) -> None:
if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True)
return
@ -252,12 +257,8 @@ class BanCog(CacheCog):
active_bans = []
for ban in bans:
active_bans.append("{0} ({1}): {2}".format(ban.user.name, ban.user.id, ban.reason))
message = (
"More than one result. "
+ "Please use one of the following IDs:\n```"
+ "\n".join(active_bans)
+ "\n```"
)
ab_message = "\n".join(active_bans)
message = f"More than one result. Please use one of the following IDs:\n```{ab_message}\n```"
await ctx.send(message)
return
else:
@ -331,13 +332,13 @@ class BanCog(CacheCog):
],
)
@admin_or_permissions(ban_members=True)
async def _bans_list(self, ctx: SlashContext, type: int = 0, active: int = 1):
async def _bans_list(self, ctx: SlashContext, type: int = 0, active: int = 1) -> None:
active = bool(active)
exists = self.check_cache(ctx, type=type, active=active)
if exists:
await ctx.defer(hidden=True)
await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True,
)
return
@ -357,10 +358,12 @@ class BanCog(CacheCog):
fields.append(
Field(
name=f"Username: {ban.username}#{ban.discrim}",
value=f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
+ f"User ID: {ban.user}\n"
+ f"Reason: {ban.reason}\n"
+ f"Type: {ban.type}\n\u200b",
value=(
f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
f"User ID: {ban.user}\n"
f"Reason: {ban.reason}\n"
f"Type: {ban.type}\n\u200b"
),
inline=False,
)
)
@ -372,10 +375,12 @@ class BanCog(CacheCog):
fields.append(
Field(
name=f"Username: {ban.user.name}#" + f"{ban.user.discriminator}",
value="Date: [unknown]\n"
+ f"User ID: {ban.user.id}\n"
+ f"Reason: {ban.reason}\n"
+ "Type: manual\n\u200b",
value=(
f"Date: [unknown]\n"
f"User ID: {ban.user.id}\n"
f"Reason: {ban.reason}\n"
"Type: manual\n\u200b"
),
inline=False,
)
)
@ -397,7 +402,7 @@ class BanCog(CacheCog):
pages.append(embed)
else:
for i in range(0, len(bans), 5):
embed = build_embed(title=title, description="", fields=fields[i : i + 5])
embed = build_embed(title=title, description="", fields=fields[i : i + 5]) # noqa: E203
embed.set_thumbnail(url=ctx.guild.icon_url)
pages.append(embed)

View file

@ -1,4 +1,6 @@
"""J.A.R.V.I.S. KickCog."""
from discord import User
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
@ -11,7 +13,9 @@ from jarvis.utils.permissions import admin_or_permissions
class KickCog(CacheCog):
def __init__(self, bot):
"""J.A.R.V.I.S. KickCog."""
def __init__(self, bot: Bot):
super().__init__(bot)
@cog_ext.cog_slash(
@ -33,7 +37,7 @@ class KickCog(CacheCog):
],
)
@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:
await ctx.send("You cannot kick yourself.", hidden=True)
return

View file

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

View file

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

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. MuteCog."""
from discord import Member
from discord.ext import commands
from discord.utils import get
@ -13,7 +14,9 @@ from jarvis.utils.permissions import admin_or_permissions
class MuteCog(commands.Cog):
def __init__(self, bot):
"""J.A.R.V.I.S. MuteCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@cog_ext.cog_slash(
@ -41,7 +44,7 @@ class MuteCog(commands.Cog):
],
)
@admin_or_permissions(mute_members=True)
async def _mute(self, ctx: SlashContext, user: Member, reason: str, duration: int = 30):
async def _mute(self, ctx: SlashContext, user: Member, reason: str, duration: int = 30) -> None:
if user == ctx.author:
await ctx.send("You cannot mute yourself.", hidden=True)
return
@ -54,7 +57,7 @@ class MuteCog(commands.Cog):
mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first()
if not mute_setting:
await ctx.send(
"Please configure a mute role " + "with /settings mute <role> first",
"Please configure a mute role with /settings mute <role> first",
hidden=True,
)
return
@ -100,11 +103,11 @@ class MuteCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, setting="mute").first()
if not mute_setting:
await ctx.send(
"Please configure a mute role with " + "/settings mute <role> first.",
"Please configure a mute role with /settings mute <role> first.",
hidden=True,
)
return

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. PurgeCog."""
from discord import TextChannel
from discord.ext import commands
from discord_slash import cog_ext
@ -10,7 +11,9 @@ from jarvis.utils.permissions import admin_or_permissions
class PurgeCog(commands.Cog):
def __init__(self, bot):
"""J.A.R.V.I.S. PurgeCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@cog_ext.cog_slash(
@ -26,7 +29,7 @@ class PurgeCog(commands.Cog):
],
)
@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:
await ctx.send("Amount must be >= 1", hidden=True)
return
@ -63,7 +66,7 @@ class PurgeCog(commands.Cog):
],
)
@admin_or_permissions(manage_messages=True)
async def _autopurge_add(self, ctx: SlashContext, channel: TextChannel, delay: int = 30):
async def _autopurge_add(self, ctx: SlashContext, channel: TextChannel, delay: int = 30) -> None:
if not isinstance(channel, TextChannel):
await ctx.send("Channel must be a TextChannel", hidden=True)
return
@ -83,7 +86,7 @@ class PurgeCog(commands.Cog):
admin=ctx.author.id,
delay=delay,
).save()
await ctx.send(f"Autopurge set up on {channel.mention}, " + f"delay is {delay} seconds")
await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds")
@cog_ext.cog_subcommand(
base="autopurge",
@ -99,7 +102,7 @@ class PurgeCog(commands.Cog):
],
)
@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)
if not autopurge:
await ctx.send("Autopurge does not exist.", hidden=True)
@ -127,7 +130,7 @@ class PurgeCog(commands.Cog):
],
)
@admin_or_permissions(manage_messages=True)
async def _autopurge_update(self, ctx: SlashContext, channel: TextChannel, delay: int):
async def _autopurge_update(self, ctx: SlashContext, channel: TextChannel, delay: int) -> None:
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
if not autopurge:
await ctx.send("Autopurge does not exist.", hidden=True)

View file

@ -1,9 +1,11 @@
"""J.A.R.V.I.S. RolepingCog."""
from datetime import datetime
from datetime import timedelta
from ButtonPaginator import Paginator
from discord import Member
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
@ -17,7 +19,9 @@ from jarvis.utils.permissions import admin_or_permissions
class RolepingCog(CacheCog):
def __init__(self, bot):
"""J.A.R.V.I.S. RolepingCog."""
def __init__(self, bot: Bot):
super().__init__(bot)
@cog_ext.cog_subcommand(
@ -34,7 +38,7 @@ class RolepingCog(CacheCog):
],
)
@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()
if roleping:
await ctx.send(f"Role `{role.name}` already in roleping.", hidden=True)
@ -62,7 +66,7 @@ class RolepingCog(CacheCog):
],
)
@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)
if not roleping:
await ctx.send("Roleping does not exist", hidden=True)
@ -76,12 +80,12 @@ class RolepingCog(CacheCog):
name="list",
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)
if exists:
await ctx.defer(hidden=True)
await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True,
)
return
@ -176,7 +180,7 @@ class RolepingCog(CacheCog):
],
)
@admin_or_permissions(manage_guild=True)
async def _roleping_bypass_user(self, ctx: SlashContext, user: Member, rping: Role):
async def _roleping_bypass_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
@ -188,7 +192,7 @@ class RolepingCog(CacheCog):
if len(roleping.bypass["users"]) == 10:
await ctx.send(
"Already have 10 users in bypass. " "Please consider using roles for roleping bypass",
"Already have 10 users in bypass. Please consider using roles for roleping bypass",
hidden=True,
)
return
@ -197,7 +201,7 @@ class RolepingCog(CacheCog):
if matching_role:
await ctx.send(
f"{user.mention} already has bypass " f"via {matching_role[0].mention}",
f"{user.mention} already has bypass via {matching_role[0].mention}",
hidden=True,
)
return
@ -229,7 +233,7 @@ class RolepingCog(CacheCog):
],
)
@admin_or_permissions(manage_guild=True)
async def _roleping_bypass_role(self, ctx: SlashContext, role: Role, rping: Role):
async def _roleping_bypass_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
@ -241,7 +245,7 @@ class RolepingCog(CacheCog):
if len(roleping.bypass["roles"]) == 10:
await ctx.send(
"Already have 10 roles in bypass. " "Please consider consolidating roles for roleping bypass",
"Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass",
hidden=True,
)
return
@ -273,7 +277,7 @@ class RolepingCog(CacheCog):
],
)
@admin_or_permissions(manage_guild=True)
async def _roleping_restore_user(self, ctx: SlashContext, user: Member, rping: Role):
async def _roleping_restore_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
@ -310,7 +314,7 @@ class RolepingCog(CacheCog):
],
)
@admin_or_permissions(manage_guild=True)
async def _roleping_restore_role(self, ctx: SlashContext, role: Role, rping: Role):
async def _roleping_restore_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
@ -322,7 +326,7 @@ class RolepingCog(CacheCog):
if len(roleping.bypass["roles"]) == 10:
await ctx.send(
"Already have 10 roles in bypass. " "Please consider consolidating roles for roleping bypass",
"Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass",
hidden=True,
)
return

View file

@ -1,8 +1,10 @@
"""J.A.R.V.I.S. WarningCog."""
from datetime import datetime
from datetime import timedelta
from ButtonPaginator import Paginator
from discord import User
from discord.ext.commands import Bot
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.model import ButtonStyle
@ -17,7 +19,9 @@ from jarvis.utils.permissions import admin_or_permissions
class WarningCog(CacheCog):
def __init__(self, bot):
"""J.A.R.V.I.S. WarningCog."""
def __init__(self, bot: Bot):
super().__init__(bot)
@cog_ext.cog_slash(
@ -45,7 +49,7 @@ class WarningCog(CacheCog):
],
)
@admin_or_permissions(manage_guild=True)
async def _warn(self, ctx: SlashContext, user: User, reason: str, duration: int = 24):
async def _warn(self, ctx: SlashContext, user: User, reason: str, duration: int = 24) -> None:
if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True)
return
@ -101,13 +105,13 @@ class WarningCog(CacheCog):
],
)
@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)
exists = self.check_cache(ctx, user_id=user.id, active=active)
if exists:
await ctx.defer(hidden=True)
await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True,
)
return
@ -138,15 +142,15 @@ class WarningCog(CacheCog):
fields.append(
Field(
name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"),
value=f"{warn.reason}\n" + f"Admin: {admin_name}\n" + "\u200b",
value=f"{warn.reason}\nAdmin: {admin_name}\n\u200b",
inline=False,
)
)
for i in range(0, len(fields), 5):
embed = build_embed(
title="Warnings",
description=f"{warnings.count()} total | " + f"{active_warns.count()} currently active",
fields=fields[i : i + 5],
description=f"{warnings.count()} total | {active_warns.count()} currently active",
fields=fields[i : i + 5], # noqa: E203
)
embed.set_author(
name=user.name + "#" + user.discriminator,
@ -170,8 +174,8 @@ class WarningCog(CacheCog):
for i in range(0, len(fields), 5):
embed = build_embed(
title="Warnings",
description=f"{warnings.count()} total | " + f"{active_warns.count()} currently active",
fields=fields[i : i + 5],
description=f"{warnings.count()} total | {active_warns.count()} currently active",
fields=fields[i : i + 5], # noqa: E203
)
embed.set_author(
name=user.name + "#" + user.discriminator,

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. Autoreact Cog."""
import re
from discord import TextChannel
@ -13,7 +14,9 @@ from jarvis.utils.permissions import admin_or_permissions
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.custom_emote = re.compile(r"^<:\w+:(\d+)>$")
@ -31,7 +34,7 @@ class AutoReactCog(commands.Cog):
],
)
@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):
await ctx.send("Channel must be a text channel", hidden=True)
return
@ -62,7 +65,7 @@ class AutoReactCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, channel=channel.id).delete()
if exists:
await ctx.send(f"Autoreact removed from {channel.mention}")
@ -89,13 +92,13 @@ class AutoReactCog(commands.Cog):
],
)
@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()
custom_emoji = self.custom_emote.match(emote)
standard_emoji = emote in emoji_list
if not custom_emoji and not standard_emoji:
await ctx.send(
"Please use either an emote from this server" + " or a unicode emoji.",
"Please use either an emote from this server or a unicode emoji.",
hidden=True,
)
return
@ -106,7 +109,7 @@ class AutoReactCog(commands.Cog):
return
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
if not exists:
await ctx.send("Please create autoreact first with " + f"/autoreact create {channel.mention}")
await ctx.send(f"Please create autoreact first with /autoreact create {channel.mention}")
return
if emote in exists.reactions:
await ctx.send(
@ -116,7 +119,7 @@ class AutoReactCog(commands.Cog):
return
if len(exists.reactions) >= 5:
await ctx.send(
"Max number of reactions hit. " + "Remove a different one to add this one",
"Max number of reactions hit. Remove a different one to add this one",
hidden=True,
)
return
@ -144,11 +147,11 @@ class AutoReactCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, channel=channel.id).first()
if not exists:
await ctx.send(
"Please create autoreact first with " + f"/autoreact create {channel.mention}",
f"Please create autoreact first with /autoreact create {channel.mention}",
hidden=True,
)
return
@ -176,11 +179,11 @@ class AutoReactCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, channel=channel.id).first()
if not exists:
await ctx.send(
"Please create autoreact first with " + f"/autoreact create {channel.mention}",
f"Please create autoreact first with /autoreact create {channel.mention}",
hidden=True,
)
return
@ -192,5 +195,6 @@ class AutoReactCog(commands.Cog):
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))

View file

@ -1,15 +1,14 @@
"""J.A.R.V.I.S. Complete the Code 2 Cog."""
import re
from datetime import datetime
from datetime import timedelta
import aiohttp
import pymongo
from ButtonPaginator import Paginator
from discord import Member
from discord import User
from discord.commands.ext import Bot
from discord.ext import commands
from discord.ext.tasks import loop
from discord.utils import find
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.model import ButtonStyle
@ -29,11 +28,16 @@ invites = re.compile(
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)
self._session = aiohttp.ClientSession()
self.url = "https://completethecodetwo.cards/pw"
def __del__(self):
self._session.close()
@cog_ext.cog_subcommand(
base="ctc2",
name="about",
@ -41,7 +45,7 @@ class CTCCog(CacheCog):
guild_ids=guild_ids,
)
@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")
@cog_ext.cog_subcommand(
@ -51,22 +55,22 @@ class CTCCog(CacheCog):
guild_ids=guild_ids,
)
@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:
await ctx.send(
"Listen here, dipshit. Don't be like " + "<@256110768724901889>. Make your guesses < 800 characters.",
"Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses < 800 characters.",
hidden=True,
)
return
elif not valid.fullmatch(guess):
await ctx.send(
"Listen here, dipshit. Don't be like " + "<@256110768724901889>. Make your guesses *readable*.",
"Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses *readable*.",
hidden=True,
)
return
elif invites.search(guess):
await ctx.send(
"Listen here, dipshit. " + "No using this to bypass sending invite links.",
"Listen here, dipshit. No using this to bypass sending invite links.",
hidden=True,
)
return
@ -90,12 +94,12 @@ class CTCCog(CacheCog):
guild_ids=guild_ids,
)
@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)
if exists:
await ctx.defer(hidden=True)
await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True,
)
return
@ -124,7 +128,7 @@ class CTCCog(CacheCog):
embed = build_embed(
title="completethecodetwo.cards guesses",
description=f"{len(fields)} guesses so far",
fields=fields[i : i + 5],
fields=fields[i : i + 5], # noqa: E203
url="https://completethecodetwo.cards",
)
embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png")
@ -158,5 +162,6 @@ class CTCCog(CacheCog):
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))

View file

@ -1,8 +1,10 @@
"""J.A.R.V.I.S. dbrand cog."""
import re
import aiohttp
from discord.ext import commands
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option
from jarvis.config import get_config
@ -20,7 +22,7 @@ class DbrandCog(commands.Cog):
Mostly support functions. Credit @cpixl for the shipping API
"""
def __init__(self, bot):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.base_url = "https://dbrand.com/"
self._session = aiohttp.ClientSession()
@ -28,6 +30,9 @@ class DbrandCog(commands.Cog):
self.api_url = get_config().urls["dbrand_shipping"]
self.cache = {}
def __del__(self):
self._session.close()
@cog_ext.cog_subcommand(
base="db",
name="skin",
@ -35,7 +40,7 @@ class DbrandCog(commands.Cog):
description="See what skins are available",
)
@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")
@cog_ext.cog_subcommand(
@ -45,7 +50,7 @@ class DbrandCog(commands.Cog):
description="Get some robot camo. Make Tony Stark proud",
)
@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")
@cog_ext.cog_subcommand(
@ -55,7 +60,7 @@ class DbrandCog(commands.Cog):
description="See devices with Grip support",
)
@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")
@cog_ext.cog_subcommand(
@ -65,7 +70,7 @@ class DbrandCog(commands.Cog):
description="Contact support",
)
@commands.cooldown(1, 30, commands.BucketType.channel)
async def _contact(self, ctx):
async def _contact(self, ctx: SlashContext) -> None:
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
@cog_ext.cog_subcommand(
@ -75,7 +80,7 @@ class DbrandCog(commands.Cog):
description="Contact support",
)
@commands.cooldown(1, 30, commands.BucketType.channel)
async def _support(self, ctx):
async def _support(self, ctx: SlashContext) -> None:
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
@cog_ext.cog_subcommand(
@ -85,7 +90,7 @@ class DbrandCog(commands.Cog):
description="Get your order status",
)
@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")
@cog_ext.cog_subcommand(
@ -95,7 +100,7 @@ class DbrandCog(commands.Cog):
description="Get your order status",
)
@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")
@cog_ext.cog_subcommand(
@ -105,7 +110,7 @@ class DbrandCog(commands.Cog):
description="dbrand status",
)
@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")
@cog_ext.cog_subcommand(
@ -115,7 +120,7 @@ class DbrandCog(commands.Cog):
description="Give us your money!",
)
@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")
@cog_ext.cog_subcommand(
@ -125,7 +130,7 @@ class DbrandCog(commands.Cog):
description="(not) extortion",
)
@commands.cooldown(1, 30, commands.BucketType.channel)
async def _extort(self, ctx):
async def _extort(self, ctx: SlashContext) -> None:
await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion")
@cog_ext.cog_subcommand(
@ -135,7 +140,7 @@ class DbrandCog(commands.Cog):
guild_ids=guild_ids,
)
@commands.cooldown(1, 30, commands.BucketType.channel)
async def _wallpapers(self, ctx):
async def _wallpapers(self, ctx: SlashContext) -> None:
await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers")
@cog_ext.cog_subcommand(
@ -147,7 +152,7 @@ class DbrandCog(commands.Cog):
(
create_option(
name="search",
description="Country search query (2 character code, " + "country name, emoji)",
description="Country search query (2 character code, country name, emoji)",
option_type=3,
required=True,
)
@ -155,7 +160,7 @@ class DbrandCog(commands.Cog):
],
)
@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()
if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
if re.match(
@ -203,10 +208,12 @@ class DbrandCog(commands.Cog):
)
country = "-".join(x for x in data["country"].split(" ") if x != "the")
country_urlsafe = country.replace("-", "%20")
description = "Click the link above to " + "see shipping time to {data['country']}."
description += "\n[View all shipping destinations]" + "(https://dbrand.com/shipping)"
description += " | [Check shipping status]"
description += "(https://dbrand.com/status" + f"#main-content:~:text={country_urlsafe})"
description = (
f"Click the link above to see shipping time to {data['country']}."
"\n[View all shipping destinations](https://dbrand.com/shipping)"
" | [Check shipping status]"
f"(https://dbrand.com/status#main-content:~:text={country_urlsafe})"
)
embed = build_embed(
title="Shipping to {}".format(data["country"]),
description=description,
@ -223,8 +230,9 @@ class DbrandCog(commands.Cog):
elif not data["is_valid"]:
embed = build_embed(
title="Check Shipping Times",
description="Country not found.\nYou can [view all shipping "
+ "destinations here](https://dbrand.com/shipping)",
description=(
"Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)"
),
fields=[],
url="https://dbrand.com/shipping",
color="#FFBB00",
@ -238,9 +246,11 @@ class DbrandCog(commands.Cog):
elif not data["shipping_available"]:
embed = build_embed(
title="Shipping to {}".format(data["country"]),
description="No shipping available.\nTime to move to a country"
+ " that has shipping available.\nYou can [find a new country "
+ "to live in here](https://dbrand.com/shipping)",
description=(
"No shipping available.\nTime to move to a country"
" that has shipping available.\nYou can [find a new country "
"to live in here](https://dbrand.com/shipping)"
),
fields=[],
url="https://dbrand.com/shipping",
color="#FFBB00",
@ -253,5 +263,6 @@ class DbrandCog(commands.Cog):
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))

View file

@ -1,13 +1,17 @@
"""J.A.R.V.I.S. Developer Cog."""
import base64
import hashlib
import re
import subprocess
import subprocess # noqa: S404
import uuid as uuidpy
from typing import Any
from typing import Union
import ulid as ulidpy
from bson import ObjectId
from discord.ext import commands
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_choice
from discord_slash.utils.manage_commands import create_option
@ -18,13 +22,13 @@ from jarvis.utils.field import Field
supported_hashes = {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}$")
URL_VERIFY = re.compile(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]" + r"|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
URL_VERIFY = re.compile(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
DN_VERIFY = re.compile(
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)" + r"?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)" + r"?(?P<domain>(?:DC=[^,]+,?)+)$"
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$"
)
ULID_VERIFY = re.compile(r"^[0-9a-z]{26}$", re.IGNORECASE)
UUID_VERIFY = re.compile(
("[a-f0-9]{8}-" + "[a-f0-9]{4}-" + "[1-5]" + "[a-f0-9]{3}-" + "[89ab][a-f0-9]{3}-" + "[a-f0-9]{12}$"),
r"[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,
)
@ -36,9 +40,8 @@ invites = re.compile(
UUID_GET = {3: uuidpy.uuid3, 5: uuidpy.uuid5}
def hash_obj(hash, data, text: bool = True) -> str:
"""
Hash data with hash object
def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str:
"""Hash data with hash object.
Data can be text or binary
"""
@ -48,14 +51,16 @@ def hash_obj(hash, data, text: bool = True) -> str:
BSIZE = 65536
block_idx = 0
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)
block_idx += 1
return hash.hexdigest()
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
@cog_ext.cog_slash(
@ -78,7 +83,7 @@ class DevCog(commands.Cog):
],
)
@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:
await ctx.send(
"No data to hash",
@ -119,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)
if version in [3, 5] and not data:
await ctx.send(f"UUID{version} requires data.", hidden=True)
@ -143,8 +148,7 @@ class DevCog(commands.Cog):
description="Generate an ObjectID",
)
@commands.cooldown(1, 2, commands.BucketType.user)
async def _objectid(self, ctx):
"""Generates new bson.ObjectId"""
async def _objectid(self, ctx: SlashContext) -> None:
await ctx.send(f"ObjectId: `{str(ObjectId())}`")
@cog_ext.cog_slash(
@ -152,8 +156,7 @@ class DevCog(commands.Cog):
description="Generate a ULID",
)
@commands.cooldown(1, 2, commands.BucketType.user)
async def _ulid(self, ctx):
"""Generates a new ULID"""
async def _ulid(self, ctx: SlashContext) -> None:
await ctx.send(f"ULID: `{ulidpy.new().str}`")
@cog_ext.cog_slash(
@ -161,8 +164,7 @@ class DevCog(commands.Cog):
description="Convert a UUID to a ULID",
)
@commands.cooldown(1, 2, commands.BucketType.user)
async def _uuid2ulid(self, ctx: commands.Context, uuid):
"""Converts a UUID to a ULID"""
async def _uuid2ulid(self, ctx: SlashContext, uuid: str) -> None:
if UUID_VERIFY.match(uuid):
u = ulidpy.parse(uuid)
await ctx.send(f"ULID: `{u.str}`")
@ -174,8 +176,7 @@ class DevCog(commands.Cog):
description="Convert a ULID to a UUID",
)
@commands.cooldown(1, 2, commands.BucketType.user)
async def _ulid2uuid(self, ctx: commands.Context, ulid):
"""Converts a ULID to a UUID"""
async def _ulid2uuid(self, ctx: SlashContext, ulid: str) -> None:
if ULID_VERIFY.match(ulid):
ulid = ulidpy.parse(ulid)
await ctx.send(f"UUID: `{ulid.uuid}`")
@ -203,8 +204,7 @@ class DevCog(commands.Cog):
),
],
)
async def _encode(self, ctx, method: str, data: str):
"""Encodes text with specified encoding method"""
async def _encode(self, ctx: SlashContext, method: str, data: str) -> None:
method = getattr(base64, method + "encode")
await ctx.send(f"`{method(data.encode('UTF-8')).decode('UTF-8')}`")
@ -227,8 +227,7 @@ class DevCog(commands.Cog):
),
],
)
async def _decode(self, ctx, method: str, data: str):
"""Decodes text with specified encoding method"""
async def _decode(self, ctx: SlashContext, method: str, data: str) -> None:
method = getattr(base64, method + "decode")
decoded = method(data.encode("UTF-8")).decode("UTF-8")
if invites.search(decoded):
@ -244,10 +243,11 @@ class DevCog(commands.Cog):
description="Get J.A.R.V.I.S. lines of code",
)
@commands.cooldown(1, 30, commands.BucketType.channel)
async def _cloc(self, ctx):
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8")
async def _cloc(self, ctx: SlashContext) -> None:
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8") # noqa: S603, S607
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))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. error handling cog."""
from discord.ext import commands
from discord_slash import SlashContext
@ -5,11 +6,14 @@ from jarvis import slash
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
@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):
await ctx.send("I'm afraid I can't let you do that.")
elif isinstance(error, commands.errors.CommandNotFound):
@ -23,7 +27,8 @@ class ErrorHandlerCog(commands.Cog):
ctx.command.reset_cooldown(ctx)
@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:
"""discord_slash on_slash_command_error override."""
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)
elif isinstance(error, commands.errors.CommandNotFound):
@ -41,5 +46,6 @@ class ErrorHandlerCog(commands.Cog):
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))

View file

@ -1,11 +1,11 @@
"""J.A.R.V.I.S. GitLab Cog."""
from datetime import datetime
from datetime import timedelta
import gitlab
from ButtonPaginator import Paginator
from discord import Embed
from discord.ext import commands
from discord.ext.tasks import loop
from discord.utils import find
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.model import ButtonStyle
@ -21,7 +21,9 @@ guild_ids = [862402786116763668]
class GitlabCog(CacheCog):
def __init__(self, bot):
"""J.A.R.V.I.S. GitLab Cog."""
def __init__(self, bot: commands.Bot):
super().__init__(bot)
config = get_config()
self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token)
@ -35,7 +37,7 @@ class GitlabCog(CacheCog):
guild_ids=guild_ids,
options=[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:
issue = self.project.issues.get(int(id))
except gitlab.exceptions.GitlabGetError:
@ -69,7 +71,7 @@ class GitlabCog(CacheCog):
fields.append(
Field(
name="Milestone",
value=f"[{issue.milestone['title']}]" + f"({issue.milestone['web_url']})",
value=f"[{issue.milestone['title']}]({issue.milestone['web_url']})",
inline=False,
)
)
@ -87,7 +89,7 @@ class GitlabCog(CacheCog):
icon_url=issue.author["avatar_url"],
url=issue.author["web_url"],
)
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)
@cog_ext.cog_subcommand(
@ -104,7 +106,7 @@ class GitlabCog(CacheCog):
)
],
)
async def _milestone(self, ctx: SlashContext, id: int):
async def _milestone(self, ctx: SlashContext, id: int) -> None:
try:
milestone = self.project.milestones.get(int(id))
except gitlab.exceptions.GitlabGetError:
@ -142,9 +144,9 @@ class GitlabCog(CacheCog):
embed.set_author(
name="J.A.R.V.I.S.",
url="https://git.zevaryx.com/jarvis",
icon_url="https://git.zevaryx.com/uploads/-" + "/system/user/avatar/11/avatar.png",
icon_url="https://git.zevaryx.com/uploads/-/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)
@cog_ext.cog_subcommand(
@ -161,7 +163,7 @@ class GitlabCog(CacheCog):
)
],
)
async def _mergerequest(self, ctx: SlashContext, id: int):
async def _mergerequest(self, ctx: SlashContext, id: int) -> None:
try:
mr = self.project.mergerequests.get(int(id))
except gitlab.exceptions.GitlabGetError:
@ -201,7 +203,7 @@ class GitlabCog(CacheCog):
fields.append(
Field(
name="Milestone",
value=f"[{mr.milestone['title']}]" + f"({mr.milestone['web_url']})",
value=f"[{mr.milestone['title']}]({mr.milestone['web_url']})",
inline=False,
)
)
@ -219,10 +221,11 @@ class GitlabCog(CacheCog):
icon_url=mr.author["avatar_url"],
url=mr.author["web_url"],
)
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)
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 = ""
if t_state:
title = f"{t_state} "
@ -241,14 +244,14 @@ class GitlabCog(CacheCog):
title=title,
description="",
fields=fields,
url="https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./" + f"{name.replace(' ', '_')}s",
url=f"https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./{name.replace(' ', '_')}s",
)
embed.set_author(
name="J.A.R.V.I.S.",
url="https://git.zevaryx.com/jarvis",
icon_url="https://git.zevaryx.com/uploads/-" + "/system/user/avatar/11/avatar.png",
icon_url="https://git.zevaryx.com/uploads/-/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
@cog_ext.cog_subcommand(
@ -270,7 +273,7 @@ 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)
if exists:
await ctx.defer(hidden=True)
@ -311,7 +314,7 @@ class GitlabCog(CacheCog):
pages = []
t_state = t_state[0].upper() + t_state[1:]
for i in range(0, len(issues), 5):
pages.append(self.build_embed_page(issues[i : i + 5], t_state=t_state, name="issue"))
pages.append(self.build_embed_page(issues[i : i + 5], t_state=t_state, name="issue")) # noqa: E203
paginator = Paginator(
bot=self.bot,
@ -357,7 +360,7 @@ 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)
if exists:
await ctx.defer(hidden=True)
@ -398,7 +401,7 @@ class GitlabCog(CacheCog):
pages = []
t_state = t_state[0].upper() + t_state[1:]
for i in range(0, len(merges), 5):
pages.append(self.build_embed_page(merges[i : i + 5], t_state=t_state, name="merge request"))
pages.append(self.build_embed_page(merges[i : i + 5], t_state=t_state, name="merge request")) # noqa: E203
paginator = Paginator(
bot=self.bot,
@ -430,12 +433,12 @@ class GitlabCog(CacheCog):
description="Get open issues from GitLab",
guild_ids=guild_ids,
)
async def _milestones(self, ctx: SlashContext):
async def _milestones(self, ctx: SlashContext) -> None:
exists = self.check_cache(ctx)
if exists:
await ctx.defer(hidden=True)
await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True,
)
return
@ -463,7 +466,7 @@ class GitlabCog(CacheCog):
pages = []
for i in range(0, len(milestones), 5):
pages.append(self.build_embed_page(milestones[i : i + 5], t_state=None, name="milestone"))
pages.append(self.build_embed_page(milestones[i : i + 5], t_state=None, name="milestone")) # noqa: E203
paginator = Paginator(
bot=self.bot,
@ -489,6 +492,7 @@ class GitlabCog(CacheCog):
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:
bot.add_cog(GitlabCog(bot))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. image processing cog."""
import re
from io import BytesIO
@ -20,19 +21,22 @@ class ImageCog(commands.Cog):
May be categorized under util later
"""
def __init__(self, bot):
def __init__(self, bot: commands.Bot):
self.bot = bot
self._session = aiohttp.ClientSession()
self.tgt_match = re.compile(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:
await ctx.send("Missing target size, i.e. 200KB.")
return
tgt = self.tgt_match.match(target)
if not tgt:
await ctx.send("Invalid target format ({}).".format(target) + " Expected format like 200KB")
await ctx.send(f"Invalid target format ({target}). Expected format like 200KB")
return
tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1])
@ -95,9 +99,10 @@ class ImageCog(commands.Cog):
@commands.command(name="resize", help="Resize an image")
@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)
def setup(bot):
def setup(bot: commands.Bot) -> None:
"""Add ImageCog to J.A.R.V.I.S."""
bot.add_cog(ImageCog(bot))

View file

@ -1,8 +1,9 @@
"""J.A.R.V.I.S. Jokes module."""
import html
import re
import traceback
from datetime import datetime
from random import randint
from secrets import randint
from discord.ext import commands
from discord_slash import cog_ext
@ -20,7 +21,7 @@ class JokeCog(commands.Cog):
May adapt over time to create jokes using machine learning
"""
def __init__(self, bot):
def __init__(self, bot: commands.Bot):
self.bot = bot
# TODO: Make this a command group with subcommands
@ -29,8 +30,8 @@ class JokeCog(commands.Cog):
description="Hear a joke",
)
@commands.cooldown(1, 10, commands.BucketType.channel)
async def _joke(self, ctx: SlashContext, id: str = None):
"""Get a joke from the database"""
async def _joke(self, ctx: SlashContext, id: str = None) -> None:
"""Get a joke from the database."""
try:
if randint(1, 100_000) == 5779 and id is None:
await ctx.send(f"<@{ctx.message.author.id}>")
@ -109,5 +110,6 @@ class JokeCog(commands.Cog):
# await ctx.send(f"**{result['title']}**\n\n{result['body']}")
def setup(bot):
def setup(bot: commands.Bot) -> None:
"""Add JokeCog to J.A.R.V.I.S."""
bot.add_cog(JokeCog(bot))

View file

@ -1,9 +1,13 @@
"""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(member.ModlogMemberCog(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.ext import commands
from discord_slash import SlashContext
@ -8,11 +9,14 @@ from jarvis.utils.field import Field
class ModlogCommandCog(commands.Cog):
def __init__(self, bot):
"""J.A.R.V.I.S. ModlogCommandCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@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"]:
modlog = Setting.objects(guild=ctx.guild.id, setting="modlog").first()
if modlog:
@ -41,5 +45,5 @@ class ModlogCommandCog(commands.Cog):
name=ctx.author.name,
icon_url=ctx.author.avatar_url,
)
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator}" + f" | {ctx.author.id}")
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
await channel.send(embed=embed)

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. ModlogMemberCog."""
import asyncio
from datetime import datetime
from datetime import timedelta
@ -19,12 +20,15 @@ from jarvis.utils.field import Field
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.cache = []
@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()
if modlog:
channel = guild.get_channel(modlog.value)
@ -61,7 +65,8 @@ class ModlogMemberCog(commands.Cog):
await channel.send(embed=embed)
@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()
if modlog:
channel = guild.get_channel(modlog.value)
@ -96,7 +101,8 @@ class ModlogMemberCog(commands.Cog):
await channel.send(embed=embed)
@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()
if modlog:
channel = user.guild.get_channel(modlog.value)
@ -140,7 +146,8 @@ class ModlogMemberCog(commands.Cog):
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
auditlog = await before.guild.audit_logs(
limit=50,
@ -171,7 +178,8 @@ class ModlogMemberCog(commands.Cog):
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
auditlog = await before.guild.audit_logs(
limit=50,
@ -202,7 +210,8 @@ class ModlogMemberCog(commands.Cog):
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
auditlog = await before.guild.audit_logs(
limit=50,
@ -220,7 +229,8 @@ class ModlogMemberCog(commands.Cog):
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
auditlog = await before.guild.audit_logs(
limit=50,
@ -249,7 +259,11 @@ class ModlogMemberCog(commands.Cog):
)
@commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member):
async def on_member_update(self, before: discord.Member, after: discord.Member) -> None:
"""Process on_member_update events.
Caches events due to double-send bug
"""
h = hash(hash(before) * hash(after))
if h not in self.cache:
self.cache.append(h)
@ -287,18 +301,18 @@ class ModlogMemberCog(commands.Cog):
fields = [
Field(
name="Before",
value=f"{bname} ({before.name}" + f"#{before.discriminator})",
value=f"{bname} ({before.name}#{before.discriminator})",
),
Field(
name="After",
value=f"{aname} ({after.name}" + f"#{after.discriminator})",
value=f"{aname} ({after.name}#{after.discriminator})",
),
]
if log.user.id != before.id:
fields.append(
Field(
name="Moderator",
value=f"{log.user.mention} ({log.user.name}" + f"#{log.user.discriminator})",
value=f"{log.user.mention} ({log.user.name}#{log.user.discriminator})",
)
)
if log.reason:

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. ModlogMessageCog."""
import discord
from discord.ext import commands
@ -7,11 +8,14 @@ from jarvis.utils.field import Field
class ModlogMessageCog(commands.Cog):
def __init__(self, bot):
"""J.A.R.V.I.S. ModlogMessageCog."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.Cog.listener()
async def on_message_edit(self, before: discord.Message, after: discord.Message):
async def on_message_edit(self, before: discord.Message, after: discord.Message) -> None:
"""Process on_message_edit events."""
if not before.author.bot:
modlog = Setting.objects(guild=after.guild.id, setting="modlog").first()
if modlog:
@ -43,11 +47,12 @@ class ModlogMessageCog(commands.Cog):
icon_url=before.author.avatar_url,
url=after.jump_url,
)
embed.set_footer(text=f"{before.author.name}#{before.author.discriminator}" + f" | {before.author.id}")
embed.set_footer(text=f"{before.author.name}#{before.author.discriminator} | {before.author.id}")
await channel.send(embed=embed)
@commands.Cog.listener()
async def on_message_delete(self, message: discord.Message):
async def on_message_delete(self, message: discord.Message) -> None:
"""Process on_message_delete events."""
modlog = Setting.objects(guild=message.guild.id, setting="modlog").first()
if modlog:
fields = [Field("Original Message", message.content or "N/A", False)]
@ -95,5 +100,5 @@ class ModlogMessageCog(commands.Cog):
icon_url=message.author.avatar_url,
url=message.jump_url,
)
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator}" + f" | {message.author.id}")
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
await channel.send(embed=embed)

View file

@ -1,7 +1,11 @@
"""J.A.R.V.I.S. Modlog Cog Utilities."""
from datetime import datetime
from datetime import timedelta
from typing import List
import discord
from discord import AuditLogEntry
from discord import Member
from discord.utils import find
from jarvis.utils import build_embed
@ -15,10 +19,11 @@ def modlog_embed(
title: str,
desc: str,
) -> discord.Embed:
"""Get modlog embed."""
fields = [
Field(
name="Moderator",
value=f"{admin.mention} ({admin.name}" + f"#{admin.discriminator})",
value=f"{admin.mention} ({admin.name}#{admin.discriminator})",
),
]
if log.reason:
@ -38,7 +43,8 @@ def modlog_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)
return find(
lambda x: x.target.id == target.id and x.created_at > before,

View file

@ -1,8 +1,10 @@
"""J.A.R.V.I.S. Owner Cog."""
import os
import sys
import traceback
from inspect import getsource
from time import time
from typing import Any
import discord
from discord import DMChannel
@ -18,18 +20,18 @@ from jarvis.utils.permissions import user_is_bot_admin
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
"""
def __init__(self, bot):
def __init__(self, bot: commands.Cog):
self.bot = bot
self.admins = Config.objects(key="admins").first()
@commands.command(name="load", hidden=True)
@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()
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
try:
@ -47,7 +49,7 @@ class OwnerCog(commands.Cog):
@commands.command(name="unload", hidden=True)
@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"]:
await ctx.send("Cannot unload `owner` cog")
return
@ -68,7 +70,7 @@ class OwnerCog(commands.Cog):
@commands.command(name="reload", hidden=True)
@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"]:
await ctx.send("Cannot reload `owner` cog")
return
@ -91,13 +93,13 @@ class OwnerCog(commands.Cog):
@commands.group(name="system", hidden=True, pass_context=True)
@user_is_bot_admin()
async def _system(self, ctx):
async def _system(self, ctx: commands.Context) -> None:
if ctx.invoked_subcommand is None:
await ctx.send("Usage: `system <subcommand>`\n" + "Subcommands: `restart`, `update`")
@_system.command(name="restart", hidden=True)
@user_is_bot_admin()
async def _restart(self, ctx):
async def _restart(self, ctx: commands.Context) -> None:
info = await self.bot.application_info()
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
await ctx.send("Restarting core systems...")
@ -117,7 +119,7 @@ class OwnerCog(commands.Cog):
@_system.command(name="update", hidden=True)
@user_is_bot_admin()
async def _update(self, ctx):
async def _update(self, ctx: commands.Context) -> None:
info = await self.bot.application_info()
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
await ctx.send("Updating core systems...")
@ -144,19 +146,19 @@ class OwnerCog(commands.Cog):
@_system.command(name="refresh", hidden=True)
@user_is_bot_admin()
async def _refresh(self, ctx):
async def _refresh(self, ctx: commands.Context) -> None:
reload_config()
await ctx.send("System refreshed")
@commands.group(name="admin", hidden=True, pass_context=True)
@commands.is_owner()
async def _admin(self, ctx):
async def _admin(self, ctx: commands.Context) -> None:
if ctx.invoked_subcommand is None:
await ctx.send("Usage: `admin <subcommand>`\n" + "Subcommands: `add`, `remove`")
@_admin.command(name="add", hidden=True)
@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:
await ctx.send(f"{user.mention} is already an admin.")
return
@ -167,7 +169,7 @@ class OwnerCog(commands.Cog):
@_admin.command(name="remove", hidden=True)
@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:
await ctx.send(f"{user.mention} is not an admin.")
return
@ -176,7 +178,8 @@ class OwnerCog(commands.Cog):
reload_config()
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__"):
var_length = len(list(variable))
if (var_length > 100) and (not isinstance(variable, str)):
@ -192,7 +195,8 @@ class OwnerCog(commands.Cog):
else f"<a long {type(variable).__name__} object " + f"with the length of {len(f'{variable}'):,}>"
)
def prepare(self, string):
def prepare(self, string: str) -> str:
"""Prepare string for eval."""
arr = string.strip("```").replace("py\n", "").replace("python\n", "").split("\n")
if not arr[::-1][0].replace(" ", "").startswith("return"):
arr[len(arr) - 1] = "return " + arr[::-1][0]
@ -200,7 +204,7 @@ class OwnerCog(commands.Cog):
@commands.command(pass_context=True, aliases=["eval", "exec", "evaluate"])
@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):
return
code = self.prepare(code)
@ -215,13 +219,13 @@ class OwnerCog(commands.Cog):
}
try:
exec(
exec( # noqa: S102
f"async def func():{code}",
globals().update(args),
locals(),
)
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):
del args, code
return
@ -230,8 +234,7 @@ class OwnerCog(commands.Cog):
response = response.replace("`", "")
await ctx.send(
f"```py\n{self.resolve_variable(response)}```"
+ f"`{type(response).__name__} | {(time() - a) / 1000} ms`"
f"```py\n{self.resolve_variable(response)}```\n`{type(response).__name__} | {(time() - a) / 1000} ms`"
)
except Exception:
await ctx.send(f"Error occurred:```\n{traceback.format_exc()}```")
@ -239,5 +242,6 @@ class OwnerCog(commands.Cog):
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))

View file

@ -1,13 +1,15 @@
"""J.A.R.V.I.S. Remind Me Cog."""
import asyncio
import re
from datetime import datetime
from datetime import timedelta
from typing import List
from typing import Optional
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.utils import find
from discord_slash import cog_ext
from discord_slash import SlashContext
from discord_slash.utils.manage_commands import create_option
@ -29,7 +31,9 @@ invites = re.compile(
class RemindmeCog(CacheCog):
def __init__(self, bot):
"""J.A.R.V.I.S. Remind Me Cog."""
def __init__(self, bot: Bot):
super().__init__(bot)
self._remind.start()
@ -77,7 +81,7 @@ class RemindmeCog(CacheCog):
days: Optional[int] = 0,
hours: Optional[int] = 0,
minutes: Optional[int] = 0,
):
) -> None:
if len(message) > 100:
await ctx.send("Reminder cannot be > 100 characters.", hidden=True)
return
@ -120,7 +124,7 @@ class RemindmeCog(CacheCog):
if reminders >= 5:
await ctx.send(
"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,
)
return
@ -162,7 +166,8 @@ class RemindmeCog(CacheCog):
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 = []
for reminder in reminders:
fields.append(
@ -175,7 +180,7 @@ class RemindmeCog(CacheCog):
embed = build_embed(
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,
)
@ -192,12 +197,12 @@ class RemindmeCog(CacheCog):
name="list",
description="List reminders for a user",
)
async def _list(self, ctx: SlashContext):
async def _list(self, ctx: SlashContext) -> None:
exists = self.check_cache(ctx)
if exists:
await ctx.defer(hidden=True)
await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True,
)
return
@ -215,7 +220,7 @@ class RemindmeCog(CacheCog):
name="delete",
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)
if not reminders:
await ctx.send("You have no reminders set", hidden=True)
@ -293,7 +298,7 @@ class RemindmeCog(CacheCog):
await message.edit(components=components)
@loop(seconds=15)
async def _remind(self):
async def _remind(self) -> None:
reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))
for reminder in reminders:
if reminder.remind_at <= datetime.utcnow():
@ -313,7 +318,7 @@ class RemindmeCog(CacheCog):
embed.set_thumbnail(url=user.avatar_url)
try:
await user.send(embed=embed)
except:
except Exception:
guild = self.bot.fetch_guild(reminder.guild)
channel = guild.get_channel(reminder.channel) if guild else None
if channel:
@ -322,5 +327,6 @@ class RemindmeCog(CacheCog):
reminder.delete()
def setup(bot):
def setup(bot: Bot) -> None:
"""Add RemindmeCog to J.A.R.V.I.S."""
bot.add_cog(RemindmeCog(bot))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. Role Giver Cog."""
from discord import Role
from discord.ext import commands
from discord_slash import cog_ext
@ -11,7 +12,9 @@ from jarvis.utils.permissions import admin_or_permissions
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
@cog_ext.cog_subcommand(
@ -28,7 +31,7 @@ class RolegiverCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, setting="rolegiver").first()
if setting and role.id in setting.value:
await ctx.send("Role already in rolegiver", hidden=True)
@ -64,7 +67,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url,
)
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} " + f"| {ctx.author.id}")
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
await ctx.send(embed=embed)
@ -82,7 +85,7 @@ class RolegiverCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, setting="rolegiver").first()
if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True)
@ -120,7 +123,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url,
)
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} " + f"| {ctx.author.id}")
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
await ctx.send(embed=embed)
@ -129,7 +132,7 @@ class RolegiverCog(commands.Cog):
name="list",
description="List roles rolegiver",
)
async def _rolegiver_list(self, ctx: SlashContext):
async def _rolegiver_list(self, ctx: SlashContext) -> None:
setting = Setting.objects(guild=ctx.guild.id, setting="rolegiver").first()
if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True)
@ -157,7 +160,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url,
)
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} " + f"| {ctx.author.id}")
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
await ctx.send(embed=embed)
@ -175,7 +178,7 @@ class RolegiverCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, setting="rolegiver").first()
if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True)
@ -212,7 +215,7 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url,
)
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} " + f"| {ctx.author.id}")
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
await ctx.send(embed=embed)
@ -230,7 +233,7 @@ class RolegiverCog(commands.Cog):
],
)
@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(guild=ctx.guild.id, setting="rolegiver").first()
if not setting or (setting and not setting.value):
await ctx.send("Rolegiver has no roles", hidden=True)
@ -267,10 +270,11 @@ class RolegiverCog(commands.Cog):
icon_url=ctx.author.avatar_url,
)
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} " + f"| {ctx.author.id}")
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
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))

View file

@ -1,3 +1,6 @@
"""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
@ -13,10 +16,13 @@ from jarvis.utils.permissions import admin_or_permissions
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
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()
if not setting:
setting = Setting(setting=setting, guild=guild, value=value)
@ -24,7 +30,8 @@ class SettingsCog(commands.Cog):
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()
@cog_ext.cog_subcommand(
@ -44,7 +51,7 @@ class SettingsCog(commands.Cog):
],
)
@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()
self.update_settings("mute", role.id, ctx.guild.id)
await ctx.send(f"Settings applied. New mute role is `{role.name}`")
@ -64,7 +71,7 @@ class SettingsCog(commands.Cog):
],
)
@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):
await ctx.send("Channel must be a TextChannel", hidden=True)
return
@ -86,7 +93,7 @@ class SettingsCog(commands.Cog):
],
)
@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):
await ctx.send("Channel must be a TextChannel", hidden=True)
return
@ -108,7 +115,7 @@ class SettingsCog(commands.Cog):
],
)
@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()
self.update_settings("massmention", amount, ctx.guild.id)
await ctx.send(f"Settings applied. New massmention limit is {amount}")
@ -128,7 +135,7 @@ class SettingsCog(commands.Cog):
],
)
@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()
self.update_settings("verified", role.id, ctx.guild.id)
await ctx.send(f"Settings applied. New verified role is `{role.name}`")
@ -148,7 +155,7 @@ class SettingsCog(commands.Cog):
],
)
@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()
self.update_settings("unverified", role.id, ctx.guild.id)
await ctx.send(f"Settings applied. New unverified role is `{role.name}`")
@ -161,7 +168,7 @@ class SettingsCog(commands.Cog):
description="Unset mute role",
)
@admin_or_permissions(manage_guild=True)
async def _unset_mute(self, ctx):
async def _unset_mute(self, ctx: SlashContext) -> None:
await ctx.defer()
self.delete_settings("mute", ctx.guild.id)
await ctx.send("Setting removed.")
@ -173,7 +180,7 @@ class SettingsCog(commands.Cog):
description="Unset modlog channel",
)
@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)
await ctx.send("Setting removed.")
@ -184,7 +191,7 @@ class SettingsCog(commands.Cog):
description="Unset userlog channel",
)
@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)
await ctx.send("Setting removed.")
@ -195,7 +202,7 @@ class SettingsCog(commands.Cog):
description="Unet massmention amount",
)
@admin_or_permissions(manage_guild=True)
async def _massmention(self, ctx):
async def _massmention(self, ctx: SlashContext) -> None:
await ctx.defer()
self.delete_settings("massmention", ctx.guild.id)
await ctx.send("Setting removed.")
@ -207,7 +214,7 @@ class SettingsCog(commands.Cog):
description="Unset verified role",
)
@admin_or_permissions(manage_guild=True)
async def _verified(self, ctx):
async def _verified(self, ctx: SlashContext) -> None:
await ctx.defer()
self.delete_settings("verified", ctx.guild.id)
await ctx.send("Setting removed.")
@ -219,14 +226,14 @@ class SettingsCog(commands.Cog):
description="Unset unverified role",
)
@admin_or_permissions(manage_guild=True)
async def _unverified(self, ctx):
async def _unverified(self, ctx: SlashContext) -> None:
await ctx.defer()
self.delete_settings("unverified", ctx.guild.id)
await ctx.send("Setting removed.")
@cog_ext.cog_subcommand(base="settings", name="view", description="View settings")
@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)
fields = []
@ -260,10 +267,11 @@ class SettingsCog(commands.Cog):
@cog_ext.cog_subcommand(base="settings", name="clear", description="Clear all settings")
@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()
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))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. Starboard Cog."""
from discord import TextChannel
from discord.ext import commands
from discord.utils import find
@ -24,7 +25,9 @@ supported_images = [
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
@cog_ext.cog_subcommand(
@ -33,7 +36,7 @@ class StarboardCog(commands.Cog):
description="Lists all Starboards",
)
@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)
if starboards != []:
message = "Available Starboards:\n"
@ -57,7 +60,7 @@ class StarboardCog(commands.Cog):
],
)
@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:
await ctx.send(
"Channel not in guild. Choose an existing channel.",
@ -99,7 +102,7 @@ class StarboardCog(commands.Cog):
],
)
@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(channel=channel.id, guild=ctx.guild.id).delete()
if deleted:
_ = Star.objects(starboard=channel.id).delete()
@ -120,7 +123,7 @@ class StarboardCog(commands.Cog):
),
create_option(
name="channel",
description="Channel that has the message, " + "required if different than command message",
description="Channel that has the message, required if different than command message",
option_type=7,
required=False,
),
@ -132,7 +135,7 @@ class StarboardCog(commands.Cog):
ctx: SlashContext,
message: str,
channel: TextChannel = None,
):
) -> None:
if not channel:
channel = ctx.channel
starboards = Starboard.objects(guild=ctx.guild.id)
@ -230,7 +233,7 @@ class StarboardCog(commands.Cog):
components[0]["components"][0]["disabled"] = True
await com_ctx.edit_origin(
content="Message saved to Starboard.\n" + f"See it in {starboard.mention}",
content=f"Message saved to Starboard.\nSee it in {starboard.mention}",
components=components,
)
@ -259,14 +262,14 @@ class StarboardCog(commands.Cog):
ctx: SlashContext,
id: int,
starboard: TextChannel,
):
) -> None:
if not isinstance(starboard, TextChannel):
await ctx.send("Channel must be a TextChannel", hidden=True)
return
exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first()
if not exists:
await ctx.send(
f"Starboard does not exist in {starboard.mention}. " + "Please create it first",
f"Starboard does not exist in {starboard.mention}. Please create it first",
hidden=True,
)
return
@ -291,5 +294,6 @@ class StarboardCog(commands.Cog):
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))

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. Utility Cog."""
import re
import secrets
import string
@ -35,7 +36,7 @@ class UtilCog(commands.Cog):
Mostly system utility functions, but may change over time
"""
def __init__(self, bot):
def __init__(self, bot: commands.Cog):
self.bot = bot
self.config = get_config()
@ -44,7 +45,7 @@ class UtilCog(commands.Cog):
description="Retrieve J.A.R.V.I.S. status",
)
@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"
desc = "All systems online"
color = "#98CCDA"
@ -68,12 +69,12 @@ class UtilCog(commands.Cog):
description="Get the current logo",
)
@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)
await ctx.send(f"```\n{lo}\n```")
@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)
@cog_ext.cog_slash(
@ -88,7 +89,7 @@ class UtilCog(commands.Cog):
)
],
)
async def _rcauto(self, ctx: SlashContext, text: str):
async def _rcauto(self, ctx: SlashContext, text: str) -> None:
to_send = ""
if len(text) == 1 and not re.match(r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()):
await ctx.send("Please use ASCII characters.", hidden=True)
@ -120,7 +121,7 @@ class UtilCog(commands.Cog):
],
)
@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:
user = ctx.author
@ -142,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 = [
Field(name="ID", value=role.id),
@ -193,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:
user = ctx.author
user_roles = user.roles
@ -230,7 +231,7 @@ class UtilCog(commands.Cog):
await ctx.send(embed=embed)
@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
owner = f"{guild.owner.name}#{guild.owner.discriminator}" if guild.owner else "||`[redacted]`||"
@ -291,7 +292,7 @@ class UtilCog(commands.Cog):
],
)
@commands.cooldown(1, 15, type=commands.BucketType.user)
async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3):
async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3) -> None:
if length > 256:
await ctx.send("Please limit password to 256 characters", hidden=True)
return
@ -305,11 +306,12 @@ class UtilCog(commands.Cog):
pw = "".join(secrets.choice(choices[chars]) for i in range(length))
await ctx.send(
f"Generated password:\n`{pw}`\n\n"
+ '**WARNING: Once you press "Dismiss Message", '
+ "*the password is lost forever***",
'**WARNING: Once you press "Dismiss Message", '
"*the password is lost forever***",
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))

View file

@ -1,4 +1,5 @@
from random import randint
"""J.A.R.V.I.S. Verify Cog."""
from secrets import randint
from discord.ext import commands
from discord_slash import cog_ext
@ -10,7 +11,8 @@ from discord_slash.utils import manage_components
from jarvis.db.models import Setting
def create_layout():
def create_layout() -> list:
"""Create verify component layout."""
buttons = []
yes = randint(0, 2)
for i in range(3):
@ -29,7 +31,9 @@ def create_layout():
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
@cog_ext.cog_slash(
@ -37,7 +41,7 @@ class VerifyCog(commands.Cog):
description="Verify that you've read the rules",
)
@commands.cooldown(1, 15, commands.BucketType.user)
async def _verify(self, ctx: SlashContext):
async def _verify(self, ctx: SlashContext) -> None:
await ctx.defer()
role = Setting.objects(guild=ctx.guild.id, setting="verified").first()
if not role:
@ -48,13 +52,13 @@ class VerifyCog(commands.Cog):
return
components = create_layout()
message = await ctx.send(
content=f"{ctx.author.mention}, " + "please press the button that says `YES`.",
content=f"{ctx.author.mention}, please press the button that says `YES`.",
components=components,
)
await message.delete(delay=15)
@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)
try:
if ctx.author.id != ctx.origin_message.mentions[0].id:
@ -75,15 +79,16 @@ class VerifyCog(commands.Cog):
role = ctx.guild.get_role(setting.value)
await ctx.author.remove_roles(role, reason="Verification passed")
await ctx.edit_origin(
content=f"Welcome, {ctx.author.mention}. " + "Please enjoy your stay.",
content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.",
components=manage_components.spread_to_rows(*components, max_in_row=5),
)
await ctx.origin_message.delete(delay=5)
else:
await ctx.edit_origin(
content=f"{ctx.author.mention}, incorrect. " + "Please press the button that says `YES`",
content=f"{ctx.author.mention}, incorrect. 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))

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. database object for mongoengine."""
from datetime import datetime
from mongoengine import Document
@ -12,10 +13,14 @@ from mongoengine.fields import StringField
class SnowflakeField(LongField):
"""Snowflake LongField Override."""
pass
class Autopurge(Document):
"""Autopurge database object."""
guild = SnowflakeField(required=True)
channel = SnowflakeField(required=True)
delay = IntField(min_value=1, max_value=300, default=30)
@ -26,6 +31,8 @@ class Autopurge(Document):
class Autoreact(Document):
"""Autoreact database object."""
guild = SnowflakeField(required=True)
channel = SnowflakeField(required=True)
reactions = ListField(field=StringField())
@ -36,6 +43,8 @@ class Autoreact(Document):
class Ban(Document):
"""Ban database object."""
active = BooleanField(default=True)
admin = SnowflakeField(required=True)
user = SnowflakeField(required=True)
@ -51,6 +60,8 @@ class Ban(Document):
class Config(Document):
"""Config database object."""
key = StringField(required=True)
value = DynamicField(required=True)
@ -58,6 +69,8 @@ class Config(Document):
class Guess(Document):
"""Guess database object."""
correct = BooleanField(default=False)
guess = StringField(max_length=800, required=True)
user = SnowflakeField(required=True)
@ -66,6 +79,8 @@ class Guess(Document):
class Joke(Document):
"""Joke database object."""
rid = StringField()
body = StringField()
title = StringField()
@ -77,6 +92,8 @@ class Joke(Document):
class Kick(Document):
"""Kick database object."""
admin = SnowflakeField(required=True)
guild = SnowflakeField(required=True)
reason = StringField(max_length=100, required=True)
@ -87,6 +104,8 @@ class Kick(Document):
class Lock(Document):
"""Lock database object."""
active = BooleanField(default=True)
admin = SnowflakeField(required=True)
channel = SnowflakeField(required=True)
@ -99,6 +118,8 @@ class Lock(Document):
class Mute(Document):
"""Mute database object."""
active = BooleanField(default=True)
user = SnowflakeField(required=True)
admin = SnowflakeField(required=True)
@ -111,6 +132,8 @@ class Mute(Document):
class Purge(Document):
"""Purge database object."""
admin = SnowflakeField(required=True)
channel = SnowflakeField(required=True)
guild = SnowflakeField(required=True)
@ -121,6 +144,8 @@ class Purge(Document):
class Reminder(Document):
"""Reminder database object."""
active = BooleanField(default=True)
user = SnowflakeField(required=True)
guild = SnowflakeField(required=True)
@ -133,6 +158,8 @@ class Reminder(Document):
class Roleping(Document):
"""Roleping database object."""
active = BooleanField(default=True)
role = SnowflakeField(required=True)
guild = SnowflakeField(required=True)
@ -144,6 +171,8 @@ class Roleping(Document):
class Setting(Document):
"""Setting database object."""
guild = SnowflakeField(required=True)
setting = StringField(required=True)
value = DynamicField()
@ -152,6 +181,8 @@ class Setting(Document):
class Star(Document):
"""Star database object."""
active = BooleanField(default=True)
index = IntField(required=True)
message = SnowflakeField(required=True)
@ -166,6 +197,8 @@ class Star(Document):
class Starboard(Document):
"""Starboard database object."""
channel = SnowflakeField(required=True)
guild = SnowflakeField(required=True)
admin = SnowflakeField(required=True)
@ -175,6 +208,8 @@ class Starboard(Document):
class Unban(Document):
"""Unban database object."""
user = SnowflakeField(required=True)
username = StringField(required=True)
discrim = IntField(min_value=1, max_value=9999, required=True)
@ -187,6 +222,8 @@ class Unban(Document):
class Warning(Document):
"""Warning database object."""
active = BooleanField(default=True)
admin = SnowflakeField(required=True)
user = SnowflakeField(required=True)

View file

@ -1,24 +1,30 @@
"""J.A.R.V.I.S. guild event handler."""
import asyncio
from discord import Guild
from discord.ext.commands import Bot
from discord.utils import find
from jarvis.db.models import Setting
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.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)
if general and general.permissions_for(guild.me).send_messages:
user = self.bot.user
await general.send(
f"Allow me to introduce myself. I am {user.mention}, a virtual "
+ "artificial intelligence, and I'm here to assist you with a "
+ "variety of tasks as best I can, "
+ "24 hours a day, seven days a week."
"artificial intelligence, and I'm here to assist you with a "
"variety of tasks as best I can, "
"24 hours a day, seven days a week."
)
await asyncio.sleep(1)
await general.send("Importing all preferences from home interface...")

View file

@ -1,15 +1,20 @@
"""J.A.R.V.I.S. Member event handler."""
from discord import Member
from discord.ext.commands import Bot
from jarvis.db.models import Mute
from jarvis.db.models import Setting
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.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
mute = Mute.objects(guild=guild.id, user=user.id, active=True).first()
if mute:

View file

@ -1,7 +1,9 @@
"""J.A.R.V.I.S. Message event handler."""
import re
from discord import DMChannel
from discord import Message
from discord.ext.commands import Bot
from discord.utils import find
from jarvis.config import get_config
@ -20,17 +22,21 @@ invites = re.compile(
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.add_listener(self.on_message)
self.bot.add_listener(self.on_message_edit)
async def autopurge(self, message: Message):
async def autopurge(self, message: Message) -> None:
"""Handle autopurge events."""
autopurge = Autopurge.objects(guild=message.guild.id, channel=message.channel.id).first()
if autopurge:
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(
guild=message.guild.id,
channel=message.channel.id,
@ -39,12 +45,13 @@ class MessageEventHandler(object):
for reaction in autoreact.reactions:
await message.add_reaction(reaction)
async def checks(self, message: Message):
async def checks(self, message: Message) -> None:
"""Other message checks."""
# #tech
channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions)
if channel and message.author.id == 293795462752894976:
await channel.send(
content="https://cdn.discordapp.com/attachments/" + "664621130044407838/805218508866453554/tech.gif"
content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif"
)
content = re.sub(r"\s+", "", message.content)
match = invites.search(content)
@ -81,20 +88,20 @@ class MessageEventHandler(object):
name=message.author.nick if message.author.nick else message.author.name,
icon_url=message.author.avatar_url,
)
embed.set_footer(
text=f"{message.author.name}#" + f"{message.author.discriminator} " + f"| {message.author.id}"
)
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
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(
guild=message.guild.id,
setting="massmention",
).first()
if (
massmention
and massmention.value > 0
and len(message.mentions) - (1 if message.author in message.mentions else 0) > massmention.value
and massmention.value > 0 # noqa: W503
and len(message.mentions) - (1 if message.author in message.mentions else 0) # noqa: W503
> massmention.value # noqa: W503
):
_ = Warning(
active=True,
@ -114,10 +121,11 @@ class MessageEventHandler(object):
name=message.author.nick if message.author.nick else message.author.name,
icon_url=message.author.avatar_url,
)
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} " + f"| {message.author.id}")
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
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)
if not rolepings:
@ -181,10 +189,11 @@ class MessageEventHandler(object):
name=message.author.nick if message.author.nick else message.author.name,
icon_url=message.author.avatar_url,
)
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} " + f"| {message.author.id}")
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
await message.channel.send(embed=embed)
async def on_message(self, message: Message):
async def on_message(self, message: Message) -> None:
"""Handle on_message event. Calls other event handlers."""
if not isinstance(message.channel, DMChannel) and not message.author.bot:
await self.autoreact(message)
await self.massmention(message)
@ -192,8 +201,11 @@ class MessageEventHandler(object):
await self.autopurge(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:
await self.massmention(after)
await self.roleping(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"""
___ ___ ______ _ _ _____ _____
|_ | / _ \ | ___ \ | | | | |_ _| / ___|
@ -82,7 +84,7 @@ logo_alligator = r"""
#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+#
##### ### ### ### ### ### ### ### ### ### ########### ### ######## ###
"""
""" # noqa: E501
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:
lo = "logo_" + lo
return globals()[lo] if lo in globals() else logo_alligator2

View file

@ -1,10 +1,12 @@
"""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()
unlock.unlock.start()
unmute.unmute.start()

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. unban background task handler."""
from datetime import datetime
from datetime import timedelta
@ -12,7 +13,8 @@ jarvis_id = get_config().client_id
@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)
unbans = []
for ban in bans:

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. unlock background task handler."""
from datetime import datetime
from datetime import timedelta
@ -8,7 +9,8 @@ from jarvis.db.models import Lock
@loop(minutes=1)
async def unlock():
async def unlock() -> None:
"""J.A.R.V.I.S. unlock background task."""
locks = Lock.objects(active=True)
for lock in locks:
if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow():

View file

@ -1,3 +1,4 @@
"""J.A.R.V.I.S. unmute background task handler."""
from datetime import datetime
from datetime import timedelta
@ -9,7 +10,8 @@ from jarvis.db.models import Setting
@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)
mute_roles = Setting.objects(setting="mute")
for mute in mutes:

View file

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

View file

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

View file

@ -1,3 +1,4 @@
"""Cog wrapper for command caching."""
from datetime import datetime
from datetime import timedelta
@ -8,24 +9,27 @@ from discord_slash import SlashContext
class CacheCog(commands.Cog):
"""Cog wrapper for command caching."""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.cache = {}
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:
kwargs = {}
return find(
lambda x: x["command"] == ctx.subcommand_name
and x["user"] == ctx.author.id
and x["guild"] == ctx.guild.id
and all(x[k] == v for k, v in kwargs.items()),
lambda x: x["command"] == ctx.subcommand_name # noqa: W503
and x["user"] == ctx.author.id # noqa: W503
and x["guild"] == ctx.guild.id # noqa: W503
and all(x[k] == v for k, v in kwargs.items()), # noqa: W503
self.cache.values(),
)
@loop(minutes=1)
async def _expire_interaction(self):
async def _expire_interaction(self) -> None:
keys = list(self.cache.keys())
for key in keys:
if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1):

View file

@ -1,12 +1,16 @@
"""Embed field helper."""
from dataclasses import dataclass
from typing import Any
@dataclass
class Field:
"""Embed Field."""
name: Any
value: Any
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}

View file

@ -1,10 +1,14 @@
"""Permissions wrappers."""
from discord.ext import commands
from jarvis.config import get_config
def user_is_bot_admin():
def predicate(ctx):
def user_is_bot_admin() -> bool:
"""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):
return ctx.author.id in get_config().admins
else:
@ -13,10 +17,12 @@ def user_is_bot_admin():
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
async def extended_check(ctx):
async def extended_check(ctx: commands.Context) -> bool:
"""Extended check predicate.""" # noqa: D401
return await commands.has_permissions(administrator=True).predicate(ctx) or await original(ctx)
return commands.check(extended_check)

1
run.py
View file

@ -1,4 +1,5 @@
#!/bin/python3
# flake8: noqa
from importlib import reload as ireload
from multiprocessing import freeze_support
from multiprocessing import Process