[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"> <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 # Just Another Rather Very Intelligent System

View file

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

View file

@ -1,4 +1,5 @@
"""JARVIS brandings.""" """JARVIS brandings."""
PRIMARY_COLOR = "#3498db" PRIMARY_COLOR = "#3498db"
HARD_ACTION = "#ff0000" HARD_ACTION = "#ff0000"
MODERATE_ACTION = "#ff9900" MODERATE_ACTION = "#ff9900"
@ -29,14 +30,14 @@ def get_command_color(command: str) -> str:
Returns: Returns:
Hex color string Hex color string
""" """
if command in COMMAND_TYPES["HARD"]: if command in COMMAND_TYPES["HARD"]:
return HARD_ACTION return HARD_ACTION
elif command in COMMAND_TYPES["MODERATE"]: if command in COMMAND_TYPES["MODERATE"]:
return MODERATE_ACTION return MODERATE_ACTION
elif command in COMMAND_TYPES["SOFT"]: if command in COMMAND_TYPES["SOFT"]:
return SOFT_ACTION return SOFT_ACTION
elif command in COMMAND_TYPES["GOOD"]: if command in COMMAND_TYPES["GOOD"]:
return GOOD_ACTION 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.""" """Custom JARVIS client."""
import logging import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -21,9 +22,7 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD)
class Jarvis(StatipyClient, ErrorMixin, EventMixin, TaskMixin): class Jarvis(StatipyClient, ErrorMixin, EventMixin, TaskMixin):
def __init__( def __init__(self, redis: "aioredis.Redis", erapi: str, *args, **kwargs):
self, redis: "aioredis.Redis", erapi: str, *args, **kwargs
): # noqa: ANN002 ANN003
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.redis = redis self.redis = redis
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
"""JARVIS temporary role handler.""" """JARVIS temporary role handler."""
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
@ -46,7 +47,7 @@ class TemproleCog(Extension):
) )
@check(admin_or_permissions(Permissions.MANAGE_ROLES)) @check(admin_or_permissions(Permissions.MANAGE_ROLES))
async def _temprole( 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: ) -> None:
await ctx.defer() await ctx.defer()
if not isinstance(user, Member): if not isinstance(user, Member):

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
"""JARVIS image processing cog.""" """JARVIS image processing cog."""
import logging import logging
import re import re
from io import BytesIO from io import BytesIO
@ -62,7 +63,7 @@ class ImageCog(Extension):
required=False, required=False,
) )
async def _resize( 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: ) -> None:
await ctx.defer() await ctx.defer()
if not attachment and not url: if not attachment and not url:

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
"""JARVIS guild-specific cogs""" """JARVIS guild-specific cogs"""
from interactions import Client from interactions import Client
from jarvis.cogs.unique import ctc2, dbrand, gl 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. 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 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 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. it running, make a PR to fix things.
""" """
import logging import logging
import re import re
@ -35,7 +36,7 @@ guild_ids = [] # [578757004059738142, 520021794380447745, 862402786116763668]
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
invites = re.compile( 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, flags=re.IGNORECASE,
) )
@ -52,9 +53,7 @@ class CTCCog(Extension):
def __del__(self): def __del__(self):
self._session.close() self._session.close()
ctc2 = SlashCommand( ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopes=guild_ids)
name="ctc2", description="CTC2 related commands", scopes=guild_ids
)
@ctc2.subcommand(sub_cmd_name="about") @ctc2.subcommand(sub_cmd_name="about")
@cooldown(bucket=Buckets.USER, rate=1, interval=30) @cooldown(bucket=Buckets.USER, rate=1, interval=30)
@ -87,23 +86,17 @@ class CTCCog(Extension):
async def _pw(self, ctx: InteractionContext, guess: str) -> None: async def _pw(self, ctx: InteractionContext, guess: str) -> None:
if len(guess) > 800: if len(guess) > 800:
await ctx.send( 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, ephemeral=True,
) )
return return
elif not valid.fullmatch(guess): if not valid.fullmatch(guess):
await ctx.send( 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, ephemeral=True,
) )
return return
elif invites.search(guess): if invites.search(guess):
await ctx.send( await ctx.send(
"Listen here, dipshit. No using this to bypass sending invite links.", "Listen here, dipshit. No using this to bypass sending invite links.",
ephemeral=True, 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. 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 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 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.client.utils import find
from interactions.models.discord.embed import EmbedField from interactions.models.discord.embed import EmbedField
from interactions.models.internal.application_commands import ( from interactions.models.internal.application_commands import (
OptionType,
SlashCommand, SlashCommand,
slash_option,
) )
from interactions.models.internal.command import cooldown from interactions.models.internal.command import cooldown
from interactions.models.internal.cooldowns import Buckets from interactions.models.internal.cooldowns import Buckets
from thefuzz import process from thefuzz import process
from jarvis.branding import CUSTOM_EMOJIS from jarvis.branding import CUSTOM_EMOJIS
from jarvis.config import load_config
from jarvis.data.dbrand import shipping_lookup from jarvis.data.dbrand import shipping_lookup
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -59,9 +56,7 @@ async def parse_db_status() -> dict:
cells = row.find_all("td") cells = row.find_all("td")
for cell in cells: for cell in cells:
if "column--comment" in cell["class"]: if "column--comment" in cell["class"]:
status_title, detail, *_ = [ status_title, detail, *_ = [x.get_text() for x in cell.find_all("div")]
x.get_text() for x in cell.find_all("div")
]
text = status_title text = status_title
if cell != "Unavailable": if cell != "Unavailable":
text += ": " + detail text += ": " + detail
@ -79,9 +74,7 @@ async def parse_db_status() -> dict:
else: else:
cell = cell.get_text().strip() cell = cell.get_text().strip()
row_data.append(cell) row_data.append(cell)
data[data_key].append( data[data_key].append({headers[idx]: value for idx, value in enumerate(row_data)})
{headers[idx]: value for idx, value in enumerate(row_data)}
)
return data return data
@ -106,24 +99,17 @@ class DbrandCog(Extension):
db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids) db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids)
@db.subcommand( @db.subcommand(sub_cmd_name="status", sub_cmd_description="Get dbrand operational status")
sub_cmd_name="status", sub_cmd_description="Get dbrand operational status"
)
async def _status(self, ctx: InteractionContext) -> None: async def _status(self, ctx: InteractionContext) -> None:
try: try:
status = self.cache.get("status") status = self.cache.get("status")
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc): if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
status = await parse_db_status() status = await parse_db_status()
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta( status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=2)
hours=2
)
self.cache["status"] = status self.cache["status"] = status
status = status.get("operations") status = status.get("operations")
emojies = [x["Status"] for x in status] emojies = [x["Status"] for x in status]
fields = [ fields = [EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"]) for x in status]
EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"])
for x in status
]
color = "#FBBD1E" color = "#FBBD1E"
if all("green" in x for x in emojies): if all("green" in x for x in emojies):
color = "#38F657" color = "#38F657"
@ -144,7 +130,7 @@ class DbrandCog(Extension):
) )
await ctx.send(embeds=embed) await ctx.send(embeds=embed)
except: except Exception:
self.bot.logger.error("Encountered error", exc_info=True) self.bot.logger.error("Encountered error", exc_info=True)
async def _db_support_cmd(self, ctx: InteractionContext) -> None: async def _db_support_cmd(self, ctx: InteractionContext) -> None:
@ -245,7 +231,7 @@ class DbrandCog(Extension):
# required=True, # required=True,
# ) # )
# @cooldown(bucket=Buckets.USER, rate=1, interval=2) # @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 not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
if re.match( if re.match(
r"^[\U0001f1e6-\U0001f1ff]{2}$", r"^[\U0001f1e6-\U0001f1ff]{2}$",
@ -254,15 +240,11 @@ class DbrandCog(Extension):
): ):
# Magic number, subtract from flag char to get ascii char # Magic number, subtract from flag char to get ascii char
uni2ascii = 127365 uni2ascii = 127365
search = chr(ord(search[0]) - uni2ascii) + chr( search = chr(ord(search[0]) - uni2ascii) + chr(ord(search[1]) - uni2ascii)
ord(search[1]) - uni2ascii
)
elif search == "🏳️": elif search == "🏳️":
search = "fr" search = "fr"
else: else:
await ctx.send( await ctx.send("Please use text to search for shipping.", ephemeral=True)
"Please use text to search for shipping.", ephemeral=True
)
return return
if len(search) > 3: if len(search) > 3:
countries = {x["country"]: x["alpha-2"] for x in shipping_lookup} 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) data = await self._session.get(api_link)
if 200 <= data.status < 400: if 200 <= data.status < 400:
data = await data.json() data = await data.json()
data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta( data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
hours=24
)
self.cache[dest] = data self.cache[dest] = data
else: else:
data = None data = None
@ -298,18 +278,12 @@ class DbrandCog(Extension):
fields = [] fields = []
for service in data["shipping_services_available"]: for service in data["shipping_services_available"]:
service_data = self.cache.get(f"{dest}-{service}") service_data = self.cache.get(f"{dest}-{service}")
if not service_data or service_data["cache_expiry"] < datetime.now( if not service_data or service_data["cache_expiry"] < datetime.now(tz=timezone.utc):
tz=timezone.utc service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
):
service_data = await self._session.get(
self.api_url + dest + "/" + service["url"]
)
if service_data.status > 400: if service_data.status > 400:
continue continue
service_data = await service_data.json() service_data = await service_data.json()
service_data["cache_expiry"] = datetime.now( service_data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
tz=timezone.utc
) + timedelta(hours=24)
self.cache[f"{dest}-{service}"] = service_data self.cache[f"{dest}-{service}"] = service_data
title = f'{service_data["carrier"]} {service_data["tier-title"]} | {service_data["costs-min"]}' title = f'{service_data["carrier"]} {service_data["tier-title"]} | {service_data["costs-min"]}'
message = service_data["time-title"] message = service_data["time-title"]
@ -320,9 +294,7 @@ class DbrandCog(Extension):
status = self.cache.get("status") status = self.cache.get("status")
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc): if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
status = await parse_db_status() status = await parse_db_status()
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta( status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=2)
hours=2
)
self.cache["status"] = status self.cache["status"] = status
status = status["countries"] status = status["countries"]
@ -335,10 +307,10 @@ class DbrandCog(Extension):
description = "" description = ""
color = "#FFBB00" color = "#FFBB00"
if shipping_info: if shipping_info:
description = f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}' description = (
created = self.cache.get("status").get("cache_expiry") - timedelta( f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}'
hours=2
) )
created = self.cache.get("status").get("cache_expiry") - timedelta(hours=2)
ts = int(created.timestamp()) ts = int(created.timestamp())
description += f" \u200b | \u200b Last updated: <t:{ts}:R>\n\u200b" description += f" \u200b | \u200b Last updated: <t:{ts}:R>\n\u200b"
if "green" in shipping_info["Status"]: if "green" in shipping_info["Status"]:
@ -367,8 +339,7 @@ class DbrandCog(Extension):
embed = build_embed( embed = build_embed(
title="Check Shipping Times", title="Check Shipping Times",
description=( description=(
"Country not found.\nYou can [view all shipping " "Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)"
"destinations here](https://dbrand.com/shipping)"
), ),
fields=[], fields=[],
url="https://dbrand.com/shipping", url="https://dbrand.com/shipping",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -128,7 +128,8 @@ ignore = [
# ... function # ... function
"D104", "D104",
# ... package # ... package
"E712",
# Ignore == True because of Beanie
# Plugins we don't currently include: flake8-return # Plugins we don't currently include: flake8-return
"RET503", "RET503",
# missing explicit return at the end of function ableto return non-None value. # 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.""" """Main run file for J.A.R.V.I.S."""
import nest_asyncio import nest_asyncio
nest_asyncio.apply() nest_asyncio.apply()
import asyncio import asyncio # noqa: E402
import nest_asyncio from jarvis import run # noqa: E402
from jarvis import run
nest_asyncio.apply() nest_asyncio.apply()