[fix] Pre-commit formatting

This commit is contained in:
Zeva Rose 2024-03-17 00:00:42 -06:00
parent 9c2c937abc
commit cd6aa668a0
54 changed files with 448 additions and 984 deletions

View file

@ -1,5 +1,5 @@
<div align="center">
<img width=15% alt="JARVIS" src="https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/raw/main/jarvis_small.png">
<img width=15% alt="JARVIS" src="https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/raw/main/jarvis_small.png">
# Just Another Rather Very Intelligent System

View file

@ -1,4 +1,5 @@
"""Main JARVIS package."""
import logging
from functools import partial
from typing import Any
@ -26,6 +27,7 @@ def jlogger(logger: logging.Logger, event: Any) -> None:
Args:
logger: Logger to use
event: Event to parse
"""
jlog = partial(logger.log, 11)
if isinstance(event, jurigged.live.WatchOperation):
@ -61,21 +63,13 @@ async def run() -> None:
config = load_config()
logger = get_logger("jarvis", show_locals=False) # jconfig.log_level == "DEBUG")
logger.setLevel(config.log_level)
file_handler = logging.FileHandler(
filename="jarvis.log", encoding="UTF-8", mode="w"
)
file_handler.setFormatter(
logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s")
)
file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w")
file_handler.setFormatter(logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s"))
logger.addHandler(file_handler)
# Configure client
intents = (
Intents.DEFAULT
| Intents.MESSAGES
| Intents.GUILD_MEMBERS
| Intents.GUILD_MESSAGES
| Intents.MESSAGE_CONTENT
Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES | Intents.MESSAGE_CONTENT
)
redis_config = config.redis.model_dump()
redis_host = redis_config.pop("host")

View file

@ -1,4 +1,5 @@
"""JARVIS brandings."""
PRIMARY_COLOR = "#3498db"
HARD_ACTION = "#ff0000"
MODERATE_ACTION = "#ff9900"
@ -29,14 +30,14 @@ def get_command_color(command: str) -> str:
Returns:
Hex color string
"""
if command in COMMAND_TYPES["HARD"]:
return HARD_ACTION
elif command in COMMAND_TYPES["MODERATE"]:
if command in COMMAND_TYPES["MODERATE"]:
return MODERATE_ACTION
elif command in COMMAND_TYPES["SOFT"]:
if command in COMMAND_TYPES["SOFT"]:
return SOFT_ACTION
elif command in COMMAND_TYPES["GOOD"]:
if command in COMMAND_TYPES["GOOD"]:
return GOOD_ACTION
else:
return CUSTOM_COMMANDS.get(command, PRIMARY_COLOR)
return CUSTOM_COMMANDS.get(command, PRIMARY_COLOR)

View file

@ -1,4 +1,5 @@
"""Custom JARVIS client."""
import logging
from typing import TYPE_CHECKING
@ -21,9 +22,7 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD)
class Jarvis(StatipyClient, ErrorMixin, EventMixin, TaskMixin):
def __init__(
self, redis: "aioredis.Redis", erapi: str, *args, **kwargs
): # noqa: ANN002 ANN003
def __init__(self, redis: "aioredis.Redis", erapi: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.redis = redis
self.logger = logging.getLogger(__name__)

View file

@ -1,4 +1,5 @@
"""JARVIS error handling mixin."""
import traceback
from datetime import datetime, timezone
@ -40,26 +41,21 @@ class ErrorMixin:
error = event.error
if isinstance(error, HTTPException):
errors = error.search_for_message(error.errors)
out = (
f"HTTPException: {error.status}|{error.response.reason}: "
+ "\n".join(errors)
)
out = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
self.logger.error(out, exc_info=error)
else:
self.logger.error(f"Ignoring exception in {source}", exc_info=error)
async def on_command_error(
self, ctx: BaseContext, error: Exception, *args: list, **kwargs: dict
) -> None:
async def on_command_error(self, ctx: BaseContext, error: Exception, *args: list, **kwargs: dict) -> None:
"""NAFF on_command_error override."""
name = ctx.invoke_target
self.logger.debug(f"Handling error in {name}: {error}")
if isinstance(error, CommandOnCooldown):
await ctx.send(str(error), ephemeral=True)
return
elif isinstance(error, CommandCheckFailure):
return None
if isinstance(error, CommandCheckFailure):
await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
return
return None
guild = await self.fetch_guild(DEFAULT_GUILD)
channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL)
error_time = datetime.now(tz=timezone.utc).strftime("%d-%m-%Y %H:%M-%S.%f UTC")
@ -80,11 +76,7 @@ class ErrorMixin:
v = v[97] + "..."
arg_str += f" - {v}"
callback_args = "\n".join(f" - {i}" for i in args) if args else " None"
callback_kwargs = (
"\n".join(f" {k}: {v}" for k, v in kwargs.items())
if kwargs
else " None"
)
callback_kwargs = "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None"
full_message = ERROR_MSG.format(
guild_name=ctx.guild.name,
error_time=error_time,
@ -96,11 +88,7 @@ class ErrorMixin:
tb = traceback.format_exception(error)
if isinstance(error, HTTPException):
errors = error.search_for_message(error.errors)
tb[
-1
] = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(
errors
)
tb[-1] = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
error_message = "".join(traceback.format_exception(error))
if len(full_message + error_message) >= 1800:
error_message = "\n ".join(error_message.split("\n"))
@ -119,9 +107,7 @@ class ErrorMixin:
f"\n```yaml\n{full_message}\n```"
f"\nException:\n```py\n{error_message}\n```"
)
await ctx.send(
"Whoops! Encountered an error. The error has been logged.", ephemeral=True
)
await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True)
try:
await ctx.defer(ephemeral=True)
return await super().on_command_error(ctx, error, *args, **kwargs)

View file

@ -1,9 +1,10 @@
"""JARVIS event mixin."""
import asyncio
from aiohttp import ClientSession
from beanie.operators import Set
from interactions import listen, Intents
from interactions import listen
from interactions.ext.prefixed_commands.context import PrefixedContext
from interactions.models.discord.channel import DMChannel
from interactions.models.discord.embed import EmbedField
@ -31,22 +32,16 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
async def _sync_domains(self) -> None:
self.logger.debug("Loading phishing domains")
async with ClientSession(
headers={"X-Identity": "Discord: zevaryx#5779"}
) as session:
async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session:
response = await session.get("https://phish.sinking.yachts/v2/all")
response.raise_for_status()
self.phishing_domains = await response.json()
self.logger.info(
f"Protected from {len(self.phishing_domains)} phishing domains"
)
self.logger.info(f"Protected from {len(self.phishing_domains)} phishing domains")
@listen()
async def on_startup(self) -> None:
"""NAFF on_startup override. Prometheus info generated here."""
await StaticStat.find_one(
StaticStat.name == "jarvis_version", StaticStat.client_id == self.user.id
).upsert(
await StaticStat.find_one(StaticStat.name == "jarvis_version", StaticStat.client_id == self.user.id).upsert(
Set(
{
StaticStat.client_name: self.client_name,
@ -64,16 +59,14 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
if not self.synced:
await self._sync_domains()
self._update_domains.start()
asyncio.create_task(self._chunk_all())
chunk_task = asyncio.create_task(self._chunk_all())
self.synced = True
except Exception as e:
self.logger.error("Failed to load anti-phishing", exc_info=e)
self.logger.info("Logged in as {}".format(self.user)) # noqa: T001
self.logger.info(
"Connected to {} guild(s)".format(len(self.guilds))
) # noqa: T001
self.logger.info("Logged in as {}".format(self.user))
self.logger.info("Connected to {} guild(s)".format(len(self.guilds)))
self.logger.info("Current version: {}".format(const.__version__))
self.logger.info( # noqa: T001
self.logger.info(
"https://discord.com/api/oauth2/authorize?client_id="
"{}&permissions=8&scope=bot%20applications.commands".format(self.user.id)
)
@ -91,9 +84,7 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
)
global_context_menus = sum(
1
for _ in self.interaction_tree[cid]
if isinstance(self.interaction_tree[cid][_], ContextMenu)
1 for _ in self.interaction_tree[cid] if isinstance(self.interaction_tree[cid][_], ContextMenu)
)
else:
guild_base_commands += sum(
@ -102,22 +93,12 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
)
guild_context_menus += sum(
1
for _ in self.interaction_tree[cid]
if isinstance(self.interaction_tree[cid][_], ContextMenu)
1 for _ in self.interaction_tree[cid] if isinstance(self.interaction_tree[cid][_], ContextMenu)
)
self.logger.info(
"Loaded {:>3} global base slash commands".format(global_base_commands)
)
self.logger.info(
"Loaded {:>3} global context menus".format(global_context_menus)
)
self.logger.info(
"Loaded {:>3} guild base slash commands".format(guild_base_commands)
)
self.logger.info(
"Loaded {:>3} guild context menus".format(guild_context_menus)
)
self.logger.info("Loaded {:>3} global base slash commands".format(global_base_commands))
self.logger.info("Loaded {:>3} global context menus".format(global_context_menus))
self.logger.info("Loaded {:>3} guild base slash commands".format(guild_base_commands))
self.logger.info("Loaded {:>3} guild context menus".format(guild_context_menus))
except Exception:
self.logger.error("interaction_tree not found, try updating NAFF")
@ -126,18 +107,15 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
_ = await Reminder.find().to_list(None)
self.logger.debug("Updating ERAPI")
await self.erapi.update_async()
await asyncio.wait([chunk_task])
# Modlog
async def on_command(self, ctx: BaseContext) -> None:
"""NAFF on_command override."""
name = ctx.invoke_target
if not isinstance(ctx.channel, DMChannel) and name not in ["pw"]:
modlog = await Setting.find_one(
Setting.guild == ctx.guild.id, Setting.setting == "activitylog"
)
ignore = await Setting.find_one(
Setting.guild == ctx.guild.id, Setting.setting == "log_ignore"
)
modlog = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "activitylog")
ignore = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "log_ignore")
if modlog and (ignore and ctx.channel.id not in ignore.value):
channel = await ctx.guild.fetch_channel(modlog.value)
args = []
@ -169,16 +147,10 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
fields=fields,
color="#fc9e3f",
)
embed.set_author(
name=ctx.author.username, icon_url=ctx.author.display_avatar.url
)
embed.set_footer(
text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}"
)
embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url)
embed.set_footer(text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}")
if channel:
await channel.send(embeds=embed)
else:
self.logger.warning(
f"Activitylog channel no longer exists in {ctx.guild.name}, removing"
)
self.logger.warning(f"Activitylog channel no longer exists in {ctx.guild.name}, removing")
await modlog.delete()

View file

@ -1,4 +1,5 @@
"""JARVIS component event mixin."""
from beanie import PydanticObjectId
from interactions import listen
from interactions.api.events.internal import ButtonPressed
@ -84,11 +85,7 @@ class ComponentEventMixin:
for component in row.components:
component.disabled = True
await context.message.edit(components=context.message.components)
msg = (
"Cancelled"
if context.custom_id == "modcase|no"
else "Moderation case opened"
)
msg = "Cancelled" if context.custom_id == "modcase|no" else "Moderation case opened"
await context.send(msg)
await self.redis.delete(user_key)
await self.redis.delete(action_key)
@ -114,9 +111,7 @@ class ComponentEventMixin:
await context.send("I'm afraid I can't let you do that", ephemeral=True)
return True # User does not have perms to delete
if pin := await Pin.find_one(
Pin.pin == context.message.id, Pin.guild == context.guild.id
):
if pin := await Pin.find_one(Pin.pin == context.message.id, Pin.guild == context.guild.id):
await pin.delete()
await context.message.delete()
@ -139,12 +134,8 @@ class ComponentEventMixin:
if await Reminder.find_one(
Reminder.parent == str(reminder.id),
Reminder.user == context.author.id,
) or await Reminder.find_one(
Reminder.id == reminder.id, Reminder.user == context.author.id
):
await context.send(
"You've already copied this reminder!", ephemeral=True
)
) or await Reminder.find_one(Reminder.id == reminder.id, Reminder.user == context.author.id):
await context.send("You've already copied this reminder!", ephemeral=True)
else:
new_reminder = Reminder(
user=context.author.id,
@ -160,9 +151,7 @@ class ComponentEventMixin:
await context.send("Reminder copied!", ephemeral=True)
else:
await context.send(
"That reminder doesn't exist anymore", ephemeral=True
)
await context.send("That reminder doesn't exist anymore", ephemeral=True)
return True

View file

@ -1,4 +1,5 @@
"""JARVIS member event mixin."""
import asyncio
from interactions import listen
@ -20,19 +21,13 @@ class MemberEventMixin:
"""Handle on_member_add event."""
user = event.member
guild = event.guild
unverified = await Setting.find_one(
Setting.guild == guild.id, Setting.setting == "unverified"
)
temproles = await Temprole.find(
Temprole.guild == guild.id, Temprole.user == user.id
).to_list()
unverified = await Setting.find_one(Setting.guild == guild.id, Setting.setting == "unverified")
temproles = await Temprole.find(Temprole.guild == guild.id, Temprole.user == user.id).to_list()
if unverified:
self.logger.debug(f"Applying unverified role to {user.id} in {guild.id}")
role = await guild.fetch_role(unverified.value)
if not role:
self.logger.warn(
f"Unverified role no longer exists for guild {guild.id}"
)
self.logger.warn(f"Unverified role no longer exists for guild {guild.id}")
await unverified.delete()
elif role not in user.roles:
await user.add_role(role, reason="User just joined and is unverified")
@ -43,18 +38,14 @@ class MemberEventMixin:
if not role:
await temprole.delete()
elif role not in user.roles:
await user.add_role(
role, reason="User joined and has existing temprole"
)
await user.add_role(role, reason="User joined and has existing temprole")
@listen()
async def on_member_remove(self, event: MemberRemove) -> None:
"""Handle on_member_remove event."""
user = event.member
guild = event.guild
log = await Setting.find_one(
Setting.guild == guild.id, Setting.setting == "activitylog"
)
log = await Setting.find_one(Setting.guild == guild.id, Setting.setting == "activitylog")
if log:
self.logger.debug(f"User {user.id} left {guild.id}")
channel = await guild.fetch_channel(log.value)
@ -95,7 +86,7 @@ class MemberEventMixin:
async def process_rolechange(self, before: Member, after: Member) -> Embed:
"""Process role changes."""
if before.roles == after.roles:
return
return None
new_roles = []
removed_roles = []
@ -126,7 +117,7 @@ class MemberEventMixin:
async def process_rename(self, before: Member, after: Member) -> None:
"""Process name change."""
if before.nickname == after.nickname and before.username == after.username:
return
return None
fields = (
EmbedField(
@ -151,29 +142,21 @@ class MemberEventMixin:
before = event.before
after = event.after
if (
before.display_name == after.display_name and before.roles == after.roles
) or (not after or not before):
if (before.display_name == after.display_name and before.roles == after.roles) or (not after or not before):
return
log = await Setting.find_one(
Setting.guild == before.guild.id, Setting.setting == "activitylog"
)
log = await Setting.find_one(Setting.guild == before.guild.id, Setting.setting == "activitylog")
if log:
channel = await before.guild.fetch_channel(log.value)
await asyncio.sleep(0.5) # Wait for audit log
embed = None
if before._role_ids != after._role_ids:
verified = await Setting.find_one(
Setting.guild == before.guild.id, Setting.setting == "verified"
)
verified = await Setting.find_one(Setting.guild == before.guild.id, Setting.setting == "verified")
v_role = None
if verified:
v_role = await before.guild.fetch_role(verified.value)
if not v_role:
self.logger.debug(
f"Guild {before.guild.id} verified role no longer exists"
)
self.logger.debug(f"Guild {before.guild.id} verified role no longer exists")
await verified.delete()
else:
if not before.has_role(v_role) and after.has_role(v_role):

View file

@ -1,4 +1,5 @@
"""JARVIS message event mixin"""
import re
from datetime import datetime, timedelta, timezone
@ -40,9 +41,7 @@ class MessageEventMixin:
)
if autopurge:
if not message.author.has_permission(Permissions.ADMINISTRATOR):
self.logger.debug(
f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}"
)
self.logger.debug(f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}")
await message.delete(delay=autopurge.delay)
async def autoreact(self, message: Message) -> None:
@ -52,9 +51,7 @@ class MessageEventMixin:
Autoreact.channel == message.channel.id,
)
if autoreact:
self.logger.debug(
f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}"
)
self.logger.debug(f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}")
for reaction in autoreact.reactions:
await message.add_reaction(reaction)
if autoreact.thread:
@ -75,13 +72,11 @@ class MessageEventMixin:
# channel = find(lambda x: x.id == 599068193339736096, message._mention_ids)
# if channel and message.author.id == 293795462752894976:
# await channel.send(
# content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501
# content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif"
# )
content = re.sub(r"\s+", "", message.content)
match = invites.search(content)
setting = await Setting.find_one(
Setting.guild == message.guild.id, Setting.setting == "noinvite"
)
setting = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "noinvite")
if not setting:
setting = Setting(guild=message.guild.id, setting="noinvite", value=True)
await setting.save()
@ -89,20 +84,19 @@ class MessageEventMixin:
guild_invites = [x.code for x in await message.guild.fetch_invites()]
if message.guild.vanity_url_code:
guild_invites.append(message.guild.vanity_url_code)
allowed = guild_invites + [
allowed = [
*guild_invites,
"dbrand",
"VtgZntXcnZ",
"gPfYGbvTCE",
"interactions",
"NTSHu97tHg",
]
is_mod = message.author.has_permission(
Permissions.MANAGE_GUILD
) or message.author.has_permission(Permissions.ADMINISTRATOR)
is_mod = message.author.has_permission(Permissions.MANAGE_GUILD) or message.author.has_permission(
Permissions.ADMINISTRATOR
)
if (m := match.group(1)) not in allowed and setting.value and not is_mod:
self.logger.debug(
f"Removing non-allowed invite `{m}` from {message.guild.id}"
)
self.logger.debug(f"Removing non-allowed invite `{m}` from {message.guild.id}")
try:
await message.delete()
except Exception:
@ -159,9 +153,7 @@ class MessageEventMixin:
value=1,
)
await Stat(meta=md, name="warning").insert()
embed = warning_embed(
message.author, "Sent a message with a filtered word", self.user
)
embed = warning_embed(message.author, "Sent a message with a filtered word", self.user)
try:
await message.reply(embeds=embed)
except Exception:
@ -180,21 +172,19 @@ class MessageEventMixin:
Setting.setting == "massmention",
)
is_mod = message.author.has_permission(
Permissions.MANAGE_GUILD
) or message.author.has_permission(Permissions.ADMINISTRATOR)
is_mod = message.author.has_permission(Permissions.MANAGE_GUILD) or message.author.has_permission(
Permissions.ADMINISTRATOR
)
if (
massmention
and int(massmention.value) > 0 # noqa: W503
and len(message._mention_ids + message._mention_roles) # noqa: W503
- (1 if message.author.id in message._mention_ids else 0) # noqa: W503
> massmention.value # noqa: W503
and not is_mod # noqa: W503
and int(massmention.value) > 0
and len(message._mention_ids + message._mention_roles)
- (1 if message.author.id in message._mention_ids else 0)
> massmention.value
and not is_mod
):
self.logger.debug(
f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}"
)
self.logger.debug(f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}")
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
await Warning(
active=True,
@ -226,20 +216,11 @@ class MessageEventMixin:
if message.author.has_permission(Permissions.MANAGE_GUILD):
return
except Exception as e:
self.logger.error(
"Failed to get permissions, pretending check failed", exc_info=e
)
self.logger.error("Failed to get permissions, pretending check failed", exc_info=e)
if (
await Roleping.find(
Roleping.guild == message.guild.id, Roleping.active == True
).count()
== 0
):
if await Roleping.find(Roleping.guild == message.guild.id, Roleping.active == True).count() == 0:
return
rolepings = await Roleping.find(
Roleping.guild == message.guild.id, Roleping.active == True
).to_list()
rolepings = await Roleping.find(Roleping.guild == message.guild.id, Roleping.active == True).to_list()
# Get all role IDs involved with message
roles = [x.id async for x in message.mention_roles]
@ -263,9 +244,7 @@ class MessageEventMixin:
# Check if user in a bypass list
def check_has_role(roleping: Roleping) -> bool:
return any(
role.id in roleping.bypass.roles for role in message.author.roles
)
return any(role.id in roleping.bypass.roles for role in message.author.roles)
user_has_bypass = False
for roleping in rolepings:
@ -276,15 +255,8 @@ class MessageEventMixin:
user_has_bypass = True
break
if (
role_in_rolepings
and user_missing_role
and not user_is_admin
and not user_has_bypass
):
self.logger.debug(
f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}"
)
if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass:
self.logger.debug(f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}")
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
await Warning(
active=True,
@ -426,9 +398,7 @@ class MessageEventMixin:
value=1,
)
await Stat(meta=md, name="warning").insert()
reasons = ", ".join(
f"{m['source']}: {m['type']}" for m in data["matches"]
)
reasons = ", ".join(f"{m['source']}: {m['type']}" for m in data["matches"])
embed = warning_embed(message.author, reasons, self.user)
try:
await message.channel.send(embeds=embed)
@ -474,9 +444,7 @@ class MessageEventMixin:
"""Timeout a user."""
expires_at = datetime.now(tz=timezone.utc) + timedelta(minutes=30)
try:
await user.timeout(
communication_disabled_until=expires_at, reason="Phishing link"
)
await user.timeout(communication_disabled_until=expires_at, reason="Phishing link")
await Mute(
user=user.id,
reason="Auto mute for harmful link",
@ -525,20 +493,10 @@ class MessageEventMixin:
before = event.before
after = event.after
if not after.author.bot:
modlog = await Setting.find_one(
Setting.guild == after.guild.id, Setting.setting == "activitylog"
)
ignore = await Setting.find_one(
Setting.guild == after.guild.id, Setting.setting == "log_ignore"
)
if modlog and (
not ignore or (ignore and after.channel.id not in ignore.value)
):
if (
not before
or before.content == after.content
or before.content is None
):
modlog = await Setting.find_one(Setting.guild == after.guild.id, Setting.setting == "activitylog")
ignore = await Setting.find_one(Setting.guild == after.guild.id, Setting.setting == "log_ignore")
if modlog and (not ignore or (ignore and after.channel.id not in ignore.value)):
if not before or before.content == after.content or before.content is None:
return
try:
channel = before.guild.get_channel(modlog.value)
@ -567,9 +525,7 @@ class MessageEventMixin:
icon_url=after.author.display_avatar.url,
url=after.jump_url,
)
embed.set_footer(
text=f"{after.author.username} | {after.author.id}"
)
embed.set_footer(text=f"{after.author.username} | {after.author.id}")
await channel.send(embeds=embed)
except Exception as e:
self.logger.warning(
@ -590,15 +546,9 @@ class MessageEventMixin:
async def on_message_delete(self, event: MessageDelete) -> None:
"""Process on_message_delete events."""
message = event.message
modlog = await Setting.find_one(
Setting.guild == message.guild.id, Setting.setting == "activitylog"
)
ignore = await Setting.find_one(
Setting.guild == message.guild.id, Setting.setting == "log_ignore"
)
if modlog and (
not ignore or (ignore and message.channel.id not in ignore.value)
):
modlog = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "activitylog")
ignore = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "log_ignore")
if modlog and (not ignore or (ignore and message.channel.id not in ignore.value)):
try:
content = message.content or message.system_content or "N/A"
except AttributeError:
@ -607,9 +557,7 @@ class MessageEventMixin:
try:
if message.attachments:
value = "\n".join(
[f"[{x.filename}]({x.url})" for x in message.attachments]
)
value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments])
fields.append(
EmbedField(
name="Attachments",
@ -619,9 +567,7 @@ class MessageEventMixin:
)
if message.sticker_items:
value = "\n".join(
[f"Sticker: {x.name}" for x in message.sticker_items]
)
value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items])
fields.append(
EmbedField(
name="Stickers",
@ -653,11 +599,7 @@ class MessageEventMixin:
icon_url=message.author.display_avatar.url,
url=message.jump_url,
)
embed.set_footer(
text=(f"{message.author.username} | " f"{message.author.id}")
)
embed.set_footer(text=(f"{message.author.username} | " f"{message.author.id}"))
await channel.send(embeds=embed)
except Exception as e:
self.logger.warning(
f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}"
)
self.logger.warning(f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}")

View file

@ -4,7 +4,6 @@ from datetime import datetime
import pytz
from aiohttp import ClientSession
from beanie.operators import Set
from croniter import croniter
from interactions.models.internal.tasks.task import Task
from interactions.models.internal.tasks.triggers import BaseTrigger, IntervalTrigger
@ -19,6 +18,7 @@ class CronTrigger(BaseTrigger):
Attributes:
schedule str: The cron schedule, use https://crontab.guru for help
"""
def __init__(self, schedule: str) -> None:
@ -31,9 +31,7 @@ class CronTrigger(BaseTrigger):
class TaskMixin:
@Task.create(IntervalTrigger(minutes=1))
async def _update_domains(self) -> None:
async with ClientSession(
headers={"X-Identity": "Discord: zevaryx#5779"}
) as session:
async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session:
response = await session.get("https://phish.sinking.yachts/v2/recent/60")
response.raise_for_status()
data = await response.json()
@ -63,7 +61,7 @@ class TaskMixin:
await self.erapi.update_async()
@Task.create(CronTrigger("0 0 1 * *"))
async def _cleanup(self) -> None:
async def _cleanup(self) -> None: # noqa: C901
self.logger.debug("Getting all unique guild, channel, and user IDs")
guild_ids = []
channel_ids = []

View file

@ -1,4 +1,5 @@
"""JARVIS Admin Cogs."""
import logging
from interactions import Client

View file

@ -1,4 +1,5 @@
"""JARVIS Autoreact Cog."""
import logging
import re
from typing import Optional, Tuple
@ -39,10 +40,9 @@ class AutoReactCog(Extension):
Returns:
Tuple of success? and error message
"""
exists = await Autoreact.find_one(
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
)
exists = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
if exists:
return False, f"Autoreact already exists for {channel.mention}."
@ -56,9 +56,7 @@ class AutoReactCog(Extension):
return True, None
async def delete_autoreact(
self, ctx: InteractionContext, channel: GuildText
) -> bool:
async def delete_autoreact(self, ctx: InteractionContext, channel: GuildText) -> bool:
"""
Remove an autoreact monitor on a channel.
@ -68,10 +66,9 @@ class AutoReactCog(Extension):
Returns:
Success?
"""
ar = await Autoreact.find_one(
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
)
ar = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
if ar:
await ar.delete()
return True
@ -107,7 +104,7 @@ class AutoReactCog(Extension):
ctx: InteractionContext,
channel: GuildText,
thread: bool = True,
emote: str = None,
emote: str = None, # noqa: RUF013
) -> None:
await ctx.defer()
if emote:
@ -125,18 +122,12 @@ class AutoReactCog(Extension):
lambda x: x.id == emoji_id,
await ctx.guild.fetch_all_custom_emojis(),
):
await ctx.send(
"Please use a custom emote from this server.", ephemeral=True
)
await ctx.send("Please use a custom emote from this server.", ephemeral=True)
return
autoreact = await Autoreact.find_one(
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
)
autoreact = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
if not autoreact:
await self.create_autoreact(ctx, channel, thread)
autoreact = await Autoreact.find_one(
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
)
autoreact = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
if emote and emote in autoreact.reactions:
await ctx.send(
f"Emote already added to {channel.mention} autoreactions.",
@ -176,12 +167,8 @@ class AutoReactCog(Extension):
required=True,
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _autoreact_remove(
self, ctx: InteractionContext, channel: GuildText, emote: str
) -> None:
autoreact = await Autoreact.find_one(
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
)
async def _autoreact_remove(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None:
autoreact = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
if not autoreact:
await ctx.send(
f"Please create autoreact first with /autoreact add {channel.mention} {emote}",
@ -215,9 +202,7 @@ class AutoReactCog(Extension):
required=True,
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _autoreact_delete(
self, ctx: InteractionContext, channel: GuildText
) -> None:
async def _autoreact_delete(self, ctx: InteractionContext, channel: GuildText) -> None:
result = await self.delete_autoreact(ctx, channel)
if not result:
await ctx.send(f"No autoreact found in {channel.mention}", ephemeral=True)
@ -234,12 +219,8 @@ class AutoReactCog(Extension):
opt_type=OptionType.CHANNEL,
required=True,
)
async def _autoreact_list(
self, ctx: InteractionContext, channel: GuildText
) -> None:
exists = await Autoreact.find_one(
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
)
async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None:
exists = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
if not exists:
await ctx.send(
f"Please create autoreact first with /autoreact add {channel.mention} <emote>",
@ -248,9 +229,7 @@ class AutoReactCog(Extension):
return
message = ""
if len(exists.reactions) > 0:
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(
exists.reactions
)
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(exists.reactions)
else:
message = f"No reactions set on {channel.mention}"
await ctx.send(message)

View file

@ -1,4 +1,5 @@
"""JARVIS BanCog."""
import re
from datetime import timedelta
@ -68,9 +69,7 @@ class BanCog(ModcaseCog):
await ctx.send(embeds=embed)
async def discord_apply_unban(
self, ctx: InteractionContext, user: User, reason: str
) -> None:
async def discord_apply_unban(self, ctx: InteractionContext, user: User, reason: str) -> None:
"""Apply a Discord unban."""
await ctx.guild.unban(user, reason=reason)
discrim = user.discriminator
@ -91,9 +90,7 @@ class BanCog(ModcaseCog):
await ctx.send(embeds=embed)
@slash_command(name="ban", description="Ban a user")
@slash_option(
name="user", description="User to ban", opt_type=OptionType.USER, required=True
)
@slash_option(name="user", description="User to ban", opt_type=OptionType.USER, required=True)
@slash_option(
name="reason",
description="Ban reason",
@ -124,14 +121,14 @@ class BanCog(ModcaseCog):
required=False,
)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _ban(
async def _ban( # noqa: C901
self,
ctx: InteractionContext,
user: User,
reason: str,
btype: str = "perm",
duration: int = 4,
delete_history: str = None,
delete_history: str = None, # noqa: RUF013
) -> None:
if user.id == ctx.author.id:
await ctx.send("You cannot ban yourself.", ephemeral=True)
@ -185,9 +182,7 @@ class BanCog(ModcaseCog):
mtype = "perma"
guild_name = ctx.guild.name
user_message = (
f"You have been {mtype}banned from {guild_name}." + f" Reason:\n{reason}"
)
user_message = f"You have been {mtype}banned from {guild_name}." + f" Reason:\n{reason}"
if mtype == "temp":
user_message += f"\nDuration: {duration} hours"
@ -216,9 +211,7 @@ class BanCog(ModcaseCog):
except Exception:
self.logger.warn(f"Failed to send ban embed to {user.id}")
try:
await self.discord_apply_ban(
ctx, reason, user, duration, active, mtype, delete_history or 0
)
await self.discord_apply_ban(ctx, reason, user, duration, active, mtype, delete_history or 0)
except Exception as e:
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", ephemeral=True)
return
@ -240,7 +233,7 @@ class BanCog(ModcaseCog):
required=True,
)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _unban(
async def _unban( # noqa: C901
self,
ctx: InteractionContext,
user: str,
@ -267,8 +260,7 @@ class BanCog(ModcaseCog):
user, discrim = user.split("#")
if discrim:
discord_ban_info = find(
lambda x: x.user.username == user
and x.user.discriminator == discrim,
lambda x: x.user.username == user and x.user.discriminator == discrim,
bans,
)
else:
@ -277,16 +269,9 @@ class BanCog(ModcaseCog):
if len(results) > 1:
active_bans = []
for ban in bans:
active_bans.append(
"{0} ({1}): {2}".format(
ban.user.username, ban.user.id, ban.reason
)
)
active_bans.append("{0} ({1}): {2}".format(ban.user.username, ban.user.id, ban.reason))
ab_message = "\n".join(active_bans)
message = (
"More than one result. "
f"Please use one of the following IDs:\n```{ab_message}\n```"
)
message = "More than one result. " f"Please use one of the following IDs:\n```{ab_message}\n```"
await ctx.send(message)
return
discord_ban_info = results[0]
@ -331,9 +316,7 @@ class BanCog(ModcaseCog):
admin=ctx.author.id,
reason=reason,
).save()
await ctx.send(
"Unable to find user in Discord, but removed entry from database."
)
await ctx.send("Unable to find user in Discord, but removed entry from database.")
bans = SlashCommand(name="bans", description="User bans")
@ -357,9 +340,7 @@ class BanCog(ModcaseCog):
required=False,
)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _bans_list(
self, ctx: InteractionContext, btype: int = 0, active: bool = True
) -> None:
async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: bool = True) -> None:
types = [0, "perm", "temp", "soft"]
search = {"guild": ctx.guild.id}
if active:
@ -421,9 +402,7 @@ class BanCog(ModcaseCog):
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])
embed.set_thumbnail(url=ctx.guild.icon.url)
pages.append(embed)

View file

@ -1,4 +1,5 @@
"""Filters cog."""
import asyncio
import difflib
import re
@ -31,16 +32,12 @@ class FilterCog(Extension):
self.bot = bot
self.cache: Dict[int, List[str]] = {}
async def _edit_filter(
self, ctx: InteractionContext, name: str, search: bool = False
) -> None:
async def _edit_filter(self, ctx: InteractionContext, name: str, search: bool = False) -> None:
try:
content = ""
f: Filter = None
if search:
if f := await Filter.find_one(
Filter.name == name, Filter.guild == ctx.guild.id
):
if f := await Filter.find_one(Filter.name == name, Filter.guild == ctx.guild.id):
content = "\n".join(f.filters)
kw = "Updating" if search else "Creating"
@ -60,9 +57,7 @@ class FilterCog(Extension):
)
await ctx.send_modal(modal)
try:
data = await self.bot.wait_for_modal(
modal, author=ctx.author.id, timeout=60 * 5
)
data = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
filters = data.responses.get("filters").split("\n")
except asyncio.TimeoutError:
return
@ -82,9 +77,7 @@ class FilterCog(Extension):
content = content.splitlines()
diff = "\n".join(difflib.ndiff(content, filters)).replace("`", "\u200b`")
await data.send(
f"Filter `{new_name}` has been updated:\n\n```diff\n{diff}\n```"
)
await data.send(f"Filter `{new_name}` has been updated:\n\n```diff\n{diff}\n```")
if ctx.guild.id not in self.cache:
self.cache[ctx.guild.id] = []
@ -97,9 +90,7 @@ class FilterCog(Extension):
filter_ = SlashCommand(name="filter", description="Manage keyword filters")
@filter_.subcommand(
sub_cmd_name="create", sub_cmd_description="Create a new filter"
)
@filter_.subcommand(sub_cmd_name="create", sub_cmd_description="Create a new filter")
@slash_option(
name="name",
description="Name of new filter",
@ -170,9 +161,7 @@ class FilterCog(Extension):
if not self.cache.get(ctx.guild.id):
filters = await Filter.find(Filter.guild == ctx.guild.id).to_list()
self.cache[ctx.guild.id] = [f.name for f in filters]
results = process.extract(
ctx.input_text, self.cache.get(ctx.guild.id), limit=25
)
results = process.extract(ctx.input_text, self.cache.get(ctx.guild.id), limit=25)
choices = [{"name": r[0], "value": r[0]} for r in results]
await ctx.send(choices=choices)

View file

@ -1,4 +1,5 @@
"""JARVIS KickCog."""
from interactions import InteractionContext, Permissions
from interactions.models.discord.user import User
from interactions.models.internal.application_commands import (

View file

@ -1,4 +1,5 @@
"""JARVIS LockCog."""
import logging
from typing import Union
@ -56,7 +57,7 @@ class LockCog(Extension):
await ctx.send("Duration must be > 0", ephemeral=True)
return
elif duration > 60 * 24 * 7:
if duration > 60 * 24 * 7:
await ctx.send("Duration must be <= 7 days", ephemeral=True)
return

View file

@ -1,4 +1,5 @@
"""JARVIS LockdownCog."""
import logging
from interactions import Client, Extension, InteractionContext
@ -26,6 +27,7 @@ async def lock(bot: Client, target: GuildChannel, admin: Member, reason: str, du
bot: Bot instance
target: Target channel
admin: Admin who initiated lockdown
"""
to_deny = Permissions.SEND_MESSAGES | Permissions.CONNECT | Permissions.SPEAK
current = get(target.permission_overwrites, id=target.guild.id)
@ -51,6 +53,7 @@ async def lock_all(bot: Client, guild: Guild, admin: Member, reason: str, durati
bot: Bot instance
guild: Target guild
admin: Admin who initiated lockdown
"""
role = await guild.fetch_role(guild.id)
categories = find_all(lambda x: isinstance(x, GuildCategory), guild.channels)
@ -71,6 +74,7 @@ async def unlock_all(bot: Client, guild: Guild, admin: Member) -> None:
bot: Bot instance
target: Target channel
admin: Admin who ended lockdown
"""
locks = Lock.find(Lock.guild == guild.id, Lock.active == True)
async for lock in locks:
@ -125,7 +129,7 @@ class LockdownCog(Extension):
if duration <= 0:
await ctx.send("Duration must be > 0", ephemeral=True)
return
elif duration > 60 * 24 * 7:
if duration > 60 * 24 * 7:
await ctx.send("Duration must be <= 7 days", ephemeral=True)
return

View file

@ -1,4 +1,5 @@
"""JARVIS Moderation Case management."""
from typing import TYPE_CHECKING, List, Optional
from interactions import Extension, InteractionContext, Permissions
@ -40,20 +41,15 @@ class CaseCog(Extension):
Args:
mod_case: Moderation case
guild: Originating guild
"""
action_table = Table()
action_table.add_column(
header="Type", justify="left", style="orange4", no_wrap=True
)
action_table.add_column(
header="Admin", justify="left", style="cyan", no_wrap=True
)
action_table.add_column(header="Type", justify="left", style="orange4", no_wrap=True)
action_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True)
action_table.add_column(header="Reason", justify="left", style="white")
note_table = Table()
note_table.add_column(
header="Admin", justify="left", style="cyan", no_wrap=True
)
note_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True)
note_table.add_column(header="Content", justify="left", style="white")
console = Console()
@ -71,17 +67,13 @@ class CaseCog(Extension):
admin_text = "[N/A]"
if admin:
admin_text = f"{admin.username}"
action_table.add_row(
action.action_type.title(), admin_text, parent_action.reason
)
action_table.add_row(action.action_type.title(), admin_text, parent_action.reason)
with console.capture() as cap:
console.print(action_table)
tmp_output = cap.get()
if len(tmp_output) >= 800:
action_output_extra = (
f"... and {len(mod_case.actions[idx:])} more actions"
)
action_output_extra = f"... and {len(mod_case.actions[idx:])} more actions"
break
action_output = tmp_output
@ -128,9 +120,7 @@ class CaseCog(Extension):
name="Actions",
value=action_output if mod_case.actions else "No Actions Found",
),
EmbedField(
name="Notes", value=note_output if mod_case.notes else "No Notes Found"
),
EmbedField(name="Notes", value=note_output if mod_case.notes else "No Notes Found"),
)
embed = build_embed(
@ -156,6 +146,7 @@ class CaseCog(Extension):
Args:
mod_case: Moderation case
guild: Originating guild
"""
embeds = []
user = await self.bot.fetch_user(mod_case.user)
@ -181,9 +172,7 @@ class CaseCog(Extension):
if admin:
admin_text = admin.mention
fields = (
EmbedField(name=action.action_type.title(), value=parent_action.reason),
)
fields = (EmbedField(name=action.action_type.title(), value=parent_action.reason),)
embed = build_embed(
title="Moderation Case Action",
description=f"{admin_text} initiated an action against {user_mention}",
@ -236,12 +225,8 @@ class CaseCog(Extension):
case = SlashCommand(name="case", description="Manage a moderation case")
show = case.group(name="show", description="Show information about a specific case")
@show.subcommand(
sub_cmd_name="summary", sub_cmd_description="Summarize a specific case"
)
@slash_option(
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
)
@show.subcommand(sub_cmd_name="summary", sub_cmd_description="Summarize a specific case")
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _case_show_summary(self, ctx: InteractionContext, cid: str) -> None:
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
@ -253,9 +238,7 @@ class CaseCog(Extension):
await ctx.send(embeds=embed)
@show.subcommand(sub_cmd_name="actions", sub_cmd_description="Get case actions")
@slash_option(
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
)
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _case_show_actions(self, ctx: InteractionContext, cid: str) -> None:
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
@ -268,9 +251,7 @@ class CaseCog(Extension):
await paginator.send(ctx)
@case.subcommand(sub_cmd_name="close", sub_cmd_description="Show a specific case")
@slash_option(
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
)
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _case_close(self, ctx: InteractionContext, cid: str) -> None:
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
@ -284,12 +265,8 @@ class CaseCog(Extension):
embed = await self.get_summary_embed(case, ctx.guild)
await ctx.send(embeds=embed)
@case.subcommand(
sub_cmd_name="repoen", sub_cmd_description="Reopen a specific case"
)
@slash_option(
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
)
@case.subcommand(sub_cmd_name="repoen", sub_cmd_description="Reopen a specific case")
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _case_reopen(self, ctx: InteractionContext, cid: str) -> None:
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
@ -303,12 +280,8 @@ class CaseCog(Extension):
embed = await self.get_summary_embed(case, ctx.guild)
await ctx.send(embeds=embed)
@case.subcommand(
sub_cmd_name="note", sub_cmd_description="Add a note to a specific case"
)
@slash_option(
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
)
@case.subcommand(sub_cmd_name="note", sub_cmd_description="Add a note to a specific case")
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
@slash_option(
name="note",
description="Note to add",
@ -323,9 +296,7 @@ class CaseCog(Extension):
return
if not case.open:
await ctx.send(
"Case is closed, please re-open to add a new comment", ephemeral=True
)
await ctx.send("Case is closed, please re-open to add a new comment", ephemeral=True)
return
if len(note) > 50:
@ -341,9 +312,7 @@ class CaseCog(Extension):
await ctx.send(embeds=embed)
@case.subcommand(sub_cmd_name="new", sub_cmd_description="Open a new case")
@slash_option(
name="user", description="Target user", opt_type=OptionType.USER, required=True
)
@slash_option(name="user", description="Target user", opt_type=OptionType.USER, required=True)
@slash_option(
name="note",
description="Note to add",
@ -352,9 +321,7 @@ class CaseCog(Extension):
)
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _case_new(self, ctx: InteractionContext, user: Member, note: str) -> None:
case = await Modlog.find_one(
Modlog.guild == ctx.guild.id, Modlog.user == user.id, Modlog.open == True
)
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.user == user.id, Modlog.open == True)
if case:
await ctx.send(f"Case already open with ID `{case.nanoid}`", ephemeral=True)
return

View file

@ -1,4 +1,5 @@
"""JARVIS MuteCog."""
import asyncio
from datetime import datetime, timedelta, timezone
@ -27,9 +28,7 @@ from jarvis.utils.permissions import admin_or_permissions
class MuteCog(ModcaseCog):
"""JARVIS MuteCog."""
async def _apply_timeout(
self, ctx: InteractionContext, user: Member, reason: str, until: datetime
) -> None:
async def _apply_timeout(self, ctx: InteractionContext, user: Member, reason: str, until: datetime) -> None:
await user.timeout(communication_disabled_until=until, reason=reason)
duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60)
await Mute(
@ -44,11 +43,7 @@ class MuteCog(ModcaseCog):
return mute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
@context_menu(name="Mute User", context_type=CommandType.USER)
@check(
admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
)
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
async def _timeout_cm(self, ctx: InteractionContext) -> None:
modal = Modal(
*[
@ -71,9 +66,7 @@ class MuteCog(ModcaseCog):
)
await ctx.send_modal(modal)
try:
response = await self.bot.wait_for_modal(
modal, author=ctx.author.id, timeout=60 * 5
)
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
reason = response.responses.get("reason")
until = response.responses.get("until")
except asyncio.TimeoutError:
@ -84,9 +77,7 @@ class MuteCog(ModcaseCog):
"RETURN_AS_TIMEZONE_AWARE": True,
}
rt_settings = base_settings.copy()
rt_settings["PARSERS"] = [
x for x in default_parsers if x not in ["absolute-time", "timestamp"]
]
rt_settings["PARSERS"] = [x for x in default_parsers if x not in ["absolute-time", "timestamp"]]
rt_until = parse(until, settings=rt_settings)
@ -101,14 +92,10 @@ class MuteCog(ModcaseCog):
until = at_until
else:
self.logger.debug(f"Failed to parse delay: {until}")
await response.send(
f"`{until}` is not a parsable date, please try again", ephemeral=True
)
await response.send(f"`{until}` is not a parsable date, please try again", ephemeral=True)
return
if until < datetime.now(tz=timezone.utc):
await response.send(
f"`{old_until}` is in the past, which isn't allowed", ephemeral=True
)
await response.send(f"`{old_until}` is in the past, which isn't allowed", ephemeral=True)
return
try:
embed = await self._apply_timeout(ctx, ctx.target, reason, until)
@ -117,9 +104,7 @@ class MuteCog(ModcaseCog):
await response.send("Unable to mute this user", ephemeral=True)
@slash_command(name="mute", description="Mute a user")
@slash_option(
name="user", description="User to mute", opt_type=OptionType.USER, required=True
)
@slash_option(name="user", description="User to mute", opt_type=OptionType.USER, required=True)
@slash_option(
name="reason",
description="Reason for mute",
@ -144,11 +129,7 @@ class MuteCog(ModcaseCog):
SlashCommandChoice(name="Week(s)", value=10080),
],
)
@check(
admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
)
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
async def _timeout(
self,
ctx: InteractionContext,
@ -173,9 +154,7 @@ class MuteCog(ModcaseCog):
# Max 4 weeks (2419200 seconds) per API
duration = time * scale
if duration > 40320:
await ctx.send(
"Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True
)
await ctx.send("Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True)
return
until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration)
@ -198,16 +177,11 @@ class MuteCog(ModcaseCog):
opt_type=OptionType.STRING,
required=True,
)
@check(
admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
)
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
async def _unmute(self, ctx: InteractionContext, user: Member, reason: str) -> None:
if (
not user.communication_disabled_until
or user.communication_disabled_until.timestamp()
< datetime.now(tz=timezone.utc).timestamp() # noqa: W503
or user.communication_disabled_until.timestamp() < datetime.now(tz=timezone.utc).timestamp()
):
await ctx.send("User is not muted", ephemeral=True)
return
@ -218,8 +192,6 @@ class MuteCog(ModcaseCog):
await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc))
embed = unmute_embed(
user=user, admin=ctx.author, reason=reason, guild=ctx.guild
)
embed = unmute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
await ctx.send(embeds=embed)

View file

@ -1,4 +1,5 @@
"""JARVIS PurgeCog."""
import logging
from interactions import Client, Extension, InteractionContext, Permissions
@ -67,7 +68,7 @@ class PurgeCog(Extension):
if delay <= 0:
await ctx.send("Delay must be > 0", ephemeral=True)
return
elif delay > 300:
if delay > 300:
await ctx.send("Delay must be < 5 minutes", ephemeral=True)
return

View file

@ -1,4 +1,5 @@
"""JARVIS RolepingCog."""
import logging
from interactions import Client, Extension, InteractionContext, Permissions

View file

@ -1,4 +1,5 @@
"""JARVIS Settings Management Cog."""
import asyncio
import logging
from typing import Any

View file

@ -1,4 +1,5 @@
"""JARVIS temporary role handler."""
import logging
from datetime import datetime, timezone
@ -46,7 +47,7 @@ class TemproleCog(Extension):
)
@check(admin_or_permissions(Permissions.MANAGE_ROLES))
async def _temprole(
self, ctx: InteractionContext, user: Member, role: Role, duration: str, reason: str = None
self, ctx: InteractionContext, user: Member, role: Role, duration: str, reason: str = None # noqa: RUF013
) -> None:
await ctx.defer()
if not isinstance(user, Member):

View file

@ -1,4 +1,5 @@
"""JARVIS Verify Cog."""
import asyncio
import logging
from random import randint
@ -14,7 +15,7 @@ from jarvis_core.db.models import Setting
def create_layout() -> list:
"""Create verify component layout."""
buttons = []
yes = randint(0, 2) # noqa: S311
yes = randint(0, 2)
for i in range(3):
label = "YES" if i == yes else "NO"
id = f"no_{i}" if not i == yes else "yes"

View file

@ -1,4 +1,5 @@
"""JARVIS WarningCog."""
from datetime import datetime, timedelta, timezone
from interactions import InteractionContext, Permissions
@ -24,9 +25,7 @@ class WarningCog(ModcaseCog):
"""JARVIS WarningCog."""
@slash_command(name="warn", description="Warn a user")
@slash_option(
name="user", description="User to warn", opt_type=OptionType.USER, required=True
)
@slash_option(name="user", description="User to warn", opt_type=OptionType.USER, required=True)
@slash_option(
name="reason",
description="Reason for warning",
@ -40,16 +39,14 @@ class WarningCog(ModcaseCog):
required=False,
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _warn(
self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24
) -> None:
async def _warn(self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24) -> None:
if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", ephemeral=True)
return
if duration <= 0:
await ctx.send("Duration must be > 0", ephemeral=True)
return
elif duration >= 120:
if duration >= 120:
await ctx.send("Duration must be < 5 days", ephemeral=True)
return
if not await ctx.guild.fetch_member(user.id):
@ -70,9 +67,7 @@ class WarningCog(ModcaseCog):
await ctx.send(embeds=embed)
@slash_command(name="warnings", description="Get count of user warnings")
@slash_option(
name="user", description="User to view", opt_type=OptionType.USER, required=True
)
@slash_option(name="user", description="User to view", opt_type=OptionType.USER, required=True)
@slash_option(
name="active",
description="View active only",
@ -80,9 +75,7 @@ class WarningCog(ModcaseCog):
required=False,
)
# @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _warnings(
self, ctx: InteractionContext, user: Member, active: bool = True
) -> None:
async def _warnings(self, ctx: InteractionContext, user: Member, active: bool = True) -> None:
warnings = (
await Warning.find(
Warning.user == user.id,
@ -126,9 +119,7 @@ class WarningCog(ModcaseCog):
for i in range(0, len(fields), 5):
embed = build_embed(
title="Warnings",
description=(
f"{len(warnings)} total | {len(active_warns)} currently active"
),
description=(f"{len(warnings)} total | {len(active_warns)} currently active"),
fields=fields[i : i + 5],
)
embed.set_author(
@ -136,9 +127,7 @@ class WarningCog(ModcaseCog):
icon_url=user.display_avatar.url,
)
embed.set_thumbnail(url=ctx.guild.icon.url)
embed.set_footer(
text=f"{user.username}#{user.discriminator} | {user.id}"
)
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
pages.append(embed)
else:
fields = []
@ -156,9 +145,7 @@ class WarningCog(ModcaseCog):
for i in range(0, len(fields), 5):
embed = build_embed(
title="Warnings",
description=(
f"{len(warnings)} total | {len(active_warns)} currently active"
),
description=(f"{len(warnings)} total | {len(active_warns)} currently active"),
fields=fields[i : i + 5],
)
embed.set_author(

View file

@ -1,6 +1,5 @@
"""JARVIS bot utility commands."""
import asyncio
import logging
import platform
from io import BytesIO
@ -49,9 +48,7 @@ class BotutilCog(Extension):
file_bytes.write(log.encode("UTF8"))
file_bytes.seek(0)
log = File(file_bytes, file_name=f"tail_{count}.log")
await ctx.reply(
content=f"Here's the last {count} lines of the log", file=log
)
await ctx.reply(content=f"Here's the last {count} lines of the log", file=log)
else:
await ctx.reply(content=f"```\n{log}\n```")
@ -80,17 +77,13 @@ class BotutilCog(Extension):
inline=False,
),
EmbedField(name="Version", value=platform.release() or "N/A", inline=False),
EmbedField(
name="System Start Time", value=f"<t:{ut_ts}:F> (<t:{ut_ts}:R>)"
),
EmbedField(name="System Start Time", value=f"<t:{ut_ts}:F> (<t:{ut_ts}:R>)"),
EmbedField(name="Python Version", value=platform.python_version()),
EmbedField(name="Bot Start Time", value=f"<t:{st_ts}:F> (<t:{st_ts}:R>)"),
)
embed = build_embed(title="System Info", description="", fields=fields)
embed.set_image(url=self.bot.user.avatar.url)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)

View file

@ -36,13 +36,9 @@ class DataCog(Extension):
data = SlashCommand(name="data", description="Data commands")
@data.subcommand(
sub_cmd_name="usage", sub_cmd_description="View data usage information"
)
@data.subcommand(sub_cmd_name="usage", sub_cmd_description="View data usage information")
async def _data_usage(self, ctx: SlashContext) -> None:
await ctx.send(
"Please review our [Privacy Policy](https://s.zevs.me/jarvis-privacy)"
)
await ctx.send("Please review our [Privacy Policy](https://s.zevs.me/jarvis-privacy)")
@data.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete my data")
async def _data_delete(self, ctx: SlashContext) -> None:
@ -91,9 +87,7 @@ class DataCog(Extension):
await message.edit(
content="❌ There was an error. Please report it to the help server, or DM `@zevaryx` for help"
)
self.logger.error(
f"Encountered error while deleting data: {e}", exc_info=True
)
self.logger.error(f"Encountered error while deleting data: {e}", exc_info=True)
else:
for row in components:

View file

@ -1,4 +1,5 @@
"""JARVIS Remind Me Cog."""
import asyncio
import logging
import re
@ -28,7 +29,7 @@ from jarvis.utils import build_embed
valid = re.compile(r"[\w\s\-\\/.!@?#$%^*()+=<>:'\",\u0080-\U000E0FFF]*")
time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?", flags=re.IGNORECASE)
invites = re.compile(
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
flags=re.IGNORECASE,
)
@ -60,12 +61,10 @@ class RemindmeCog(Extension):
self,
ctx: InteractionContext,
timezone: str = "UTC",
private: bool = None,
private: bool = False,
) -> None:
if private is None and ctx.guild:
if not private and ctx.guild:
private = ctx.guild.member_count >= 5000
elif private is None and not ctx.guild:
private = False
timezone = pytz.timezone(timezone)
modal = Modal(
*[
@ -96,9 +95,7 @@ class RemindmeCog(Extension):
await ctx.send_modal(modal)
try:
response = await self.bot.wait_for_modal(
modal, author=ctx.author.id, timeout=60 * 5
)
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
message = response.responses.get("message").strip()
delay = response.responses.get("delay").strip()
cron = response.responses.get("cron").strip()
@ -107,7 +104,7 @@ class RemindmeCog(Extension):
if len(message) > 500:
await response.send("Reminder cannot be > 500 characters.", ephemeral=True)
return
elif invites.search(message):
if invites.search(message):
await response.send(
"Listen, don't use this to try and bypass the rules",
ephemeral=True,
@ -118,12 +115,10 @@ class RemindmeCog(Extension):
# "Hey, you should probably make this readable", ephemeral=True
# )
# return
elif len(message) == 0:
await response.send(
"Hey, you should probably add content to your reminder", ephemeral=True
)
if len(message) == 0:
await response.send("Hey, you should probably add content to your reminder", ephemeral=True)
return
elif cron and not croniter.is_valid(cron):
if cron and not croniter.is_valid(cron):
await response.send(
f"Invalid cron: {cron}\n\nUse https://crontab.guru to help",
ephemeral=True,
@ -136,9 +131,7 @@ class RemindmeCog(Extension):
"RETURN_AS_TIMEZONE_AWARE": True,
}
rt_settings = base_settings.copy()
rt_settings["PARSERS"] = [
x for x in default_parsers if x not in ["absolute-time", "timestamp"]
]
rt_settings["PARSERS"] = [x for x in default_parsers if x not in ["absolute-time", "timestamp"]]
rt_remind_at = parse(delay, settings=rt_settings)
@ -165,7 +158,7 @@ class RemindmeCog(Extension):
)
return
elif remind_at < datetime.now(tz=timezone):
if remind_at < datetime.now(tz=timezone):
pass
r = Reminder(
@ -217,17 +210,13 @@ class RemindmeCog(Extension):
)
components = [delete_button]
if not r.guild == ctx.author.id:
copy_button = Button(
style=ButtonStyle.GREEN, emoji="📋", custom_id=f"copy|rme|{r.id}"
)
copy_button = Button(style=ButtonStyle.GREEN, emoji="📋", custom_id=f"copy|rme|{r.id}")
components.append(copy_button)
private = private if private is not None else False
components = [ActionRow(*components)]
await response.send(embeds=embed, components=components, ephemeral=private)
async def get_reminders_embed(
self, ctx: InteractionContext, reminders: List[Reminder]
) -> Embed:
async def get_reminders_embed(self, ctx: InteractionContext, reminders: List[Reminder]) -> Embed:
"""Build embed for paginator."""
fields = []
for reminder in reminders:
@ -264,22 +253,16 @@ class RemindmeCog(Extension):
@reminders.subcommand(sub_cmd_name="list", sub_cmd_description="List reminders")
async def _list(self, ctx: InteractionContext) -> None:
reminders = await Reminder.find(
Reminder.user == ctx.author.id, Reminder.active == True
).to_list()
reminders = await Reminder.find(Reminder.user == ctx.author.id, Reminder.active == True).to_list()
if not reminders:
await ctx.send("You have no reminders set.", ephemeral=True)
return
embed = await self.get_reminders_embed(ctx, reminders)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@reminders.subcommand(
sub_cmd_name="delete", sub_cmd_description="Delete a reminder"
)
@reminders.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a reminder")
@slash_option(
name="content",
description="Content of the reminder",
@ -309,9 +292,7 @@ class RemindmeCog(Extension):
)
embed.set_thumbnail(url=ctx.author.display_avatar.url)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
try:
await reminder.delete()
except Exception:
@ -343,18 +324,14 @@ class RemindmeCog(Extension):
EmbedField(name="Created At", value=f"<t:{cts}:F> (<t:{cts}:R>)"),
]
embed = build_embed(
title="You have a reminder!", description=reminder.message, fields=fields
)
embed = build_embed(title="You have a reminder!", description=reminder.message, fields=fields)
embed.set_author(
name=ctx.author.display_name,
icon_url=ctx.author.display_avatar.url,
)
embed.set_thumbnail(url=ctx.author.display_avatar.url)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, ephemeral=reminder.private, components=components)
if reminder.remind_at <= datetime.now(tz=timezone.utc) and not reminder.active:
try:
@ -366,10 +343,7 @@ class RemindmeCog(Extension):
@_delete.autocomplete("content")
async def _search_reminders(self, ctx: AutocompleteContext) -> None:
reminders = await Reminder.find(Reminder.user == ctx.author.id).to_list()
lookup = {
f"[{r.created_at.strftime('%d/%m/%Y %H:%M.%S')}] {r.message}": str(r.id)
for r in reminders
}
lookup = {f"[{r.created_at.strftime('%d/%m/%Y %H:%M.%S')}] {r.message}": str(r.id) for r in reminders}
results = process.extract(ctx.input_text, list(lookup.keys()), limit=5)
choices = [{"name": r[0], "value": lookup[r[0]]} for r in results]
await ctx.send(choices=choices)

View file

@ -55,9 +55,7 @@ class UtilCog(Extension):
bot = SlashCommand(name="bot", description="Bot commands")
@bot.subcommand(
sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS"
)
@bot.subcommand(sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS")
async def _donate(self, ctx: SlashContext) -> None:
await ctx.send(
"""Want to support JARVIS? Donate here:
@ -68,9 +66,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
"""
)
@bot.subcommand(
sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS"
)
@bot.subcommand(sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS")
async def _donate(self, ctx: SlashContext) -> None:
await ctx.send(
"""Want to support JARVIS? Donate here:
@ -111,10 +107,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
)
)
self.bot.logger.debug("Getting repo information")
repo_url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/"
fields.append(
EmbedField(name="Online Since", value=f"<t:{uptime}:F>", inline=False)
)
fields.append(EmbedField(name="Online Since", value=f"<t:{uptime}:F>", inline=False))
num_domains = len(self.bot.phishing_domains)
fields.append(
EmbedField(
@ -123,9 +116,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
)
)
embed = build_embed(title=title, description=desc, fields=fields, color=color)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@bot.subcommand(
@ -188,9 +179,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
embed.set_image(url=avatar)
embed.set_author(name=f"{user.username}", icon_url=avatar)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@slash_command(name="emotes", description="Get all emotes")
@ -246,9 +235,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
EmbedField(name="Name", value=role.mention, inline=True),
EmbedField(name="Color", value=str(role.color.hex), inline=True),
EmbedField(name="Mention", value=f"`{role.mention}`", inline=True),
EmbedField(
name="Hoisted", value="Yes" if role.hoist else "No", inline=True
),
EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True),
EmbedField(name="Position", value=str(role.position), inline=True),
EmbedField(
name="Mentionable",
@ -256,9 +243,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
inline=True,
),
EmbedField(name="Member Count", value=str(len(role.members)), inline=True),
EmbedField(
name="Created At", value=f"<t:{int(role.created_at.timestamp())}:F>"
),
EmbedField(name="Created At", value=f"<t:{int(role.created_at.timestamp())}:F>"),
]
embed = build_embed(
title="",
@ -331,18 +316,14 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
),
EmbedField(
name=f"Roles [{len(user_roles)}]",
value=(
" ".join([x.mention for x in user_roles]) if user_roles else "None"
),
value=(" ".join([x.mention for x in user_roles]) if user_roles else "None"),
inline=False,
),
]
if muted:
ts = int(user.communication_disabled_until.timestamp())
fields.append(
EmbedField(name="Muted Until", value=f"<t:{ts}:F> (<t:{ts}:R>)")
)
fields.append(EmbedField(name="Muted Until", value=f"<t:{ts}:F> (<t:{ts}:R>)"))
embed = build_embed(
title="",
@ -357,9 +338,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
)
embed.set_thumbnail(url=user.display_avatar.url)
embed.set_footer(text=f"ID: {user.id}")
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@slash_command(name="lmgtfy", description="Let me Google that for you")
@ -396,9 +375,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
owner = await guild.fetch_owner()
owner = (
f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
)
owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
categories = len([x for x in guild.channels if isinstance(x, GuildCategory)])
text_channels = len([x for x in guild.channels if isinstance(x, GuildText)])
@ -417,45 +394,31 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
EmbedField(name="Threads", value=str(threads), inline=True),
EmbedField(name="Members", value=str(members), inline=True),
EmbedField(name="Roles", value=str(roles), inline=True),
EmbedField(
name="Created At", value=f"<t:{int(guild.created_at.timestamp())}:F>"
),
EmbedField(name="Created At", value=f"<t:{int(guild.created_at.timestamp())}:F>"),
]
role_embeds = []
if len(comma_role_list) < 1024:
fields.append(
EmbedField(
name=f"Role List [{roles}]", value=comma_role_list, inline=False
)
)
fields.append(EmbedField(name=f"Role List [{roles}]", value=comma_role_list, inline=False))
else:
current_role_list = role_list[0].mention
for role in role_list[1:]:
if len(current_role_list + ", " + role.mention) > 3192:
role_embed = build_embed(
title="", description=current_role_list, fields=[]
)
role_embed = build_embed(title="", description=current_role_list, fields=[])
role_embeds.append(role_embed)
current_role_list = role.mention
else:
current_role_list += ", " + role.mention
if len(current_role_list) > 0:
role_embed = build_embed(
title="", description=current_role_list, fields=[]
)
role_embed = build_embed(title="", description=current_role_list, fields=[])
role_embeds.append(role_embed)
embed = build_embed(
title="", description="", fields=fields, timestamp=guild.created_at
)
embed = build_embed(title="", description="", fields=fields, timestamp=guild.created_at)
embed.set_author(name=guild.name, icon_url=guild.icon.url)
embed.set_thumbnail(url=guild.icon.url)
embed.set_footer(text=f"ID: {guild.id} | Server Created")
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@slash_command(
@ -482,9 +445,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
],
)
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
async def _pw_gen(
self, ctx: SlashContext, length: int = 32, chars: int = 3
) -> None:
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", ephemeral=True)
return
@ -541,18 +502,14 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
opt_type=OptionType.BOOLEAN,
required=False,
)
async def _timestamp(
self, ctx: SlashContext, string: str, private: bool = False
) -> None:
async def _timestamp(self, ctx: SlashContext, string: str, private: bool = False) -> None:
timestamp = parse(string)
if not timestamp:
await ctx.send("Valid time not found, try again", ephemeral=True)
return
if not timestamp.tzinfo:
timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(
tz=timezone.utc
)
timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(tz=timezone.utc)
timestamp_utc = timestamp.astimezone(tz=timezone.utc)
@ -565,12 +522,8 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
EmbedField(name="Relative Time", value=f"<t:{ts_utc}:R>\n`<t:{ts_utc}:R>`"),
EmbedField(name="ISO8601", value=timestamp.isoformat()),
]
embed = build_embed(
title="Converted Time", description=f"`{string}`", fields=fields
)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
embed = build_embed(title="Converted Time", description=f"`{string}`", fields=fields)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, ephemeral=private, components=components)
@bot.subcommand(sub_cmd_name="support", sub_cmd_description="Got issues?")

View file

@ -1,4 +1,5 @@
"""JARVIS extra, optional cogs"""
from interactions import Client
from jarvis.cogs.extra import calc, dev, image, pinboard, rolegiver, tags

View file

@ -1,4 +1,5 @@
"""JARVIS Calculator Cog."""
from calculator import calculate
from erapi import const
from interactions import AutocompleteContext, Client, Extension, InteractionContext
@ -38,9 +39,7 @@ class CalcCog(Extension):
calc = SlashCommand(name="calc", description="Calculate some things")
@calc.subcommand(
sub_cmd_name="math", sub_cmd_description="Do a basic math calculation"
)
@calc.subcommand(sub_cmd_name="math", sub_cmd_description="Do a basic math calculation")
@slash_option(
name="expression",
description="Expression to calculate",
@ -77,16 +76,12 @@ class CalcCog(Extension):
)
embed = build_embed(title="Calculator", description=None, fields=fields)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
convert = calc.group(name="convert", description="Conversion helpers")
@convert.subcommand(
sub_cmd_name="temperature", sub_cmd_description="Convert between temperatures"
)
@convert.subcommand(sub_cmd_name="temperature", sub_cmd_description="Convert between temperatures")
@slash_option(
name="value",
description="Value to convert",
@ -136,9 +131,7 @@ class CalcCog(Extension):
description=f"°{TEMP_LOOKUP.get(from_unit)} -> °{TEMP_LOOKUP.get(to_unit)}",
fields=fields,
)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@convert.subcommand(
@ -201,9 +194,7 @@ class CalcCog(Extension):
)
await ctx.send(embeds=embed, components=components)
async def _convert(
self, ctx: InteractionContext, from_: str, to: str, value: int
) -> Embed:
async def _convert(self, ctx: InteractionContext, from_: str, to: str, value: int) -> Embed:
*_, which = ctx.invoke_target.split(" ")
which = getattr(units, which.capitalize(), None)
ratio = which.get_rate(from_, to)
@ -212,12 +203,8 @@ class CalcCog(Extension):
EmbedField(name=from_, value=f"{value:0.2f}"),
EmbedField(name=to, value=f"{converted:0.2f}"),
)
embed = build_embed(
title="Conversion", description=f"{from_} -> {to}", fields=fields
)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
embed = build_embed(title="Conversion", description=f"{from_} -> {to}", fields=fields)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@convert.subcommand(sub_cmd_name="angle", sub_cmd_description="Convert angles")
@ -241,9 +228,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_angle(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_angle(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="area", sub_cmd_description="Convert areas")
@ -267,9 +252,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_area(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_area(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="data", sub_cmd_description="Convert data sizes")
@ -293,9 +276,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_data(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_data(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="energy", sub_cmd_description="Convert energy")
@ -319,9 +300,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_energy(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_energy(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="length", sub_cmd_description="Convert lengths")
@ -345,9 +324,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_length(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_length(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="power", sub_cmd_description="Convert powers")
@ -371,14 +348,10 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_power(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_power(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(
sub_cmd_name="pressure", sub_cmd_description="Convert pressures"
)
@convert.subcommand(sub_cmd_name="pressure", sub_cmd_description="Convert pressures")
@slash_option(
name="value",
description="Pressure to convert",
@ -399,9 +372,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_pressure(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_pressure(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="speed", sub_cmd_description="Convert speeds")
@ -425,9 +396,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_speed(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_speed(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="time", sub_cmd_description="Convert times")
@ -451,9 +420,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_time(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_time(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="volume", sub_cmd_description="Convert volumes")
@ -477,9 +444,7 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_volume(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_volume(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
@convert.subcommand(sub_cmd_name="weight", sub_cmd_description="Convert weights")
@ -503,17 +468,13 @@ class CalcCog(Extension):
required=True,
autocomplete=True,
)
async def _calc_convert_weight(
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
) -> None:
async def _calc_convert_weight(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
await self._convert(ctx, from_unit, to_unit, value)
def _unit_autocomplete(
self, which: units.Converter, unit: str
) -> list[dict[str, str]]:
def _unit_autocomplete(self, which: units.Converter, unit: str) -> list[dict[str, str]]:
options = list(which.CONVERSIONS.keys())
results = process.extract(unit, options, limit=25)
if any([r[1] > 0 for r in results]):
if any(r[1] > 0 for r in results):
return [{"name": r[0], "value": r[0]} for r in results if r[1] > 50]
return [{"name": r[0], "value": r[0]} for r in results]
@ -564,8 +525,8 @@ class CalcCog(Extension):
if r[1] > results[name]:
results[name] = r[1]
results = sorted(list(results.items()), key=lambda x: -x[1])[:10]
if any([r[1] > 0 for r in results]):
results = sorted(results.items(), key=lambda x: -x[1])[:10]
if any(r[1] > 0 for r in results):
return [{"name": r[0], "value": lookup_name[r[0]]} for r in results if r[1]]
return [{"name": r[0], "value": lookup_name[r[0]]} for r in results]

View file

@ -1,12 +1,14 @@
"""JARVIS Developer Cog."""
import base64
import hashlib
import logging
import re
import subprocess # noqa: S404
import subprocess
import uuid as uuidpy
from datetime import datetime
from io import BytesIO
from typing import ClassVar
import nanoid
import pytz
@ -40,11 +42,9 @@ from jarvis.utils import build_embed
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]|[$-_@.&+]|[!*\(\),]|(?:%[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>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$" # noqa: E501
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(
@ -127,7 +127,7 @@ class DevCog(Extension):
self,
ctx: InteractionContext,
method: str,
data: str = None,
data: str = None, # noqa: RUF013
attach: Attachment = None,
) -> None:
if not data and not attach:
@ -146,12 +146,8 @@ class DevCog(Extension):
elif url.match(data):
try:
if (size := await get_size(data)) > MAX_FILESIZE:
await ctx.send(
"Please hash files that are <= 5GB in size", ephemeral=True
)
self.logger.debug(
f"Refused to hash file of size {convert_bytesize(size)}"
)
await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True)
self.logger.debug(f"Refused to hash file of size {convert_bytesize(size)}")
return
except Exception as e:
await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True)
@ -174,9 +170,7 @@ class DevCog(Extension):
]
embed = build_embed(title=title, description=description, fields=fields)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@dev.subcommand(sub_cmd_name="uuid", sub_cmd_description="Generate a UUID")
@ -193,9 +187,7 @@ class DevCog(Extension):
opt_type=OptionType.STRING,
required=False,
)
async def _uuid(
self, ctx: InteractionContext, version: str, data: str = None
) -> None:
async def _uuid(self, ctx: InteractionContext, version: str, data: str = None) -> None: # noqa: RUF013
version = int(version)
if version in [3, 5] and not data:
await ctx.send(f"UUID{version} requires data.", ephemeral=True)
@ -220,7 +212,7 @@ class DevCog(Extension):
)
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
async def _objectid(self, ctx: InteractionContext) -> None:
await ctx.send(f"ObjectId: `{str(ObjectId())}`")
await ctx.send(f"ObjectId: `{ObjectId()!s}`")
@dev.subcommand(
sub_cmd_name="ulid",
@ -266,7 +258,7 @@ class DevCog(Extension):
else:
await ctx.send("Invalid ULID.")
base64_methods = ["b64", "b16", "b32", "a85", "b85"]
base64_methods: ClassVar[list[str]] = ["b64", "b16", "b32", "a85", "b85"]
@dev.subcommand(sub_cmd_name="encode", sub_cmd_description="Encode some data")
@slash_option(
@ -301,9 +293,7 @@ class DevCog(Extension):
EmbedField(name=mstr, value=f"`{encoded}`", inline=False),
]
embed = build_embed(title="Encoded Data", description="", fields=fields)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@dev.subcommand(sub_cmd_name="decode", sub_cmd_description="Decode some data")
@ -339,18 +329,14 @@ class DevCog(Extension):
EmbedField(name=mstr, value=f"`{decoded}`", inline=False),
]
embed = build_embed(title="Decoded Data", description="", fields=fields)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@dev.subcommand(sub_cmd_name="cloc", sub_cmd_description="Get JARVIS lines of code")
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
async def _cloc(self, ctx: InteractionContext) -> None:
await ctx.defer()
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode(
"UTF-8"
) # noqa: S603, S607
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8")
console = Console()
with console.capture() as capture:
console.print(output)

View file

@ -1,4 +1,5 @@
"""JARVIS image processing cog."""
import logging
import re
from io import BytesIO
@ -62,7 +63,7 @@ class ImageCog(Extension):
required=False,
)
async def _resize(
self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None
self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None # noqa: RUF013
) -> None:
await ctx.defer()
if not attachment and not url:

View file

@ -1,4 +1,5 @@
"""JARVIS Starboard Cog."""
import asyncio
import logging
@ -44,9 +45,7 @@ class PinboardCog(Extension):
async def _purge_starboard(self, ctx: InteractionContext, board: Pinboard) -> None:
channel = await ctx.guild.fetch_channel(board.channel)
async for pin in Pin.find(
Pin.pinboard == channel.id, Pin.guild == ctx.guild.id
):
async for pin in Pin.find(Pin.pinboard == channel.id, Pin.guild == ctx.guild.id):
if message := await channel.fetch_message(pin.message):
try:
await message.delete()
@ -91,13 +90,9 @@ class PinboardCog(Extension):
await ctx.send("Channel must be a GuildText", ephemeral=True)
return
exists = await Pinboard.find_one(
Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id
)
exists = await Pinboard.find_one(Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id)
if exists:
await ctx.send(
f"Pinboard already exists at {channel.mention}.", ephemeral=True
)
await ctx.send(f"Pinboard already exists at {channel.mention}.", ephemeral=True)
return
count = await Pinboard.find(Pinboard.guild == ctx.guild.id).count()
@ -121,17 +116,16 @@ class PinboardCog(Extension):
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD, Permissions.MANAGE_MESSAGES))
async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None:
found = await Pinboard.find_one(
Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id
)
found = await Pinboard.find_one(Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id)
if found:
await found.delete()
asyncio.create_task(self._purge_starboard(ctx, found))
task = asyncio.create_task(self._purge_starboard(ctx, found))
await ctx.send(f"Pinboard deleted from {channel.mention}.")
await asyncio.wait([task])
else:
await ctx.send(f"Pinboard not found in {channel.mention}.", ephemeral=True)
async def _star_add(
async def _star_add( # noqa: C901
self,
ctx: InteractionContext,
message: str,
@ -166,9 +160,7 @@ class PinboardCog(Extension):
channel_list.append(c)
channel_to_pinboard[c.id] = pinboard
else:
self.logger.warning(
f"Pinboard {pinboard.channel} no longer valid in {ctx.guild.name}"
)
self.logger.warning(f"Pinboard {pinboard.channel} no longer valid in {ctx.guild.name}")
to_delete.append(pinboard)
for pinboard in to_delete:
@ -182,10 +174,7 @@ class PinboardCog(Extension):
if x:
select_channels.append(StringSelectOption(label=x.name, value=str(idx)))
select_channels = [
StringSelectOption(label=x.name, value=str(idx))
for idx, x in enumerate(channel_list)
]
select_channels = [StringSelectOption(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list)]
select = StringSelectMenu(
*select_channels,
@ -219,9 +208,7 @@ class PinboardCog(Extension):
)
return
count = await Pin.find(
Pin.guild == ctx.guild.id, Pin.pinboard == pinboard.id
).count()
count = await Pin.find(Pin.guild == ctx.guild.id, Pin.pinboard == pinboard.id).count()
content = message.content
attachments = message.attachments

View file

@ -1,4 +1,5 @@
"""JARVIS Role Giver Cog."""
import asyncio
import logging
@ -69,15 +70,11 @@ class RolegiverCog(Extension):
rolegiver.roles = roles
await rolegiver.save()
rolegiver = SlashCommand(
name="rolegiver", description="Allow users to choose their own roles"
)
rolegiver = SlashCommand(name="rolegiver", description="Allow users to choose their own roles")
rg_group = rolegiver.group(name="group", description="Manage rolegiver groups")
@rg_group.subcommand(
sub_cmd_name="create", sub_cmd_description="Create a rolegiver group"
)
@rg_group.subcommand(sub_cmd_name="create", sub_cmd_description="Create a rolegiver group")
@slash_option(
name="group",
description="Group to create",
@ -86,9 +83,7 @@ class RolegiverCog(Extension):
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _group_create(self, ctx: InteractionContext, group: str):
if await Rolegiver.find_one(
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
):
if await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group):
await ctx.send("Group already exists!", ephemeral=True)
return
@ -102,9 +97,7 @@ class RolegiverCog(Extension):
elif rolegiver.group not in self._group_cache[ctx.guild.id]:
self._group_cache[ctx.guild.id][rolegiver.group] = {}
@rg_group.subcommand(
sub_cmd_name="delete", sub_cmd_description="DDelete a rolegiver group"
)
@rg_group.subcommand(sub_cmd_name="delete", sub_cmd_description="DDelete a rolegiver group")
@slash_option(
name="group",
description="Group to delete",
@ -114,9 +107,7 @@ class RolegiverCog(Extension):
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _group_delete(self, ctx: InteractionContext, group: str):
if rolegiver := await Rolegiver.find_one(
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
):
if rolegiver := await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group):
await rolegiver.delete()
await ctx.send(f"Rolegiver group {group} deleted!")
del self._group_cache[ctx.guild.id][rolegiver.group]
@ -127,9 +118,7 @@ class RolegiverCog(Extension):
sub_cmd_name="add",
sub_cmd_description="Add a role to rolegiver",
)
@slash_option(
name="role", description="Role to add", opt_type=OptionType.ROLE, required=True
)
@slash_option(name="role", description="Role to add", opt_type=OptionType.ROLE, required=True)
@slash_option(
name="group",
description="Group to add to",
@ -138,9 +127,7 @@ class RolegiverCog(Extension):
autocomplete=True,
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _rolegiver_add(
self, ctx: InteractionContext, role: Role, group: str = "Default"
) -> None:
async def _rolegiver_add(self, ctx: InteractionContext, role: Role, group: str = "Default") -> None:
if role.id == ctx.guild.id:
await ctx.send("Cannot add `@everyone` to rolegiver", ephemeral=True)
return
@ -152,14 +139,12 @@ class RolegiverCog(Extension):
)
return
rolegiver = await Rolegiver.find_one(
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
)
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
if rolegiver and rolegiver.roles and role.id in rolegiver.roles:
await ctx.send("Role already in rolegiver", ephemeral=True)
return
elif rolegiver and len(rolegiver.roles) >= 15:
if rolegiver and len(rolegiver.roles) >= 15:
await ctx.send(
f"Maximum roles in group {group}. Please make a new group",
ephemeral=True,
@ -200,18 +185,14 @@ class RolegiverCog(Extension):
embed.set_thumbnail(url=ctx.guild.icon.url)
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
if ctx.guild.id not in self._group_cache:
self._group_cache[ctx.guild.id] = {group: {}}
self._group_cache[ctx.guild.id][group].update({role.name: role.id})
@rolegiver.subcommand(
sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver"
)
@rolegiver.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver")
@slash_option(
name="group",
description="Name of group to remove from",
@ -220,9 +201,7 @@ class RolegiverCog(Extension):
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _rolegiver_remove(self, ctx: InteractionContext, group: str) -> None:
rolegiver = await Rolegiver.find_one(
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
)
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
if not rolegiver or (rolegiver and not rolegiver.roles):
await ctx.send(f"Rolegiver {group} has no roles", ephemeral=True)
return
@ -234,11 +213,9 @@ class RolegiverCog(Extension):
if role:
roles[int(role.id)] = role
rolegiver.roles = [r for r in roles.keys()]
rolegiver.roles = list(roles.keys())
options = [
StringSelectOption(label=v.name, value=str(k)) for k, v in roles.items()
][:25]
options = [StringSelectOption(label=v.name, value=str(k)) for k, v in roles.items()][:25]
select = StringSelectMenu(
*options,
placeholder="Select roles to remove",
@ -247,9 +224,7 @@ class RolegiverCog(Extension):
)
components = [ActionRow(select)]
message = await ctx.send(
content=f"Removing roles from {group}", components=components
)
message = await ctx.send(content=f"Removing roles from {group}", components=components)
try:
resp = await self.bot.wait_for_component(
@ -279,9 +254,7 @@ class RolegiverCog(Extension):
),
EmbedField(
name="Remaining Role(s)",
value="\n".join(
x.mention for x in roles.values() if x not in removed_roles
),
value="\n".join(x.mention for x in roles.values() if x not in removed_roles),
),
]
@ -297,13 +270,9 @@ class RolegiverCog(Extension):
embeds=embed,
)
self._group_cache[ctx.guild.id].update(
{group: {v.name: k for k, v in roles.items() if v not in removed_roles}}
)
self._group_cache[ctx.guild.id].update({group: {v.name: k for k, v in roles.items() if v not in removed_roles}})
@rolegiver.subcommand(
sub_cmd_name="list", sub_cmd_description="List rolegiver roles"
)
@rolegiver.subcommand(sub_cmd_name="list", sub_cmd_description="List rolegiver roles")
@slash_option(
name="group",
description="Name of group to list",
@ -311,12 +280,8 @@ class RolegiverCog(Extension):
required=False,
autocomplete=True,
)
async def _rolegiver_list(
self, ctx: InteractionContext, group: str = "Default"
) -> None:
setting = await Rolegiver.find_one(
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
)
async def _rolegiver_list(self, ctx: InteractionContext, group: str = "Default") -> None:
setting = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
if not setting or (setting and not setting.roles):
await ctx.send("Rolegiver has no roles", ephemeral=True)
return
@ -342,9 +307,7 @@ class RolegiverCog(Extension):
embed.set_thumbnail(url=ctx.guild.icon.url)
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
role = SlashCommand(name="role", description="Get roles!")
@ -359,9 +322,7 @@ class RolegiverCog(Extension):
)
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
async def _role_get(self, ctx: InteractionContext, group: str = "Default") -> None:
rolegiver = await Rolegiver.find_one(
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
)
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
if not rolegiver or (rolegiver and not rolegiver.roles):
await ctx.send(f"Rolegiver group {group} has no roles", ephemeral=True)
return
@ -409,9 +370,7 @@ class RolegiverCog(Extension):
added_roles.append(role)
await ctx.author.add_role(role, reason="Rolegiver")
avalue = (
"\n".join([r.mention for r in added_roles]) if added_roles else "None"
)
avalue = "\n".join([r.mention for r in added_roles]) if added_roles else "None"
fields = [
EmbedField(name="Added Role(s)", value=avalue),
]
@ -434,9 +393,7 @@ class RolegiverCog(Extension):
for component in row.components:
component.disabled = True
await response.ctx.edit_origin(
embeds=embed, content="\u200b", components=components
)
await response.ctx.edit_origin(embeds=embed, content="\u200b", components=components)
except asyncio.TimeoutError:
for row in components:
for component in row.components:
@ -452,21 +409,15 @@ class RolegiverCog(Extension):
autocomplete=True,
)
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
async def _role_remove(
self, ctx: InteractionContext, group: str = "Default"
) -> None:
async def _role_remove(self, ctx: InteractionContext, group: str = "Default") -> None:
user_roles = ctx.author.roles
rolegiver = await Rolegiver.find_one(
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
)
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
if not rolegiver or (rolegiver and not rolegiver.roles):
await ctx.send("Rolegiver has no roles", ephemeral=True)
return
elif not any(x.id in rolegiver.roles for x in user_roles):
await ctx.send(
f"You have no rolegiver roles from group {group}", ephemeral=True
)
if not any(x.id in rolegiver.roles for x in user_roles):
await ctx.send(f"You have no rolegiver roles from group {group}", ephemeral=True)
return
valid = list(filter(lambda x: x.id in rolegiver.roles, user_roles))
@ -500,11 +451,7 @@ class RolegiverCog(Extension):
user_roles.remove(role)
removed_roles.append(role)
rvalue = (
"\n".join([r.mention for r in removed_roles])
if removed_roles
else "None"
)
rvalue = "\n".join([r.mention for r in removed_roles]) if removed_roles else "None"
fields = [
EmbedField(name="Removed Role(s)", value=rvalue),
]
@ -516,9 +463,7 @@ class RolegiverCog(Extension):
)
embed.set_thumbnail(url=ctx.guild.icon.url)
embed.set_author(
name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url
)
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
@ -526,9 +471,7 @@ class RolegiverCog(Extension):
for component in row.components:
component.disabled = True
await context.ctx.edit_origin(
embeds=embed, components=components, content="\u200b"
)
await context.ctx.edit_origin(embeds=embed, components=components, content="\u200b")
except asyncio.TimeoutError:
for row in components:
@ -574,9 +517,7 @@ class RolegiverCog(Extension):
if rolegivers:
await self._update_cache_from_autocomplete(ctx, rolegivers)
async def _update_cache_from_autocomplete(
self, ctx: AutocompleteContext, rolegivers: list[Rolegiver]
) -> None:
async def _update_cache_from_autocomplete(self, ctx: AutocompleteContext, rolegivers: list[Rolegiver]) -> None:
"""Update the cache with a lock to prevent multiple simultaneous updates"""
lock = self._cache_update_locks.get(ctx.guild.id)
if not lock:
@ -595,9 +536,7 @@ class RolegiverCog(Extension):
continue
roles.append(role)
await r.save()
self._group_cache[ctx.guild.id][r.group] = {
role.name: role.role.id for role in roles
}
self._group_cache[ctx.guild.id][r.group] = {role.name: role.role.id for role in roles}
def setup(bot: Client) -> None:

View file

@ -1,4 +1,5 @@
"""JARVIS Tags Cog."""
import asyncio
import re
from datetime import datetime, timezone
@ -26,7 +27,7 @@ from thefuzz import process
from jarvis.utils import build_embed
invites = re.compile(
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
flags=re.IGNORECASE,
)
tag_name = re.compile(r"^[\w\ \-]{1,40}$")
@ -82,17 +83,13 @@ class TagCog(Extension):
await ctx.send_modal(modal)
try:
response = await self.bot.wait_for_modal(
modal, author=ctx.author.id, timeout=60 * 5
)
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
name = response.responses.get("name").replace("`", "")
content = response.responses.get("content")
except asyncio.TimeoutError:
return
noinvite = await Setting.find_one(
Setting.guild == ctx.guild.id, Setting.setting == "noinvite"
)
noinvite = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "noinvite")
if (
(invites.search(content) or invites.search(name))
@ -102,17 +99,13 @@ class TagCog(Extension):
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
)
):
await response.send(
"Listen, don't use this to try and bypass the rules", ephemeral=True
)
await response.send("Listen, don't use this to try and bypass the rules", ephemeral=True)
return
elif not content.strip() or not name.strip():
if not content.strip() or not name.strip():
await response.send("Content and name required", ephemeral=True)
return
elif not tag_name.match(name):
await response.send(
"Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True
)
if not tag_name.match(name):
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
return
tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
@ -144,9 +137,7 @@ class TagCog(Extension):
icon_url=ctx.author.display_avatar.url,
)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await response.send(embeds=embed, components=components)
if ctx.guild.id not in self.cache:
@ -167,13 +158,11 @@ class TagCog(Extension):
if not tag:
await ctx.send("Tag not found", ephemeral=True)
return
elif tag.creator != ctx.author.id and not (
if tag.creator != ctx.author.id and not (
ctx.author.has_permission(Permissions.ADMINISTRATOR)
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
):
await ctx.send(
"You didn't create this tag, ask the creator to edit it", ephemeral=True
)
await ctx.send("You didn't create this tag, ask the creator to edit it", ephemeral=True)
return
modal = Modal(
@ -198,9 +187,7 @@ class TagCog(Extension):
await ctx.send_modal(modal)
try:
response = await self.bot.wait_for_modal(
modal, author=ctx.author.id, timeout=60 * 5
)
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
name = response.responses.get("name").replace("`", "")
content = response.responses.get("content")
except asyncio.TimeoutError:
@ -214,9 +201,7 @@ class TagCog(Extension):
)
return
noinvite = await Setting.find_one(
Setting.guild == ctx.guild.id, Setting.setting == "noinvite"
)
noinvite = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "noinvite")
if (
(invites.search(content) or invites.search(name))
@ -226,17 +211,13 @@ class TagCog(Extension):
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
)
):
await response.send(
"Listen, don't use this to try and bypass the rules", ephemeral=True
)
await response.send("Listen, don't use this to try and bypass the rules", ephemeral=True)
return
elif not content.strip() or not name.strip():
if not content.strip() or not name.strip():
await response.send("Content and name required", ephemeral=True)
return
elif not tag_name.match(name):
await response.send(
"Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True
)
if not tag_name.match(name):
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
return
tag.content = re.sub(r"\\?([@<])", r"\\\g<1>", content)
@ -259,9 +240,7 @@ class TagCog(Extension):
name=ctx.author.username + "#" + ctx.author.discriminator,
icon_url=ctx.author.display_avatar.url,
)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await response.send(embeds=embed, components=components)
if tag.name not in self.cache[ctx.guild.id]:
self.cache[ctx.guild.id].remove(old_name)
@ -280,7 +259,7 @@ class TagCog(Extension):
if not tag:
await ctx.send("Tag not found", ephemeral=True)
return
elif tag.creator != ctx.author.id and not (
if tag.creator != ctx.author.id and not (
ctx.author.has_permission(Permissions.ADMINISTRATOR)
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
):
@ -341,9 +320,7 @@ class TagCog(Extension):
name=f"{username}#{discrim}" if username else "Unknown User",
icon_url=url,
)
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@tag.subcommand(sub_cmd_name="list", sub_cmd_description="List tag names")
@ -351,9 +328,7 @@ class TagCog(Extension):
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
names = "\n".join(f"`{t.name}`" for t in tags)
embed = build_embed(title="All Tags", description=names, fields=[])
components = Button(
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
)
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
await ctx.send(embeds=embed, components=components)
@_get.autocomplete("name")
@ -364,9 +339,7 @@ class TagCog(Extension):
if not self.cache.get(ctx.guild.id):
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
self.cache[ctx.guild.id] = [tag.name for tag in tags]
results = process.extract(
ctx.input_text, self.cache.get(ctx.guild.id), limit=25
)
results = process.extract(ctx.input_text, self.cache.get(ctx.guild.id), limit=25)
choices = [{"name": r[0], "value": r[0]} for r in results]
await ctx.send(choices=choices)

View file

@ -1,4 +1,5 @@
"""JARVIS guild-specific cogs"""
from interactions import Client
from jarvis.cogs.unique import ctc2, dbrand, gl

View file

@ -5,12 +5,13 @@ This cog is now abandoned due to conflict with the dbrand moderators.
Please do not file feature requests related to this cog; they will be closed.
This cog has been abandoned by @zevaryx and any support must come through PRs.
This cog has been abandoned by @zevaryx and any support must come through PRs.
No longer being a part of the community and hearing about *certain users* talking about me behind my back
that request said features, means that I am no longer putting any energy into this. If you want to keep
it running, make a PR to fix things.
"""
import logging
import re
@ -35,7 +36,7 @@ guild_ids = [] # [578757004059738142, 520021794380447745, 862402786116763668]
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
invites = re.compile(
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
flags=re.IGNORECASE,
)
@ -52,9 +53,7 @@ class CTCCog(Extension):
def __del__(self):
self._session.close()
ctc2 = SlashCommand(
name="ctc2", description="CTC2 related commands", scopes=guild_ids
)
ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopes=guild_ids)
@ctc2.subcommand(sub_cmd_name="about")
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
@ -87,23 +86,17 @@ class CTCCog(Extension):
async def _pw(self, ctx: InteractionContext, 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."),
ephemeral=True,
)
return
elif not valid.fullmatch(guess):
if 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*."),
ephemeral=True,
)
return
elif invites.search(guess):
if invites.search(guess):
await ctx.send(
"Listen here, dipshit. No using this to bypass sending invite links.",
ephemeral=True,

View file

@ -5,7 +5,7 @@ This cog is now abandoned due to conflict with the dbrand moderators.
Please do not file feature requests related to this cog; they will be closed.
This cog has been abandoned by @zevaryx and any support must come through PRs.
This cog has been abandoned by @zevaryx and any support must come through PRs.
No longer being a part of the community and hearing about *certain users* talking about me behind my back
that request said features, means that I am no longer putting any energy into this. If you want to keep
@ -22,16 +22,13 @@ from interactions import Client, Extension, InteractionContext
from interactions.client.utils import find
from interactions.models.discord.embed import EmbedField
from interactions.models.internal.application_commands import (
OptionType,
SlashCommand,
slash_option,
)
from interactions.models.internal.command import cooldown
from interactions.models.internal.cooldowns import Buckets
from thefuzz import process
from jarvis.branding import CUSTOM_EMOJIS
from jarvis.config import load_config
from jarvis.data.dbrand import shipping_lookup
from jarvis.utils import build_embed
@ -59,9 +56,7 @@ async def parse_db_status() -> dict:
cells = row.find_all("td")
for cell in cells:
if "column--comment" in cell["class"]:
status_title, detail, *_ = [
x.get_text() for x in cell.find_all("div")
]
status_title, detail, *_ = [x.get_text() for x in cell.find_all("div")]
text = status_title
if cell != "Unavailable":
text += ": " + detail
@ -79,9 +74,7 @@ async def parse_db_status() -> dict:
else:
cell = cell.get_text().strip()
row_data.append(cell)
data[data_key].append(
{headers[idx]: value for idx, value in enumerate(row_data)}
)
data[data_key].append({headers[idx]: value for idx, value in enumerate(row_data)})
return data
@ -106,24 +99,17 @@ class DbrandCog(Extension):
db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids)
@db.subcommand(
sub_cmd_name="status", sub_cmd_description="Get dbrand operational status"
)
@db.subcommand(sub_cmd_name="status", sub_cmd_description="Get dbrand operational status")
async def _status(self, ctx: InteractionContext) -> None:
try:
status = self.cache.get("status")
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
status = await parse_db_status()
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
hours=2
)
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=2)
self.cache["status"] = status
status = status.get("operations")
emojies = [x["Status"] for x in status]
fields = [
EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"])
for x in status
]
fields = [EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"]) for x in status]
color = "#FBBD1E"
if all("green" in x for x in emojies):
color = "#38F657"
@ -144,7 +130,7 @@ class DbrandCog(Extension):
)
await ctx.send(embeds=embed)
except:
except Exception:
self.bot.logger.error("Encountered error", exc_info=True)
async def _db_support_cmd(self, ctx: InteractionContext) -> None:
@ -245,7 +231,7 @@ class DbrandCog(Extension):
# required=True,
# )
# @cooldown(bucket=Buckets.USER, rate=1, interval=2)
async def _shipping(self, ctx: InteractionContext, search: str) -> None:
async def _shipping(self, ctx: InteractionContext, search: str) -> None: # noqa: C901
if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
if re.match(
r"^[\U0001f1e6-\U0001f1ff]{2}$",
@ -254,15 +240,11 @@ class DbrandCog(Extension):
):
# Magic number, subtract from flag char to get ascii char
uni2ascii = 127365
search = chr(ord(search[0]) - uni2ascii) + chr(
ord(search[1]) - uni2ascii
)
search = chr(ord(search[0]) - uni2ascii) + chr(ord(search[1]) - uni2ascii)
elif search == "🏳️":
search = "fr"
else:
await ctx.send(
"Please use text to search for shipping.", ephemeral=True
)
await ctx.send("Please use text to search for shipping.", ephemeral=True)
return
if len(search) > 3:
countries = {x["country"]: x["alpha-2"] for x in shipping_lookup}
@ -287,9 +269,7 @@ class DbrandCog(Extension):
data = await self._session.get(api_link)
if 200 <= data.status < 400:
data = await data.json()
data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
hours=24
)
data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
self.cache[dest] = data
else:
data = None
@ -298,18 +278,12 @@ class DbrandCog(Extension):
fields = []
for service in data["shipping_services_available"]:
service_data = self.cache.get(f"{dest}-{service}")
if not service_data or service_data["cache_expiry"] < datetime.now(
tz=timezone.utc
):
service_data = await self._session.get(
self.api_url + dest + "/" + service["url"]
)
if not service_data or service_data["cache_expiry"] < datetime.now(tz=timezone.utc):
service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
if service_data.status > 400:
continue
service_data = await service_data.json()
service_data["cache_expiry"] = datetime.now(
tz=timezone.utc
) + timedelta(hours=24)
service_data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
self.cache[f"{dest}-{service}"] = service_data
title = f'{service_data["carrier"]} {service_data["tier-title"]} | {service_data["costs-min"]}'
message = service_data["time-title"]
@ -320,9 +294,7 @@ class DbrandCog(Extension):
status = self.cache.get("status")
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
status = await parse_db_status()
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
hours=2
)
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=2)
self.cache["status"] = status
status = status["countries"]
@ -335,10 +307,10 @@ class DbrandCog(Extension):
description = ""
color = "#FFBB00"
if shipping_info:
description = f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}'
created = self.cache.get("status").get("cache_expiry") - timedelta(
hours=2
description = (
f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}'
)
created = self.cache.get("status").get("cache_expiry") - timedelta(hours=2)
ts = int(created.timestamp())
description += f" \u200b | \u200b Last updated: <t:{ts}:R>\n\u200b"
if "green" in shipping_info["Status"]:
@ -367,8 +339,7 @@ class DbrandCog(Extension):
embed = build_embed(
title="Check Shipping Times",
description=(
"Country not found.\nYou can [view all shipping "
"destinations here](https://dbrand.com/shipping)"
"Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)"
),
fields=[],
url="https://dbrand.com/shipping",

View file

@ -1,4 +1,5 @@
"""JARVIS GitLab Cog."""
import asyncio
import logging
from datetime import datetime

View file

@ -51,13 +51,9 @@ class Config(BaseSettings, case_sensitive=False):
log_level: str = "INFO"
jurigged: bool = False
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8", env_nested_delimiter="."
)
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", env_nested_delimiter=".")
def load_config() -> Config:
"""
Load the config using the specified method first
"""
"""Load the config."""
return Config()

View file

@ -1,5 +1,3 @@
"""JARVIS constants."""
from importlib.metadata import version as _v
__version__ = "2.5.4"

View file

@ -1,4 +1,5 @@
"""dbrand-specific data."""
shipping_lookup = [
{"country": "Afghanistan", "alpha-2": "AF", "alpha-3": "AFG", "numeric": "0004"},
{"country": "Aland Islands", "alpha-2": "AX", "alpha-3": "ALA", "numeric": "0248"},

View file

@ -1,4 +1,5 @@
"""Pigpen lookup."""
lookup = {
"a": "",
"b": "",

View file

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

View file

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

View file

@ -3,39 +3,41 @@ Unit Converter utilities.
Rates based on https://github.com/microsoft/calculator
"""
from dataclasses import dataclass, field
from typing import ClassVar
@dataclass
class Converter:
BASE: str = "sample"
CONVERSIONS: dict[str, int] = field(default_factory=dict)
CONVERSIONS: ClassVar[dict[str, float]] = field(default_factory=dict)
@classmethod
def get_rate(cls, from_: str, to: str) -> int:
def get_rate(cls, from_: str, to: str) -> float:
"""
Get the conversion rate
Args:
from_: Convert from this
to: Convert to this
"""
if from_ == cls.BASE:
return cls.CONVERSIONS.get(to, None)
else:
from_rate = cls.CONVERSIONS.get(from_)
to_rate = cls.CONVERSIONS.get(to)
return (1 / from_rate) * to_rate
from_rate = cls.CONVERSIONS.get(from_)
to_rate = cls.CONVERSIONS.get(to)
return (1 / from_rate) * to_rate
class Angle(Converter):
BASE = "degree"
CONVERSIONS = {"degree": 1, "radian": 57.29577951308233, "gradian": 0.9}
CONVERSIONS: ClassVar[dict[str, float]] = {"degree": 1, "radian": 57.29577951308233, "gradian": 0.9}
class Area(Converter):
BASE = "square meter"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"acre": 4046.8564224,
"square meter": 1,
"square foot": 0.09290304,
@ -56,7 +58,7 @@ class Area(Converter):
class Data(Converter):
BASE = "megabyte"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"bit": 0.000000125,
"byte": 0.000001,
"kilobyte": 0.001,
@ -99,7 +101,7 @@ class Data(Converter):
class Energy(Converter):
BASE = "joule"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"calorie": 4.184,
"kilocalorie": 4184,
"british thermal unit": 1055.056,
@ -115,7 +117,7 @@ class Energy(Converter):
class Length(Converter):
BASE = "meter"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"inch": 0.0254,
"foot": 0.3048,
"yard": 0.9144,
@ -135,7 +137,7 @@ class Length(Converter):
class Power(Converter):
BASE = "watt"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"british thermal unit per minute": 17.58426666666667,
"foot pound per minute": 0.0225969658055233,
"watt": 1,
@ -149,7 +151,7 @@ class Power(Converter):
class Pressure(Converter):
BASE = "atmosphere"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"atmosphere": 1,
"bar": 0.9869232667160128,
"kilopascal": 0.0098692326671601,
@ -161,7 +163,7 @@ class Pressure(Converter):
class Speed(Converter):
BASE = "centimeters per second"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"centimeters per second": 1,
"feet per second": 30.48,
"kilometers per hour": 27.777777777777777777778,
@ -177,7 +179,7 @@ class Speed(Converter):
class Time(Converter):
BASE = "second"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"day": 86400,
"second": 1,
"week": 604800,
@ -190,7 +192,7 @@ class Time(Converter):
class Volume(Converter):
BASE = "millileter"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"cup (US)": 236.588237,
"pint (US)": 473.176473,
"pint (UK)": 568.26125,
@ -219,7 +221,7 @@ class Volume(Converter):
class Weight(Converter):
BASE = "kilogram"
CONVERSIONS = {
CONVERSIONS: ClassVar[dict[str, float]] = {
"kilogram": 1,
"hectogram": 0.1,
"decagram": 0.01,

View file

@ -1,4 +1,5 @@
"""JARVIS bot-specific embeds."""
from typing import Optional
from interactions.models.discord.embed import Embed, EmbedField
@ -29,6 +30,7 @@ def ban_embed(
guild: Guild to ban from
duration: Optional temporary ban duration
dm: If the embed should be a user embed
"""
fields = [EmbedField(name="Reason", value=reason), EmbedField(name="Type", value=type)]
if duration:
@ -64,6 +66,7 @@ def unban_embed(user: User, admin: User, reason: str) -> Embed:
user: User to unban
admin: Admin to execute unban
reason: Reason for unban
"""
fields = (
EmbedField(name="Reason", value=reason),
@ -92,6 +95,7 @@ def kick_embed(user: Member, admin: Member, reason: str, guild: Guild, dm: bool
reason: Reason for kick
guild: Guild to kick from
dm: If the embed should be a user embed
"""
fields = [
EmbedField(name="Reason", value="A valid reason"),
@ -129,6 +133,7 @@ def mute_embed(user: Member, admin: Member, reason: str, guild: Guild) -> Embed:
user: User to mute
admin: Admin to execute mute
reason: Reason for mute
"""
until = int(user.communication_disabled_until.timestamp())
fields = (
@ -161,6 +166,7 @@ def unmute_embed(user: Member, admin: Member, reason: str, guild: Guild) -> Embe
user: User to unmute
admin: Admin to execute unmute
reason: Reason for unmute
"""
fields = (
EmbedField(name="Reason", value=reason),
@ -191,6 +197,7 @@ def warning_embed(user: Member, reason: str, admin: Member) -> Embed:
user: User to warn
reason: Warning reason
admin: Admin who sent the warning
"""
fields = (
EmbedField(name="Reason", value=reason, inline=False),

View file

@ -2,6 +2,7 @@
from datetime import datetime, timezone
from pkgutil import iter_modules
from typing import Optional
import pytz
from beanie.operators import Set
@ -19,7 +20,7 @@ def build_embed(
description: str,
fields: list,
color: str = PRIMARY_COLOR,
timestamp: datetime = None,
timestamp: Optional[datetime] = None,
**kwargs: dict,
) -> Embed:
"""Embed builder utility function."""
@ -42,8 +43,8 @@ def modlog_embed(
admin: Member,
title: str,
desc: str,
log: AuditLogEntry = None,
reason: str = None,
log: Optional[AuditLogEntry] = None,
reason: Optional[str] = None,
) -> Embed:
"""Get modlog embed."""
fields = [
@ -76,9 +77,7 @@ def get_extensions(path: str) -> list:
async def cleanup_user(bot: Client, user_id: int):
for model in models.all_models:
if hasattr(model, "admin"):
await model.find(model.admin == user_id).update(
Set({model.admin: bot.user.id})
)
await model.find(model.admin == user_id).update(Set({model.admin: bot.user.id}))
if hasattr(model, "user"):
await model.find(model.user == user_id).delete()
if hasattr(model, "creator"):

View file

@ -1,4 +1,5 @@
"""Cog wrapper for command caching."""
import logging
from datetime import timedelta
@ -30,12 +31,13 @@ class ModcaseCog(Extension):
self.logger = logging.getLogger(__name__)
self.add_extension_postrun(self.log)
async def log(self, ctx: InteractionContext, *_args: list, **kwargs: dict) -> None:
async def log(self, ctx: InteractionContext, *_args: list, **kwargs: dict) -> None: # noqa: C901
"""
Log a moderation activity in a moderation case.
Args:
ctx: Command context
"""
name = self.__name__.replace("Cog", "")
@ -101,13 +103,9 @@ class ModcaseCog(Extension):
)
if name == "Mute":
mts = int(user.communication_disabled_until.timestamp())
embed.add_field(
name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)"
)
embed.add_field(name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)")
guild_url = f"https://discord.com/channels/{ctx.guild.id}"
embed.set_author(
name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url
)
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url)
embed.set_thumbnail(url=ctx.guild.icon.url)
try:
await user.send(embeds=embed)
@ -126,9 +124,7 @@ class ModcaseCog(Extension):
await modlog.save()
return
modlog = await Setting.find_one(
Setting.guild == ctx.guild.id, Setting.setting == "modlog"
)
modlog = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "modlog")
if not modlog:
return
@ -148,31 +144,23 @@ class ModcaseCog(Extension):
description=f"Admin action has been taken against {user.mention}",
fields=fields,
)
embed.set_author(
name=f"{user.username}", icon_url=user.display_avatar.url
)
embed.set_author(name=f"{user.username}", icon_url=user.display_avatar.url)
embed.set_footer(text=f"User ID: {user.id}")
if name == "Mute":
mts = int(user.communication_disabled_until.timestamp())
embed.add_field(
name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)"
)
embed.add_field(name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)")
await channel.send(embeds=embed)
lookup_key = f"{user.id}|{ctx.guild.id}"
async with self.bot.redis.lock("lock|" + lookup_key):
if await self.bot.redis.get(lookup_key):
self.logger.debug(
f"User {user.id} in {ctx.guild.id} already has pending case"
)
self.logger.debug(f"User {user.id} in {ctx.guild.id} already has pending case")
return
channel = await ctx.guild.fetch_channel(modlog.value)
if not channel:
self.logger.warn(
f"Guild {ctx.guild.id} modlog channel no longer exists, deleting"
)
self.logger.warn(f"Guild {ctx.guild.id} modlog channel no longer exists, deleting")
await modlog.delete()
return
@ -187,19 +175,11 @@ class ModcaseCog(Extension):
embed.set_author(name=user.username, icon_url=avatar_url)
components = [
ActionRow(
Button(
style=ButtonStyle.RED, emoji="✖️", custom_id="modcase|no"
),
Button(
style=ButtonStyle.GREEN, emoji="✔️", custom_id="modcase|yes"
),
Button(style=ButtonStyle.RED, emoji="✖️", custom_id="modcase|no"),
Button(style=ButtonStyle.GREEN, emoji="✔️", custom_id="modcase|yes"),
)
]
message = await channel.send(embeds=embed, components=components)
await self.bot.redis.set(
lookup_key, f"{name.lower()}|{action.id}", ex=timedelta(days=7)
)
await self.bot.redis.set(
f"msg|{message.id}", user.id, ex=timedelta(days=7)
)
await self.bot.redis.set(lookup_key, f"{name.lower()}|{action.id}", ex=timedelta(days=7))
await self.bot.redis.set(f"msg|{message.id}", user.id, ex=timedelta(days=7))

View file

@ -1,4 +1,5 @@
"""Permissions wrappers."""
from interactions import InteractionContext, Permissions
@ -6,7 +7,7 @@ def admin_or_permissions(*perms: list) -> bool:
"""Check if a user is an admin or has other perms."""
async def predicate(ctx: InteractionContext) -> bool:
"""Extended check predicate.""" # noqa: D401
"""Extended check predicate."""
is_admin = ctx.author.has_permission(Permissions.ADMINISTRATOR)
has_other = any(ctx.author.has_permission(perm) for perm in perms)
return is_admin or has_other

View file

@ -1,5 +1,5 @@
"""JARVIS update handler."""
import logging
from dataclasses import dataclass
from importlib import import_module
from inspect import getmembers, isclass

View file

@ -128,7 +128,8 @@ ignore = [
# ... function
"D104",
# ... package
"E712",
# Ignore == True because of Beanie
# Plugins we don't currently include: flake8-return
"RET503",
# missing explicit return at the end of function ableto return non-None value.

7
run.py
View file

@ -1,13 +1,12 @@
"""Main run file for J.A.R.V.I.S."""
import nest_asyncio
nest_asyncio.apply()
import asyncio
import asyncio # noqa: E402
import nest_asyncio
from jarvis import run
from jarvis import run # noqa: E402
nest_asyncio.apply()