[fix] Pre-commit formatting
This commit is contained in:
parent
9c2c937abc
commit
cd6aa668a0
54 changed files with 448 additions and 984 deletions
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img width=15% alt="JARVIS" src="https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/raw/main/jarvis_small.png">
|
||||
<img width=15% alt="JARVIS" src="https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/raw/main/jarvis_small.png">
|
||||
|
||||
# Just Another Rather Very Intelligent System
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Main JARVIS package."""
|
||||
|
||||
import logging
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
@ -26,6 +27,7 @@ def jlogger(logger: logging.Logger, event: Any) -> None:
|
|||
Args:
|
||||
logger: Logger to use
|
||||
event: Event to parse
|
||||
|
||||
"""
|
||||
jlog = partial(logger.log, 11)
|
||||
if isinstance(event, jurigged.live.WatchOperation):
|
||||
|
@ -61,21 +63,13 @@ async def run() -> None:
|
|||
config = load_config()
|
||||
logger = get_logger("jarvis", show_locals=False) # jconfig.log_level == "DEBUG")
|
||||
logger.setLevel(config.log_level)
|
||||
file_handler = logging.FileHandler(
|
||||
filename="jarvis.log", encoding="UTF-8", mode="w"
|
||||
)
|
||||
file_handler.setFormatter(
|
||||
logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s")
|
||||
)
|
||||
file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w")
|
||||
file_handler.setFormatter(logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s"))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Configure client
|
||||
intents = (
|
||||
Intents.DEFAULT
|
||||
| Intents.MESSAGES
|
||||
| Intents.GUILD_MEMBERS
|
||||
| Intents.GUILD_MESSAGES
|
||||
| Intents.MESSAGE_CONTENT
|
||||
Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES | Intents.MESSAGE_CONTENT
|
||||
)
|
||||
redis_config = config.redis.model_dump()
|
||||
redis_host = redis_config.pop("host")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS brandings."""
|
||||
|
||||
PRIMARY_COLOR = "#3498db"
|
||||
HARD_ACTION = "#ff0000"
|
||||
MODERATE_ACTION = "#ff9900"
|
||||
|
@ -29,14 +30,14 @@ def get_command_color(command: str) -> str:
|
|||
|
||||
Returns:
|
||||
Hex color string
|
||||
|
||||
"""
|
||||
if command in COMMAND_TYPES["HARD"]:
|
||||
return HARD_ACTION
|
||||
elif command in COMMAND_TYPES["MODERATE"]:
|
||||
if command in COMMAND_TYPES["MODERATE"]:
|
||||
return MODERATE_ACTION
|
||||
elif command in COMMAND_TYPES["SOFT"]:
|
||||
if command in COMMAND_TYPES["SOFT"]:
|
||||
return SOFT_ACTION
|
||||
elif command in COMMAND_TYPES["GOOD"]:
|
||||
if command in COMMAND_TYPES["GOOD"]:
|
||||
return GOOD_ACTION
|
||||
else:
|
||||
return CUSTOM_COMMANDS.get(command, PRIMARY_COLOR)
|
||||
return CUSTOM_COMMANDS.get(command, PRIMARY_COLOR)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Custom JARVIS client."""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
@ -21,9 +22,7 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD)
|
|||
|
||||
|
||||
class Jarvis(StatipyClient, ErrorMixin, EventMixin, TaskMixin):
|
||||
def __init__(
|
||||
self, redis: "aioredis.Redis", erapi: str, *args, **kwargs
|
||||
): # noqa: ANN002 ANN003
|
||||
def __init__(self, redis: "aioredis.Redis", erapi: str, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.redis = redis
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS error handling mixin."""
|
||||
|
||||
import traceback
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
@ -40,26 +41,21 @@ class ErrorMixin:
|
|||
error = event.error
|
||||
if isinstance(error, HTTPException):
|
||||
errors = error.search_for_message(error.errors)
|
||||
out = (
|
||||
f"HTTPException: {error.status}|{error.response.reason}: "
|
||||
+ "\n".join(errors)
|
||||
)
|
||||
out = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
|
||||
self.logger.error(out, exc_info=error)
|
||||
else:
|
||||
self.logger.error(f"Ignoring exception in {source}", exc_info=error)
|
||||
|
||||
async def on_command_error(
|
||||
self, ctx: BaseContext, error: Exception, *args: list, **kwargs: dict
|
||||
) -> None:
|
||||
async def on_command_error(self, ctx: BaseContext, error: Exception, *args: list, **kwargs: dict) -> None:
|
||||
"""NAFF on_command_error override."""
|
||||
name = ctx.invoke_target
|
||||
self.logger.debug(f"Handling error in {name}: {error}")
|
||||
if isinstance(error, CommandOnCooldown):
|
||||
await ctx.send(str(error), ephemeral=True)
|
||||
return
|
||||
elif isinstance(error, CommandCheckFailure):
|
||||
return None
|
||||
if isinstance(error, CommandCheckFailure):
|
||||
await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
|
||||
return
|
||||
return None
|
||||
guild = await self.fetch_guild(DEFAULT_GUILD)
|
||||
channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL)
|
||||
error_time = datetime.now(tz=timezone.utc).strftime("%d-%m-%Y %H:%M-%S.%f UTC")
|
||||
|
@ -80,11 +76,7 @@ class ErrorMixin:
|
|||
v = v[97] + "..."
|
||||
arg_str += f" - {v}"
|
||||
callback_args = "\n".join(f" - {i}" for i in args) if args else " None"
|
||||
callback_kwargs = (
|
||||
"\n".join(f" {k}: {v}" for k, v in kwargs.items())
|
||||
if kwargs
|
||||
else " None"
|
||||
)
|
||||
callback_kwargs = "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None"
|
||||
full_message = ERROR_MSG.format(
|
||||
guild_name=ctx.guild.name,
|
||||
error_time=error_time,
|
||||
|
@ -96,11 +88,7 @@ class ErrorMixin:
|
|||
tb = traceback.format_exception(error)
|
||||
if isinstance(error, HTTPException):
|
||||
errors = error.search_for_message(error.errors)
|
||||
tb[
|
||||
-1
|
||||
] = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(
|
||||
errors
|
||||
)
|
||||
tb[-1] = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
|
||||
error_message = "".join(traceback.format_exception(error))
|
||||
if len(full_message + error_message) >= 1800:
|
||||
error_message = "\n ".join(error_message.split("\n"))
|
||||
|
@ -119,9 +107,7 @@ class ErrorMixin:
|
|||
f"\n```yaml\n{full_message}\n```"
|
||||
f"\nException:\n```py\n{error_message}\n```"
|
||||
)
|
||||
await ctx.send(
|
||||
"Whoops! Encountered an error. The error has been logged.", ephemeral=True
|
||||
)
|
||||
await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True)
|
||||
try:
|
||||
await ctx.defer(ephemeral=True)
|
||||
return await super().on_command_error(ctx, error, *args, **kwargs)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"""JARVIS event mixin."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from beanie.operators import Set
|
||||
from interactions import listen, Intents
|
||||
from interactions import listen
|
||||
from interactions.ext.prefixed_commands.context import PrefixedContext
|
||||
from interactions.models.discord.channel import DMChannel
|
||||
from interactions.models.discord.embed import EmbedField
|
||||
|
@ -31,22 +32,16 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
|||
|
||||
async def _sync_domains(self) -> None:
|
||||
self.logger.debug("Loading phishing domains")
|
||||
async with ClientSession(
|
||||
headers={"X-Identity": "Discord: zevaryx#5779"}
|
||||
) as session:
|
||||
async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session:
|
||||
response = await session.get("https://phish.sinking.yachts/v2/all")
|
||||
response.raise_for_status()
|
||||
self.phishing_domains = await response.json()
|
||||
self.logger.info(
|
||||
f"Protected from {len(self.phishing_domains)} phishing domains"
|
||||
)
|
||||
self.logger.info(f"Protected from {len(self.phishing_domains)} phishing domains")
|
||||
|
||||
@listen()
|
||||
async def on_startup(self) -> None:
|
||||
"""NAFF on_startup override. Prometheus info generated here."""
|
||||
await StaticStat.find_one(
|
||||
StaticStat.name == "jarvis_version", StaticStat.client_id == self.user.id
|
||||
).upsert(
|
||||
await StaticStat.find_one(StaticStat.name == "jarvis_version", StaticStat.client_id == self.user.id).upsert(
|
||||
Set(
|
||||
{
|
||||
StaticStat.client_name: self.client_name,
|
||||
|
@ -64,16 +59,14 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
|||
if not self.synced:
|
||||
await self._sync_domains()
|
||||
self._update_domains.start()
|
||||
asyncio.create_task(self._chunk_all())
|
||||
chunk_task = asyncio.create_task(self._chunk_all())
|
||||
self.synced = True
|
||||
except Exception as e:
|
||||
self.logger.error("Failed to load anti-phishing", exc_info=e)
|
||||
self.logger.info("Logged in as {}".format(self.user)) # noqa: T001
|
||||
self.logger.info(
|
||||
"Connected to {} guild(s)".format(len(self.guilds))
|
||||
) # noqa: T001
|
||||
self.logger.info("Logged in as {}".format(self.user))
|
||||
self.logger.info("Connected to {} guild(s)".format(len(self.guilds)))
|
||||
self.logger.info("Current version: {}".format(const.__version__))
|
||||
self.logger.info( # noqa: T001
|
||||
self.logger.info(
|
||||
"https://discord.com/api/oauth2/authorize?client_id="
|
||||
"{}&permissions=8&scope=bot%20applications.commands".format(self.user.id)
|
||||
)
|
||||
|
@ -91,9 +84,7 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
|||
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||
)
|
||||
global_context_menus = sum(
|
||||
1
|
||||
for _ in self.interaction_tree[cid]
|
||||
if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||
1 for _ in self.interaction_tree[cid] if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||
)
|
||||
else:
|
||||
guild_base_commands += sum(
|
||||
|
@ -102,22 +93,12 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
|||
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||
)
|
||||
guild_context_menus += sum(
|
||||
1
|
||||
for _ in self.interaction_tree[cid]
|
||||
if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||
1 for _ in self.interaction_tree[cid] if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||
)
|
||||
self.logger.info(
|
||||
"Loaded {:>3} global base slash commands".format(global_base_commands)
|
||||
)
|
||||
self.logger.info(
|
||||
"Loaded {:>3} global context menus".format(global_context_menus)
|
||||
)
|
||||
self.logger.info(
|
||||
"Loaded {:>3} guild base slash commands".format(guild_base_commands)
|
||||
)
|
||||
self.logger.info(
|
||||
"Loaded {:>3} guild context menus".format(guild_context_menus)
|
||||
)
|
||||
self.logger.info("Loaded {:>3} global base slash commands".format(global_base_commands))
|
||||
self.logger.info("Loaded {:>3} global context menus".format(global_context_menus))
|
||||
self.logger.info("Loaded {:>3} guild base slash commands".format(guild_base_commands))
|
||||
self.logger.info("Loaded {:>3} guild context menus".format(guild_context_menus))
|
||||
except Exception:
|
||||
self.logger.error("interaction_tree not found, try updating NAFF")
|
||||
|
||||
|
@ -126,18 +107,15 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
|||
_ = await Reminder.find().to_list(None)
|
||||
self.logger.debug("Updating ERAPI")
|
||||
await self.erapi.update_async()
|
||||
await asyncio.wait([chunk_task])
|
||||
|
||||
# Modlog
|
||||
async def on_command(self, ctx: BaseContext) -> None:
|
||||
"""NAFF on_command override."""
|
||||
name = ctx.invoke_target
|
||||
if not isinstance(ctx.channel, DMChannel) and name not in ["pw"]:
|
||||
modlog = await Setting.find_one(
|
||||
Setting.guild == ctx.guild.id, Setting.setting == "activitylog"
|
||||
)
|
||||
ignore = await Setting.find_one(
|
||||
Setting.guild == ctx.guild.id, Setting.setting == "log_ignore"
|
||||
)
|
||||
modlog = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "activitylog")
|
||||
ignore = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "log_ignore")
|
||||
if modlog and (ignore and ctx.channel.id not in ignore.value):
|
||||
channel = await ctx.guild.fetch_channel(modlog.value)
|
||||
args = []
|
||||
|
@ -169,16 +147,10 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
|||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
embed.set_author(
|
||||
name=ctx.author.username, icon_url=ctx.author.display_avatar.url
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}"
|
||||
)
|
||||
embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url)
|
||||
embed.set_footer(text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
if channel:
|
||||
await channel.send(embeds=embed)
|
||||
else:
|
||||
self.logger.warning(
|
||||
f"Activitylog channel no longer exists in {ctx.guild.name}, removing"
|
||||
)
|
||||
self.logger.warning(f"Activitylog channel no longer exists in {ctx.guild.name}, removing")
|
||||
await modlog.delete()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS component event mixin."""
|
||||
|
||||
from beanie import PydanticObjectId
|
||||
from interactions import listen
|
||||
from interactions.api.events.internal import ButtonPressed
|
||||
|
@ -84,11 +85,7 @@ class ComponentEventMixin:
|
|||
for component in row.components:
|
||||
component.disabled = True
|
||||
await context.message.edit(components=context.message.components)
|
||||
msg = (
|
||||
"Cancelled"
|
||||
if context.custom_id == "modcase|no"
|
||||
else "Moderation case opened"
|
||||
)
|
||||
msg = "Cancelled" if context.custom_id == "modcase|no" else "Moderation case opened"
|
||||
await context.send(msg)
|
||||
await self.redis.delete(user_key)
|
||||
await self.redis.delete(action_key)
|
||||
|
@ -114,9 +111,7 @@ class ComponentEventMixin:
|
|||
await context.send("I'm afraid I can't let you do that", ephemeral=True)
|
||||
return True # User does not have perms to delete
|
||||
|
||||
if pin := await Pin.find_one(
|
||||
Pin.pin == context.message.id, Pin.guild == context.guild.id
|
||||
):
|
||||
if pin := await Pin.find_one(Pin.pin == context.message.id, Pin.guild == context.guild.id):
|
||||
await pin.delete()
|
||||
|
||||
await context.message.delete()
|
||||
|
@ -139,12 +134,8 @@ class ComponentEventMixin:
|
|||
if await Reminder.find_one(
|
||||
Reminder.parent == str(reminder.id),
|
||||
Reminder.user == context.author.id,
|
||||
) or await Reminder.find_one(
|
||||
Reminder.id == reminder.id, Reminder.user == context.author.id
|
||||
):
|
||||
await context.send(
|
||||
"You've already copied this reminder!", ephemeral=True
|
||||
)
|
||||
) or await Reminder.find_one(Reminder.id == reminder.id, Reminder.user == context.author.id):
|
||||
await context.send("You've already copied this reminder!", ephemeral=True)
|
||||
else:
|
||||
new_reminder = Reminder(
|
||||
user=context.author.id,
|
||||
|
@ -160,9 +151,7 @@ class ComponentEventMixin:
|
|||
|
||||
await context.send("Reminder copied!", ephemeral=True)
|
||||
else:
|
||||
await context.send(
|
||||
"That reminder doesn't exist anymore", ephemeral=True
|
||||
)
|
||||
await context.send("That reminder doesn't exist anymore", ephemeral=True)
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS member event mixin."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from interactions import listen
|
||||
|
@ -20,19 +21,13 @@ class MemberEventMixin:
|
|||
"""Handle on_member_add event."""
|
||||
user = event.member
|
||||
guild = event.guild
|
||||
unverified = await Setting.find_one(
|
||||
Setting.guild == guild.id, Setting.setting == "unverified"
|
||||
)
|
||||
temproles = await Temprole.find(
|
||||
Temprole.guild == guild.id, Temprole.user == user.id
|
||||
).to_list()
|
||||
unverified = await Setting.find_one(Setting.guild == guild.id, Setting.setting == "unverified")
|
||||
temproles = await Temprole.find(Temprole.guild == guild.id, Temprole.user == user.id).to_list()
|
||||
if unverified:
|
||||
self.logger.debug(f"Applying unverified role to {user.id} in {guild.id}")
|
||||
role = await guild.fetch_role(unverified.value)
|
||||
if not role:
|
||||
self.logger.warn(
|
||||
f"Unverified role no longer exists for guild {guild.id}"
|
||||
)
|
||||
self.logger.warn(f"Unverified role no longer exists for guild {guild.id}")
|
||||
await unverified.delete()
|
||||
elif role not in user.roles:
|
||||
await user.add_role(role, reason="User just joined and is unverified")
|
||||
|
@ -43,18 +38,14 @@ class MemberEventMixin:
|
|||
if not role:
|
||||
await temprole.delete()
|
||||
elif role not in user.roles:
|
||||
await user.add_role(
|
||||
role, reason="User joined and has existing temprole"
|
||||
)
|
||||
await user.add_role(role, reason="User joined and has existing temprole")
|
||||
|
||||
@listen()
|
||||
async def on_member_remove(self, event: MemberRemove) -> None:
|
||||
"""Handle on_member_remove event."""
|
||||
user = event.member
|
||||
guild = event.guild
|
||||
log = await Setting.find_one(
|
||||
Setting.guild == guild.id, Setting.setting == "activitylog"
|
||||
)
|
||||
log = await Setting.find_one(Setting.guild == guild.id, Setting.setting == "activitylog")
|
||||
if log:
|
||||
self.logger.debug(f"User {user.id} left {guild.id}")
|
||||
channel = await guild.fetch_channel(log.value)
|
||||
|
@ -95,7 +86,7 @@ class MemberEventMixin:
|
|||
async def process_rolechange(self, before: Member, after: Member) -> Embed:
|
||||
"""Process role changes."""
|
||||
if before.roles == after.roles:
|
||||
return
|
||||
return None
|
||||
|
||||
new_roles = []
|
||||
removed_roles = []
|
||||
|
@ -126,7 +117,7 @@ class MemberEventMixin:
|
|||
async def process_rename(self, before: Member, after: Member) -> None:
|
||||
"""Process name change."""
|
||||
if before.nickname == after.nickname and before.username == after.username:
|
||||
return
|
||||
return None
|
||||
|
||||
fields = (
|
||||
EmbedField(
|
||||
|
@ -151,29 +142,21 @@ class MemberEventMixin:
|
|||
before = event.before
|
||||
after = event.after
|
||||
|
||||
if (
|
||||
before.display_name == after.display_name and before.roles == after.roles
|
||||
) or (not after or not before):
|
||||
if (before.display_name == after.display_name and before.roles == after.roles) or (not after or not before):
|
||||
return
|
||||
|
||||
log = await Setting.find_one(
|
||||
Setting.guild == before.guild.id, Setting.setting == "activitylog"
|
||||
)
|
||||
log = await Setting.find_one(Setting.guild == before.guild.id, Setting.setting == "activitylog")
|
||||
if log:
|
||||
channel = await before.guild.fetch_channel(log.value)
|
||||
await asyncio.sleep(0.5) # Wait for audit log
|
||||
embed = None
|
||||
if before._role_ids != after._role_ids:
|
||||
verified = await Setting.find_one(
|
||||
Setting.guild == before.guild.id, Setting.setting == "verified"
|
||||
)
|
||||
verified = await Setting.find_one(Setting.guild == before.guild.id, Setting.setting == "verified")
|
||||
v_role = None
|
||||
if verified:
|
||||
v_role = await before.guild.fetch_role(verified.value)
|
||||
if not v_role:
|
||||
self.logger.debug(
|
||||
f"Guild {before.guild.id} verified role no longer exists"
|
||||
)
|
||||
self.logger.debug(f"Guild {before.guild.id} verified role no longer exists")
|
||||
await verified.delete()
|
||||
else:
|
||||
if not before.has_role(v_role) and after.has_role(v_role):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS message event mixin"""
|
||||
|
||||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
@ -40,9 +41,7 @@ class MessageEventMixin:
|
|||
)
|
||||
if autopurge:
|
||||
if not message.author.has_permission(Permissions.ADMINISTRATOR):
|
||||
self.logger.debug(
|
||||
f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}"
|
||||
)
|
||||
self.logger.debug(f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}")
|
||||
await message.delete(delay=autopurge.delay)
|
||||
|
||||
async def autoreact(self, message: Message) -> None:
|
||||
|
@ -52,9 +51,7 @@ class MessageEventMixin:
|
|||
Autoreact.channel == message.channel.id,
|
||||
)
|
||||
if autoreact:
|
||||
self.logger.debug(
|
||||
f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}"
|
||||
)
|
||||
self.logger.debug(f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}")
|
||||
for reaction in autoreact.reactions:
|
||||
await message.add_reaction(reaction)
|
||||
if autoreact.thread:
|
||||
|
@ -75,13 +72,11 @@ class MessageEventMixin:
|
|||
# channel = find(lambda x: x.id == 599068193339736096, message._mention_ids)
|
||||
# if channel and message.author.id == 293795462752894976:
|
||||
# await channel.send(
|
||||
# content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501
|
||||
# content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif"
|
||||
# )
|
||||
content = re.sub(r"\s+", "", message.content)
|
||||
match = invites.search(content)
|
||||
setting = await Setting.find_one(
|
||||
Setting.guild == message.guild.id, Setting.setting == "noinvite"
|
||||
)
|
||||
setting = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "noinvite")
|
||||
if not setting:
|
||||
setting = Setting(guild=message.guild.id, setting="noinvite", value=True)
|
||||
await setting.save()
|
||||
|
@ -89,20 +84,19 @@ class MessageEventMixin:
|
|||
guild_invites = [x.code for x in await message.guild.fetch_invites()]
|
||||
if message.guild.vanity_url_code:
|
||||
guild_invites.append(message.guild.vanity_url_code)
|
||||
allowed = guild_invites + [
|
||||
allowed = [
|
||||
*guild_invites,
|
||||
"dbrand",
|
||||
"VtgZntXcnZ",
|
||||
"gPfYGbvTCE",
|
||||
"interactions",
|
||||
"NTSHu97tHg",
|
||||
]
|
||||
is_mod = message.author.has_permission(
|
||||
Permissions.MANAGE_GUILD
|
||||
) or message.author.has_permission(Permissions.ADMINISTRATOR)
|
||||
is_mod = message.author.has_permission(Permissions.MANAGE_GUILD) or message.author.has_permission(
|
||||
Permissions.ADMINISTRATOR
|
||||
)
|
||||
if (m := match.group(1)) not in allowed and setting.value and not is_mod:
|
||||
self.logger.debug(
|
||||
f"Removing non-allowed invite `{m}` from {message.guild.id}"
|
||||
)
|
||||
self.logger.debug(f"Removing non-allowed invite `{m}` from {message.guild.id}")
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception:
|
||||
|
@ -159,9 +153,7 @@ class MessageEventMixin:
|
|||
value=1,
|
||||
)
|
||||
await Stat(meta=md, name="warning").insert()
|
||||
embed = warning_embed(
|
||||
message.author, "Sent a message with a filtered word", self.user
|
||||
)
|
||||
embed = warning_embed(message.author, "Sent a message with a filtered word", self.user)
|
||||
try:
|
||||
await message.reply(embeds=embed)
|
||||
except Exception:
|
||||
|
@ -180,21 +172,19 @@ class MessageEventMixin:
|
|||
Setting.setting == "massmention",
|
||||
)
|
||||
|
||||
is_mod = message.author.has_permission(
|
||||
Permissions.MANAGE_GUILD
|
||||
) or message.author.has_permission(Permissions.ADMINISTRATOR)
|
||||
is_mod = message.author.has_permission(Permissions.MANAGE_GUILD) or message.author.has_permission(
|
||||
Permissions.ADMINISTRATOR
|
||||
)
|
||||
|
||||
if (
|
||||
massmention
|
||||
and int(massmention.value) > 0 # noqa: W503
|
||||
and len(message._mention_ids + message._mention_roles) # noqa: W503
|
||||
- (1 if message.author.id in message._mention_ids else 0) # noqa: W503
|
||||
> massmention.value # noqa: W503
|
||||
and not is_mod # noqa: W503
|
||||
and int(massmention.value) > 0
|
||||
and len(message._mention_ids + message._mention_roles)
|
||||
- (1 if message.author.id in message._mention_ids else 0)
|
||||
> massmention.value
|
||||
and not is_mod
|
||||
):
|
||||
self.logger.debug(
|
||||
f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}"
|
||||
)
|
||||
self.logger.debug(f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}")
|
||||
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||
await Warning(
|
||||
active=True,
|
||||
|
@ -226,20 +216,11 @@ class MessageEventMixin:
|
|||
if message.author.has_permission(Permissions.MANAGE_GUILD):
|
||||
return
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
"Failed to get permissions, pretending check failed", exc_info=e
|
||||
)
|
||||
self.logger.error("Failed to get permissions, pretending check failed", exc_info=e)
|
||||
|
||||
if (
|
||||
await Roleping.find(
|
||||
Roleping.guild == message.guild.id, Roleping.active == True
|
||||
).count()
|
||||
== 0
|
||||
):
|
||||
if await Roleping.find(Roleping.guild == message.guild.id, Roleping.active == True).count() == 0:
|
||||
return
|
||||
rolepings = await Roleping.find(
|
||||
Roleping.guild == message.guild.id, Roleping.active == True
|
||||
).to_list()
|
||||
rolepings = await Roleping.find(Roleping.guild == message.guild.id, Roleping.active == True).to_list()
|
||||
|
||||
# Get all role IDs involved with message
|
||||
roles = [x.id async for x in message.mention_roles]
|
||||
|
@ -263,9 +244,7 @@ class MessageEventMixin:
|
|||
|
||||
# Check if user in a bypass list
|
||||
def check_has_role(roleping: Roleping) -> bool:
|
||||
return any(
|
||||
role.id in roleping.bypass.roles for role in message.author.roles
|
||||
)
|
||||
return any(role.id in roleping.bypass.roles for role in message.author.roles)
|
||||
|
||||
user_has_bypass = False
|
||||
for roleping in rolepings:
|
||||
|
@ -276,15 +255,8 @@ class MessageEventMixin:
|
|||
user_has_bypass = True
|
||||
break
|
||||
|
||||
if (
|
||||
role_in_rolepings
|
||||
and user_missing_role
|
||||
and not user_is_admin
|
||||
and not user_has_bypass
|
||||
):
|
||||
self.logger.debug(
|
||||
f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}"
|
||||
)
|
||||
if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass:
|
||||
self.logger.debug(f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}")
|
||||
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||
await Warning(
|
||||
active=True,
|
||||
|
@ -426,9 +398,7 @@ class MessageEventMixin:
|
|||
value=1,
|
||||
)
|
||||
await Stat(meta=md, name="warning").insert()
|
||||
reasons = ", ".join(
|
||||
f"{m['source']}: {m['type']}" for m in data["matches"]
|
||||
)
|
||||
reasons = ", ".join(f"{m['source']}: {m['type']}" for m in data["matches"])
|
||||
embed = warning_embed(message.author, reasons, self.user)
|
||||
try:
|
||||
await message.channel.send(embeds=embed)
|
||||
|
@ -474,9 +444,7 @@ class MessageEventMixin:
|
|||
"""Timeout a user."""
|
||||
expires_at = datetime.now(tz=timezone.utc) + timedelta(minutes=30)
|
||||
try:
|
||||
await user.timeout(
|
||||
communication_disabled_until=expires_at, reason="Phishing link"
|
||||
)
|
||||
await user.timeout(communication_disabled_until=expires_at, reason="Phishing link")
|
||||
await Mute(
|
||||
user=user.id,
|
||||
reason="Auto mute for harmful link",
|
||||
|
@ -525,20 +493,10 @@ class MessageEventMixin:
|
|||
before = event.before
|
||||
after = event.after
|
||||
if not after.author.bot:
|
||||
modlog = await Setting.find_one(
|
||||
Setting.guild == after.guild.id, Setting.setting == "activitylog"
|
||||
)
|
||||
ignore = await Setting.find_one(
|
||||
Setting.guild == after.guild.id, Setting.setting == "log_ignore"
|
||||
)
|
||||
if modlog and (
|
||||
not ignore or (ignore and after.channel.id not in ignore.value)
|
||||
):
|
||||
if (
|
||||
not before
|
||||
or before.content == after.content
|
||||
or before.content is None
|
||||
):
|
||||
modlog = await Setting.find_one(Setting.guild == after.guild.id, Setting.setting == "activitylog")
|
||||
ignore = await Setting.find_one(Setting.guild == after.guild.id, Setting.setting == "log_ignore")
|
||||
if modlog and (not ignore or (ignore and after.channel.id not in ignore.value)):
|
||||
if not before or before.content == after.content or before.content is None:
|
||||
return
|
||||
try:
|
||||
channel = before.guild.get_channel(modlog.value)
|
||||
|
@ -567,9 +525,7 @@ class MessageEventMixin:
|
|||
icon_url=after.author.display_avatar.url,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{after.author.username} | {after.author.id}"
|
||||
)
|
||||
embed.set_footer(text=f"{after.author.username} | {after.author.id}")
|
||||
await channel.send(embeds=embed)
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
|
@ -590,15 +546,9 @@ class MessageEventMixin:
|
|||
async def on_message_delete(self, event: MessageDelete) -> None:
|
||||
"""Process on_message_delete events."""
|
||||
message = event.message
|
||||
modlog = await Setting.find_one(
|
||||
Setting.guild == message.guild.id, Setting.setting == "activitylog"
|
||||
)
|
||||
ignore = await Setting.find_one(
|
||||
Setting.guild == message.guild.id, Setting.setting == "log_ignore"
|
||||
)
|
||||
if modlog and (
|
||||
not ignore or (ignore and message.channel.id not in ignore.value)
|
||||
):
|
||||
modlog = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "activitylog")
|
||||
ignore = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "log_ignore")
|
||||
if modlog and (not ignore or (ignore and message.channel.id not in ignore.value)):
|
||||
try:
|
||||
content = message.content or message.system_content or "N/A"
|
||||
except AttributeError:
|
||||
|
@ -607,9 +557,7 @@ class MessageEventMixin:
|
|||
|
||||
try:
|
||||
if message.attachments:
|
||||
value = "\n".join(
|
||||
[f"[{x.filename}]({x.url})" for x in message.attachments]
|
||||
)
|
||||
value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments])
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name="Attachments",
|
||||
|
@ -619,9 +567,7 @@ class MessageEventMixin:
|
|||
)
|
||||
|
||||
if message.sticker_items:
|
||||
value = "\n".join(
|
||||
[f"Sticker: {x.name}" for x in message.sticker_items]
|
||||
)
|
||||
value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items])
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name="Stickers",
|
||||
|
@ -653,11 +599,7 @@ class MessageEventMixin:
|
|||
icon_url=message.author.display_avatar.url,
|
||||
url=message.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=(f"{message.author.username} | " f"{message.author.id}")
|
||||
)
|
||||
embed.set_footer(text=(f"{message.author.username} | " f"{message.author.id}"))
|
||||
await channel.send(embeds=embed)
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}"
|
||||
)
|
||||
self.logger.warning(f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}")
|
||||
|
|
|
@ -4,7 +4,6 @@ from datetime import datetime
|
|||
|
||||
import pytz
|
||||
from aiohttp import ClientSession
|
||||
from beanie.operators import Set
|
||||
from croniter import croniter
|
||||
from interactions.models.internal.tasks.task import Task
|
||||
from interactions.models.internal.tasks.triggers import BaseTrigger, IntervalTrigger
|
||||
|
@ -19,6 +18,7 @@ class CronTrigger(BaseTrigger):
|
|||
|
||||
Attributes:
|
||||
schedule str: The cron schedule, use https://crontab.guru for help
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, schedule: str) -> None:
|
||||
|
@ -31,9 +31,7 @@ class CronTrigger(BaseTrigger):
|
|||
class TaskMixin:
|
||||
@Task.create(IntervalTrigger(minutes=1))
|
||||
async def _update_domains(self) -> None:
|
||||
async with ClientSession(
|
||||
headers={"X-Identity": "Discord: zevaryx#5779"}
|
||||
) as session:
|
||||
async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session:
|
||||
response = await session.get("https://phish.sinking.yachts/v2/recent/60")
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
|
@ -63,7 +61,7 @@ class TaskMixin:
|
|||
await self.erapi.update_async()
|
||||
|
||||
@Task.create(CronTrigger("0 0 1 * *"))
|
||||
async def _cleanup(self) -> None:
|
||||
async def _cleanup(self) -> None: # noqa: C901
|
||||
self.logger.debug("Getting all unique guild, channel, and user IDs")
|
||||
guild_ids = []
|
||||
channel_ids = []
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Admin Cogs."""
|
||||
|
||||
import logging
|
||||
|
||||
from interactions import Client
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Autoreact Cog."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
@ -39,10 +40,9 @@ class AutoReactCog(Extension):
|
|||
|
||||
Returns:
|
||||
Tuple of success? and error message
|
||||
|
||||
"""
|
||||
exists = await Autoreact.find_one(
|
||||
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
|
||||
)
|
||||
exists = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
|
||||
if exists:
|
||||
return False, f"Autoreact already exists for {channel.mention}."
|
||||
|
||||
|
@ -56,9 +56,7 @@ class AutoReactCog(Extension):
|
|||
|
||||
return True, None
|
||||
|
||||
async def delete_autoreact(
|
||||
self, ctx: InteractionContext, channel: GuildText
|
||||
) -> bool:
|
||||
async def delete_autoreact(self, ctx: InteractionContext, channel: GuildText) -> bool:
|
||||
"""
|
||||
Remove an autoreact monitor on a channel.
|
||||
|
||||
|
@ -68,10 +66,9 @@ class AutoReactCog(Extension):
|
|||
|
||||
Returns:
|
||||
Success?
|
||||
|
||||
"""
|
||||
ar = await Autoreact.find_one(
|
||||
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
|
||||
)
|
||||
ar = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
|
||||
if ar:
|
||||
await ar.delete()
|
||||
return True
|
||||
|
@ -107,7 +104,7 @@ class AutoReactCog(Extension):
|
|||
ctx: InteractionContext,
|
||||
channel: GuildText,
|
||||
thread: bool = True,
|
||||
emote: str = None,
|
||||
emote: str = None, # noqa: RUF013
|
||||
) -> None:
|
||||
await ctx.defer()
|
||||
if emote:
|
||||
|
@ -125,18 +122,12 @@ class AutoReactCog(Extension):
|
|||
lambda x: x.id == emoji_id,
|
||||
await ctx.guild.fetch_all_custom_emojis(),
|
||||
):
|
||||
await ctx.send(
|
||||
"Please use a custom emote from this server.", ephemeral=True
|
||||
)
|
||||
await ctx.send("Please use a custom emote from this server.", ephemeral=True)
|
||||
return
|
||||
autoreact = await Autoreact.find_one(
|
||||
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
|
||||
)
|
||||
autoreact = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
|
||||
if not autoreact:
|
||||
await self.create_autoreact(ctx, channel, thread)
|
||||
autoreact = await Autoreact.find_one(
|
||||
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
|
||||
)
|
||||
autoreact = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
|
||||
if emote and emote in autoreact.reactions:
|
||||
await ctx.send(
|
||||
f"Emote already added to {channel.mention} autoreactions.",
|
||||
|
@ -176,12 +167,8 @@ class AutoReactCog(Extension):
|
|||
required=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _autoreact_remove(
|
||||
self, ctx: InteractionContext, channel: GuildText, emote: str
|
||||
) -> None:
|
||||
autoreact = await Autoreact.find_one(
|
||||
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
|
||||
)
|
||||
async def _autoreact_remove(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None:
|
||||
autoreact = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
|
||||
if not autoreact:
|
||||
await ctx.send(
|
||||
f"Please create autoreact first with /autoreact add {channel.mention} {emote}",
|
||||
|
@ -215,9 +202,7 @@ class AutoReactCog(Extension):
|
|||
required=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _autoreact_delete(
|
||||
self, ctx: InteractionContext, channel: GuildText
|
||||
) -> None:
|
||||
async def _autoreact_delete(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
result = await self.delete_autoreact(ctx, channel)
|
||||
if not result:
|
||||
await ctx.send(f"No autoreact found in {channel.mention}", ephemeral=True)
|
||||
|
@ -234,12 +219,8 @@ class AutoReactCog(Extension):
|
|||
opt_type=OptionType.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
async def _autoreact_list(
|
||||
self, ctx: InteractionContext, channel: GuildText
|
||||
) -> None:
|
||||
exists = await Autoreact.find_one(
|
||||
Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id
|
||||
)
|
||||
async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
exists = await Autoreact.find_one(Autoreact.guild == ctx.guild.id, Autoreact.channel == channel.id)
|
||||
if not exists:
|
||||
await ctx.send(
|
||||
f"Please create autoreact first with /autoreact add {channel.mention} <emote>",
|
||||
|
@ -248,9 +229,7 @@ class AutoReactCog(Extension):
|
|||
return
|
||||
message = ""
|
||||
if len(exists.reactions) > 0:
|
||||
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(
|
||||
exists.reactions
|
||||
)
|
||||
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(exists.reactions)
|
||||
else:
|
||||
message = f"No reactions set on {channel.mention}"
|
||||
await ctx.send(message)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS BanCog."""
|
||||
|
||||
import re
|
||||
from datetime import timedelta
|
||||
|
||||
|
@ -68,9 +69,7 @@ class BanCog(ModcaseCog):
|
|||
|
||||
await ctx.send(embeds=embed)
|
||||
|
||||
async def discord_apply_unban(
|
||||
self, ctx: InteractionContext, user: User, reason: str
|
||||
) -> None:
|
||||
async def discord_apply_unban(self, ctx: InteractionContext, user: User, reason: str) -> None:
|
||||
"""Apply a Discord unban."""
|
||||
await ctx.guild.unban(user, reason=reason)
|
||||
discrim = user.discriminator
|
||||
|
@ -91,9 +90,7 @@ class BanCog(ModcaseCog):
|
|||
await ctx.send(embeds=embed)
|
||||
|
||||
@slash_command(name="ban", description="Ban a user")
|
||||
@slash_option(
|
||||
name="user", description="User to ban", opt_type=OptionType.USER, required=True
|
||||
)
|
||||
@slash_option(name="user", description="User to ban", opt_type=OptionType.USER, required=True)
|
||||
@slash_option(
|
||||
name="reason",
|
||||
description="Ban reason",
|
||||
|
@ -124,14 +121,14 @@ class BanCog(ModcaseCog):
|
|||
required=False,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _ban(
|
||||
async def _ban( # noqa: C901
|
||||
self,
|
||||
ctx: InteractionContext,
|
||||
user: User,
|
||||
reason: str,
|
||||
btype: str = "perm",
|
||||
duration: int = 4,
|
||||
delete_history: str = None,
|
||||
delete_history: str = None, # noqa: RUF013
|
||||
) -> None:
|
||||
if user.id == ctx.author.id:
|
||||
await ctx.send("You cannot ban yourself.", ephemeral=True)
|
||||
|
@ -185,9 +182,7 @@ class BanCog(ModcaseCog):
|
|||
mtype = "perma"
|
||||
|
||||
guild_name = ctx.guild.name
|
||||
user_message = (
|
||||
f"You have been {mtype}banned from {guild_name}." + f" Reason:\n{reason}"
|
||||
)
|
||||
user_message = f"You have been {mtype}banned from {guild_name}." + f" Reason:\n{reason}"
|
||||
if mtype == "temp":
|
||||
user_message += f"\nDuration: {duration} hours"
|
||||
|
||||
|
@ -216,9 +211,7 @@ class BanCog(ModcaseCog):
|
|||
except Exception:
|
||||
self.logger.warn(f"Failed to send ban embed to {user.id}")
|
||||
try:
|
||||
await self.discord_apply_ban(
|
||||
ctx, reason, user, duration, active, mtype, delete_history or 0
|
||||
)
|
||||
await self.discord_apply_ban(ctx, reason, user, duration, active, mtype, delete_history or 0)
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", ephemeral=True)
|
||||
return
|
||||
|
@ -240,7 +233,7 @@ class BanCog(ModcaseCog):
|
|||
required=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _unban(
|
||||
async def _unban( # noqa: C901
|
||||
self,
|
||||
ctx: InteractionContext,
|
||||
user: str,
|
||||
|
@ -267,8 +260,7 @@ class BanCog(ModcaseCog):
|
|||
user, discrim = user.split("#")
|
||||
if discrim:
|
||||
discord_ban_info = find(
|
||||
lambda x: x.user.username == user
|
||||
and x.user.discriminator == discrim,
|
||||
lambda x: x.user.username == user and x.user.discriminator == discrim,
|
||||
bans,
|
||||
)
|
||||
else:
|
||||
|
@ -277,16 +269,9 @@ class BanCog(ModcaseCog):
|
|||
if len(results) > 1:
|
||||
active_bans = []
|
||||
for ban in bans:
|
||||
active_bans.append(
|
||||
"{0} ({1}): {2}".format(
|
||||
ban.user.username, ban.user.id, ban.reason
|
||||
)
|
||||
)
|
||||
active_bans.append("{0} ({1}): {2}".format(ban.user.username, ban.user.id, ban.reason))
|
||||
ab_message = "\n".join(active_bans)
|
||||
message = (
|
||||
"More than one result. "
|
||||
f"Please use one of the following IDs:\n```{ab_message}\n```"
|
||||
)
|
||||
message = "More than one result. " f"Please use one of the following IDs:\n```{ab_message}\n```"
|
||||
await ctx.send(message)
|
||||
return
|
||||
discord_ban_info = results[0]
|
||||
|
@ -331,9 +316,7 @@ class BanCog(ModcaseCog):
|
|||
admin=ctx.author.id,
|
||||
reason=reason,
|
||||
).save()
|
||||
await ctx.send(
|
||||
"Unable to find user in Discord, but removed entry from database."
|
||||
)
|
||||
await ctx.send("Unable to find user in Discord, but removed entry from database.")
|
||||
|
||||
bans = SlashCommand(name="bans", description="User bans")
|
||||
|
||||
|
@ -357,9 +340,7 @@ class BanCog(ModcaseCog):
|
|||
required=False,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _bans_list(
|
||||
self, ctx: InteractionContext, btype: int = 0, active: bool = True
|
||||
) -> None:
|
||||
async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: bool = True) -> None:
|
||||
types = [0, "perm", "temp", "soft"]
|
||||
search = {"guild": ctx.guild.id}
|
||||
if active:
|
||||
|
@ -421,9 +402,7 @@ class BanCog(ModcaseCog):
|
|||
pages.append(embed)
|
||||
else:
|
||||
for i in range(0, len(bans), 5):
|
||||
embed = build_embed(
|
||||
title=title, description="", fields=fields[i : i + 5]
|
||||
)
|
||||
embed = build_embed(title=title, description="", fields=fields[i : i + 5])
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
pages.append(embed)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Filters cog."""
|
||||
|
||||
import asyncio
|
||||
import difflib
|
||||
import re
|
||||
|
@ -31,16 +32,12 @@ class FilterCog(Extension):
|
|||
self.bot = bot
|
||||
self.cache: Dict[int, List[str]] = {}
|
||||
|
||||
async def _edit_filter(
|
||||
self, ctx: InteractionContext, name: str, search: bool = False
|
||||
) -> None:
|
||||
async def _edit_filter(self, ctx: InteractionContext, name: str, search: bool = False) -> None:
|
||||
try:
|
||||
content = ""
|
||||
f: Filter = None
|
||||
if search:
|
||||
if f := await Filter.find_one(
|
||||
Filter.name == name, Filter.guild == ctx.guild.id
|
||||
):
|
||||
if f := await Filter.find_one(Filter.name == name, Filter.guild == ctx.guild.id):
|
||||
content = "\n".join(f.filters)
|
||||
|
||||
kw = "Updating" if search else "Creating"
|
||||
|
@ -60,9 +57,7 @@ class FilterCog(Extension):
|
|||
)
|
||||
await ctx.send_modal(modal)
|
||||
try:
|
||||
data = await self.bot.wait_for_modal(
|
||||
modal, author=ctx.author.id, timeout=60 * 5
|
||||
)
|
||||
data = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
||||
filters = data.responses.get("filters").split("\n")
|
||||
except asyncio.TimeoutError:
|
||||
return
|
||||
|
@ -82,9 +77,7 @@ class FilterCog(Extension):
|
|||
content = content.splitlines()
|
||||
diff = "\n".join(difflib.ndiff(content, filters)).replace("`", "\u200b`")
|
||||
|
||||
await data.send(
|
||||
f"Filter `{new_name}` has been updated:\n\n```diff\n{diff}\n```"
|
||||
)
|
||||
await data.send(f"Filter `{new_name}` has been updated:\n\n```diff\n{diff}\n```")
|
||||
|
||||
if ctx.guild.id not in self.cache:
|
||||
self.cache[ctx.guild.id] = []
|
||||
|
@ -97,9 +90,7 @@ class FilterCog(Extension):
|
|||
|
||||
filter_ = SlashCommand(name="filter", description="Manage keyword filters")
|
||||
|
||||
@filter_.subcommand(
|
||||
sub_cmd_name="create", sub_cmd_description="Create a new filter"
|
||||
)
|
||||
@filter_.subcommand(sub_cmd_name="create", sub_cmd_description="Create a new filter")
|
||||
@slash_option(
|
||||
name="name",
|
||||
description="Name of new filter",
|
||||
|
@ -170,9 +161,7 @@ class FilterCog(Extension):
|
|||
if not self.cache.get(ctx.guild.id):
|
||||
filters = await Filter.find(Filter.guild == ctx.guild.id).to_list()
|
||||
self.cache[ctx.guild.id] = [f.name for f in filters]
|
||||
results = process.extract(
|
||||
ctx.input_text, self.cache.get(ctx.guild.id), limit=25
|
||||
)
|
||||
results = process.extract(ctx.input_text, self.cache.get(ctx.guild.id), limit=25)
|
||||
choices = [{"name": r[0], "value": r[0]} for r in results]
|
||||
await ctx.send(choices=choices)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS KickCog."""
|
||||
|
||||
from interactions import InteractionContext, Permissions
|
||||
from interactions.models.discord.user import User
|
||||
from interactions.models.internal.application_commands import (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS LockCog."""
|
||||
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
|
@ -56,7 +57,7 @@ class LockCog(Extension):
|
|||
await ctx.send("Duration must be > 0", ephemeral=True)
|
||||
return
|
||||
|
||||
elif duration > 60 * 24 * 7:
|
||||
if duration > 60 * 24 * 7:
|
||||
await ctx.send("Duration must be <= 7 days", ephemeral=True)
|
||||
return
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS LockdownCog."""
|
||||
|
||||
import logging
|
||||
|
||||
from interactions import Client, Extension, InteractionContext
|
||||
|
@ -26,6 +27,7 @@ async def lock(bot: Client, target: GuildChannel, admin: Member, reason: str, du
|
|||
bot: Bot instance
|
||||
target: Target channel
|
||||
admin: Admin who initiated lockdown
|
||||
|
||||
"""
|
||||
to_deny = Permissions.SEND_MESSAGES | Permissions.CONNECT | Permissions.SPEAK
|
||||
current = get(target.permission_overwrites, id=target.guild.id)
|
||||
|
@ -51,6 +53,7 @@ async def lock_all(bot: Client, guild: Guild, admin: Member, reason: str, durati
|
|||
bot: Bot instance
|
||||
guild: Target guild
|
||||
admin: Admin who initiated lockdown
|
||||
|
||||
"""
|
||||
role = await guild.fetch_role(guild.id)
|
||||
categories = find_all(lambda x: isinstance(x, GuildCategory), guild.channels)
|
||||
|
@ -71,6 +74,7 @@ async def unlock_all(bot: Client, guild: Guild, admin: Member) -> None:
|
|||
bot: Bot instance
|
||||
target: Target channel
|
||||
admin: Admin who ended lockdown
|
||||
|
||||
"""
|
||||
locks = Lock.find(Lock.guild == guild.id, Lock.active == True)
|
||||
async for lock in locks:
|
||||
|
@ -125,7 +129,7 @@ class LockdownCog(Extension):
|
|||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", ephemeral=True)
|
||||
return
|
||||
elif duration > 60 * 24 * 7:
|
||||
if duration > 60 * 24 * 7:
|
||||
await ctx.send("Duration must be <= 7 days", ephemeral=True)
|
||||
return
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Moderation Case management."""
|
||||
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from interactions import Extension, InteractionContext, Permissions
|
||||
|
@ -40,20 +41,15 @@ class CaseCog(Extension):
|
|||
Args:
|
||||
mod_case: Moderation case
|
||||
guild: Originating guild
|
||||
|
||||
"""
|
||||
action_table = Table()
|
||||
action_table.add_column(
|
||||
header="Type", justify="left", style="orange4", no_wrap=True
|
||||
)
|
||||
action_table.add_column(
|
||||
header="Admin", justify="left", style="cyan", no_wrap=True
|
||||
)
|
||||
action_table.add_column(header="Type", justify="left", style="orange4", no_wrap=True)
|
||||
action_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True)
|
||||
action_table.add_column(header="Reason", justify="left", style="white")
|
||||
|
||||
note_table = Table()
|
||||
note_table.add_column(
|
||||
header="Admin", justify="left", style="cyan", no_wrap=True
|
||||
)
|
||||
note_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True)
|
||||
note_table.add_column(header="Content", justify="left", style="white")
|
||||
|
||||
console = Console()
|
||||
|
@ -71,17 +67,13 @@ class CaseCog(Extension):
|
|||
admin_text = "[N/A]"
|
||||
if admin:
|
||||
admin_text = f"{admin.username}"
|
||||
action_table.add_row(
|
||||
action.action_type.title(), admin_text, parent_action.reason
|
||||
)
|
||||
action_table.add_row(action.action_type.title(), admin_text, parent_action.reason)
|
||||
with console.capture() as cap:
|
||||
console.print(action_table)
|
||||
|
||||
tmp_output = cap.get()
|
||||
if len(tmp_output) >= 800:
|
||||
action_output_extra = (
|
||||
f"... and {len(mod_case.actions[idx:])} more actions"
|
||||
)
|
||||
action_output_extra = f"... and {len(mod_case.actions[idx:])} more actions"
|
||||
break
|
||||
|
||||
action_output = tmp_output
|
||||
|
@ -128,9 +120,7 @@ class CaseCog(Extension):
|
|||
name="Actions",
|
||||
value=action_output if mod_case.actions else "No Actions Found",
|
||||
),
|
||||
EmbedField(
|
||||
name="Notes", value=note_output if mod_case.notes else "No Notes Found"
|
||||
),
|
||||
EmbedField(name="Notes", value=note_output if mod_case.notes else "No Notes Found"),
|
||||
)
|
||||
|
||||
embed = build_embed(
|
||||
|
@ -156,6 +146,7 @@ class CaseCog(Extension):
|
|||
Args:
|
||||
mod_case: Moderation case
|
||||
guild: Originating guild
|
||||
|
||||
"""
|
||||
embeds = []
|
||||
user = await self.bot.fetch_user(mod_case.user)
|
||||
|
@ -181,9 +172,7 @@ class CaseCog(Extension):
|
|||
if admin:
|
||||
admin_text = admin.mention
|
||||
|
||||
fields = (
|
||||
EmbedField(name=action.action_type.title(), value=parent_action.reason),
|
||||
)
|
||||
fields = (EmbedField(name=action.action_type.title(), value=parent_action.reason),)
|
||||
embed = build_embed(
|
||||
title="Moderation Case Action",
|
||||
description=f"{admin_text} initiated an action against {user_mention}",
|
||||
|
@ -236,12 +225,8 @@ class CaseCog(Extension):
|
|||
case = SlashCommand(name="case", description="Manage a moderation case")
|
||||
show = case.group(name="show", description="Show information about a specific case")
|
||||
|
||||
@show.subcommand(
|
||||
sub_cmd_name="summary", sub_cmd_description="Summarize a specific case"
|
||||
)
|
||||
@slash_option(
|
||||
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||
)
|
||||
@show.subcommand(sub_cmd_name="summary", sub_cmd_description="Summarize a specific case")
|
||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _case_show_summary(self, ctx: InteractionContext, cid: str) -> None:
|
||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||
|
@ -253,9 +238,7 @@ class CaseCog(Extension):
|
|||
await ctx.send(embeds=embed)
|
||||
|
||||
@show.subcommand(sub_cmd_name="actions", sub_cmd_description="Get case actions")
|
||||
@slash_option(
|
||||
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||
)
|
||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _case_show_actions(self, ctx: InteractionContext, cid: str) -> None:
|
||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||
|
@ -268,9 +251,7 @@ class CaseCog(Extension):
|
|||
await paginator.send(ctx)
|
||||
|
||||
@case.subcommand(sub_cmd_name="close", sub_cmd_description="Show a specific case")
|
||||
@slash_option(
|
||||
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||
)
|
||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _case_close(self, ctx: InteractionContext, cid: str) -> None:
|
||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||
|
@ -284,12 +265,8 @@ class CaseCog(Extension):
|
|||
embed = await self.get_summary_embed(case, ctx.guild)
|
||||
await ctx.send(embeds=embed)
|
||||
|
||||
@case.subcommand(
|
||||
sub_cmd_name="repoen", sub_cmd_description="Reopen a specific case"
|
||||
)
|
||||
@slash_option(
|
||||
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||
)
|
||||
@case.subcommand(sub_cmd_name="repoen", sub_cmd_description="Reopen a specific case")
|
||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _case_reopen(self, ctx: InteractionContext, cid: str) -> None:
|
||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||
|
@ -303,12 +280,8 @@ class CaseCog(Extension):
|
|||
embed = await self.get_summary_embed(case, ctx.guild)
|
||||
await ctx.send(embeds=embed)
|
||||
|
||||
@case.subcommand(
|
||||
sub_cmd_name="note", sub_cmd_description="Add a note to a specific case"
|
||||
)
|
||||
@slash_option(
|
||||
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||
)
|
||||
@case.subcommand(sub_cmd_name="note", sub_cmd_description="Add a note to a specific case")
|
||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
||||
@slash_option(
|
||||
name="note",
|
||||
description="Note to add",
|
||||
|
@ -323,9 +296,7 @@ class CaseCog(Extension):
|
|||
return
|
||||
|
||||
if not case.open:
|
||||
await ctx.send(
|
||||
"Case is closed, please re-open to add a new comment", ephemeral=True
|
||||
)
|
||||
await ctx.send("Case is closed, please re-open to add a new comment", ephemeral=True)
|
||||
return
|
||||
|
||||
if len(note) > 50:
|
||||
|
@ -341,9 +312,7 @@ class CaseCog(Extension):
|
|||
await ctx.send(embeds=embed)
|
||||
|
||||
@case.subcommand(sub_cmd_name="new", sub_cmd_description="Open a new case")
|
||||
@slash_option(
|
||||
name="user", description="Target user", opt_type=OptionType.USER, required=True
|
||||
)
|
||||
@slash_option(name="user", description="Target user", opt_type=OptionType.USER, required=True)
|
||||
@slash_option(
|
||||
name="note",
|
||||
description="Note to add",
|
||||
|
@ -352,9 +321,7 @@ class CaseCog(Extension):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _case_new(self, ctx: InteractionContext, user: Member, note: str) -> None:
|
||||
case = await Modlog.find_one(
|
||||
Modlog.guild == ctx.guild.id, Modlog.user == user.id, Modlog.open == True
|
||||
)
|
||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.user == user.id, Modlog.open == True)
|
||||
if case:
|
||||
await ctx.send(f"Case already open with ID `{case.nanoid}`", ephemeral=True)
|
||||
return
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS MuteCog."""
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
@ -27,9 +28,7 @@ from jarvis.utils.permissions import admin_or_permissions
|
|||
class MuteCog(ModcaseCog):
|
||||
"""JARVIS MuteCog."""
|
||||
|
||||
async def _apply_timeout(
|
||||
self, ctx: InteractionContext, user: Member, reason: str, until: datetime
|
||||
) -> None:
|
||||
async def _apply_timeout(self, ctx: InteractionContext, user: Member, reason: str, until: datetime) -> None:
|
||||
await user.timeout(communication_disabled_until=until, reason=reason)
|
||||
duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60)
|
||||
await Mute(
|
||||
|
@ -44,11 +43,7 @@ class MuteCog(ModcaseCog):
|
|||
return mute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
|
||||
|
||||
@context_menu(name="Mute User", context_type=CommandType.USER)
|
||||
@check(
|
||||
admin_or_permissions(
|
||||
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||
)
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
|
||||
async def _timeout_cm(self, ctx: InteractionContext) -> None:
|
||||
modal = Modal(
|
||||
*[
|
||||
|
@ -71,9 +66,7 @@ class MuteCog(ModcaseCog):
|
|||
)
|
||||
await ctx.send_modal(modal)
|
||||
try:
|
||||
response = await self.bot.wait_for_modal(
|
||||
modal, author=ctx.author.id, timeout=60 * 5
|
||||
)
|
||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
||||
reason = response.responses.get("reason")
|
||||
until = response.responses.get("until")
|
||||
except asyncio.TimeoutError:
|
||||
|
@ -84,9 +77,7 @@ class MuteCog(ModcaseCog):
|
|||
"RETURN_AS_TIMEZONE_AWARE": True,
|
||||
}
|
||||
rt_settings = base_settings.copy()
|
||||
rt_settings["PARSERS"] = [
|
||||
x for x in default_parsers if x not in ["absolute-time", "timestamp"]
|
||||
]
|
||||
rt_settings["PARSERS"] = [x for x in default_parsers if x not in ["absolute-time", "timestamp"]]
|
||||
|
||||
rt_until = parse(until, settings=rt_settings)
|
||||
|
||||
|
@ -101,14 +92,10 @@ class MuteCog(ModcaseCog):
|
|||
until = at_until
|
||||
else:
|
||||
self.logger.debug(f"Failed to parse delay: {until}")
|
||||
await response.send(
|
||||
f"`{until}` is not a parsable date, please try again", ephemeral=True
|
||||
)
|
||||
await response.send(f"`{until}` is not a parsable date, please try again", ephemeral=True)
|
||||
return
|
||||
if until < datetime.now(tz=timezone.utc):
|
||||
await response.send(
|
||||
f"`{old_until}` is in the past, which isn't allowed", ephemeral=True
|
||||
)
|
||||
await response.send(f"`{old_until}` is in the past, which isn't allowed", ephemeral=True)
|
||||
return
|
||||
try:
|
||||
embed = await self._apply_timeout(ctx, ctx.target, reason, until)
|
||||
|
@ -117,9 +104,7 @@ class MuteCog(ModcaseCog):
|
|||
await response.send("Unable to mute this user", ephemeral=True)
|
||||
|
||||
@slash_command(name="mute", description="Mute a user")
|
||||
@slash_option(
|
||||
name="user", description="User to mute", opt_type=OptionType.USER, required=True
|
||||
)
|
||||
@slash_option(name="user", description="User to mute", opt_type=OptionType.USER, required=True)
|
||||
@slash_option(
|
||||
name="reason",
|
||||
description="Reason for mute",
|
||||
|
@ -144,11 +129,7 @@ class MuteCog(ModcaseCog):
|
|||
SlashCommandChoice(name="Week(s)", value=10080),
|
||||
],
|
||||
)
|
||||
@check(
|
||||
admin_or_permissions(
|
||||
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||
)
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
|
||||
async def _timeout(
|
||||
self,
|
||||
ctx: InteractionContext,
|
||||
|
@ -173,9 +154,7 @@ class MuteCog(ModcaseCog):
|
|||
# Max 4 weeks (2419200 seconds) per API
|
||||
duration = time * scale
|
||||
if duration > 40320:
|
||||
await ctx.send(
|
||||
"Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True
|
||||
)
|
||||
await ctx.send("Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True)
|
||||
return
|
||||
|
||||
until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration)
|
||||
|
@ -198,16 +177,11 @@ class MuteCog(ModcaseCog):
|
|||
opt_type=OptionType.STRING,
|
||||
required=True,
|
||||
)
|
||||
@check(
|
||||
admin_or_permissions(
|
||||
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||
)
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
|
||||
async def _unmute(self, ctx: InteractionContext, user: Member, reason: str) -> None:
|
||||
if (
|
||||
not user.communication_disabled_until
|
||||
or user.communication_disabled_until.timestamp()
|
||||
< datetime.now(tz=timezone.utc).timestamp() # noqa: W503
|
||||
or user.communication_disabled_until.timestamp() < datetime.now(tz=timezone.utc).timestamp()
|
||||
):
|
||||
await ctx.send("User is not muted", ephemeral=True)
|
||||
return
|
||||
|
@ -218,8 +192,6 @@ class MuteCog(ModcaseCog):
|
|||
|
||||
await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc))
|
||||
|
||||
embed = unmute_embed(
|
||||
user=user, admin=ctx.author, reason=reason, guild=ctx.guild
|
||||
)
|
||||
embed = unmute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
|
||||
|
||||
await ctx.send(embeds=embed)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS PurgeCog."""
|
||||
|
||||
import logging
|
||||
|
||||
from interactions import Client, Extension, InteractionContext, Permissions
|
||||
|
@ -67,7 +68,7 @@ class PurgeCog(Extension):
|
|||
if delay <= 0:
|
||||
await ctx.send("Delay must be > 0", ephemeral=True)
|
||||
return
|
||||
elif delay > 300:
|
||||
if delay > 300:
|
||||
await ctx.send("Delay must be < 5 minutes", ephemeral=True)
|
||||
return
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS RolepingCog."""
|
||||
|
||||
import logging
|
||||
|
||||
from interactions import Client, Extension, InteractionContext, Permissions
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Settings Management Cog."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS temporary role handler."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
@ -46,7 +47,7 @@ class TemproleCog(Extension):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_ROLES))
|
||||
async def _temprole(
|
||||
self, ctx: InteractionContext, user: Member, role: Role, duration: str, reason: str = None
|
||||
self, ctx: InteractionContext, user: Member, role: Role, duration: str, reason: str = None # noqa: RUF013
|
||||
) -> None:
|
||||
await ctx.defer()
|
||||
if not isinstance(user, Member):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Verify Cog."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from random import randint
|
||||
|
@ -14,7 +15,7 @@ from jarvis_core.db.models import Setting
|
|||
def create_layout() -> list:
|
||||
"""Create verify component layout."""
|
||||
buttons = []
|
||||
yes = randint(0, 2) # noqa: S311
|
||||
yes = randint(0, 2)
|
||||
for i in range(3):
|
||||
label = "YES" if i == yes else "NO"
|
||||
id = f"no_{i}" if not i == yes else "yes"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS WarningCog."""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from interactions import InteractionContext, Permissions
|
||||
|
@ -24,9 +25,7 @@ class WarningCog(ModcaseCog):
|
|||
"""JARVIS WarningCog."""
|
||||
|
||||
@slash_command(name="warn", description="Warn a user")
|
||||
@slash_option(
|
||||
name="user", description="User to warn", opt_type=OptionType.USER, required=True
|
||||
)
|
||||
@slash_option(name="user", description="User to warn", opt_type=OptionType.USER, required=True)
|
||||
@slash_option(
|
||||
name="reason",
|
||||
description="Reason for warning",
|
||||
|
@ -40,16 +39,14 @@ class WarningCog(ModcaseCog):
|
|||
required=False,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _warn(
|
||||
self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24
|
||||
) -> None:
|
||||
async def _warn(self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24) -> None:
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||
return
|
||||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", ephemeral=True)
|
||||
return
|
||||
elif duration >= 120:
|
||||
if duration >= 120:
|
||||
await ctx.send("Duration must be < 5 days", ephemeral=True)
|
||||
return
|
||||
if not await ctx.guild.fetch_member(user.id):
|
||||
|
@ -70,9 +67,7 @@ class WarningCog(ModcaseCog):
|
|||
await ctx.send(embeds=embed)
|
||||
|
||||
@slash_command(name="warnings", description="Get count of user warnings")
|
||||
@slash_option(
|
||||
name="user", description="User to view", opt_type=OptionType.USER, required=True
|
||||
)
|
||||
@slash_option(name="user", description="User to view", opt_type=OptionType.USER, required=True)
|
||||
@slash_option(
|
||||
name="active",
|
||||
description="View active only",
|
||||
|
@ -80,9 +75,7 @@ class WarningCog(ModcaseCog):
|
|||
required=False,
|
||||
)
|
||||
# @check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _warnings(
|
||||
self, ctx: InteractionContext, user: Member, active: bool = True
|
||||
) -> None:
|
||||
async def _warnings(self, ctx: InteractionContext, user: Member, active: bool = True) -> None:
|
||||
warnings = (
|
||||
await Warning.find(
|
||||
Warning.user == user.id,
|
||||
|
@ -126,9 +119,7 @@ class WarningCog(ModcaseCog):
|
|||
for i in range(0, len(fields), 5):
|
||||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=(
|
||||
f"{len(warnings)} total | {len(active_warns)} currently active"
|
||||
),
|
||||
description=(f"{len(warnings)} total | {len(active_warns)} currently active"),
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
embed.set_author(
|
||||
|
@ -136,9 +127,7 @@ class WarningCog(ModcaseCog):
|
|||
icon_url=user.display_avatar.url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_footer(
|
||||
text=f"{user.username}#{user.discriminator} | {user.id}"
|
||||
)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
pages.append(embed)
|
||||
else:
|
||||
fields = []
|
||||
|
@ -156,9 +145,7 @@ class WarningCog(ModcaseCog):
|
|||
for i in range(0, len(fields), 5):
|
||||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=(
|
||||
f"{len(warnings)} total | {len(active_warns)} currently active"
|
||||
),
|
||||
description=(f"{len(warnings)} total | {len(active_warns)} currently active"),
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
embed.set_author(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"""JARVIS bot utility commands."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import platform
|
||||
from io import BytesIO
|
||||
|
@ -49,9 +48,7 @@ class BotutilCog(Extension):
|
|||
file_bytes.write(log.encode("UTF8"))
|
||||
file_bytes.seek(0)
|
||||
log = File(file_bytes, file_name=f"tail_{count}.log")
|
||||
await ctx.reply(
|
||||
content=f"Here's the last {count} lines of the log", file=log
|
||||
)
|
||||
await ctx.reply(content=f"Here's the last {count} lines of the log", file=log)
|
||||
else:
|
||||
await ctx.reply(content=f"```\n{log}\n```")
|
||||
|
||||
|
@ -80,17 +77,13 @@ class BotutilCog(Extension):
|
|||
inline=False,
|
||||
),
|
||||
EmbedField(name="Version", value=platform.release() or "N/A", inline=False),
|
||||
EmbedField(
|
||||
name="System Start Time", value=f"<t:{ut_ts}:F> (<t:{ut_ts}:R>)"
|
||||
),
|
||||
EmbedField(name="System Start Time", value=f"<t:{ut_ts}:F> (<t:{ut_ts}:R>)"),
|
||||
EmbedField(name="Python Version", value=platform.python_version()),
|
||||
EmbedField(name="Bot Start Time", value=f"<t:{st_ts}:F> (<t:{st_ts}:R>)"),
|
||||
)
|
||||
embed = build_embed(title="System Info", description="", fields=fields)
|
||||
embed.set_image(url=self.bot.user.avatar.url)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
|
||||
|
|
|
@ -36,13 +36,9 @@ class DataCog(Extension):
|
|||
|
||||
data = SlashCommand(name="data", description="Data commands")
|
||||
|
||||
@data.subcommand(
|
||||
sub_cmd_name="usage", sub_cmd_description="View data usage information"
|
||||
)
|
||||
@data.subcommand(sub_cmd_name="usage", sub_cmd_description="View data usage information")
|
||||
async def _data_usage(self, ctx: SlashContext) -> None:
|
||||
await ctx.send(
|
||||
"Please review our [Privacy Policy](https://s.zevs.me/jarvis-privacy)"
|
||||
)
|
||||
await ctx.send("Please review our [Privacy Policy](https://s.zevs.me/jarvis-privacy)")
|
||||
|
||||
@data.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete my data")
|
||||
async def _data_delete(self, ctx: SlashContext) -> None:
|
||||
|
@ -91,9 +87,7 @@ class DataCog(Extension):
|
|||
await message.edit(
|
||||
content="❌ There was an error. Please report it to the help server, or DM `@zevaryx` for help"
|
||||
)
|
||||
self.logger.error(
|
||||
f"Encountered error while deleting data: {e}", exc_info=True
|
||||
)
|
||||
self.logger.error(f"Encountered error while deleting data: {e}", exc_info=True)
|
||||
|
||||
else:
|
||||
for row in components:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Remind Me Cog."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
|
@ -28,7 +29,7 @@ from jarvis.utils import build_embed
|
|||
valid = re.compile(r"[\w\s\-\\/.!@?#$%^*()+=<>:'\",\u0080-\U000E0FFF]*")
|
||||
time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?", flags=re.IGNORECASE)
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
@ -60,12 +61,10 @@ class RemindmeCog(Extension):
|
|||
self,
|
||||
ctx: InteractionContext,
|
||||
timezone: str = "UTC",
|
||||
private: bool = None,
|
||||
private: bool = False,
|
||||
) -> None:
|
||||
if private is None and ctx.guild:
|
||||
if not private and ctx.guild:
|
||||
private = ctx.guild.member_count >= 5000
|
||||
elif private is None and not ctx.guild:
|
||||
private = False
|
||||
timezone = pytz.timezone(timezone)
|
||||
modal = Modal(
|
||||
*[
|
||||
|
@ -96,9 +95,7 @@ class RemindmeCog(Extension):
|
|||
|
||||
await ctx.send_modal(modal)
|
||||
try:
|
||||
response = await self.bot.wait_for_modal(
|
||||
modal, author=ctx.author.id, timeout=60 * 5
|
||||
)
|
||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
||||
message = response.responses.get("message").strip()
|
||||
delay = response.responses.get("delay").strip()
|
||||
cron = response.responses.get("cron").strip()
|
||||
|
@ -107,7 +104,7 @@ class RemindmeCog(Extension):
|
|||
if len(message) > 500:
|
||||
await response.send("Reminder cannot be > 500 characters.", ephemeral=True)
|
||||
return
|
||||
elif invites.search(message):
|
||||
if invites.search(message):
|
||||
await response.send(
|
||||
"Listen, don't use this to try and bypass the rules",
|
||||
ephemeral=True,
|
||||
|
@ -118,12 +115,10 @@ class RemindmeCog(Extension):
|
|||
# "Hey, you should probably make this readable", ephemeral=True
|
||||
# )
|
||||
# return
|
||||
elif len(message) == 0:
|
||||
await response.send(
|
||||
"Hey, you should probably add content to your reminder", ephemeral=True
|
||||
)
|
||||
if len(message) == 0:
|
||||
await response.send("Hey, you should probably add content to your reminder", ephemeral=True)
|
||||
return
|
||||
elif cron and not croniter.is_valid(cron):
|
||||
if cron and not croniter.is_valid(cron):
|
||||
await response.send(
|
||||
f"Invalid cron: {cron}\n\nUse https://crontab.guru to help",
|
||||
ephemeral=True,
|
||||
|
@ -136,9 +131,7 @@ class RemindmeCog(Extension):
|
|||
"RETURN_AS_TIMEZONE_AWARE": True,
|
||||
}
|
||||
rt_settings = base_settings.copy()
|
||||
rt_settings["PARSERS"] = [
|
||||
x for x in default_parsers if x not in ["absolute-time", "timestamp"]
|
||||
]
|
||||
rt_settings["PARSERS"] = [x for x in default_parsers if x not in ["absolute-time", "timestamp"]]
|
||||
|
||||
rt_remind_at = parse(delay, settings=rt_settings)
|
||||
|
||||
|
@ -165,7 +158,7 @@ class RemindmeCog(Extension):
|
|||
)
|
||||
return
|
||||
|
||||
elif remind_at < datetime.now(tz=timezone):
|
||||
if remind_at < datetime.now(tz=timezone):
|
||||
pass
|
||||
|
||||
r = Reminder(
|
||||
|
@ -217,17 +210,13 @@ class RemindmeCog(Extension):
|
|||
)
|
||||
components = [delete_button]
|
||||
if not r.guild == ctx.author.id:
|
||||
copy_button = Button(
|
||||
style=ButtonStyle.GREEN, emoji="📋", custom_id=f"copy|rme|{r.id}"
|
||||
)
|
||||
copy_button = Button(style=ButtonStyle.GREEN, emoji="📋", custom_id=f"copy|rme|{r.id}")
|
||||
components.append(copy_button)
|
||||
private = private if private is not None else False
|
||||
components = [ActionRow(*components)]
|
||||
await response.send(embeds=embed, components=components, ephemeral=private)
|
||||
|
||||
async def get_reminders_embed(
|
||||
self, ctx: InteractionContext, reminders: List[Reminder]
|
||||
) -> Embed:
|
||||
async def get_reminders_embed(self, ctx: InteractionContext, reminders: List[Reminder]) -> Embed:
|
||||
"""Build embed for paginator."""
|
||||
fields = []
|
||||
for reminder in reminders:
|
||||
|
@ -264,22 +253,16 @@ class RemindmeCog(Extension):
|
|||
|
||||
@reminders.subcommand(sub_cmd_name="list", sub_cmd_description="List reminders")
|
||||
async def _list(self, ctx: InteractionContext) -> None:
|
||||
reminders = await Reminder.find(
|
||||
Reminder.user == ctx.author.id, Reminder.active == True
|
||||
).to_list()
|
||||
reminders = await Reminder.find(Reminder.user == ctx.author.id, Reminder.active == True).to_list()
|
||||
if not reminders:
|
||||
await ctx.send("You have no reminders set.", ephemeral=True)
|
||||
return
|
||||
|
||||
embed = await self.get_reminders_embed(ctx, reminders)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@reminders.subcommand(
|
||||
sub_cmd_name="delete", sub_cmd_description="Delete a reminder"
|
||||
)
|
||||
@reminders.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a reminder")
|
||||
@slash_option(
|
||||
name="content",
|
||||
description="Content of the reminder",
|
||||
|
@ -309,9 +292,7 @@ class RemindmeCog(Extension):
|
|||
)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
try:
|
||||
await reminder.delete()
|
||||
except Exception:
|
||||
|
@ -343,18 +324,14 @@ class RemindmeCog(Extension):
|
|||
EmbedField(name="Created At", value=f"<t:{cts}:F> (<t:{cts}:R>)"),
|
||||
]
|
||||
|
||||
embed = build_embed(
|
||||
title="You have a reminder!", description=reminder.message, fields=fields
|
||||
)
|
||||
embed = build_embed(title="You have a reminder!", description=reminder.message, fields=fields)
|
||||
embed.set_author(
|
||||
name=ctx.author.display_name,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, ephemeral=reminder.private, components=components)
|
||||
if reminder.remind_at <= datetime.now(tz=timezone.utc) and not reminder.active:
|
||||
try:
|
||||
|
@ -366,10 +343,7 @@ class RemindmeCog(Extension):
|
|||
@_delete.autocomplete("content")
|
||||
async def _search_reminders(self, ctx: AutocompleteContext) -> None:
|
||||
reminders = await Reminder.find(Reminder.user == ctx.author.id).to_list()
|
||||
lookup = {
|
||||
f"[{r.created_at.strftime('%d/%m/%Y %H:%M.%S')}] {r.message}": str(r.id)
|
||||
for r in reminders
|
||||
}
|
||||
lookup = {f"[{r.created_at.strftime('%d/%m/%Y %H:%M.%S')}] {r.message}": str(r.id) for r in reminders}
|
||||
results = process.extract(ctx.input_text, list(lookup.keys()), limit=5)
|
||||
choices = [{"name": r[0], "value": lookup[r[0]]} for r in results]
|
||||
await ctx.send(choices=choices)
|
||||
|
|
|
@ -55,9 +55,7 @@ class UtilCog(Extension):
|
|||
|
||||
bot = SlashCommand(name="bot", description="Bot commands")
|
||||
|
||||
@bot.subcommand(
|
||||
sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS"
|
||||
)
|
||||
@bot.subcommand(sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS")
|
||||
async def _donate(self, ctx: SlashContext) -> None:
|
||||
await ctx.send(
|
||||
"""Want to support JARVIS? Donate here:
|
||||
|
@ -68,9 +66,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
"""
|
||||
)
|
||||
|
||||
@bot.subcommand(
|
||||
sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS"
|
||||
)
|
||||
@bot.subcommand(sub_cmd_name="donate", sub_cmd_description="Support the development of JARVIS")
|
||||
async def _donate(self, ctx: SlashContext) -> None:
|
||||
await ctx.send(
|
||||
"""Want to support JARVIS? Donate here:
|
||||
|
@ -111,10 +107,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
)
|
||||
)
|
||||
self.bot.logger.debug("Getting repo information")
|
||||
repo_url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/"
|
||||
fields.append(
|
||||
EmbedField(name="Online Since", value=f"<t:{uptime}:F>", inline=False)
|
||||
)
|
||||
fields.append(EmbedField(name="Online Since", value=f"<t:{uptime}:F>", inline=False))
|
||||
num_domains = len(self.bot.phishing_domains)
|
||||
fields.append(
|
||||
EmbedField(
|
||||
|
@ -123,9 +116,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
)
|
||||
)
|
||||
embed = build_embed(title=title, description=desc, fields=fields, color=color)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@bot.subcommand(
|
||||
|
@ -188,9 +179,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
|
||||
embed.set_image(url=avatar)
|
||||
embed.set_author(name=f"{user.username}", icon_url=avatar)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@slash_command(name="emotes", description="Get all emotes")
|
||||
|
@ -246,9 +235,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
EmbedField(name="Name", value=role.mention, inline=True),
|
||||
EmbedField(name="Color", value=str(role.color.hex), inline=True),
|
||||
EmbedField(name="Mention", value=f"`{role.mention}`", inline=True),
|
||||
EmbedField(
|
||||
name="Hoisted", value="Yes" if role.hoist else "No", inline=True
|
||||
),
|
||||
EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True),
|
||||
EmbedField(name="Position", value=str(role.position), inline=True),
|
||||
EmbedField(
|
||||
name="Mentionable",
|
||||
|
@ -256,9 +243,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
inline=True,
|
||||
),
|
||||
EmbedField(name="Member Count", value=str(len(role.members)), inline=True),
|
||||
EmbedField(
|
||||
name="Created At", value=f"<t:{int(role.created_at.timestamp())}:F>"
|
||||
),
|
||||
EmbedField(name="Created At", value=f"<t:{int(role.created_at.timestamp())}:F>"),
|
||||
]
|
||||
embed = build_embed(
|
||||
title="",
|
||||
|
@ -331,18 +316,14 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
),
|
||||
EmbedField(
|
||||
name=f"Roles [{len(user_roles)}]",
|
||||
value=(
|
||||
" ".join([x.mention for x in user_roles]) if user_roles else "None"
|
||||
),
|
||||
value=(" ".join([x.mention for x in user_roles]) if user_roles else "None"),
|
||||
inline=False,
|
||||
),
|
||||
]
|
||||
|
||||
if muted:
|
||||
ts = int(user.communication_disabled_until.timestamp())
|
||||
fields.append(
|
||||
EmbedField(name="Muted Until", value=f"<t:{ts}:F> (<t:{ts}:R>)")
|
||||
)
|
||||
fields.append(EmbedField(name="Muted Until", value=f"<t:{ts}:F> (<t:{ts}:R>)"))
|
||||
|
||||
embed = build_embed(
|
||||
title="",
|
||||
|
@ -357,9 +338,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
)
|
||||
embed.set_thumbnail(url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"ID: {user.id}")
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@slash_command(name="lmgtfy", description="Let me Google that for you")
|
||||
|
@ -396,9 +375,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
|
||||
owner = await guild.fetch_owner()
|
||||
|
||||
owner = (
|
||||
f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
|
||||
)
|
||||
owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
|
||||
|
||||
categories = len([x for x in guild.channels if isinstance(x, GuildCategory)])
|
||||
text_channels = len([x for x in guild.channels if isinstance(x, GuildText)])
|
||||
|
@ -417,45 +394,31 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
EmbedField(name="Threads", value=str(threads), inline=True),
|
||||
EmbedField(name="Members", value=str(members), inline=True),
|
||||
EmbedField(name="Roles", value=str(roles), inline=True),
|
||||
EmbedField(
|
||||
name="Created At", value=f"<t:{int(guild.created_at.timestamp())}:F>"
|
||||
),
|
||||
EmbedField(name="Created At", value=f"<t:{int(guild.created_at.timestamp())}:F>"),
|
||||
]
|
||||
|
||||
role_embeds = []
|
||||
if len(comma_role_list) < 1024:
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name=f"Role List [{roles}]", value=comma_role_list, inline=False
|
||||
)
|
||||
)
|
||||
fields.append(EmbedField(name=f"Role List [{roles}]", value=comma_role_list, inline=False))
|
||||
else:
|
||||
current_role_list = role_list[0].mention
|
||||
for role in role_list[1:]:
|
||||
if len(current_role_list + ", " + role.mention) > 3192:
|
||||
role_embed = build_embed(
|
||||
title="", description=current_role_list, fields=[]
|
||||
)
|
||||
role_embed = build_embed(title="", description=current_role_list, fields=[])
|
||||
role_embeds.append(role_embed)
|
||||
current_role_list = role.mention
|
||||
else:
|
||||
current_role_list += ", " + role.mention
|
||||
if len(current_role_list) > 0:
|
||||
role_embed = build_embed(
|
||||
title="", description=current_role_list, fields=[]
|
||||
)
|
||||
role_embed = build_embed(title="", description=current_role_list, fields=[])
|
||||
role_embeds.append(role_embed)
|
||||
|
||||
embed = build_embed(
|
||||
title="", description="", fields=fields, timestamp=guild.created_at
|
||||
)
|
||||
embed = build_embed(title="", description="", fields=fields, timestamp=guild.created_at)
|
||||
|
||||
embed.set_author(name=guild.name, icon_url=guild.icon.url)
|
||||
embed.set_thumbnail(url=guild.icon.url)
|
||||
embed.set_footer(text=f"ID: {guild.id} | Server Created")
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@slash_command(
|
||||
|
@ -482,9 +445,7 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
],
|
||||
)
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
|
||||
async def _pw_gen(
|
||||
self, ctx: SlashContext, length: int = 32, chars: int = 3
|
||||
) -> None:
|
||||
async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3) -> None:
|
||||
if length > 256:
|
||||
await ctx.send("Please limit password to 256 characters", ephemeral=True)
|
||||
return
|
||||
|
@ -541,18 +502,14 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
opt_type=OptionType.BOOLEAN,
|
||||
required=False,
|
||||
)
|
||||
async def _timestamp(
|
||||
self, ctx: SlashContext, string: str, private: bool = False
|
||||
) -> None:
|
||||
async def _timestamp(self, ctx: SlashContext, string: str, private: bool = False) -> None:
|
||||
timestamp = parse(string)
|
||||
if not timestamp:
|
||||
await ctx.send("Valid time not found, try again", ephemeral=True)
|
||||
return
|
||||
|
||||
if not timestamp.tzinfo:
|
||||
timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(
|
||||
tz=timezone.utc
|
||||
)
|
||||
timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(tz=timezone.utc)
|
||||
|
||||
timestamp_utc = timestamp.astimezone(tz=timezone.utc)
|
||||
|
||||
|
@ -565,12 +522,8 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
EmbedField(name="Relative Time", value=f"<t:{ts_utc}:R>\n`<t:{ts_utc}:R>`"),
|
||||
EmbedField(name="ISO8601", value=timestamp.isoformat()),
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Converted Time", description=f"`{string}`", fields=fields
|
||||
)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
embed = build_embed(title="Converted Time", description=f"`{string}`", fields=fields)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, ephemeral=private, components=components)
|
||||
|
||||
@bot.subcommand(sub_cmd_name="support", sub_cmd_description="Got issues?")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS extra, optional cogs"""
|
||||
|
||||
from interactions import Client
|
||||
|
||||
from jarvis.cogs.extra import calc, dev, image, pinboard, rolegiver, tags
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Calculator Cog."""
|
||||
|
||||
from calculator import calculate
|
||||
from erapi import const
|
||||
from interactions import AutocompleteContext, Client, Extension, InteractionContext
|
||||
|
@ -38,9 +39,7 @@ class CalcCog(Extension):
|
|||
|
||||
calc = SlashCommand(name="calc", description="Calculate some things")
|
||||
|
||||
@calc.subcommand(
|
||||
sub_cmd_name="math", sub_cmd_description="Do a basic math calculation"
|
||||
)
|
||||
@calc.subcommand(sub_cmd_name="math", sub_cmd_description="Do a basic math calculation")
|
||||
@slash_option(
|
||||
name="expression",
|
||||
description="Expression to calculate",
|
||||
|
@ -77,16 +76,12 @@ class CalcCog(Extension):
|
|||
)
|
||||
embed = build_embed(title="Calculator", description=None, fields=fields)
|
||||
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
convert = calc.group(name="convert", description="Conversion helpers")
|
||||
|
||||
@convert.subcommand(
|
||||
sub_cmd_name="temperature", sub_cmd_description="Convert between temperatures"
|
||||
)
|
||||
@convert.subcommand(sub_cmd_name="temperature", sub_cmd_description="Convert between temperatures")
|
||||
@slash_option(
|
||||
name="value",
|
||||
description="Value to convert",
|
||||
|
@ -136,9 +131,7 @@ class CalcCog(Extension):
|
|||
description=f"°{TEMP_LOOKUP.get(from_unit)} -> °{TEMP_LOOKUP.get(to_unit)}",
|
||||
fields=fields,
|
||||
)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@convert.subcommand(
|
||||
|
@ -201,9 +194,7 @@ class CalcCog(Extension):
|
|||
)
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
async def _convert(
|
||||
self, ctx: InteractionContext, from_: str, to: str, value: int
|
||||
) -> Embed:
|
||||
async def _convert(self, ctx: InteractionContext, from_: str, to: str, value: int) -> Embed:
|
||||
*_, which = ctx.invoke_target.split(" ")
|
||||
which = getattr(units, which.capitalize(), None)
|
||||
ratio = which.get_rate(from_, to)
|
||||
|
@ -212,12 +203,8 @@ class CalcCog(Extension):
|
|||
EmbedField(name=from_, value=f"{value:0.2f}"),
|
||||
EmbedField(name=to, value=f"{converted:0.2f}"),
|
||||
)
|
||||
embed = build_embed(
|
||||
title="Conversion", description=f"{from_} -> {to}", fields=fields
|
||||
)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
embed = build_embed(title="Conversion", description=f"{from_} -> {to}", fields=fields)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="angle", sub_cmd_description="Convert angles")
|
||||
|
@ -241,9 +228,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_angle(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_angle(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="area", sub_cmd_description="Convert areas")
|
||||
|
@ -267,9 +252,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_area(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_area(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="data", sub_cmd_description="Convert data sizes")
|
||||
|
@ -293,9 +276,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_data(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_data(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="energy", sub_cmd_description="Convert energy")
|
||||
|
@ -319,9 +300,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_energy(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_energy(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="length", sub_cmd_description="Convert lengths")
|
||||
|
@ -345,9 +324,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_length(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_length(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="power", sub_cmd_description="Convert powers")
|
||||
|
@ -371,14 +348,10 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_power(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_power(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(
|
||||
sub_cmd_name="pressure", sub_cmd_description="Convert pressures"
|
||||
)
|
||||
@convert.subcommand(sub_cmd_name="pressure", sub_cmd_description="Convert pressures")
|
||||
@slash_option(
|
||||
name="value",
|
||||
description="Pressure to convert",
|
||||
|
@ -399,9 +372,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_pressure(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_pressure(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="speed", sub_cmd_description="Convert speeds")
|
||||
|
@ -425,9 +396,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_speed(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_speed(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="time", sub_cmd_description="Convert times")
|
||||
|
@ -451,9 +420,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_time(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_time(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="volume", sub_cmd_description="Convert volumes")
|
||||
|
@ -477,9 +444,7 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_volume(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_volume(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
@convert.subcommand(sub_cmd_name="weight", sub_cmd_description="Convert weights")
|
||||
|
@ -503,17 +468,13 @@ class CalcCog(Extension):
|
|||
required=True,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _calc_convert_weight(
|
||||
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||
) -> None:
|
||||
async def _calc_convert_weight(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
||||
await self._convert(ctx, from_unit, to_unit, value)
|
||||
|
||||
def _unit_autocomplete(
|
||||
self, which: units.Converter, unit: str
|
||||
) -> list[dict[str, str]]:
|
||||
def _unit_autocomplete(self, which: units.Converter, unit: str) -> list[dict[str, str]]:
|
||||
options = list(which.CONVERSIONS.keys())
|
||||
results = process.extract(unit, options, limit=25)
|
||||
if any([r[1] > 0 for r in results]):
|
||||
if any(r[1] > 0 for r in results):
|
||||
return [{"name": r[0], "value": r[0]} for r in results if r[1] > 50]
|
||||
return [{"name": r[0], "value": r[0]} for r in results]
|
||||
|
||||
|
@ -564,8 +525,8 @@ class CalcCog(Extension):
|
|||
if r[1] > results[name]:
|
||||
results[name] = r[1]
|
||||
|
||||
results = sorted(list(results.items()), key=lambda x: -x[1])[:10]
|
||||
if any([r[1] > 0 for r in results]):
|
||||
results = sorted(results.items(), key=lambda x: -x[1])[:10]
|
||||
if any(r[1] > 0 for r in results):
|
||||
return [{"name": r[0], "value": lookup_name[r[0]]} for r in results if r[1]]
|
||||
return [{"name": r[0], "value": lookup_name[r[0]]} for r in results]
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
"""JARVIS Developer Cog."""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
import re
|
||||
import subprocess # noqa: S404
|
||||
import subprocess
|
||||
import uuid as uuidpy
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from typing import ClassVar
|
||||
|
||||
import nanoid
|
||||
import pytz
|
||||
|
@ -40,11 +42,9 @@ from jarvis.utils import build_embed
|
|||
supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x}
|
||||
|
||||
OID_VERIFY = re.compile(r"^([1-9][0-9]{0,3}|0)(\.([1-9][0-9]{0,3}|0)){5,13}$")
|
||||
URL_VERIFY = re.compile(
|
||||
r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
|
||||
)
|
||||
URL_VERIFY = re.compile(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
|
||||
DN_VERIFY = re.compile(
|
||||
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$" # noqa: E501
|
||||
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$"
|
||||
)
|
||||
ULID_VERIFY = re.compile(r"^[0-9a-z]{26}$", re.IGNORECASE)
|
||||
UUID_VERIFY = re.compile(
|
||||
|
@ -127,7 +127,7 @@ class DevCog(Extension):
|
|||
self,
|
||||
ctx: InteractionContext,
|
||||
method: str,
|
||||
data: str = None,
|
||||
data: str = None, # noqa: RUF013
|
||||
attach: Attachment = None,
|
||||
) -> None:
|
||||
if not data and not attach:
|
||||
|
@ -146,12 +146,8 @@ class DevCog(Extension):
|
|||
elif url.match(data):
|
||||
try:
|
||||
if (size := await get_size(data)) > MAX_FILESIZE:
|
||||
await ctx.send(
|
||||
"Please hash files that are <= 5GB in size", ephemeral=True
|
||||
)
|
||||
self.logger.debug(
|
||||
f"Refused to hash file of size {convert_bytesize(size)}"
|
||||
)
|
||||
await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True)
|
||||
self.logger.debug(f"Refused to hash file of size {convert_bytesize(size)}")
|
||||
return
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True)
|
||||
|
@ -174,9 +170,7 @@ class DevCog(Extension):
|
|||
]
|
||||
|
||||
embed = build_embed(title=title, description=description, fields=fields)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@dev.subcommand(sub_cmd_name="uuid", sub_cmd_description="Generate a UUID")
|
||||
|
@ -193,9 +187,7 @@ class DevCog(Extension):
|
|||
opt_type=OptionType.STRING,
|
||||
required=False,
|
||||
)
|
||||
async def _uuid(
|
||||
self, ctx: InteractionContext, version: str, data: str = None
|
||||
) -> None:
|
||||
async def _uuid(self, ctx: InteractionContext, version: str, data: str = None) -> None: # noqa: RUF013
|
||||
version = int(version)
|
||||
if version in [3, 5] and not data:
|
||||
await ctx.send(f"UUID{version} requires data.", ephemeral=True)
|
||||
|
@ -220,7 +212,7 @@ class DevCog(Extension):
|
|||
)
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _objectid(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(f"ObjectId: `{str(ObjectId())}`")
|
||||
await ctx.send(f"ObjectId: `{ObjectId()!s}`")
|
||||
|
||||
@dev.subcommand(
|
||||
sub_cmd_name="ulid",
|
||||
|
@ -266,7 +258,7 @@ class DevCog(Extension):
|
|||
else:
|
||||
await ctx.send("Invalid ULID.")
|
||||
|
||||
base64_methods = ["b64", "b16", "b32", "a85", "b85"]
|
||||
base64_methods: ClassVar[list[str]] = ["b64", "b16", "b32", "a85", "b85"]
|
||||
|
||||
@dev.subcommand(sub_cmd_name="encode", sub_cmd_description="Encode some data")
|
||||
@slash_option(
|
||||
|
@ -301,9 +293,7 @@ class DevCog(Extension):
|
|||
EmbedField(name=mstr, value=f"`{encoded}`", inline=False),
|
||||
]
|
||||
embed = build_embed(title="Encoded Data", description="", fields=fields)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@dev.subcommand(sub_cmd_name="decode", sub_cmd_description="Decode some data")
|
||||
|
@ -339,18 +329,14 @@ class DevCog(Extension):
|
|||
EmbedField(name=mstr, value=f"`{decoded}`", inline=False),
|
||||
]
|
||||
embed = build_embed(title="Decoded Data", description="", fields=fields)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@dev.subcommand(sub_cmd_name="cloc", sub_cmd_description="Get JARVIS lines of code")
|
||||
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||
async def _cloc(self, ctx: InteractionContext) -> None:
|
||||
await ctx.defer()
|
||||
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode(
|
||||
"UTF-8"
|
||||
) # noqa: S603, S607
|
||||
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8")
|
||||
console = Console()
|
||||
with console.capture() as capture:
|
||||
console.print(output)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS image processing cog."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from io import BytesIO
|
||||
|
@ -62,7 +63,7 @@ class ImageCog(Extension):
|
|||
required=False,
|
||||
)
|
||||
async def _resize(
|
||||
self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None
|
||||
self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None # noqa: RUF013
|
||||
) -> None:
|
||||
await ctx.defer()
|
||||
if not attachment and not url:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Starboard Cog."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
|
@ -44,9 +45,7 @@ class PinboardCog(Extension):
|
|||
|
||||
async def _purge_starboard(self, ctx: InteractionContext, board: Pinboard) -> None:
|
||||
channel = await ctx.guild.fetch_channel(board.channel)
|
||||
async for pin in Pin.find(
|
||||
Pin.pinboard == channel.id, Pin.guild == ctx.guild.id
|
||||
):
|
||||
async for pin in Pin.find(Pin.pinboard == channel.id, Pin.guild == ctx.guild.id):
|
||||
if message := await channel.fetch_message(pin.message):
|
||||
try:
|
||||
await message.delete()
|
||||
|
@ -91,13 +90,9 @@ class PinboardCog(Extension):
|
|||
await ctx.send("Channel must be a GuildText", ephemeral=True)
|
||||
return
|
||||
|
||||
exists = await Pinboard.find_one(
|
||||
Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id
|
||||
)
|
||||
exists = await Pinboard.find_one(Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id)
|
||||
if exists:
|
||||
await ctx.send(
|
||||
f"Pinboard already exists at {channel.mention}.", ephemeral=True
|
||||
)
|
||||
await ctx.send(f"Pinboard already exists at {channel.mention}.", ephemeral=True)
|
||||
return
|
||||
|
||||
count = await Pinboard.find(Pinboard.guild == ctx.guild.id).count()
|
||||
|
@ -121,17 +116,16 @@ class PinboardCog(Extension):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD, Permissions.MANAGE_MESSAGES))
|
||||
async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
found = await Pinboard.find_one(
|
||||
Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id
|
||||
)
|
||||
found = await Pinboard.find_one(Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id)
|
||||
if found:
|
||||
await found.delete()
|
||||
asyncio.create_task(self._purge_starboard(ctx, found))
|
||||
task = asyncio.create_task(self._purge_starboard(ctx, found))
|
||||
await ctx.send(f"Pinboard deleted from {channel.mention}.")
|
||||
await asyncio.wait([task])
|
||||
else:
|
||||
await ctx.send(f"Pinboard not found in {channel.mention}.", ephemeral=True)
|
||||
|
||||
async def _star_add(
|
||||
async def _star_add( # noqa: C901
|
||||
self,
|
||||
ctx: InteractionContext,
|
||||
message: str,
|
||||
|
@ -166,9 +160,7 @@ class PinboardCog(Extension):
|
|||
channel_list.append(c)
|
||||
channel_to_pinboard[c.id] = pinboard
|
||||
else:
|
||||
self.logger.warning(
|
||||
f"Pinboard {pinboard.channel} no longer valid in {ctx.guild.name}"
|
||||
)
|
||||
self.logger.warning(f"Pinboard {pinboard.channel} no longer valid in {ctx.guild.name}")
|
||||
to_delete.append(pinboard)
|
||||
|
||||
for pinboard in to_delete:
|
||||
|
@ -182,10 +174,7 @@ class PinboardCog(Extension):
|
|||
if x:
|
||||
select_channels.append(StringSelectOption(label=x.name, value=str(idx)))
|
||||
|
||||
select_channels = [
|
||||
StringSelectOption(label=x.name, value=str(idx))
|
||||
for idx, x in enumerate(channel_list)
|
||||
]
|
||||
select_channels = [StringSelectOption(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list)]
|
||||
|
||||
select = StringSelectMenu(
|
||||
*select_channels,
|
||||
|
@ -219,9 +208,7 @@ class PinboardCog(Extension):
|
|||
)
|
||||
return
|
||||
|
||||
count = await Pin.find(
|
||||
Pin.guild == ctx.guild.id, Pin.pinboard == pinboard.id
|
||||
).count()
|
||||
count = await Pin.find(Pin.guild == ctx.guild.id, Pin.pinboard == pinboard.id).count()
|
||||
content = message.content
|
||||
|
||||
attachments = message.attachments
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Role Giver Cog."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
|
@ -69,15 +70,11 @@ class RolegiverCog(Extension):
|
|||
rolegiver.roles = roles
|
||||
await rolegiver.save()
|
||||
|
||||
rolegiver = SlashCommand(
|
||||
name="rolegiver", description="Allow users to choose their own roles"
|
||||
)
|
||||
rolegiver = SlashCommand(name="rolegiver", description="Allow users to choose their own roles")
|
||||
|
||||
rg_group = rolegiver.group(name="group", description="Manage rolegiver groups")
|
||||
|
||||
@rg_group.subcommand(
|
||||
sub_cmd_name="create", sub_cmd_description="Create a rolegiver group"
|
||||
)
|
||||
@rg_group.subcommand(sub_cmd_name="create", sub_cmd_description="Create a rolegiver group")
|
||||
@slash_option(
|
||||
name="group",
|
||||
description="Group to create",
|
||||
|
@ -86,9 +83,7 @@ class RolegiverCog(Extension):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _group_create(self, ctx: InteractionContext, group: str):
|
||||
if await Rolegiver.find_one(
|
||||
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
|
||||
):
|
||||
if await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group):
|
||||
await ctx.send("Group already exists!", ephemeral=True)
|
||||
return
|
||||
|
||||
|
@ -102,9 +97,7 @@ class RolegiverCog(Extension):
|
|||
elif rolegiver.group not in self._group_cache[ctx.guild.id]:
|
||||
self._group_cache[ctx.guild.id][rolegiver.group] = {}
|
||||
|
||||
@rg_group.subcommand(
|
||||
sub_cmd_name="delete", sub_cmd_description="DDelete a rolegiver group"
|
||||
)
|
||||
@rg_group.subcommand(sub_cmd_name="delete", sub_cmd_description="DDelete a rolegiver group")
|
||||
@slash_option(
|
||||
name="group",
|
||||
description="Group to delete",
|
||||
|
@ -114,9 +107,7 @@ class RolegiverCog(Extension):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _group_delete(self, ctx: InteractionContext, group: str):
|
||||
if rolegiver := await Rolegiver.find_one(
|
||||
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
|
||||
):
|
||||
if rolegiver := await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group):
|
||||
await rolegiver.delete()
|
||||
await ctx.send(f"Rolegiver group {group} deleted!")
|
||||
del self._group_cache[ctx.guild.id][rolegiver.group]
|
||||
|
@ -127,9 +118,7 @@ class RolegiverCog(Extension):
|
|||
sub_cmd_name="add",
|
||||
sub_cmd_description="Add a role to rolegiver",
|
||||
)
|
||||
@slash_option(
|
||||
name="role", description="Role to add", opt_type=OptionType.ROLE, required=True
|
||||
)
|
||||
@slash_option(name="role", description="Role to add", opt_type=OptionType.ROLE, required=True)
|
||||
@slash_option(
|
||||
name="group",
|
||||
description="Group to add to",
|
||||
|
@ -138,9 +127,7 @@ class RolegiverCog(Extension):
|
|||
autocomplete=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _rolegiver_add(
|
||||
self, ctx: InteractionContext, role: Role, group: str = "Default"
|
||||
) -> None:
|
||||
async def _rolegiver_add(self, ctx: InteractionContext, role: Role, group: str = "Default") -> None:
|
||||
if role.id == ctx.guild.id:
|
||||
await ctx.send("Cannot add `@everyone` to rolegiver", ephemeral=True)
|
||||
return
|
||||
|
@ -152,14 +139,12 @@ class RolegiverCog(Extension):
|
|||
)
|
||||
return
|
||||
|
||||
rolegiver = await Rolegiver.find_one(
|
||||
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
|
||||
)
|
||||
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
|
||||
if rolegiver and rolegiver.roles and role.id in rolegiver.roles:
|
||||
await ctx.send("Role already in rolegiver", ephemeral=True)
|
||||
return
|
||||
|
||||
elif rolegiver and len(rolegiver.roles) >= 15:
|
||||
if rolegiver and len(rolegiver.roles) >= 15:
|
||||
await ctx.send(
|
||||
f"Maximum roles in group {group}. Please make a new group",
|
||||
ephemeral=True,
|
||||
|
@ -200,18 +185,14 @@ class RolegiverCog(Extension):
|
|||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
if ctx.guild.id not in self._group_cache:
|
||||
self._group_cache[ctx.guild.id] = {group: {}}
|
||||
self._group_cache[ctx.guild.id][group].update({role.name: role.id})
|
||||
|
||||
@rolegiver.subcommand(
|
||||
sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver"
|
||||
)
|
||||
@rolegiver.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver")
|
||||
@slash_option(
|
||||
name="group",
|
||||
description="Name of group to remove from",
|
||||
|
@ -220,9 +201,7 @@ class RolegiverCog(Extension):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _rolegiver_remove(self, ctx: InteractionContext, group: str) -> None:
|
||||
rolegiver = await Rolegiver.find_one(
|
||||
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
|
||||
)
|
||||
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
|
||||
if not rolegiver or (rolegiver and not rolegiver.roles):
|
||||
await ctx.send(f"Rolegiver {group} has no roles", ephemeral=True)
|
||||
return
|
||||
|
@ -234,11 +213,9 @@ class RolegiverCog(Extension):
|
|||
if role:
|
||||
roles[int(role.id)] = role
|
||||
|
||||
rolegiver.roles = [r for r in roles.keys()]
|
||||
rolegiver.roles = list(roles.keys())
|
||||
|
||||
options = [
|
||||
StringSelectOption(label=v.name, value=str(k)) for k, v in roles.items()
|
||||
][:25]
|
||||
options = [StringSelectOption(label=v.name, value=str(k)) for k, v in roles.items()][:25]
|
||||
select = StringSelectMenu(
|
||||
*options,
|
||||
placeholder="Select roles to remove",
|
||||
|
@ -247,9 +224,7 @@ class RolegiverCog(Extension):
|
|||
)
|
||||
components = [ActionRow(select)]
|
||||
|
||||
message = await ctx.send(
|
||||
content=f"Removing roles from {group}", components=components
|
||||
)
|
||||
message = await ctx.send(content=f"Removing roles from {group}", components=components)
|
||||
|
||||
try:
|
||||
resp = await self.bot.wait_for_component(
|
||||
|
@ -279,9 +254,7 @@ class RolegiverCog(Extension):
|
|||
),
|
||||
EmbedField(
|
||||
name="Remaining Role(s)",
|
||||
value="\n".join(
|
||||
x.mention for x in roles.values() if x not in removed_roles
|
||||
),
|
||||
value="\n".join(x.mention for x in roles.values() if x not in removed_roles),
|
||||
),
|
||||
]
|
||||
|
||||
|
@ -297,13 +270,9 @@ class RolegiverCog(Extension):
|
|||
embeds=embed,
|
||||
)
|
||||
|
||||
self._group_cache[ctx.guild.id].update(
|
||||
{group: {v.name: k for k, v in roles.items() if v not in removed_roles}}
|
||||
)
|
||||
self._group_cache[ctx.guild.id].update({group: {v.name: k for k, v in roles.items() if v not in removed_roles}})
|
||||
|
||||
@rolegiver.subcommand(
|
||||
sub_cmd_name="list", sub_cmd_description="List rolegiver roles"
|
||||
)
|
||||
@rolegiver.subcommand(sub_cmd_name="list", sub_cmd_description="List rolegiver roles")
|
||||
@slash_option(
|
||||
name="group",
|
||||
description="Name of group to list",
|
||||
|
@ -311,12 +280,8 @@ class RolegiverCog(Extension):
|
|||
required=False,
|
||||
autocomplete=True,
|
||||
)
|
||||
async def _rolegiver_list(
|
||||
self, ctx: InteractionContext, group: str = "Default"
|
||||
) -> None:
|
||||
setting = await Rolegiver.find_one(
|
||||
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
|
||||
)
|
||||
async def _rolegiver_list(self, ctx: InteractionContext, group: str = "Default") -> None:
|
||||
setting = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
@ -342,9 +307,7 @@ class RolegiverCog(Extension):
|
|||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
role = SlashCommand(name="role", description="Get roles!")
|
||||
|
@ -359,9 +322,7 @@ class RolegiverCog(Extension):
|
|||
)
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
||||
async def _role_get(self, ctx: InteractionContext, group: str = "Default") -> None:
|
||||
rolegiver = await Rolegiver.find_one(
|
||||
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
|
||||
)
|
||||
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
|
||||
if not rolegiver or (rolegiver and not rolegiver.roles):
|
||||
await ctx.send(f"Rolegiver group {group} has no roles", ephemeral=True)
|
||||
return
|
||||
|
@ -409,9 +370,7 @@ class RolegiverCog(Extension):
|
|||
added_roles.append(role)
|
||||
await ctx.author.add_role(role, reason="Rolegiver")
|
||||
|
||||
avalue = (
|
||||
"\n".join([r.mention for r in added_roles]) if added_roles else "None"
|
||||
)
|
||||
avalue = "\n".join([r.mention for r in added_roles]) if added_roles else "None"
|
||||
fields = [
|
||||
EmbedField(name="Added Role(s)", value=avalue),
|
||||
]
|
||||
|
@ -434,9 +393,7 @@ class RolegiverCog(Extension):
|
|||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
await response.ctx.edit_origin(
|
||||
embeds=embed, content="\u200b", components=components
|
||||
)
|
||||
await response.ctx.edit_origin(embeds=embed, content="\u200b", components=components)
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
|
@ -452,21 +409,15 @@ class RolegiverCog(Extension):
|
|||
autocomplete=True,
|
||||
)
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
||||
async def _role_remove(
|
||||
self, ctx: InteractionContext, group: str = "Default"
|
||||
) -> None:
|
||||
async def _role_remove(self, ctx: InteractionContext, group: str = "Default") -> None:
|
||||
user_roles = ctx.author.roles
|
||||
|
||||
rolegiver = await Rolegiver.find_one(
|
||||
Rolegiver.guild == ctx.guild.id, Rolegiver.group == group
|
||||
)
|
||||
rolegiver = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id, Rolegiver.group == group)
|
||||
if not rolegiver or (rolegiver and not rolegiver.roles):
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
elif not any(x.id in rolegiver.roles for x in user_roles):
|
||||
await ctx.send(
|
||||
f"You have no rolegiver roles from group {group}", ephemeral=True
|
||||
)
|
||||
if not any(x.id in rolegiver.roles for x in user_roles):
|
||||
await ctx.send(f"You have no rolegiver roles from group {group}", ephemeral=True)
|
||||
return
|
||||
|
||||
valid = list(filter(lambda x: x.id in rolegiver.roles, user_roles))
|
||||
|
@ -500,11 +451,7 @@ class RolegiverCog(Extension):
|
|||
user_roles.remove(role)
|
||||
removed_roles.append(role)
|
||||
|
||||
rvalue = (
|
||||
"\n".join([r.mention for r in removed_roles])
|
||||
if removed_roles
|
||||
else "None"
|
||||
)
|
||||
rvalue = "\n".join([r.mention for r in removed_roles]) if removed_roles else "None"
|
||||
fields = [
|
||||
EmbedField(name="Removed Role(s)", value=rvalue),
|
||||
]
|
||||
|
@ -516,9 +463,7 @@ class RolegiverCog(Extension):
|
|||
)
|
||||
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_author(
|
||||
name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url
|
||||
)
|
||||
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||
|
||||
|
@ -526,9 +471,7 @@ class RolegiverCog(Extension):
|
|||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
await context.ctx.edit_origin(
|
||||
embeds=embed, components=components, content="\u200b"
|
||||
)
|
||||
await context.ctx.edit_origin(embeds=embed, components=components, content="\u200b")
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
|
@ -574,9 +517,7 @@ class RolegiverCog(Extension):
|
|||
if rolegivers:
|
||||
await self._update_cache_from_autocomplete(ctx, rolegivers)
|
||||
|
||||
async def _update_cache_from_autocomplete(
|
||||
self, ctx: AutocompleteContext, rolegivers: list[Rolegiver]
|
||||
) -> None:
|
||||
async def _update_cache_from_autocomplete(self, ctx: AutocompleteContext, rolegivers: list[Rolegiver]) -> None:
|
||||
"""Update the cache with a lock to prevent multiple simultaneous updates"""
|
||||
lock = self._cache_update_locks.get(ctx.guild.id)
|
||||
if not lock:
|
||||
|
@ -595,9 +536,7 @@ class RolegiverCog(Extension):
|
|||
continue
|
||||
roles.append(role)
|
||||
await r.save()
|
||||
self._group_cache[ctx.guild.id][r.group] = {
|
||||
role.name: role.role.id for role in roles
|
||||
}
|
||||
self._group_cache[ctx.guild.id][r.group] = {role.name: role.role.id for role in roles}
|
||||
|
||||
|
||||
def setup(bot: Client) -> None:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS Tags Cog."""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
|
@ -26,7 +27,7 @@ from thefuzz import process
|
|||
from jarvis.utils import build_embed
|
||||
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
tag_name = re.compile(r"^[\w\ \-]{1,40}$")
|
||||
|
@ -82,17 +83,13 @@ class TagCog(Extension):
|
|||
|
||||
await ctx.send_modal(modal)
|
||||
try:
|
||||
response = await self.bot.wait_for_modal(
|
||||
modal, author=ctx.author.id, timeout=60 * 5
|
||||
)
|
||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
||||
name = response.responses.get("name").replace("`", "")
|
||||
content = response.responses.get("content")
|
||||
except asyncio.TimeoutError:
|
||||
return
|
||||
|
||||
noinvite = await Setting.find_one(
|
||||
Setting.guild == ctx.guild.id, Setting.setting == "noinvite"
|
||||
)
|
||||
noinvite = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "noinvite")
|
||||
|
||||
if (
|
||||
(invites.search(content) or invites.search(name))
|
||||
|
@ -102,17 +99,13 @@ class TagCog(Extension):
|
|||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||
)
|
||||
):
|
||||
await response.send(
|
||||
"Listen, don't use this to try and bypass the rules", ephemeral=True
|
||||
)
|
||||
await response.send("Listen, don't use this to try and bypass the rules", ephemeral=True)
|
||||
return
|
||||
elif not content.strip() or not name.strip():
|
||||
if not content.strip() or not name.strip():
|
||||
await response.send("Content and name required", ephemeral=True)
|
||||
return
|
||||
elif not tag_name.match(name):
|
||||
await response.send(
|
||||
"Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True
|
||||
)
|
||||
if not tag_name.match(name):
|
||||
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
|
||||
return
|
||||
|
||||
tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
|
||||
|
@ -144,9 +137,7 @@ class TagCog(Extension):
|
|||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
|
||||
await response.send(embeds=embed, components=components)
|
||||
if ctx.guild.id not in self.cache:
|
||||
|
@ -167,13 +158,11 @@ class TagCog(Extension):
|
|||
if not tag:
|
||||
await ctx.send("Tag not found", ephemeral=True)
|
||||
return
|
||||
elif tag.creator != ctx.author.id and not (
|
||||
if tag.creator != ctx.author.id and not (
|
||||
ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
||||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||
):
|
||||
await ctx.send(
|
||||
"You didn't create this tag, ask the creator to edit it", ephemeral=True
|
||||
)
|
||||
await ctx.send("You didn't create this tag, ask the creator to edit it", ephemeral=True)
|
||||
return
|
||||
|
||||
modal = Modal(
|
||||
|
@ -198,9 +187,7 @@ class TagCog(Extension):
|
|||
|
||||
await ctx.send_modal(modal)
|
||||
try:
|
||||
response = await self.bot.wait_for_modal(
|
||||
modal, author=ctx.author.id, timeout=60 * 5
|
||||
)
|
||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
||||
name = response.responses.get("name").replace("`", "")
|
||||
content = response.responses.get("content")
|
||||
except asyncio.TimeoutError:
|
||||
|
@ -214,9 +201,7 @@ class TagCog(Extension):
|
|||
)
|
||||
return
|
||||
|
||||
noinvite = await Setting.find_one(
|
||||
Setting.guild == ctx.guild.id, Setting.setting == "noinvite"
|
||||
)
|
||||
noinvite = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "noinvite")
|
||||
|
||||
if (
|
||||
(invites.search(content) or invites.search(name))
|
||||
|
@ -226,17 +211,13 @@ class TagCog(Extension):
|
|||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||
)
|
||||
):
|
||||
await response.send(
|
||||
"Listen, don't use this to try and bypass the rules", ephemeral=True
|
||||
)
|
||||
await response.send("Listen, don't use this to try and bypass the rules", ephemeral=True)
|
||||
return
|
||||
elif not content.strip() or not name.strip():
|
||||
if not content.strip() or not name.strip():
|
||||
await response.send("Content and name required", ephemeral=True)
|
||||
return
|
||||
elif not tag_name.match(name):
|
||||
await response.send(
|
||||
"Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True
|
||||
)
|
||||
if not tag_name.match(name):
|
||||
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
|
||||
return
|
||||
|
||||
tag.content = re.sub(r"\\?([@<])", r"\\\g<1>", content)
|
||||
|
@ -259,9 +240,7 @@ class TagCog(Extension):
|
|||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await response.send(embeds=embed, components=components)
|
||||
if tag.name not in self.cache[ctx.guild.id]:
|
||||
self.cache[ctx.guild.id].remove(old_name)
|
||||
|
@ -280,7 +259,7 @@ class TagCog(Extension):
|
|||
if not tag:
|
||||
await ctx.send("Tag not found", ephemeral=True)
|
||||
return
|
||||
elif tag.creator != ctx.author.id and not (
|
||||
if tag.creator != ctx.author.id and not (
|
||||
ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
||||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||
):
|
||||
|
@ -341,9 +320,7 @@ class TagCog(Extension):
|
|||
name=f"{username}#{discrim}" if username else "Unknown User",
|
||||
icon_url=url,
|
||||
)
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@tag.subcommand(sub_cmd_name="list", sub_cmd_description="List tag names")
|
||||
|
@ -351,9 +328,7 @@ class TagCog(Extension):
|
|||
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
|
||||
names = "\n".join(f"`{t.name}`" for t in tags)
|
||||
embed = build_embed(title="All Tags", description=names, fields=[])
|
||||
components = Button(
|
||||
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||
)
|
||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
||||
await ctx.send(embeds=embed, components=components)
|
||||
|
||||
@_get.autocomplete("name")
|
||||
|
@ -364,9 +339,7 @@ class TagCog(Extension):
|
|||
if not self.cache.get(ctx.guild.id):
|
||||
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
|
||||
self.cache[ctx.guild.id] = [tag.name for tag in tags]
|
||||
results = process.extract(
|
||||
ctx.input_text, self.cache.get(ctx.guild.id), limit=25
|
||||
)
|
||||
results = process.extract(ctx.input_text, self.cache.get(ctx.guild.id), limit=25)
|
||||
choices = [{"name": r[0], "value": r[0]} for r in results]
|
||||
await ctx.send(choices=choices)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS guild-specific cogs"""
|
||||
|
||||
from interactions import Client
|
||||
|
||||
from jarvis.cogs.unique import ctc2, dbrand, gl
|
||||
|
|
|
@ -5,12 +5,13 @@ This cog is now abandoned due to conflict with the dbrand moderators.
|
|||
|
||||
Please do not file feature requests related to this cog; they will be closed.
|
||||
|
||||
This cog has been abandoned by @zevaryx and any support must come through PRs.
|
||||
This cog has been abandoned by @zevaryx and any support must come through PRs.
|
||||
|
||||
No longer being a part of the community and hearing about *certain users* talking about me behind my back
|
||||
that request said features, means that I am no longer putting any energy into this. If you want to keep
|
||||
it running, make a PR to fix things.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
@ -35,7 +36,7 @@ guild_ids = [] # [578757004059738142, 520021794380447745, 862402786116763668]
|
|||
|
||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
@ -52,9 +53,7 @@ class CTCCog(Extension):
|
|||
def __del__(self):
|
||||
self._session.close()
|
||||
|
||||
ctc2 = SlashCommand(
|
||||
name="ctc2", description="CTC2 related commands", scopes=guild_ids
|
||||
)
|
||||
ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopes=guild_ids)
|
||||
|
||||
@ctc2.subcommand(sub_cmd_name="about")
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
|
@ -87,23 +86,17 @@ class CTCCog(Extension):
|
|||
async def _pw(self, ctx: InteractionContext, guess: str) -> None:
|
||||
if len(guess) > 800:
|
||||
await ctx.send(
|
||||
(
|
||||
"Listen here, dipshit. Don't be like <@256110768724901889>. "
|
||||
"Make your guesses < 800 characters."
|
||||
),
|
||||
("Listen here, dipshit. Don't be like <@256110768724901889>. " "Make your guesses < 800 characters."),
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
elif not valid.fullmatch(guess):
|
||||
if not valid.fullmatch(guess):
|
||||
await ctx.send(
|
||||
(
|
||||
"Listen here, dipshit. Don't be like <@256110768724901889>. "
|
||||
"Make your guesses *readable*."
|
||||
),
|
||||
("Listen here, dipshit. Don't be like <@256110768724901889>. " "Make your guesses *readable*."),
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
elif invites.search(guess):
|
||||
if invites.search(guess):
|
||||
await ctx.send(
|
||||
"Listen here, dipshit. No using this to bypass sending invite links.",
|
||||
ephemeral=True,
|
||||
|
|
|
@ -5,7 +5,7 @@ This cog is now abandoned due to conflict with the dbrand moderators.
|
|||
|
||||
Please do not file feature requests related to this cog; they will be closed.
|
||||
|
||||
This cog has been abandoned by @zevaryx and any support must come through PRs.
|
||||
This cog has been abandoned by @zevaryx and any support must come through PRs.
|
||||
|
||||
No longer being a part of the community and hearing about *certain users* talking about me behind my back
|
||||
that request said features, means that I am no longer putting any energy into this. If you want to keep
|
||||
|
@ -22,16 +22,13 @@ from interactions import Client, Extension, InteractionContext
|
|||
from interactions.client.utils import find
|
||||
from interactions.models.discord.embed import EmbedField
|
||||
from interactions.models.internal.application_commands import (
|
||||
OptionType,
|
||||
SlashCommand,
|
||||
slash_option,
|
||||
)
|
||||
from interactions.models.internal.command import cooldown
|
||||
from interactions.models.internal.cooldowns import Buckets
|
||||
from thefuzz import process
|
||||
|
||||
from jarvis.branding import CUSTOM_EMOJIS
|
||||
from jarvis.config import load_config
|
||||
from jarvis.data.dbrand import shipping_lookup
|
||||
from jarvis.utils import build_embed
|
||||
|
||||
|
@ -59,9 +56,7 @@ async def parse_db_status() -> dict:
|
|||
cells = row.find_all("td")
|
||||
for cell in cells:
|
||||
if "column--comment" in cell["class"]:
|
||||
status_title, detail, *_ = [
|
||||
x.get_text() for x in cell.find_all("div")
|
||||
]
|
||||
status_title, detail, *_ = [x.get_text() for x in cell.find_all("div")]
|
||||
text = status_title
|
||||
if cell != "Unavailable":
|
||||
text += ": " + detail
|
||||
|
@ -79,9 +74,7 @@ async def parse_db_status() -> dict:
|
|||
else:
|
||||
cell = cell.get_text().strip()
|
||||
row_data.append(cell)
|
||||
data[data_key].append(
|
||||
{headers[idx]: value for idx, value in enumerate(row_data)}
|
||||
)
|
||||
data[data_key].append({headers[idx]: value for idx, value in enumerate(row_data)})
|
||||
return data
|
||||
|
||||
|
||||
|
@ -106,24 +99,17 @@ class DbrandCog(Extension):
|
|||
|
||||
db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids)
|
||||
|
||||
@db.subcommand(
|
||||
sub_cmd_name="status", sub_cmd_description="Get dbrand operational status"
|
||||
)
|
||||
@db.subcommand(sub_cmd_name="status", sub_cmd_description="Get dbrand operational status")
|
||||
async def _status(self, ctx: InteractionContext) -> None:
|
||||
try:
|
||||
status = self.cache.get("status")
|
||||
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
|
||||
status = await parse_db_status()
|
||||
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
|
||||
hours=2
|
||||
)
|
||||
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=2)
|
||||
self.cache["status"] = status
|
||||
status = status.get("operations")
|
||||
emojies = [x["Status"] for x in status]
|
||||
fields = [
|
||||
EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"])
|
||||
for x in status
|
||||
]
|
||||
fields = [EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"]) for x in status]
|
||||
color = "#FBBD1E"
|
||||
if all("green" in x for x in emojies):
|
||||
color = "#38F657"
|
||||
|
@ -144,7 +130,7 @@ class DbrandCog(Extension):
|
|||
)
|
||||
|
||||
await ctx.send(embeds=embed)
|
||||
except:
|
||||
except Exception:
|
||||
self.bot.logger.error("Encountered error", exc_info=True)
|
||||
|
||||
async def _db_support_cmd(self, ctx: InteractionContext) -> None:
|
||||
|
@ -245,7 +231,7 @@ class DbrandCog(Extension):
|
|||
# required=True,
|
||||
# )
|
||||
# @cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _shipping(self, ctx: InteractionContext, search: str) -> None:
|
||||
async def _shipping(self, ctx: InteractionContext, search: str) -> None: # noqa: C901
|
||||
if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
|
||||
if re.match(
|
||||
r"^[\U0001f1e6-\U0001f1ff]{2}$",
|
||||
|
@ -254,15 +240,11 @@ class DbrandCog(Extension):
|
|||
):
|
||||
# Magic number, subtract from flag char to get ascii char
|
||||
uni2ascii = 127365
|
||||
search = chr(ord(search[0]) - uni2ascii) + chr(
|
||||
ord(search[1]) - uni2ascii
|
||||
)
|
||||
search = chr(ord(search[0]) - uni2ascii) + chr(ord(search[1]) - uni2ascii)
|
||||
elif search == "🏳️":
|
||||
search = "fr"
|
||||
else:
|
||||
await ctx.send(
|
||||
"Please use text to search for shipping.", ephemeral=True
|
||||
)
|
||||
await ctx.send("Please use text to search for shipping.", ephemeral=True)
|
||||
return
|
||||
if len(search) > 3:
|
||||
countries = {x["country"]: x["alpha-2"] for x in shipping_lookup}
|
||||
|
@ -287,9 +269,7 @@ class DbrandCog(Extension):
|
|||
data = await self._session.get(api_link)
|
||||
if 200 <= data.status < 400:
|
||||
data = await data.json()
|
||||
data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
|
||||
hours=24
|
||||
)
|
||||
data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||
self.cache[dest] = data
|
||||
else:
|
||||
data = None
|
||||
|
@ -298,18 +278,12 @@ class DbrandCog(Extension):
|
|||
fields = []
|
||||
for service in data["shipping_services_available"]:
|
||||
service_data = self.cache.get(f"{dest}-{service}")
|
||||
if not service_data or service_data["cache_expiry"] < datetime.now(
|
||||
tz=timezone.utc
|
||||
):
|
||||
service_data = await self._session.get(
|
||||
self.api_url + dest + "/" + service["url"]
|
||||
)
|
||||
if not service_data or service_data["cache_expiry"] < datetime.now(tz=timezone.utc):
|
||||
service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
|
||||
if service_data.status > 400:
|
||||
continue
|
||||
service_data = await service_data.json()
|
||||
service_data["cache_expiry"] = datetime.now(
|
||||
tz=timezone.utc
|
||||
) + timedelta(hours=24)
|
||||
service_data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||
self.cache[f"{dest}-{service}"] = service_data
|
||||
title = f'{service_data["carrier"]} {service_data["tier-title"]} | {service_data["costs-min"]}'
|
||||
message = service_data["time-title"]
|
||||
|
@ -320,9 +294,7 @@ class DbrandCog(Extension):
|
|||
status = self.cache.get("status")
|
||||
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
|
||||
status = await parse_db_status()
|
||||
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
|
||||
hours=2
|
||||
)
|
||||
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=2)
|
||||
self.cache["status"] = status
|
||||
status = status["countries"]
|
||||
|
||||
|
@ -335,10 +307,10 @@ class DbrandCog(Extension):
|
|||
description = ""
|
||||
color = "#FFBB00"
|
||||
if shipping_info:
|
||||
description = f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}'
|
||||
created = self.cache.get("status").get("cache_expiry") - timedelta(
|
||||
hours=2
|
||||
description = (
|
||||
f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}'
|
||||
)
|
||||
created = self.cache.get("status").get("cache_expiry") - timedelta(hours=2)
|
||||
ts = int(created.timestamp())
|
||||
description += f" \u200b | \u200b Last updated: <t:{ts}:R>\n\u200b"
|
||||
if "green" in shipping_info["Status"]:
|
||||
|
@ -367,8 +339,7 @@ class DbrandCog(Extension):
|
|||
embed = build_embed(
|
||||
title="Check Shipping Times",
|
||||
description=(
|
||||
"Country not found.\nYou can [view all shipping "
|
||||
"destinations here](https://dbrand.com/shipping)"
|
||||
"Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)"
|
||||
),
|
||||
fields=[],
|
||||
url="https://dbrand.com/shipping",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS GitLab Cog."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
|
|
@ -51,13 +51,9 @@ class Config(BaseSettings, case_sensitive=False):
|
|||
log_level: str = "INFO"
|
||||
jurigged: bool = False
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env", env_file_encoding="utf-8", env_nested_delimiter="."
|
||||
)
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", env_nested_delimiter=".")
|
||||
|
||||
|
||||
def load_config() -> Config:
|
||||
"""
|
||||
Load the config using the specified method first
|
||||
"""
|
||||
"""Load the config."""
|
||||
return Config()
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
"""JARVIS constants."""
|
||||
|
||||
from importlib.metadata import version as _v
|
||||
|
||||
__version__ = "2.5.4"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""dbrand-specific data."""
|
||||
|
||||
shipping_lookup = [
|
||||
{"country": "Afghanistan", "alpha-2": "AF", "alpha-3": "AFG", "numeric": "0004"},
|
||||
{"country": "Aland Islands", "alpha-2": "AX", "alpha-3": "ALA", "numeric": "0248"},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Pigpen lookup."""
|
||||
|
||||
lookup = {
|
||||
"a": "⌟",
|
||||
"b": "⊔",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Robot Camo emote lookups."""
|
||||
|
||||
emotes = {
|
||||
"A": 852317928572715038,
|
||||
"B": 852317954975727679,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Unicode emoji data."""
|
||||
|
||||
import json
|
||||
from os import getcwd
|
||||
from os import sep as s
|
||||
|
|
|
@ -3,39 +3,41 @@ Unit Converter utilities.
|
|||
|
||||
Rates based on https://github.com/microsoft/calculator
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
@dataclass
|
||||
class Converter:
|
||||
BASE: str = "sample"
|
||||
CONVERSIONS: dict[str, int] = field(default_factory=dict)
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def get_rate(cls, from_: str, to: str) -> int:
|
||||
def get_rate(cls, from_: str, to: str) -> float:
|
||||
"""
|
||||
Get the conversion rate
|
||||
|
||||
Args:
|
||||
from_: Convert from this
|
||||
to: Convert to this
|
||||
|
||||
"""
|
||||
if from_ == cls.BASE:
|
||||
return cls.CONVERSIONS.get(to, None)
|
||||
else:
|
||||
from_rate = cls.CONVERSIONS.get(from_)
|
||||
to_rate = cls.CONVERSIONS.get(to)
|
||||
return (1 / from_rate) * to_rate
|
||||
from_rate = cls.CONVERSIONS.get(from_)
|
||||
to_rate = cls.CONVERSIONS.get(to)
|
||||
return (1 / from_rate) * to_rate
|
||||
|
||||
|
||||
class Angle(Converter):
|
||||
BASE = "degree"
|
||||
CONVERSIONS = {"degree": 1, "radian": 57.29577951308233, "gradian": 0.9}
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {"degree": 1, "radian": 57.29577951308233, "gradian": 0.9}
|
||||
|
||||
|
||||
class Area(Converter):
|
||||
BASE = "square meter"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"acre": 4046.8564224,
|
||||
"square meter": 1,
|
||||
"square foot": 0.09290304,
|
||||
|
@ -56,7 +58,7 @@ class Area(Converter):
|
|||
|
||||
class Data(Converter):
|
||||
BASE = "megabyte"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"bit": 0.000000125,
|
||||
"byte": 0.000001,
|
||||
"kilobyte": 0.001,
|
||||
|
@ -99,7 +101,7 @@ class Data(Converter):
|
|||
|
||||
class Energy(Converter):
|
||||
BASE = "joule"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"calorie": 4.184,
|
||||
"kilocalorie": 4184,
|
||||
"british thermal unit": 1055.056,
|
||||
|
@ -115,7 +117,7 @@ class Energy(Converter):
|
|||
|
||||
class Length(Converter):
|
||||
BASE = "meter"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"inch": 0.0254,
|
||||
"foot": 0.3048,
|
||||
"yard": 0.9144,
|
||||
|
@ -135,7 +137,7 @@ class Length(Converter):
|
|||
|
||||
class Power(Converter):
|
||||
BASE = "watt"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"british thermal unit per minute": 17.58426666666667,
|
||||
"foot pound per minute": 0.0225969658055233,
|
||||
"watt": 1,
|
||||
|
@ -149,7 +151,7 @@ class Power(Converter):
|
|||
|
||||
class Pressure(Converter):
|
||||
BASE = "atmosphere"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"atmosphere": 1,
|
||||
"bar": 0.9869232667160128,
|
||||
"kilopascal": 0.0098692326671601,
|
||||
|
@ -161,7 +163,7 @@ class Pressure(Converter):
|
|||
|
||||
class Speed(Converter):
|
||||
BASE = "centimeters per second"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"centimeters per second": 1,
|
||||
"feet per second": 30.48,
|
||||
"kilometers per hour": 27.777777777777777777778,
|
||||
|
@ -177,7 +179,7 @@ class Speed(Converter):
|
|||
|
||||
class Time(Converter):
|
||||
BASE = "second"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"day": 86400,
|
||||
"second": 1,
|
||||
"week": 604800,
|
||||
|
@ -190,7 +192,7 @@ class Time(Converter):
|
|||
|
||||
class Volume(Converter):
|
||||
BASE = "millileter"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"cup (US)": 236.588237,
|
||||
"pint (US)": 473.176473,
|
||||
"pint (UK)": 568.26125,
|
||||
|
@ -219,7 +221,7 @@ class Volume(Converter):
|
|||
|
||||
class Weight(Converter):
|
||||
BASE = "kilogram"
|
||||
CONVERSIONS = {
|
||||
CONVERSIONS: ClassVar[dict[str, float]] = {
|
||||
"kilogram": 1,
|
||||
"hectogram": 0.1,
|
||||
"decagram": 0.01,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""JARVIS bot-specific embeds."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from interactions.models.discord.embed import Embed, EmbedField
|
||||
|
@ -29,6 +30,7 @@ def ban_embed(
|
|||
guild: Guild to ban from
|
||||
duration: Optional temporary ban duration
|
||||
dm: If the embed should be a user embed
|
||||
|
||||
"""
|
||||
fields = [EmbedField(name="Reason", value=reason), EmbedField(name="Type", value=type)]
|
||||
if duration:
|
||||
|
@ -64,6 +66,7 @@ def unban_embed(user: User, admin: User, reason: str) -> Embed:
|
|||
user: User to unban
|
||||
admin: Admin to execute unban
|
||||
reason: Reason for unban
|
||||
|
||||
"""
|
||||
fields = (
|
||||
EmbedField(name="Reason", value=reason),
|
||||
|
@ -92,6 +95,7 @@ def kick_embed(user: Member, admin: Member, reason: str, guild: Guild, dm: bool
|
|||
reason: Reason for kick
|
||||
guild: Guild to kick from
|
||||
dm: If the embed should be a user embed
|
||||
|
||||
"""
|
||||
fields = [
|
||||
EmbedField(name="Reason", value="A valid reason"),
|
||||
|
@ -129,6 +133,7 @@ def mute_embed(user: Member, admin: Member, reason: str, guild: Guild) -> Embed:
|
|||
user: User to mute
|
||||
admin: Admin to execute mute
|
||||
reason: Reason for mute
|
||||
|
||||
"""
|
||||
until = int(user.communication_disabled_until.timestamp())
|
||||
fields = (
|
||||
|
@ -161,6 +166,7 @@ def unmute_embed(user: Member, admin: Member, reason: str, guild: Guild) -> Embe
|
|||
user: User to unmute
|
||||
admin: Admin to execute unmute
|
||||
reason: Reason for unmute
|
||||
|
||||
"""
|
||||
fields = (
|
||||
EmbedField(name="Reason", value=reason),
|
||||
|
@ -191,6 +197,7 @@ def warning_embed(user: Member, reason: str, admin: Member) -> Embed:
|
|||
user: User to warn
|
||||
reason: Warning reason
|
||||
admin: Admin who sent the warning
|
||||
|
||||
"""
|
||||
fields = (
|
||||
EmbedField(name="Reason", value=reason, inline=False),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from datetime import datetime, timezone
|
||||
from pkgutil import iter_modules
|
||||
from typing import Optional
|
||||
|
||||
import pytz
|
||||
from beanie.operators import Set
|
||||
|
@ -19,7 +20,7 @@ def build_embed(
|
|||
description: str,
|
||||
fields: list,
|
||||
color: str = PRIMARY_COLOR,
|
||||
timestamp: datetime = None,
|
||||
timestamp: Optional[datetime] = None,
|
||||
**kwargs: dict,
|
||||
) -> Embed:
|
||||
"""Embed builder utility function."""
|
||||
|
@ -42,8 +43,8 @@ def modlog_embed(
|
|||
admin: Member,
|
||||
title: str,
|
||||
desc: str,
|
||||
log: AuditLogEntry = None,
|
||||
reason: str = None,
|
||||
log: Optional[AuditLogEntry] = None,
|
||||
reason: Optional[str] = None,
|
||||
) -> Embed:
|
||||
"""Get modlog embed."""
|
||||
fields = [
|
||||
|
@ -76,9 +77,7 @@ def get_extensions(path: str) -> list:
|
|||
async def cleanup_user(bot: Client, user_id: int):
|
||||
for model in models.all_models:
|
||||
if hasattr(model, "admin"):
|
||||
await model.find(model.admin == user_id).update(
|
||||
Set({model.admin: bot.user.id})
|
||||
)
|
||||
await model.find(model.admin == user_id).update(Set({model.admin: bot.user.id}))
|
||||
if hasattr(model, "user"):
|
||||
await model.find(model.user == user_id).delete()
|
||||
if hasattr(model, "creator"):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Cog wrapper for command caching."""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
|
@ -30,12 +31,13 @@ class ModcaseCog(Extension):
|
|||
self.logger = logging.getLogger(__name__)
|
||||
self.add_extension_postrun(self.log)
|
||||
|
||||
async def log(self, ctx: InteractionContext, *_args: list, **kwargs: dict) -> None:
|
||||
async def log(self, ctx: InteractionContext, *_args: list, **kwargs: dict) -> None: # noqa: C901
|
||||
"""
|
||||
Log a moderation activity in a moderation case.
|
||||
|
||||
Args:
|
||||
ctx: Command context
|
||||
|
||||
"""
|
||||
name = self.__name__.replace("Cog", "")
|
||||
|
||||
|
@ -101,13 +103,9 @@ class ModcaseCog(Extension):
|
|||
)
|
||||
if name == "Mute":
|
||||
mts = int(user.communication_disabled_until.timestamp())
|
||||
embed.add_field(
|
||||
name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)"
|
||||
)
|
||||
embed.add_field(name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)")
|
||||
guild_url = f"https://discord.com/channels/{ctx.guild.id}"
|
||||
embed.set_author(
|
||||
name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url
|
||||
)
|
||||
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
try:
|
||||
await user.send(embeds=embed)
|
||||
|
@ -126,9 +124,7 @@ class ModcaseCog(Extension):
|
|||
await modlog.save()
|
||||
return
|
||||
|
||||
modlog = await Setting.find_one(
|
||||
Setting.guild == ctx.guild.id, Setting.setting == "modlog"
|
||||
)
|
||||
modlog = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "modlog")
|
||||
if not modlog:
|
||||
return
|
||||
|
||||
|
@ -148,31 +144,23 @@ class ModcaseCog(Extension):
|
|||
description=f"Admin action has been taken against {user.mention}",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=f"{user.username}", icon_url=user.display_avatar.url
|
||||
)
|
||||
embed.set_author(name=f"{user.username}", icon_url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"User ID: {user.id}")
|
||||
if name == "Mute":
|
||||
mts = int(user.communication_disabled_until.timestamp())
|
||||
embed.add_field(
|
||||
name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)"
|
||||
)
|
||||
embed.add_field(name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)")
|
||||
await channel.send(embeds=embed)
|
||||
|
||||
lookup_key = f"{user.id}|{ctx.guild.id}"
|
||||
|
||||
async with self.bot.redis.lock("lock|" + lookup_key):
|
||||
if await self.bot.redis.get(lookup_key):
|
||||
self.logger.debug(
|
||||
f"User {user.id} in {ctx.guild.id} already has pending case"
|
||||
)
|
||||
self.logger.debug(f"User {user.id} in {ctx.guild.id} already has pending case")
|
||||
return
|
||||
|
||||
channel = await ctx.guild.fetch_channel(modlog.value)
|
||||
if not channel:
|
||||
self.logger.warn(
|
||||
f"Guild {ctx.guild.id} modlog channel no longer exists, deleting"
|
||||
)
|
||||
self.logger.warn(f"Guild {ctx.guild.id} modlog channel no longer exists, deleting")
|
||||
await modlog.delete()
|
||||
return
|
||||
|
||||
|
@ -187,19 +175,11 @@ class ModcaseCog(Extension):
|
|||
embed.set_author(name=user.username, icon_url=avatar_url)
|
||||
components = [
|
||||
ActionRow(
|
||||
Button(
|
||||
style=ButtonStyle.RED, emoji="✖️", custom_id="modcase|no"
|
||||
),
|
||||
Button(
|
||||
style=ButtonStyle.GREEN, emoji="✔️", custom_id="modcase|yes"
|
||||
),
|
||||
Button(style=ButtonStyle.RED, emoji="✖️", custom_id="modcase|no"),
|
||||
Button(style=ButtonStyle.GREEN, emoji="✔️", custom_id="modcase|yes"),
|
||||
)
|
||||
]
|
||||
message = await channel.send(embeds=embed, components=components)
|
||||
|
||||
await self.bot.redis.set(
|
||||
lookup_key, f"{name.lower()}|{action.id}", ex=timedelta(days=7)
|
||||
)
|
||||
await self.bot.redis.set(
|
||||
f"msg|{message.id}", user.id, ex=timedelta(days=7)
|
||||
)
|
||||
await self.bot.redis.set(lookup_key, f"{name.lower()}|{action.id}", ex=timedelta(days=7))
|
||||
await self.bot.redis.set(f"msg|{message.id}", user.id, ex=timedelta(days=7))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Permissions wrappers."""
|
||||
|
||||
from interactions import InteractionContext, Permissions
|
||||
|
||||
|
||||
|
@ -6,7 +7,7 @@ def admin_or_permissions(*perms: list) -> bool:
|
|||
"""Check if a user is an admin or has other perms."""
|
||||
|
||||
async def predicate(ctx: InteractionContext) -> bool:
|
||||
"""Extended check predicate.""" # noqa: D401
|
||||
"""Extended check predicate."""
|
||||
is_admin = ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
||||
has_other = any(ctx.author.has_permission(perm) for perm in perms)
|
||||
return is_admin or has_other
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""JARVIS update handler."""
|
||||
import logging
|
||||
|
||||
from dataclasses import dataclass
|
||||
from importlib import import_module
|
||||
from inspect import getmembers, isclass
|
||||
|
|
|
@ -128,7 +128,8 @@ ignore = [
|
|||
# ... function
|
||||
"D104",
|
||||
# ... package
|
||||
|
||||
"E712",
|
||||
# Ignore == True because of Beanie
|
||||
# Plugins we don't currently include: flake8-return
|
||||
"RET503",
|
||||
# missing explicit return at the end of function ableto return non-None value.
|
||||
|
|
7
run.py
7
run.py
|
@ -1,13 +1,12 @@
|
|||
"""Main run file for J.A.R.V.I.S."""
|
||||
|
||||
import nest_asyncio
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
import asyncio
|
||||
import asyncio # noqa: E402
|
||||
|
||||
import nest_asyncio
|
||||
|
||||
from jarvis import run
|
||||
from jarvis import run # noqa: E402
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue