Merge Alpha 1 into dev
Closes #108, #111, #120, #121, #122 See merge request stark-industries/j.a.r.v.i.s.!49
This commit is contained in:
commit
3c26e3406a
50 changed files with 1597 additions and 2567 deletions
|
@ -26,7 +26,7 @@ repos:
|
|||
language_version: python3.10
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-isort
|
||||
rev: V5.10.1
|
||||
rev: v5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
|
@ -37,7 +37,7 @@ repos:
|
|||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-annotations~=2.0
|
||||
- flake8-bandit~=2.1
|
||||
#- flake8-bandit~=2.1
|
||||
- flake8-docstrings~=1.5
|
||||
- flake8-bugbear
|
||||
- flake8-comprehensions
|
||||
|
|
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
|||
“Commons Clause” License Condition v1.0
|
||||
|
||||
The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
|
||||
|
||||
Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.
|
||||
|
||||
For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
|
||||
|
||||
Software: JARVIS
|
||||
|
||||
License: Expat License
|
||||
|
||||
Licensor: zevaryx
|
55
PRIVACY.md
Normal file
55
PRIVACY.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Privacy Policy
|
||||
Your privacy is important to us. It is JARVIS' policy to respect your privacy and comply with any applicable law and regulation regarding any personal information we may collect about you through our JARVIS bot.
|
||||
|
||||
This policy is effective as of 20 March 2022 and was last updated on 10 March 2022.
|
||||
|
||||
## Information We Collect
|
||||
Information we collect includes both information you knowingly and actively provide us when using or participating in any of our services and promotions, and any information automatically sent by your devices in the course of accessing our products and services.
|
||||
|
||||
## Log Data
|
||||
When you use our JARVIS services, if opted in to usage data collection services, we may collect data in certain cicumstances:
|
||||
|
||||
- Administrative activity (i.e. ban, warn, mute)
|
||||
- Your User ID (either as admin or as the recipient of the activity)
|
||||
- Guild ID of activity origin
|
||||
- Your discriminator at time of activity (bans only)
|
||||
- Your username at time of activity (bans only)
|
||||
- Admin commands
|
||||
- User ID of admin who executes admin command
|
||||
- Reminders
|
||||
- Your User ID
|
||||
- The guild in which the command originated
|
||||
- The channel in which the command originated
|
||||
- Private text entered via the command
|
||||
- Starboard
|
||||
- Message ID of starred message
|
||||
- Channel ID of starred message
|
||||
- Guild ID of origin guild
|
||||
- Automated activity logging
|
||||
- We store no information about users who edit nicknames, join/leave servers, or other related activities. However, this information, if configured by server admins, is relayed into a Discord channel and is not automatically deleted, nor do we have control over this information.
|
||||
- This information is also stored by Discord via their Audit Log, which we also have no control over. Please contact Discord for their own privacy policy and asking about your rights on their platform.
|
||||
|
||||
## Use of Information
|
||||
We use the information we collect to provide, maintain, and improve our services. Common uses where this data may be used includes sending reminders, helping administrate Discord servers, and providing extra utilities into Discord based on user content.
|
||||
|
||||
## Security of Your Personal Information
|
||||
Although we will do our best to protect the personal information you provide to us, we advise that no method of electronic transmission or storage is 100% secure, and no one can guarantee absolute data security. We will comply with laws applicable to us in respect of any data breach.
|
||||
|
||||
## How Long We Keep Your Personal Information
|
||||
We keep your personal information only for as long as we need to. This time period may depend on what we are using your information for, in accordance with this privacy policy. If your personal information is no longer required, we will delete it or make it anonymous by removing all details that identify you.
|
||||
|
||||
## Your Rights and Controlling Your Personal Information
|
||||
You may request access to your personal information, and change what you are okay with us collecting from you. You may also request that we delete your personal identifying information. Please message **zevaryx#5779** on Discord, or join the Discord server at https://discord.gg/4TuFvW5n and ask in there.
|
||||
|
||||
## Limits of Our Policy
|
||||
Our website may link to external sites that are not operated by us (ie. discord.com). Please be aware that we have no control over the content and policies of those sites, and cannot accept responsibility or liability for their respective privacy practices.
|
||||
|
||||
## Changes to This Policy
|
||||
At our discretion, we may change our privacy policy to reflect updates to our business processes, current acceptable practices, or legislative or regulatory changes. If we decide to change this privacy policy, we will post the changes here at the same link by which you are accessing this privacy policy.
|
||||
|
||||
## Contact Us
|
||||
For any questions or concerns regarding your privacy, you may contact us using the following details:
|
||||
|
||||
### Discord
|
||||
#### zevaryx#5779
|
||||
#### https://discord.gg/4TuFvW5n
|
15
TERMS.md
Normal file
15
TERMS.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Terms Of Use
|
||||
Please just be reasonable do not try to mess with the bot in a malicious way. This includes but is not limited to:
|
||||
|
||||
- Spamming
|
||||
- Flooding
|
||||
- Hacking
|
||||
- DOS Attacks
|
||||
|
||||
## Contact Us
|
||||
For any questions or concerns regarding the terms, feel free to contact us:
|
||||
|
||||
### Discord
|
||||
|
||||
#### zevaryx#5779
|
||||
#### https://discord.gg/4TuFvW5n
|
|
@ -1,13 +1,18 @@
|
|||
"""Main J.A.R.V.I.S. package."""
|
||||
import logging
|
||||
from importlib.metadata import version as _v
|
||||
|
||||
from dis_snek import Intents, Snake, listen
|
||||
from mongoengine import connect
|
||||
from dis_snek import Intents
|
||||
from jarvis_core.db import connect
|
||||
|
||||
# from jarvis import logo # noqa: F401
|
||||
from jarvis import tasks, utils
|
||||
from jarvis import utils
|
||||
from jarvis.client import Jarvis
|
||||
from jarvis.config import get_config
|
||||
from jarvis.events import member, message
|
||||
|
||||
try:
|
||||
__version__ = _v("jarvis")
|
||||
except Exception:
|
||||
__version__ = "0.0.0"
|
||||
|
||||
jconfig = get_config()
|
||||
|
||||
|
@ -17,60 +22,20 @@ file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode
|
|||
file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s"))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
intents = Intents.DEFAULT
|
||||
intents.members = True
|
||||
intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES
|
||||
restart_ctx = None
|
||||
|
||||
|
||||
jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync)
|
||||
|
||||
__version__ = "2.0.0a0"
|
||||
jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync)
|
||||
|
||||
|
||||
@listen()
|
||||
async def on_ready() -> None:
|
||||
"""Lepton on_ready override."""
|
||||
global restart_ctx
|
||||
print(" Logged in as {0.user}".format(jarvis)) # noqa: T001
|
||||
print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001
|
||||
|
||||
|
||||
@listen()
|
||||
async def on_startup() -> None:
|
||||
"""Lepton on_startup override."""
|
||||
tasks.init()
|
||||
|
||||
|
||||
def run() -> None:
|
||||
async def run() -> None:
|
||||
"""Run J.A.R.V.I.S."""
|
||||
connect(
|
||||
db="ctc2",
|
||||
alias="ctc2",
|
||||
authentication_source="admin",
|
||||
**jconfig.mongo["connect"],
|
||||
)
|
||||
connect(
|
||||
db=jconfig.mongo["database"],
|
||||
alias="main",
|
||||
authentication_source="admin",
|
||||
**jconfig.mongo["connect"],
|
||||
)
|
||||
connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis")
|
||||
jconfig.get_db_config()
|
||||
|
||||
for extension in utils.get_extensions():
|
||||
jarvis.load_extension(extension)
|
||||
|
||||
print( # noqa: T001
|
||||
" https://discord.com/api/oauth2/authorize?client_id="
|
||||
"{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id)
|
||||
)
|
||||
|
||||
jarvis.max_messages = jconfig.max_messages
|
||||
|
||||
# Add event listeners
|
||||
if jconfig.events:
|
||||
_ = [
|
||||
member.MemberEventHandler(jarvis),
|
||||
message.MessageEventHandler(jarvis),
|
||||
]
|
||||
jarvis.start(jconfig.token)
|
||||
await jarvis.astart(jconfig.token)
|
||||
|
|
464
jarvis/client.py
Normal file
464
jarvis/client.py
Normal file
|
@ -0,0 +1,464 @@
|
|||
"""Custom JARVIS client."""
|
||||
import re
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from dis_snek import Snake, listen
|
||||
from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate
|
||||
from dis_snek.client.utils.misc_utils import find_all
|
||||
from dis_snek.models.discord.channel import DMChannel
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.enums import Permissions
|
||||
from dis_snek.models.discord.message import Message
|
||||
from dis_snek.models.discord.user import Member
|
||||
from dis_snek.models.snek.context import Context, InteractionContext
|
||||
from dis_snek.models.snek.tasks.task import Task
|
||||
from dis_snek.models.snek.tasks.triggers import IntervalTrigger
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning
|
||||
from jarvis_core.filters import invites, url
|
||||
from jarvis_core.util import build_embed
|
||||
from jarvis_core.util.ansi import RESET, Fore, Format, fmt
|
||||
from pastypy import AsyncPaste as Paste
|
||||
|
||||
from jarvis.utils.embeds import warning_embed
|
||||
|
||||
DEFAULT_GUILD = 862402786116763668
|
||||
DEFAULT_ERROR_CHANNEL = 943395824560394250
|
||||
DEFAULT_SITE = "https://paste.zevs.me"
|
||||
|
||||
ERROR_MSG = """
|
||||
Command Information:
|
||||
Name: {invoked_name}
|
||||
Args:
|
||||
{arg_str}
|
||||
|
||||
Callback:
|
||||
Args:
|
||||
{callback_args}
|
||||
Kwargs:
|
||||
{callback_kwargs}
|
||||
"""
|
||||
|
||||
KEY_FMT = fmt(Fore.GRAY)
|
||||
VAL_FMT = fmt(Fore.WHITE)
|
||||
CMD_FMT = fmt(Fore.GREEN, Format.BOLD)
|
||||
|
||||
|
||||
class Jarvis(Snake):
|
||||
def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003
|
||||
super().__init__(*args, **kwargs)
|
||||
self.phishing_domains = []
|
||||
|
||||
@Task.create(IntervalTrigger(days=1))
|
||||
async def _update_domains(self) -> None:
|
||||
async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session:
|
||||
response = await session.get("https://phish.sinking.yachts/v2/recent/86415")
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
|
||||
for update in data:
|
||||
if update["type"] == "add":
|
||||
if update["domain"] not in self.phishing_domains:
|
||||
self.phishing_domains.append(update["domain"])
|
||||
elif update["type"] == "delete":
|
||||
if update["domain"] in self.phishing_domains:
|
||||
self.phishing_domains.remove(update["domain"])
|
||||
|
||||
async def _sync_domains(self) -> None:
|
||||
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()
|
||||
|
||||
@listen()
|
||||
async def on_ready(self) -> None:
|
||||
"""Lepton on_ready override."""
|
||||
await self._sync_domains()
|
||||
self._update_domains.start()
|
||||
print("Logged in as {}".format(self.user)) # noqa: T001
|
||||
print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001
|
||||
print( # noqa: T001
|
||||
"https://discord.com/api/oauth2/authorize?client_id="
|
||||
"{}&permissions=8&scope=bot%20applications.commands".format(self.user.id)
|
||||
)
|
||||
|
||||
async def on_command_error(
|
||||
self, ctx: Context, error: Exception, *args: list, **kwargs: dict
|
||||
) -> None:
|
||||
"""Lepton on_command_error override."""
|
||||
guild = await self.fetch_guild(DEFAULT_GUILD)
|
||||
channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL)
|
||||
error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC")
|
||||
timestamp = int(datetime.now().timestamp())
|
||||
timestamp = f"<t:{timestamp}:T>"
|
||||
arg_str = (
|
||||
"\n".join(f" {k}: {v}" for k, v in ctx.kwargs.items()) if ctx.kwargs else " None"
|
||||
)
|
||||
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"
|
||||
)
|
||||
full_message = ERROR_MSG.format(
|
||||
error_time=error_time,
|
||||
invoked_name=ctx.invoked_name,
|
||||
arg_str=arg_str,
|
||||
callback_args=callback_args,
|
||||
callback_kwargs=callback_kwargs,
|
||||
)
|
||||
if len(full_message) >= 1900:
|
||||
error_message = " ".join(traceback.format_exception(error))
|
||||
full_message += "Exception: |\n " + error_message
|
||||
paste = Paste(content=full_message)
|
||||
await paste.save(DEFAULT_SITE)
|
||||
|
||||
await channel.send(
|
||||
f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord."
|
||||
f"\nPlease see log at {paste.url}"
|
||||
)
|
||||
else:
|
||||
error_message = "".join(traceback.format_exception(error))
|
||||
await channel.send(
|
||||
f"JARVIS encountered an error at {timestamp}:"
|
||||
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)
|
||||
return await super().on_command_error(ctx, error, *args, **kwargs)
|
||||
|
||||
# Modlog
|
||||
async def on_command(self, ctx: InteractionContext) -> None:
|
||||
"""Lepton on_command override."""
|
||||
if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]:
|
||||
modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog"))
|
||||
if modlog:
|
||||
channel = await ctx.guild.fetch_channel(modlog.value)
|
||||
args = " ".join(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}" for k, v in ctx.kwargs.items())
|
||||
fields = [
|
||||
EmbedField(
|
||||
name="Command",
|
||||
value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```",
|
||||
inline=False,
|
||||
),
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Command Invoked",
|
||||
description=f"{ctx.author.mention} invoked a command",
|
||||
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}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
||||
# Events
|
||||
async def on_member_join(self, user: Member) -> None:
|
||||
"""Handle on_member_join event."""
|
||||
guild = user.guild
|
||||
unverified = await Setting.find_one(q(guild=guild.id, setting="unverified"))
|
||||
if unverified:
|
||||
role = guild.get_role(unverified.value)
|
||||
if role not in user.roles:
|
||||
await user.add_role(role, reason="User just joined and is unverified")
|
||||
|
||||
async def autopurge(self, message: Message) -> None:
|
||||
"""Handle autopurge events."""
|
||||
autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id))
|
||||
if autopurge:
|
||||
await message.delete(delay=autopurge.delay)
|
||||
|
||||
async def autoreact(self, message: Message) -> None:
|
||||
"""Handle autoreact events."""
|
||||
autoreact = await Autoreact.find_one(
|
||||
q(
|
||||
guild=message.guild.id,
|
||||
channel=message.channel.id,
|
||||
)
|
||||
)
|
||||
if autoreact:
|
||||
for reaction in autoreact.reactions:
|
||||
await message.add_reaction(reaction)
|
||||
|
||||
async def checks(self, message: Message) -> None:
|
||||
"""Other message checks."""
|
||||
# #tech
|
||||
# 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 = re.sub(r"\s+", "", message.content)
|
||||
match = invites.search(content)
|
||||
setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite"))
|
||||
if not setting:
|
||||
setting = Setting(guild=message.guild.id, setting="noinvite", value=True)
|
||||
await setting.commit()
|
||||
if match:
|
||||
guild_invites = await message.guild.invites()
|
||||
guild_invites.append(message.guild.vanity_url_code)
|
||||
allowed = [x.code for x in guild_invites] + [
|
||||
"dbrand",
|
||||
"VtgZntXcnZ",
|
||||
"gPfYGbvTCE",
|
||||
]
|
||||
if match.group(1) not in allowed and setting.value:
|
||||
await message.delete()
|
||||
w = Warning(
|
||||
active=True,
|
||||
admin=self.user.id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Sent an invite link",
|
||||
user=message.author.id,
|
||||
)
|
||||
await w.commit()
|
||||
embed = warning_embed(message.author, "Sent an invite link")
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
async def massmention(self, message: Message) -> None:
|
||||
"""Handle massmention events."""
|
||||
massmention = await Setting.find_one(
|
||||
q(
|
||||
guild=message.guild.id,
|
||||
setting="massmention",
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
massmention
|
||||
and 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
|
||||
):
|
||||
w = Warning(
|
||||
active=True,
|
||||
admin=self.user.id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Mass Mention",
|
||||
user=message.author.id,
|
||||
)
|
||||
await w.commit()
|
||||
embed = warning_embed(message.author, "Mass Mention")
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
async def roleping(self, message: Message) -> None:
|
||||
"""Handle roleping events."""
|
||||
if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0:
|
||||
return
|
||||
rolepings = await Roleping.find(q(guild=message.guild.id, active=True)).to_list(None)
|
||||
|
||||
# Get all role IDs involved with message
|
||||
roles = []
|
||||
async for mention in message.mention_roles:
|
||||
roles.append(mention.id)
|
||||
async for mention in message.mention_users:
|
||||
for role in mention.roles:
|
||||
roles.append(role.id)
|
||||
|
||||
if not roles:
|
||||
return
|
||||
|
||||
# Get all roles that are rolepinged
|
||||
roleping_ids = [r.role for r in rolepings]
|
||||
|
||||
# Get roles in rolepings
|
||||
role_in_rolepings = find_all(lambda x: x in roleping_ids, roles)
|
||||
|
||||
# Check if the user has the role, so they are allowed to ping it
|
||||
user_missing_role = any(x.id not in roleping_ids for x in message.author.roles)
|
||||
|
||||
# Admins can ping whoever
|
||||
user_is_admin = message.author.has_permission(Permissions.ADMINISTRATOR)
|
||||
|
||||
# Check if user in a bypass list
|
||||
user_has_bypass = False
|
||||
for roleping in rolepings:
|
||||
if message.author.id in roleping.bypass["users"]:
|
||||
user_has_bypass = True
|
||||
break
|
||||
if any(role.id in roleping.bypass["roles"] for role in message.author.roles):
|
||||
user_has_bypass = True
|
||||
break
|
||||
|
||||
if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass:
|
||||
w = Warning(
|
||||
active=True,
|
||||
admin=self.user.id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Pinged a blocked role/user with a blocked role",
|
||||
user=message.author.id,
|
||||
)
|
||||
await w.commit()
|
||||
embed = warning_embed(message.author, "Pinged a blocked role/user with a blocked role")
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
async def phishing(self, message: Message) -> None:
|
||||
"""Check if the message contains any known phishing domains."""
|
||||
for match in url.finditer(message.content):
|
||||
if match.group("domain") in self.phishing_domains:
|
||||
w = Warning(
|
||||
active=True,
|
||||
admin=self.user.id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Phishing URL",
|
||||
user=message.author.id,
|
||||
)
|
||||
await w.commit()
|
||||
embed = warning_embed(message.author, "Phishing URL")
|
||||
await message.channel.send(embed=embed)
|
||||
await message.delete()
|
||||
return True
|
||||
return False
|
||||
|
||||
async def malicious_url(self, message: Message) -> None:
|
||||
"""Check if the message contains any known phishing domains."""
|
||||
for match in url.finditer(message.content):
|
||||
async with ClientSession() as session:
|
||||
resp = await session.get(
|
||||
"https://spoopy.oceanlord.me/api/check_website", json={"website": match.string}
|
||||
)
|
||||
if resp.status != 200:
|
||||
break
|
||||
data = await resp.json()
|
||||
for item in data["processed"]["urls"].values():
|
||||
if not item["safe"]:
|
||||
w = Warning(
|
||||
active=True,
|
||||
admin=self.user.id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Unsafe URL",
|
||||
user=message.author.id,
|
||||
)
|
||||
await w.commit()
|
||||
reasons = ", ".join(item["not_safe_reasons"])
|
||||
embed = warning_embed(message.author, reasons)
|
||||
await message.channel.send(embed=embed)
|
||||
await message.delete()
|
||||
return True
|
||||
return False
|
||||
|
||||
@listen()
|
||||
async def on_message(self, event: MessageCreate) -> None:
|
||||
"""Handle on_message event. Calls other event handlers."""
|
||||
message = event.message
|
||||
if not isinstance(message.channel, DMChannel) and not message.author.bot:
|
||||
await self.autoreact(message)
|
||||
await self.massmention(message)
|
||||
await self.roleping(message)
|
||||
await self.autopurge(message)
|
||||
await self.checks(message)
|
||||
if not await self.phishing(message):
|
||||
await self.malicious_url(message)
|
||||
|
||||
@listen()
|
||||
async def on_message_edit(self, event: MessageUpdate) -> None:
|
||||
"""Process on_message_edit events."""
|
||||
before = event.before
|
||||
after = event.after
|
||||
if not after.author.bot:
|
||||
modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog"))
|
||||
if modlog:
|
||||
if not before or before.content == after.content or before.content is None:
|
||||
return
|
||||
channel = before.guild.get_channel(modlog.value)
|
||||
fields = [
|
||||
EmbedField(
|
||||
"Original Message",
|
||||
before.content if before.content else "N/A",
|
||||
False,
|
||||
),
|
||||
EmbedField(
|
||||
"New Message",
|
||||
after.content if after.content else "N/A",
|
||||
False,
|
||||
),
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Message Edited",
|
||||
description=f"{after.author.mention} edited a message",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
timestamp=after.edited_timestamp,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_author(
|
||||
name=after.author.username,
|
||||
icon_url=after.author.display_avatar.url,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{after.author.user.username}#{after.author.discriminator} | {after.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
if not isinstance(after.channel, DMChannel) and not after.author.bot:
|
||||
await self.massmention(after)
|
||||
await self.roleping(after)
|
||||
await self.checks(after)
|
||||
await self.roleping(after)
|
||||
await self.checks(after)
|
||||
if not await self.phishing(after):
|
||||
await self.malicious_url(after)
|
||||
|
||||
@listen()
|
||||
async def on_message_delete(self, event: MessageDelete) -> None:
|
||||
"""Process on_message_delete events."""
|
||||
message = event.message
|
||||
modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog"))
|
||||
if modlog:
|
||||
fields = [EmbedField("Original Message", message.content or "N/A", False)]
|
||||
|
||||
if message.attachments:
|
||||
value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments])
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name="Attachments",
|
||||
value=value,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
||||
if message.sticker_items:
|
||||
value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items])
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name="Stickers",
|
||||
value=value,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
||||
if message.embeds:
|
||||
value = str(len(message.embeds)) + " embeds"
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name="Embeds",
|
||||
value=value,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
||||
channel = message.guild.get_channel(modlog.value)
|
||||
embed = build_embed(
|
||||
title="Message Deleted",
|
||||
description=f"{message.author.mention}'s message was deleted",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
|
||||
embed.set_author(
|
||||
name=message.author.username,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
url=message.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
|
@ -1,8 +1,8 @@
|
|||
"""J.A.R.V.I.S. BanCog."""
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek import InteractionContext, Permissions, Snake
|
||||
from dis_snek import InteractionContext, Permissions, Scale
|
||||
from dis_snek.client.utils.misc_utils import find, find_all
|
||||
from dis_snek.ext.paginators import Paginator
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.user import User
|
||||
|
@ -13,19 +13,16 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Ban, Unban
|
||||
|
||||
from jarvis.db.models import Ban, Unban
|
||||
from jarvis.utils import build_embed, find, find_all
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class BanCog(CacheCog):
|
||||
class BanCog(Scale):
|
||||
"""J.A.R.V.I.S. BanCog."""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
|
||||
async def discord_apply_ban(
|
||||
self,
|
||||
ctx: InteractionContext,
|
||||
|
@ -38,7 +35,7 @@ class BanCog(CacheCog):
|
|||
) -> None:
|
||||
"""Apply a Discord ban."""
|
||||
await ctx.guild.ban(user, reason=reason)
|
||||
_ = Ban(
|
||||
b = Ban(
|
||||
user=user.id,
|
||||
username=user.username,
|
||||
discrim=user.discriminator,
|
||||
|
@ -48,7 +45,8 @@ class BanCog(CacheCog):
|
|||
type=mtype,
|
||||
duration=duration,
|
||||
active=active,
|
||||
).save()
|
||||
)
|
||||
await b.commit()
|
||||
|
||||
embed = build_embed(
|
||||
title="User Banned",
|
||||
|
@ -68,14 +66,15 @@ class BanCog(CacheCog):
|
|||
async def discord_apply_unban(self, ctx: InteractionContext, user: User, reason: str) -> None:
|
||||
"""Apply a Discord unban."""
|
||||
await ctx.guild.unban(user, reason=reason)
|
||||
_ = Unban(
|
||||
u = Unban(
|
||||
user=user.id,
|
||||
username=user.username,
|
||||
discrim=user.discriminator,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
reason=reason,
|
||||
).save()
|
||||
)
|
||||
await u.commit()
|
||||
|
||||
embed = build_embed(
|
||||
title="User Unbanned",
|
||||
|
@ -242,7 +241,9 @@ class BanCog(CacheCog):
|
|||
# We take advantage of the previous checks to save CPU cycles
|
||||
if not discord_ban_info:
|
||||
if isinstance(user, int):
|
||||
database_ban_info = Ban.objects(guild=ctx.guild.id, user=user, active=True).first()
|
||||
database_ban_info = await Ban.find_one(
|
||||
q(guild=ctx.guild.id, user=user, active=True)
|
||||
)
|
||||
else:
|
||||
search = {
|
||||
"guild": ctx.guild.id,
|
||||
|
@ -251,15 +252,16 @@ class BanCog(CacheCog):
|
|||
}
|
||||
if discrim:
|
||||
search["discrim"] = discrim
|
||||
database_ban_info = Ban.objects(**search).first()
|
||||
database_ban_info = await Ban.find_one(q(**search))
|
||||
|
||||
if not discord_ban_info and not database_ban_info:
|
||||
await ctx.send(f"Unable to find user {orig_user}", ephemeral=True)
|
||||
|
||||
elif discord_ban_info:
|
||||
elif discord_ban_info and not database_ban_info:
|
||||
await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
|
||||
|
||||
else:
|
||||
discord_ban_info = find(lambda x: x.user.id == database_ban_info["id"], bans)
|
||||
discord_ban_info = find(lambda x: x.user.id == database_ban_info.id, bans)
|
||||
if discord_ban_info:
|
||||
await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
|
||||
else:
|
||||
|
@ -273,9 +275,7 @@ class BanCog(CacheCog):
|
|||
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.")
|
||||
|
||||
@slash_command(
|
||||
name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans"
|
||||
|
@ -300,26 +300,18 @@ class BanCog(CacheCog):
|
|||
choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)],
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _bans_list(self, ctx: InteractionContext, type: int = 0, active: int = 1) -> None:
|
||||
async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: int = 1) -> None:
|
||||
active = bool(active)
|
||||
exists = self.check_cache(ctx, type=type, active=active)
|
||||
if exists:
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
types = [0, "perm", "temp", "soft"]
|
||||
search = {"guild": ctx.guild.id}
|
||||
if active:
|
||||
search["active"] = True
|
||||
if type > 0:
|
||||
search["type"] = types[type]
|
||||
bans = Ban.objects(**search).order_by("-created_at")
|
||||
if btype > 0:
|
||||
search["type"] = types[btype]
|
||||
bans = Ban.find(search).sort([("created_at", -1)])
|
||||
db_bans = []
|
||||
fields = []
|
||||
for ban in bans:
|
||||
async for ban in bans:
|
||||
if not ban.username:
|
||||
user = await self.bot.fetch_user(ban.user)
|
||||
ban.username = user.username if user else "[deleted user]"
|
||||
|
@ -355,9 +347,9 @@ class BanCog(CacheCog):
|
|||
|
||||
pages = []
|
||||
title = "Active " if active else "Inactive "
|
||||
if type > 0:
|
||||
title += types[type]
|
||||
if type == 1:
|
||||
if btype > 0:
|
||||
title += types[btype]
|
||||
if btype == 1:
|
||||
title += "a"
|
||||
title += "bans"
|
||||
if len(fields) == 0:
|
||||
|
@ -376,14 +368,4 @@ class BanCog(CacheCog):
|
|||
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"guild": ctx.guild.id,
|
||||
"user": ctx.author.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"type": type,
|
||||
"active": active,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.send(ctx)
|
||||
|
|
|
@ -8,8 +8,8 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db.models import Kick
|
||||
|
||||
from jarvis.db.models import Kick
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
@ -33,6 +33,7 @@ class KickCog(Scale):
|
|||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||
return
|
||||
|
||||
guild_name = ctx.guild.name
|
||||
embed = build_embed(
|
||||
title=f"You have been kicked from {guild_name}",
|
||||
|
@ -64,10 +65,11 @@ class KickCog(Scale):
|
|||
embed.set_thumbnail(url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
_ = Kick(
|
||||
k = Kick(
|
||||
user=user.id,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
).save()
|
||||
)
|
||||
await k.commit()
|
||||
await ctx.send(embed=embed)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""J.A.R.V.I.S. LockCog."""
|
||||
from dis_snek import Scale
|
||||
|
||||
# TODO: Uncomment 99% of code once implementation is figured out
|
||||
# from dis_snek import Scale
|
||||
#
|
||||
# # TODO: Uncomment 99% of code once implementation is figured out
|
||||
# from contextlib import suppress
|
||||
# from typing import Union
|
||||
#
|
||||
|
@ -20,94 +20,94 @@ from dis_snek import Scale
|
|||
#
|
||||
# from jarvis.db.models import Lock
|
||||
# from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class LockCog(Scale):
|
||||
"""J.A.R.V.I.S. LockCog."""
|
||||
|
||||
# @slash_command(name="lock", description="Lock a channel")
|
||||
# @slash_option(name="reason",
|
||||
# description="Lock Reason",
|
||||
# opt_type=3,
|
||||
# required=True,)
|
||||
# @slash_option(name="duration",
|
||||
# description="Lock duration in minutes (default 10)",
|
||||
# opt_type=4,
|
||||
# required=False,)
|
||||
# @slash_option(name="channel",
|
||||
# description="Channel to lock",
|
||||
# opt_type=7,
|
||||
# required=False,)
|
||||
# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS))
|
||||
# async def _lock(
|
||||
# self,
|
||||
# ctx: InteractionContext,
|
||||
# reason: str,
|
||||
# duration: int = 10,
|
||||
# channel: Union[GuildText, GuildVoice] = None,
|
||||
# ) -> None:
|
||||
# await ctx.defer(ephemeral=True)
|
||||
# if duration <= 0:
|
||||
# await ctx.send("Duration must be > 0", ephemeral=True)
|
||||
# return
|
||||
#
|
||||
# elif duration > 60 * 12:
|
||||
# await ctx.send("Duration must be <= 12 hours", ephemeral=True)
|
||||
# return
|
||||
#
|
||||
# if len(reason) > 100:
|
||||
# await ctx.send("Reason must be <= 100 characters", ephemeral=True)
|
||||
# return
|
||||
# if not channel:
|
||||
# channel = ctx.channel
|
||||
#
|
||||
# # role = ctx.guild.default_role # Uncomment once implemented
|
||||
# if isinstance(channel, GuildText):
|
||||
# to_deny = Permissions.SEND_MESSAGES
|
||||
# elif isinstance(channel, GuildVoice):
|
||||
# to_deny = Permissions.CONNECT | Permissions.SPEAK
|
||||
#
|
||||
# overwrite = PermissionOverwrite(type=PermissionTypes.ROLE, deny=to_deny)
|
||||
# # TODO: Get original permissions
|
||||
# # TODO: Apply overwrite
|
||||
# overwrite = overwrite
|
||||
# _ = Lock(
|
||||
# channel=channel.id,
|
||||
# guild=ctx.guild.id,
|
||||
# admin=ctx.author.id,
|
||||
# reason=reason,
|
||||
# duration=duration,
|
||||
# ) # .save() # Uncomment once implemented
|
||||
# # await ctx.send(f"{channel.mention} locked for {duration} minute(s)")
|
||||
# await ctx.send("Unfortunately, this is not yet implemented", hidden=True)
|
||||
#
|
||||
# @cog_ext.cog_slash(
|
||||
# name="unlock",
|
||||
# description="Unlocks a channel",
|
||||
# choices=[
|
||||
# create_option(
|
||||
# name="channel",
|
||||
# description="Channel to lock",
|
||||
# opt_type=7,
|
||||
# required=False,
|
||||
# ),
|
||||
# ],
|
||||
# )
|
||||
# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS))
|
||||
# async def _unlock(
|
||||
# self,
|
||||
# ctx: InteractionContext,
|
||||
# channel: Union[GuildText, GuildVoice] = None,
|
||||
# ) -> None:
|
||||
# if not channel:
|
||||
# channel = ctx.channel
|
||||
# lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first()
|
||||
# if not lock:
|
||||
# await ctx.send(f"{channel.mention} not locked.", ephemeral=True)
|
||||
# return
|
||||
# for role in ctx.guild.roles:
|
||||
# with suppress(Exception):
|
||||
# await self._unlock_channel(channel, role, ctx.author)
|
||||
# lock.active = False
|
||||
# lock.save()
|
||||
# await ctx.send(f"{channel.mention} unlocked")
|
||||
#
|
||||
#
|
||||
# class LockCog(Scale):
|
||||
# """J.A.R.V.I.S. LockCog."""
|
||||
#
|
||||
# @slash_command(name="lock", description="Lock a channel")
|
||||
# @slash_option(name="reason",
|
||||
# description="Lock Reason",
|
||||
# opt_type=3,
|
||||
# required=True,)
|
||||
# @slash_option(name="duration",
|
||||
# description="Lock duration in minutes (default 10)",
|
||||
# opt_type=4,
|
||||
# required=False,)
|
||||
# @slash_option(name="channel",
|
||||
# description="Channel to lock",
|
||||
# opt_type=7,
|
||||
# required=False,)
|
||||
# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS))
|
||||
# async def _lock(
|
||||
# self,
|
||||
# ctx: InteractionContext,
|
||||
# reason: str,
|
||||
# duration: int = 10,
|
||||
# channel: Union[GuildText, GuildVoice] = None,
|
||||
# ) -> None:
|
||||
# await ctx.defer(ephemeral=True)
|
||||
# if duration <= 0:
|
||||
# await ctx.send("Duration must be > 0", ephemeral=True)
|
||||
# return
|
||||
#
|
||||
# elif duration > 60 * 12:
|
||||
# await ctx.send("Duration must be <= 12 hours", ephemeral=True)
|
||||
# return
|
||||
#
|
||||
# if len(reason) > 100:
|
||||
# await ctx.send("Reason must be <= 100 characters", ephemeral=True)
|
||||
# return
|
||||
# if not channel:
|
||||
# channel = ctx.channel
|
||||
#
|
||||
# # role = ctx.guild.default_role # Uncomment once implemented
|
||||
# if isinstance(channel, GuildText):
|
||||
# to_deny = Permissions.SEND_MESSAGES
|
||||
# elif isinstance(channel, GuildVoice):
|
||||
# to_deny = Permissions.CONNECT | Permissions.SPEAK
|
||||
#
|
||||
# overwrite = PermissionOverwrite(type=PermissionTypes.ROLE, deny=to_deny)
|
||||
# # TODO: Get original permissions
|
||||
# # TODO: Apply overwrite
|
||||
# overwrite = overwrite
|
||||
# _ = Lock(
|
||||
# channel=channel.id,
|
||||
# guild=ctx.guild.id,
|
||||
# admin=ctx.author.id,
|
||||
# reason=reason,
|
||||
# duration=duration,
|
||||
# ) # .save() # Uncomment once implemented
|
||||
# # await ctx.send(f"{channel.mention} locked for {duration} minute(s)")
|
||||
# await ctx.send("Unfortunately, this is not yet implemented", hidden=True)
|
||||
#
|
||||
# @cog_ext.cog_slash(
|
||||
# name="unlock",
|
||||
# description="Unlocks a channel",
|
||||
# choices=[
|
||||
# create_option(
|
||||
# name="channel",
|
||||
# description="Channel to lock",
|
||||
# opt_type=7,
|
||||
# required=False,
|
||||
# ),
|
||||
# ],
|
||||
# )
|
||||
# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS))
|
||||
# async def _unlock(
|
||||
# self,
|
||||
# ctx: InteractionContext,
|
||||
# channel: Union[GuildText, GuildVoice] = None,
|
||||
# ) -> None:
|
||||
# if not channel:
|
||||
# channel = ctx.channel
|
||||
# lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first()
|
||||
# if not lock:
|
||||
# await ctx.send(f"{channel.mention} not locked.", ephemeral=True)
|
||||
# return
|
||||
# for role in ctx.guild.roles:
|
||||
# with suppress(Exception):
|
||||
# await self._unlock_channel(channel, role, ctx.author)
|
||||
# lock.active = False
|
||||
# lock.save()
|
||||
# await ctx.send(f"{channel.mention} unlocked")
|
||||
|
|
|
@ -11,8 +11,8 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db.models import Mute
|
||||
|
||||
from jarvis.db.models import Mute
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
@ -74,14 +74,15 @@ class MuteCog(Scale):
|
|||
return
|
||||
|
||||
await user.timeout(communication_disabled_until=duration, reason=reason)
|
||||
_ = Mute(
|
||||
m = Mute(
|
||||
user=user.id,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
duration=duration,
|
||||
active=True,
|
||||
).save()
|
||||
)
|
||||
await m.commit()
|
||||
|
||||
embed = build_embed(
|
||||
title="User Muted",
|
||||
|
@ -119,7 +120,3 @@ class MuteCog(Scale):
|
|||
embed.set_thumbnail(url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
await ctx.send(embed=embed)
|
||||
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||
embed.set_thumbnail(url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
await ctx.send(embed=embed)
|
||||
|
|
|
@ -7,8 +7,9 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Autopurge, Purge
|
||||
|
||||
from jarvis.db.models import Autopurge, Purge
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
|
@ -31,17 +32,17 @@ class PurgeCog(Scale):
|
|||
await ctx.send("Amount must be >= 1", ephemeral=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
channel = ctx.channel
|
||||
|
||||
messages = []
|
||||
async for message in channel.history(limit=amount + 1):
|
||||
async for message in ctx.channel.history(limit=amount + 1):
|
||||
messages.append(message)
|
||||
await channel.delete_messages(messages)
|
||||
_ = Purge(
|
||||
await ctx.channel.delete_messages(messages, reason=f"Purge by {ctx.author.username}")
|
||||
await Purge(
|
||||
channel=ctx.channel.id,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
count=amount,
|
||||
).save()
|
||||
).commit()
|
||||
|
||||
@slash_command(
|
||||
name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages"
|
||||
|
@ -71,16 +72,19 @@ class PurgeCog(Scale):
|
|||
elif delay > 300:
|
||||
await ctx.send("Delay must be < 5 minutes", ephemeral=True)
|
||||
return
|
||||
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
|
||||
autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if autopurge:
|
||||
await ctx.send("Autopurge already exists.", ephemeral=True)
|
||||
return
|
||||
_ = Autopurge(
|
||||
|
||||
await Autopurge(
|
||||
guild=ctx.guild.id,
|
||||
channel=channel.id,
|
||||
admin=ctx.author.id,
|
||||
delay=delay,
|
||||
).save()
|
||||
).commit()
|
||||
|
||||
await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds")
|
||||
|
||||
@slash_command(
|
||||
|
@ -94,11 +98,11 @@ class PurgeCog(Scale):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
|
||||
async def _autopurge_remove(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
|
||||
autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if not autopurge:
|
||||
await ctx.send("Autopurge does not exist.", ephemeral=True)
|
||||
return
|
||||
autopurge.delete()
|
||||
await autopurge.delete()
|
||||
await ctx.send(f"Autopurge removed from {channel.mention}.")
|
||||
|
||||
@slash_command(
|
||||
|
@ -122,10 +126,12 @@ class PurgeCog(Scale):
|
|||
async def _autopurge_update(
|
||||
self, ctx: InteractionContext, channel: GuildText, delay: int
|
||||
) -> None:
|
||||
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
|
||||
autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if not autopurge:
|
||||
await ctx.send("Autopurge does not exist.", ephemeral=True)
|
||||
return
|
||||
|
||||
autopurge.delay = delay
|
||||
autopurge.save()
|
||||
await autopurge.commit()
|
||||
|
||||
await ctx.send(f"Autopurge delay updated to {delay} seconds on {channel.mention}.")
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""J.A.R.V.I.S. RolepingCog."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek import InteractionContext, Permissions, Snake
|
||||
from dis_snek import InteractionContext, Permissions, Scale
|
||||
from dis_snek.client.utils.misc_utils import find_all
|
||||
from dis_snek.ext.paginators import Paginator
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.role import Role
|
||||
|
@ -12,36 +11,34 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Roleping
|
||||
|
||||
from jarvis.db.models import Roleping
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class RolepingCog(CacheCog):
|
||||
class RolepingCog(Scale):
|
||||
"""J.A.R.V.I.S. RolepingCog."""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
|
||||
@slash_command(
|
||||
name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping"
|
||||
)
|
||||
@slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _roleping_add(self, ctx: InteractionContext, role: Role) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first()
|
||||
roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id))
|
||||
if roleping:
|
||||
await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True)
|
||||
return
|
||||
_ = Roleping(
|
||||
|
||||
_ = await Roleping(
|
||||
role=role.id,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
active=True,
|
||||
bypass={"roles": [], "users": []},
|
||||
).save()
|
||||
).commit()
|
||||
await ctx.send(f"Role `{role.name}` added to roleping.")
|
||||
|
||||
@slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role")
|
||||
|
@ -50,37 +47,29 @@ class RolepingCog(CacheCog):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _roleping_remove(self, ctx: InteractionContext, role: Role) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id)
|
||||
roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id))
|
||||
if not roleping:
|
||||
await ctx.send("Roleping does not exist", ephemeral=True)
|
||||
return
|
||||
|
||||
roleping.delete()
|
||||
await roleping.delete()
|
||||
await ctx.send(f"Role `{role.name}` removed from roleping.")
|
||||
|
||||
@slash_command(name="roleping", sub_cmd_name="list", description="Lick all blocklisted roles")
|
||||
async def _roleping_list(self, ctx: InteractionContext) -> None:
|
||||
exists = self.check_cache(ctx)
|
||||
if exists:
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
rolepings = Roleping.objects(guild=ctx.guild.id)
|
||||
rolepings = await Roleping.find(q(guild=ctx.guild.id)).to_list(None)
|
||||
if not rolepings:
|
||||
await ctx.send("No rolepings configured", ephemeral=True)
|
||||
return
|
||||
|
||||
embeds = []
|
||||
for roleping in rolepings:
|
||||
role = await ctx.guild.get_role(roleping.role)
|
||||
bypass_roles = list(filter(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles))
|
||||
bypass_roles = [r.mention or "||`[redacted]`||" for r in bypass_roles]
|
||||
role = await ctx.guild.fetch_role(roleping.role)
|
||||
broles = find_all(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles)
|
||||
bypass_roles = [r.mention or "||`[redacted]`||" for r in broles]
|
||||
bypass_users = [
|
||||
await ctx.guild.get_member(u).mention or "||`[redacted]`||"
|
||||
(await ctx.guild.fetch_member(u)).mention or "||`[redacted]`||"
|
||||
for u in roleping.bypass["users"]
|
||||
]
|
||||
bypass_roles = bypass_roles or ["None"]
|
||||
|
@ -95,37 +84,31 @@ class RolepingCog(CacheCog):
|
|||
value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"),
|
||||
inline=False,
|
||||
),
|
||||
EmbedField(name="Active", value=str(roleping.active)),
|
||||
# EmbedField(name="Active", value=str(roleping.active), inline=True),
|
||||
EmbedField(
|
||||
name="Bypass Users",
|
||||
value="\n".join(bypass_users),
|
||||
inline=True,
|
||||
),
|
||||
EmbedField(
|
||||
name="Bypass Roles",
|
||||
value="\n".join(bypass_roles),
|
||||
inline=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
admin = await ctx.guild.get_member(roleping.admin)
|
||||
admin = await ctx.guild.fetch_member(roleping.admin)
|
||||
if not admin:
|
||||
admin = self.bot.user
|
||||
|
||||
embed.set_author(name=admin.display_name, icon_url=admin.display_avatar.url)
|
||||
embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}")
|
||||
embed.set_footer(text=f"{admin.username}#{admin.discriminator} | {admin.id}")
|
||||
|
||||
embeds.append(embed)
|
||||
|
||||
paginator = Paginator.create_from_embeds(self.bot, *embeds, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
"guild": ctx.guild.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.send(ctx)
|
||||
|
||||
@slash_command(
|
||||
|
@ -136,21 +119,23 @@ class RolepingCog(CacheCog):
|
|||
sub_cmd_name="user",
|
||||
sub_cmd_description="Add a user as a bypass to a roleping",
|
||||
)
|
||||
@slash_option(name="user", description="User to add", opt_type=OptionTypes.USER, required=True)
|
||||
@slash_option(
|
||||
name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
name="bypass", description="User to add", opt_type=OptionTypes.USER, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _roleping_bypass_user(
|
||||
self, ctx: InteractionContext, user: Member, rping: Role
|
||||
self, ctx: InteractionContext, bypass: Member, role: Role
|
||||
) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||
roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id))
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if user.id in roleping.bypass["users"]:
|
||||
await ctx.send(f"{user.mention} already in bypass", ephemeral=True)
|
||||
if bypass.id in roleping.bypass["users"]:
|
||||
await ctx.send(f"{bypass.mention} already in bypass", ephemeral=True)
|
||||
return
|
||||
|
||||
if len(roleping.bypass["users"]) == 10:
|
||||
|
@ -160,18 +145,18 @@ class RolepingCog(CacheCog):
|
|||
)
|
||||
return
|
||||
|
||||
matching_role = list(filter(lambda x: x.id in roleping.bypass["roles"], user.roles))
|
||||
matching_role = list(filter(lambda x: x.id in roleping.bypass["roles"], bypass.roles))
|
||||
|
||||
if matching_role:
|
||||
await ctx.send(
|
||||
f"{user.mention} already has bypass via {matching_role[0].mention}",
|
||||
f"{bypass.mention} already has bypass via {matching_role[0].mention}",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
roleping.bypass["users"].append(user.id)
|
||||
roleping.save()
|
||||
await ctx.send(f"{user.display_name} user bypass added for `{rping.name}`")
|
||||
roleping.bypass["users"].append(bypass.id)
|
||||
await roleping.commit()
|
||||
await ctx.send(f"{bypass.display_name} user bypass added for `{role.name}`")
|
||||
|
||||
@slash_command(
|
||||
name="roleping",
|
||||
|
@ -179,19 +164,23 @@ class RolepingCog(CacheCog):
|
|||
sub_cmd_name="role",
|
||||
description="Add a role as a bypass to roleping",
|
||||
)
|
||||
@slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
|
||||
@slash_option(
|
||||
name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
name="bypass", description="Role to add", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||
async def _roleping_bypass_role(
|
||||
self, ctx: InteractionContext, bypass: Role, role: Role
|
||||
) -> None:
|
||||
roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id))
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if role.id in roleping.bypass["roles"]:
|
||||
await ctx.send(f"{role.mention} already in bypass", ephemeral=True)
|
||||
if bypass.id in roleping.bypass["roles"]:
|
||||
await ctx.send(f"{bypass.mention} already in bypass", ephemeral=True)
|
||||
return
|
||||
|
||||
if len(roleping.bypass["roles"]) == 10:
|
||||
|
@ -202,9 +191,9 @@ class RolepingCog(CacheCog):
|
|||
)
|
||||
return
|
||||
|
||||
roleping.bypass["roles"].append(role.id)
|
||||
roleping.save()
|
||||
await ctx.send(f"{role.name} role bypass added for `{rping.name}`")
|
||||
roleping.bypass["roles"].append(bypass.id)
|
||||
await roleping.commit()
|
||||
await ctx.send(f"{bypass.name} role bypass added for `{role.name}`")
|
||||
|
||||
@slash_command(
|
||||
name="roleping",
|
||||
|
@ -215,27 +204,27 @@ class RolepingCog(CacheCog):
|
|||
sub_cmd_description="Remove a bypass from a roleping (restoring it)",
|
||||
)
|
||||
@slash_option(
|
||||
name="user", description="User to remove", opt_type=OptionTypes.USER, required=True
|
||||
name="bypass", description="User to remove", opt_type=OptionTypes.USER, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _roleping_restore_user(
|
||||
self, ctx: InteractionContext, user: Member, rping: Role
|
||||
self, ctx: InteractionContext, bypass: Member, role: Role
|
||||
) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||
roleping: Roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id))
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if user.id not in roleping.bypass["users"]:
|
||||
await ctx.send(f"{user.mention} not in bypass", ephemeral=True)
|
||||
if bypass.id not in roleping.bypass.users:
|
||||
await ctx.send(f"{bypass.mention} not in bypass", ephemeral=True)
|
||||
return
|
||||
|
||||
roleping.bypass["users"].delete(user.id)
|
||||
roleping.save()
|
||||
await ctx.send(f"{user.display_name} user bypass removed for `{rping.name}`")
|
||||
roleping.bypass.users.remove(bypass.id)
|
||||
await roleping.commit()
|
||||
await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`")
|
||||
|
||||
@slash_command(
|
||||
name="roleping",
|
||||
|
@ -244,32 +233,24 @@ class RolepingCog(CacheCog):
|
|||
description="Remove a bypass from a roleping (restoring it)",
|
||||
)
|
||||
@slash_option(
|
||||
name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True
|
||||
name="bypass", description="Role to remove", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _roleping_restore_role(
|
||||
self, ctx: InteractionContext, role: Role, rping: Role
|
||||
self, ctx: InteractionContext, bypass: Role, role: Role
|
||||
) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||
roleping: Roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id))
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if role.id in roleping.bypass["roles"]:
|
||||
await ctx.send(f"{role.mention} already in bypass", ephemeral=True)
|
||||
if bypass.id not in roleping.bypass.roles:
|
||||
await ctx.send(f"{bypass.mention} not in bypass", ephemeral=True)
|
||||
return
|
||||
|
||||
if len(roleping.bypass["roles"]) == 10:
|
||||
await ctx.send(
|
||||
"Already have 10 roles in bypass. "
|
||||
"Please consider consolidating roles for roleping bypass",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
roleping.bypass["roles"].append(role.id)
|
||||
roleping.save()
|
||||
await ctx.send(f"{role.name} role bypass added for `{rping.name}`")
|
||||
roleping.bypass.roles.remove(bypass.id)
|
||||
await roleping.commit()
|
||||
await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`")
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""J.A.R.V.I.S. WarningCog."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek import InteractionContext, Permissions, Snake
|
||||
from dis_snek import InteractionContext, Permissions, Scale
|
||||
from dis_snek.client.utils.misc_utils import get_all
|
||||
from dis_snek.ext.paginators import Paginator
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.user import User
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
|
@ -11,20 +11,16 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db.models import Warning
|
||||
|
||||
from jarvis.db.models import Warning
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.embeds import warning_embed
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class WarningCog(CacheCog):
|
||||
class WarningCog(Scale):
|
||||
"""J.A.R.V.I.S. WarningCog."""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
|
||||
@slash_command(name="warn", description="Warn a user")
|
||||
@slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True)
|
||||
@slash_option(
|
||||
|
@ -53,23 +49,15 @@ class WarningCog(CacheCog):
|
|||
await ctx.send("Duration must be < 5 days", ephemeral=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
_ = Warning(
|
||||
await Warning(
|
||||
user=user.id,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
duration=duration,
|
||||
active=True,
|
||||
).save()
|
||||
fields = [Field("Reason", reason, False)]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{user.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
|
||||
).commit()
|
||||
embed = warning_embed(user, reason)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@slash_command(name="warnings", description="Get count of user warnings")
|
||||
|
@ -87,25 +75,20 @@ class WarningCog(CacheCog):
|
|||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None:
|
||||
active = bool(active)
|
||||
exists = self.check_cache(ctx, user_id=user.id, active=active)
|
||||
if exists:
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
ephemeral=True,
|
||||
|
||||
warnings = (
|
||||
await Warning.find(
|
||||
user=user.id,
|
||||
guild=ctx.guild.id,
|
||||
)
|
||||
return
|
||||
warnings = Warning.objects(
|
||||
user=user.id,
|
||||
guild=ctx.guild.id,
|
||||
).order_by("-created_at")
|
||||
active_warns = Warning.objects(user=user.id, guild=ctx.guild.id, active=True).order_by(
|
||||
"-created_at"
|
||||
.sort("created_at", -1)
|
||||
.to_list(None)
|
||||
)
|
||||
active_warns = get_all(warnings, active=True)
|
||||
|
||||
pages = []
|
||||
if active:
|
||||
if active_warns.count() == 0:
|
||||
if len(active_warns) == 0:
|
||||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=f"{warnings.count()} total | 0 currently active",
|
||||
|
@ -122,7 +105,7 @@ class WarningCog(CacheCog):
|
|||
if admin:
|
||||
admin_name = f"{admin.username}#{admin.discriminator}"
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
||||
value=f"{warn.reason}\nAdmin: {admin_name}\n\u200b",
|
||||
inline=False,
|
||||
|
@ -132,7 +115,7 @@ class WarningCog(CacheCog):
|
|||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=(
|
||||
f"{warnings.count()} total | {active_warns.count()} currently active"
|
||||
f"{len(warnings)} total | {len(active_warns)} currently active"
|
||||
),
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
|
@ -149,7 +132,7 @@ class WarningCog(CacheCog):
|
|||
title = "[A] " if warn.active else "[I] "
|
||||
title += warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name=title,
|
||||
value=warn.reason + "\n\u200b",
|
||||
inline=False,
|
||||
|
@ -158,9 +141,7 @@ class WarningCog(CacheCog):
|
|||
for i in range(0, len(fields), 5):
|
||||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=(
|
||||
f"{warnings.count()} total | {active_warns.count()} currently active"
|
||||
),
|
||||
description=(f"{len(warnings)} total | {len(active_warns)} currently active"),
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
embed.set_author(
|
||||
|
@ -171,14 +152,4 @@ class WarningCog(CacheCog):
|
|||
|
||||
paginator = Paginator(bot=self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"guild": ctx.guild.id,
|
||||
"user": ctx.author.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"user_id": user.id,
|
||||
"active": active,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.send(ctx)
|
||||
|
|
|
@ -3,6 +3,7 @@ import re
|
|||
from typing import Optional, Tuple
|
||||
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
from dis_snek.client.utils.misc_utils import find
|
||||
from dis_snek.models.discord.channel import GuildText
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
|
@ -10,10 +11,10 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Autoreact
|
||||
|
||||
from jarvis.data.unicode import emoji_list
|
||||
from jarvis.db.models import Autoreact
|
||||
from jarvis.utils import find
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
|
@ -37,7 +38,7 @@ class AutoReactCog(Scale):
|
|||
Returns:
|
||||
Tuple of success? and error message
|
||||
"""
|
||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
exists = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if exists:
|
||||
return False, f"Autoreact already exists for {channel.mention}."
|
||||
|
||||
|
@ -93,10 +94,10 @@ class AutoReactCog(Scale):
|
|||
if not find(lambda x: x.id == emoji_id, ctx.guild.emojis):
|
||||
await ctx.send("Please use a custom emote from this server.", ephemeral=True)
|
||||
return
|
||||
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if not autoreact:
|
||||
self.create_autoreact(ctx, channel)
|
||||
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if emote in autoreact.reactions:
|
||||
await ctx.send(
|
||||
f"Emote already added to {channel.mention} autoreactions.",
|
||||
|
@ -134,7 +135,7 @@ class AutoReactCog(Scale):
|
|||
async def _autoreact_remove(
|
||||
self, ctx: InteractionContext, channel: GuildText, emote: str
|
||||
) -> None:
|
||||
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if not autoreact:
|
||||
await ctx.send(
|
||||
f"Please create autoreact first with /autoreact add {channel.mention} {emote}",
|
||||
|
@ -169,7 +170,7 @@ class AutoReactCog(Scale):
|
|||
required=True,
|
||||
)
|
||||
async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
exists = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id))
|
||||
if not exists:
|
||||
await ctx.send(
|
||||
f"Please create autoreact first with /autoreact add {channel.mention} <emote>",
|
||||
|
|
|
@ -10,8 +10,9 @@ from dis_snek.models.discord.user import Member, User
|
|||
from dis_snek.models.snek.application_commands import slash_command
|
||||
from dis_snek.models.snek.command import cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Guess
|
||||
|
||||
from jarvis.db.models import Guess
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
|
||||
|
@ -74,7 +75,7 @@ class CTCCog(CacheCog):
|
|||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
guessed = Guess.objects(guess=guess).first()
|
||||
guessed = await Guess.find_one(q(guess=guess))
|
||||
if guessed:
|
||||
await ctx.send("Already guessed, dipshit.", ephemeral=True)
|
||||
return
|
||||
|
|
|
@ -4,12 +4,12 @@ import hashlib
|
|||
import re
|
||||
import subprocess # noqa: S404
|
||||
import uuid as uuidpy
|
||||
from typing import Any, Union
|
||||
|
||||
import ulid as ulidpy
|
||||
from bson import ObjectId
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.message import Attachment
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
|
@ -18,8 +18,11 @@ from dis_snek.models.snek.application_commands import (
|
|||
)
|
||||
from dis_snek.models.snek.command import cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
from jarvis_core.filters import invites, url
|
||||
from jarvis_core.util import convert_bytesize, hash
|
||||
from jarvis_core.util.http import get_size
|
||||
|
||||
from jarvis.utils import build_embed, convert_bytesize
|
||||
from jarvis.utils import build_embed
|
||||
|
||||
supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x}
|
||||
|
||||
|
@ -36,29 +39,9 @@ UUID_VERIFY = re.compile(
|
|||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
UUID_GET = {3: uuidpy.uuid3, 5: uuidpy.uuid5}
|
||||
|
||||
|
||||
def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str:
|
||||
"""Hash data with hash object.
|
||||
|
||||
Data can be text or binary
|
||||
"""
|
||||
if text:
|
||||
hash.update(data.encode("UTF-8"))
|
||||
return hash.hexdigest()
|
||||
BSIZE = 65536
|
||||
block_idx = 0
|
||||
while block_idx * BSIZE < len(data):
|
||||
block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)]
|
||||
hash.update(block)
|
||||
block_idx += 1
|
||||
return hash.hexdigest()
|
||||
MAX_FILESIZE = 5 * (1024**3) # 5GB
|
||||
|
||||
|
||||
class DevCog(Scale):
|
||||
|
@ -76,26 +59,51 @@ class DevCog(Scale):
|
|||
name="data",
|
||||
description="Data to hash",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
required=False,
|
||||
)
|
||||
@slash_option(
|
||||
name="attach", description="File to hash", opt_type=OptionTypes.ATTACHMENT, required=False
|
||||
)
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _hash(self, ctx: InteractionContext, method: str, data: str) -> None:
|
||||
if not data:
|
||||
async def _hash(
|
||||
self, ctx: InteractionContext, method: str, data: str = None, attach: Attachment = None
|
||||
) -> None:
|
||||
if not data and not attach:
|
||||
await ctx.send(
|
||||
"No data to hash",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
text = True
|
||||
# Default to sha256, just in case
|
||||
hash = getattr(hashlib, method, hashlib.sha256)()
|
||||
hex = hash_obj(hash, data, text)
|
||||
data_size = convert_bytesize(len(data))
|
||||
title = data if text else ctx.message.attachments[0].filename
|
||||
if data and invites.match(data):
|
||||
await ctx.send("No hashing invites", ephemeral=True)
|
||||
return
|
||||
title = data
|
||||
if attach:
|
||||
data = attach.url
|
||||
title = attach.filename
|
||||
elif url.match(data):
|
||||
try:
|
||||
if await get_size(data) > MAX_FILESIZE:
|
||||
await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True)
|
||||
return
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True)
|
||||
return
|
||||
title = data.split("/")[-1]
|
||||
|
||||
await ctx.defer()
|
||||
try:
|
||||
hexstr, size, c_type = await hash(data, method)
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to hash data: ```\n{e}\n```", ephemeral=True)
|
||||
return
|
||||
|
||||
data_size = convert_bytesize(size)
|
||||
description = "Hashed using " + method
|
||||
fields = [
|
||||
EmbedField("Content Type", c_type, False),
|
||||
EmbedField("Data Size", data_size, False),
|
||||
EmbedField("Hash", f"`{hex}`", False),
|
||||
EmbedField("Hash", f"`{hexstr}`", False),
|
||||
]
|
||||
|
||||
embed = build_embed(title=title, description=description, fields=fields)
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
"""J.A.R.V.I.S. error handling cog."""
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext
|
||||
|
||||
from jarvis import slash
|
||||
|
||||
|
||||
class ErrorHandlerCog(commands.Cog):
|
||||
"""J.A.R.V.I.S. error handling cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx: commands.Context, error: Exception) -> None:
|
||||
"""d.py on_command_error override."""
|
||||
if isinstance(error, commands.errors.MissingPermissions):
|
||||
await ctx.send("I'm afraid I can't let you do that.")
|
||||
elif isinstance(error, commands.errors.CommandNotFound):
|
||||
return
|
||||
elif isinstance(error, commands.errors.CommandOnCooldown):
|
||||
await ctx.send(
|
||||
"Command on cooldown. "
|
||||
f"Please wait {error.retry_after:0.2f}s before trying again",
|
||||
)
|
||||
else:
|
||||
await ctx.send(f"Error processing command:\n```{error}```")
|
||||
ctx.command.reset_cooldown(ctx)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_slash_command_error(self, ctx: SlashContext, error: Exception) -> None:
|
||||
"""discord_slash on_slash_command_error override."""
|
||||
if isinstance(error, commands.errors.MissingPermissions) or isinstance(
|
||||
error, commands.errors.CheckFailure
|
||||
):
|
||||
await ctx.send("I'm afraid I can't let you do that.", ephemeral=True)
|
||||
elif isinstance(error, commands.errors.CommandNotFound):
|
||||
return
|
||||
elif isinstance(error, commands.errors.CommandOnCooldown):
|
||||
await ctx.send(
|
||||
"Command on cooldown. "
|
||||
f"Please wait {error.retry_after:0.2f}s before trying again",
|
||||
ephemeral=True,
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
f"Error processing command:\n```{error}```",
|
||||
ephemeral=True,
|
||||
)
|
||||
raise error
|
||||
slash.commands[ctx.command].reset_cooldown(ctx)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
"""Add ErrorHandlerCog to J.A.R.V.I.S."""
|
||||
ErrorHandlerCog(bot)
|
|
@ -1,8 +1,8 @@
|
|||
"""J.A.R.V.I.S. GitLab Cog."""
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
import gitlab
|
||||
from dis_snek import InteractionContext, Snake
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.ext.paginators import Paginator
|
||||
from dis_snek.models.discord.embed import Embed, EmbedField
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
|
@ -14,16 +14,15 @@ from dis_snek.models.snek.application_commands import (
|
|||
|
||||
from jarvis.config import get_config
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
|
||||
guild_ids = [862402786116763668]
|
||||
|
||||
|
||||
class GitlabCog(CacheCog):
|
||||
class GitlabCog(Scale):
|
||||
"""J.A.R.V.I.S. GitLab Cog."""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
self.bot = bot
|
||||
config = get_config()
|
||||
self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token)
|
||||
# J.A.R.V.I.S. GitLab ID is 29
|
||||
|
@ -236,10 +235,11 @@ class GitlabCog(CacheCog):
|
|||
title += f"J.A.R.V.I.S. {name}s"
|
||||
fields = []
|
||||
for item in api_list:
|
||||
description = item.description or "No description"
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name=f"[#{item.iid}] {item.title}",
|
||||
value=item.description + f"\n\n[View this {name}]({item.web_url})",
|
||||
value=(description[:200] + f"...\n\n[View this {name}]({item.web_url})"),
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
@ -275,14 +275,6 @@ class GitlabCog(CacheCog):
|
|||
],
|
||||
)
|
||||
async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None:
|
||||
exists = self.check_cache(ctx, state=state)
|
||||
if exists:
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
await ctx.defer()
|
||||
m_state = state
|
||||
if m_state == "all":
|
||||
|
@ -319,15 +311,6 @@ class GitlabCog(CacheCog):
|
|||
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
"guild": ctx.guild.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"state": state,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.send(ctx)
|
||||
|
||||
@slash_command(
|
||||
|
@ -348,14 +331,6 @@ class GitlabCog(CacheCog):
|
|||
],
|
||||
)
|
||||
async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None:
|
||||
exists = self.check_cache(ctx, state=state)
|
||||
if exists:
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
await ctx.defer()
|
||||
m_state = state
|
||||
if m_state == "all":
|
||||
|
@ -394,15 +369,6 @@ class GitlabCog(CacheCog):
|
|||
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
"guild": ctx.guild.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"state": state,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.send(ctx)
|
||||
|
||||
@slash_command(
|
||||
|
@ -412,14 +378,6 @@ class GitlabCog(CacheCog):
|
|||
scopes=guild_ids,
|
||||
)
|
||||
async def _milestones(self, ctx: InteractionContext) -> None:
|
||||
exists = self.check_cache(ctx)
|
||||
if exists:
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
await ctx.defer()
|
||||
milestones = []
|
||||
page = 1
|
||||
|
@ -450,14 +408,6 @@ class GitlabCog(CacheCog):
|
|||
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
"guild": ctx.guild.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.send(ctx)
|
||||
|
||||
|
||||
|
|
|
@ -5,13 +5,18 @@ from io import BytesIO
|
|||
import aiohttp
|
||||
import cv2
|
||||
import numpy as np
|
||||
from dis_snek import MessageContext, Scale, Snake, message_command
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.file import File
|
||||
from dis_snek.models.snek.command import cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
from dis_snek.models.discord.message import Attachment
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from jarvis_core.util import build_embed, convert_bytesize, unconvert_bytesize
|
||||
|
||||
from jarvis.utils import build_embed, convert_bytesize, unconvert_bytesize
|
||||
MIN_ACCURACY = 0.80
|
||||
|
||||
|
||||
class ImageCog(Scale):
|
||||
|
@ -24,39 +29,65 @@ class ImageCog(Scale):
|
|||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self._session = aiohttp.ClientSession()
|
||||
self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE)
|
||||
self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B?)", re.IGNORECASE)
|
||||
|
||||
def __del__(self):
|
||||
self._session.close()
|
||||
|
||||
async def _resize(self, ctx: MessageContext, target: str, url: str = None) -> None:
|
||||
if not target:
|
||||
await ctx.send("Missing target size, i.e. 200KB.")
|
||||
@slash_command(name="resize", description="Resize an image")
|
||||
@slash_option(
|
||||
name="target",
|
||||
description="Target size, i.e. 200KB",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="attachment",
|
||||
description="Image to resize",
|
||||
opt_type=OptionTypes.ATTACHMENT,
|
||||
required=False,
|
||||
)
|
||||
@slash_option(
|
||||
name="url",
|
||||
description="URL to download and resize",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=False,
|
||||
)
|
||||
async def _resize(
|
||||
self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None
|
||||
) -> None:
|
||||
if not attachment and not url:
|
||||
await ctx.send("A URL or attachment is required", ephemeral=True)
|
||||
return
|
||||
|
||||
if attachment and not attachment.content_type.startswith("image"):
|
||||
await ctx.send("Attachment must be an image", ephemeral=True)
|
||||
return
|
||||
|
||||
tgt = self.tgt_match.match(target)
|
||||
if not tgt:
|
||||
await ctx.send(f"Invalid target format ({target}). Expected format like 200KB")
|
||||
await ctx.send(
|
||||
f"Invalid target format ({target}). Expected format like 200KB", ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1])
|
||||
if tgt_size > unconvert_bytesize(8, "MB"):
|
||||
await ctx.send("Target too large to send. Please make target < 8MB")
|
||||
await ctx.send("Target too large to send. Please make target < 8MB", ephemeral=True)
|
||||
return
|
||||
file = None
|
||||
filename = None
|
||||
if ctx.message.attachments is not None and len(ctx.message.attachments) > 0:
|
||||
file = await ctx.message.attachments[0].read()
|
||||
filename = ctx.message.attachments[0].filename
|
||||
elif url is not None:
|
||||
async with self._session.get(url) as resp:
|
||||
if resp.status == 200:
|
||||
file = await resp.read()
|
||||
filename = url.split("/")[-1]
|
||||
else:
|
||||
ctx.send("Missing file as either attachment or URL.")
|
||||
|
||||
size = len(file)
|
||||
if attachment:
|
||||
url = attachment.url
|
||||
filename = attachment.filename
|
||||
else:
|
||||
filename = url.split("/")[-1]
|
||||
|
||||
data = None
|
||||
async with self._session.get(url) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.read()
|
||||
|
||||
size = len(data)
|
||||
if size <= tgt_size:
|
||||
await ctx.send("Image already meets target.")
|
||||
return
|
||||
|
@ -64,44 +95,39 @@ class ImageCog(Scale):
|
|||
ratio = max(tgt_size / size - 0.02, 0.50)
|
||||
|
||||
accuracy = 0.0
|
||||
# TODO: Optimize to not run multiple times
|
||||
while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < 0.65):
|
||||
old_file = file
|
||||
|
||||
buffer = np.frombuffer(file, dtype=np.uint8)
|
||||
while len(data) > tgt_size or (len(data) <= tgt_size and accuracy < MIN_ACCURACY):
|
||||
old_file = data
|
||||
|
||||
buffer = np.frombuffer(data, dtype=np.uint8)
|
||||
img = cv2.imdecode(buffer, flags=-1)
|
||||
|
||||
width = int(img.shape[1] * ratio)
|
||||
height = int(img.shape[0] * ratio)
|
||||
|
||||
new_img = cv2.resize(img, (width, height))
|
||||
file = cv2.imencode(".png", new_img)[1].tobytes()
|
||||
accuracy = (len(file) / tgt_size) * 100
|
||||
data = cv2.imencode(".png", new_img)[1].tobytes()
|
||||
accuracy = (len(data) / tgt_size) * 100
|
||||
if accuracy <= 0.50:
|
||||
file = old_file
|
||||
data = old_file
|
||||
ratio += 0.1
|
||||
else:
|
||||
ratio = max(tgt_size / len(file) - 0.02, 0.65)
|
||||
ratio = max(tgt_size / len(data) - 0.02, 0.65)
|
||||
|
||||
bufio = BytesIO(file)
|
||||
accuracy = (len(file) / tgt_size) * 100
|
||||
bufio = BytesIO(data)
|
||||
accuracy = (len(data) / tgt_size) * 100
|
||||
fields = [
|
||||
EmbedField("Original Size", convert_bytesize(size), False),
|
||||
EmbedField("New Size", convert_bytesize(len(file)), False),
|
||||
EmbedField("New Size", convert_bytesize(len(data)), False),
|
||||
EmbedField("Accuracy", f"{accuracy:.02f}%", False),
|
||||
]
|
||||
embed = build_embed(title=filename, description="", fields=fields)
|
||||
embed.set_image(url="attachment://resized.png")
|
||||
await ctx.send(
|
||||
embed=embed,
|
||||
file=File(file=bufio, filename="resized.png"),
|
||||
file=File(file=bufio, file_name="resized.png"),
|
||||
)
|
||||
|
||||
@message_command(name="resize")
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=60)
|
||||
async def _resize_pref(self, ctx: MessageContext, target: str, url: str = None) -> None:
|
||||
await self._resize(ctx, target, url)
|
||||
|
||||
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add ImageCog to J.A.R.V.I.S."""
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
"""J.A.R.V.I.S. Jokes module."""
|
||||
import html
|
||||
import re
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from random import randint
|
||||
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
|
||||
from jarvis.db.models import Joke
|
||||
from jarvis.utils import build_embed
|
||||
|
||||
|
||||
class JokeCog(Scale):
|
||||
"""
|
||||
Joke library for J.A.R.V.I.S.
|
||||
|
||||
May adapt over time to create jokes using machine learning
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
|
||||
# TODO: Make this a command group with subcommands
|
||||
@slash_command(
|
||||
name="joke",
|
||||
description="Hear a joke",
|
||||
)
|
||||
@slash_option(name="id", description="Joke ID", required=False, opt_type=OptionTypes.INTEGER)
|
||||
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=10)
|
||||
async def _joke(self, ctx: InteractionContext, id: str = None) -> None:
|
||||
"""Get a joke from the database."""
|
||||
try:
|
||||
if randint(1, 100_000) == 5779 and id is None: # noqa: S311
|
||||
await ctx.send(f"<@{ctx.message.author.id}>")
|
||||
return
|
||||
# TODO: Add this as a parameter that can be passed in
|
||||
threshold = 500 # Minimum score
|
||||
result = None
|
||||
if id:
|
||||
result = Joke.objects(rid=id).first()
|
||||
else:
|
||||
pipeline = [
|
||||
{"$match": {"score": {"$gt": threshold}}},
|
||||
{"$sample": {"size": 1}},
|
||||
]
|
||||
result = Joke.objects().aggregate(pipeline).next()
|
||||
while result["body"] in ["[removed]", "[deleted]"]:
|
||||
result = Joke.objects().aggregate(pipeline).next()
|
||||
|
||||
if result is None:
|
||||
await ctx.send("Humor module failed. Please try again later.", ephemeral=True)
|
||||
return
|
||||
emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"])
|
||||
for match in emotes:
|
||||
result["body"] = result["body"].replace(match, html.unescape(match))
|
||||
emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["title"])
|
||||
for match in emotes:
|
||||
result["title"] = result["title"].replace(match, html.unescape(match))
|
||||
body_chunks = []
|
||||
|
||||
body = ""
|
||||
for word in result["body"].split(" "):
|
||||
if len(body) + 1 + len(word) > 1024:
|
||||
body_chunks.append(EmbedField("", body, False))
|
||||
body = ""
|
||||
if word == "\n" and body == "":
|
||||
continue
|
||||
elif word == "\n":
|
||||
body += word
|
||||
else:
|
||||
body += " " + word
|
||||
|
||||
desc = ""
|
||||
title = result["title"]
|
||||
if len(title) > 256:
|
||||
new_title = ""
|
||||
limit = False
|
||||
for word in title.split(" "):
|
||||
if len(new_title) + len(word) + 1 > 253 and not limit:
|
||||
new_title += "..."
|
||||
desc = "..."
|
||||
limit = True
|
||||
if not limit:
|
||||
new_title += word + " "
|
||||
else:
|
||||
desc += word + " "
|
||||
|
||||
body_chunks.append(EmbedField("", body, False))
|
||||
|
||||
fields = body_chunks
|
||||
fields.append(EmbedField("Score", result["score"]))
|
||||
# Field(
|
||||
# "Created At",
|
||||
# str(datetime.fromtimestamp(result["created_utc"])),
|
||||
# ),
|
||||
fields.append(EmbedField("ID", result["rid"]))
|
||||
embed = build_embed(
|
||||
title=title,
|
||||
description=desc,
|
||||
fields=fields,
|
||||
url=f"https://reddit.com/r/jokes/comments/{result['rid']}",
|
||||
timestamp=datetime.fromtimestamp(result["created_utc"]),
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
except Exception:
|
||||
await ctx.send("Encountered error:\n```\n" + traceback.format_exc() + "\n```")
|
||||
# await ctx.send(f"**{result['title']}**\n\n{result['body']}")
|
||||
|
||||
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add JokeCog to J.A.R.V.I.S."""
|
||||
JokeCog(bot)
|
|
@ -1,11 +0,0 @@
|
|||
"""J.A.R.V.I.S. Modlog Cogs."""
|
||||
from discord.ext.commands import Bot
|
||||
|
||||
from jarvis.cogs.modlog import command, member, message
|
||||
|
||||
|
||||
def setup(bot: Bot) -> None:
|
||||
"""Add modlog cogs to J.A.R.V.I.S."""
|
||||
command.ModlogCommandCog(bot)
|
||||
member.ModlogMemberCog(bot)
|
||||
message.ModlogMessageCog(bot)
|
|
@ -1,48 +0,0 @@
|
|||
"""J.A.R.V.I.S. ModlogCommandCog."""
|
||||
from discord import DMChannel
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext
|
||||
|
||||
from jarvis.db.models import Setting
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class ModlogCommandCog(commands.Cog):
|
||||
"""J.A.R.V.I.S. ModlogCommandCog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_slash_command(self, ctx: SlashContext) -> None:
|
||||
"""Process on_slash_command events."""
|
||||
if not isinstance(ctx.channel, DMChannel) and ctx.name not in ["pw"]:
|
||||
modlog = Setting.objects(guild=ctx.guild.id, setting="modlog").first()
|
||||
if modlog:
|
||||
channel = ctx.guild.get_channel(modlog.value)
|
||||
fields = [
|
||||
Field("Command", ctx.name),
|
||||
]
|
||||
if ctx.kwargs:
|
||||
kwargs_string = " ".join(f"{k}: {str(ctx.kwargs[k])}" for k in ctx.kwargs)
|
||||
fields.append(
|
||||
Field(
|
||||
"Keyword Args",
|
||||
kwargs_string,
|
||||
False,
|
||||
)
|
||||
)
|
||||
if ctx.subcommand_name:
|
||||
fields.insert(1, Field("Subcommand", ctx.subcommand_name))
|
||||
embed = build_embed(
|
||||
title="Command Invoked",
|
||||
description=f"{ctx.author.mention} invoked a command",
|
||||
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.username}#{ctx.author.discriminator} | {ctx.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
|
@ -1,332 +0,0 @@
|
|||
"""J.A.R.V.I.S. ModlogMemberCog."""
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.utils import find
|
||||
|
||||
from jarvis.cogs.modlog.utils import get_latest_log, modlog_embed
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.models import Ban, Kick, Mute, Setting, Unban
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class ModlogMemberCog(commands.Cog):
|
||||
"""J.A.R.V.I.S. ModlogMemberCog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
self.cache = []
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_member_ban(self, guild: discord.Guild, user: discord.User) -> None:
|
||||
"""Process on_member_ban events."""
|
||||
modlog = Setting.objects(guild=guild.id, setting="modlog").first()
|
||||
if modlog:
|
||||
channel = guild.get_channel(modlog.value)
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
auditlog = await guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.ban,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, user)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
await asyncio.sleep(3)
|
||||
ban = (
|
||||
Ban.objects(
|
||||
guild=guild.id,
|
||||
user=user.id,
|
||||
active=True,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
if ban:
|
||||
admin = guild.get_member(ban.admin)
|
||||
embed = modlog_embed(
|
||||
user,
|
||||
admin,
|
||||
log,
|
||||
"User banned",
|
||||
f"{user.mention} was banned from {guild.name}",
|
||||
)
|
||||
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_member_unban(self, guild: discord.Guild, user: discord.User) -> None:
|
||||
"""Process on_member_unban events."""
|
||||
modlog = Setting.objects(guild=guild.id, setting="modlog").first()
|
||||
if modlog:
|
||||
channel = guild.get_channel(modlog.value)
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
auditlog = await guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.unban,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, user)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
await asyncio.sleep(3)
|
||||
unban = (
|
||||
Unban.objects(
|
||||
guild=guild.id,
|
||||
user=user.id,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
admin = guild.get_member(unban.admin)
|
||||
embed = modlog_embed(
|
||||
user,
|
||||
admin,
|
||||
log,
|
||||
"User unbanned",
|
||||
f"{user.mention} was unbanned from {guild.name}",
|
||||
)
|
||||
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_member_remove(self, user: discord.Member) -> None:
|
||||
"""Process on_member_remove events."""
|
||||
modlog = Setting.objects(guild=user.guild.id, setting="modlog").first()
|
||||
if modlog:
|
||||
channel = user.guild.get_channel(modlog.value)
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
auditlog = await user.guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.kick,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
count = 0
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, user)
|
||||
while not log:
|
||||
if count == 30:
|
||||
break
|
||||
await asyncio.sleep(0.5)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, user)
|
||||
count += 1
|
||||
if not log:
|
||||
return
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
await asyncio.sleep(3)
|
||||
kick = (
|
||||
Kick.objects(
|
||||
guild=user.guild.id,
|
||||
user=user.id,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
if kick:
|
||||
admin = user.guild.get_member(kick.admin)
|
||||
embed = modlog_embed(
|
||||
user,
|
||||
admin,
|
||||
log,
|
||||
"User Kicked",
|
||||
f"{user.mention} was kicked from {user.guild.name}",
|
||||
)
|
||||
|
||||
await channel.send(embed=embed)
|
||||
|
||||
async def process_mute(self, before: discord.Member, after: discord.Member) -> discord.Embed:
|
||||
"""Process mute event."""
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
auditlog = await before.guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.member_role_update,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
await asyncio.sleep(3)
|
||||
mute = (
|
||||
Mute.objects(
|
||||
guild=before.guild.id,
|
||||
user=before.id,
|
||||
active=True,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
if mute:
|
||||
admin = before.guild.get_member(mute.admin)
|
||||
return modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
title="User Muted",
|
||||
desc=f"{before.mention} was muted",
|
||||
)
|
||||
|
||||
async def process_unmute(self, before: discord.Member, after: discord.Member) -> discord.Embed:
|
||||
"""Process unmute event."""
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
auditlog = await before.guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.member_role_update,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
await asyncio.sleep(3)
|
||||
mute = (
|
||||
Mute.objects(
|
||||
guild=before.guild.id,
|
||||
user=before.id,
|
||||
active=True,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
if mute:
|
||||
admin = before.guild.get_member(mute.admin)
|
||||
return modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
title="User Muted",
|
||||
desc=f"{before.mention} was muted",
|
||||
)
|
||||
|
||||
async def process_verify(self, before: discord.Member, after: discord.Member) -> discord.Embed:
|
||||
"""Process verification event."""
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
auditlog = await before.guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.member_role_update,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
return modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
title="User Verified",
|
||||
desc=f"{before.mention} was verified",
|
||||
)
|
||||
|
||||
async def process_rolechange(
|
||||
self, before: discord.Member, after: discord.Member
|
||||
) -> discord.Embed:
|
||||
"""Process rolechange event."""
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
auditlog = await before.guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.member_role_update,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
role = None
|
||||
title = "User Given Role"
|
||||
verb = "was given"
|
||||
if len(before.roles) > len(after.roles):
|
||||
title = "User Forfeited Role"
|
||||
verb = "forfeited"
|
||||
role = find(lambda x: x not in after.roles, before.roles)
|
||||
elif len(before.roles) < len(after.roles):
|
||||
role = find(lambda x: x not in before.roles, after.roles)
|
||||
role_text = role.mention if role else "||`[redacted]`||"
|
||||
return modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
title=title,
|
||||
desc=f"{before.mention} {verb} role {role_text}",
|
||||
)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_member_update(self, before: discord.Member, after: discord.Member) -> None:
|
||||
"""Process on_member_update events.
|
||||
|
||||
Caches events due to double-send bug
|
||||
"""
|
||||
h = hash(hash(before) * hash(after))
|
||||
if h not in self.cache:
|
||||
self.cache.append(h)
|
||||
else:
|
||||
return
|
||||
modlog = Setting.objects(guild=before.guild.id, setting="modlog").first()
|
||||
if modlog:
|
||||
channel = after.guild.get_channel(modlog.value)
|
||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||
embed = None
|
||||
mute = Setting.objects(guild=before.guild.id, setting="mute").first()
|
||||
verified = Setting.objects(guild=before.guild.id, setting="verified").first()
|
||||
mute_role = None
|
||||
verified_role = None
|
||||
if mute:
|
||||
mute_role = before.guild.get_role(mute.value)
|
||||
if verified:
|
||||
verified_role = before.guild.get_role(verified.value)
|
||||
if mute and mute_role in after.roles and mute_role not in before.roles:
|
||||
embed = await self.process_mute(before, after)
|
||||
elif mute and mute_role in before.roles and mute_role not in after.roles:
|
||||
embed = await self.process_unmute(before, after)
|
||||
elif verified and verified_role not in before.roles and verified_role in after.roles:
|
||||
embed = await self.process_verify(before, after)
|
||||
elif before.nick != after.nick:
|
||||
auditlog = await before.guild.audit_logs(
|
||||
limit=50,
|
||||
action=discord.AuditLogAction.member_update,
|
||||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
bname = before.nick if before.nick else before.name
|
||||
aname = after.nick if after.nick else after.name
|
||||
fields = [
|
||||
Field(
|
||||
name="Before",
|
||||
value=f"{bname} ({before.name}#{before.discriminator})",
|
||||
),
|
||||
Field(
|
||||
name="After",
|
||||
value=f"{aname} ({after.name}#{after.discriminator})",
|
||||
),
|
||||
]
|
||||
if log.user.id != before.id:
|
||||
fields.append(
|
||||
Field(
|
||||
name="Moderator",
|
||||
value=f"{log.user.mention} ({log.user.name}#{log.user.discriminator})",
|
||||
)
|
||||
)
|
||||
if log.reason:
|
||||
fields.append(
|
||||
Field(name="Reason", value=log.reason, inline=False),
|
||||
)
|
||||
embed = build_embed(
|
||||
title="User Nick Changed",
|
||||
description=f"{after.mention} changed their nickname",
|
||||
color="#fc9e3f",
|
||||
fields=fields,
|
||||
timestamp=log.created_at,
|
||||
)
|
||||
embed.set_author(name=f"{after.name}", icon_url=after.display_avatar.url)
|
||||
embed.set_footer(text=f"{after.name}#{after.discriminator} | {after.id}")
|
||||
elif len(before.roles) != len(after.roles):
|
||||
# TODO: User got a new role
|
||||
embed = await self.process_rolechange(before, after)
|
||||
if embed:
|
||||
await channel.send(embed=embed)
|
||||
self.cache.remove(h)
|
|
@ -1,108 +0,0 @@
|
|||
"""J.A.R.V.I.S. ModlogMessageCog."""
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from jarvis.db.models import Setting
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class ModlogMessageCog(commands.Cog):
|
||||
"""J.A.R.V.I.S. ModlogMessageCog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message_edit(self, before: discord.Message, after: discord.Message) -> None:
|
||||
"""Process on_message_edit events."""
|
||||
if not before.author.bot:
|
||||
modlog = Setting.objects(guild=after.guild.id, setting="modlog").first()
|
||||
if modlog:
|
||||
if before.content == after.content or before.content is None:
|
||||
return
|
||||
channel = before.guild.get_channel(modlog.value)
|
||||
fields = [
|
||||
Field(
|
||||
"Original Message",
|
||||
before.content if before.content else "N/A",
|
||||
False,
|
||||
),
|
||||
Field(
|
||||
"New Message",
|
||||
after.content if after.content else "N/A",
|
||||
False,
|
||||
),
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Message Edited",
|
||||
description=f"{before.author.mention} edited a message",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
timestamp=after.edited_at,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_author(
|
||||
name=before.author.name,
|
||||
icon_url=before.author.display_avatar.url,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{before.author.name}#{before.author.discriminator} | {before.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message_delete(self, message: discord.Message) -> None:
|
||||
"""Process on_message_delete events."""
|
||||
modlog = Setting.objects(guild=message.guild.id, setting="modlog").first()
|
||||
if modlog:
|
||||
fields = [Field("Original Message", message.content or "N/A", False)]
|
||||
|
||||
if message.attachments:
|
||||
value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments])
|
||||
fields.append(
|
||||
Field(
|
||||
name="Attachments",
|
||||
value=value,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
||||
if message.stickers:
|
||||
value = "\n".join([f"[{x.name}]({x.image_url})" for x in message.stickers])
|
||||
fields.append(
|
||||
Field(
|
||||
name="Stickers",
|
||||
value=value,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
||||
if message.embeds:
|
||||
value = str(len(message.embeds)) + " embeds"
|
||||
fields.append(
|
||||
Field(
|
||||
name="Embeds",
|
||||
value=value,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
||||
channel = message.guild.get_channel(modlog.value)
|
||||
embed = build_embed(
|
||||
title="Message Deleted",
|
||||
description=f"{message.author.mention}'s message was deleted",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
|
||||
embed.set_author(
|
||||
name=message.author.name,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
url=message.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
|
@ -1,47 +0,0 @@
|
|||
"""J.A.R.V.I.S. Modlog Cog Utilities."""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
|
||||
import discord
|
||||
from discord import AuditLogEntry, Member
|
||||
from discord.utils import find
|
||||
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
def modlog_embed(
|
||||
member: discord.Member,
|
||||
admin: discord.Member,
|
||||
log: discord.AuditLogEntry,
|
||||
title: str,
|
||||
desc: str,
|
||||
) -> discord.Embed:
|
||||
"""Get modlog embed."""
|
||||
fields = [
|
||||
Field(
|
||||
name="Moderator",
|
||||
value=f"{admin.mention} ({admin.name}#{admin.discriminator})",
|
||||
),
|
||||
]
|
||||
if log.reason:
|
||||
fields.append(Field(name="Reason", value=log.reason, inline=False))
|
||||
embed = build_embed(
|
||||
title=title,
|
||||
description=desc,
|
||||
color="#fc9e3f",
|
||||
fields=fields,
|
||||
timestamp=log.created_at,
|
||||
)
|
||||
embed.set_author(name=f"{member.name}", icon_url=member.display_avatar.url)
|
||||
embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}")
|
||||
return embed
|
||||
|
||||
|
||||
def get_latest_log(auditlog: List[AuditLogEntry], target: Member) -> AuditLogEntry:
|
||||
"""Filter AuditLog to get latest entry."""
|
||||
before = datetime.utcnow() - timedelta(seconds=10)
|
||||
return find(
|
||||
lambda x: x.target.id == target.id and x.created_at > before,
|
||||
auditlog,
|
||||
)
|
|
@ -1,47 +0,0 @@
|
|||
"""J.A.R.V.I.S. Owner Cog."""
|
||||
from dis_snek import MessageContext, Scale, Snake, message_command
|
||||
from dis_snek.models.discord.user import User
|
||||
from dis_snek.models.snek.checks import is_owner
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.config import reload_config
|
||||
from jarvis.db.models import Config
|
||||
|
||||
|
||||
class OwnerCog(Scale):
|
||||
"""
|
||||
J.A.R.V.I.S. management cog.
|
||||
|
||||
Used by admins to control core J.A.R.V.I.S. systems
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.admins = Config.objects(key="admins").first()
|
||||
|
||||
@message_command(name="addadmin")
|
||||
@check(is_owner())
|
||||
async def _add(self, ctx: MessageContext, user: User) -> None:
|
||||
if user.id in self.admins.value:
|
||||
await ctx.send(f"{user.mention} is already an admin.")
|
||||
return
|
||||
self.admins.value.append(user.id)
|
||||
self.admins.save()
|
||||
reload_config()
|
||||
await ctx.send(f"{user.mention} is now an admin. Use this power carefully.")
|
||||
|
||||
@message_command(name="deladmin")
|
||||
@is_owner()
|
||||
async def _remove(self, ctx: MessageContext, user: User) -> None:
|
||||
if user.id not in self.admins.value:
|
||||
await ctx.send(f"{user.mention} is not an admin.")
|
||||
return
|
||||
self.admins.value.remove(user.id)
|
||||
self.admins.save()
|
||||
reload_config()
|
||||
await ctx.send(f"{user.mention} is no longer an admin.")
|
||||
|
||||
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add OwnerCog to J.A.R.V.I.S."""
|
||||
OwnerCog(bot)
|
|
@ -2,74 +2,81 @@
|
|||
import asyncio
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
from bson import ObjectId
|
||||
from dis_snek import InteractionContext, Snake
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.client.utils.misc_utils import get
|
||||
from dis_snek.models.discord.channel import GuildChannel
|
||||
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||
from dis_snek.models.discord.embed import Embed, EmbedField
|
||||
from dis_snek.models.discord.modal import InputText, Modal, TextStyles
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Reminder
|
||||
|
||||
from jarvis.db.models import Reminder
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
|
||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>:,\u0080-\U000E0FFF]*")
|
||||
time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?", flags=re.IGNORECASE)
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
class RemindmeCog(CacheCog):
|
||||
class RemindmeCog(Scale):
|
||||
"""J.A.R.V.I.S. Remind Me Cog."""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
|
||||
@slash_command(name="remindme", description="Set a reminder")
|
||||
@slash_option(
|
||||
name="message",
|
||||
description="What to remind you of?",
|
||||
name="private",
|
||||
description="Send as DM?",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="weeks",
|
||||
description="Number of weeks?",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
)
|
||||
@slash_option(
|
||||
name="days", description="Number of days?", opt_type=OptionTypes.INTEGER, required=False
|
||||
)
|
||||
@slash_option(
|
||||
name="hours",
|
||||
description="Number of hours?",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
)
|
||||
@slash_option(
|
||||
name="minutes",
|
||||
description="Number of minutes?",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Yes", value="y"),
|
||||
SlashCommandChoice(name="No", value="n"),
|
||||
],
|
||||
)
|
||||
async def _remindme(
|
||||
self,
|
||||
ctx: InteractionContext,
|
||||
message: Optional[str] = None,
|
||||
weeks: Optional[int] = 0,
|
||||
days: Optional[int] = 0,
|
||||
hours: Optional[int] = 0,
|
||||
minutes: Optional[int] = 0,
|
||||
private: str = "n",
|
||||
) -> None:
|
||||
if len(message) > 100:
|
||||
await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True)
|
||||
private = private == "y"
|
||||
modal = Modal(
|
||||
title="Set your reminder!",
|
||||
components=[
|
||||
InputText(
|
||||
label="What to remind you?",
|
||||
placeholder="Reminder",
|
||||
style=TextStyles.PARAGRAPH,
|
||||
custom_id="message",
|
||||
max_length=500,
|
||||
),
|
||||
InputText(
|
||||
label="When to remind you?",
|
||||
placeholder="1h 30m",
|
||||
style=TextStyles.SHORT,
|
||||
custom_id="delay",
|
||||
),
|
||||
],
|
||||
)
|
||||
await ctx.send_modal(modal)
|
||||
try:
|
||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
||||
message = response.responses.get("message")
|
||||
delay = response.responses.get("delay")
|
||||
except asyncio.TimeoutError:
|
||||
return
|
||||
if len(message) > 500:
|
||||
await ctx.send("Reminder cannot be > 500 characters.", ephemeral=True)
|
||||
return
|
||||
elif invites.search(message):
|
||||
await ctx.send(
|
||||
|
@ -81,32 +88,23 @@ class RemindmeCog(CacheCog):
|
|||
await ctx.send("Hey, you should probably make this readable", ephemeral=True)
|
||||
return
|
||||
|
||||
if not any([weeks, days, hours, minutes]):
|
||||
units = {"w": "weeks", "d": "days", "h": "hours", "m": "minutes", "s": "seconds"}
|
||||
delta = {"weeks": 0, "days": 0, "hours": 0, "minutes": 0, "seconds": 0}
|
||||
|
||||
if times := time_pattern.findall(delay):
|
||||
for t in times:
|
||||
delta[units[t[-1]]] += float(t[:-1])
|
||||
else:
|
||||
await ctx.send(
|
||||
"Invalid time string, please follow example: `1w 3d 7h 5m 20s`", ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
if not any(value for value in delta.items()):
|
||||
await ctx.send("At least one time period is required", ephemeral=True)
|
||||
return
|
||||
|
||||
weeks = abs(weeks)
|
||||
days = abs(days)
|
||||
hours = abs(hours)
|
||||
minutes = abs(minutes)
|
||||
|
||||
if weeks and weeks > 4:
|
||||
await ctx.send("Cannot be farther than 4 weeks out!", ephemeral=True)
|
||||
return
|
||||
|
||||
elif days and days > 6:
|
||||
await ctx.send("Use weeks instead of 7+ days, please.", ephemeral=True)
|
||||
return
|
||||
|
||||
elif hours and hours > 23:
|
||||
await ctx.send("Use days instead of 24+ hours, please.", ephemeral=True)
|
||||
return
|
||||
|
||||
elif minutes and minutes > 59:
|
||||
await ctx.send("Use hours instead of 59+ minutes, please.", ephemeral=True)
|
||||
return
|
||||
|
||||
reminders = Reminder.objects(user=ctx.author.id, active=True).count()
|
||||
reminders = len([x async for x in Reminder.find(q(user=ctx.author.id, active=True))])
|
||||
if reminders >= 5:
|
||||
await ctx.send(
|
||||
"You already have 5 (or more) active reminders. "
|
||||
|
@ -115,21 +113,19 @@ class RemindmeCog(CacheCog):
|
|||
)
|
||||
return
|
||||
|
||||
remind_at = datetime.utcnow() + timedelta(
|
||||
weeks=weeks,
|
||||
days=days,
|
||||
hours=hours,
|
||||
minutes=minutes,
|
||||
)
|
||||
remind_at = datetime.now() + timedelta(**delta)
|
||||
|
||||
_ = Reminder(
|
||||
user=ctx.author_id,
|
||||
r = Reminder(
|
||||
user=ctx.author.id,
|
||||
channel=ctx.channel.id,
|
||||
guild=ctx.guild.id,
|
||||
message=message,
|
||||
remind_at=remind_at,
|
||||
private=private,
|
||||
active=True,
|
||||
).save()
|
||||
)
|
||||
|
||||
await r.commit()
|
||||
|
||||
embed = build_embed(
|
||||
title="Reminder Set",
|
||||
|
@ -150,7 +146,7 @@ class RemindmeCog(CacheCog):
|
|||
)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
await response.send(embed=embed, ephemeral=private)
|
||||
|
||||
async def get_reminders_embed(
|
||||
self, ctx: InteractionContext, reminders: List[Reminder]
|
||||
|
@ -158,13 +154,22 @@ class RemindmeCog(CacheCog):
|
|||
"""Build embed for paginator."""
|
||||
fields = []
|
||||
for reminder in reminders:
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value=f"{reminder.message}\n\u200b",
|
||||
inline=False,
|
||||
if reminder.private and isinstance(ctx.channel, GuildChannel):
|
||||
fields.embed(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value="Please DM me this command to view the content of this reminder",
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
else:
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value=f"{reminder.message}\n\u200b",
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
embed = build_embed(
|
||||
title=f"{len(reminders)} Active Reminder(s)",
|
||||
|
@ -190,7 +195,7 @@ class RemindmeCog(CacheCog):
|
|||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
reminders = Reminder.objects(user=ctx.author.id, active=True)
|
||||
reminders = await Reminder.find(q(user=ctx.author.id, active=True))
|
||||
if not reminders:
|
||||
await ctx.send("You have no reminders set.", ephemeral=True)
|
||||
return
|
||||
|
@ -201,7 +206,7 @@ class RemindmeCog(CacheCog):
|
|||
|
||||
@slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder")
|
||||
async def _delete(self, ctx: InteractionContext) -> None:
|
||||
reminders = Reminder.objects(user=ctx.author.id, active=True)
|
||||
reminders = await Reminder.find(q(user=ctx.author.id, active=True))
|
||||
if not reminders:
|
||||
await ctx.send("You have no reminders set", ephemeral=True)
|
||||
return
|
||||
|
@ -237,22 +242,32 @@ class RemindmeCog(CacheCog):
|
|||
messages=message,
|
||||
timeout=60 * 5,
|
||||
)
|
||||
|
||||
fields = []
|
||||
for to_delete in context.context.values:
|
||||
_ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete()
|
||||
reminder = get(reminders, user=ctx.author.id, id=ObjectId(to_delete))
|
||||
if reminder.private and isinstance(ctx.channel, GuildChannel):
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value="Private reminder",
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
else:
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value=reminder.message,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
await reminder.delete()
|
||||
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
fields = []
|
||||
for reminder in filter(lambda x: str(x.id) in context.context.values, reminders):
|
||||
fields.append(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value=reminder.message,
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
embed = build_embed(
|
||||
title="Deleted Reminder(s)",
|
||||
description="",
|
||||
|
@ -260,7 +275,7 @@ class RemindmeCog(CacheCog):
|
|||
)
|
||||
|
||||
embed.set_author(
|
||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||
name=ctx.author.display_name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import asyncio
|
||||
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
from dis_snek.client.utils.misc_utils import get
|
||||
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.role import Role
|
||||
|
@ -12,9 +13,10 @@ from dis_snek.models.snek.application_commands import (
|
|||
)
|
||||
from dis_snek.models.snek.command import check, cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Rolegiver
|
||||
|
||||
from jarvis.db.models import Rolegiver
|
||||
from jarvis.utils import build_embed, get
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
|
@ -30,7 +32,7 @@ class RolegiverCog(Scale):
|
|||
@slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None:
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
setting = await Rolegiver.find_one(q(guild=ctx.guild.id))
|
||||
if setting and role.id in setting.roles:
|
||||
await ctx.send("Role already in rolegiver", ephemeral=True)
|
||||
return
|
||||
|
@ -80,7 +82,7 @@ class RolegiverCog(Scale):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _rolegiver_remove(self, ctx: InteractionContext) -> None:
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
setting = await Rolegiver.find_one(q(guild=ctx.guild.id))
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
@ -162,7 +164,7 @@ class RolegiverCog(Scale):
|
|||
|
||||
@slash_command(name="rolegiver", sub_cmd_name="list", description="List rolegiver roles")
|
||||
async def _rolegiver_list(self, ctx: InteractionContext) -> None:
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
setting = await Rolegiver.find_one(q(guild=ctx.guild.id))
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
@ -198,7 +200,7 @@ class RolegiverCog(Scale):
|
|||
@slash_command(name="role", sub_cmd_name="get", sub_cmd_description="Get a role")
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
||||
async def _role_get(self, ctx: InteractionContext) -> None:
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
setting = await Rolegiver.find_one(q(guild=ctx.guild.id))
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
@ -276,7 +278,7 @@ class RolegiverCog(Scale):
|
|||
async def _role_remove(self, ctx: InteractionContext) -> None:
|
||||
user_roles = ctx.author.roles
|
||||
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
setting = await Rolegiver.find_one(q(guild=ctx.guild.id))
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
@ -355,7 +357,7 @@ class RolegiverCog(Scale):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None:
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
setting = await Rolegiver.find_one(q(guild=ctx.guild.id))
|
||||
if not setting or not setting.roles:
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
guild_role_ids = [r.id for r in ctx.guild.roles]
|
||||
|
|
|
@ -7,6 +7,7 @@ from discord.ext import commands
|
|||
from discord.utils import find
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from jarvis_core.db import q
|
||||
|
||||
from jarvis.db.models import Setting
|
||||
from jarvis.utils import build_embed
|
||||
|
@ -20,9 +21,9 @@ class SettingsCog(commands.Cog):
|
|||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
def update_settings(self, setting: str, value: Any, guild: int) -> bool:
|
||||
async def update_settings(self, setting: str, value: Any, guild: int) -> bool:
|
||||
"""Update a guild setting."""
|
||||
existing = Setting.objects(setting=setting, guild=guild).first()
|
||||
existing = await Setting.find_one(q(setting=setting, guild=guild))
|
||||
if not existing:
|
||||
existing = Setting(setting=setting, guild=guild, value=value)
|
||||
existing.value = value
|
||||
|
@ -30,9 +31,12 @@ class SettingsCog(commands.Cog):
|
|||
|
||||
return updated is not None
|
||||
|
||||
def delete_settings(self, setting: str, guild: int) -> bool:
|
||||
async def delete_settings(self, setting: str, guild: int) -> bool:
|
||||
"""Delete a guild setting."""
|
||||
return Setting.objects(setting=setting, guild=guild).delete()
|
||||
existing = await Setting.find_one(q(setting=setting, guild=guild))
|
||||
if existing:
|
||||
return await existing.delete()
|
||||
return False
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
|
@ -59,24 +63,24 @@ class SettingsCog(commands.Cog):
|
|||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
subcommand_group="set",
|
||||
name="userlog",
|
||||
description="Set userlog channel",
|
||||
name="activitylog",
|
||||
description="Set activitylog channel",
|
||||
choices=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Userlog channel",
|
||||
description="Activitylog channel",
|
||||
opt_type=7,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _set_userlog(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
async def _set_activitylog(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a TextChannel", ephemeral=True)
|
||||
return
|
||||
self.update_settings("userlog", channel.id, ctx.guild.id)
|
||||
await ctx.send(f"Settings applied. New userlog channel is {channel.mention}")
|
||||
self.update_settings("activitylog", channel.id, ctx.guild.id)
|
||||
await ctx.send(f"Settings applied. New activitylog channel is {channel.mention}")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
|
@ -172,12 +176,12 @@ class SettingsCog(commands.Cog):
|
|||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
subcommand_group="unset",
|
||||
name="userlog",
|
||||
description="Unset userlog channel",
|
||||
name="activitylog",
|
||||
description="Unset activitylog channel",
|
||||
)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _unset_userlog(self, ctx: SlashContext) -> None:
|
||||
self.delete_settings("userlog", ctx.guild.id)
|
||||
async def _unset_activitylog(self, ctx: SlashContext) -> None:
|
||||
self.delete_settings("activitylog", ctx.guild.id)
|
||||
await ctx.send("Setting removed.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
|
@ -230,7 +234,7 @@ class SettingsCog(commands.Cog):
|
|||
value = value.mention
|
||||
else:
|
||||
value = "||`[redacted]`||"
|
||||
elif setting.setting in ["userlog", "modlog"]:
|
||||
elif setting.setting in ["activitylog", "modlog"]:
|
||||
value = find(lambda x: x.id == value, ctx.guild.text_channels)
|
||||
if value:
|
||||
value = value.mention
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""J.A.R.V.I.S. Starboard Cog."""
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
from dis_snek.client.utils.misc_utils import find
|
||||
from dis_snek.models.discord.channel import GuildText
|
||||
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||
from dis_snek.models.discord.message import Message
|
||||
|
@ -11,9 +12,10 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Star, Starboard
|
||||
|
||||
from jarvis.db.models import Star, Starboard
|
||||
from jarvis.utils import build_embed, find
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
supported_images = [
|
||||
|
@ -34,7 +36,7 @@ class StarboardCog(Scale):
|
|||
@slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards")
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _list(self, ctx: InteractionContext) -> None:
|
||||
starboards = Starboard.objects(guild=ctx.guild.id)
|
||||
starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None)
|
||||
if starboards != []:
|
||||
message = "Available Starboards:\n"
|
||||
for s in starboards:
|
||||
|
@ -64,21 +66,21 @@ class StarboardCog(Scale):
|
|||
await ctx.send("Channel must be a GuildText", ephemeral=True)
|
||||
return
|
||||
|
||||
exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first()
|
||||
exists = await Starboard.find_one(q(channel=channel.id, guild=ctx.guild.id))
|
||||
if exists:
|
||||
await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True)
|
||||
return
|
||||
|
||||
count = Starboard.objects(guild=ctx.guild.id).count()
|
||||
count = await Starboard.count_documents(q(guild=ctx.guild.id))
|
||||
if count >= 25:
|
||||
await ctx.send("25 starboard limit reached", ephemeral=True)
|
||||
return
|
||||
|
||||
_ = Starboard(
|
||||
await Starboard(
|
||||
guild=ctx.guild.id,
|
||||
channel=channel.id,
|
||||
admin=ctx.author.id,
|
||||
).save()
|
||||
).commit()
|
||||
await ctx.send(f"Starboard created. Check it out at {channel.mention}.")
|
||||
|
||||
@slash_command(
|
||||
|
@ -92,29 +94,13 @@ class StarboardCog(Scale):
|
|||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
deleted = Starboard.objects(channel=channel.id, guild=ctx.guild.id).delete()
|
||||
if deleted:
|
||||
_ = Star.objects(starboard=channel.id).delete()
|
||||
found = await Starboard.find_one(q(channel=channel.id, guild=ctx.guild.id))
|
||||
if found:
|
||||
await found.delete()
|
||||
await ctx.send(f"Starboard deleted from {channel.mention}.")
|
||||
else:
|
||||
await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True)
|
||||
|
||||
@context_menu(name="Star Message", context_type=CommandTypes.MESSAGE)
|
||||
async def _star_message(self, ctx: InteractionContext) -> None:
|
||||
await self._star_add._can_run(ctx)
|
||||
await self._star_add.callback(ctx, message=str(ctx.target_id))
|
||||
|
||||
@slash_command(name="star", sub_cmd_name="add", description="Star a message")
|
||||
@slash_option(
|
||||
name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Channel that has the message, not required if used in same channel",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=False,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _star_add(
|
||||
self,
|
||||
ctx: InteractionContext,
|
||||
|
@ -123,7 +109,7 @@ class StarboardCog(Scale):
|
|||
) -> None:
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
starboards = Starboard.objects(guild=ctx.guild.id)
|
||||
starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None)
|
||||
if not starboards:
|
||||
await ctx.send("No starboards exist.", ephemeral=True)
|
||||
return
|
||||
|
@ -133,7 +119,7 @@ class StarboardCog(Scale):
|
|||
if not isinstance(message, Message):
|
||||
if message.startswith("https://"):
|
||||
message = message.split("/")[-1]
|
||||
message = await channel.get_message(int(message))
|
||||
message = await channel.fetch_message(int(message))
|
||||
|
||||
if not message:
|
||||
await ctx.send("Message not found", ephemeral=True)
|
||||
|
@ -165,12 +151,14 @@ class StarboardCog(Scale):
|
|||
|
||||
starboard = channel_list[int(com_ctx.context.values[0])]
|
||||
|
||||
exists = Star.objects(
|
||||
message=message.id,
|
||||
channel=message.channel.id,
|
||||
guild=message.guild.id,
|
||||
starboard=starboard.id,
|
||||
).first()
|
||||
exists = await Star.find_one(
|
||||
q(
|
||||
message=message.id,
|
||||
channel=channel.id,
|
||||
guild=ctx.guild.id,
|
||||
starboard=starboard.id,
|
||||
)
|
||||
)
|
||||
|
||||
if exists:
|
||||
await ctx.send(
|
||||
|
@ -179,7 +167,7 @@ class StarboardCog(Scale):
|
|||
)
|
||||
return
|
||||
|
||||
count = Star.objects(guild=message.guild.id, starboard=starboard.id).count()
|
||||
count = await Star.count_documents(q(guild=ctx.guild.id, starboard=starboard.id))
|
||||
content = message.content
|
||||
|
||||
attachments = message.attachments
|
||||
|
@ -202,24 +190,24 @@ class StarboardCog(Scale):
|
|||
embed.set_author(
|
||||
name=message.author.display_name,
|
||||
url=message.jump_url,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
icon_url=message.author.avatar.url,
|
||||
)
|
||||
embed.set_footer(text=message.guild.name + " | " + message.channel.name)
|
||||
embed.set_footer(text=ctx.guild.name + " | " + channel.name)
|
||||
if image_url:
|
||||
embed.set_image(url=image_url)
|
||||
|
||||
star = await starboard.send(embed=embed)
|
||||
|
||||
_ = Star(
|
||||
await Star(
|
||||
index=count,
|
||||
message=message.id,
|
||||
channel=message.channel.id,
|
||||
guild=message.guild.id,
|
||||
channel=channel.id,
|
||||
guild=ctx.guild.id,
|
||||
starboard=starboard.id,
|
||||
admin=ctx.author.id,
|
||||
star=star.id,
|
||||
active=True,
|
||||
).save()
|
||||
).commit()
|
||||
|
||||
components[0].components[0].disabled = True
|
||||
|
||||
|
@ -228,6 +216,27 @@ class StarboardCog(Scale):
|
|||
components=components,
|
||||
)
|
||||
|
||||
@context_menu(name="Star Message", context_type=CommandTypes.MESSAGE)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _star_message(self, ctx: InteractionContext) -> None:
|
||||
await self._star_add(ctx, message=str(ctx.target_id))
|
||||
|
||||
@slash_command(name="star", sub_cmd_name="add", description="Star a message")
|
||||
@slash_option(
|
||||
name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Channel that has the message, not required if used in same channel",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=False,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _star_message_slash(
|
||||
self, ctx: InteractionContext, message: str, channel: GuildText
|
||||
) -> None:
|
||||
await self._star_add(ctx, message, channel)
|
||||
|
||||
@slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message")
|
||||
@slash_option(
|
||||
name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True
|
||||
|
@ -248,30 +257,33 @@ class StarboardCog(Scale):
|
|||
if not isinstance(starboard, GuildText):
|
||||
await ctx.send("Channel must be a GuildText channel", ephemeral=True)
|
||||
return
|
||||
exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first()
|
||||
|
||||
exists = await Starboard.find_one(q(channel=starboard.id, guild=ctx.guild.id))
|
||||
if not exists:
|
||||
# TODO: automagically create starboard
|
||||
await ctx.send(
|
||||
f"Starboard does not exist in {starboard.mention}. Please create it first",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
star = Star.objects(
|
||||
starboard=starboard.id,
|
||||
index=id,
|
||||
guild=ctx.guild.id,
|
||||
active=True,
|
||||
).first()
|
||||
star = await Star.find_one(
|
||||
q(
|
||||
starboard=starboard.id,
|
||||
index=id,
|
||||
guild=ctx.guild.id,
|
||||
active=True,
|
||||
)
|
||||
)
|
||||
if not star:
|
||||
await ctx.send(f"No star exists with id {id}", ephemeral=True)
|
||||
return
|
||||
|
||||
message = await starboard.get_message(star.star)
|
||||
message = await starboard.fetch_message(star.star)
|
||||
if message:
|
||||
await message.delete()
|
||||
|
||||
star.active = False
|
||||
star.save()
|
||||
await star.delete()
|
||||
|
||||
await ctx.send(f"Star {id} deleted from {starboard.mention}")
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import asyncio
|
||||
|
||||
import tweepy
|
||||
from bson import ObjectId
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
from dis_snek.client.utils.misc_utils import get
|
||||
from dis_snek.models.discord.channel import GuildText
|
||||
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
|
@ -13,9 +13,10 @@ from dis_snek.models.snek.application_commands import (
|
|||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import TwitterAccount, TwitterFollow
|
||||
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.models import Twitter
|
||||
from jarvis import jconfig
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
|
@ -24,7 +25,7 @@ class TwitterCog(Scale):
|
|||
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
config = get_config()
|
||||
config = jconfig
|
||||
auth = tweepy.AppAuthHandler(
|
||||
config.twitter["consumer_key"], config.twitter["consumer_secret"]
|
||||
)
|
||||
|
@ -67,7 +68,7 @@ class TwitterCog(Scale):
|
|||
return
|
||||
|
||||
try:
|
||||
account = (await asyncio.to_thread(self.api.get_user(screen_name=handle)))[0]
|
||||
account = await asyncio.to_thread(self.api.get_user, screen_name=handle)
|
||||
latest_tweet = (await asyncio.to_thread(self.api.user_timeline, screen_name=handle))[0]
|
||||
except Exception:
|
||||
await ctx.send(
|
||||
|
@ -75,42 +76,54 @@ class TwitterCog(Scale):
|
|||
)
|
||||
return
|
||||
|
||||
count = Twitter.objects(guild=ctx.guild.id).count()
|
||||
count = len([i async for i in TwitterFollow.find(q(guild=ctx.guild.id))])
|
||||
if count >= 12:
|
||||
await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True)
|
||||
return
|
||||
|
||||
exists = Twitter.objects(twitter_id=account.id, guild=ctx.guild.id)
|
||||
exists = await TwitterFollow.find_one(q(twitter_id=account.id, guild=ctx.guild.id))
|
||||
if exists:
|
||||
await ctx.send("Twitter account already being followed in this guild", ephemeral=True)
|
||||
return
|
||||
|
||||
t = Twitter(
|
||||
handle=account.screen_name,
|
||||
ta = await TwitterAccount.find_one(q(twitter_id=account.id))
|
||||
if not ta:
|
||||
ta = TwitterAccount(
|
||||
handle=account.screen_name,
|
||||
twitter_id=account.id,
|
||||
last_tweet=latest_tweet.id,
|
||||
)
|
||||
await ta.commit()
|
||||
|
||||
tf = TwitterFollow(
|
||||
twitter_id=account.id,
|
||||
guild=ctx.guild.id,
|
||||
channel=channel.id,
|
||||
admin=ctx.author.id,
|
||||
last_tweet=latest_tweet.id,
|
||||
retweets=retweets,
|
||||
)
|
||||
|
||||
t.save()
|
||||
await tf.commit()
|
||||
|
||||
await ctx.send(f"Now following `@{handle}` in {channel.mention}")
|
||||
|
||||
@slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts")
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _twitter_unfollow(self, ctx: InteractionContext) -> None:
|
||||
twitters = Twitter.objects(guild=ctx.guild.id)
|
||||
t = TwitterFollow.find(q(guild=ctx.guild.id))
|
||||
twitters = []
|
||||
async for twitter in t:
|
||||
twitters.append(twitter)
|
||||
if not twitters:
|
||||
await ctx.send("You need to follow a Twitter account first", ephemeral=True)
|
||||
return
|
||||
|
||||
options = []
|
||||
handlemap = {str(x.id): x.handle for x in twitters}
|
||||
handlemap = {}
|
||||
for twitter in twitters:
|
||||
option = SelectOption(label=twitter.handle, value=str(twitter.id))
|
||||
account = await TwitterAccount.find_one(q(twitter_id=twitter.twitter_id))
|
||||
handlemap[str(twitter.twitter_id)] = account.handle
|
||||
option = SelectOption(label=account.handle, value=str(twitter.twitter_id))
|
||||
options.append(option)
|
||||
|
||||
select = Select(
|
||||
|
@ -118,7 +131,7 @@ class TwitterCog(Scale):
|
|||
)
|
||||
|
||||
components = [ActionRow(select)]
|
||||
block = "\n".join(x.handle for x in twitters)
|
||||
block = "\n".join(x for x in handlemap.values())
|
||||
message = await ctx.send(
|
||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
||||
"Please choose accounts to unfollow",
|
||||
|
@ -132,7 +145,8 @@ class TwitterCog(Scale):
|
|||
timeout=60 * 5,
|
||||
)
|
||||
for to_delete in context.context.values:
|
||||
_ = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_delete)).delete()
|
||||
follow = get(twitters, guild=ctx.guild.id, twitter_id=int(to_delete))
|
||||
await follow.delete()
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
@ -163,14 +177,20 @@ class TwitterCog(Scale):
|
|||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None:
|
||||
retweets = retweets == "Yes"
|
||||
twitters = Twitter.objects(guild=ctx.guild.id)
|
||||
t = TwitterFollow.find(q(guild=ctx.guild.id))
|
||||
twitters = []
|
||||
async for twitter in t:
|
||||
twitters.append(twitter)
|
||||
if not twitters:
|
||||
await ctx.send("You need to follow a Twitter account first", ephemeral=True)
|
||||
return
|
||||
|
||||
options = []
|
||||
handlemap = {}
|
||||
for twitter in twitters:
|
||||
option = SelectOption(label=twitter.handle, value=str(twitter.id))
|
||||
account = await TwitterAccount.find_one(q(twitter_id=twitter.id))
|
||||
handlemap[str(twitter.twitter_id)] = account.handle
|
||||
option = SelectOption(label=account.handle, value=str(twitter.twitter_id))
|
||||
options.append(option)
|
||||
|
||||
select = Select(
|
||||
|
@ -178,7 +198,7 @@ class TwitterCog(Scale):
|
|||
)
|
||||
|
||||
components = [ActionRow(select)]
|
||||
block = "\n".join(x.handle for x in twitters)
|
||||
block = "\n".join(x for x in handlemap.values())
|
||||
message = await ctx.send(
|
||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
||||
f"Please choose which accounts to {'un' if not retweets else ''}follow retweets from",
|
||||
|
@ -192,11 +212,13 @@ class TwitterCog(Scale):
|
|||
timeout=60 * 5,
|
||||
)
|
||||
|
||||
handlemap = {str(x.id): x.handle for x in twitters}
|
||||
handlemap = {}
|
||||
for to_update in context.context.values:
|
||||
t = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_update)).first()
|
||||
t.retweets = retweets
|
||||
t.save()
|
||||
account = await TwitterAccount.find_one(q(twitter_id=int(to_update)))
|
||||
handlemap[str(twitter.twitter_id)] = account.handle
|
||||
t = get(twitters, guild=ctx.guild.id, twitter_id=int(to_update))
|
||||
t.update(q(retweets=True))
|
||||
await t.commit()
|
||||
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
|
|
|
@ -219,7 +219,7 @@ class UtilCog(Scale):
|
|||
async def _server_info(self, ctx: InteractionContext) -> None:
|
||||
guild: Guild = ctx.guild
|
||||
|
||||
owner = await guild.get_owner()
|
||||
owner = await guild.fetch_owner()
|
||||
|
||||
owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ from dis_snek.models.application_commands import slash_command
|
|||
from dis_snek.models.discord.components import Button, ButtonStyles, spread_to_rows
|
||||
from dis_snek.models.snek.command import cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
|
||||
from jarvis.db.models import Setting
|
||||
from jarvis_core.db import q
|
||||
from jarvis_core.db.models import Setting
|
||||
|
||||
|
||||
def create_layout() -> list:
|
||||
|
@ -39,7 +39,7 @@ class VerifyCog(Scale):
|
|||
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
|
||||
async def _verify(self, ctx: InteractionContext) -> None:
|
||||
await ctx.defer()
|
||||
role = Setting.objects(guild=ctx.guild.id, setting="verified").first()
|
||||
role = await Setting.find_one(q(guild=ctx.guild.id, setting="verified"))
|
||||
if not role:
|
||||
await ctx.send("This guild has not enabled verification", delete_after=5)
|
||||
return
|
||||
|
@ -62,10 +62,10 @@ class VerifyCog(Scale):
|
|||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
setting = Setting.objects(guild=ctx.guild.id, setting="verified").first()
|
||||
setting = await Setting.find_one(guild=ctx.guild.id, setting="verified")
|
||||
role = await ctx.guild.get_role(setting.value)
|
||||
await ctx.author.add_roles(role, reason="Verification passed")
|
||||
setting = Setting.objects(guild=ctx.guild.id, setting="unverified").first()
|
||||
setting = await Setting.find_one(guild=ctx.guild.id, setting="unverified")
|
||||
if setting:
|
||||
role = await ctx.guild.get_role(setting.value)
|
||||
await ctx.author.remove_roles(role, reason="Verification passed")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Load the config for J.A.R.V.I.S."""
|
||||
import os
|
||||
|
||||
from jarvis_core.config import Config as CConfig
|
||||
from pymongo import MongoClient
|
||||
from yaml import load
|
||||
|
||||
|
@ -10,6 +11,19 @@ except ImportError:
|
|||
from yaml import Loader
|
||||
|
||||
|
||||
class JarvisConfig(CConfig):
|
||||
REQUIRED = ["token", "client_id", "mongo", "urls"]
|
||||
OPTIONAL = {
|
||||
"sync": False,
|
||||
"log_level": "WARNING",
|
||||
"scales": None,
|
||||
"events": True,
|
||||
"gitlab_token": None,
|
||||
"max_messages": 1000,
|
||||
"twitter": None,
|
||||
}
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""Config singleton object for J.A.R.V.I.S."""
|
||||
|
||||
|
|
|
@ -1,263 +0,0 @@
|
|||
"""J.A.R.V.I.S. database object for mongoengine."""
|
||||
from datetime import datetime
|
||||
|
||||
from mongoengine import Document
|
||||
from mongoengine.fields import (
|
||||
BooleanField,
|
||||
DateTimeField,
|
||||
DictField,
|
||||
DynamicField,
|
||||
IntField,
|
||||
ListField,
|
||||
LongField,
|
||||
StringField,
|
||||
)
|
||||
|
||||
|
||||
class SnowflakeField(LongField):
|
||||
"""Snowflake LongField Override."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Autopurge(Document):
|
||||
"""Autopurge database object."""
|
||||
|
||||
guild = SnowflakeField(required=True)
|
||||
channel = SnowflakeField(required=True)
|
||||
delay = IntField(min_value=1, max_value=300, default=30)
|
||||
admin = SnowflakeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Autoreact(Document):
|
||||
"""Autoreact database object."""
|
||||
|
||||
guild = SnowflakeField(required=True)
|
||||
channel = SnowflakeField(required=True)
|
||||
reactions = ListField(field=StringField())
|
||||
admin = SnowflakeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Ban(Document):
|
||||
"""Ban database object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
user = SnowflakeField(required=True)
|
||||
username = StringField(required=True)
|
||||
discrim = IntField(min_value=1, max_value=9999, required=True)
|
||||
duration = IntField(min_value=1, max_value=744, required=False)
|
||||
guild = SnowflakeField(required=True)
|
||||
type = StringField(default="perm", max_length=4, required=True)
|
||||
reason = StringField(max_length=100, required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Config(Document):
|
||||
"""Config database object."""
|
||||
|
||||
key = StringField(required=True)
|
||||
value = DynamicField(required=True)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Guess(Document):
|
||||
"""Guess database object."""
|
||||
|
||||
correct = BooleanField(default=False)
|
||||
guess = StringField(max_length=800, required=True)
|
||||
user = SnowflakeField(required=True)
|
||||
|
||||
meta = {"db_alias": "ctc2"}
|
||||
|
||||
|
||||
class Joke(Document):
|
||||
"""Joke database object."""
|
||||
|
||||
rid = StringField()
|
||||
body = StringField()
|
||||
title = StringField()
|
||||
created_utc = DateTimeField()
|
||||
over_18 = BooleanField()
|
||||
score = IntField()
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Kick(Document):
|
||||
"""Kick database object."""
|
||||
|
||||
admin = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
reason = StringField(max_length=100, required=True)
|
||||
user = SnowflakeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Lock(Document):
|
||||
"""Lock database object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
channel = SnowflakeField(required=True)
|
||||
duration = IntField(min_value=1, max_value=300, default=10)
|
||||
guild = SnowflakeField(required=True)
|
||||
reason = StringField(max_length=100, required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Mute(Document):
|
||||
"""Mute database object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
user = SnowflakeField(required=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
duration = IntField(min_value=-1, max_value=300, default=10)
|
||||
guild = SnowflakeField(required=True)
|
||||
reason = StringField(max_length=100, required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Purge(Document):
|
||||
"""Purge database object."""
|
||||
|
||||
admin = SnowflakeField(required=True)
|
||||
channel = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
count = IntField(min_value=1, default=10)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Reminder(Document):
|
||||
"""Reminder database object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
user = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
channel = SnowflakeField(required=True)
|
||||
message = StringField(max_length=100, required=True)
|
||||
remind_at = DateTimeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Rolegiver(Document):
|
||||
"""Rolegiver database object."""
|
||||
|
||||
guild = SnowflakeField(required=True)
|
||||
roles = ListField(field=SnowflakeField())
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Roleping(Document):
|
||||
"""Roleping database object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
role = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
bypass = DictField()
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Setting(Document):
|
||||
"""Setting database object."""
|
||||
|
||||
guild = SnowflakeField(required=True)
|
||||
setting = StringField(required=True)
|
||||
value = DynamicField()
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Star(Document):
|
||||
"""Star database object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
index = IntField(required=True)
|
||||
message = SnowflakeField(required=True)
|
||||
channel = SnowflakeField(required=True)
|
||||
starboard = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
star = SnowflakeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Starboard(Document):
|
||||
"""Starboard database object."""
|
||||
|
||||
channel = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Twitter(Document):
|
||||
"""Twitter Follow object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
twitter_id = IntField(required=True)
|
||||
handle = StringField(required=True)
|
||||
channel = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
last_tweet = SnowflakeField(required=True)
|
||||
retweets = BooleanField(default=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
last_sync = DateTimeField()
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Unban(Document):
|
||||
"""Unban database object."""
|
||||
|
||||
user = SnowflakeField(required=True)
|
||||
username = StringField(required=True)
|
||||
discrim = IntField(min_value=1, max_value=9999, required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
reason = StringField(max_length=100, required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
||||
class Warning(Document):
|
||||
"""Warning database object."""
|
||||
|
||||
active = BooleanField(default=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
user = SnowflakeField(required=True)
|
||||
guild = SnowflakeField(required=True)
|
||||
duration = IntField(min_value=1, max_value=120, default=24)
|
||||
reason = StringField(max_length=100, required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
|
||||
meta = {"db_alias": "main"}
|
|
@ -1,28 +0,0 @@
|
|||
"""J.A.R.V.I.S. Member event handler."""
|
||||
from dis_snek import Snake, listen
|
||||
from dis_snek.models.discord.user import Member
|
||||
|
||||
from jarvis.db.models import Mute, Setting
|
||||
|
||||
|
||||
class MemberEventHandler(object):
|
||||
"""J.A.R.V.I.S. Member event handler."""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.bot.add_listener(self.on_member_join)
|
||||
|
||||
@listen()
|
||||
async def on_member_join(self, user: Member) -> None:
|
||||
"""Handle on_member_join event."""
|
||||
guild = user.guild
|
||||
mute = Mute.objects(guild=guild.id, user=user.id, active=True).first()
|
||||
if mute:
|
||||
mute_role = Setting.objects(guild=guild.id, setting="mute").first()
|
||||
role = guild.get_role(mute_role.value)
|
||||
await user.add_roles(role, reason="User is still muted from prior mute")
|
||||
unverified = Setting.objects(guild=guild.id, setting="unverified").first()
|
||||
if unverified:
|
||||
role = guild.get_role(unverified.value)
|
||||
if role not in user.roles:
|
||||
await user.add_roles(role, reason="User just joined and is unverified")
|
|
@ -1,226 +0,0 @@
|
|||
"""J.A.R.V.I.S. Message event handler."""
|
||||
import re
|
||||
|
||||
from dis_snek import Snake, listen
|
||||
from dis_snek.models.discord.channel import DMChannel
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.message import Message
|
||||
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning
|
||||
from jarvis.utils import build_embed, find
|
||||
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
class MessageEventHandler(object):
|
||||
"""J.A.R.V.I.S. Message event handler."""
|
||||
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.bot.add_listener(self.on_message)
|
||||
self.bot.add_listener(self.on_message_edit)
|
||||
|
||||
async def autopurge(self, message: Message) -> None:
|
||||
"""Handle autopurge events."""
|
||||
autopurge = Autopurge.objects(guild=message.guild.id, channel=message.channel.id).first()
|
||||
if autopurge:
|
||||
await message.delete(delay=autopurge.delay)
|
||||
|
||||
async def autoreact(self, message: Message) -> None:
|
||||
"""Handle autoreact events."""
|
||||
autoreact = Autoreact.objects(
|
||||
guild=message.guild.id,
|
||||
channel=message.channel.id,
|
||||
).first()
|
||||
if autoreact:
|
||||
for reaction in autoreact.reactions:
|
||||
await message.add_reaction(reaction)
|
||||
|
||||
async def checks(self, message: Message) -> None:
|
||||
"""Other message checks."""
|
||||
# #tech
|
||||
channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions)
|
||||
if channel and message.author.id == 293795462752894976:
|
||||
await channel.send(
|
||||
content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501
|
||||
)
|
||||
content = re.sub(r"\s+", "", message.content)
|
||||
match = invites.search(content)
|
||||
setting = Setting.objects(guild=message.guild.id, setting="noinvite").first()
|
||||
if not setting:
|
||||
setting = Setting(guild=message.guild.id, setting="noinvite", value=True)
|
||||
setting.save()
|
||||
if match:
|
||||
guild_invites = await message.guild.invites()
|
||||
allowed = [x.code for x in guild_invites] + [
|
||||
"dbrand",
|
||||
"VtgZntXcnZ",
|
||||
"gPfYGbvTCE",
|
||||
]
|
||||
if match.group(1) not in allowed and setting.value:
|
||||
await message.delete()
|
||||
_ = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Sent an invite link",
|
||||
user=message.author.id,
|
||||
).save()
|
||||
fields = [
|
||||
EmbedField(
|
||||
name="Reason",
|
||||
value="Sent an invite link",
|
||||
inline=False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick if message.author.nick else message.author.name,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
async def massmention(self, message: Message) -> None:
|
||||
"""Handle massmention events."""
|
||||
massmention = Setting.objects(
|
||||
guild=message.guild.id,
|
||||
setting="massmention",
|
||||
).first()
|
||||
if (
|
||||
massmention
|
||||
and massmention.value > 0 # noqa: W503
|
||||
and len(message.mentions) # noqa: W503
|
||||
- (1 if message.author in message.mentions else 0) # noqa: W503
|
||||
> massmention.value # noqa: W503
|
||||
):
|
||||
_ = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Mass Mention",
|
||||
user=message.author.id,
|
||||
).save()
|
||||
fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick if message.author.nick else message.author.name,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
async def roleping(self, message: Message) -> None:
|
||||
"""Handle roleping events."""
|
||||
rolepings = Roleping.objects(guild=message.guild.id, active=True)
|
||||
|
||||
if not rolepings:
|
||||
return
|
||||
|
||||
# Get all role IDs involved with message
|
||||
roles = []
|
||||
for mention in message.role_mentions:
|
||||
roles.append(mention.id)
|
||||
for mention in message.mentions:
|
||||
for role in mention.roles:
|
||||
roles.append(role.id)
|
||||
|
||||
if not roles:
|
||||
return
|
||||
|
||||
# Get all roles that are rolepinged
|
||||
roleping_ids = [r.role for r in rolepings]
|
||||
|
||||
# Get roles in rolepings
|
||||
role_in_rolepings = list(filter(lambda x: x in roleping_ids, roles))
|
||||
|
||||
# Check if the user has the role, so they are allowed to ping it
|
||||
user_missing_role = any(x.id not in roleping_ids for x in message.author.roles)
|
||||
|
||||
# Admins can ping whoever
|
||||
user_is_admin = message.author.guild_permissions.administrator
|
||||
|
||||
# Check if user in a bypass list
|
||||
user_has_bypass = False
|
||||
for roleping in rolepings:
|
||||
if message.author.id in roleping.bypass["users"]:
|
||||
user_has_bypass = True
|
||||
break
|
||||
if any(role.id in roleping.bypass["roles"] for role in message.author.roles):
|
||||
user_has_bypass = True
|
||||
break
|
||||
|
||||
if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass:
|
||||
_ = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Pinged a blocked role/user with a blocked role",
|
||||
user=message.author.id,
|
||||
).save()
|
||||
fields = [
|
||||
EmbedField(
|
||||
name="Reason",
|
||||
value="Pinged a blocked role/user with a blocked role",
|
||||
inline=False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick if message.author.nick else message.author.name,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
@listen()
|
||||
async def on_message(self, message: Message) -> None:
|
||||
"""Handle on_message event. Calls other event handlers."""
|
||||
if not isinstance(message.channel, DMChannel) and not message.author.bot:
|
||||
await self.autoreact(message)
|
||||
await self.massmention(message)
|
||||
await self.roleping(message)
|
||||
await self.autopurge(message)
|
||||
await self.checks(message)
|
||||
|
||||
@listen()
|
||||
async def on_message_edit(self, before: Message, after: Message) -> None:
|
||||
"""Handle on_message_edit event. Calls other event handlers."""
|
||||
if not isinstance(after.channel, DMChannel) and not after.author.bot:
|
||||
await self.massmention(after)
|
||||
await self.roleping(after)
|
||||
await self.checks(after)
|
||||
await self.roleping(after)
|
||||
await self.checks(after)
|
||||
"""Handle on_message_edit event. Calls other event handlers."""
|
||||
if not isinstance(after.channel, DMChannel) and not after.author.bot:
|
||||
await self.massmention(after)
|
||||
await self.roleping(after)
|
||||
await self.checks(after)
|
||||
await self.roleping(after)
|
||||
await self.checks(after)
|
106
jarvis/logo.py
106
jarvis/logo.py
|
@ -1,106 +0,0 @@
|
|||
"""Logos for J.A.R.V.I.S."""
|
||||
|
||||
logo_doom = r"""
|
||||
___ ___ ______ _ _ _____ _____
|
||||
|_ | / _ \ | ___ \ | | | | |_ _| / ___|
|
||||
| | / /_\ \ | |_/ / | | | | | | \ `--.
|
||||
| | | _ | | / | | | | | | `--. \
|
||||
/\__/ / _ | | | | _ | |\ \ _ \ \_/ / _ _| |_ _ /\__/ / _
|
||||
\____/ (_)\_| |_/(_)\_| \_|(_) \___/ (_) \___/ (_)\____/ (_)
|
||||
|
||||
"""
|
||||
|
||||
logo_epic = r"""
|
||||
_________ _______ _______ _________ _______
|
||||
\__ _/ ( ___ ) ( ____ ) |\ /| \__ __/ ( ____ \
|
||||
) ( | ( ) | | ( )| | ) ( | ) ( | ( \/
|
||||
| | | (___) | | (____)| | | | | | | | (_____
|
||||
| | | ___ | | __) ( ( ) ) | | (_____ )
|
||||
| | | ( ) | | (\ ( \ \_/ / | | ) |
|
||||
|\_) ) _ | ) ( | _ | ) \ \__ _ \ / _ ___) (___ _ /\____) | _
|
||||
(____/ (_)|/ \|(_)|/ \__/(_) \_/ (_)\_______/(_)\_______)(_)
|
||||
|
||||
"""
|
||||
|
||||
logo_ivrit = r"""
|
||||
_ _ ____ __ __ ___ ____
|
||||
| | / \ | _ \ \ \ / / |_ _| / ___|
|
||||
_ | | / _ \ | |_) | \ \ / / | | \___ \
|
||||
| |_| | _ / ___ \ _ | _ < _ \ V / _ | | _ ___) | _
|
||||
\___/ (_) /_/ \_\ (_) |_| \_\ (_) \_/ (_) |___| (_) |____/ (_)
|
||||
|
||||
"""
|
||||
|
||||
logo_kban = r"""
|
||||
|
||||
'||' . | . '||''|. . '||' '|' . '||' . .|'''.| .
|
||||
|| ||| || || '|. .' || ||.. '
|
||||
|| | || ||''|' || | || ''|||.
|
||||
|| .''''|. || |. ||| || . '||
|
||||
|| .|' .|. .||. .||. '|' | .||. |'....|'
|
||||
'''
|
||||
|
||||
"""
|
||||
|
||||
logo_larry3d = r"""
|
||||
|
||||
_____ ______ ____ __ __ ______ ____
|
||||
/\___ \ /\ _ \ /\ _`\ /\ \/\ \ /\__ _\ /\ _`\
|
||||
\/__/\ \ \ \ \L\ \ \ \ \L\ \ \ \ \ \ \ \/_/\ \/ \ \,\L\_\
|
||||
_\ \ \ \ \ __ \ \ \ , / \ \ \ \ \ \ \ \ \/_\__ \
|
||||
/\ \_\ \ __ \ \ \/\ \ __ \ \ \\ \ __ \ \ \_/ \ __ \_\ \__ __ /\ \L\ \ __
|
||||
\ \____//\_\ \ \_\ \_\/\_\ \ \_\ \_\/\_\ \ `\___//\_\ /\_____\/\_\ \ `\____\/\_\
|
||||
\/___/ \/_/ \/_/\/_/\/_/ \/_/\/ /\/_/ `\/__/ \/_/ \/_____/\/_/ \/_____/\/_/
|
||||
|
||||
"""
|
||||
|
||||
logo_slane = r"""
|
||||
|
||||
__ ___ ____ _ __ ____ _____
|
||||
/ / / | / __ \ | | / / / _/ / ___/
|
||||
__ / / / /| | / /_/ / | | / / / / \__ \
|
||||
/ /_/ / _ / ___ | _ / _, _/ _ | |/ / _ _/ / _ ___/ / _
|
||||
\____/ (_)/_/ |_|(_)/_/ |_| (_)|___/ (_)/___/ (_)/____/ (_)
|
||||
|
||||
"""
|
||||
|
||||
logo_standard = r"""
|
||||
|
||||
_ _ ____ __ __ ___ ____
|
||||
| | / \ | _ \ \ \ / / |_ _| / ___|
|
||||
_ | | / _ \ | |_) | \ \ / / | | \___ \
|
||||
| |_| | _ / ___ \ _ | _ < _ \ V / _ | | _ ___) | _
|
||||
\___/ (_) /_/ \_\ (_) |_| \_\ (_) \_/ (_) |___| (_) |____/ (_)
|
||||
|
||||
"""
|
||||
|
||||
logo_alligator = r"""
|
||||
|
||||
::::::::::: ::: ::::::::: ::: ::: ::::::::::: ::::::::
|
||||
:+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
|
||||
+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
||||
+#+ +#++:++#++: +#++:++#: +#+ +:+ +#+ +#++:++#++
|
||||
+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+
|
||||
#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+#
|
||||
##### ### ### ### ### ### ### ### ### ### ########### ### ######## ###
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
logo_alligator2 = r"""
|
||||
|
||||
::::::::::: ::: ::::::::: ::: ::: ::::::::::: ::::::::
|
||||
:+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
|
||||
+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
||||
+#+ +#++:++#++: +#++:++#: +#+ +:+ +#+ +#++:++#++
|
||||
+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+
|
||||
#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+#
|
||||
##### ### ### ### ### ### ### ### ### ### ########### ### ######## ###
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def get_logo(lo: str) -> str:
|
||||
"""Get a logo."""
|
||||
if "logo_" not in lo:
|
||||
lo = "logo_" + lo
|
||||
return globals()[lo] if lo in globals() else logo_alligator2
|
|
@ -1,10 +0,0 @@
|
|||
"""J.A.R.V.I.S. background task handlers."""
|
||||
from jarvis.tasks import twitter, unban, unlock, unwarn
|
||||
|
||||
|
||||
def init() -> None:
|
||||
"""Start the background task handlers."""
|
||||
unban.unban.start()
|
||||
unlock.unlock.start()
|
||||
unwarn.unwarn.start()
|
||||
twitter.tweets.start()
|
|
@ -1,45 +0,0 @@
|
|||
"""J.A.R.V.I.S. reminder background task handler."""
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.models import Reminder
|
||||
from jarvis.utils import build_embed
|
||||
|
||||
|
||||
async def _remind() -> None:
|
||||
"""J.A.R.V.I.S. reminder blocking task."""
|
||||
reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))
|
||||
for reminder in reminders:
|
||||
if reminder.remind_at <= datetime.utcnow():
|
||||
user = await jarvis.jarvis.fetch_user(reminder.user)
|
||||
if not user:
|
||||
reminder.delete()
|
||||
continue
|
||||
embed = build_embed(
|
||||
title="You have a reminder",
|
||||
description=reminder.message,
|
||||
fields=[],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.name + "#" + user.discriminator, icon_url=user.display_avatar.url
|
||||
)
|
||||
embed.set_thumbnail(url=user.display_avatar.url)
|
||||
try:
|
||||
await user.send(embed=embed)
|
||||
except Exception:
|
||||
guild = jarvis.jarvis.fetch_guild(reminder.guild)
|
||||
channel = guild.get_channel(reminder.channel) if guild else None
|
||||
if channel:
|
||||
await channel.send(f"{user.mention}", embed=embed)
|
||||
finally:
|
||||
reminder.delete()
|
||||
|
||||
|
||||
@Task.create(trigger=IntervalTrigger(seconds=15))
|
||||
async def remind() -> None:
|
||||
"""J.A.R.V.I.S. reminder background task."""
|
||||
await to_thread(_remind)
|
|
@ -1,70 +0,0 @@
|
|||
"""J.A.R.V.I.S. twitter background task handler."""
|
||||
import logging
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import tweepy
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
import jarvis
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.models import Twitter
|
||||
|
||||
logger = logging.getLogger("jarvis")
|
||||
|
||||
__auth = tweepy.AppAuthHandler(
|
||||
get_config().twitter["consumer_key"], get_config().twitter["consumer_secret"]
|
||||
)
|
||||
__api = tweepy.API(__auth)
|
||||
|
||||
|
||||
async def _tweets() -> None:
|
||||
"""J.A.R.V.I.S. twitter blocking task."""
|
||||
guild_cache = {}
|
||||
channel_cache = {}
|
||||
twitters = Twitter.objects(active=True)
|
||||
for twitter in twitters:
|
||||
try:
|
||||
if not twitter.twitter_id or not twitter.last_sync:
|
||||
user = __api.get_user(screen_name=twitter.handle)
|
||||
twitter.twitter_id = user.id
|
||||
twitter.handle = user.screen_name
|
||||
twitter.last_sync = datetime.now()
|
||||
|
||||
if twitter.last_sync + timedelta(hours=1) <= datetime.now():
|
||||
user = __api.get_user(id=twitter.twitter_id)
|
||||
twitter.handle = user.screen_name
|
||||
twitter.last_sync = datetime.now()
|
||||
|
||||
if tweets := __api.user_timeline(id=twitter.twitter_id):
|
||||
guild_id = twitter.guild
|
||||
channel_id = twitter.channel
|
||||
tweets = sorted(tweets, key=lambda x: x.id)
|
||||
if guild_id not in guild_cache:
|
||||
guild_cache[guild_id] = await jarvis.jarvis.get_guild(guild_id)
|
||||
guild = guild_cache[twitter.guild]
|
||||
if channel_id not in channel_cache:
|
||||
channel_cache[channel_id] = await guild.fetch_channel(channel_id)
|
||||
channel = channel_cache[channel_id]
|
||||
for tweet in tweets:
|
||||
retweet = "retweeted_status" in tweet.__dict__
|
||||
if retweet and not twitter.retweets:
|
||||
continue
|
||||
timestamp = int(tweet.created_at.timestamp())
|
||||
url = f"https://twitter.com/{twitter.handle}/status/{tweet.id}"
|
||||
verb = "re" if retweet else ""
|
||||
await channel.send(
|
||||
f"`@{twitter.handle}` {verb}tweeted this at <t:{timestamp}:f>: {url}"
|
||||
)
|
||||
newest = max(tweets, key=lambda x: x.id)
|
||||
twitter.last_tweet = newest.id
|
||||
twitter.save()
|
||||
except Exception as e:
|
||||
logger.error(f"Error with tweets: {e}")
|
||||
|
||||
|
||||
@Task.create(trigger=IntervalTrigger(minutes=1))
|
||||
async def tweets() -> None:
|
||||
"""J.A.R.V.I.S. twitter background task."""
|
||||
await to_thread(_tweets)
|
|
@ -1,46 +0,0 @@
|
|||
"""J.A.R.V.I.S. unban background task handler."""
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
import jarvis
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.models import Ban, Unban
|
||||
|
||||
jarvis_id = get_config().client_id
|
||||
|
||||
|
||||
async def _unban() -> None:
|
||||
"""J.A.R.V.I.S. unban blocking task."""
|
||||
bans = Ban.objects(type="temp", active=True)
|
||||
unbans = []
|
||||
for ban in bans:
|
||||
if ban.created_at + timedelta(hours=ban.duration) < datetime.utcnow() + timedelta(
|
||||
minutes=10
|
||||
):
|
||||
guild = await jarvis.jarvis.get_guild(ban.guild)
|
||||
user = await jarvis.jarvis.get_user(ban.user)
|
||||
if user:
|
||||
await guild.unban(user=user, reason="Ban expired")
|
||||
ban.active = False
|
||||
ban.save()
|
||||
unbans.append(
|
||||
Unban(
|
||||
user=user.id,
|
||||
guild=guild.id,
|
||||
username=user.name,
|
||||
discrim=user.discriminator,
|
||||
admin=jarvis_id,
|
||||
reason="Ban expired",
|
||||
)
|
||||
)
|
||||
if unbans:
|
||||
Unban.objects().insert(unbans)
|
||||
|
||||
|
||||
@Task.create(IntervalTrigger(minutes=10))
|
||||
async def unban() -> None:
|
||||
"""J.A.R.V.I.S. unban background task."""
|
||||
await to_thread(_unban)
|
|
@ -1,37 +0,0 @@
|
|||
"""J.A.R.V.I.S. unlock background task handler."""
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.models import Lock
|
||||
|
||||
|
||||
async def _unlock() -> None:
|
||||
"""J.A.R.V.I.S. unlock blocking task."""
|
||||
locks = Lock.objects(active=True)
|
||||
# Block execution for now
|
||||
# TODO: Reevaluate with admin/lock[down]
|
||||
if False:
|
||||
for lock in locks:
|
||||
if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow():
|
||||
guild = await jarvis.jarvis.get_guild(lock.guild)
|
||||
channel = await jarvis.jarvis.get_guild(lock.channel)
|
||||
if channel:
|
||||
roles = await guild.fetch_roles()
|
||||
for role in roles:
|
||||
overrides = channel.overwrites_for(role)
|
||||
overrides.send_messages = None
|
||||
await channel.set_permissions(
|
||||
role, overwrite=overrides, reason="Lock expired"
|
||||
)
|
||||
lock.active = False
|
||||
lock.save()
|
||||
|
||||
|
||||
@Task.create(IntervalTrigger(minutes=1))
|
||||
async def unlock() -> None:
|
||||
"""J.A.R.V.I.S. unlock background task."""
|
||||
await to_thread(_unlock)
|
|
@ -1,23 +0,0 @@
|
|||
"""J.A.R.V.I.S. unwarn background task handler."""
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
from jarvis.db.models import Warning
|
||||
|
||||
|
||||
async def _unwarn() -> None:
|
||||
"""J.A.R.V.I.S. unwarn blocking task."""
|
||||
warns = Warning.objects(active=True)
|
||||
for warn in warns:
|
||||
if warn.created_at + timedelta(hours=warn.duration) < datetime.utcnow():
|
||||
warn.active = False
|
||||
warn.save()
|
||||
|
||||
|
||||
@Task.create(IntervalTrigger(hours=1))
|
||||
async def unwarn() -> None:
|
||||
"""J.A.R.V.I.S. unwarn background task."""
|
||||
await to_thread(_unwarn)
|
|
@ -1,18 +1,16 @@
|
|||
"""J.A.R.V.I.S. Utility Functions."""
|
||||
from datetime import datetime
|
||||
from pkgutil import iter_modules
|
||||
from typing import Any, Callable, Iterable, List, Optional, TypeVar
|
||||
|
||||
import git
|
||||
from dis_snek.models.discord.embed import Embed
|
||||
from dis_snek.models.discord.embed import Embed, EmbedField
|
||||
from dis_snek.models.discord.guild import AuditLogEntry
|
||||
from dis_snek.models.discord.user import Member
|
||||
|
||||
import jarvis.cogs
|
||||
import jarvis.db
|
||||
from jarvis.config import get_config
|
||||
|
||||
__all__ = ["field", "db", "cachecog", "permissions"]
|
||||
|
||||
T = TypeVar("T")
|
||||
__all__ = ["cachecog", "permissions"]
|
||||
|
||||
|
||||
def build_embed(
|
||||
|
@ -38,25 +36,32 @@ def build_embed(
|
|||
return embed
|
||||
|
||||
|
||||
def convert_bytesize(b: int) -> str:
|
||||
"""Convert bytes amount to human readable."""
|
||||
b = float(b)
|
||||
sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
|
||||
size = 0
|
||||
while b >= 1024 and size < len(sizes) - 1:
|
||||
b = b / 1024
|
||||
size += 1
|
||||
return "{:0.3f} {}".format(b, sizes[size])
|
||||
|
||||
|
||||
def unconvert_bytesize(size: int, ending: str) -> int:
|
||||
"""Convert human readable to bytes."""
|
||||
ending = ending.upper()
|
||||
sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
|
||||
if ending == "B":
|
||||
return size
|
||||
# Rounding is only because bytes cannot be partial
|
||||
return round(size * (1024 ** sizes.index(ending)))
|
||||
def modlog_embed(
|
||||
member: Member,
|
||||
admin: Member,
|
||||
log: AuditLogEntry,
|
||||
title: str,
|
||||
desc: str,
|
||||
) -> Embed:
|
||||
"""Get modlog embed."""
|
||||
fields = [
|
||||
EmbedField(
|
||||
name="Moderator",
|
||||
value=f"{admin.mention} ({admin.username}#{admin.discriminator})",
|
||||
),
|
||||
]
|
||||
if log.reason:
|
||||
fields.append(EmbedField(name="Reason", value=log.reason, inline=False))
|
||||
embed = build_embed(
|
||||
title=title,
|
||||
description=desc,
|
||||
color="#fc9e3f",
|
||||
fields=fields,
|
||||
timestamp=log.created_at,
|
||||
)
|
||||
embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url)
|
||||
embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}")
|
||||
return embed
|
||||
|
||||
|
||||
def get_extensions(path: str = jarvis.cogs.__path__) -> list:
|
||||
|
@ -85,103 +90,3 @@ def get_repo_hash() -> str:
|
|||
"""J.A.R.V.I.S. current branch hash."""
|
||||
repo = git.Repo(".")
|
||||
return repo.head.object.hexsha
|
||||
|
||||
|
||||
def find(predicate: Callable, sequence: Iterable) -> Optional[Any]:
|
||||
"""
|
||||
Find the first element in a sequence that matches the predicate.
|
||||
|
||||
??? Hint "Example Usage:"
|
||||
```python
|
||||
member = find(lambda m: m.name == "UserName", guild.members)
|
||||
```
|
||||
Args:
|
||||
predicate: A callable that returns a boolean value
|
||||
sequence: A sequence to be searched
|
||||
|
||||
Returns:
|
||||
A match if found, otherwise None
|
||||
|
||||
"""
|
||||
for el in sequence:
|
||||
if predicate(el):
|
||||
return el
|
||||
return None
|
||||
|
||||
|
||||
def find_all(predicate: Callable, sequence: Iterable) -> List[Any]:
|
||||
"""
|
||||
Find all elements in a sequence that match the predicate.
|
||||
|
||||
??? Hint "Example Usage:"
|
||||
```python
|
||||
members = find_all(lambda m: m.name == "UserName", guild.members)
|
||||
```
|
||||
Args:
|
||||
predicate: A callable that returns a boolean value
|
||||
sequence: A sequence to be searched
|
||||
|
||||
Returns:
|
||||
A list of matches
|
||||
|
||||
"""
|
||||
matches = []
|
||||
for el in sequence:
|
||||
if predicate(el):
|
||||
matches.append(el)
|
||||
return matches
|
||||
|
||||
|
||||
def get(sequence: Iterable, **kwargs: Any) -> Optional[Any]:
|
||||
"""
|
||||
Find the first element in a sequence that matches all attrs.
|
||||
|
||||
??? Hint "Example Usage:"
|
||||
```python
|
||||
channel = get(guild.channels, nsfw=False, category="General")
|
||||
```
|
||||
|
||||
Args:
|
||||
sequence: A sequence to be searched
|
||||
kwargs: Keyword arguments to search the sequence for
|
||||
|
||||
Returns:
|
||||
A match if found, otherwise None
|
||||
"""
|
||||
if not kwargs:
|
||||
return sequence[0]
|
||||
|
||||
for el in sequence:
|
||||
if any(not hasattr(el, attr) for attr in kwargs.keys()):
|
||||
continue
|
||||
if all(getattr(el, attr) == value for attr, value in kwargs.items()):
|
||||
return el
|
||||
return None
|
||||
|
||||
|
||||
def get_all(sequence: Iterable, **kwargs: Any) -> List[Any]:
|
||||
"""
|
||||
Find all elements in a sequence that match all attrs.
|
||||
|
||||
??? Hint "Example Usage:"
|
||||
```python
|
||||
channels = get_all(guild.channels, nsfw=False, category="General")
|
||||
```
|
||||
|
||||
Args:
|
||||
sequence: A sequence to be searched
|
||||
kwargs: Keyword arguments to search the sequence for
|
||||
|
||||
Returns:
|
||||
A list of matches
|
||||
"""
|
||||
if not kwargs:
|
||||
return sequence
|
||||
|
||||
matches = []
|
||||
for el in sequence:
|
||||
if any(not hasattr(el, attr) for attr in kwargs.keys()):
|
||||
continue
|
||||
if all(getattr(el, attr) == value for attr, value in kwargs.items()):
|
||||
matches.append(el)
|
||||
return matches
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.client.utils.misc_utils import find
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
from jarvis.utils import find
|
||||
|
||||
|
||||
class CacheCog(Scale):
|
||||
"""Cog wrapper for command caching."""
|
||||
|
|
21
jarvis/utils/embeds.py
Normal file
21
jarvis/utils/embeds.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
"""JARVIS bot-specific embeds."""
|
||||
from dis_snek.models.discord.embed import Embed, EmbedField
|
||||
from dis_snek.models.discord.user import Member
|
||||
from jarvis_core.util import build_embed
|
||||
|
||||
|
||||
def warning_embed(user: Member, reason: str) -> Embed:
|
||||
"""
|
||||
Generate a warning embed.
|
||||
|
||||
Args:
|
||||
user: User to warn
|
||||
reason: Warning reason
|
||||
"""
|
||||
fields = [EmbedField(name="Reason", value=reason, inline=False)]
|
||||
embed = build_embed(
|
||||
title="Warning", description=f"{user.mention} has been warned", fields=fields
|
||||
)
|
||||
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
return embed
|
468
poetry.lock
generated
468
poetry.lock
generated
|
@ -106,7 +106,7 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.0.11"
|
||||
version = "2.0.12"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -117,7 +117,7 @@ unicode_backport = ["unicodedata2"]
|
|||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.3"
|
||||
version = "8.0.4"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -136,7 +136,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|||
|
||||
[[package]]
|
||||
name = "dis-snek"
|
||||
version = "5.0.0"
|
||||
version = "7.0.0"
|
||||
description = "An API wrapper for Discord filled with snakes"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -145,8 +145,20 @@ python-versions = ">=3.10"
|
|||
[package.dependencies]
|
||||
aiohttp = "*"
|
||||
attrs = "*"
|
||||
discord-typings = "*"
|
||||
tomli = "*"
|
||||
|
||||
[[package]]
|
||||
name = "discord-typings"
|
||||
version = "0.3.1"
|
||||
description = "Maintained typings of payloads that Discord sends"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
typing_extensions = ">=4,<5"
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
version = "4.0.1"
|
||||
|
@ -181,7 +193,7 @@ smmap = ">=3.0.1,<6"
|
|||
|
||||
[[package]]
|
||||
name = "gitpython"
|
||||
version = "3.1.26"
|
||||
version = "3.1.27"
|
||||
description = "GitPython is a python library used to interact with Git repositories"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -212,6 +224,28 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
|||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
plugins = ["setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "jarvis-core"
|
||||
version = "0.6.1"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "^3.10"
|
||||
develop = false
|
||||
|
||||
[package.dependencies]
|
||||
dis-snek = "*"
|
||||
motor = "^2.5.1"
|
||||
orjson = "^3.6.6"
|
||||
PyYAML = "^6.0"
|
||||
umongo = "^3.1.0"
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git"
|
||||
reference = "main"
|
||||
resolved_reference = "52a3d568030a79db8ad5ddf65c26216913598bf5"
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.18.1"
|
||||
|
@ -235,6 +269,23 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "marshmallow"
|
||||
version = "3.15.0"
|
||||
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
packaging = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "pytz", "simplejson", "mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)", "tox"]
|
||||
docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"]
|
||||
lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"]
|
||||
tests = ["pytest", "pytz", "simplejson"]
|
||||
|
||||
[[package]]
|
||||
name = "mccabe"
|
||||
version = "0.6.1"
|
||||
|
@ -254,6 +305,20 @@ python-versions = ">=3.6"
|
|||
[package.dependencies]
|
||||
pymongo = ">=3.4,<4.0"
|
||||
|
||||
[[package]]
|
||||
name = "motor"
|
||||
version = "2.5.1"
|
||||
description = "Non-blocking MongoDB driver for Tornado or asyncio"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5.2"
|
||||
|
||||
[package.dependencies]
|
||||
pymongo = ">=3.12,<4"
|
||||
|
||||
[package.extras]
|
||||
encryption = ["pymongo[encryption] (>=3.12,<4)"]
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "6.0.2"
|
||||
|
@ -272,7 +337,7 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.22.1"
|
||||
version = "1.22.3"
|
||||
description = "NumPy is the fundamental package for array computing with Python."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -293,7 +358,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
|
|||
|
||||
[[package]]
|
||||
name = "opencv-python"
|
||||
version = "4.5.5.62"
|
||||
version = "4.5.5.64"
|
||||
description = "Wrapper package for OpenCV python bindings."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -309,12 +374,23 @@ numpy = [
|
|||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.6.6"
|
||||
version = "3.6.7"
|
||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.3"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.3"
|
||||
|
@ -327,6 +403,29 @@ python-versions = ">=3.6"
|
|||
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||
testing = ["docopt", "pytest (<6.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pastypy"
|
||||
version = "1.0.1"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
|
||||
[package.dependencies]
|
||||
aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""}
|
||||
aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""}
|
||||
async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""}
|
||||
attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""}
|
||||
certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""}
|
||||
charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""}
|
||||
frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""}
|
||||
idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""}
|
||||
multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""}
|
||||
pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""}
|
||||
requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""}
|
||||
urllib3 = {version = "1.26.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version < \"4\""}
|
||||
yarl = {version = "1.7.2", markers = "python_version >= \"3.6\""}
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
|
@ -337,7 +436,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
|||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "9.0.0"
|
||||
version = "9.0.1"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -345,7 +444,7 @@ python-versions = ">=3.7"
|
|||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.4.1"
|
||||
version = "2.5.1"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -386,6 +485,14 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.14.1"
|
||||
description = "Cryptographic library for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pydocstyle"
|
||||
version = "6.1.1"
|
||||
|
@ -442,9 +549,20 @@ srv = ["dnspython (>=1.16.0,<1.17.0)"]
|
|||
tls = ["ipaddress"]
|
||||
zstd = ["zstandard"]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.7"
|
||||
description = "Python parsing module"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "python-gitlab"
|
||||
version = "3.1.1"
|
||||
version = "3.2.0"
|
||||
description = "Interact with GitLab API"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -474,18 +592,18 @@ test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"]
|
|||
|
||||
[[package]]
|
||||
name = "python-lsp-server"
|
||||
version = "1.3.3"
|
||||
version = "1.4.0"
|
||||
description = "Python Language Server for the Language Server Protocol"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
autopep8 = {version = ">=1.6.0,<1.7.0", optional = true, markers = "extra == \"all\""}
|
||||
flake8 = {version = ">=4.0.0,<4.1.0", optional = true, markers = "extra == \"all\""}
|
||||
jedi = ">=0.17.2,<0.19.0"
|
||||
mccabe = {version = ">=0.6.0,<0.7.0", optional = true, markers = "extra == \"all\""}
|
||||
pluggy = "*"
|
||||
pluggy = ">=1.0.0"
|
||||
pycodestyle = {version = ">=2.8.0,<2.9.0", optional = true, markers = "extra == \"all\""}
|
||||
pydocstyle = {version = ">=2.0.0", optional = true, markers = "extra == \"all\""}
|
||||
pyflakes = {version = ">=2.4.0,<2.5.0", optional = true, markers = "extra == \"all\""}
|
||||
|
@ -562,7 +680,7 @@ requests = ">=2.0.1,<3.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "rope"
|
||||
version = "0.22.0"
|
||||
version = "0.23.0"
|
||||
description = "a python refactoring library..."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -597,7 +715,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -605,22 +723,31 @@ python-versions = ">=3.7"
|
|||
|
||||
[[package]]
|
||||
name = "tweepy"
|
||||
version = "4.5.0"
|
||||
version = "4.7.0"
|
||||
description = "Twitter library for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
oauthlib = ">=3.2.0,<4"
|
||||
requests = ">=2.27.0,<3"
|
||||
requests-oauthlib = ">=1.0.0,<2"
|
||||
requests-oauthlib = ">=1.2.0,<2"
|
||||
|
||||
[package.extras]
|
||||
async = ["aiohttp (>=3.7.3,<4)", "oauthlib (>=3.1.0,<4)"]
|
||||
async = ["aiohttp (>=3.7.3,<4)"]
|
||||
dev = ["coveralls (>=2.1.0)", "tox (>=3.14.0)"]
|
||||
socks = ["requests[socks] (>=2.27.0,<3)"]
|
||||
test = ["vcrpy (>=1.10.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.1.1"
|
||||
description = "Backported and Experimental Type Hints for Python 3.6+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "ujson"
|
||||
version = "5.1.0"
|
||||
|
@ -637,6 +764,23 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "umongo"
|
||||
version = "3.1.0"
|
||||
description = "sync/async MongoDB ODM, yes."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
marshmallow = ">=3.10.0"
|
||||
pymongo = ">=3.7.0"
|
||||
|
||||
[package.extras]
|
||||
mongomock = ["mongomock"]
|
||||
motor = ["motor (>=2.0,<3.0)"]
|
||||
txmongo = ["txmongo (>=19.2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.8"
|
||||
|
@ -681,7 +825,7 @@ multidict = ">=4.0"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "8a1e6e29ff70363abddad36082a494c4ce1f9cc672fe7aff30b6d5b596d50dac"
|
||||
content-hash = "2adcfd60566d51e43a6f5a3ee0f96140a38e91401800916042cc7cd7e6adb37d"
|
||||
|
||||
[metadata.files]
|
||||
aiohttp = [
|
||||
|
@ -808,20 +952,24 @@ certifi = [
|
|||
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"},
|
||||
{file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"},
|
||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
|
||||
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
|
||||
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
|
||||
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
dis-snek = [
|
||||
{file = "dis-snek-5.0.0.tar.gz", hash = "sha256:cc733b510d6b20523a8067f19b6d9b99804c13e37d78cd6e0fc401098adbca27"},
|
||||
{file = "dis_snek-5.0.0-py3-none-any.whl", hash = "sha256:d1d50ba468ad6b0788e9281eb9d83f6eb2f8d964c1212ccd0e3fb33295462263"},
|
||||
{file = "dis-snek-7.0.0.tar.gz", hash = "sha256:c39d0ff5e1f0cde3a0feefcd05f4a7d6de1d6b1aafbda745bbaa7a63d541af0f"},
|
||||
{file = "dis_snek-7.0.0-py3-none-any.whl", hash = "sha256:5a1fa72d3d5de96a7550a480d33a4f4a6ac8509391fa20890c2eb495fb45d221"},
|
||||
]
|
||||
discord-typings = [
|
||||
{file = "discord-typings-0.3.1.tar.gz", hash = "sha256:854cfb66d34edad49b36d8aaffc93179bb397a97c81caba2da02896e72821a74"},
|
||||
{file = "discord_typings-0.3.1-py3-none-any.whl", hash = "sha256:65890c467751daa025dcef15683c32160f07427baf83380cfdf11d84ceec980a"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
|
||||
|
@ -893,8 +1041,8 @@ gitdb = [
|
|||
{file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
|
||||
]
|
||||
gitpython = [
|
||||
{file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"},
|
||||
{file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"},
|
||||
{file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"},
|
||||
{file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
|
@ -904,6 +1052,7 @@ isort = [
|
|||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
jarvis-core = []
|
||||
jedi = [
|
||||
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
|
||||
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
|
||||
|
@ -947,6 +1096,10 @@ lazy-object-proxy = [
|
|||
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
|
||||
{file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
|
||||
]
|
||||
marshmallow = [
|
||||
{file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"},
|
||||
{file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"},
|
||||
]
|
||||
mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
|
@ -955,6 +1108,10 @@ mongoengine = [
|
|||
{file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"},
|
||||
{file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"},
|
||||
]
|
||||
motor = [
|
||||
{file = "motor-2.5.1-py3-none-any.whl", hash = "sha256:961fdceacaae2c7236c939166f66415be81be8bbb762da528386738de3a0f509"},
|
||||
{file = "motor-2.5.1.tar.gz", hash = "sha256:663473f4498f955d35db7b6f25651cb165514c247136f368b84419cb7635f6b8"},
|
||||
]
|
||||
multidict = [
|
||||
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
|
||||
|
@ -1021,113 +1178,130 @@ mypy-extensions = [
|
|||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
numpy = [
|
||||
{file = "numpy-1.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d62d6b0870b53799204515145935608cdeb4cebb95a26800b6750e48884cc5b"},
|
||||
{file = "numpy-1.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:831f2df87bd3afdfc77829bc94bd997a7c212663889d56518359c827d7113b1f"},
|
||||
{file = "numpy-1.22.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d1563060e77096367952fb44fca595f2b2f477156de389ce7c0ade3aef29e21"},
|
||||
{file = "numpy-1.22.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69958735d5e01f7b38226a6c6e7187d72b7e4d42b6b496aca5860b611ca0c193"},
|
||||
{file = "numpy-1.22.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45a7dfbf9ed8d68fd39763940591db7637cf8817c5bce1a44f7b56c97cbe211e"},
|
||||
{file = "numpy-1.22.1-cp310-cp310-win_amd64.whl", hash = "sha256:7e957ca8112c689b728037cea9c9567c27cf912741fabda9efc2c7d33d29dfa1"},
|
||||
{file = "numpy-1.22.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:800dfeaffb2219d49377da1371d710d7952c9533b57f3d51b15e61c4269a1b5b"},
|
||||
{file = "numpy-1.22.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:65f5e257987601fdfc63f1d02fca4d1c44a2b85b802f03bd6abc2b0b14648dd2"},
|
||||
{file = "numpy-1.22.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:632e062569b0fe05654b15ef0e91a53c0a95d08ffe698b66f6ba0f927ad267c2"},
|
||||
{file = "numpy-1.22.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d245a2bf79188d3f361137608c3cd12ed79076badd743dc660750a9f3074f7c"},
|
||||
{file = "numpy-1.22.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b4018a19d2ad9606ce9089f3d52206a41b23de5dfe8dc947d2ec49ce45d015"},
|
||||
{file = "numpy-1.22.1-cp38-cp38-win32.whl", hash = "sha256:f8ad59e6e341f38266f1549c7c2ec70ea0e3d1effb62a44e5c3dba41c55f0187"},
|
||||
{file = "numpy-1.22.1-cp38-cp38-win_amd64.whl", hash = "sha256:60f19c61b589d44fbbab8ff126640ae712e163299c2dd422bfe4edc7ec51aa9b"},
|
||||
{file = "numpy-1.22.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2db01d9838a497ba2aa9a87515aeaf458f42351d72d4e7f3b8ddbd1eba9479f2"},
|
||||
{file = "numpy-1.22.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bcd19dab43b852b03868796f533b5f5561e6c0e3048415e675bec8d2e9d286c1"},
|
||||
{file = "numpy-1.22.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78bfbdf809fc236490e7e65715bbd98377b122f329457fffde206299e163e7f3"},
|
||||
{file = "numpy-1.22.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c51124df17f012c3b757380782ae46eee85213a3215e51477e559739f57d9bf6"},
|
||||
{file = "numpy-1.22.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d54b7b516f0ca38a69590557814de2dd638d7d4ed04864826acaac5ebb8f01"},
|
||||
{file = "numpy-1.22.1-cp39-cp39-win32.whl", hash = "sha256:b5ec9a5eaf391761c61fd873363ef3560a3614e9b4ead17347e4deda4358bca4"},
|
||||
{file = "numpy-1.22.1-cp39-cp39-win_amd64.whl", hash = "sha256:4ac4d7c9f8ea2a79d721ebfcce81705fc3cd61a10b731354f1049eb8c99521e8"},
|
||||
{file = "numpy-1.22.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e60ef82c358ded965fdd3132b5738eade055f48067ac8a5a8ac75acc00cad31f"},
|
||||
{file = "numpy-1.22.1.zip", hash = "sha256:e348ccf5bc5235fc405ab19d53bec215bb373300e5523c7b476cc0da8a5e9973"},
|
||||
{file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"},
|
||||
{file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"},
|
||||
{file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e"},
|
||||
{file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4"},
|
||||
{file = "numpy-1.22.3-cp310-cp310-win32.whl", hash = "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430"},
|
||||
{file = "numpy-1.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4"},
|
||||
{file = "numpy-1.22.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce"},
|
||||
{file = "numpy-1.22.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe"},
|
||||
{file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5"},
|
||||
{file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1"},
|
||||
{file = "numpy-1.22.3-cp38-cp38-win32.whl", hash = "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62"},
|
||||
{file = "numpy-1.22.3-cp38-cp38-win_amd64.whl", hash = "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676"},
|
||||
{file = "numpy-1.22.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123"},
|
||||
{file = "numpy-1.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802"},
|
||||
{file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d"},
|
||||
{file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168"},
|
||||
{file = "numpy-1.22.3-cp39-cp39-win32.whl", hash = "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"},
|
||||
{file = "numpy-1.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a"},
|
||||
{file = "numpy-1.22.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f"},
|
||||
{file = "numpy-1.22.3.zip", hash = "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18"},
|
||||
]
|
||||
oauthlib = [
|
||||
{file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"},
|
||||
{file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"},
|
||||
]
|
||||
opencv-python = [
|
||||
{file = "opencv-python-4.5.5.62.tar.gz", hash = "sha256:3efe232b32d5e1327e7c82bc6d61230737821c5190ce5c783e64a1bc8d514e18"},
|
||||
{file = "opencv_python-4.5.5.62-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:2601388def0d6b957cc30dd88f8ff74a5651ae6940dd9e488241608cfa2b15c7"},
|
||||
{file = "opencv_python-4.5.5.62-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71fdc49df412b102d97f14927321309043c79c4a3582cce1dc803370ff9c39c0"},
|
||||
{file = "opencv_python-4.5.5.62-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:130cc75d56b29aa3c5de8b6ac438242dd2574ba6eaa8bccdffdcfd6b78632f7f"},
|
||||
{file = "opencv_python-4.5.5.62-cp36-abi3-win32.whl", hash = "sha256:3a75c7ad45b032eea0c72e389aac6dd435f5c87e87f60237095c083400bc23aa"},
|
||||
{file = "opencv_python-4.5.5.62-cp36-abi3-win_amd64.whl", hash = "sha256:c463d2276d8662b972d20ca9644702188507de200ca5405b89e1fe71c5c99989"},
|
||||
{file = "opencv_python-4.5.5.62-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:ac92e743e22681f30001942d78512c1e39bce53dbffc504e5645fdc45c0f2c47"},
|
||||
{file = "opencv-python-4.5.5.64.tar.gz", hash = "sha256:f65de0446a330c3b773cd04ba10345d8ce1b15dcac3f49770204e37602d0b3f7"},
|
||||
{file = "opencv_python-4.5.5.64-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a512a0c59b6fec0fac3844b2f47d6ecb1a9d18d235e6c5491ce8dbbe0663eae8"},
|
||||
{file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6138b6903910e384067d001763d40f97656875487381aed32993b076f44375"},
|
||||
{file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b293ced62f4360d9f11cf72ae7e9df95320ff7bf5b834d87546f844e838c0c35"},
|
||||
{file = "opencv_python-4.5.5.64-cp36-abi3-win32.whl", hash = "sha256:6247e584813c00c3b9ed69a795da40d2c153dc923d0182e957e1c2f00a554ac2"},
|
||||
{file = "opencv_python-4.5.5.64-cp36-abi3-win_amd64.whl", hash = "sha256:408d5332550287aa797fd06bef47b2dfed163c6787668cc82ef9123a9484b56a"},
|
||||
{file = "opencv_python-4.5.5.64-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7787bb017ae93d5f9bb1b817ac8e13e45dd193743cb648498fcab21d00cf20a3"},
|
||||
]
|
||||
orjson = [
|
||||
{file = "orjson-3.6.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:e4a7cad6c63306318453980d302c7c0b74c0cc290dd1f433bbd7d31a5af90cf1"},
|
||||
{file = "orjson-3.6.6-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e533941dca4a0530a876de32e54bf2fd3269cdec3751aebde7bfb5b5eba98e74"},
|
||||
{file = "orjson-3.6.6-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:9adf63be386eaa34278967512b83ff8fc4bed036a246391ae236f68d23c47452"},
|
||||
{file = "orjson-3.6.6-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:3b636753ae34d4619b11ea7d664a2f1e87e55e9738e5123e12bcce22acae9d13"},
|
||||
{file = "orjson-3.6.6-cp310-none-win_amd64.whl", hash = "sha256:78a10295ed048fd916c6584d6d27c232eae805a43e7c14be56e3745f784f0eb6"},
|
||||
{file = "orjson-3.6.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:82b4f9fb2af7799b52932a62eac484083f930d5519560d6f64b24d66a368d03f"},
|
||||
{file = "orjson-3.6.6-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a0033d07309cc7d8b8c4bc5d42f0dd4422b53ceb91dee9f4086bb2afa70b7772"},
|
||||
{file = "orjson-3.6.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b321f99473116ab7c7c028377372f7b4adba4029aaca19cd567e83898f55579"},
|
||||
{file = "orjson-3.6.6-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:b9c98ed94f1688cc11b5c61b8eea39d854a1a2f09f71d8a5af005461b14994ed"},
|
||||
{file = "orjson-3.6.6-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:00b333a41392bd07a8603c42670547dbedf9b291485d773f90c6470eff435608"},
|
||||
{file = "orjson-3.6.6-cp37-none-win_amd64.whl", hash = "sha256:8d4fd3bdee65a81f2b79c50937d4b3c054e1e6bfa3fc72ed018a97c0c7c3d521"},
|
||||
{file = "orjson-3.6.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:954c9f8547247cd7a8c91094ff39c9fe314b5eaeaec90b7bfb7384a4108f416f"},
|
||||
{file = "orjson-3.6.6-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:74e5aed657ed0b91ef05d44d6a26d3e3e12ce4d2d71f75df41a477b05878c4a9"},
|
||||
{file = "orjson-3.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4008a5130e6e9c33abaa95e939e0e755175da10745740aa6968461b2f16830e2"},
|
||||
{file = "orjson-3.6.6-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:012761d5f3d186deb4f6238f15e9ea7c1aac6deebc8f5b741ba3b4fafe017460"},
|
||||
{file = "orjson-3.6.6-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:b464546718a940b48d095a98df4c04808bfa6c8706fe751fc3f9390bc2f82643"},
|
||||
{file = "orjson-3.6.6-cp38-none-win_amd64.whl", hash = "sha256:f10a800f4e5a4aab52076d4628e9e4dab9370bdd9d8ea254ebfde846b653ab25"},
|
||||
{file = "orjson-3.6.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8010d2610cfab721725ef14d578c7071e946bbdae63322d8f7b49061cf3fde8d"},
|
||||
{file = "orjson-3.6.6-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8dca67a4855e1e0f9a2ea0386e8db892708522e1171dc0ddf456932288fbae63"},
|
||||
{file = "orjson-3.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af065d60523139b99bd35b839c7a2d8c5da55df8a8c4402d2eb6cdc07fa7a624"},
|
||||
{file = "orjson-3.6.6-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:fa1f389cc9f766ae0cf7ba3533d5089836b01a5ccb3f8d904297f1fcf3d9dc34"},
|
||||
{file = "orjson-3.6.6-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:ec1221ad78f94d27b162a1d35672b62ef86f27f0e4c2b65051edb480cc86b286"},
|
||||
{file = "orjson-3.6.6-cp39-none-win_amd64.whl", hash = "sha256:afed2af55eeda1de6b3f1cbc93431981b19d380fcc04f6ed86e74c1913070304"},
|
||||
{file = "orjson-3.6.6.tar.gz", hash = "sha256:55dd988400fa7fbe0e31407c683f5aaab013b5bd967167b8fe058186773c4d6c"},
|
||||
{file = "orjson-3.6.7-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:93188a9d6eb566419ad48befa202dfe7cd7a161756444b99c4ec77faea9352a4"},
|
||||
{file = "orjson-3.6.7-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:82515226ecb77689a029061552b5df1802b75d861780c401e96ca6bc8495f775"},
|
||||
{file = "orjson-3.6.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3af57ffab7848aaec6ba6b9e9b41331250b57bf696f9d502bacdc71a0ebab0ba"},
|
||||
{file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:a7297504d1142e7efa236ffc53f056d73934a993a08646dbcee89fc4308a8fcf"},
|
||||
{file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:5a50cde0dbbde255ce751fd1bca39d00ecd878ba0903c0480961b31984f2fab7"},
|
||||
{file = "orjson-3.6.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d21f9a2d1c30e58070f93988db4cad154b9009fafbde238b52c1c760e3607fbe"},
|
||||
{file = "orjson-3.6.7-cp310-none-win_amd64.whl", hash = "sha256:e152464c4606b49398afd911777decebcf9749cc8810c5b4199039e1afb0991e"},
|
||||
{file = "orjson-3.6.7-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:0a65f3c403f38b0117c6dd8e76e85a7bd51fcd92f06c5598dfeddbc44697d3e5"},
|
||||
{file = "orjson-3.6.7-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c47cfca18e41f7f37b08ff3e7abf5ada2d0f27b5ade934f05be5fc5bb956e9d"},
|
||||
{file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63185af814c243fad7a72441e5f98120c9ecddf2675befa486d669fb65539e9b"},
|
||||
{file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2da6fde42182b80b40df2e6ab855c55090ebfa3fcc21c182b7ad1762b61d55c"},
|
||||
{file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:48c5831ec388b4e2682d4ff56d6bfa4a2ef76c963f5e75f4ff4785f9cf338a80"},
|
||||
{file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:913fac5d594ccabf5e8fbac15b9b3bb9c576d537d49eeec9f664e7a64dde4c4b"},
|
||||
{file = "orjson-3.6.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:58f244775f20476e5851e7546df109f75160a5178d44257d437ba6d7e562bfe8"},
|
||||
{file = "orjson-3.6.7-cp37-none-win_amd64.whl", hash = "sha256:2d5f45c6b85e5f14646df2d32ecd7ff20fcccc71c0ea1155f4d3df8c5299bbb7"},
|
||||
{file = "orjson-3.6.7-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:612d242493afeeb2068bc72ff2544aa3b1e627578fcf92edee9daebb5893ffea"},
|
||||
{file = "orjson-3.6.7-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:539cdc5067db38db27985e257772d073cd2eb9462d0a41bde96da4e4e60bd99b"},
|
||||
{file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d103b721bbc4f5703f62b3882e638c0b65fcdd48622531c7ffd45047ef8e87c"},
|
||||
{file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb10a20f80e95102dd35dfbc3a22531661b44a09b55236b012a446955846b023"},
|
||||
{file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:bb68d0da349cf8a68971a48ad179434f75256159fe8b0715275d9b49fa23b7a3"},
|
||||
{file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:4a2c7d0a236aaeab7f69c17b7ab4c078874e817da1bfbb9827cb8c73058b3050"},
|
||||
{file = "orjson-3.6.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3be045ca3b96119f592904cf34b962969ce97bd7843cbfca084009f6c8d2f268"},
|
||||
{file = "orjson-3.6.7-cp38-none-win_amd64.whl", hash = "sha256:bd765c06c359d8a814b90f948538f957fa8a1f55ad1aaffcdc5771996aaea061"},
|
||||
{file = "orjson-3.6.7-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7dd9e1e46c0776eee9e0649e3ae9584ea368d96851bcaeba18e217fa5d755283"},
|
||||
{file = "orjson-3.6.7-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:c4b4f20a1e3df7e7c83717aff0ef4ab69e42ce2fb1f5234682f618153c458406"},
|
||||
{file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7107a5673fd0b05adbb58bf71c1578fc84d662d29c096eb6d998982c8635c221"},
|
||||
{file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08b6940dd9a98ccf09785890112a0f81eadb4f35b51b9a80736d1725437e22c"},
|
||||
{file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:f5d1648e5a9d1070f3628a69a7c6c17634dbb0caf22f2085eca6910f7427bf1f"},
|
||||
{file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:e6201494e8dff2ce7fd21da4e3f6dfca1a3fed38f9dcefc972f552f6596a7621"},
|
||||
{file = "orjson-3.6.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:70d0386abe02879ebaead2f9632dd2acb71000b4721fd8c1a2fb8c031a38d4d5"},
|
||||
{file = "orjson-3.6.7-cp39-none-win_amd64.whl", hash = "sha256:d9a3288861bfd26f3511fb4081561ca768674612bac59513cb9081bb61fcc87f"},
|
||||
{file = "orjson-3.6.7.tar.gz", hash = "sha256:a4bb62b11289b7620eead2f25695212e9ac77fcfba76f050fa8a540fb5c32401"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
parso = [
|
||||
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
|
||||
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
|
||||
]
|
||||
pastypy = [
|
||||
{file = "pastypy-1.0.1-py3-none-any.whl", hash = "sha256:63cc664568f86f6ddeb7e5687422bbf4b338d067ea887ed240223c8cbcf6fd2d"},
|
||||
{file = "pastypy-1.0.1.tar.gz", hash = "sha256:0393d1635b5031170eae3efaf376b14c3a4af7737c778d7ba7d56f2bd25bf5b1"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
pillow = [
|
||||
{file = "Pillow-9.0.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-win32.whl", hash = "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-win32.whl", hash = "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-win32.whl", hash = "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-win32.whl", hash = "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553"},
|
||||
{file = "Pillow-9.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb"},
|
||||
{file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315"},
|
||||
{file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d"},
|
||||
{file = "Pillow-9.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05"},
|
||||
{file = "Pillow-9.0.0.tar.gz", hash = "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e"},
|
||||
{file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"},
|
||||
{file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"},
|
||||
{file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"},
|
||||
{file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"},
|
||||
{file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"},
|
||||
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"},
|
||||
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"},
|
||||
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"},
|
||||
{file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"},
|
||||
{file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"},
|
||||
{file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"},
|
||||
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"},
|
||||
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"},
|
||||
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"},
|
||||
{file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"},
|
||||
{file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"},
|
||||
{file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"},
|
||||
{file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"},
|
||||
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"},
|
||||
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"},
|
||||
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"},
|
||||
{file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"},
|
||||
{file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"},
|
||||
{file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"},
|
||||
{file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"},
|
||||
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"},
|
||||
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"},
|
||||
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"},
|
||||
{file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"},
|
||||
{file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"},
|
||||
{file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"},
|
||||
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"},
|
||||
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"},
|
||||
{file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"},
|
||||
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
|
||||
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
|
||||
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
|
||||
{file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
|
@ -1171,6 +1345,38 @@ pycodestyle = [
|
|||
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
|
||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||
]
|
||||
pycryptodome = [
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:75a3a364fee153e77ed889c957f6f94ec6d234b82e7195b117180dcc9fc16f96"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:aae395f79fa549fb1f6e3dc85cf277f0351e15a22e6547250056c7f0c990d6a5"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f403a3e297a59d94121cb3ee4b1cf41f844332940a62d71f9e4a009cc3533493"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ce7a875694cd6ccd8682017a7c06c6483600f151d8916f2b25cf7a439e600263"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a36ab51674b014ba03da7f98b675fcb8eabd709a2d8e18219f784aba2db73b72"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:50a5346af703330944bea503106cd50c9c2212174cfcb9939db4deb5305a8367"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-win32.whl", hash = "sha256:36e3242c4792e54ed906c53f5d840712793dc68b726ec6baefd8d978c5282d30"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c880a98376939165b7dc504559f60abe234b99e294523a273847f9e7756f4132"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dcd65355acba9a1d0fc9b923875da35ed50506e339b35436277703d7ace3e222"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:766a8e9832128c70012e0c2b263049506cbf334fb21ff7224e2704102b6ef59e"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2562de213960693b6d657098505fd4493c45f3429304da67efcbeb61f0edfe89"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d1b7739b68a032ad14c5e51f7e4e1a5f92f3628bba024a2bda1f30c481fc85d8"},
|
||||
{file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:27e92c1293afcb8d2639baf7eb43f4baada86e4de0f1fb22312bfc989b95dae2"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f2772af1c3ef8025c85335f8b828d0193fa1e43256621f613280e2c81bfad423"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:9ec761a35dbac4a99dcbc5cd557e6e57432ddf3e17af8c3c86b44af9da0189c0"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e64738207a02a83590df35f59d708bf1e7ea0d6adce712a777be2967e5f7043c"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:e24d4ec4b029611359566c52f31af45c5aecde7ef90bf8f31620fd44c438efe7"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:8b5c28058102e2974b9868d72ae5144128485d466ba8739abd674b77971454cc"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:924b6aad5386fb54f2645f22658cb0398b1f25bc1e714a6d1522c75d527deaa5"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-win32.whl", hash = "sha256:53dedbd2a6a0b02924718b520a723e88bcf22e37076191eb9b91b79934fb2192"},
|
||||
{file = "pycryptodome-3.14.1-cp35-abi3-win_amd64.whl", hash = "sha256:ea56a35fd0d13121417d39a83f291017551fa2c62d6daa6b04af6ece7ed30d84"},
|
||||
{file = "pycryptodome-3.14.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:028dcbf62d128b4335b61c9fbb7dd8c376594db607ef36d5721ee659719935d5"},
|
||||
{file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:69f05aaa90c99ac2f2af72d8d7f185f729721ad7c4be89e9e3d0ab101b0ee875"},
|
||||
{file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:12ef157eb1e01a157ca43eda275fa68f8db0dd2792bc4fe00479ab8f0e6ae075"},
|
||||
{file = "pycryptodome-3.14.1-pp27-pypy_73-win32.whl", hash = "sha256:f572a3ff7b6029dd9b904d6be4e0ce9e309dcb847b03e3ac8698d9d23bb36525"},
|
||||
{file = "pycryptodome-3.14.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9924248d6920b59c260adcae3ee231cd5af404ac706ad30aa4cd87051bf09c50"},
|
||||
{file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e0c04c41e9ade19fbc0eff6aacea40b831bfcb2c91c266137bcdfd0d7b2f33ba"},
|
||||
{file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:893f32210de74b9f8ac869ed66c97d04e7d351182d6d39ebd3b36d3db8bda65d"},
|
||||
{file = "pycryptodome-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0"},
|
||||
{file = "pycryptodome-3.14.1.tar.gz", hash = "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b"},
|
||||
]
|
||||
pydocstyle = [
|
||||
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
|
||||
{file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"},
|
||||
|
@ -1292,17 +1498,21 @@ pymongo = [
|
|||
{file = "pymongo-3.12.3-py2.7-macosx-10.14-intel.egg", hash = "sha256:d81299f63dc33cc172c26faf59cc54dd795fc6dd5821a7676cca112a5ee8bbd6"},
|
||||
{file = "pymongo-3.12.3.tar.gz", hash = "sha256:0a89cadc0062a5e53664dde043f6c097172b8c1c5f0094490095282ff9995a5f"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
||||
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
|
||||
]
|
||||
python-gitlab = [
|
||||
{file = "python-gitlab-3.1.1.tar.gz", hash = "sha256:cad1338c1ff1a791a7bae7995dc621e26c77dfbf225bade456acec7512782825"},
|
||||
{file = "python_gitlab-3.1.1-py3-none-any.whl", hash = "sha256:2a7de39c8976db6d0db20031e71b3e43d262e99e64b471ef09bf00482cd3d9fa"},
|
||||
{file = "python-gitlab-3.2.0.tar.gz", hash = "sha256:8f6ee81109fec231fc2b74e2c4035bb7de0548eaf82dd119fe294df2c4a524be"},
|
||||
{file = "python_gitlab-3.2.0-py3-none-any.whl", hash = "sha256:48f72e033c06ab1c244266af85de2cb0a175f8a3614417567e2b14254ead9b2e"},
|
||||
]
|
||||
python-lsp-jsonrpc = [
|
||||
{file = "python-lsp-jsonrpc-1.0.0.tar.gz", hash = "sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd"},
|
||||
{file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"},
|
||||
]
|
||||
python-lsp-server = [
|
||||
{file = "python-lsp-server-1.3.3.tar.gz", hash = "sha256:1b48ccd8b70103522e8a8b9cb9ae1be2b27a5db0dfd661e7e44e6253ebefdc40"},
|
||||
{file = "python_lsp_server-1.3.3-py3-none-any.whl", hash = "sha256:ea7b1e623591ccbfbbf8e5dfe0df8119f27863125a58bdcacbacd1937d8e8cb3"},
|
||||
{file = "python-lsp-server-1.4.0.tar.gz", hash = "sha256:769142c07573f6b66e930cbd7c588b826082550bef6267bb0aec63e7b6260009"},
|
||||
{file = "python_lsp_server-1.4.0-py3-none-any.whl", hash = "sha256:3160c97c6d1edd8456f262fc0e4aad6b322c0cfd1b58d322a41679e57787594d"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
|
@ -1352,8 +1562,8 @@ requests-toolbelt = [
|
|||
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
|
||||
]
|
||||
rope = [
|
||||
{file = "rope-0.22.0-py3-none-any.whl", hash = "sha256:2847220bf72ead09b5abe72b1edc9cacff90ab93663ece06913fc97324167870"},
|
||||
{file = "rope-0.22.0.tar.gz", hash = "sha256:b00fbc064a26fc62d7220578a27fd639b2fad57213663cc396c137e92d73f10f"},
|
||||
{file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"},
|
||||
{file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"},
|
||||
]
|
||||
smmap = [
|
||||
{file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
|
||||
|
@ -1368,12 +1578,16 @@ toml = [
|
|||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"},
|
||||
{file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"},
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
tweepy = [
|
||||
{file = "tweepy-4.5.0-py2.py3-none-any.whl", hash = "sha256:1efe228d5994e0d996577bd052b73c59dada96ff8045e176bf46c175afe61859"},
|
||||
{file = "tweepy-4.5.0.tar.gz", hash = "sha256:12cc4b0a3d7b745928b08c3eb55a992236895e00028584d11fa41258f07df1b9"},
|
||||
{file = "tweepy-4.7.0-py2.py3-none-any.whl", hash = "sha256:d7e78c49232e849b660d82c7c2e504e8487d8014dcb73b39b490b61827965aba"},
|
||||
{file = "tweepy-4.7.0.tar.gz", hash = "sha256:82323505d549e3868e14a4570fc069ab3058ef95f9e578d1476d69bf2c831483"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
||||
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
||||
]
|
||||
ujson = [
|
||||
{file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"},
|
||||
|
@ -1431,6 +1645,10 @@ ulid-py = [
|
|||
{file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"},
|
||||
{file = "ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"},
|
||||
]
|
||||
umongo = [
|
||||
{file = "umongo-3.1.0-py2.py3-none-any.whl", hash = "sha256:f6913027651ae673d71aaf54285f9ebf1e49a3f57662e526d029ba72e1a3fcd5"},
|
||||
{file = "umongo-3.1.0.tar.gz", hash = "sha256:20c72f09edae931285c22c1928862af35b90ec639a4dac2dbf015aaaac00e931"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
|
||||
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[tool.poetry]
|
||||
name = "jarvis"
|
||||
version = "2.0.0a0"
|
||||
version = "2.0.0b1"
|
||||
description = "J.A.R.V.I.S. admin bot"
|
||||
authors = ["Zevaryx <zevaryx@gmail.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
PyYAML = "^6.0"
|
||||
dis-snek = "^5.0.0"
|
||||
dis-snek = "*"
|
||||
GitPython = "^3.1.26"
|
||||
mongoengine = "^0.23.1"
|
||||
opencv-python = "^4.5.5"
|
||||
|
@ -17,6 +17,9 @@ python-gitlab = "^3.1.1"
|
|||
ulid-py = "^1.1.0"
|
||||
tweepy = "^4.5.0"
|
||||
orjson = "^3.6.6"
|
||||
jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "main"}
|
||||
aiohttp = "^3.8.1"
|
||||
pastypy = "^1.0.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
python-lsp-server = {extras = ["all"], version = "^1.3.3"}
|
||||
|
|
4
run.py
4
run.py
|
@ -1,5 +1,7 @@
|
|||
"""Main run file for J.A.R.V.I.S."""
|
||||
import asyncio
|
||||
|
||||
from jarvis import run
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
asyncio.run(run())
|
||||
|
|
Loading…
Add table
Reference in a new issue