This commit is contained in:
Zeva Rose 2022-08-12 17:12:12 +00:00
commit c806f14935
13 changed files with 575 additions and 66 deletions

3
.gitignore vendored
View file

@ -145,3 +145,6 @@ config.yaml
# VSCode
.vscode/
# Custom NAFF versions
naff/

View file

@ -19,6 +19,7 @@ __version__ = const.__version__
async def run() -> None:
"""Run JARVIS"""
# Configure logger
jconfig = JarvisConfig.from_yaml()
logger = get_logger("jarvis", show_locals=jconfig.log_level == "DEBUG")
logger.setLevel(jconfig.log_level)
@ -28,6 +29,7 @@ async def run() -> None:
)
logger.addHandler(file_handler)
# Configure client
intents = (
Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT
)
@ -42,18 +44,23 @@ async def run() -> None:
delete_unused_application_cmds=True,
send_command_tracebacks=False,
redis=redis,
logger=logger,
)
# External modules
if jconfig.log_level == "DEBUG":
jurigged.watch(pattern="jarvis/*.py")
if jconfig.rook_token:
rook.start(token=jconfig.rook_token, labels={"env": "dev"})
# Initialize bot
logger.info("Starting JARVIS")
logger.debug("Connecting to database")
connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis")
logger.debug("Loading configuration from database")
# jconfig.get_db_config()
# Load extensions
logger.debug("Loading extensions")
for extension in get_extensions(cogs_path):
jarvis.load_extension(extension)

View file

@ -430,10 +430,11 @@ class Jarvis(StatsClient):
"""Handle autopurge events."""
autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id))
if autopurge:
self.logger.debug(
f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}"
)
await message.delete(delay=autopurge.delay)
if not message.author.has_permission(Permissions.ADMINISTRATOR):
self.logger.debug(
f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}"
)
await message.delete(delay=autopurge.delay)
async def autoreact(self, message: Message) -> None:
"""Handle autoreact events."""
@ -450,7 +451,8 @@ class Jarvis(StatsClient):
for reaction in autoreact.reactions:
await message.add_reaction(reaction)
if autoreact.thread:
name = message.content
name = message.content.replace("\n", " ")
name = re.sub(r"<:\w+:(\d+)>", "", name)
if len(name) > 100:
name = name[:97] + "..."
await message.create_thread(name=message.content, reason="Autoreact")

View file

@ -1,4 +1,5 @@
"""JARVIS bot utility commands."""
import asyncio
import logging
import platform
from io import BytesIO
@ -26,6 +27,12 @@ class BotutilCog(Extension):
"""Checks if author is bot owner."""
return ctx.author.id == self.bot.owner.id
@prefixed_command(name="stop")
async def _stop(self, ctx: PrefixedContext) -> None:
await ctx.send("Shutting down now")
loop = asyncio.get_running_loop()
loop.stop()
@prefixed_command(name="tail")
async def _tail(self, ctx: PrefixedContext, count: int = 10) -> None:
lines = []

View file

@ -5,6 +5,8 @@ import re
import aiohttp
from jarvis_core.db import q
from jarvis_core.db.models import Guess
from jarvis.utils import build_embed
from naff import Client, Extension, InteractionContext
from naff.ext.paginators import Paginator
from naff.models.discord.components import ActionRow, Button, ButtonStyles
@ -109,14 +111,16 @@ class CTCCog(Extension):
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
async def _guesses(self, ctx: InteractionContext) -> None:
await ctx.defer()
cache = {}
guesses = Guess.find().sort("correct", -1).sort("id", -1)
fields = []
async for guess in guesses:
user = await self.bot.fetch_user(guess["user"])
user = cache.get(guess["user"]) or await self.bot.fetch_user(guess["user"])
if not user:
user = "[redacted]"
if isinstance(user, (Member, User)):
user = user.username + "#" + user.discriminator
cache[guess["user"]] = user
name = "Correctly" if guess["correct"] else "Incorrectly"
name += " guessed by: " + user
fields.append(

View file

@ -1,6 +1,7 @@
"""JARVIS dbrand cog."""
import logging
import re
from datetime import datetime, timedelta
import aiohttp
from naff import Client, Extension, InteractionContext
@ -116,14 +117,15 @@ class DbrandCog(Extension):
search = matches[0]
dest = search.lower()
data = self.cache.get(dest, None)
if not data:
if not data or data["cache_expiry"] < datetime.utcnow():
api_link = self.api_url + dest
data = await self._session.get(api_link)
if 200 <= data.status < 400:
data = await data.json()
data["cache_expiry"] = datetime.utcnow() + timedelta(hours=24)
self.cache[dest] = data
else:
data = None
self.cache[dest] = data
fields = None
if data is not None and data["is_valid"] and data["shipping_available"]:
fields = []
@ -131,10 +133,16 @@ class DbrandCog(Extension):
EmbedField(data["carrier"] + " " + data["tier-title"], data["time-title"])
)
for service in data["shipping_services_available"][1:]:
service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
if service_data.status > 400:
continue
service_data = await service_data.json()
service_data = self.cache.get(f"{dest}-{service}")
if not service_data or service_data["cache_expiry"] < datetime.utcnow():
service_data = await self._session.get(
self.api_url + dest + "/" + service["url"]
)
if service_data.status > 400:
continue
service_data = await service_data.json()
service_data["cache_expiry"] = datetime.utcnow() + timedelta(hours=24)
self.cache[f"{dest}-{service}"] = service_data
fields.append(
EmbedField(
service_data["carrier"] + " " + service_data["tier-title"],

View file

@ -16,8 +16,6 @@ from naff.models.naff.application_commands import (
slash_command,
slash_option,
)
from naff.models.naff.command import cooldown
from naff.models.naff.cooldowns import Buckets
from jarvis.config import JarvisConfig
from jarvis.utils import build_embed
@ -424,7 +422,6 @@ class GitlabCog(Extension):
opt_type=OptionTypes.USER,
required=False,
)
@cooldown(bucket=Buckets.USER, rate=1, interval=600)
async def _open_issue(self, ctx: InteractionContext, user: Member = None) -> None:
user = user or ctx.author
modal = Modal(

View file

@ -9,7 +9,18 @@ from asyncpraw.models.reddit.submission import Submission
from asyncpraw.models.reddit.submission import Subreddit as Sub
from asyncprawcore.exceptions import Forbidden, NotFound, Redirect
from jarvis_core.db import q
from jarvis_core.db.models import Subreddit, SubredditFollow, UserSetting
from jarvis_core.db.models import (
Redditor,
RedditorFollow,
Subreddit,
SubredditFollow,
UserSetting,
)
from jarvis import const
from jarvis.config import JarvisConfig
from jarvis.utils import build_embed
from jarvis.utils.permissions import admin_or_permissions
from naff import Client, Extension, InteractionContext, Permissions
from naff.client.utils.misc_utils import get
from naff.models.discord.channel import ChannelTypes, GuildText
@ -23,13 +34,10 @@ from naff.models.naff.application_commands import (
)
from naff.models.naff.command import check
from jarvis import const
from jarvis.config import JarvisConfig
from jarvis.utils import build_embed
from jarvis.utils.permissions import admin_or_permissions
DEFAULT_USER_AGENT = f"python:JARVIS:{const.__version__} (by u/zevaryx)"
sub_name = re.compile(r"\A[A-Za-z0-9][A-Za-z0-9_]{2,20}\Z")
user_name = re.compile(r"[A-Za-z0-9_-]+")
image_link = re.compile(r"https?://(?:www)?\.?preview\.redd\.it\/(.*\..*)\?.*")
class RedditCog(Extension):
@ -89,6 +97,7 @@ class RedditCog(Extension):
if post.spoiler:
content += "||"
content += f"\n\n[View this post]({url})"
content = "\n".join(image_link.sub(r"https://i.redd.it/\1", x) for x in content.split("\n"))
if not images and not content:
self.logger.debug(f"Post {post.id} had neither content nor images?")
@ -122,8 +131,126 @@ class RedditCog(Extension):
return embeds
reddit = SlashCommand(name="reddit", description="Manage Reddit follows")
follow = reddit.group(name="follow", description="Add a follow")
unfollow = reddit.group(name="unfollow", description="Remove a follow")
@reddit.subcommand(sub_cmd_name="follow", sub_cmd_description="Follow a Subreddit")
@follow.subcommand(sub_cmd_name="redditor", sub_cmd_description="Follow a Redditor")
@slash_option(
name="name",
description="Redditor name",
opt_type=OptionTypes.STRING,
required=True,
)
@slash_option(
name="channel",
description="Channel to post to",
opt_type=OptionTypes.CHANNEL,
channel_types=[ChannelTypes.GUILD_TEXT],
required=True,
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _redditor_follow(
self, ctx: InteractionContext, name: str, channel: GuildText
) -> None:
if not user_name.match(name):
await ctx.send("Invalid Redditor name", ephemeral=True)
return
if not isinstance(channel, GuildText):
await ctx.send("Channel must be a text channel", ephemeral=True)
return
try:
redditor = await self.api.redditor(name)
await redditor.load()
except (NotFound, Forbidden, Redirect) as e:
self.logger.debug(f"Redditor {name} raised {e.__class__.__name__} on add")
await ctx.send("Redditor may be deleted or nonexistent.", ephemeral=True)
return
exists = await RedditorFollow.find_one(q(name=redditor.name, guild=ctx.guild.id))
if exists:
await ctx.send("Redditor already being followed in this guild", ephemeral=True)
return
count = len([i async for i in SubredditFollow.find(q(guild=ctx.guild.id))])
if count >= 12:
await ctx.send("Cannot follow more than 12 Redditors", ephemeral=True)
return
sr = await Redditor.find_one(q(name=redditor.name))
if not sr:
sr = Redditor(name=redditor.name)
await sr.commit()
srf = RedditorFollow(
name=redditor.name,
channel=channel.id,
guild=ctx.guild.id,
admin=ctx.author.id,
)
await srf.commit()
await ctx.send(f"Now following `u/{name}` in {channel.mention}")
@unfollow.subcommand(sub_cmd_name="redditor", sub_cmd_description="Unfollow Redditor")
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _redditor_unfollow(self, ctx: InteractionContext) -> None:
subs = RedditorFollow.find(q(guild=ctx.guild.id))
redditors = []
async for sub in subs:
redditors.append(sub)
if not redditors:
await ctx.send("You need to follow a redditor first", ephemeral=True)
return
options = []
names = []
for idx, redditor in enumerate(redditors):
sub = await Redditor.find_one(q(name=redditor.name))
names.append(sub.name)
option = SelectOption(label=sub.name, value=str(idx))
options.append(option)
select = Select(
options=options, custom_id="to_delete", min_values=1, max_values=len(redditors)
)
components = [ActionRow(select)]
block = "\n".join(x for x in names)
message = await ctx.send(
content=f"You are following the following redditors:\n```\n{block}\n```\n\n"
"Please choose redditors to unfollow",
components=components,
)
try:
context = await self.bot.wait_for_component(
check=lambda x: ctx.author.id == x.context.author.id,
messages=message,
timeout=60 * 5,
)
for to_delete in context.context.values:
follow = get(redditors, guild=ctx.guild.id, name=names[int(to_delete)])
try:
await follow.delete()
except Exception:
self.logger.debug("Ignoring deletion error")
for row in components:
for component in row.components:
component.disabled = True
block = "\n".join(names[int(x)] for x in context.context.values)
await context.context.edit_origin(
content=f"Unfollowed the following:\n```\n{block}\n```", components=components
)
except asyncio.TimeoutError:
for row in components:
for component in row.components:
component.disabled = True
await message.edit(components=components)
@follow.subcommand(sub_cmd_name="subreddit", sub_cmd_description="Follow a Subreddit")
@slash_option(
name="name",
description="Subreddit display name",
@ -138,7 +265,9 @@ class RedditCog(Extension):
required=True,
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _reddit_follow(self, ctx: InteractionContext, name: str, channel: GuildText) -> None:
async def _subreddit_follow(
self, ctx: InteractionContext, name: str, channel: GuildText
) -> None:
if not sub_name.match(name):
await ctx.send("Invalid Subreddit name", ephemeral=True)
return
@ -189,7 +318,7 @@ class RedditCog(Extension):
await ctx.send(f"Now following `r/{name}` in {channel.mention}")
@reddit.subcommand(sub_cmd_name="unfollow", sub_cmd_description="Unfollow Subreddits")
@unfollow.subcommand(sub_cmd_name="subreddit", sub_cmd_description="Unfollow Subreddits")
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _subreddit_unfollow(self, ctx: InteractionContext) -> None:
subs = SubredditFollow.find(q(guild=ctx.guild.id))

330
jarvis/cogs/tags.py Normal file
View file

@ -0,0 +1,330 @@
"""JARVIS Tags Cog."""
import asyncio
import re
from datetime import datetime, timezone
from typing import Dict, List
from jarvis_core.db import q
from jarvis_core.db.models import Setting, Tag
from naff import AutocompleteContext, Client, Extension, InteractionContext
from naff.models.discord.embed import EmbedField
from naff.models.discord.enums import Permissions
from naff.models.discord.modal import InputText, Modal, TextStyles
from naff.models.naff.application_commands import (
OptionTypes,
SlashCommand,
slash_option,
)
from thefuzz import process
from jarvis.utils import build_embed
invites = re.compile(
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
flags=re.IGNORECASE,
)
tag_name = re.compile(r"$[\w\ \-]{1,40}^")
class TagCog(Extension):
def __init__(self, bot: Client):
self.bot = bot
self.cache: Dict[int, List[int]] = {}
tag = SlashCommand(name="tag", description="Create and manage custom tags")
@tag.subcommand(sub_cmd_name="get", sub_cmd_description="Get a tag")
@slash_option(
name="name",
description="Tag to get",
autocomplete=True,
opt_type=OptionTypes.STRING,
required=True,
)
async def _get(self, ctx: InteractionContext, name: str) -> None:
tag = await Tag.find_one(q(guild=ctx.guild.id, name=name))
if not tag:
await ctx.send(
"Well this is awkward, looks like the tag was deleted just now", ephemeral=True
)
return
await ctx.send(tag.content)
@tag.subcommand(sub_cmd_name="create", sub_cmd_description="Create a tag")
async def _create(self, ctx: InteractionContext) -> None:
modal = Modal(
title="Create a new tag!",
components=[
InputText(
label="Tag name",
placeholder="name",
style=TextStyles.SHORT,
custom_id="name",
max_length=40,
),
InputText(
label="Content",
placeholder="Content to send here",
style=TextStyles.PARAGRAPH,
custom_id="content",
max_length=512,
),
],
)
await ctx.send_modal(modal)
try:
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
name = response.responses.get("name").replace("`", "")
content = response.responses.get("content")
except asyncio.TimeoutError:
return
noinvite = await Setting.find_one(q(guild=ctx.guild.id, setting="noinvite"))
if (
(invites.search(content) or invites.search(name))
and noinvite.value
and not (
ctx.author.has_permission(Permissions.ADMINISTRATOR)
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
)
):
await response.send(
"Listen, don't use this to try and bypass the rules", ephemeral=True
)
return
elif not content.strip() or not name.strip():
await response.send("Content and name required", ephemeral=True)
return
elif not tag_name.match(name):
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
return
tag = await Tag.find_one(q(guild=ctx.guild.id, name=name))
if tag:
await response.send("That tag already exists", ephemeral=True)
return
content = re.sub(r"\\?([@<])", r"\\\g<1>", content)
tag = Tag(
creator=ctx.author.id,
name=name,
content=content,
guild=ctx.guild.id,
)
await tag.commit()
embed = build_embed(
title="Tag Created",
description=f"{ctx.author.mention} created a new tag",
fields=[EmbedField(name="Name", value=name), EmbedField(name="Content", value=content)],
)
embed.set_author(
name=ctx.author.username + "#" + ctx.author.discriminator,
icon_url=ctx.author.display_avatar.url,
)
await response.send(embeds=embed)
if ctx.guild.id not in self.cache:
self.cache[ctx.guild.id] = []
self.cache[ctx.guild.id].append(tag.name)
@tag.subcommand(sub_cmd_name="edit", sub_cmd_description="Edit a tag")
@slash_option(
name="name",
description="Tag name",
opt_type=OptionTypes.STRING,
autocomplete=True,
required=True,
)
async def _edit(self, ctx: InteractionContext, name: str) -> None:
old_name = name
tag = await Tag.find_one(q(guild=ctx.guild.id, name=name))
if not tag:
await ctx.send("Tag not found", ephemeral=True)
return
elif tag.creator != ctx.author.id and not (
ctx.author.has_permission(Permissions.ADMINISTRATOR)
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
):
await ctx.send("You didn't create this tag, ask the creator to edit it", ephemeral=True)
return
modal = Modal(
title="Edit a tag!",
components=[
InputText(
label="Tag name",
value=tag.name,
style=TextStyles.SHORT,
custom_id="name",
max_length=40,
),
InputText(
label="Content",
value=tag.content,
style=TextStyles.PARAGRAPH,
custom_id="content",
max_length=512,
),
],
)
await ctx.send_modal(modal)
try:
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
name = response.responses.get("name").replace("`", "")
content = response.responses.get("content")
except asyncio.TimeoutError:
return
new_tag = await Tag.find_one(q(guild=ctx.guild.id, name=name))
if new_tag and new_tag.id != tag.id:
await ctx.send(
"That tag name is used by another tag, choose another name", ephemeral=True
)
return
noinvite = await Setting.find_one(q(guild=ctx.guild.id, setting="noinvite"))
if (
(invites.search(content) or invites.search(name))
and noinvite.value
and not (
ctx.author.has_permission(Permissions.ADMINISTRATOR)
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
)
):
await response.send(
"Listen, don't use this to try and bypass the rules", ephemeral=True
)
return
elif not content.strip() or not name.strip():
await response.send("Content and name required", ephemeral=True)
return
elif not tag_name.match(name):
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
return
tag.content = re.sub(r"\\?([@<])", r"\\\g<1>", content)
tag.name = name
tag.edited_at = datetime.now(tz=timezone.utc)
tag.editor = ctx.author.id
await tag.commit()
embed = build_embed(
title="Tag Updated",
description=f"{ctx.author.mention} updated a tag",
fields=[
EmbedField(name="Name", value=name),
EmbedField(name="Content", value=tag.content),
],
)
embed.set_author(
name=ctx.author.username + "#" + ctx.author.discriminator,
icon_url=ctx.author.display_avatar.url,
)
await response.send(embeds=embed)
if tag.name not in self.cache[ctx.guild.id]:
self.cache[ctx.guild.id].remove(old_name)
self.cache[ctx.guild.id].append(tag.name)
@tag.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a tag")
@slash_option(
name="name",
description="Tag name",
opt_type=OptionTypes.STRING,
required=True,
autocomplete=True,
)
async def _delete(self, ctx: InteractionContext, name: str) -> None:
tag = await Tag.find_one(q(guild=ctx.guild.id, name=name))
if not tag:
await ctx.send("Tag not found", ephemeral=True)
return
elif tag.creator != ctx.author.id and not (
ctx.author.has_permission(Permissions.ADMINISTRATOR)
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
):
await ctx.send(
"You didn't create this tag, ask the creator to delete it", ephemeral=True
)
return
await tag.delete()
await ctx.send(f"Tag `{name}` deleted")
self.cache[ctx.guild.id].remove(tag.name)
@tag.subcommand(sub_cmd_name="info", sub_cmd_description="Get info on a tag")
@slash_option(
name="name",
description="Tag name",
opt_type=OptionTypes.STRING,
required=True,
autocomplete=True,
)
async def _info(self, ctx: InteractionContext, name: str) -> None:
tag = await Tag.find_one(q(guild=ctx.guild.id, name=name))
if not tag:
await ctx.send("Tag not found", ephemeral=True)
return
username, discrim, url, mention = None, None, None, "Unknown User"
author = await self.bot.fetch_user(tag.creator)
if author:
username = author.username
discrim = author.discriminator
url = author.display_avatar.url
mention = author.mention
ts = int(tag.created_at.timestamp())
embed = build_embed(
title="Tag Info",
description=f"Here's the info on the tag `{name}`",
fields=[
EmbedField(name="Name", value=name),
EmbedField(name="Content", value=tag.content),
EmbedField(name="Created At", value=f"<t:{ts}:F>"),
EmbedField(name="Created By", value=mention),
],
)
if tag.edited_at:
ets = int(tag.edited_at.timestamp())
editor = await self.bot.fetch_user(tag.editor)
emention = "Unknown User"
if editor:
emention = editor.mention
embed.add_field(name="Edited At", value=f"<t:{ets}:F>")
embed.add_field(name="Edited By", value=emention)
embed.set_author(
name=f"{username}#{discrim}" if username else "Unknown User",
icon_url=url,
)
await ctx.send(embeds=embed)
@_get.autocomplete("name")
@_edit.autocomplete("name")
@_delete.autocomplete("name")
@_info.autocomplete("name")
async def _autocomplete(self, ctx: AutocompleteContext, name: str) -> None:
if not self.cache.get(ctx.guild.id):
tags = await Tag.find(q(guild=ctx.guild.id)).to_list(None)
self.cache[ctx.guild.id] = [tag.name for tag in tags]
results = process.extract(name, self.cache.get(ctx.guild.id), limit=25)
choices = [{"name": r[0], "value": r[0]} for r in results]
await ctx.send(choices=choices)
def setup(bot: Client) -> None:
"""Add TagCog to JARVIS"""
TagCog(bot)

View file

@ -8,13 +8,20 @@ from io import BytesIO
import numpy as np
from dateparser import parse
from PIL import Image
from tzlocal import get_localzone
from jarvis import const as jconst
from jarvis.data import pigpen
from jarvis.data.robotcamo import emotes, hk, names
from jarvis.utils import build_embed, get_repo_hash
from naff import Client, Extension, InteractionContext, const
from naff.models.discord.channel import GuildCategory, GuildText, GuildVoice
from naff.models.discord.embed import EmbedField
from naff.models.discord.file import File
from naff.models.discord.guild import Guild
from naff.models.discord.role import Role
from naff.models.discord.user import Member, User
from naff.models.discord.user import User
from naff.models.naff.application_commands import (
CommandTypes,
OptionTypes,
@ -25,13 +32,6 @@ from naff.models.naff.application_commands import (
)
from naff.models.naff.command import cooldown
from naff.models.naff.cooldowns import Buckets
from PIL import Image
from tzlocal import get_localzone
from jarvis import const as jconst
from jarvis.data import pigpen
from jarvis.data.robotcamo import emotes, hk, names
from jarvis.utils import build_embed, get_repo_hash
JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA")
@ -125,9 +125,7 @@ class UtilCog(Extension):
if not user:
user = ctx.author
avatar = user.avatar.url
if isinstance(user, Member):
avatar = user.display_avatar.url
avatar = user.display_avatar.url
embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
embed.set_image(url=avatar)

80
poetry.lock generated
View file

@ -106,17 +106,6 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "asgiref"
version = "3.5.2"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
[[package]]
name = "async-generator"
version = "1.10"
@ -414,7 +403,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[[package]]
name = "jarvis-core"
version = "0.10.2"
version = "0.12.0"
description = "JARVIS core"
category = "main"
optional = false
@ -435,7 +424,7 @@ umongo = "^3.1.0"
type = "git"
url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git"
reference = "main"
resolved_reference = "7bb9b25f636fbcbea97e0924f2192a1e497258dd"
resolved_reference = "fe24fce330cfd23a7af3834ef11b675780e6325d"
[[package]]
name = "jinxed"
@ -525,7 +514,7 @@ python-versions = "*"
[[package]]
name = "naff"
version = "1.4.0"
version = "1.7.1"
description = "Not another freaking fork"
category = "main"
optional = false
@ -540,6 +529,7 @@ tomli = "*"
[package.extras]
all = ["PyNaCl (>=1.5.0,<1.6)", "cchardet", "aiodns", "orjson", "brotli"]
speedup = ["cchardet", "aiodns", "orjson", "brotli"]
tests = ["pytest", "pytest-recording", "pytest-asyncio", "pytest-cov", "typeguard"]
voice = ["PyNaCl (>=1.5.0,<1.6)"]
[[package]]
@ -552,15 +542,15 @@ python-versions = "^3.10"
develop = false
[package.dependencies]
naff = {version = "^1.2.0", extras = ["orjson"]}
naff = {version = "^1.7.1", extras = ["orjson"]}
prometheus-client = "^0.14.1"
uvicorn = "^0.17.6"
uvicorn = "^0.18.2"
[package.source]
type = "git"
url = "https://github.com/artem30801/nafftrack.git"
reference = "master"
resolved_reference = "e3b6f102d6784731d90c52e84b401c86117583f2"
resolved_reference = "eae6ffd93a1a7854347eb0e147b894bf307c0003"
[[package]]
name = "nanoid"
@ -570,6 +560,14 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "nest-asyncio"
version = "1.5.5"
description = "Patch asyncio to allow nested event loops"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "numpy"
version = "1.22.4"
@ -816,6 +814,14 @@ requests-toolbelt = ">=0.9.1"
autocompletion = ["argcomplete (>=1.10.0,<3)"]
yaml = ["PyYaml (>=5.2)"]
[[package]]
name = "python-levenshtein"
version = "0.12.2"
description = "Python extension for computing string edit distances and similarities."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pytz"
version = "2022.1"
@ -946,6 +952,20 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "thefuzz"
version = "0.19.0"
description = "Fuzzy string matching in python"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
python-levenshtein = {version = ">=0.12", optional = true, markers = "extra == \"speedup\""}
[package.extras]
speedup = ["python-levenshtein (>=0.12)"]
[[package]]
name = "tomli"
version = "2.0.1"
@ -1078,19 +1098,18 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvicorn"
version = "0.17.6"
version = "0.18.2"
description = "The lightning-fast ASGI server."
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
asgiref = ">=3.4.0"
click = ">=7.0"
h11 = ">=0.8"
[package.extras]
standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
[[package]]
name = "watchdog"
@ -1159,7 +1178,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "c1a2a46f16c8966603c1e92166f4ad1e243b4425752f1cf8e78d2a421aacd0b9"
content-hash = "d4a3ccd2f79fe0c323784bfba2c5950817257639bbdcdb57a6e71682a8846504"
[metadata.files]
aiofile = [
@ -1268,10 +1287,6 @@ appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
asgiref = [
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
]
async-generator = [
{file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"},
{file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"},
@ -1549,15 +1564,16 @@ mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
naff = [
{file = "naff-1.4.0-py3-none-any.whl", hash = "sha256:81d1e42dbc761b5ec3820b3bbf64f45c23ffdd185aed6c5512c9a8b24e0277de"},
{file = "naff-1.4.0.tar.gz", hash = "sha256:2f8bc2216c54a0b58db05aa8f787d33e2ad3db3d1e512751dc3efb16e5891653"},
]
naff = []
nafftrack = []
nanoid = [
{file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"},
{file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"},
]
nest-asyncio = [
{file = "nest_asyncio-1.5.5-py3-none-any.whl", hash = "sha256:b98e3ec1b246135e4642eceffa5a6c23a3ab12c82ff816a92c612d68205813b2"},
{file = "nest_asyncio-1.5.5.tar.gz", hash = "sha256:e442291cd942698be619823a17a86a5759eabe1f8613084790de189fe9e16d65"},
]
numpy = [
{file = "numpy-1.22.4-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:ba9ead61dfb5d971d77b6c131a9dbee62294a932bf6a356e48c75ae684e635b3"},
{file = "numpy-1.22.4-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1ce7ab2053e36c0a71e7a13a7475bd3b1f54750b4b433adc96313e127b870887"},
@ -1919,6 +1935,7 @@ python-gitlab = [
{file = "python-gitlab-3.5.0.tar.gz", hash = "sha256:29ae7fb9b8c9aeb2e6e19bd2fd04867e93ecd7af719978ce68fac0cf116ab30d"},
{file = "python_gitlab-3.5.0-py3-none-any.whl", hash = "sha256:73b5aa6502efa557ee1a51227cceb0243fac5529627da34f08c5f265bf50417c"},
]
python-levenshtein = []
pytz = [
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
@ -2084,6 +2101,7 @@ smmap = [
{file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
{file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
]
thefuzz = []
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
@ -2125,8 +2143,8 @@ urllib3 = [
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
]
uvicorn = [
{file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"},
{file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"},
{file = "uvicorn-0.18.2-py3-none-any.whl", hash = "sha256:c19a057deb1c5bb060946e2e5c262fc01590c6529c0af2c3d9ce941e89bc30e0"},
{file = "uvicorn-0.18.2.tar.gz", hash = "sha256:cade07c403c397f9fe275492a48c1b869efd175d5d8a692df649e6e7e2ed8f4e"},
]
watchdog = [
{file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:676263bee67b165f16b05abc52acc7a94feac5b5ab2449b491f1a97638a79277"},

View file

@ -28,6 +28,8 @@ aioredis = "^2.0.1"
naff = { version = "^1.2.0", extras = ["orjson"] }
nafftrack = {git = "https://github.com/artem30801/nafftrack.git", rev = "master"}
ansitoimg = "^2022.1"
nest-asyncio = "^1.5.5"
thefuzz = {extras = ["speedup"], version = "^0.19.0"}
[tool.poetry.dev-dependencies]
black = {version = "^22.3.0", allow-prereleases = true}

4
run.py
View file

@ -5,7 +5,11 @@ nest_asyncio.apply()
import asyncio
import nest_asyncio
from jarvis import run
nest_asyncio.apply()
if __name__ == "__main__":
asyncio.run(run())