Merge branch 'v2.0' into 'dev'
V2.0 Alpha 0 Closes #116, #113, #117, #92, #93, #94, #96, #98, #99, #100, #103, #97, #101, #105, and #104 See merge request stark-industries/j.a.r.v.i.s.!48
This commit is contained in:
commit
6fb8b8ba53
56 changed files with 3951 additions and 2865 deletions
14
.flake8
Normal file
14
.flake8
Normal file
|
@ -0,0 +1,14 @@
|
|||
[flake8]
|
||||
extend-ignore =
|
||||
Q0, E501, C812, E203, W503, # These default to arguing with Black. We might configure some of them eventually
|
||||
ANN1, # Ignore self and cls annotations
|
||||
ANN204, ANN206, # return annotations for special methods and class methods
|
||||
D105, D107, # Missing Docstrings in magic method and __init__
|
||||
S311, # Standard pseudo-random generators are not suitable for security/cryptographic purposes.
|
||||
D401, # First line should be in imperative mood; try rephrasing
|
||||
D400, # First line should end with a period
|
||||
D101, # Missing docstring in public class
|
||||
|
||||
# Plugins we don't currently include: flake8-return
|
||||
R503, # missing explicit return at the end of function ableto return non-None value.
|
||||
max-line-length=100
|
|
@ -1,12 +1,15 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
rev: v4.1.0
|
||||
hooks:
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
args: [--unsafe]
|
||||
- id: check-merge-conflict
|
||||
- id: requirements-txt-fixer
|
||||
- id: end-of-file-fixer
|
||||
- id: debug-statements
|
||||
language_version: python3.10
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
|
||||
|
@ -16,23 +19,31 @@ repos:
|
|||
- id: python-check-blanket-noqa
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.7b0
|
||||
rev: 22.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--line-length=120]
|
||||
args: [--line-length=100, --target-version=py310]
|
||||
language_version: python3.10
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-isort
|
||||
rev: V5.9.3
|
||||
rev: V5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
rev: 4.0.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-annotations~=2.0
|
||||
- flake8-bandit~=2.1
|
||||
- flake8-docstrings~=1.5
|
||||
args: [--max-line-length=120, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204]
|
||||
- flake8-bugbear
|
||||
- flake8-comprehensions
|
||||
- flake8-quotes
|
||||
- flake8-raise
|
||||
- flake8-deprecated
|
||||
- flake8-print
|
||||
- flake8-return
|
||||
language_version: python3.10
|
||||
|
|
20
README.md
20
README.md
|
@ -1,12 +1,16 @@
|
|||
<div align="center">
|
||||
<img width=15% alt="J.A.R.V.I.S" src="https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./-/raw/main/jarvis_small.png">
|
||||
|
||||
# Just Another Rather Very Intelligent System
|
||||
<br />
|
||||
|
||||
|
||||
[]()
|
||||
[](https://git.zevaryx.com/stark-industries/j.a.r.v.i.s.)
|
||||
[](https://discord.gg/VtgZntXcnZ)
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<img width="150" height="150" align="left" alt="J.A.R.V.I.S" src="https://i.imgur.com/7bnHam2.png">
|
||||
|
||||
# Just Another Very Intelligent System (J.A.R.V.I.S.)
|
||||
Welcome to the J.A.R.V.I.S. Initiative! While the main goal is to create the best discord bot there can be, a great achievement would be to present him to the Robots and have him integrated into the dbrand server. Feel free to suggest anything you may think to be useful… or cool.
|
||||
|
||||
**Note:** Some commands have been custom made to be used in the dbrand server.
|
||||
|
@ -34,19 +38,17 @@ If you wish to contribute to the J.A.R.V.I.S codebase or documentation, join the
|
|||
Join the [Stark R&D Department Discord server](https://discord.gg/VtgZntXcnZ) to be kept up-to-date on code updates and issues.
|
||||
|
||||
## Requirements
|
||||
- MongoDB 4.4 or higher
|
||||
- Python 3.8 or higher
|
||||
- MongoDB 5.0 or higher
|
||||
- Python 3.10 or higher
|
||||
- [tokei](https://github.com/XAMPPRocky/tokei) 12.1 or higher
|
||||
|
||||
On top of the above requirements, the following pip packages are also required:
|
||||
- `discord-py>=1.7, <2`
|
||||
- `dis-snek>=5.0.0`
|
||||
- `psutil>=5.8, <6`
|
||||
- `GitPython>=3.1, <4`
|
||||
- `PyYaml>=5.4, <6`
|
||||
- `discord-py-slash-command>=2.3.2, <3`
|
||||
- `pymongo>=3.12.0, <4`
|
||||
- `opencv-python>=4.5, <5`
|
||||
- `ButtonPaginator>=0.0.3`
|
||||
- `Pillow>=8.2.0, <9`
|
||||
- `python-gitlab>=2.9.0, <3`
|
||||
- `ulid-py>=1.1.0, <2`
|
||||
|
|
BIN
jarvis.png
BIN
jarvis.png
Binary file not shown.
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.5 MiB |
14
jarvis.svg
14
jarvis.svg
|
@ -1,16 +1,16 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15360" height="15360" viewBox="0 0 1080 1080">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<defs>
|
||||
<style>
|
||||
.a {
|
||||
fill: #3498DB;
|
||||
fill: #3498db;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<title>logotests</title>
|
||||
<title>jarvis</title>
|
||||
<g>
|
||||
<path class="a" d="M500.16,699.361c-15.864,0-31.731-.186-47.589.121-4.415.085-6.757-1.462-8.868-5.129Q358.817,546.94,273.657,399.686c-2.385-4.122-1.979-6.914.278-10.773q23.957-40.968,47.4-82.235c2.583-4.557,5.44-6.172,10.731-6.164q168.183.256,336.365,0c5.351-.009,8.141,1.751,10.688,6.244q23.394,41.288,47.384,82.233c2.6,4.409,2.259,7.322-.148,11.48q-84.848,146.517-169.341,293.24c-2.477,4.3-5.14,5.879-10.072,5.776C531.354,699.156,515.755,699.362,500.16,699.361Z"/>
|
||||
<path class="a" d="M830.455,402.341c3.487-6.006,3.164-10.053-.2-15.857L679.691,124.975a5.046,5.046,0,0,1,4.38-7.558c31.658.014,62.194.087,92.727-.117,5.027-.034,6.191,3.24,8.042,6.444L938,388.752c2.542,4.305,2.461,7.329,0,11.574Q853.239,546.711,768.849,693.311c-2.624,4.561-5.4,6.337-10.714,6.291-25.437-.22-57.755-.138-85.331-.118a8.44,8.44,0,0,1-7.3-12.679Z"/>
|
||||
<path class="a" d="M281.535,209.29c-6.873-.016-10.394,2.148-13.747,7.989L116.067,479.618a4.479,4.479,0,0,1-7.758,0c-15.635-27.113-31.377-54.571-47.067-81.306-2.578-4.394-.135-7.026,1.659-10.136L215.492,123.665c2.542-4.576,5.217-6.387,10.594-6.379q168.249.252,336.5.156c3.287,0,6.7-.987,9.082,3.193,15.182,26.623,31.107,54.043,46.743,81.068a5.139,5.139,0,0,1-4.389,7.71Z"/>
|
||||
<path class="a" d="M705.242,800.514a6.557,6.557,0,0,0-5.583-9.992c-45.182-.007-209.265-.024-294.784.122-5.965.01-9.493-1.368-12.719-6.924C340.927,695.465,229.08,504.187,224.47,496.4a6.562,6.562,0,0,0-11.329.073c-15.415,26.744-30.437,52.884-45.654,78.911-2.168,3.708-.393,5.992,1.218,8.719Q255,730.093,341.177,876.15c2.812,4.774,5.763,6.589,11.348,6.564q148.016-.678,296.032-.843c4.973-.007,7.683-1.729,10.232-5.823Z"/>
|
||||
<path class="a" d="M500.163,703.949c-16.228,0-32.461-.191-48.684.123-4.516.088-6.912-1.5-9.072-5.247q-86.839-150.8-173.959-301.448c-2.44-4.217-2.024-7.072.285-11.02q24.507-41.912,48.491-84.128c2.641-4.662,5.564-6.314,10.978-6.306q172.052.264,344.105,0c5.474-.009,8.329,1.791,10.934,6.388q23.933,42.237,48.474,84.125c2.658,4.511,2.312,7.491-.151,11.744q-86.8,149.889-173.238,299.989c-2.534,4.4-5.259,6.014-10.3,5.909C532.075,703.739,516.117,703.95,500.163,703.949Z"/>
|
||||
<path class="a" d="M838.06,400.093c3.567-6.144,3.236-10.284-.206-16.221L683.826,116.344a5.162,5.162,0,0,1,4.481-7.731c32.387.014,63.625.088,94.861-.12,5.143-.035,6.334,3.315,8.227,6.592L948.08,386.192c2.6,4.4,2.518,7.5,0,11.84Q861.369,547.786,775.037,697.76c-2.685,4.665-5.529,6.483-10.962,6.436-26.022-.225-59.084-.142-87.294-.121A8.635,8.635,0,0,1,669.31,691.1Z"/>
|
||||
<path class="a" d="M276.507,202.6c-7.031-.016-10.633,2.2-14.063,8.172L107.231,479.149a4.582,4.582,0,0,1-7.937,0C83.3,451.414,67.2,423.324,51.145,395.974c-2.638-4.5-.138-7.188,1.7-10.369L208.944,115c2.6-4.682,5.338-6.534,10.838-6.526q172.121.258,344.243.16c3.362,0,6.852-1.01,9.291,3.267,15.531,27.235,31.822,55.286,47.818,82.933a5.258,5.258,0,0,1-4.49,7.888Z"/>
|
||||
<path class="a" d="M709.965,807.43a6.707,6.707,0,0,0-5.711-10.222c-46.221-.007-214.081-.024-301.569.125-6.1.01-9.711-1.4-13.01-7.083-52.409-90.286-166.83-285.967-171.546-293.935a6.714,6.714,0,0,0-11.59.074c-15.769,27.36-31.137,54.1-46.7,80.728-2.218,3.794-.4,6.13,1.247,8.92q88.278,149.351,176.441,298.769c2.877,4.885,5.9,6.741,11.609,6.715q151.422-.693,302.845-.862c5.088-.007,7.859-1.768,10.467-5.957Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -1,20 +1,13 @@
|
|||
"""Main J.A.R.V.I.S. package."""
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from discord import Intents
|
||||
from discord.ext import commands
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashCommand
|
||||
from dis_snek import Intents, Snake, listen
|
||||
from mongoengine import connect
|
||||
from psutil import Process
|
||||
|
||||
from jarvis import logo # noqa: F401
|
||||
# from jarvis import logo # noqa: F401
|
||||
from jarvis import tasks, utils
|
||||
from jarvis.config import get_config
|
||||
from jarvis.events import guild, member, message
|
||||
from jarvis.events import member, message
|
||||
|
||||
jconfig = get_config()
|
||||
|
||||
|
@ -24,53 +17,32 @@ 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)
|
||||
|
||||
if asyncio.get_event_loop().is_closed():
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
|
||||
intents = Intents.default()
|
||||
intents = Intents.DEFAULT
|
||||
intents.members = True
|
||||
restart_ctx = None
|
||||
|
||||
|
||||
jarvis = commands.Bot(
|
||||
command_prefix=utils.get_prefix,
|
||||
intents=intents,
|
||||
help_command=None,
|
||||
max_messages=jconfig.max_messages,
|
||||
)
|
||||
jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync)
|
||||
|
||||
slash = SlashCommand(jarvis, sync_commands=False, sync_on_cog_reload=True)
|
||||
jarvis_self = Process()
|
||||
__version__ = "1.11.4"
|
||||
__version__ = "2.0.0a0"
|
||||
|
||||
|
||||
@jarvis.event
|
||||
@listen()
|
||||
async def on_ready() -> None:
|
||||
"""d.py on_ready override."""
|
||||
"""Lepton on_ready override."""
|
||||
global restart_ctx
|
||||
print(" Logged in as {0.user}".format(jarvis))
|
||||
print(" Connected to {} guild(s)".format(len(jarvis.guilds)))
|
||||
with jarvis_self.oneshot():
|
||||
print(f" Current PID: {jarvis_self.pid}")
|
||||
Path(f"jarvis.{jarvis_self.pid}.pid").touch()
|
||||
if restart_ctx:
|
||||
channel = None
|
||||
if "guild" in restart_ctx:
|
||||
guild = find(lambda x: x.id == restart_ctx["guild"], jarvis.guilds)
|
||||
if guild:
|
||||
channel = find(lambda x: x.id == restart_ctx["channel"], guild.channels)
|
||||
elif "user" in restart_ctx:
|
||||
channel = jarvis.get_user(restart_ctx["user"])
|
||||
if channel:
|
||||
await channel.send("Core systems restarted and back online.")
|
||||
restart_ctx = None
|
||||
print(" Logged in as {0.user}".format(jarvis)) # noqa: T001
|
||||
print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001
|
||||
|
||||
|
||||
def run(ctx: dict = None) -> Optional[dict]:
|
||||
@listen()
|
||||
async def on_startup() -> None:
|
||||
"""Lepton on_startup override."""
|
||||
tasks.init()
|
||||
|
||||
|
||||
def run() -> None:
|
||||
"""Run J.A.R.V.I.S."""
|
||||
global restart_ctx
|
||||
if ctx:
|
||||
restart_ctx = ctx
|
||||
connect(
|
||||
db="ctc2",
|
||||
alias="ctc2",
|
||||
|
@ -84,27 +56,21 @@ def run(ctx: dict = None) -> Optional[dict]:
|
|||
**jconfig.mongo["connect"],
|
||||
)
|
||||
jconfig.get_db_config()
|
||||
|
||||
for extension in utils.get_extensions():
|
||||
jarvis.load_extension(extension)
|
||||
print(
|
||||
|
||||
print( # noqa: T001
|
||||
" https://discord.com/api/oauth2/authorize?client_id="
|
||||
+ "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) # noqa: W503
|
||||
"{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id)
|
||||
)
|
||||
|
||||
jarvis.max_messages = jconfig.max_messages
|
||||
tasks.init()
|
||||
|
||||
# Add event listeners
|
||||
if jconfig.events:
|
||||
_ = [
|
||||
guild.GuildEventHandler(jarvis),
|
||||
member.MemberEventHandler(jarvis),
|
||||
message.MessageEventHandler(jarvis),
|
||||
]
|
||||
jarvis.run(jconfig.token, bot=True, reconnect=True)
|
||||
for cog in jarvis.cogs:
|
||||
session = getattr(cog, "_session", None)
|
||||
if session:
|
||||
session.close()
|
||||
if restart_ctx:
|
||||
return restart_ctx
|
||||
jarvis.start(jconfig.token)
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
"""J.A.R.V.I.S. Admin Cogs."""
|
||||
from discord.ext.commands import Bot
|
||||
from dis_snek import Snake
|
||||
|
||||
from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning
|
||||
from jarvis.cogs.admin import ban, kick, mute, purge, roleping, warning
|
||||
|
||||
|
||||
def setup(bot: Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add admin cogs to J.A.R.V.I.S."""
|
||||
bot.add_cog(ban.BanCog(bot))
|
||||
bot.add_cog(kick.KickCog(bot))
|
||||
bot.add_cog(lock.LockCog(bot))
|
||||
bot.add_cog(lockdown.LockdownCog(bot))
|
||||
bot.add_cog(mute.MuteCog(bot))
|
||||
bot.add_cog(purge.PurgeCog(bot))
|
||||
bot.add_cog(roleping.RolepingCog(bot))
|
||||
bot.add_cog(warning.WarningCog(bot))
|
||||
ban.BanCog(bot)
|
||||
kick.KickCog(bot)
|
||||
# lock.LockCog(bot)
|
||||
# lockdown.LockdownCog(bot)
|
||||
mute.MuteCog(bot)
|
||||
purge.PurgeCog(bot)
|
||||
roleping.RolepingCog(bot)
|
||||
warning.WarningCog(bot)
|
||||
|
|
|
@ -2,30 +2,33 @@
|
|||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ButtonPaginator import Paginator
|
||||
from discord import User
|
||||
from discord.ext import commands
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
from dis_snek import InteractionContext, Permissions, Snake
|
||||
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,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.db.models import Ban, Unban
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils import build_embed, find, find_all
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class BanCog(CacheCog):
|
||||
"""J.A.R.V.I.S. BanCog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
|
||||
async def discord_apply_ban(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
ctx: InteractionContext,
|
||||
reason: str,
|
||||
user: User,
|
||||
duration: int,
|
||||
|
@ -37,7 +40,7 @@ class BanCog(CacheCog):
|
|||
await ctx.guild.ban(user, reason=reason)
|
||||
_ = Ban(
|
||||
user=user.id,
|
||||
username=user.name,
|
||||
username=user.username,
|
||||
discrim=user.discriminator,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
|
@ -54,20 +57,20 @@ class BanCog(CacheCog):
|
|||
)
|
||||
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
name=user.display_name,
|
||||
icon_url=user.avatar,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
embed.set_thumbnail(url=user.avatar)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
async def discord_apply_unban(self, ctx: SlashContext, user: User, reason: str) -> None:
|
||||
async def discord_apply_unban(self, ctx: InteractionContext, user: User, reason: str) -> None:
|
||||
"""Apply a Discord unban."""
|
||||
await ctx.guild.unban(user, reason=reason)
|
||||
_ = Unban(
|
||||
user=user.id,
|
||||
username=user.name,
|
||||
username=user.username,
|
||||
discrim=user.discriminator,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
|
@ -77,77 +80,56 @@ class BanCog(CacheCog):
|
|||
embed = build_embed(
|
||||
title="User Unbanned",
|
||||
description=f"<@{user.id}> was unbanned",
|
||||
fields=[Field(name="Reason", value=reason)],
|
||||
fields=[EmbedField(name="Reason", value=reason)],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.name,
|
||||
icon_url=user.avatar_url,
|
||||
name=user.username,
|
||||
icon_url=user.avatar,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
embed.set_thumbnail(url=user.avatar)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="ban",
|
||||
description="Ban a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to ban",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Ban reason",
|
||||
required=True,
|
||||
option_type=3,
|
||||
),
|
||||
create_option(
|
||||
name="btype",
|
||||
description="Ban type",
|
||||
option_type=3,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(value="perm", name="Permanent"),
|
||||
create_choice(value="temp", name="Temporary"),
|
||||
create_choice(value="soft", name="Soft"),
|
||||
],
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Ban duration in hours if temporary",
|
||||
required=False,
|
||||
option_type=4,
|
||||
),
|
||||
@slash_command(name="ban", description="Ban a user")
|
||||
@slash_option(name="user", description="User to ban", opt_type=OptionTypes.USER, required=True)
|
||||
@slash_option(
|
||||
name="reason", description="Ban reason", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="btype",
|
||||
description="Ban type",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Permanent", value="perm"),
|
||||
SlashCommandChoice(name="Temporary", value="temp"),
|
||||
SlashCommandChoice(name="Soft", value="soft"),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(ban_members=True)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _ban(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
ctx: InteractionContext,
|
||||
reason: str,
|
||||
user: User = None,
|
||||
reason: str = None,
|
||||
btype: str = "perm",
|
||||
duration: int = 4,
|
||||
) -> None:
|
||||
if not user or user == ctx.author:
|
||||
await ctx.send("You cannot ban yourself.", hidden=True)
|
||||
await ctx.send("You cannot ban yourself.", ephemeral=True)
|
||||
return
|
||||
if user == self.bot.user:
|
||||
await ctx.send("I'm afraid I can't let you do that", hidden=True)
|
||||
await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
|
||||
return
|
||||
if btype == "temp" and duration < 0:
|
||||
await ctx.send("You cannot set a temp ban to < 0 hours.", hidden=True)
|
||||
await ctx.send("You cannot set a temp ban to < 0 hours.", ephemeral=True)
|
||||
return
|
||||
elif btype == "temp" and duration > 744:
|
||||
await ctx.send("You cannot set a temp ban to > 1 month", hidden=True)
|
||||
await ctx.send("You cannot set a temp ban to > 1 month", ephemeral=True)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||
return
|
||||
if not reason:
|
||||
reason = "Mr. Stark is displeased with your presence. Please leave."
|
||||
|
||||
await ctx.defer()
|
||||
|
||||
|
@ -160,10 +142,10 @@ class BanCog(CacheCog):
|
|||
if mtype == "temp":
|
||||
user_message += f"\nDuration: {duration} hours"
|
||||
|
||||
fields = [Field(name="Type", value=mtype)]
|
||||
fields = [EmbedField(name="Type", value=mtype)]
|
||||
|
||||
if mtype == "temp":
|
||||
fields.append(Field(name="Duration", value=f"{duration} hour(s)"))
|
||||
fields.append(EmbedField(name="Duration", value=f"{duration} hour(s)"))
|
||||
|
||||
user_embed = build_embed(
|
||||
title=f"You have been banned from {ctx.guild.name}",
|
||||
|
@ -172,10 +154,10 @@ class BanCog(CacheCog):
|
|||
)
|
||||
|
||||
user_embed.set_author(
|
||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar,
|
||||
)
|
||||
user_embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
user_embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
|
||||
try:
|
||||
await user.send(embed=user_embed)
|
||||
|
@ -184,13 +166,13 @@ class BanCog(CacheCog):
|
|||
try:
|
||||
await ctx.guild.ban(user, reason=reason)
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", hidden=True)
|
||||
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", ephemeral=True)
|
||||
return
|
||||
send_failed = False
|
||||
if mtype == "soft":
|
||||
await ctx.guild.unban(user, reason="Ban was softban")
|
||||
|
||||
fields.append(Field(name="DM Sent?", value=str(not send_failed)))
|
||||
fields.append(EmbedField(name="DM Sent?", value=str(not send_failed)))
|
||||
if btype != "temp":
|
||||
duration = None
|
||||
active = True
|
||||
|
@ -199,33 +181,22 @@ class BanCog(CacheCog):
|
|||
|
||||
await self.discord_apply_ban(ctx, reason, user, duration, active, fields, mtype)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="unban",
|
||||
description="Unban a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to unban",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Unban reason",
|
||||
required=True,
|
||||
option_type=3,
|
||||
),
|
||||
],
|
||||
@slash_command(name="unban", description="Unban a user")
|
||||
@slash_option(
|
||||
name="user", description="User to unban", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@admin_or_permissions(ban_members=True)
|
||||
@slash_option(
|
||||
name="reason", description="Unban reason", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _unban(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
ctx: InteractionContext,
|
||||
user: str,
|
||||
reason: str,
|
||||
) -> None:
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||
return
|
||||
|
||||
orig_user = user
|
||||
|
@ -236,26 +207,31 @@ class BanCog(CacheCog):
|
|||
bans = await ctx.guild.bans()
|
||||
|
||||
# Try to get ban information out of Discord
|
||||
if re.match("^[0-9]{1,}$", user): # User ID
|
||||
if re.match(r"^[0-9]{1,}$", user): # User ID
|
||||
user = int(user)
|
||||
discord_ban_info = find(lambda x: x.user.id == user, bans)
|
||||
else: # User name
|
||||
if re.match("#[0-9]{4}$", user): # User name has discrim
|
||||
if re.match(r"#[0-9]{4}$", user): # User name has discrim
|
||||
user, discrim = user.split("#")
|
||||
if discrim:
|
||||
discord_ban_info = find(
|
||||
lambda x: x.user.name == user and x.user.discriminator == discrim,
|
||||
lambda x: x.user.username == user and x.user.discriminator == discrim,
|
||||
bans,
|
||||
)
|
||||
else:
|
||||
results = [x for x in filter(lambda x: x.user.name == user, bans)]
|
||||
results = find_all(lambda x: x.user.username == user, bans)
|
||||
if results:
|
||||
if len(results) > 1:
|
||||
active_bans = []
|
||||
for ban in bans:
|
||||
active_bans.append("{0} ({1}): {2}".format(ban.user.name, ban.user.id, ban.reason))
|
||||
active_bans.append(
|
||||
"{0} ({1}): {2}".format(ban.user.username, ban.user.id, ban.reason)
|
||||
)
|
||||
ab_message = "\n".join(active_bans)
|
||||
message = f"More than one result. Please use one of the following IDs:\n```{ab_message}\n```"
|
||||
message = (
|
||||
"More than one result. "
|
||||
f"Please use one of the following IDs:\n```{ab_message}\n```"
|
||||
)
|
||||
await ctx.send(message)
|
||||
return
|
||||
else:
|
||||
|
@ -278,7 +254,7 @@ class BanCog(CacheCog):
|
|||
database_ban_info = Ban.objects(**search).first()
|
||||
|
||||
if not discord_ban_info and not database_ban_info:
|
||||
await ctx.send(f"Unable to find user {orig_user}", hidden=True)
|
||||
await ctx.send(f"Unable to find user {orig_user}", ephemeral=True)
|
||||
|
||||
elif discord_ban_info:
|
||||
await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
|
||||
|
@ -297,46 +273,41 @@ 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."
|
||||
)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="bans",
|
||||
name="list",
|
||||
description="List bans",
|
||||
options=[
|
||||
create_option(
|
||||
name="type",
|
||||
description="Ban type",
|
||||
option_type=4,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(value=0, name="All"),
|
||||
create_choice(value=1, name="Permanent"),
|
||||
create_choice(value=2, name="Temporary"),
|
||||
create_choice(value=3, name="Soft"),
|
||||
],
|
||||
),
|
||||
create_option(
|
||||
name="active",
|
||||
description="Active bans",
|
||||
option_type=4,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(value=1, name="Yes"),
|
||||
create_choice(value=0, name="No"),
|
||||
],
|
||||
),
|
||||
@slash_command(
|
||||
name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans"
|
||||
)
|
||||
@slash_option(
|
||||
name="btype",
|
||||
description="Ban type",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="All", value=0),
|
||||
SlashCommandChoice(name="Permanent", value=1),
|
||||
SlashCommandChoice(name="Temporary", value=2),
|
||||
SlashCommandChoice(name="Soft", value=3),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(ban_members=True)
|
||||
async def _bans_list(self, ctx: SlashContext, type: int = 0, active: int = 1) -> None:
|
||||
@slash_option(
|
||||
name="active",
|
||||
description="Active bans",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
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:
|
||||
active = bool(active)
|
||||
exists = self.check_cache(ctx, type=type, active=active)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
types = [0, "perm", "temp", "soft"]
|
||||
|
@ -351,9 +322,9 @@ class BanCog(CacheCog):
|
|||
for ban in bans:
|
||||
if not ban.username:
|
||||
user = await self.bot.fetch_user(ban.user)
|
||||
ban.username = user.name if user else "[deleted user]"
|
||||
ban.username = user.username if user else "[deleted user]"
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name=f"Username: {ban.username}#{ban.discrim}",
|
||||
value=(
|
||||
f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
|
||||
|
@ -370,8 +341,8 @@ class BanCog(CacheCog):
|
|||
for ban in bans:
|
||||
if ban.user.id not in db_bans:
|
||||
fields.append(
|
||||
Field(
|
||||
name=f"Username: {ban.user.name}#" + f"{ban.user.discriminator}",
|
||||
EmbedField(
|
||||
name=f"Username: {ban.user.username}#" + f"{ban.user.discriminator}",
|
||||
value=(
|
||||
f"Date: [unknown]\n"
|
||||
f"User ID: {ban.user.id}\n"
|
||||
|
@ -395,26 +366,15 @@ class BanCog(CacheCog):
|
|||
description=f"No {'in' if not active else ''}active bans",
|
||||
fields=[],
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
pages.append(embed)
|
||||
else:
|
||||
for i in range(0, len(bans), 5):
|
||||
embed = build_embed(title=title, description="", fields=fields[i : i + 5]) # noqa: E203
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed = build_embed(title=title, description="", fields=fields[i : i + 5])
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
pages.append(embed)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"guild": ctx.guild.id,
|
||||
|
@ -426,4 +386,4 @@ class BanCog(CacheCog):
|
|||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
||||
await paginator.send(ctx)
|
||||
|
|
|
@ -1,53 +1,38 @@
|
|||
"""J.A.R.V.I.S. KickCog."""
|
||||
from discord import User
|
||||
from discord.ext.commands import Bot
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from dis_snek import InteractionContext, Permissions, Scale
|
||||
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,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.db.models import Kick
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class KickCog(CacheCog):
|
||||
class KickCog(Scale):
|
||||
"""J.A.R.V.I.S. KickCog."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
super().__init__(bot)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="kick",
|
||||
description="Kick a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to kick",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Kick reason",
|
||||
required=False,
|
||||
option_type=3,
|
||||
),
|
||||
],
|
||||
@slash_command(name="kick", description="Kick a user")
|
||||
@slash_option(name="user", description="User to kick", opt_type=OptionTypes.USER, required=True)
|
||||
@slash_option(
|
||||
name="reason", description="Kick reason", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@admin_or_permissions(kick_members=True)
|
||||
async def _kick(self, ctx: SlashContext, user: User, reason: str = None) -> None:
|
||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||
async def _kick(self, ctx: InteractionContext, user: User, reason: str) -> None:
|
||||
if not user or user == ctx.author:
|
||||
await ctx.send("You cannot kick yourself.", hidden=True)
|
||||
await ctx.send("You cannot kick yourself.", ephemeral=True)
|
||||
return
|
||||
if user == self.bot.user:
|
||||
await ctx.send("I'm afraid I can't let you do that", hidden=True)
|
||||
await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||
return
|
||||
if not reason:
|
||||
reason = "Mr. Stark is displeased with your presence. Please leave."
|
||||
guild_name = ctx.guild.name
|
||||
embed = build_embed(
|
||||
title=f"You have been kicked from {guild_name}",
|
||||
|
@ -56,10 +41,10 @@ class KickCog(CacheCog):
|
|||
)
|
||||
|
||||
embed.set_author(
|
||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
|
||||
send_failed = False
|
||||
try:
|
||||
|
@ -68,19 +53,16 @@ class KickCog(CacheCog):
|
|||
send_failed = True
|
||||
await ctx.guild.kick(user, reason=reason)
|
||||
|
||||
fields = [Field(name="DM Sent?", value=str(not send_failed))]
|
||||
fields = [EmbedField(name="DM Sent?", value=str(not send_failed))]
|
||||
embed = build_embed(
|
||||
title="User Kicked",
|
||||
description=f"Reason: {reason}",
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
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)
|
||||
_ = Kick(
|
||||
|
|
|
@ -1,134 +1,113 @@
|
|||
"""J.A.R.V.I.S. LockCog."""
|
||||
from contextlib import suppress
|
||||
from typing import Union
|
||||
from dis_snek import Scale
|
||||
|
||||
from discord import Role, TextChannel, User, VoiceChannel
|
||||
from discord.ext.commands import Bot
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
|
||||
from jarvis.db.models import Lock
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
# TODO: Uncomment 99% of code once implementation is figured out
|
||||
# from contextlib import suppress
|
||||
# from typing import Union
|
||||
#
|
||||
# from dis_snek import InteractionContext, Scale, Snake
|
||||
# from dis_snek.models.discord.enums import Permissions
|
||||
# from dis_snek.models.discord.role import Role
|
||||
# from dis_snek.models.discord.user import User
|
||||
# from dis_snek.models.discord.channel import GuildText, GuildVoice, PermissionOverwrite
|
||||
# from dis_snek.models.snek.application_commands import (
|
||||
# OptionTypes,
|
||||
# PermissionTypes,
|
||||
# slash_command,
|
||||
# slash_option,
|
||||
# )
|
||||
# from dis_snek.models.snek.command import check
|
||||
#
|
||||
# from jarvis.db.models import Lock
|
||||
# from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class LockCog(CacheCog):
|
||||
class LockCog(Scale):
|
||||
"""J.A.R.V.I.S. LockCog."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
super().__init__(bot)
|
||||
|
||||
async def _lock_channel(
|
||||
self,
|
||||
channel: Union[TextChannel, VoiceChannel],
|
||||
role: Role,
|
||||
admin: User,
|
||||
reason: str,
|
||||
allow_send: bool = False,
|
||||
) -> None:
|
||||
overrides = channel.overwrites_for(role)
|
||||
if isinstance(channel, TextChannel):
|
||||
overrides.send_messages = allow_send
|
||||
elif isinstance(channel, VoiceChannel):
|
||||
overrides.speak = allow_send
|
||||
await channel.set_permissions(role, overwrite=overrides, reason=reason)
|
||||
|
||||
async def _unlock_channel(
|
||||
self,
|
||||
channel: Union[TextChannel, VoiceChannel],
|
||||
role: Role,
|
||||
admin: User,
|
||||
) -> None:
|
||||
overrides = channel.overwrites_for(role)
|
||||
if isinstance(channel, TextChannel):
|
||||
overrides.send_messages = None
|
||||
elif isinstance(channel, VoiceChannel):
|
||||
overrides.speak = None
|
||||
await channel.set_permissions(role, overwrite=overrides)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="lock",
|
||||
description="Locks a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Lock Reason",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Lock duration in minutes (default 10)",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to lock",
|
||||
option_type=7,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_channels=True)
|
||||
async def _lock(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
reason: str,
|
||||
duration: int = 10,
|
||||
channel: Union[TextChannel, VoiceChannel] = None,
|
||||
) -> None:
|
||||
await ctx.defer(hidden=True)
|
||||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", hidden=True)
|
||||
return
|
||||
elif duration >= 300:
|
||||
await ctx.send("Duration must be < 5 hours", hidden=True)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
return
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
for role in ctx.guild.roles:
|
||||
with suppress(Exception):
|
||||
await self._lock_channel(channel, role, ctx.author, reason)
|
||||
_ = Lock(
|
||||
channel=channel.id,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
reason=reason,
|
||||
duration=duration,
|
||||
).save()
|
||||
await ctx.send(f"{channel.mention} locked for {duration} minute(s)")
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="unlock",
|
||||
description="Unlocks a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to lock",
|
||||
option_type=7,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_channels=True)
|
||||
async def _unlock(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
channel: Union[TextChannel, VoiceChannel] = 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.", hidden=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")
|
||||
# @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")
|
||||
|
|
|
@ -8,7 +8,8 @@ from discord_slash.utils.manage_commands import create_option
|
|||
|
||||
from jarvis.db.models import Lock
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
# from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class LockdownCog(CacheCog):
|
||||
|
@ -21,34 +22,34 @@ class LockdownCog(CacheCog):
|
|||
base="lockdown",
|
||||
name="start",
|
||||
description="Locks a server",
|
||||
options=[
|
||||
choices=[
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Lockdown Reason",
|
||||
option_type=3,
|
||||
opt_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Lockdown duration in minutes (default 10)",
|
||||
option_type=4,
|
||||
opt_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_channels=True)
|
||||
# @check(admin_or_permissions(manage_channels=True))
|
||||
async def _lockdown_start(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
reason: str,
|
||||
duration: int = 10,
|
||||
) -> None:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", hidden=True)
|
||||
await ctx.send("Duration must be > 0", ephemeral=True)
|
||||
return
|
||||
elif duration >= 300:
|
||||
await ctx.send("Duration must be < 5 hours", hidden=True)
|
||||
await ctx.send("Duration must be < 5 hours", ephemeral=True)
|
||||
return
|
||||
channels = ctx.guild.channels
|
||||
roles = ctx.guild.roles
|
||||
|
@ -87,7 +88,7 @@ class LockdownCog(CacheCog):
|
|||
update = False
|
||||
locks = Lock.objects(guild=ctx.guild.id, active=True)
|
||||
if not locks:
|
||||
await ctx.send("No lockdown detected.", hidden=True)
|
||||
await ctx.send("No lockdown detected.", ephemeral=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
for channel in channels:
|
||||
|
|
|
@ -1,132 +1,125 @@
|
|||
"""J.A.R.V.I.S. MuteCog."""
|
||||
from discord import Member
|
||||
from discord.ext import commands
|
||||
from discord.utils import get
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from datetime import datetime
|
||||
|
||||
from jarvis.db.models import Mute, Setting
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.user import Member
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.db.models import Mute
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class MuteCog(commands.Cog):
|
||||
class MuteCog(Scale):
|
||||
"""J.A.R.V.I.S. MuteCog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="mute",
|
||||
description="Mute a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to mute",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Reason for mute",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Duration of mute in minutes, default 30",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
@slash_command(name="mute", description="Mute a user")
|
||||
@slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True)
|
||||
@slash_option(
|
||||
name="reason",
|
||||
description="Reason for mute",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="time",
|
||||
description="Duration of mute, default 1",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
)
|
||||
@slash_option(
|
||||
name="scale",
|
||||
description="Time scale, default Hour(s)",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Minute(s)", value=1),
|
||||
SlashCommandChoice(name="Hour(s)", value=60),
|
||||
SlashCommandChoice(name="Day(s)", value=3600),
|
||||
SlashCommandChoice(name="Week(s)", value=604800),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(mute_members=True)
|
||||
async def _mute(self, ctx: SlashContext, user: Member, reason: str, duration: int = 30) -> None:
|
||||
@check(
|
||||
admin_or_permissions(
|
||||
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||
)
|
||||
)
|
||||
async def _timeout(
|
||||
self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60
|
||||
) -> None:
|
||||
if user == ctx.author:
|
||||
await ctx.send("You cannot mute yourself.", hidden=True)
|
||||
await ctx.send("You cannot mute yourself.", ephemeral=True)
|
||||
return
|
||||
if user == self.bot.user:
|
||||
await ctx.send("I'm afraid I can't let you do that", hidden=True)
|
||||
await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||
return
|
||||
mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first()
|
||||
if not mute_setting:
|
||||
await ctx.send(
|
||||
"Please configure a mute role with /settings mute <role> first",
|
||||
hidden=True,
|
||||
)
|
||||
|
||||
# Max 4 weeks (2419200 seconds) per API
|
||||
duration = time * scale
|
||||
if duration > 2419200:
|
||||
await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True)
|
||||
return
|
||||
role = get(ctx.guild.roles, id=mute_setting.value)
|
||||
if role in user.roles:
|
||||
await ctx.send("User already muted", hidden=True)
|
||||
return
|
||||
await user.add_roles(role, reason=reason)
|
||||
if duration < 0 or duration > 300:
|
||||
duration = -1
|
||||
|
||||
await user.timeout(communication_disabled_until=duration, reason=reason)
|
||||
_ = Mute(
|
||||
user=user.id,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
duration=duration,
|
||||
active=True if duration >= 0 else False,
|
||||
active=True,
|
||||
).save()
|
||||
|
||||
embed = build_embed(
|
||||
title="User Muted",
|
||||
description=f"{user.mention} has been muted",
|
||||
fields=[Field(name="Reason", value=reason)],
|
||||
fields=[EmbedField(name="Reason", value=reason)],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
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)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="unmute",
|
||||
description="Unmute a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to unmute",
|
||||
option_type=6,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
@slash_command(name="unmute", description="Unmute a user")
|
||||
@slash_option(
|
||||
name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True
|
||||
)
|
||||
@admin_or_permissions(mute_members=True)
|
||||
async def _unmute(self, ctx: SlashContext, user: Member) -> None:
|
||||
mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first()
|
||||
if not mute_setting:
|
||||
await ctx.send(
|
||||
"Please configure a mute role with /settings mute <role> first.",
|
||||
hidden=True,
|
||||
)
|
||||
@check(
|
||||
admin_or_permissions(
|
||||
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||
)
|
||||
)
|
||||
async def _unmute(self, ctx: InteractionContext, user: Member) -> None:
|
||||
if (
|
||||
not user.communication_disabled_until
|
||||
or user.communication_disabled_until < datetime.now() # noqa: W503
|
||||
):
|
||||
await ctx.send("User is not muted", ephemeral=True)
|
||||
return
|
||||
|
||||
role = get(ctx.guild.roles, id=mute_setting.value)
|
||||
if role in user.roles:
|
||||
await user.remove_roles(role, reason="Unmute")
|
||||
else:
|
||||
await ctx.send("User is not muted.", hidden=True)
|
||||
return
|
||||
|
||||
_ = Mute.objects(guild=ctx.guild.id, user=user.id).update(set__active=False)
|
||||
embed = build_embed(
|
||||
title="User Unmuted",
|
||||
description=f"{user.mention} has been unmuted",
|
||||
fields=[],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
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)
|
||||
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)
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
"""J.A.R.V.I.S. PurgeCog."""
|
||||
from discord import TextChannel
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
from dis_snek.models.discord.channel import GuildText
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.db.models import Autopurge, Purge
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class PurgeCog(commands.Cog):
|
||||
class PurgeCog(Scale):
|
||||
"""J.A.R.V.I.S. PurgeCog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="purge",
|
||||
description="Purge messages from channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="amount",
|
||||
description="Amount of messages to purge",
|
||||
required=False,
|
||||
option_type=4,
|
||||
)
|
||||
],
|
||||
@slash_command(name="purge", description="Purge messages from channel")
|
||||
@slash_option(
|
||||
name="amount",
|
||||
description="Amount of messages to purge, default 10",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _purge(self, ctx: SlashContext, amount: int = 10) -> None:
|
||||
@check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
|
||||
async def _purge(self, ctx: InteractionContext, amount: int = 10) -> None:
|
||||
if amount < 1:
|
||||
await ctx.send("Amount must be >= 1", hidden=True)
|
||||
await ctx.send("Amount must be >= 1", ephemeral=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
channel = ctx.channel
|
||||
|
@ -44,39 +43,37 @@ class PurgeCog(commands.Cog):
|
|||
count=amount,
|
||||
).save()
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autopurge",
|
||||
name="add",
|
||||
description="Automatically purge messages after x seconds",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to autopurge",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="delay",
|
||||
description="Seconds to keep message before purge, default 30",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages"
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _autopurge_add(self, ctx: SlashContext, channel: TextChannel, delay: int = 30) -> None:
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Channel to autopurge",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="delay",
|
||||
description="Seconds to keep message before purge, default 30",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
|
||||
async def _autopurge_add(
|
||||
self, ctx: InteractionContext, channel: GuildText, delay: int = 30
|
||||
) -> None:
|
||||
if not isinstance(channel, GuildText):
|
||||
await ctx.send("Channel must be a GuildText channel", ephemeral=True)
|
||||
return
|
||||
if delay <= 0:
|
||||
await ctx.send("Delay must be > 0", hidden=True)
|
||||
await ctx.send("Delay must be > 0", ephemeral=True)
|
||||
return
|
||||
elif delay > 300:
|
||||
await ctx.send("Delay must be < 5 minutes", hidden=True)
|
||||
await ctx.send("Delay must be < 5 minutes", ephemeral=True)
|
||||
return
|
||||
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if autopurge:
|
||||
await ctx.send("Autopurge already exists.", hidden=True)
|
||||
await ctx.send("Autopurge already exists.", ephemeral=True)
|
||||
return
|
||||
_ = Autopurge(
|
||||
guild=ctx.guild.id,
|
||||
|
@ -86,52 +83,48 @@ class PurgeCog(commands.Cog):
|
|||
).save()
|
||||
await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autopurge",
|
||||
name="remove",
|
||||
description="Remove an autopurge",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to remove from autopurge",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="autopurge", sub_cmd_name="remove", sub_cmd_description="Remove an autopurge"
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _autopurge_remove(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Channel to remove from autopurge",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@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)
|
||||
if not autopurge:
|
||||
await ctx.send("Autopurge does not exist.", hidden=True)
|
||||
await ctx.send("Autopurge does not exist.", ephemeral=True)
|
||||
return
|
||||
autopurge.delete()
|
||||
await ctx.send(f"Autopurge removed from {channel.mention}.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autopurge",
|
||||
name="update",
|
||||
description="Update autopurge on a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to update",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="delay",
|
||||
description="New time to save",
|
||||
option_type=4,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="autopurge",
|
||||
sub_cmd_name="update",
|
||||
sub_cmd_description="Update autopurge on a channel",
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _autopurge_update(self, ctx: SlashContext, channel: TextChannel, delay: int) -> None:
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Channel to update",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="delay",
|
||||
description="New time to save",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
|
||||
async def _autopurge_update(
|
||||
self, ctx: InteractionContext, channel: GuildText, delay: int
|
||||
) -> None:
|
||||
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
|
||||
if not autopurge:
|
||||
await ctx.send("Autopurge does not exist.", hidden=True)
|
||||
await ctx.send("Autopurge does not exist.", ephemeral=True)
|
||||
return
|
||||
autopurge.delay = delay
|
||||
autopurge.save()
|
||||
|
|
|
@ -1,44 +1,39 @@
|
|||
"""J.A.R.V.I.S. RolepingCog."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ButtonPaginator import Paginator
|
||||
from discord import Member, Role
|
||||
from discord.ext.commands import Bot
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from dis_snek import InteractionContext, Permissions, Snake
|
||||
from dis_snek.ext.paginators import Paginator
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.role import Role
|
||||
from dis_snek.models.discord.user import Member
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.db.models import Roleping
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class RolepingCog(CacheCog):
|
||||
"""J.A.R.V.I.S. RolepingCog."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
name="add",
|
||||
description="Add a role to roleping",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to add to roleping",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
@slash_command(
|
||||
name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping"
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _roleping_add(self, ctx: SlashContext, role: Role) -> None:
|
||||
@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()
|
||||
if roleping:
|
||||
await ctx.send(f"Role `{role.name}` already in roleping.", hidden=True)
|
||||
await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True)
|
||||
return
|
||||
_ = Roleping(
|
||||
role=role.id,
|
||||
|
@ -49,55 +44,45 @@ class RolepingCog(CacheCog):
|
|||
).save()
|
||||
await ctx.send(f"Role `{role.name}` added to roleping.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
name="remove",
|
||||
description="Remove a role from the roleping",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to remove from roleping",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
@slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role")
|
||||
@slash_option(
|
||||
name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _roleping_remove(self, ctx: SlashContext, role: Role) -> None:
|
||||
@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)
|
||||
if not roleping:
|
||||
await ctx.send("Roleping does not exist", hidden=True)
|
||||
await ctx.send("Roleping does not exist", ephemeral=True)
|
||||
return
|
||||
|
||||
roleping.delete()
|
||||
await ctx.send(f"Role `{role.name}` removed from roleping.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
name="list",
|
||||
description="List all blocklisted roles",
|
||||
)
|
||||
async def _roleping_list(self, ctx: SlashContext) -> None:
|
||||
@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(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
rolepings = Roleping.objects(guild=ctx.guild.id)
|
||||
if not rolepings:
|
||||
await ctx.send("No rolepings configured", hidden=True)
|
||||
await ctx.send("No rolepings configured", ephemeral=True)
|
||||
return
|
||||
|
||||
embeds = []
|
||||
for roleping in rolepings:
|
||||
role = ctx.guild.get_role(roleping.role)
|
||||
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]
|
||||
bypass_users = [ctx.guild.get_member(u).mention or "||`[redacted]`||" for u in roleping.bypass["users"]]
|
||||
bypass_users = [
|
||||
await ctx.guild.get_member(u).mention or "||`[redacted]`||"
|
||||
for u in roleping.bypass["users"]
|
||||
]
|
||||
bypass_roles = bypass_roles or ["None"]
|
||||
bypass_users = bypass_users or ["None"]
|
||||
embed = build_embed(
|
||||
|
@ -105,44 +90,33 @@ class RolepingCog(CacheCog):
|
|||
description=role.mention,
|
||||
color=str(role.color),
|
||||
fields=[
|
||||
Field(
|
||||
EmbedField(
|
||||
name="Created At",
|
||||
value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"),
|
||||
inline=False,
|
||||
),
|
||||
Field(name="Active", value=str(roleping.active)),
|
||||
Field(
|
||||
EmbedField(name="Active", value=str(roleping.active)),
|
||||
EmbedField(
|
||||
name="Bypass Users",
|
||||
value="\n".join(bypass_users),
|
||||
),
|
||||
Field(
|
||||
EmbedField(
|
||||
name="Bypass Roles",
|
||||
value="\n".join(bypass_roles),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
admin = ctx.guild.get_member(roleping.admin)
|
||||
admin = await ctx.guild.get_member(roleping.admin)
|
||||
if not admin:
|
||||
admin = self.bot.user
|
||||
|
||||
embed.set_author(name=admin.nick or admin.name, icon_url=admin.avatar_url)
|
||||
embed.set_author(name=admin.display_name, icon_url=admin.display_avatar.url)
|
||||
embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}")
|
||||
|
||||
embeds.append(embed)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=embeds,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(embeds) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
paginator = Paginator.create_from_embeds(self.bot, *embeds, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
|
@ -152,45 +126,37 @@ class RolepingCog(CacheCog):
|
|||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
||||
await paginator.send(ctx)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
subcommand_group="bypass",
|
||||
name="user",
|
||||
description="Add a user as a bypass to a roleping",
|
||||
base_desc="Block roles from being pinged",
|
||||
sub_group_desc="Allow specific users/roles to ping rolepings",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to add",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="rping",
|
||||
description="Rolepinged role",
|
||||
option_type=8,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="roleping",
|
||||
description="Block roles from being pinged",
|
||||
group_name="bypass",
|
||||
group_description="Allow specific users/roles to ping rolepings",
|
||||
sub_cmd_name="user",
|
||||
sub_cmd_description="Add a user as a bypass to a roleping",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _roleping_bypass_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
|
||||
@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
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _roleping_bypass_user(
|
||||
self, ctx: InteractionContext, user: Member, rping: Role
|
||||
) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if user.id in roleping.bypass["users"]:
|
||||
await ctx.send(f"{user.mention} already in bypass", hidden=True)
|
||||
await ctx.send(f"{user.mention} already in bypass", ephemeral=True)
|
||||
return
|
||||
|
||||
if len(roleping.bypass["users"]) == 10:
|
||||
await ctx.send(
|
||||
"Already have 10 users in bypass. Please consider using roles for roleping bypass",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -199,51 +165,40 @@ class RolepingCog(CacheCog):
|
|||
if matching_role:
|
||||
await ctx.send(
|
||||
f"{user.mention} already has bypass via {matching_role[0].mention}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
roleping.bypass["users"].append(user.id)
|
||||
roleping.save()
|
||||
await ctx.send(f"{user.nick or user.name} user bypass added for `{rping.name}`")
|
||||
await ctx.send(f"{user.display_name} user bypass added for `{rping.name}`")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
subcommand_group="bypass",
|
||||
name="role",
|
||||
description="Add a role as a bypass to a roleping",
|
||||
base_desc="Block roles from being pinged",
|
||||
sub_group_desc="Allow specific users/roles to ping rolepings",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to add",
|
||||
option_type=8,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="rping",
|
||||
description="Rolepinged role",
|
||||
option_type=8,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="roleping",
|
||||
group_name="bypass",
|
||||
sub_cmd_name="role",
|
||||
description="Add a role as a bypass to roleping",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _roleping_bypass_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
|
||||
@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
|
||||
)
|
||||
@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()
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if role.id in roleping.bypass["roles"]:
|
||||
await ctx.send(f"{role.mention} already in bypass", hidden=True)
|
||||
await ctx.send(f"{role.mention} already 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",
|
||||
hidden=True,
|
||||
"Already have 10 roles in bypass. "
|
||||
"Please consider consolidating roles for roleping bypass",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -251,80 +206,67 @@ class RolepingCog(CacheCog):
|
|||
roleping.save()
|
||||
await ctx.send(f"{role.name} role bypass added for `{rping.name}`")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
subcommand_group="restore",
|
||||
name="user",
|
||||
description="Remove a role bypass",
|
||||
base_desc="Block roles from being pinged",
|
||||
sub_group_desc="Remove a bypass from a roleping (restoring it)",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to add",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="rping",
|
||||
description="Rolepinged role",
|
||||
option_type=8,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="roleping",
|
||||
description="Block roles from being pinged",
|
||||
group_name="restore",
|
||||
group_description="Remove a roleping bypass",
|
||||
sub_cmd_name="user",
|
||||
sub_cmd_description="Remove a bypass from a roleping (restoring it)",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _roleping_restore_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
|
||||
@slash_option(
|
||||
name="user", description="User to remove", opt_type=OptionTypes.USER, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="rping", 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
|
||||
) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if user.id not in roleping.bypass["users"]:
|
||||
await ctx.send(f"{user.mention} not in bypass", hidden=True)
|
||||
await ctx.send(f"{user.mention} not in bypass", ephemeral=True)
|
||||
return
|
||||
|
||||
roleping.bypass["users"].delete(user.id)
|
||||
roleping.save()
|
||||
await ctx.send(f"{user.nick or user.name} user bypass removed for `{rping.name}`")
|
||||
await ctx.send(f"{user.display_name} user bypass removed for `{rping.name}`")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
subcommand_group="restore",
|
||||
name="role",
|
||||
description="Remove a role bypass",
|
||||
base_desc="Block roles from being pinged",
|
||||
sub_group_desc="Remove a bypass from a roleping (restoring it)",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to add",
|
||||
option_type=8,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="rping",
|
||||
description="Rolepinged role",
|
||||
option_type=8,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="roleping",
|
||||
group_name="restore",
|
||||
sub_cmd_name="role",
|
||||
description="Remove a bypass from a roleping (restoring it)",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _roleping_restore_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
|
||||
@slash_option(
|
||||
name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
|
||||
)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _roleping_restore_role(
|
||||
self, ctx: InteractionContext, role: Role, rping: Role
|
||||
) -> None:
|
||||
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||
if not roleping:
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True)
|
||||
await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
|
||||
return
|
||||
|
||||
if role.id in roleping.bypass["roles"]:
|
||||
await ctx.send(f"{role.mention} already in bypass", hidden=True)
|
||||
await ctx.send(f"{role.mention} already 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",
|
||||
hidden=True,
|
||||
"Already have 10 roles in bypass. "
|
||||
"Please consider consolidating roles for roleping bypass",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
"""J.A.R.V.I.S. WarningCog."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ButtonPaginator import Paginator
|
||||
from discord import User
|
||||
from discord.ext.commands import Bot
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
from dis_snek import InteractionContext, Permissions, Snake
|
||||
from dis_snek.ext.paginators import Paginator
|
||||
from dis_snek.models.discord.user import User
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.db.models import Warning
|
||||
from jarvis.utils import build_embed
|
||||
|
@ -18,43 +22,35 @@ from jarvis.utils.permissions import admin_or_permissions
|
|||
class WarningCog(CacheCog):
|
||||
"""J.A.R.V.I.S. WarningCog."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="warn",
|
||||
description="Warn a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to warn",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Reason for warning",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Duration of warning in hours, default 24",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
@slash_command(name="warn", description="Warn a user")
|
||||
@slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True)
|
||||
@slash_option(
|
||||
name="reason",
|
||||
description="Reason for warning",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _warn(self, ctx: SlashContext, user: User, reason: str, duration: int = 24) -> None:
|
||||
@slash_option(
|
||||
name="duration",
|
||||
description="Duration of warning in hours, default 24",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _warn(
|
||||
self, ctx: InteractionContext, user: User, reason: str, duration: int = 24
|
||||
) -> None:
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||
return
|
||||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", hidden=True)
|
||||
await ctx.send("Duration must be > 0", ephemeral=True)
|
||||
return
|
||||
elif duration >= 120:
|
||||
await ctx.send("Duration must be < 5 days", hidden=True)
|
||||
await ctx.send("Duration must be < 5 days", ephemeral=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
_ = Warning(
|
||||
|
@ -71,52 +67,41 @@ class WarningCog(CacheCog):
|
|||
description=f"{user.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="warnings",
|
||||
description="Get count of user warnings",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to view",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="active",
|
||||
description="View only active",
|
||||
option_type=4,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(name="Yes", value=1),
|
||||
create_choice(name="No", value=0),
|
||||
],
|
||||
),
|
||||
@slash_command(name="warnings", description="Get count of user warnings")
|
||||
@slash_option(name="user", description="User to view", opt_type=OptionTypes.USER, required=True)
|
||||
@slash_option(
|
||||
name="active",
|
||||
description="View active only",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Yes", value=1),
|
||||
SlashCommandChoice(name="No", value=0),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _warnings(self, ctx: SlashContext, user: User, active: bool = 1) -> None:
|
||||
@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(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
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")
|
||||
active_warns = Warning.objects(user=user.id, guild=ctx.guild.id, active=True).order_by(
|
||||
"-created_at"
|
||||
)
|
||||
|
||||
pages = []
|
||||
if active:
|
||||
|
@ -126,16 +111,16 @@ class WarningCog(CacheCog):
|
|||
description=f"{warnings.count()} total | 0 currently active",
|
||||
fields=[],
|
||||
)
|
||||
embed.set_author(name=user.name, icon_url=user.avatar_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_author(name=user.username, icon_url=user.display_avatar.url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
pages.append(embed)
|
||||
else:
|
||||
fields = []
|
||||
for warn in active_warns:
|
||||
admin = ctx.guild.get_member(warn.admin)
|
||||
admin = await ctx.guild.get_member(warn.admin)
|
||||
admin_name = "||`[redacted]`||"
|
||||
if admin:
|
||||
admin_name = f"{admin.name}#{admin.discriminator}"
|
||||
admin_name = f"{admin.username}#{admin.discriminator}"
|
||||
fields.append(
|
||||
Field(
|
||||
name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
||||
|
@ -146,15 +131,17 @@ 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",
|
||||
fields=fields[i : i + 5], # noqa: E203
|
||||
description=(
|
||||
f"{warnings.count()} total | {active_warns.count()} currently active"
|
||||
),
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.name + "#" + user.discriminator,
|
||||
icon_url=user.avatar_url,
|
||||
name=user.username + "#" + user.discriminator,
|
||||
icon_url=user.display_avatar.url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||
pages.append(embed)
|
||||
else:
|
||||
fields = []
|
||||
|
@ -171,28 +158,18 @@ 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",
|
||||
fields=fields[i : i + 5], # noqa: E203
|
||||
description=(
|
||||
f"{warnings.count()} total | {active_warns.count()} currently active"
|
||||
),
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.name + "#" + user.discriminator,
|
||||
icon_url=user.avatar_url,
|
||||
name=user.username + "#" + user.discriminator, icon_url=user.display_avatar.url
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
pages.append(embed)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
paginator = Paginator(bot=self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"guild": ctx.guild.id,
|
||||
|
@ -204,4 +181,4 @@ class WarningCog(CacheCog):
|
|||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
||||
await paginator.send(ctx)
|
||||
|
|
|
@ -1,46 +1,45 @@
|
|||
"""J.A.R.V.I.S. Autoreact Cog."""
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from discord import TextChannel
|
||||
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 dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
from dis_snek.models.discord.channel import GuildText
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
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
|
||||
|
||||
|
||||
class AutoReactCog(commands.Cog):
|
||||
class AutoReactCog(Scale):
|
||||
"""J.A.R.V.I.S. Autoreact Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.custom_emote = re.compile(r"^<:\w+:(\d+)>$")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autoreact",
|
||||
name="create",
|
||||
description="Add an autoreact to a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to monitor",
|
||||
option_type=7,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _autoreact_create(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a text channel", hidden=True)
|
||||
return
|
||||
async def create_autoreact(
|
||||
self, ctx: InteractionContext, channel: GuildText
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
Create an autoreact monitor on a channel.
|
||||
|
||||
Args:
|
||||
ctx: Interaction context of command
|
||||
channel: Channel to monitor
|
||||
|
||||
Returns:
|
||||
Tuple of success? and error message
|
||||
"""
|
||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if exists:
|
||||
await ctx.send(f"Autoreact already exists for {channel.mention}.", hidden=True)
|
||||
return
|
||||
return False, f"Autoreact already exists for {channel.mention}."
|
||||
|
||||
_ = Autoreact(
|
||||
guild=ctx.guild.id,
|
||||
|
@ -48,152 +47,145 @@ class AutoReactCog(commands.Cog):
|
|||
reactions=[],
|
||||
admin=ctx.author.id,
|
||||
).save()
|
||||
await ctx.send(f"Autoreact created for {channel.mention}!")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autoreact",
|
||||
name="delete",
|
||||
description="Delete an autoreact from a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to stop monitoring",
|
||||
option_type=7,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _autoreact_delete(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete()
|
||||
if exists:
|
||||
await ctx.send(f"Autoreact removed from {channel.mention}")
|
||||
else:
|
||||
await ctx.send(f"Autoreact not found on {channel.mention}", hidden=True)
|
||||
return True, None
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autoreact",
|
||||
name="add",
|
||||
description="Add an autoreact emote to an existing autoreact",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Autoreact channel to add emote to",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="emote",
|
||||
description="Emote to add",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
async def delete_autoreact(self, ctx: InteractionContext, channel: GuildText) -> bool:
|
||||
"""
|
||||
Remove an autoreact monitor on a channel.
|
||||
|
||||
Args:
|
||||
ctx: Interaction context of command
|
||||
channel: Channel to stop monitoring
|
||||
|
||||
Returns:
|
||||
Success?
|
||||
"""
|
||||
return Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete() is not None
|
||||
|
||||
@slash_command(
|
||||
name="autoreact",
|
||||
sub_cmd_name="add",
|
||||
sub_cmd_description="Add an autoreact emote to a channel",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _autoreact_add(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None:
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Autoreact channel to add emote to",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _autoreact_add(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None:
|
||||
await ctx.defer()
|
||||
custom_emoji = self.custom_emote.match(emote)
|
||||
standard_emoji = emote in emoji_list
|
||||
if not custom_emoji and not standard_emoji:
|
||||
await ctx.send(
|
||||
"Please use either an emote from this server or a unicode emoji.",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
if custom_emoji:
|
||||
emoji_id = int(custom_emoji.group(1))
|
||||
if not find(lambda x: x.id == emoji_id, ctx.guild.emojis):
|
||||
await ctx.send("Please use a custom emote from this server.", hidden=True)
|
||||
await ctx.send("Please use a custom emote from this server.", ephemeral=True)
|
||||
return
|
||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if not exists:
|
||||
await ctx.send(f"Please create autoreact first with /autoreact create {channel.mention}")
|
||||
return
|
||||
if emote in exists.reactions:
|
||||
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if not autoreact:
|
||||
self.create_autoreact(ctx, channel)
|
||||
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if emote in autoreact.reactions:
|
||||
await ctx.send(
|
||||
f"Emote already added to {channel.mention} autoreactions.",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
if len(exists.reactions) >= 5:
|
||||
if len(autoreact.reactions) >= 5:
|
||||
await ctx.send(
|
||||
"Max number of reactions hit. Remove a different one to add this one",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
exists.reactions.append(emote)
|
||||
exists.save()
|
||||
autoreact.reactions.append(emote)
|
||||
autoreact.save()
|
||||
await ctx.send(f"Added {emote} to {channel.mention} autoreact.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autoreact",
|
||||
name="remove",
|
||||
description="Remove an autoreact emote from an existing autoreact",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Autoreact channel to remove emote from",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="emote",
|
||||
description="Emote to remove",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="autoreact",
|
||||
sub_cmd_name="remove",
|
||||
sub_cmd_description="Remove an autoreact emote to a channel",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _autoreact_remove(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None:
|
||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if not exists:
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Autoreact channel to remove emote from",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="emote",
|
||||
description="Emote to remove (use all to delete)",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _autoreact_remove(
|
||||
self, ctx: InteractionContext, channel: GuildText, emote: str
|
||||
) -> None:
|
||||
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if not autoreact:
|
||||
await ctx.send(
|
||||
f"Please create autoreact first with /autoreact create {channel.mention}",
|
||||
hidden=True,
|
||||
f"Please create autoreact first with /autoreact add {channel.mention} {emote}",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
if emote not in exists.reactions:
|
||||
if emote.lower() == "all":
|
||||
self.delete_autoreact(ctx, channel)
|
||||
await ctx.send(f"Autoreact removed from {channel.mention}")
|
||||
elif emote not in autoreact.reactions:
|
||||
await ctx.send(
|
||||
f"{emote} not used in {channel.mention} autoreactions.",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
exists.reactions.remove(emote)
|
||||
exists.save()
|
||||
await ctx.send(f"Removed {emote} from {channel.mention} autoreact.")
|
||||
else:
|
||||
autoreact.reactions.remove(emote)
|
||||
autoreact.save()
|
||||
if len(autoreact.reactions) == 0:
|
||||
self.delete_autoreact(ctx, channel)
|
||||
await ctx.send(f"Removed {emote} from {channel.mention} autoreact.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autoreact",
|
||||
name="list",
|
||||
description="List all autoreacts on a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Autoreact channel to list",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="autoreact",
|
||||
sub_cmd_name="list",
|
||||
sub_cmd_description="List all autoreacts on a channel",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _autoreact_list(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Autoreact channel to list",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||
if not exists:
|
||||
await ctx.send(
|
||||
f"Please create autoreact first with /autoreact create {channel.mention}",
|
||||
hidden=True,
|
||||
f"Please create autoreact first with /autoreact add {channel.mention} <emote>",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
message = ""
|
||||
if len(exists.reactions) > 0:
|
||||
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(exists.reactions)
|
||||
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(
|
||||
exists.reactions
|
||||
)
|
||||
else:
|
||||
message = f"No reactions set on {channel.mention}"
|
||||
await ctx.send(message)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add AutoReactCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(AutoReactCog(bot))
|
||||
AutoReactCog(bot)
|
||||
|
|
|
@ -3,22 +3,23 @@ import re
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import aiohttp
|
||||
from ButtonPaginator import Paginator
|
||||
from discord import Member, User
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from dis_snek import InteractionContext, Snake
|
||||
from dis_snek.ext.paginators import Paginator
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
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.db.models import Guess
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
guild_ids = [578757004059738142, 520021794380447745, 862402786116763668]
|
||||
|
||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
@ -26,7 +27,7 @@ invites = re.compile(
|
|||
class CTCCog(CacheCog):
|
||||
"""J.A.R.V.I.S. Complete the Code 2 Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
self._session = aiohttp.ClientSession()
|
||||
self.url = "https://completethecodetwo.cards/pw"
|
||||
|
@ -34,45 +35,48 @@ class CTCCog(CacheCog):
|
|||
def __del__(self):
|
||||
self._session.close()
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="ctc2",
|
||||
name="about",
|
||||
description="CTC2 related commands",
|
||||
guild_ids=guild_ids,
|
||||
@slash_command(
|
||||
name="ctc2", sub_cmd_name="about", description="CTC2 related commands", scopes=guild_ids
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _about(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _about(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send("See https://completethecode.com for more information")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="ctc2",
|
||||
name="pw",
|
||||
description="Guess a password for https://completethecodetwo.cards",
|
||||
guild_ids=guild_ids,
|
||||
@slash_command(
|
||||
name="ctc2",
|
||||
sub_cmd_name="pw",
|
||||
sub_cmd_description="Guess a password for https://completethecodetwo.cards",
|
||||
scopes=guild_ids,
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _pw(self, ctx: SlashContext, guess: str) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _pw(self, ctx: InteractionContext, guess: str) -> None:
|
||||
if len(guess) > 800:
|
||||
await ctx.send(
|
||||
"Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses < 800 characters.",
|
||||
hidden=True,
|
||||
(
|
||||
"Listen here, dipshit. Don't be like <@256110768724901889>. "
|
||||
"Make your guesses < 800 characters."
|
||||
),
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
elif not valid.fullmatch(guess):
|
||||
await ctx.send(
|
||||
"Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses *readable*.",
|
||||
hidden=True,
|
||||
(
|
||||
"Listen here, dipshit. Don't be like <@256110768724901889>. "
|
||||
"Make your guesses *readable*."
|
||||
),
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
elif invites.search(guess):
|
||||
await ctx.send(
|
||||
"Listen here, dipshit. No using this to bypass sending invite links.",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
guessed = Guess.objects(guess=guess).first()
|
||||
if guessed:
|
||||
await ctx.send("Already guessed, dipshit.", hidden=True)
|
||||
await ctx.send("Already guessed, dipshit.", ephemeral=True)
|
||||
return
|
||||
result = await self._session.post(self.url, data=guess)
|
||||
correct = False
|
||||
|
@ -80,30 +84,30 @@ class CTCCog(CacheCog):
|
|||
await ctx.send(f"{ctx.author.mention} got it! Password is {guess}!")
|
||||
correct = True
|
||||
else:
|
||||
await ctx.send("Nope.", hidden=True)
|
||||
await ctx.send("Nope.", ephemeral=True)
|
||||
_ = Guess(guess=guess, user=ctx.author.id, correct=correct).save()
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="ctc2",
|
||||
name="guesses",
|
||||
description="Show guesses made for https://completethecodetwo.cards",
|
||||
guild_ids=guild_ids,
|
||||
@slash_command(
|
||||
name="ctc2",
|
||||
sub_cmd_name="guesses",
|
||||
sub_cmd_description="Show guesses made for https://completethecodetwo.cards",
|
||||
scopes=guild_ids,
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _guesses(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _guesses(self, ctx: InteractionContext) -> None:
|
||||
exists = self.check_cache(ctx)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
guesses = Guess.objects().order_by("-correct", "-id")
|
||||
fields = []
|
||||
for guess in guesses:
|
||||
user = ctx.guild.get_member(guess["user"])
|
||||
user = await ctx.guild.get_member(guess["user"])
|
||||
if not user:
|
||||
user = await self.bot.fetch_user(guess["user"])
|
||||
if not user:
|
||||
|
@ -113,7 +117,7 @@ class CTCCog(CacheCog):
|
|||
name = "Correctly" if guess["correct"] else "Incorrectly"
|
||||
name += " guessed by: " + user
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name=name,
|
||||
value=guess["guess"] + "\n\u200b",
|
||||
inline=False,
|
||||
|
@ -124,7 +128,7 @@ class CTCCog(CacheCog):
|
|||
embed = build_embed(
|
||||
title="completethecodetwo.cards guesses",
|
||||
description=f"{len(fields)} guesses so far",
|
||||
fields=fields[i : i + 5], # noqa: E203
|
||||
fields=fields[i : i + 5],
|
||||
url="https://completethecodetwo.cards",
|
||||
)
|
||||
embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png")
|
||||
|
@ -134,18 +138,7 @@ class CTCCog(CacheCog):
|
|||
)
|
||||
pages.append(embed)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
only=ctx.author,
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"guild": ctx.guild.id,
|
||||
|
@ -155,9 +148,9 @@ class CTCCog(CacheCog):
|
|||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
||||
await paginator.send(ctx)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add CTCCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(CTCCog(bot))
|
||||
CTCCog(bot)
|
||||
|
|
|
@ -2,26 +2,31 @@
|
|||
import re
|
||||
|
||||
import aiohttp
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
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.config import get_config
|
||||
from jarvis.data.dbrand import shipping_lookup
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
guild_ids = [578757004059738142, 520021794380447745, 862402786116763668]
|
||||
|
||||
|
||||
class DbrandCog(commands.Cog):
|
||||
class DbrandCog(Scale):
|
||||
"""
|
||||
dbrand functions for J.A.R.V.I.S.
|
||||
|
||||
Mostly support functions. Credit @cpixl for the shipping API
|
||||
"""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.base_url = "https://dbrand.com/"
|
||||
self._session = aiohttp.ClientSession()
|
||||
|
@ -32,134 +37,130 @@ class DbrandCog(commands.Cog):
|
|||
def __del__(self):
|
||||
self._session.close()
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="skin",
|
||||
guild_ids=guild_ids,
|
||||
description="See what skins are available",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="skin",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="See what skins are available",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _skin(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _skin(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(self.base_url + "/skins")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="robotcamo",
|
||||
guild_ids=guild_ids,
|
||||
description="Get some robot camo. Make Tony Stark proud",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="robotcamo",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="Get some robot camo. Make Tony Stark proud",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _camo(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _camo(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(self.base_url + "robot-camo")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="grip",
|
||||
guild_ids=guild_ids,
|
||||
description="See devices with Grip support",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="grip",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="See devices with Grip support",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _grip(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _grip(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(self.base_url + "grip")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="contact",
|
||||
guild_ids=guild_ids,
|
||||
description="Contact support",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="contact",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="Contact support",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _contact(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _contact(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="support",
|
||||
guild_ids=guild_ids,
|
||||
description="Contact support",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="support",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="Contact support",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _support(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _support(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="orderstat",
|
||||
guild_ids=guild_ids,
|
||||
description="Get your order status",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="orderstat",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="Get your order status",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _orderstat(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _orderstat(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(self.base_url + "order-status")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="orders",
|
||||
guild_ids=guild_ids,
|
||||
description="Get your order status",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="orders",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="Get your order status",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _orders(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _orders(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(self.base_url + "order-status")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="status",
|
||||
guild_ids=guild_ids,
|
||||
description="dbrand status",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="status",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="dbrand status",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _status(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _status(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(self.base_url + "status")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="buy",
|
||||
guild_ids=guild_ids,
|
||||
description="Give us your money!",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="buy",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="Give us your money!",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _buy(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _buy(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send("Give us your money! " + self.base_url + "shop")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="extortion",
|
||||
guild_ids=guild_ids,
|
||||
description="(not) extortion",
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="extortion",
|
||||
scopes=guild_ids,
|
||||
sub_cmd_description="(not) extortion",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _extort(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _extort(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="wallpapers",
|
||||
description="Robot Camo Wallpapers",
|
||||
guild_ids=guild_ids,
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="wallpapers",
|
||||
sub_cmd_description="Robot Camo Wallpapers",
|
||||
scopes=guild_ids,
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _wallpapers(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||
async def _wallpapers(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="db",
|
||||
name="ship",
|
||||
description="Get shipping information for your country",
|
||||
guild_ids=guild_ids,
|
||||
options=[
|
||||
(
|
||||
create_option(
|
||||
name="search",
|
||||
description="Country search query (2 character code, country name, emoji)",
|
||||
option_type=3,
|
||||
required=True,
|
||||
)
|
||||
)
|
||||
],
|
||||
@slash_command(
|
||||
name="db",
|
||||
sub_cmd_name="ship",
|
||||
sub_cmd_description="Get shipping information for your country",
|
||||
scopes=guild_ids,
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _shipping(self, ctx: SlashContext, search: str) -> None:
|
||||
@slash_option(
|
||||
name="search",
|
||||
description="Country search query (2 character code, country name, flag emoji)",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _shipping(self, ctx: InteractionContext, search: str) -> None:
|
||||
await ctx.defer()
|
||||
if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
|
||||
if re.match(
|
||||
|
@ -173,7 +174,6 @@ class DbrandCog(commands.Cog):
|
|||
elif search == "🏳️":
|
||||
search = "fr"
|
||||
else:
|
||||
print(search)
|
||||
await ctx.send("Please use text to search for shipping.")
|
||||
return
|
||||
if len(search) > 2:
|
||||
|
@ -193,14 +193,14 @@ class DbrandCog(commands.Cog):
|
|||
fields = None
|
||||
if data is not None and data["is_valid"] and data["shipping_available"]:
|
||||
fields = []
|
||||
fields.append(Field(data["short-name"], data["time-title"]))
|
||||
fields.append(EmbedField(data["short-name"], 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()
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
service_data["short-name"],
|
||||
service_data["time-title"],
|
||||
)
|
||||
|
@ -215,7 +215,7 @@ class DbrandCog(commands.Cog):
|
|||
)
|
||||
embed = build_embed(
|
||||
title="Shipping to {}".format(data["country"]),
|
||||
description=description,
|
||||
sub_cmd_description=description,
|
||||
color="#FFBB00",
|
||||
fields=fields,
|
||||
url=self.base_url + "shipping/" + country,
|
||||
|
@ -229,8 +229,9 @@ class DbrandCog(commands.Cog):
|
|||
elif not data["is_valid"]:
|
||||
embed = build_embed(
|
||||
title="Check Shipping Times",
|
||||
description=(
|
||||
"Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)"
|
||||
sub_cmd_description=(
|
||||
"Country not found.\nYou can [view all shipping "
|
||||
"destinations here](https://dbrand.com/shipping)"
|
||||
),
|
||||
fields=[],
|
||||
url="https://dbrand.com/shipping",
|
||||
|
@ -245,7 +246,7 @@ class DbrandCog(commands.Cog):
|
|||
elif not data["shipping_available"]:
|
||||
embed = build_embed(
|
||||
title="Shipping to {}".format(data["country"]),
|
||||
description=(
|
||||
sub_cmd_description=(
|
||||
"No shipping available.\nTime to move to a country"
|
||||
" that has shipping available.\nYou can [find a new country "
|
||||
"to live in here](https://dbrand.com/shipping)"
|
||||
|
@ -262,6 +263,6 @@ class DbrandCog(commands.Cog):
|
|||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add dbrandcog to J.A.R.V.I.S."""
|
||||
bot.add_cog(DbrandCog(bot))
|
||||
DbrandCog(bot)
|
||||
|
|
|
@ -8,19 +8,27 @@ from typing import Any, Union
|
|||
|
||||
import ulid as ulidpy
|
||||
from bson import ObjectId
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
|
||||
from jarvis.utils import build_embed, convert_bytesize
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x}
|
||||
|
||||
OID_VERIFY = re.compile(r"^([1-9][0-9]{0,3}|0)(\.([1-9][0-9]{0,3}|0)){5,13}$")
|
||||
URL_VERIFY = re.compile(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
|
||||
URL_VERIFY = re.compile(
|
||||
r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
|
||||
)
|
||||
DN_VERIFY = re.compile(
|
||||
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$"
|
||||
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$" # noqa: E501
|
||||
)
|
||||
ULID_VERIFY = re.compile(r"^[0-9a-z]{26}$", re.IGNORECASE)
|
||||
UUID_VERIFY = re.compile(
|
||||
|
@ -29,7 +37,7 @@ UUID_VERIFY = re.compile(
|
|||
)
|
||||
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
@ -47,43 +55,35 @@ def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str:
|
|||
BSIZE = 65536
|
||||
block_idx = 0
|
||||
while block_idx * BSIZE < len(data):
|
||||
block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)] # noqa: E203
|
||||
block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)]
|
||||
hash.update(block)
|
||||
block_idx += 1
|
||||
return hash.hexdigest()
|
||||
|
||||
|
||||
class DevCog(commands.Cog):
|
||||
class DevCog(Scale):
|
||||
"""J.A.R.V.I.S. Developer Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="hash",
|
||||
description="Hash some data",
|
||||
options=[
|
||||
create_option(
|
||||
name="method",
|
||||
description="Hash method",
|
||||
option_type=3,
|
||||
required=True,
|
||||
choices=[create_choice(name=x, value=x) for x in supported_hashes],
|
||||
),
|
||||
create_option(
|
||||
name="data",
|
||||
description="Data to hash",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(name="hash", description="Hash some data")
|
||||
@slash_option(
|
||||
name="method",
|
||||
description="Hash method",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
choices=[SlashCommandChoice(name=x, value=x) for x in supported_hashes],
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _hash(self, ctx: SlashContext, method: str, data: str) -> None:
|
||||
@slash_option(
|
||||
name="data",
|
||||
description="Data to hash",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _hash(self, ctx: InteractionContext, method: str, data: str) -> None:
|
||||
if not data:
|
||||
await ctx.send(
|
||||
"No data to hash",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
text = True
|
||||
|
@ -94,36 +94,31 @@ class DevCog(commands.Cog):
|
|||
title = data if text else ctx.message.attachments[0].filename
|
||||
description = "Hashed using " + method
|
||||
fields = [
|
||||
Field("Data Size", data_size, False),
|
||||
Field("Hash", f"`{hex}`", False),
|
||||
EmbedField("Data Size", data_size, False),
|
||||
EmbedField("Hash", f"`{hex}`", False),
|
||||
]
|
||||
|
||||
embed = build_embed(title=title, description=description, fields=fields)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="uuid",
|
||||
description="Generate a UUID",
|
||||
options=[
|
||||
create_option(
|
||||
name="version",
|
||||
description="UUID version",
|
||||
option_type=3,
|
||||
required=True,
|
||||
choices=[create_choice(name=x, value=x) for x in ["3", "4", "5"]],
|
||||
),
|
||||
create_option(
|
||||
name="data",
|
||||
description="Data for UUID version 3,5",
|
||||
option_type=3,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
@slash_command(name="uuid", description="Generate a UUID")
|
||||
@slash_option(
|
||||
name="version",
|
||||
description="UUID version",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
choices=[SlashCommandChoice(name=x, value=x) for x in ["3", "4", "5"]],
|
||||
)
|
||||
async def _uuid(self, ctx: SlashContext, version: str, data: str = None) -> None:
|
||||
@slash_option(
|
||||
name="data",
|
||||
description="Data for UUID version 3,5",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=False,
|
||||
)
|
||||
async def _uuid(self, ctx: InteractionContext, version: str, data: str = None) -> None:
|
||||
version = int(version)
|
||||
if version in [3, 5] and not data:
|
||||
await ctx.send(f"UUID{version} requires data.", hidden=True)
|
||||
await ctx.send(f"UUID{version} requires data.", ephemeral=True)
|
||||
return
|
||||
if version == 4:
|
||||
await ctx.send(f"UUID4: `{uuidpy.uuid4()}`")
|
||||
|
@ -139,40 +134,40 @@ class DevCog(commands.Cog):
|
|||
to_send = UUID_GET[version](uuidpy.NAMESPACE_DNS, data)
|
||||
await ctx.send(f"UUID{version}: `{to_send}`")
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="objectid",
|
||||
description="Generate an ObjectID",
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _objectid(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _objectid(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(f"ObjectId: `{str(ObjectId())}`")
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="ulid",
|
||||
description="Generate a ULID",
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _ulid(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _ulid(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(f"ULID: `{ulidpy.new().str}`")
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="uuid2ulid",
|
||||
description="Convert a UUID to a ULID",
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _uuid2ulid(self, ctx: SlashContext, uuid: str) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _uuid2ulid(self, ctx: InteractionContext, uuid: str) -> None:
|
||||
if UUID_VERIFY.match(uuid):
|
||||
u = ulidpy.parse(uuid)
|
||||
await ctx.send(f"ULID: `{u.str}`")
|
||||
else:
|
||||
await ctx.send("Invalid UUID")
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="ulid2uuid",
|
||||
description="Convert a ULID to a UUID",
|
||||
)
|
||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
||||
async def _ulid2uuid(self, ctx: SlashContext, ulid: str) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||
async def _ulid2uuid(self, ctx: InteractionContext, ulid: str) -> None:
|
||||
if ULID_VERIFY.match(ulid):
|
||||
ulid = ulidpy.parse(ulid)
|
||||
await ctx.send(f"UUID: `{ulid.uuid}`")
|
||||
|
@ -181,82 +176,71 @@ class DevCog(commands.Cog):
|
|||
|
||||
base64_methods = ["b64", "b16", "b32", "a85", "b85"]
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="encode",
|
||||
description="Encode some data",
|
||||
options=[
|
||||
create_option(
|
||||
name="method",
|
||||
description="Encode method",
|
||||
option_type=3,
|
||||
required=True,
|
||||
choices=[create_choice(name=x, value=x) for x in base64_methods],
|
||||
),
|
||||
create_option(
|
||||
name="data",
|
||||
description="Data to encode",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(name="encode", description="Encode some data")
|
||||
@slash_option(
|
||||
name="method",
|
||||
description="Encode method",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods],
|
||||
)
|
||||
async def _encode(self, ctx: SlashContext, method: str, data: str) -> None:
|
||||
@slash_option(
|
||||
name="data",
|
||||
description="Data to encode",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None:
|
||||
mstr = method
|
||||
method = getattr(base64, method + "encode")
|
||||
encoded = method(data.encode("UTF-8")).decode("UTF-8")
|
||||
fields = [
|
||||
Field(name="Plaintext", value=f"`{data}`", inline=False),
|
||||
Field(name=mstr, value=f"`{encoded}`", inline=False),
|
||||
EmbedField(name="Plaintext", value=f"`{data}`", inline=False),
|
||||
EmbedField(name=mstr, value=f"`{encoded}`", inline=False),
|
||||
]
|
||||
embed = build_embed(title="Decoded Data", description="", fields=fields)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="decode",
|
||||
description="Decode some data",
|
||||
options=[
|
||||
create_option(
|
||||
name="method",
|
||||
description="Decode method",
|
||||
option_type=3,
|
||||
required=True,
|
||||
choices=[create_choice(name=x, value=x) for x in base64_methods],
|
||||
),
|
||||
create_option(
|
||||
name="data",
|
||||
description="Data to encode",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(name="decode", description="Decode some data")
|
||||
@slash_option(
|
||||
name="method",
|
||||
description="Decode method",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods],
|
||||
)
|
||||
async def _decode(self, ctx: SlashContext, method: str, data: str) -> None:
|
||||
@slash_option(
|
||||
name="data",
|
||||
description="Data to encode",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
async def _decode(self, ctx: InteractionContext, method: str, data: str) -> None:
|
||||
mstr = method
|
||||
method = getattr(base64, method + "decode")
|
||||
decoded = method(data.encode("UTF-8")).decode("UTF-8")
|
||||
if invites.search(decoded):
|
||||
await ctx.send(
|
||||
"Please don't use this to bypass invite restrictions",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
fields = [
|
||||
Field(name="Plaintext", value=f"`{data}`", inline=False),
|
||||
Field(name=mstr, value=f"`{decoded}`", inline=False),
|
||||
EmbedField(name="Plaintext", value=f"`{data}`", inline=False),
|
||||
EmbedField(name=mstr, value=f"`{decoded}`", inline=False),
|
||||
]
|
||||
embed = build_embed(title="Decoded Data", description="", fields=fields)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="cloc",
|
||||
description="Get J.A.R.V.I.S. lines of code",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _cloc(self, ctx: SlashContext) -> None:
|
||||
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8") # noqa: S603, S607
|
||||
@slash_command(name="cloc", description="Get J.A.R.V.I.S. lines of code")
|
||||
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||
async def _cloc(self, ctx: InteractionContext) -> None:
|
||||
output = subprocess.check_output( # noqa: S603, S607
|
||||
["tokei", "-C", "--sort", "code"]
|
||||
).decode("UTF-8")
|
||||
await ctx.send(f"```\n{output}\n```")
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add DevCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(DevCog(bot))
|
||||
DevCog(bot)
|
||||
|
|
|
@ -20,7 +20,8 @@ class ErrorHandlerCog(commands.Cog):
|
|||
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",
|
||||
"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}```")
|
||||
|
@ -29,19 +30,22 @@ class ErrorHandlerCog(commands.Cog):
|
|||
@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.", hidden=True)
|
||||
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",
|
||||
hidden=True,
|
||||
"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}```",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
raise error
|
||||
slash.commands[ctx.command].reset_cooldown(ctx)
|
||||
|
@ -49,4 +53,4 @@ class ErrorHandlerCog(commands.Cog):
|
|||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
"""Add ErrorHandlerCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(ErrorHandlerCog(bot))
|
||||
ErrorHandlerCog(bot)
|
||||
|
|
|
@ -2,17 +2,19 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import gitlab
|
||||
from ButtonPaginator import Paginator
|
||||
from discord import Embed
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
from dis_snek import InteractionContext, 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 (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
|
||||
from jarvis.config import get_config
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
guild_ids = [862402786116763668]
|
||||
|
||||
|
@ -20,25 +22,22 @@ guild_ids = [862402786116763668]
|
|||
class GitlabCog(CacheCog):
|
||||
"""J.A.R.V.I.S. GitLab Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(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
|
||||
self.project = self._gitlab.projects.get(29)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="gl",
|
||||
name="issue",
|
||||
description="Get an issue from GitLab",
|
||||
guild_ids=guild_ids,
|
||||
options=[create_option(name="id", description="Issue ID", option_type=4, required=True)],
|
||||
@slash_command(
|
||||
name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids
|
||||
)
|
||||
async def _issue(self, ctx: SlashContext, id: int) -> None:
|
||||
@slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True)
|
||||
async def _issue(self, ctx: InteractionContext, id: int) -> None:
|
||||
try:
|
||||
issue = self.project.issues.get(int(id))
|
||||
except gitlab.exceptions.GitlabGetError:
|
||||
await ctx.send("Issue does not exist.", hidden=True)
|
||||
await ctx.send("Issue does not exist.", ephemeral=True)
|
||||
return
|
||||
assignee = issue.assignee
|
||||
if assignee:
|
||||
|
@ -46,7 +45,9 @@ class GitlabCog(CacheCog):
|
|||
else:
|
||||
assignee = "None"
|
||||
|
||||
created_at = datetime.strptime(issue.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
created_at = datetime.strptime(issue.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
)
|
||||
|
||||
labels = issue.labels
|
||||
if labels:
|
||||
|
@ -55,18 +56,20 @@ class GitlabCog(CacheCog):
|
|||
labels = "None"
|
||||
|
||||
fields = [
|
||||
Field(name="State", value=issue.state[0].upper() + issue.state[1:]),
|
||||
Field(name="Assignee", value=assignee),
|
||||
Field(name="Labels", value=labels),
|
||||
EmbedField(name="State", value=issue.state[0].upper() + issue.state[1:]),
|
||||
EmbedField(name="Assignee", value=assignee),
|
||||
EmbedField(name="Labels", value=labels),
|
||||
]
|
||||
color = self.project.labels.get(issue.labels[0]).color
|
||||
fields.append(Field(name="Created At", value=created_at))
|
||||
fields.append(EmbedField(name="Created At", value=created_at))
|
||||
if issue.state == "closed":
|
||||
closed_at = datetime.strptime(issue.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
fields.append(Field(name="Closed At", value=closed_at))
|
||||
closed_at = datetime.strptime(issue.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
)
|
||||
fields.append(EmbedField(name="Closed At", value=closed_at))
|
||||
if issue.milestone:
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name="Milestone",
|
||||
value=f"[{issue.milestone['title']}]({issue.milestone['web_url']})",
|
||||
inline=False,
|
||||
|
@ -83,50 +86,49 @@ class GitlabCog(CacheCog):
|
|||
)
|
||||
embed.set_author(
|
||||
name=issue.author["name"],
|
||||
icon_url=issue.author["avatar_url"],
|
||||
icon_url=issue.author["display_avatar"],
|
||||
url=issue.author["web_url"],
|
||||
)
|
||||
embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
|
||||
embed.set_thumbnail(
|
||||
url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png"
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="gl",
|
||||
name="milestone",
|
||||
@slash_command(
|
||||
name="gl",
|
||||
sub_cmd_name="milestone",
|
||||
description="Get a milestone from GitLab",
|
||||
guild_ids=guild_ids,
|
||||
options=[
|
||||
create_option(
|
||||
name="id",
|
||||
description="Milestone ID",
|
||||
option_type=4,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
scopes=guild_ids,
|
||||
)
|
||||
async def _milestone(self, ctx: SlashContext, id: int) -> None:
|
||||
@slash_option(
|
||||
name="id", description="Milestone ID", opt_type=OptionTypes.INTEGER, required=True
|
||||
)
|
||||
async def _milestone(self, ctx: InteractionContext, id: int) -> None:
|
||||
try:
|
||||
milestone = self.project.milestones.get(int(id))
|
||||
except gitlab.exceptions.GitlabGetError:
|
||||
await ctx.send("Milestone does not exist.", hidden=True)
|
||||
await ctx.send("Milestone does not exist.", ephemeral=True)
|
||||
return
|
||||
|
||||
created_at = datetime.strptime(milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
created_at = datetime.strptime(milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
)
|
||||
|
||||
fields = [
|
||||
Field(
|
||||
EmbedField(
|
||||
name="State",
|
||||
value=milestone.state[0].upper() + milestone.state[1:],
|
||||
),
|
||||
Field(name="Start Date", value=milestone.start_date),
|
||||
Field(name="Due Date", value=milestone.due_date),
|
||||
Field(name="Created At", value=created_at),
|
||||
EmbedField(name="Start Date", value=milestone.start_date),
|
||||
EmbedField(name="Due Date", value=milestone.due_date),
|
||||
EmbedField(name="Created At", value=created_at),
|
||||
]
|
||||
|
||||
if milestone.updated_at:
|
||||
updated_at = datetime.strptime(milestone.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
)
|
||||
fields.append(Field(name="Updated At", value=updated_at))
|
||||
fields.append(EmbedField(name="Updated At", value=updated_at))
|
||||
|
||||
if len(milestone.title) > 200:
|
||||
milestone.title = milestone.title[:200] + "..."
|
||||
|
@ -143,28 +145,25 @@ class GitlabCog(CacheCog):
|
|||
url="https://git.zevaryx.com/jarvis",
|
||||
icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png",
|
||||
)
|
||||
embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
|
||||
embed.set_thumbnail(
|
||||
url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png"
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="gl",
|
||||
name="mergerequest",
|
||||
description="Get an merge request from GitLab",
|
||||
guild_ids=guild_ids,
|
||||
options=[
|
||||
create_option(
|
||||
name="id",
|
||||
description="Merge Request ID",
|
||||
option_type=4,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
@slash_command(
|
||||
name="gl",
|
||||
sub_cmd_name="mr",
|
||||
description="Get a merge request from GitLab",
|
||||
scopes=guild_ids,
|
||||
)
|
||||
async def _mergerequest(self, ctx: SlashContext, id: int) -> None:
|
||||
@slash_option(
|
||||
name="id", description="Merge Request ID", opt_type=OptionTypes.INTEGER, required=True
|
||||
)
|
||||
async def _mergerequest(self, ctx: InteractionContext, id: int) -> None:
|
||||
try:
|
||||
mr = self.project.mergerequests.get(int(id))
|
||||
except gitlab.exceptions.GitlabGetError:
|
||||
await ctx.send("Merge request does not exist.", hidden=True)
|
||||
await ctx.send("Merge request does not exist.", ephemeral=True)
|
||||
return
|
||||
assignee = mr.assignee
|
||||
if assignee:
|
||||
|
@ -172,7 +171,9 @@ class GitlabCog(CacheCog):
|
|||
else:
|
||||
assignee = "None"
|
||||
|
||||
created_at = datetime.strptime(mr.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
created_at = datetime.strptime(mr.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
)
|
||||
|
||||
labels = mr.labels
|
||||
if labels:
|
||||
|
@ -181,24 +182,28 @@ class GitlabCog(CacheCog):
|
|||
labels = "None"
|
||||
|
||||
fields = [
|
||||
Field(name="State", value=mr.state[0].upper() + mr.state[1:]),
|
||||
Field(name="Assignee", value=assignee),
|
||||
Field(name="Labels", value=labels),
|
||||
EmbedField(name="State", value=mr.state[0].upper() + mr.state[1:]),
|
||||
EmbedField(name="Assignee", value=assignee),
|
||||
EmbedField(name="Labels", value=labels),
|
||||
]
|
||||
if mr.labels:
|
||||
color = self.project.labels.get(mr.labels[0]).color
|
||||
else:
|
||||
color = "#00FFEE"
|
||||
fields.append(Field(name="Created At", value=created_at))
|
||||
fields.append(EmbedField(name="Created At", value=created_at))
|
||||
if mr.state == "merged":
|
||||
merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
fields.append(Field(name="Merged At", value=merged_at))
|
||||
merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
)
|
||||
fields.append(EmbedField(name="Merged At", value=merged_at))
|
||||
elif mr.state == "closed":
|
||||
closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
fields.append(Field(name="Closed At", value=closed_at))
|
||||
closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
)
|
||||
fields.append(EmbedField(name="Closed At", value=closed_at))
|
||||
if mr.milestone:
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name="Milestone",
|
||||
value=f"[{mr.milestone['title']}]({mr.milestone['web_url']})",
|
||||
inline=False,
|
||||
|
@ -215,10 +220,12 @@ class GitlabCog(CacheCog):
|
|||
)
|
||||
embed.set_author(
|
||||
name=mr.author["name"],
|
||||
icon_url=mr.author["avatar_url"],
|
||||
icon_url=mr.author["display_avatar"],
|
||||
url=mr.author["web_url"],
|
||||
)
|
||||
embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
|
||||
embed.set_thumbnail(
|
||||
url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png"
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
def build_embed_page(self, api_list: list, t_state: str, name: str) -> Embed:
|
||||
|
@ -230,7 +237,7 @@ class GitlabCog(CacheCog):
|
|||
fields = []
|
||||
for item in api_list:
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name=f"[#{item.iid}] {item.title}",
|
||||
value=item.description + f"\n\n[View this {name}]({item.web_url})",
|
||||
inline=False,
|
||||
|
@ -248,35 +255,32 @@ class GitlabCog(CacheCog):
|
|||
url="https://git.zevaryx.com/jarvis",
|
||||
icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png",
|
||||
)
|
||||
embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png")
|
||||
embed.set_thumbnail(
|
||||
url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png"
|
||||
)
|
||||
return embed
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="gl",
|
||||
name="issues",
|
||||
description="Get open issues from GitLab",
|
||||
guild_ids=guild_ids,
|
||||
options=[
|
||||
create_option(
|
||||
name="state",
|
||||
description="State of issues to get",
|
||||
option_type=3,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(name="Open", value="opened"),
|
||||
create_choice(name="Closed", value="closed"),
|
||||
create_choice(name="All", value="all"),
|
||||
],
|
||||
)
|
||||
@slash_command(
|
||||
name="gl", sub_cmd_name="issues", description="Get issues from GitLab", scopes=guild_ids
|
||||
)
|
||||
@slash_option(
|
||||
name="state",
|
||||
description="State of issues to get",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Open", value="opened"),
|
||||
SlashCommandChoice(name="Closed", value="closed"),
|
||||
SlashCommandChoice(name="All", value="all"),
|
||||
],
|
||||
)
|
||||
async def _issues(self, ctx: SlashContext, state: str = "opened") -> None:
|
||||
async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None:
|
||||
exists = self.check_cache(ctx, state=state)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
await ctx.defer()
|
||||
|
@ -311,20 +315,9 @@ class GitlabCog(CacheCog):
|
|||
pages = []
|
||||
t_state = t_state[0].upper() + t_state[1:]
|
||||
for i in range(0, len(issues), 5):
|
||||
pages.append(self.build_embed_page(issues[i : i + 5], t_state=t_state, name="issue")) # noqa: E203
|
||||
pages.append(self.build_embed_page(issues[i : i + 5], t_state=t_state, name="issue"))
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
|
@ -335,35 +328,32 @@ class GitlabCog(CacheCog):
|
|||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
||||
await paginator.send(ctx)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="gl",
|
||||
name="mergerequests",
|
||||
description="Get open issues from GitLab",
|
||||
guild_ids=guild_ids,
|
||||
options=[
|
||||
create_option(
|
||||
name="state",
|
||||
description="State of issues to get",
|
||||
option_type=3,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(name="Open", value="opened"),
|
||||
create_choice(name="Closed", value="closed"),
|
||||
create_choice(name="Merged", value="merged"),
|
||||
create_choice(name="All", value="all"),
|
||||
],
|
||||
)
|
||||
@slash_command(
|
||||
name="gl",
|
||||
sub_cmd_name="mrs",
|
||||
description="Get merge requests from GitLab",
|
||||
scopes=guild_ids,
|
||||
)
|
||||
@slash_option(
|
||||
name="state",
|
||||
description="State of merge requests to get",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Open", value="opened"),
|
||||
SlashCommandChoice(name="Closed", value="closed"),
|
||||
SlashCommandChoice(name="All", value="all"),
|
||||
],
|
||||
)
|
||||
async def _mergerequests(self, ctx: SlashContext, state: str = "opened") -> None:
|
||||
async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None:
|
||||
exists = self.check_cache(ctx, state=state)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
await ctx.defer()
|
||||
|
@ -398,20 +388,11 @@ class GitlabCog(CacheCog):
|
|||
pages = []
|
||||
t_state = t_state[0].upper() + t_state[1:]
|
||||
for i in range(0, len(merges), 5):
|
||||
pages.append(self.build_embed_page(merges[i : i + 5], t_state=t_state, name="merge request")) # noqa: E203
|
||||
pages.append(
|
||||
self.build_embed_page(merges[i : i + 5], t_state=t_state, name="merge request")
|
||||
)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
|
@ -422,21 +403,21 @@ class GitlabCog(CacheCog):
|
|||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
||||
await paginator.send(ctx)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="gl",
|
||||
name="milestones",
|
||||
description="Get open issues from GitLab",
|
||||
guild_ids=guild_ids,
|
||||
@slash_command(
|
||||
name="gl",
|
||||
sub_cmd_name="milestones",
|
||||
description="Get milestones from GitLab",
|
||||
scopes=guild_ids,
|
||||
)
|
||||
async def _milestones(self, ctx: SlashContext) -> None:
|
||||
async def _milestones(self, ctx: InteractionContext) -> None:
|
||||
exists = self.check_cache(ctx)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
await ctx.defer()
|
||||
|
@ -463,20 +444,11 @@ class GitlabCog(CacheCog):
|
|||
|
||||
pages = []
|
||||
for i in range(0, len(milestones), 5):
|
||||
pages.append(self.build_embed_page(milestones[i : i + 5], t_state=None, name="milestone")) # noqa: E203
|
||||
pages.append(
|
||||
self.build_embed_page(milestones[i : i + 5], t_state=None, name="milestone")
|
||||
)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"user": ctx.author.id,
|
||||
|
@ -486,10 +458,10 @@ class GitlabCog(CacheCog):
|
|||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
||||
await paginator.send(ctx)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists."""
|
||||
if get_config().gitlab_token:
|
||||
bot.add_cog(GitlabCog(bot))
|
||||
GitlabCog(bot)
|
||||
|
|
|
@ -5,21 +5,23 @@ from io import BytesIO
|
|||
import aiohttp
|
||||
import cv2
|
||||
import numpy as np
|
||||
from discord import File
|
||||
from discord.ext import commands
|
||||
from dis_snek import MessageContext, Scale, Snake, message_command
|
||||
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 jarvis.utils import build_embed, convert_bytesize, unconvert_bytesize
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class ImageCog(commands.Cog):
|
||||
class ImageCog(Scale):
|
||||
"""
|
||||
Image processing functions for J.A.R.V.I.S.
|
||||
|
||||
May be categorized under util later
|
||||
"""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
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)
|
||||
|
@ -27,7 +29,7 @@ class ImageCog(commands.Cog):
|
|||
def __del__(self):
|
||||
self._session.close()
|
||||
|
||||
async def _resize(self, ctx: commands.Context, target: str, url: str = None) -> None:
|
||||
async def _resize(self, ctx: MessageContext, target: str, url: str = None) -> None:
|
||||
if not target:
|
||||
await ctx.send("Missing target size, i.e. 200KB.")
|
||||
return
|
||||
|
@ -84,23 +86,23 @@ class ImageCog(commands.Cog):
|
|||
bufio = BytesIO(file)
|
||||
accuracy = (len(file) / tgt_size) * 100
|
||||
fields = [
|
||||
Field("Original Size", convert_bytesize(size), False),
|
||||
Field("New Size", convert_bytesize(len(file)), False),
|
||||
Field("Accuracy", f"{accuracy:.02f}%", False),
|
||||
EmbedField("Original Size", convert_bytesize(size), False),
|
||||
EmbedField("New Size", convert_bytesize(len(file)), 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(bufio, filename="resized.png"),
|
||||
file=File(file=bufio, filename="resized.png"),
|
||||
)
|
||||
|
||||
@commands.command(name="resize", help="Resize an image")
|
||||
@commands.cooldown(1, 60, commands.BucketType.user)
|
||||
async def _resize_pref(self, ctx: commands.Context, target: str, url: str = None) -> None:
|
||||
@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: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add ImageCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(ImageCog(bot))
|
||||
ImageCog(bot)
|
||||
|
|
|
@ -5,31 +5,38 @@ import traceback
|
|||
from datetime import datetime
|
||||
from random import randint
|
||||
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
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
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class JokeCog(commands.Cog):
|
||||
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: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
|
||||
# TODO: Make this a command group with subcommands
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="joke",
|
||||
description="Hear a joke",
|
||||
)
|
||||
@commands.cooldown(1, 10, commands.BucketType.channel)
|
||||
async def _joke(self, ctx: SlashContext, id: str = None) -> None:
|
||||
@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
|
||||
|
@ -50,7 +57,7 @@ class JokeCog(commands.Cog):
|
|||
result = Joke.objects().aggregate(pipeline).next()
|
||||
|
||||
if result is None:
|
||||
await ctx.send("Humor module failed. Please try again later.", hidden=True)
|
||||
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:
|
||||
|
@ -63,7 +70,7 @@ class JokeCog(commands.Cog):
|
|||
body = ""
|
||||
for word in result["body"].split(" "):
|
||||
if len(body) + 1 + len(word) > 1024:
|
||||
body_chunks.append(Field("", body, False))
|
||||
body_chunks.append(EmbedField("", body, False))
|
||||
body = ""
|
||||
if word == "\n" and body == "":
|
||||
continue
|
||||
|
@ -87,15 +94,15 @@ class JokeCog(commands.Cog):
|
|||
else:
|
||||
desc += word + " "
|
||||
|
||||
body_chunks.append(Field("", body, False))
|
||||
body_chunks.append(EmbedField("", body, False))
|
||||
|
||||
fields = body_chunks
|
||||
fields.append(Field("Score", result["score"]))
|
||||
fields.append(EmbedField("Score", result["score"]))
|
||||
# Field(
|
||||
# "Created At",
|
||||
# str(datetime.fromtimestamp(result["created_utc"])),
|
||||
# ),
|
||||
fields.append(Field("ID", result["rid"]))
|
||||
fields.append(EmbedField("ID", result["rid"]))
|
||||
embed = build_embed(
|
||||
title=title,
|
||||
description=desc,
|
||||
|
@ -109,6 +116,6 @@ class JokeCog(commands.Cog):
|
|||
# await ctx.send(f"**{result['title']}**\n\n{result['body']}")
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add JokeCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(JokeCog(bot))
|
||||
JokeCog(bot)
|
||||
|
|
|
@ -6,6 +6,6 @@ from jarvis.cogs.modlog import command, member, message
|
|||
|
||||
def setup(bot: Bot) -> None:
|
||||
"""Add modlog cogs to J.A.R.V.I.S."""
|
||||
bot.add_cog(command.ModlogCommandCog(bot))
|
||||
bot.add_cog(member.ModlogMemberCog(bot))
|
||||
bot.add_cog(message.ModlogMessageCog(bot))
|
||||
command.ModlogCommandCog(bot)
|
||||
member.ModlogMemberCog(bot)
|
||||
message.ModlogMessageCog(bot)
|
||||
|
|
|
@ -41,9 +41,8 @@ class ModlogCommandCog(commands.Cog):
|
|||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
embed.set_author(
|
||||
name=ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
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}"
|
||||
)
|
||||
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
await channel.send(embed=embed)
|
||||
|
|
|
@ -223,7 +223,9 @@ class ModlogMemberCog(commands.Cog):
|
|||
desc=f"{before.mention} was verified",
|
||||
)
|
||||
|
||||
async def process_rolechange(self, before: discord.Member, after: discord.Member) -> discord.Embed:
|
||||
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(
|
||||
|
@ -320,10 +322,7 @@ class ModlogMemberCog(commands.Cog):
|
|||
fields=fields,
|
||||
timestamp=log.created_at,
|
||||
)
|
||||
embed.set_author(
|
||||
name=f"{after.name}",
|
||||
icon_url=after.avatar_url,
|
||||
)
|
||||
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
|
||||
|
|
|
@ -44,10 +44,12 @@ class ModlogMessageCog(commands.Cog):
|
|||
)
|
||||
embed.set_author(
|
||||
name=before.author.name,
|
||||
icon_url=before.author.avatar_url,
|
||||
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}")
|
||||
embed.set_footer(
|
||||
text=f"{before.author.name}#{before.author.discriminator} | {before.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
|
@ -97,8 +99,10 @@ class ModlogMessageCog(commands.Cog):
|
|||
|
||||
embed.set_author(
|
||||
name=message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
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}")
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
|
|
@ -33,10 +33,7 @@ def modlog_embed(
|
|||
fields=fields,
|
||||
timestamp=log.created_at,
|
||||
)
|
||||
embed.set_author(
|
||||
name=f"{member.name}",
|
||||
icon_url=member.avatar_url,
|
||||
)
|
||||
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
|
||||
|
||||
|
|
|
@ -1,163 +1,27 @@
|
|||
"""J.A.R.V.I.S. Owner Cog."""
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from inspect import getsource
|
||||
from time import time
|
||||
from typing import Any
|
||||
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
|
||||
|
||||
import discord
|
||||
from discord import DMChannel, User
|
||||
from discord.ext import commands
|
||||
|
||||
import jarvis
|
||||
from jarvis.config import reload_config
|
||||
from jarvis.db.models import Config
|
||||
from jarvis.utils import update
|
||||
from jarvis.utils.permissions import user_is_bot_admin
|
||||
|
||||
|
||||
class OwnerCog(commands.Cog):
|
||||
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: commands.Cog):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.admins = Config.objects(key="admins").first()
|
||||
|
||||
@commands.command(name="load", hidden=True)
|
||||
@user_is_bot_admin()
|
||||
async def _load_cog(self, ctx: commands.Context, *, cog: str) -> None:
|
||||
info = await self.bot.application_info()
|
||||
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
|
||||
try:
|
||||
if "jarvis.cogs." not in cog:
|
||||
cog = "jarvis.cogs." + cog.split(".")[-1]
|
||||
self.bot.load_extension(cog)
|
||||
except commands.errors.ExtensionAlreadyLoaded:
|
||||
await ctx.send(f"Cog `{cog}` already loaded")
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to load new cog `{cog}`: {type(e).name} - {e}")
|
||||
else:
|
||||
await ctx.send(f"Successfully loaded new cog `{cog}`")
|
||||
else:
|
||||
await ctx.send("I'm afraid I can't let you do that")
|
||||
|
||||
@commands.command(name="unload", hidden=True)
|
||||
@user_is_bot_admin()
|
||||
async def _unload_cog(self, ctx: commands.Context, *, cog: str) -> None:
|
||||
if cog in ["jarvis.cogs.owner", "owner"]:
|
||||
await ctx.send("Cannot unload `owner` cog")
|
||||
return
|
||||
info = await self.bot.application_info()
|
||||
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
|
||||
try:
|
||||
if "jarvis.cogs." not in cog:
|
||||
cog = "jarvis.cogs." + cog.split(".")[-1]
|
||||
self.bot.unload_extension(cog)
|
||||
except commands.errors.ExtensionNotLoaded:
|
||||
await ctx.send(f"Cog `{cog}` not loaded")
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to unload cog `{cog}` {type(e).__name__} - {e}")
|
||||
else:
|
||||
await ctx.send(f"Successfully unloaded cog `{cog}`")
|
||||
else:
|
||||
await ctx.send("I'm afraid I can't let you do that")
|
||||
|
||||
@commands.command(name="reload", hidden=True)
|
||||
@user_is_bot_admin()
|
||||
async def _cog_reload(self, ctx: commands.Context, *, cog: str) -> None:
|
||||
if cog in ["jarvis.cogs.owner", "owner"]:
|
||||
await ctx.send("Cannot reload `owner` cog")
|
||||
return
|
||||
info = await self.bot.application_info()
|
||||
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
|
||||
try:
|
||||
if "jarvis.cogs." not in cog:
|
||||
cog = "jarvis.cogs." + cog.split(".")[-1]
|
||||
try:
|
||||
self.bot.load_extension(cog)
|
||||
except commands.errors.ExtensionNotLoaded:
|
||||
pass
|
||||
self.bot.unload_extension(cog)
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to reload cog `{cog}` {type(e).__name__} - {e}")
|
||||
else:
|
||||
await ctx.send(f"Successfully reloaded cog `{cog}`")
|
||||
else:
|
||||
await ctx.send("I'm afraid I can't let you do that")
|
||||
|
||||
@commands.group(name="system", hidden=True, pass_context=True)
|
||||
@user_is_bot_admin()
|
||||
async def _system(self, ctx: commands.Context) -> None:
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send("Usage: `system <subcommand>`\n" + "Subcommands: `restart`, `update`")
|
||||
|
||||
@_system.command(name="restart", hidden=True)
|
||||
@user_is_bot_admin()
|
||||
async def _restart(self, ctx: commands.Context) -> None:
|
||||
info = await self.bot.application_info()
|
||||
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
|
||||
await ctx.send("Restarting core systems...")
|
||||
if isinstance(ctx.channel, discord.channel.DMChannel):
|
||||
jarvis.restart_ctx = {
|
||||
"user": ctx.message.author.id,
|
||||
"channel": ctx.channel.id,
|
||||
}
|
||||
else:
|
||||
jarvis.restart_ctx = {
|
||||
"guild": ctx.message.guild.id,
|
||||
"channel": ctx.channel.id,
|
||||
}
|
||||
await self.bot.close()
|
||||
else:
|
||||
await ctx.send("I'm afraid I can't let you do that")
|
||||
|
||||
@_system.command(name="update", hidden=True)
|
||||
@user_is_bot_admin()
|
||||
async def _update(self, ctx: commands.Context) -> None:
|
||||
info = await self.bot.application_info()
|
||||
if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value:
|
||||
await ctx.send("Updating core systems...")
|
||||
status = update()
|
||||
if status == 0:
|
||||
await ctx.send("Core systems updated. Restarting...")
|
||||
if isinstance(ctx.channel, discord.channel.DMChannel):
|
||||
jarvis.restart_ctx = {
|
||||
"user": ctx.message.author.id,
|
||||
"channel": ctx.channel.id,
|
||||
}
|
||||
else:
|
||||
jarvis.restart_ctx = {
|
||||
"guild": ctx.message.guild.id,
|
||||
"channel": ctx.channel.id,
|
||||
}
|
||||
await self.bot.close()
|
||||
elif status == 1:
|
||||
await ctx.send("Core systems already up to date.")
|
||||
elif status == 2:
|
||||
await ctx.send("Core system update available, but core is dirty.")
|
||||
else:
|
||||
await ctx.send("I'm afraid I can't let you do that")
|
||||
|
||||
@_system.command(name="refresh", hidden=True)
|
||||
@user_is_bot_admin()
|
||||
async def _refresh(self, ctx: commands.Context) -> None:
|
||||
reload_config()
|
||||
await ctx.send("System refreshed")
|
||||
|
||||
@commands.group(name="admin", hidden=True, pass_context=True)
|
||||
@commands.is_owner()
|
||||
async def _admin(self, ctx: commands.Context) -> None:
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send("Usage: `admin <subcommand>`\n" + "Subcommands: `add`, `remove`")
|
||||
|
||||
@_admin.command(name="add", hidden=True)
|
||||
@commands.is_owner()
|
||||
async def _add(self, ctx: commands.Context, user: User) -> None:
|
||||
@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
|
||||
|
@ -166,9 +30,9 @@ class OwnerCog(commands.Cog):
|
|||
reload_config()
|
||||
await ctx.send(f"{user.mention} is now an admin. Use this power carefully.")
|
||||
|
||||
@_admin.command(name="remove", hidden=True)
|
||||
@commands.is_owner()
|
||||
async def _remove(self, ctx: commands.Context, user: User) -> None:
|
||||
@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
|
||||
|
@ -177,70 +41,7 @@ class OwnerCog(commands.Cog):
|
|||
reload_config()
|
||||
await ctx.send(f"{user.mention} is no longer an admin.")
|
||||
|
||||
def resolve_variable(self, variable: Any) -> Any:
|
||||
"""Resolve a variable from eval."""
|
||||
if hasattr(variable, "__iter__"):
|
||||
var_length = len(list(variable))
|
||||
if (var_length > 100) and (not isinstance(variable, str)):
|
||||
return f"<a {type(variable).__name__} iterable " + f"with more than 100 values ({var_length})>"
|
||||
elif not var_length:
|
||||
return f"<an empty {type(variable).__name__} iterable>"
|
||||
|
||||
if (not variable) and (not isinstance(variable, bool)):
|
||||
return f"<an empty {type(variable).__name__} object>"
|
||||
return (
|
||||
variable
|
||||
if (len(f"{variable}") <= 1000)
|
||||
else f"<a long {type(variable).__name__} object " + f"with the length of {len(f'{variable}'):,}>"
|
||||
)
|
||||
|
||||
def prepare(self, string: str) -> str:
|
||||
"""Prepare string for eval."""
|
||||
arr = string.strip("```").replace("py\n", "").replace("python\n", "").split("\n")
|
||||
if not arr[::-1][0].replace(" ", "").startswith("return"):
|
||||
arr[len(arr) - 1] = "return " + arr[::-1][0]
|
||||
return "".join(f"\n\t{i}" for i in arr)
|
||||
|
||||
@commands.command(pass_context=True, aliases=["eval", "exec", "evaluate"])
|
||||
@user_is_bot_admin()
|
||||
async def _eval(self, ctx: commands.Context, *, code: str) -> None:
|
||||
if not isinstance(ctx.message.channel, DMChannel):
|
||||
return
|
||||
code = self.prepare(code)
|
||||
args = {
|
||||
"discord": discord,
|
||||
"sauce": getsource,
|
||||
"sys": sys,
|
||||
"os": os,
|
||||
"imp": __import__,
|
||||
"this": self,
|
||||
"ctx": ctx,
|
||||
}
|
||||
|
||||
try:
|
||||
exec( # noqa: S102
|
||||
f"async def func():{code}",
|
||||
globals().update(args),
|
||||
locals(),
|
||||
)
|
||||
a = time()
|
||||
response = await eval("func()", globals().update(args), locals()) # noqa: S307
|
||||
if response is None or isinstance(response, discord.Message):
|
||||
del args, code
|
||||
return
|
||||
|
||||
if isinstance(response, str):
|
||||
response = response.replace("`", "")
|
||||
|
||||
await ctx.send(
|
||||
f"```py\n{self.resolve_variable(response)}```\n`{type(response).__name__} | {(time() - a) / 1000} ms`"
|
||||
)
|
||||
except Exception:
|
||||
await ctx.send(f"Error occurred:```\n{traceback.format_exc()}```")
|
||||
|
||||
del args, code
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add OwnerCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(OwnerCog(bot))
|
||||
OwnerCog(bot)
|
||||
|
|
|
@ -5,26 +5,22 @@ from datetime import datetime, timedelta
|
|||
from typing import List, Optional
|
||||
|
||||
from bson import ObjectId
|
||||
from discord import Embed
|
||||
from discord.ext.commands import Bot
|
||||
from discord.ext.tasks import loop
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from discord_slash.utils.manage_components import (
|
||||
create_actionrow,
|
||||
create_select,
|
||||
create_select_option,
|
||||
wait_for_component,
|
||||
from dis_snek import InteractionContext, Snake
|
||||
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||
from dis_snek.models.discord.embed import Embed, EmbedField
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
|
||||
from jarvis.db.models import Reminder
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
@ -32,49 +28,40 @@ invites = re.compile(
|
|||
class RemindmeCog(CacheCog):
|
||||
"""J.A.R.V.I.S. Remind Me Cog."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
super().__init__(bot)
|
||||
self._remind.start()
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="remindme",
|
||||
description="Set a reminder",
|
||||
options=[
|
||||
create_option(
|
||||
name="message",
|
||||
description="What to remind you of",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="weeks",
|
||||
description="Number of weeks?",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
create_option(
|
||||
name="days",
|
||||
description="Number of days?",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
create_option(
|
||||
name="hours",
|
||||
description="Number of hours?",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
create_option(
|
||||
name="minutes",
|
||||
description="Number of minutes?",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
@slash_command(name="remindme", description="Set a reminder")
|
||||
@slash_option(
|
||||
name="message",
|
||||
description="What to remind you of?",
|
||||
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,
|
||||
)
|
||||
async def _remindme(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
ctx: InteractionContext,
|
||||
message: Optional[str] = None,
|
||||
weeks: Optional[int] = 0,
|
||||
days: Optional[int] = 0,
|
||||
|
@ -82,20 +69,20 @@ class RemindmeCog(CacheCog):
|
|||
minutes: Optional[int] = 0,
|
||||
) -> None:
|
||||
if len(message) > 100:
|
||||
await ctx.send("Reminder cannot be > 100 characters.", hidden=True)
|
||||
await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True)
|
||||
return
|
||||
elif invites.search(message):
|
||||
await ctx.send(
|
||||
"Listen, don't use this to try and bypass the rules",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
elif not valid.fullmatch(message):
|
||||
await ctx.send("Hey, you should probably make this readable", hidden=True)
|
||||
await ctx.send("Hey, you should probably make this readable", ephemeral=True)
|
||||
return
|
||||
|
||||
if not any([weeks, days, hours, minutes]):
|
||||
await ctx.send("At least one time period is required", hidden=True)
|
||||
await ctx.send("At least one time period is required", ephemeral=True)
|
||||
return
|
||||
|
||||
weeks = abs(weeks)
|
||||
|
@ -104,19 +91,19 @@ class RemindmeCog(CacheCog):
|
|||
minutes = abs(minutes)
|
||||
|
||||
if weeks and weeks > 4:
|
||||
await ctx.send("Cannot be farther than 4 weeks out!", hidden=True)
|
||||
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.", hidden=True)
|
||||
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.", hidden=True)
|
||||
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.", hidden=True)
|
||||
await ctx.send("Use hours instead of 59+ minutes, please.", ephemeral=True)
|
||||
return
|
||||
|
||||
reminders = Reminder.objects(user=ctx.author.id, active=True).count()
|
||||
|
@ -124,7 +111,7 @@ class RemindmeCog(CacheCog):
|
|||
await ctx.send(
|
||||
"You already have 5 (or more) active reminders. "
|
||||
"Please either remove an old one, or wait for one to pass",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -148,8 +135,8 @@ class RemindmeCog(CacheCog):
|
|||
title="Reminder Set",
|
||||
description=f"{ctx.author.mention} set a reminder",
|
||||
fields=[
|
||||
Field(name="Message", value=message),
|
||||
Field(
|
||||
EmbedField(name="Message", value=message),
|
||||
EmbedField(
|
||||
name="When",
|
||||
value=remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
inline=False,
|
||||
|
@ -158,19 +145,21 @@ class RemindmeCog(CacheCog):
|
|||
)
|
||||
|
||||
embed.set_author(
|
||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.author.avatar_url)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
async def get_reminders_embed(self, ctx: SlashContext, reminders: List[Reminder]) -> Embed:
|
||||
async def get_reminders_embed(
|
||||
self, ctx: InteractionContext, reminders: List[Reminder]
|
||||
) -> Embed:
|
||||
"""Build embed for paginator."""
|
||||
fields = []
|
||||
for reminder in reminders:
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value=f"{reminder.message}\n\u200b",
|
||||
inline=False,
|
||||
|
@ -184,57 +173,49 @@ class RemindmeCog(CacheCog):
|
|||
)
|
||||
|
||||
embed.set_author(
|
||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.author.avatar_url)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
|
||||
return embed
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="reminders",
|
||||
name="list",
|
||||
description="List reminders for a user",
|
||||
)
|
||||
async def _list(self, ctx: SlashContext) -> None:
|
||||
@slash_command(name="reminders", sub_cmd_name="list", sub_cmd_description="List reminders")
|
||||
async def _list(self, ctx: InteractionContext) -> None:
|
||||
exists = self.check_cache(ctx)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.defer(ephemeral=True)
|
||||
await ctx.send(
|
||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
reminders = Reminder.objects(user=ctx.author.id, active=True)
|
||||
if not reminders:
|
||||
await ctx.send("You have no reminders set.", hidden=True)
|
||||
await ctx.send("You have no reminders set.", ephemeral=True)
|
||||
return
|
||||
|
||||
embed = await self.get_reminders_embed(ctx, reminders)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="reminders",
|
||||
name="delete",
|
||||
description="Delete a reminder",
|
||||
)
|
||||
async def _delete(self, ctx: SlashContext) -> None:
|
||||
@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)
|
||||
if not reminders:
|
||||
await ctx.send("You have no reminders set", hidden=True)
|
||||
await ctx.send("You have no reminders set", ephemeral=True)
|
||||
return
|
||||
|
||||
options = []
|
||||
for reminder in reminders:
|
||||
option = create_select_option(
|
||||
option = SelectOption(
|
||||
label=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value=str(reminder.id),
|
||||
emoji="⏰",
|
||||
)
|
||||
options.append(option)
|
||||
|
||||
select = create_select(
|
||||
select = Select(
|
||||
options=options,
|
||||
custom_id="to_delete",
|
||||
placeholder="Select reminders to delete",
|
||||
|
@ -242,7 +223,7 @@ class RemindmeCog(CacheCog):
|
|||
max_values=len(reminders),
|
||||
)
|
||||
|
||||
components = [create_actionrow(select)]
|
||||
components = [ActionRow(select)]
|
||||
embed = await self.get_reminders_embed(ctx, reminders)
|
||||
message = await ctx.send(
|
||||
content=f"You have {len(reminders)} reminder(s) set:",
|
||||
|
@ -251,23 +232,22 @@ class RemindmeCog(CacheCog):
|
|||
)
|
||||
|
||||
try:
|
||||
context = await wait_for_component(
|
||||
self.bot,
|
||||
check=lambda x: ctx.author.id == x.author_id,
|
||||
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.selected_options:
|
||||
for to_delete in context.context.values:
|
||||
_ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete()
|
||||
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
fields = []
|
||||
for reminder in filter(lambda x: str(x.id) in context.selected_options, reminders):
|
||||
for reminder in filter(lambda x: str(x.id) in context.context.values, reminders):
|
||||
fields.append(
|
||||
Field(
|
||||
EmbedField(
|
||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||
value=reminder.message,
|
||||
inline=False,
|
||||
|
@ -280,52 +260,23 @@ class RemindmeCog(CacheCog):
|
|||
)
|
||||
|
||||
embed.set_author(
|
||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.author.avatar_url)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
|
||||
await context.edit_origin(
|
||||
content=f"Deleted {len(context.selected_options)} reminder(s)",
|
||||
await context.context.edit_origin(
|
||||
content=f"Deleted {len(context.context.values)} reminder(s)",
|
||||
components=components,
|
||||
embed=embed,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(components=components)
|
||||
|
||||
@loop(seconds=15)
|
||||
async def _remind(self) -> None:
|
||||
reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))
|
||||
for reminder in reminders:
|
||||
if reminder.remind_at <= datetime.utcnow():
|
||||
user = await self.bot.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.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
try:
|
||||
await user.send(embed=embed)
|
||||
except Exception:
|
||||
guild = self.bot.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()
|
||||
|
||||
|
||||
def setup(bot: Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add RemindmeCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(RemindmeCog(bot))
|
||||
RemindmeCog(bot)
|
||||
|
|
|
@ -1,54 +1,45 @@
|
|||
"""J.A.R.V.I.S. Role Giver Cog."""
|
||||
import asyncio
|
||||
|
||||
from discord import Role
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from discord_slash.utils.manage_components import (
|
||||
create_actionrow,
|
||||
create_select,
|
||||
create_select_option,
|
||||
wait_for_component,
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
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
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check, cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
|
||||
from jarvis.db.models import Rolegiver
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils import build_embed, get
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class RolegiverCog(commands.Cog):
|
||||
class RolegiverCog(Scale):
|
||||
"""J.A.R.V.I.S. Role Giver Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="rolegiver",
|
||||
name="add",
|
||||
description="Add a role to rolegiver",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to add",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
@slash_command(
|
||||
name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver"
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _rolegiver_add(self, ctx: SlashContext, role: Role) -> None:
|
||||
@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()
|
||||
if setting and role.id in setting.roles:
|
||||
await ctx.send("Role already in rolegiver", hidden=True)
|
||||
await ctx.send("Role already in rolegiver", ephemeral=True)
|
||||
return
|
||||
|
||||
if not setting:
|
||||
setting = Rolegiver(guild=ctx.guild.id, roles=[])
|
||||
|
||||
if len(setting.roles) >= 20:
|
||||
await ctx.send("You can only have 20 roles in the rolegiver", hidden=True)
|
||||
await ctx.send("You can only have 20 roles in the rolegiver", ephemeral=True)
|
||||
return
|
||||
|
||||
setting.roles.append(role.id)
|
||||
|
@ -58,7 +49,7 @@ class RolegiverCog(commands.Cog):
|
|||
for role_id in setting.roles:
|
||||
if role_id == role.id:
|
||||
continue
|
||||
e_role = ctx.guild.get_role(role_id)
|
||||
e_role = await ctx.guild.get_role(role_id)
|
||||
if not e_role:
|
||||
continue
|
||||
roles.append(e_role)
|
||||
|
@ -67,8 +58,8 @@ class RolegiverCog(commands.Cog):
|
|||
|
||||
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
||||
fields = [
|
||||
Field(name="New Role", value=f"{role.mention}"),
|
||||
Field(name="Existing Role(s)", value=value),
|
||||
EmbedField(name="New Role", value=f"{role.mention}"),
|
||||
EmbedField(name="Existing Role(s)", value=value),
|
||||
]
|
||||
|
||||
embed = build_embed(
|
||||
|
@ -77,61 +68,60 @@ class RolegiverCog(commands.Cog):
|
|||
fields=fields,
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_author(
|
||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="rolegiver",
|
||||
name="remove",
|
||||
description="Remove a role from rolegiver",
|
||||
@slash_command(
|
||||
name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver"
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _rolegiver_remove(self, ctx: SlashContext) -> None:
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _rolegiver_remove(self, ctx: InteractionContext) -> None:
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
||||
options = []
|
||||
for role in setting.roles:
|
||||
role: Role = ctx.guild.get_role(role)
|
||||
option = create_select_option(label=role.name, value=str(role.id))
|
||||
role: Role = await ctx.guild.get_role(role)
|
||||
option = SelectOption(label=role.name, value=str(role.id))
|
||||
options.append(option)
|
||||
|
||||
select = create_select(
|
||||
select = Select(
|
||||
options=options,
|
||||
custom_id="to_delete",
|
||||
placeholder="Select roles to remove",
|
||||
min_values=1,
|
||||
max_values=len(options),
|
||||
)
|
||||
components = [create_actionrow(select)]
|
||||
components = [ActionRow(select)]
|
||||
|
||||
message = await ctx.send(content="\u200b", components=components)
|
||||
try:
|
||||
context = await wait_for_component(
|
||||
self.bot,
|
||||
check=lambda x: ctx.author.id == x.author.id,
|
||||
message=message,
|
||||
context = await self.bot.wait_for_component(
|
||||
check=lambda x: ctx.author.id == x.context.author.id,
|
||||
messages=message,
|
||||
timeout=60 * 1,
|
||||
)
|
||||
for to_delete in context.selected_options:
|
||||
removed_roles = []
|
||||
for to_delete in context.context.values:
|
||||
role = await ctx.guild.get_role(to_delete)
|
||||
if role:
|
||||
removed_roles.append(role)
|
||||
setting.roles.remove(int(to_delete))
|
||||
setting.save()
|
||||
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
roles = []
|
||||
for role_id in setting.roles:
|
||||
e_role = ctx.guild.get_role(role_id)
|
||||
e_role = await ctx.guild.get_role(role_id)
|
||||
if not e_role:
|
||||
continue
|
||||
roles.append(e_role)
|
||||
|
@ -140,9 +130,10 @@ class RolegiverCog(commands.Cog):
|
|||
roles.sort(key=lambda x: -x.position)
|
||||
|
||||
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
||||
rvalue = "\n".join([r.mention for r in removed_roles]) if removed_roles else "None"
|
||||
fields = [
|
||||
Field(name="Removed Role", value=f"{role.mention}"),
|
||||
Field(name="Remaining Role(s)", value=value),
|
||||
EmbedField(name="Removed Role(s)", value=rvalue),
|
||||
EmbedField(name="Remaining Role(s)", value=value),
|
||||
]
|
||||
|
||||
embed = build_embed(
|
||||
|
@ -151,39 +142,34 @@ class RolegiverCog(commands.Cog):
|
|||
fields=fields,
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_author(
|
||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
|
||||
|
||||
embed.set_footer(
|
||||
text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}"
|
||||
)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
|
||||
await context.edit_origin(
|
||||
content=f"Removed {len(context.selected_options)} role(s)",
|
||||
await context.context.edit_origin(
|
||||
content=f"Removed {len(context.context.values)} role(s)",
|
||||
embed=embed,
|
||||
components=components,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(components=components)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="rolegiver",
|
||||
name="list",
|
||||
description="List roles rolegiver",
|
||||
)
|
||||
async def _rolegiver_list(self, ctx: SlashContext) -> None:
|
||||
@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()
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
||||
roles = []
|
||||
for role_id in setting.roles:
|
||||
e_role = ctx.guild.get_role(role_id)
|
||||
e_role = await ctx.guild.get_role(role_id)
|
||||
if not e_role:
|
||||
continue
|
||||
roles.append(e_role)
|
||||
|
@ -199,59 +185,52 @@ class RolegiverCog(commands.Cog):
|
|||
fields=[],
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_author(
|
||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
name=ctx.author.display_name,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="role",
|
||||
name="get",
|
||||
description="Get a role from rolegiver",
|
||||
)
|
||||
@commands.cooldown(1, 10, commands.BucketType.user)
|
||||
async def _role_get(self, ctx: SlashContext) -> None:
|
||||
@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()
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
|
||||
options = []
|
||||
for role in setting.roles:
|
||||
role: Role = ctx.guild.get_role(role)
|
||||
option = create_select_option(label=role.name, value=str(role.id))
|
||||
role: Role = await ctx.guild.get_role(role)
|
||||
option = SelectOption(label=role.name, value=str(role.id))
|
||||
options.append(option)
|
||||
|
||||
select = create_select(
|
||||
select = Select(
|
||||
options=options,
|
||||
custom_id="to_delete",
|
||||
placeholder="Select roles to add",
|
||||
min_values=1,
|
||||
max_values=len(options),
|
||||
)
|
||||
components = [create_actionrow(select)]
|
||||
components = [ActionRow(select)]
|
||||
|
||||
message = await ctx.send(content="\u200b", components=components)
|
||||
|
||||
try:
|
||||
|
||||
context = await wait_for_component(
|
||||
self.bot,
|
||||
check=lambda x: ctx.author.id == x.author.id,
|
||||
context = await self.bot.wait_for_component(
|
||||
check=lambda x: ctx.author.id == x.context.author.id,
|
||||
messages=message,
|
||||
timeout=60 * 5,
|
||||
)
|
||||
|
||||
added_roles = []
|
||||
for role in context.selected_options:
|
||||
role = ctx.guild.get_role(int(role))
|
||||
for role in context.context.values:
|
||||
role = await ctx.guild.get_role(int(role))
|
||||
added_roles.append(role)
|
||||
await ctx.author.add_roles(role, reason="Rolegiver")
|
||||
await ctx.author.add_role(role, reason="Rolegiver")
|
||||
|
||||
roles = ctx.author.roles
|
||||
if roles:
|
||||
|
@ -261,101 +240,125 @@ class RolegiverCog(commands.Cog):
|
|||
avalue = "\n".join([r.mention for r in added_roles]) if added_roles else "None"
|
||||
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
||||
fields = [
|
||||
Field(name="Added Role(s)", value=avalue),
|
||||
Field(name="Prior Role(s)", value=value),
|
||||
EmbedField(name="Added Role(s)", value=avalue),
|
||||
EmbedField(name="Prior Role(s)", value=value),
|
||||
]
|
||||
|
||||
embed = build_embed(
|
||||
title="User Given Role",
|
||||
description=f"{role.mention} given to {ctx.author.mention}",
|
||||
description=f"{len(added_roles)} role(s) given to {ctx.author.mention}",
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_author(
|
||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
name=ctx.author.display_name,
|
||||
icon_url=ctx.author.display_avatar.url,
|
||||
)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
embed.set_footer(
|
||||
text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}"
|
||||
)
|
||||
|
||||
await message.edit_origin(embed=embed, content="\u200b", components=components)
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
await context.context.edit_origin(embed=embed, content="\u200b", components=components)
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(components=components)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="role",
|
||||
name="forfeit",
|
||||
description="Have rolegiver take away role",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to remove",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@commands.cooldown(1, 10, commands.BucketType.user)
|
||||
async def _role_forfeit(self, ctx: SlashContext, role: Role) -> None:
|
||||
@slash_command(name="role", sub_cmd_name="remove", sub_cmd_description="Remove a role")
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
||||
async def _role_remove(self, ctx: InteractionContext) -> None:
|
||||
user_roles = ctx.author.roles
|
||||
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
if not setting or (setting and not setting.roles):
|
||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
return
|
||||
elif role.id not in setting.roles:
|
||||
await ctx.send("Role not in rolegiver", hidden=True)
|
||||
return
|
||||
elif role not in ctx.author.roles:
|
||||
await ctx.send("You do not have that role", hidden=True)
|
||||
elif not any(x.id in setting.roles for x in user_roles):
|
||||
await ctx.send("You have no rolegiver roles", ephemeral=True)
|
||||
return
|
||||
|
||||
await ctx.author.remove_roles(role, reason="Rolegiver")
|
||||
valid = list(filter(lambda x: x.id in setting.roles, user_roles))
|
||||
options = []
|
||||
for role in valid:
|
||||
option = SelectOption(label=role.name, value=str(role.id))
|
||||
options.append(option)
|
||||
|
||||
roles = ctx.author.roles
|
||||
if roles:
|
||||
roles.sort(key=lambda x: -x.position)
|
||||
_ = roles.pop(-1)
|
||||
|
||||
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
||||
fields = [
|
||||
Field(name="Taken Role", value=f"{role.mention}"),
|
||||
Field(name="Remaining Role(s)", value=value),
|
||||
]
|
||||
|
||||
embed = build_embed(
|
||||
title="User Forfeited Role",
|
||||
description=f"{role.mention} taken from {ctx.author.mention}",
|
||||
fields=fields,
|
||||
select = Select(
|
||||
options=options,
|
||||
custom_id="to_remove",
|
||||
placeholder="Select roles to remove",
|
||||
min_values=1,
|
||||
max_values=len(options),
|
||||
)
|
||||
components = [ActionRow(select)]
|
||||
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_author(
|
||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
)
|
||||
message = await ctx.send(content="\u200b", components=components)
|
||||
|
||||
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
|
||||
try:
|
||||
context = await self.bot.wait_for_component(
|
||||
check=lambda x: ctx.author.id == x.context.author.id,
|
||||
messages=message,
|
||||
timeout=60 * 5,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
removed_roles = []
|
||||
for to_remove in context.context.values:
|
||||
role = get(user_roles, id=int(to_remove))
|
||||
await ctx.author.remove_role(role, reason="Rolegiver")
|
||||
user_roles.remove(role)
|
||||
removed_roles.append(role)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="rolegiver",
|
||||
name="cleanup",
|
||||
description="Cleanup rolegiver roles",
|
||||
user_roles.sort(key=lambda x: -x.position)
|
||||
_ = user_roles.pop(-1)
|
||||
|
||||
value = "\n".join([r.mention for r in user_roles]) if user_roles else "None"
|
||||
rvalue = "\n".join([r.mention for r in removed_roles]) if removed_roles else "None"
|
||||
fields = [
|
||||
EmbedField(name="Removed Role(s)", value=rvalue),
|
||||
EmbedField(name="Remaining Role(s)", value=value),
|
||||
]
|
||||
|
||||
embed = build_embed(
|
||||
title="User Forfeited Role",
|
||||
description=f"{len(removed_roles)} role(s) removed from {ctx.author.mention}",
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
|
||||
|
||||
embed.set_footer(
|
||||
text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}"
|
||||
)
|
||||
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
await context.context.edit_origin(embed=embed, components=components, content="\u200b")
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(components=components)
|
||||
|
||||
@slash_command(
|
||||
name="rolegiver", sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver"
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _rolegiver_cleanup(self, ctx: SlashContext) -> None:
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None:
|
||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||
if not setting or not setting.roles:
|
||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
||||
guild_roles = await ctx.guild.fetch_roles()
|
||||
guild_role_ids = [x.id for x in guild_roles]
|
||||
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||
guild_role_ids = [r.id for r in ctx.guild.roles]
|
||||
for role_id in setting.roles:
|
||||
if role_id not in guild_role_ids:
|
||||
setting.roles.remove(role_id)
|
||||
|
@ -364,6 +367,6 @@ class RolegiverCog(commands.Cog):
|
|||
await ctx.send("Rolegiver cleanup finished")
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add RolegiverCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(RolegiverCog(bot))
|
||||
RolegiverCog(bot)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""J.A.R.V.I.S. Settings Management Cog."""
|
||||
from typing import Any
|
||||
|
||||
from dis_snek.models.snek.command import check
|
||||
from discord import Role, TextChannel
|
||||
from discord.ext import commands
|
||||
from discord.utils import find
|
||||
|
@ -33,46 +34,24 @@ class SettingsCog(commands.Cog):
|
|||
"""Delete a guild setting."""
|
||||
return Setting.objects(setting=setting, guild=guild).delete()
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
base_desc="Settings management",
|
||||
subcommand_group="set",
|
||||
subcommand_group_description="Set a setting",
|
||||
name="mute",
|
||||
description="Set mute role",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Mute role",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _set_mute(self, ctx: SlashContext, role: Role) -> None:
|
||||
await ctx.defer()
|
||||
self.update_settings("mute", role.id, ctx.guild.id)
|
||||
await ctx.send(f"Settings applied. New mute role is `{role.name}`")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
subcommand_group="set",
|
||||
name="modlog",
|
||||
description="Set modlog channel",
|
||||
options=[
|
||||
choices=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Modlog channel",
|
||||
option_type=7,
|
||||
opt_type=7,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _set_modlog(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
||||
await ctx.send("Channel must be a TextChannel", ephemeral=True)
|
||||
return
|
||||
self.update_settings("modlog", channel.id, ctx.guild.id)
|
||||
await ctx.send(f"Settings applied. New modlog channel is {channel.mention}")
|
||||
|
@ -82,19 +61,19 @@ class SettingsCog(commands.Cog):
|
|||
subcommand_group="set",
|
||||
name="userlog",
|
||||
description="Set userlog channel",
|
||||
options=[
|
||||
choices=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Userlog channel",
|
||||
option_type=7,
|
||||
opt_type=7,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _set_userlog(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
||||
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}")
|
||||
|
@ -104,16 +83,16 @@ class SettingsCog(commands.Cog):
|
|||
subcommand_group="set",
|
||||
name="massmention",
|
||||
description="Set massmention amount",
|
||||
options=[
|
||||
choices=[
|
||||
create_option(
|
||||
name="amount",
|
||||
description="Amount of mentions (0 to disable)",
|
||||
option_type=4,
|
||||
opt_type=4,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _set_massmention(self, ctx: SlashContext, amount: int) -> None:
|
||||
await ctx.defer()
|
||||
self.update_settings("massmention", amount, ctx.guild.id)
|
||||
|
@ -124,16 +103,16 @@ class SettingsCog(commands.Cog):
|
|||
subcommand_group="set",
|
||||
name="verified",
|
||||
description="Set verified role",
|
||||
options=[
|
||||
choices=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="verified role",
|
||||
option_type=8,
|
||||
opt_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _set_verified(self, ctx: SlashContext, role: Role) -> None:
|
||||
await ctx.defer()
|
||||
self.update_settings("verified", role.id, ctx.guild.id)
|
||||
|
@ -144,16 +123,16 @@ class SettingsCog(commands.Cog):
|
|||
subcommand_group="set",
|
||||
name="unverified",
|
||||
description="Set unverified role",
|
||||
options=[
|
||||
choices=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Unverified role",
|
||||
option_type=8,
|
||||
opt_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _set_unverified(self, ctx: SlashContext, role: Role) -> None:
|
||||
await ctx.defer()
|
||||
self.update_settings("unverified", role.id, ctx.guild.id)
|
||||
|
@ -164,41 +143,28 @@ class SettingsCog(commands.Cog):
|
|||
subcommand_group="set",
|
||||
name="noinvite",
|
||||
description="Set if invite deletion should happen",
|
||||
options=[
|
||||
choices=[
|
||||
create_option(
|
||||
name="active",
|
||||
description="Active?",
|
||||
option_type=4,
|
||||
opt_type=4,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _set_invitedel(self, ctx: SlashContext, active: int) -> None:
|
||||
await ctx.defer()
|
||||
self.update_settings("noinvite", bool(active), ctx.guild.id)
|
||||
await ctx.send(f"Settings applied. Automatic invite active: {bool(active)}")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
subcommand_group="unset",
|
||||
subcommand_group_description="Unset a setting",
|
||||
name="mute",
|
||||
description="Unset mute role",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _unset_mute(self, ctx: SlashContext) -> None:
|
||||
await ctx.defer()
|
||||
self.delete_settings("mute", ctx.guild.id)
|
||||
await ctx.send("Setting removed.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="settings",
|
||||
subcommand_group="unset",
|
||||
name="modlog",
|
||||
description="Unset modlog channel",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _unset_modlog(self, ctx: SlashContext) -> None:
|
||||
self.delete_settings("modlog", ctx.guild.id)
|
||||
await ctx.send("Setting removed.")
|
||||
|
@ -209,7 +175,7 @@ class SettingsCog(commands.Cog):
|
|||
name="userlog",
|
||||
description="Unset userlog channel",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _unset_userlog(self, ctx: SlashContext) -> None:
|
||||
self.delete_settings("userlog", ctx.guild.id)
|
||||
await ctx.send("Setting removed.")
|
||||
|
@ -220,7 +186,7 @@ class SettingsCog(commands.Cog):
|
|||
name="massmention",
|
||||
description="Unet massmention amount",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _massmention(self, ctx: SlashContext) -> None:
|
||||
await ctx.defer()
|
||||
self.delete_settings("massmention", ctx.guild.id)
|
||||
|
@ -232,7 +198,7 @@ class SettingsCog(commands.Cog):
|
|||
name="verified",
|
||||
description="Unset verified role",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _verified(self, ctx: SlashContext) -> None:
|
||||
await ctx.defer()
|
||||
self.delete_settings("verified", ctx.guild.id)
|
||||
|
@ -244,14 +210,14 @@ class SettingsCog(commands.Cog):
|
|||
name="unverified",
|
||||
description="Unset unverified role",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _unverified(self, ctx: SlashContext) -> None:
|
||||
await ctx.defer()
|
||||
self.delete_settings("unverified", ctx.guild.id)
|
||||
await ctx.send("Setting removed.")
|
||||
|
||||
@cog_ext.cog_subcommand(base="settings", name="view", description="View settings")
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _view(self, ctx: SlashContext) -> None:
|
||||
settings = Setting.objects(guild=ctx.guild.id)
|
||||
|
||||
|
@ -272,7 +238,7 @@ class SettingsCog(commands.Cog):
|
|||
value = "||`[redacted]`||"
|
||||
elif setting.setting == "rolegiver":
|
||||
value = ""
|
||||
for role in setting.value:
|
||||
for _role in setting.value:
|
||||
nvalue = find(lambda x: x.id == value, ctx.guild.roles)
|
||||
if value:
|
||||
value += "\n" + nvalue.mention
|
||||
|
@ -285,7 +251,7 @@ class SettingsCog(commands.Cog):
|
|||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_subcommand(base="settings", name="clear", description="Clear all settings")
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(manage_guild=True))
|
||||
async def _clear(self, ctx: SlashContext) -> None:
|
||||
deleted = Setting.objects(guild=ctx.guild.id).delete()
|
||||
await ctx.send(f"Guild settings cleared: `{deleted is not None}`")
|
||||
|
@ -293,4 +259,4 @@ class SettingsCog(commands.Cog):
|
|||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
"""Add SettingsCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(SettingsCog(bot))
|
||||
SettingsCog(bot)
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
"""J.A.R.V.I.S. Starboard Cog."""
|
||||
from discord import TextChannel
|
||||
from discord.ext import commands
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.context import MenuContext
|
||||
from discord_slash.model import ContextMenuType, SlashMessage
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
from discord_slash.utils.manage_components import (
|
||||
create_actionrow,
|
||||
create_select,
|
||||
create_select_option,
|
||||
wait_for_component,
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
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
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
CommandTypes,
|
||||
OptionTypes,
|
||||
context_menu,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.db.models import Star, Starboard
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils import build_embed, find
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
supported_images = [
|
||||
|
@ -26,19 +25,15 @@ supported_images = [
|
|||
]
|
||||
|
||||
|
||||
class StarboardCog(commands.Cog):
|
||||
class StarboardCog(Scale):
|
||||
"""J.A.R.V.I.S. Starboard Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="starboard",
|
||||
name="list",
|
||||
description="Lists all Starboards",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _list(self, ctx: SlashContext) -> None:
|
||||
@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)
|
||||
if starboards != []:
|
||||
message = "Available Starboards:\n"
|
||||
|
@ -48,39 +43,35 @@ class StarboardCog(commands.Cog):
|
|||
else:
|
||||
await ctx.send("No Starboards available.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="starboard",
|
||||
name="create",
|
||||
description="Create a starboard",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Starboard channel",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="starboard", sub_cmd_name="create", sub_cmd_description="Create a starboard"
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _create(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Starboard channel",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _create(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||
if channel not in ctx.guild.channels:
|
||||
await ctx.send(
|
||||
"Channel not in guild. Choose an existing channel.",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
||||
if not isinstance(channel, GuildText):
|
||||
await ctx.send("Channel must be a GuildText", ephemeral=True)
|
||||
return
|
||||
|
||||
exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first()
|
||||
if exists:
|
||||
await ctx.send(f"Starboard already exists at {channel.mention}.", hidden=True)
|
||||
await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True)
|
||||
return
|
||||
|
||||
count = Starboard.objects(guild=ctx.guild.id).count()
|
||||
if count >= 25:
|
||||
await ctx.send("25 starboard limit reached", hidden=True)
|
||||
await ctx.send("25 starboard limit reached", ephemeral=True)
|
||||
return
|
||||
|
||||
_ = Starboard(
|
||||
|
@ -90,95 +81,89 @@ class StarboardCog(commands.Cog):
|
|||
).save()
|
||||
await ctx.send(f"Starboard created. Check it out at {channel.mention}.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="starboard",
|
||||
name="delete",
|
||||
description="Delete a starboard",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Starboard channel",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@slash_command(
|
||||
name="starboard", sub_cmd_name="delete", sub_cmd_description="Delete a starboard"
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _delete(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Starboard channel",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@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()
|
||||
await ctx.send(f"Starboard deleted from {channel.mention}.", hidden=True)
|
||||
await ctx.send(f"Starboard deleted from {channel.mention}.")
|
||||
else:
|
||||
await ctx.send(f"Starboard not found in {channel.mention}.", hidden=True)
|
||||
await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True)
|
||||
|
||||
@cog_ext.cog_context_menu(name="Star Message", target=ContextMenuType.MESSAGE)
|
||||
async def _star_message(self, ctx: MenuContext) -> None:
|
||||
await self._star_add.invoke(ctx, ctx.target_message)
|
||||
@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))
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="star",
|
||||
name="add",
|
||||
description="Star a message",
|
||||
options=[
|
||||
create_option(
|
||||
name="message",
|
||||
description="Message to star",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel that has the message, required if different than command message",
|
||||
option_type=7,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
@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
|
||||
)
|
||||
@admin_or_permissions(manage_guild=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: SlashContext,
|
||||
ctx: InteractionContext,
|
||||
message: str,
|
||||
channel: TextChannel = None,
|
||||
channel: GuildText = None,
|
||||
) -> None:
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
starboards = Starboard.objects(guild=ctx.guild.id)
|
||||
if not starboards:
|
||||
await ctx.send("No starboards exist.", hidden=True)
|
||||
await ctx.send("No starboards exist.", ephemeral=True)
|
||||
return
|
||||
|
||||
await ctx.defer()
|
||||
|
||||
if not isinstance(message, Message):
|
||||
if message.startswith("https://"):
|
||||
message = message.split("/")[-1]
|
||||
message = await channel.get_message(int(message))
|
||||
|
||||
if not message:
|
||||
await ctx.send("Message not found", ephemeral=True)
|
||||
return
|
||||
|
||||
channel_list = []
|
||||
for starboard in starboards:
|
||||
channel_list.append(find(lambda x: x.id == starboard.channel, ctx.guild.channels))
|
||||
|
||||
select_channels = [create_select_option(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list)]
|
||||
select_channels = [
|
||||
SelectOption(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list)
|
||||
]
|
||||
|
||||
select = create_select(
|
||||
select = Select(
|
||||
options=select_channels,
|
||||
min_values=1,
|
||||
max_values=1,
|
||||
)
|
||||
|
||||
components = [create_actionrow(select)]
|
||||
components = [ActionRow(select)]
|
||||
|
||||
msg = await ctx.send(content="Choose a starboard", components=components)
|
||||
|
||||
com_ctx = await wait_for_component(
|
||||
self.bot,
|
||||
com_ctx = await self.bot.wait_for_component(
|
||||
messages=msg,
|
||||
components=components,
|
||||
check=lambda x: x.author.id == ctx.author.id,
|
||||
check=lambda x: ctx.author.id == x.context.author.id,
|
||||
)
|
||||
|
||||
starboard = channel_list[int(com_ctx.selected_options[0])]
|
||||
|
||||
if not isinstance(message, SlashMessage):
|
||||
if message.startswith("https://"):
|
||||
message = message.split("/")[-1]
|
||||
message = await channel.fetch_message(message)
|
||||
starboard = channel_list[int(com_ctx.context.values[0])]
|
||||
|
||||
exists = Star.objects(
|
||||
message=message.id,
|
||||
|
@ -190,7 +175,7 @@ class StarboardCog(commands.Cog):
|
|||
if exists:
|
||||
await ctx.send(
|
||||
f"Message already sent to Starboard {starboard.mention}",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -215,9 +200,9 @@ class StarboardCog(commands.Cog):
|
|||
timestamp=message.created_at,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.name,
|
||||
name=message.author.display_name,
|
||||
url=message.jump_url,
|
||||
icon_url=message.author.avatar_url,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
)
|
||||
embed.set_footer(text=message.guild.name + " | " + message.channel.name)
|
||||
if image_url:
|
||||
|
@ -236,47 +221,38 @@ class StarboardCog(commands.Cog):
|
|||
active=True,
|
||||
).save()
|
||||
|
||||
components[0]["components"][0]["disabled"] = True
|
||||
components[0].components[0].disabled = True
|
||||
|
||||
await com_ctx.edit_origin(
|
||||
await com_ctx.context.edit_origin(
|
||||
content=f"Message saved to Starboard.\nSee it in {starboard.mention}",
|
||||
components=components,
|
||||
)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="star",
|
||||
name="delete",
|
||||
description="Delete a starred message",
|
||||
options=[
|
||||
create_option(
|
||||
name="id",
|
||||
description="Star to delete",
|
||||
option_type=4,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="starboard",
|
||||
description="Starboard to delete star from",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
@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
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@slash_option(
|
||||
name="starboard",
|
||||
description="Starboard to delete star from",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _star_delete(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
ctx: InteractionContext,
|
||||
id: int,
|
||||
starboard: TextChannel,
|
||||
starboard: GuildText,
|
||||
) -> None:
|
||||
if not isinstance(starboard, TextChannel):
|
||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
||||
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()
|
||||
if not exists:
|
||||
await ctx.send(
|
||||
f"Starboard does not exist in {starboard.mention}. Please create it first",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -287,19 +263,19 @@ class StarboardCog(commands.Cog):
|
|||
active=True,
|
||||
).first()
|
||||
if not star:
|
||||
await ctx.send(f"No star exists with id {id}", hidden=True)
|
||||
await ctx.send(f"No star exists with id {id}", ephemeral=True)
|
||||
return
|
||||
|
||||
message = await starboard.fetch_message(star.star)
|
||||
message = await starboard.get_message(star.star)
|
||||
if message:
|
||||
await message.delete()
|
||||
|
||||
star.active = False
|
||||
star.save()
|
||||
|
||||
await ctx.send(f"Star {id} deleted")
|
||||
await ctx.send(f"Star {id} deleted from {starboard.mention}")
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add StarboardCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(StarboardCog(bot))
|
||||
StarboardCog(bot)
|
||||
|
|
|
@ -1,131 +1,93 @@
|
|||
"""J.A.R.V.I.S. Twitter Cog."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import tweepy
|
||||
from bson import ObjectId
|
||||
from discord import TextChannel
|
||||
from discord.ext import commands
|
||||
from discord.ext.tasks import loop
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import SlashCommandOptionType as COptionType
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
from discord_slash.utils.manage_components import (
|
||||
create_actionrow,
|
||||
create_select,
|
||||
create_select_option,
|
||||
wait_for_component,
|
||||
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||
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 (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import check
|
||||
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.models import Twitter
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
logger = logging.getLogger("discord")
|
||||
|
||||
|
||||
class TwitterCog(commands.Cog):
|
||||
class TwitterCog(Scale):
|
||||
"""J.A.R.V.I.S. Twitter Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
config = get_config()
|
||||
auth = tweepy.AppAuthHandler(config.twitter["consumer_key"], config.twitter["consumer_secret"])
|
||||
auth = tweepy.AppAuthHandler(
|
||||
config.twitter["consumer_key"], config.twitter["consumer_secret"]
|
||||
)
|
||||
self.api = tweepy.API(auth)
|
||||
self._tweets.start()
|
||||
self._guild_cache = {}
|
||||
self._channel_cache = {}
|
||||
|
||||
@loop(seconds=30)
|
||||
async def _tweets(self) -> None:
|
||||
twitters = Twitter.objects(active=True)
|
||||
handles = Twitter.objects.distinct("handle")
|
||||
twitter_data = {}
|
||||
for handle in handles:
|
||||
try:
|
||||
twitter_data[handle] = self.api.user_timeline(screen_name=handle)
|
||||
except Exception as e:
|
||||
logger.error(f"Error with fetching: {e}")
|
||||
for twitter in twitters:
|
||||
try:
|
||||
tweets = list(filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle]))
|
||||
if tweets:
|
||||
tweets = sorted(tweets, key=lambda x: x.id)
|
||||
if twitter.guild not in self._guild_cache:
|
||||
self._guild_cache[twitter.guild] = await self.bot.fetch_guild(twitter.guild)
|
||||
guild = self._guild_cache[twitter.guild]
|
||||
if twitter.channel not in self._channel_cache:
|
||||
channels = await guild.fetch_channels()
|
||||
self._channel_cache[twitter.channel] = find(lambda x: x.id == twitter.channel, channels)
|
||||
channel = self._channel_cache[twitter.channel]
|
||||
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}")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="twitter",
|
||||
base_description="Twitter commands",
|
||||
name="follow",
|
||||
description="Follow a Twitter account",
|
||||
options=[
|
||||
create_option(name="handle", description="Twitter account", option_type=COptionType.STRING, required=True),
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to post tweets into",
|
||||
option_type=COptionType.CHANNEL,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="retweets",
|
||||
description="Mirror re-tweets?",
|
||||
option_type=COptionType.STRING,
|
||||
required=False,
|
||||
choices=[create_choice(name="Yes", value="Yes"), create_choice(name="No", value="No")],
|
||||
),
|
||||
@slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount")
|
||||
@slash_option(
|
||||
name="handle", description="Twitter account", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
@slash_option(
|
||||
name="channel",
|
||||
description="Channel to post tweets to",
|
||||
opt_type=OptionTypes.CHANNEL,
|
||||
required=True,
|
||||
)
|
||||
@slash_option(
|
||||
name="retweets",
|
||||
description="Mirror re-tweets?",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Yes", value="Yes"),
|
||||
SlashCommandChoice(name="No", value="No"),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||
async def _twitter_follow(
|
||||
self, ctx: SlashContext, handle: str, channel: TextChannel, retweets: str = "Yes"
|
||||
self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: str = "Yes"
|
||||
) -> None:
|
||||
handle = handle.lower()
|
||||
retweets = retweets == "Yes"
|
||||
if len(handle) > 15:
|
||||
await ctx.send("Invalid Twitter handle", hidden=True)
|
||||
await ctx.send("Invalid Twitter handle", ephemeral=True)
|
||||
return
|
||||
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a text channel", hidden=True)
|
||||
if not isinstance(channel, GuildText):
|
||||
await ctx.send("Channel must be a text channel", ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
latest_tweet = self.api.user_timeline(screen_name=handle, count=1)[0]
|
||||
account = (await asyncio.to_thread(self.api.get_user(screen_name=handle)))[0]
|
||||
latest_tweet = (await asyncio.to_thread(self.api.user_timeline, screen_name=handle))[0]
|
||||
except Exception:
|
||||
await ctx.send("Unable to get user timeline. Are you sure the handle is correct?", hidden=True)
|
||||
await ctx.send(
|
||||
"Unable to get user timeline. Are you sure the handle is correct?", ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
count = Twitter.objects(guild=ctx.guild.id).count()
|
||||
if count >= 12:
|
||||
await ctx.send("Cannot follow more than 12 Twitter accounts", hidden=True)
|
||||
await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True)
|
||||
return
|
||||
|
||||
exists = Twitter.objects(handle=handle, guild=ctx.guild.id)
|
||||
exists = Twitter.objects(twitter_id=account.id, guild=ctx.guild.id)
|
||||
if exists:
|
||||
await ctx.send("Twitter handle already being followed in this guild", hidden=True)
|
||||
await ctx.send("Twitter account already being followed in this guild", ephemeral=True)
|
||||
return
|
||||
|
||||
t = Twitter(
|
||||
handle=handle,
|
||||
handle=account.screen_name,
|
||||
twitter_id=account.id,
|
||||
guild=ctx.guild.id,
|
||||
channel=channel.id,
|
||||
admin=ctx.author.id,
|
||||
|
@ -137,27 +99,25 @@ class TwitterCog(commands.Cog):
|
|||
|
||||
await ctx.send(f"Now following `@{handle}` in {channel.mention}")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="twitter",
|
||||
name="unfollow",
|
||||
description="Unfollow Twitter accounts",
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _twitter_unfollow(self, ctx: SlashContext) -> None:
|
||||
@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)
|
||||
if not twitters:
|
||||
await ctx.send("You need to follow a Twitter account first", hidden=True)
|
||||
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}
|
||||
for twitter in twitters:
|
||||
option = create_select_option(label=twitter.handle, value=str(twitter.id))
|
||||
option = SelectOption(label=twitter.handle, value=str(twitter.id))
|
||||
options.append(option)
|
||||
|
||||
select = create_select(options=options, custom_id="to_delete", min_values=1, max_values=len(twitters))
|
||||
select = Select(
|
||||
options=options, custom_id="to_delete", min_values=1, max_values=len(twitters)
|
||||
)
|
||||
|
||||
components = [create_actionrow(select)]
|
||||
components = [ActionRow(select)]
|
||||
block = "\n".join(x.handle for x in twitters)
|
||||
message = await ctx.send(
|
||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
||||
|
@ -166,52 +126,58 @@ class TwitterCog(commands.Cog):
|
|||
)
|
||||
|
||||
try:
|
||||
context = await wait_for_component(
|
||||
self.bot, check=lambda x: ctx.author.id == x.author.id, messages=message, timeout=60 * 5
|
||||
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.selected_options:
|
||||
for to_delete in context.context.values:
|
||||
_ = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_delete)).delete()
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
block = "\n".join(handlemap[x] for x in context.selected_options)
|
||||
await context.edit_origin(content=f"Unfollowed the following:\n```\n{block}\n```", components=components)
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
block = "\n".join(handlemap[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
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(components=components)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="twitter",
|
||||
@slash_command(
|
||||
name="twitter", sub_cmd_name="retweets", description="Modify followed Twitter accounts"
|
||||
)
|
||||
@slash_option(
|
||||
name="retweets",
|
||||
description="Modify followed Twitter accounts",
|
||||
options=[
|
||||
create_option(
|
||||
name="retweets",
|
||||
description="Mirror re-tweets?",
|
||||
option_type=COptionType.STRING,
|
||||
required=True,
|
||||
choices=[create_choice(name="Yes", value="Yes"), create_choice(name="No", value="No")],
|
||||
),
|
||||
description="Mirror re-tweets?",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="Yes", value="Yes"),
|
||||
SlashCommandChoice(name="No", value="No"),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_guild=True)
|
||||
async def _twitter_modify(self, ctx: SlashContext, retweets: str) -> None:
|
||||
@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)
|
||||
if not twitters:
|
||||
await ctx.send("You need to follow a Twitter account first", hidden=True)
|
||||
await ctx.send("You need to follow a Twitter account first", ephemeral=True)
|
||||
return
|
||||
|
||||
options = []
|
||||
for twitter in twitters:
|
||||
option = create_select_option(label=twitter.handle, value=str(twitter.id))
|
||||
option = SelectOption(label=twitter.handle, value=str(twitter.id))
|
||||
options.append(option)
|
||||
|
||||
select = create_select(options=options, custom_id="to_update", min_values=1, max_values=len(twitters))
|
||||
select = Select(
|
||||
options=options, custom_id="to_update", min_values=1, max_values=len(twitters)
|
||||
)
|
||||
|
||||
components = [create_actionrow(select)]
|
||||
components = [ActionRow(select)]
|
||||
block = "\n".join(x.handle for x in twitters)
|
||||
message = await ctx.send(
|
||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
||||
|
@ -220,30 +186,38 @@ class TwitterCog(commands.Cog):
|
|||
)
|
||||
|
||||
try:
|
||||
context = await wait_for_component(
|
||||
self.bot, check=lambda x: ctx.author.id == x.author.id, messages=message, timeout=60 * 5
|
||||
context = await self.bot.wait_for_component(
|
||||
check=lambda x: ctx.author.id == x.author.id,
|
||||
messages=message,
|
||||
timeout=60 * 5,
|
||||
)
|
||||
|
||||
handlemap = {str(x.id): x.handle for x in twitters}
|
||||
for to_update in context.selected_options:
|
||||
for to_update in context.context.values:
|
||||
t = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_update)).first()
|
||||
t.retweets = retweets
|
||||
t.save()
|
||||
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
block = "\n".join(handlemap[x] for x in context.selected_options)
|
||||
await context.edit_origin(
|
||||
content=f"{'Unfollowed' if not retweets else 'Followed'} retweets from the following:"
|
||||
f"\n```\n{block}\n```",
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
|
||||
block = "\n".join(handlemap[x] for x in context.context.values)
|
||||
await context.context.edit_origin(
|
||||
content=(
|
||||
f"{'Unfollowed' if not retweets else 'Followed'} "
|
||||
"retweets from the following:"
|
||||
f"\n```\n{block}\n```"
|
||||
),
|
||||
components=components,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
for component in row["components"]:
|
||||
component["disabled"] = True
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(components=components)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add TwitterCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(TwitterCog(bot))
|
||||
TwitterCog(bot)
|
||||
|
|
|
@ -1,165 +1,148 @@
|
|||
"""J.A.R.V.I.S. Utility Cog."""
|
||||
import platform
|
||||
import re
|
||||
import secrets
|
||||
import string
|
||||
from io import BytesIO
|
||||
|
||||
import discord
|
||||
import discord_slash
|
||||
import numpy as np
|
||||
from discord import File, Guild, Role, User
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
from dis_snek import InteractionContext, Scale, Snake, const
|
||||
from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice
|
||||
from dis_snek.models.discord.embed import EmbedField
|
||||
from dis_snek.models.discord.file import File
|
||||
from dis_snek.models.discord.guild import Guild
|
||||
from dis_snek.models.discord.role import Role
|
||||
from dis_snek.models.discord.user import User
|
||||
from dis_snek.models.snek.application_commands import (
|
||||
OptionTypes,
|
||||
SlashCommandChoice,
|
||||
slash_command,
|
||||
slash_option,
|
||||
)
|
||||
from dis_snek.models.snek.command import cooldown
|
||||
from dis_snek.models.snek.cooldowns import Buckets
|
||||
from PIL import Image
|
||||
|
||||
import jarvis
|
||||
from jarvis import jarvis_self
|
||||
from jarvis.config import get_config
|
||||
from jarvis.data import pigpen
|
||||
from jarvis.data.robotcamo import emotes, hk, names
|
||||
from jarvis.utils import build_embed, convert_bytesize, get_repo_hash
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils import build_embed, get_repo_hash
|
||||
|
||||
JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA")
|
||||
|
||||
|
||||
class UtilCog(commands.Cog):
|
||||
class UtilCog(Scale):
|
||||
"""
|
||||
Utility functions for J.A.R.V.I.S.
|
||||
|
||||
Mostly system utility functions, but may change over time
|
||||
"""
|
||||
|
||||
def __init__(self, bot: commands.Cog):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.config = get_config()
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="status",
|
||||
description="Retrieve J.A.R.V.I.S. status",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _status(self, ctx: SlashContext) -> None:
|
||||
@slash_command(name="status", description="Retrieve J.A.R.V.I.S. status")
|
||||
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||
async def _status(self, ctx: InteractionContext) -> None:
|
||||
title = "J.A.R.V.I.S. Status"
|
||||
desc = "All systems online"
|
||||
color = "#98CCDA"
|
||||
color = "#3498db"
|
||||
fields = []
|
||||
with jarvis_self.oneshot():
|
||||
fields.append(Field("CPU Usage", jarvis_self.cpu_percent()))
|
||||
fields.append(
|
||||
Field(
|
||||
"RAM Usage",
|
||||
convert_bytesize(jarvis_self.memory_info().rss),
|
||||
)
|
||||
)
|
||||
fields.append(Field("PID", jarvis_self.pid))
|
||||
fields.append(Field("discord_slash", discord_slash.__version__))
|
||||
fields.append(Field("discord.py", discord.__version__))
|
||||
fields.append(Field("Version", jarvis.__version__, False))
|
||||
fields.append(Field("Git Hash", get_repo_hash()[:7], False))
|
||||
embed = build_embed(title=title, description=desc, fields=fields, color=color)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
fields.append(EmbedField(name="dis-snek", value=const.__version__))
|
||||
fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False))
|
||||
fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False))
|
||||
embed = build_embed(title=title, description=desc, fields=fields, color=color)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@slash_command(
|
||||
name="logo",
|
||||
description="Get the current logo",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
||||
async def _logo(self, ctx: SlashContext) -> None:
|
||||
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||
async def _logo(self, ctx: InteractionContext) -> None:
|
||||
with BytesIO() as image_bytes:
|
||||
JARVIS_LOGO.save(image_bytes, "PNG")
|
||||
image_bytes.seek(0)
|
||||
logo = File(image_bytes, filename="logo.png")
|
||||
|
||||
logo = File(image_bytes, file_name="logo.png")
|
||||
await ctx.send(file=logo)
|
||||
|
||||
@cog_ext.cog_slash(name="rchk", description="Robot Camo HK416")
|
||||
async def _rchk(self, ctx: SlashContext) -> None:
|
||||
@slash_command(name="rchk", description="Robot Camo HK416")
|
||||
async def _rchk(self, ctx: InteractionContext) -> None:
|
||||
await ctx.send(content=hk)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="rcauto",
|
||||
description="Automates robot camo letters",
|
||||
options=[
|
||||
create_option(
|
||||
name="text",
|
||||
description="Text to camo-ify",
|
||||
option_type=3,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
async def _rcauto(self, ctx: SlashContext, text: str) -> None:
|
||||
@slash_option(
|
||||
name="text",
|
||||
description="Text to camo-ify",
|
||||
opt_type=OptionTypes.STRING,
|
||||
required=True,
|
||||
)
|
||||
async def _rcauto(self, ctx: InteractionContext, text: str) -> None:
|
||||
to_send = ""
|
||||
if len(text) == 1 and not re.match(r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()):
|
||||
await ctx.send("Please use ASCII characters.", hidden=True)
|
||||
await ctx.send("Please use ASCII characters.", ephemeral=True)
|
||||
return
|
||||
for letter in text.upper():
|
||||
if letter == " ":
|
||||
to_send += " "
|
||||
elif re.match(r"^[A-Z0-9-()$@!?^'#.]$", letter):
|
||||
id = emotes[letter]
|
||||
if ctx.author.is_on_mobile():
|
||||
to_send += f":{names[id]}:"
|
||||
else:
|
||||
to_send += f"<:{names[id]}:{id}>"
|
||||
to_send += f":{names[id]}:"
|
||||
if len(to_send) > 2000:
|
||||
await ctx.send("Too long.", hidden=True)
|
||||
await ctx.send("Too long.", ephemeral=True)
|
||||
else:
|
||||
await ctx.send(to_send)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="avatar",
|
||||
description="Get a user avatar",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to view avatar of",
|
||||
option_type=6,
|
||||
required=False,
|
||||
)
|
||||
],
|
||||
@slash_command(name="avatar", description="Get a user avatar")
|
||||
@slash_option(
|
||||
name="user",
|
||||
description="User to view avatar of",
|
||||
opt_type=OptionTypes.USER,
|
||||
required=False,
|
||||
)
|
||||
@commands.cooldown(1, 5, commands.BucketType.user)
|
||||
async def _avatar(self, ctx: SlashContext, user: User = None) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=5)
|
||||
async def _avatar(self, ctx: InteractionContext, user: User = None) -> None:
|
||||
if not user:
|
||||
user = ctx.author
|
||||
|
||||
avatar = user.avatar_url
|
||||
avatar = user.display_avatar.url
|
||||
embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
|
||||
embed.set_image(url=avatar)
|
||||
embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=avatar)
|
||||
embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="roleinfo",
|
||||
description="Get role info",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to get info of",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
async def _roleinfo(self, ctx: SlashContext, role: Role) -> None:
|
||||
@slash_option(
|
||||
name="role",
|
||||
description="Role to get info of",
|
||||
opt_type=OptionTypes.ROLE,
|
||||
required=True,
|
||||
)
|
||||
async def _roleinfo(self, ctx: InteractionContext, role: Role) -> None:
|
||||
fields = [
|
||||
Field(name="ID", value=role.id),
|
||||
Field(name="Name", value=role.name),
|
||||
Field(name="Color", value=str(role.color)),
|
||||
Field(name="Mention", value=f"`{role.mention}`"),
|
||||
Field(name="Hoisted", value="Yes" if role.hoist else "No"),
|
||||
Field(name="Position", value=str(role.position)),
|
||||
Field(name="Mentionable", value="Yes" if role.mentionable else "No"),
|
||||
EmbedField(name="ID", value=str(role.id), inline=True),
|
||||
EmbedField(name="Name", value=role.name, inline=True),
|
||||
EmbedField(name="Color", value=str(role.color.hex), inline=True),
|
||||
EmbedField(name="Mention", value=f"`{role.mention}`", inline=True),
|
||||
EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True),
|
||||
EmbedField(name="Position", value=str(role.position), inline=True),
|
||||
EmbedField(name="Mentionable", value="Yes" if role.mentionable else "No", inline=True),
|
||||
EmbedField(name="Member Count", value=str(len(role.members)), inline=True),
|
||||
]
|
||||
|
||||
embed = build_embed(
|
||||
title="",
|
||||
description="",
|
||||
fields=fields,
|
||||
color=str(role.color),
|
||||
color=role.color,
|
||||
timestamp=role.created_at,
|
||||
)
|
||||
embed.set_footer(text="Role Created")
|
||||
|
@ -170,46 +153,47 @@ class UtilCog(commands.Cog):
|
|||
|
||||
fill = a > 0
|
||||
|
||||
data[..., :-1][fill.T] = list(role.color.to_rgb())
|
||||
data[..., :-1][fill.T] = list(role.color.rgb)
|
||||
|
||||
im = Image.fromarray(data)
|
||||
|
||||
with BytesIO() as image_bytes:
|
||||
im.save(image_bytes, "PNG")
|
||||
image_bytes.seek(0)
|
||||
color_show = File(image_bytes, filename="color_show.png")
|
||||
color_show = File(image_bytes, file_name="color_show.png")
|
||||
|
||||
await ctx.send(embed=embed, file=color_show)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
@slash_command(
|
||||
name="userinfo",
|
||||
description="Get user info",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to get info of",
|
||||
option_type=6,
|
||||
required=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
async def _userinfo(self, ctx: SlashContext, user: User = None) -> None:
|
||||
@slash_option(
|
||||
name="user",
|
||||
description="User to get info of",
|
||||
opt_type=OptionTypes.USER,
|
||||
required=False,
|
||||
)
|
||||
async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None:
|
||||
if not user:
|
||||
user = ctx.author
|
||||
user_roles = user.roles
|
||||
if user_roles:
|
||||
user_roles = sorted(user.roles, key=lambda x: -x.position)
|
||||
_ = user_roles.pop(-1)
|
||||
format_string = "%a, %b %-d, %Y %-I:%M %p"
|
||||
if platform.system() == "Windows":
|
||||
format_string = "%a, %b %#d, %Y %#I:%M %p"
|
||||
|
||||
fields = [
|
||||
Field(
|
||||
EmbedField(
|
||||
name="Joined",
|
||||
value=user.joined_at.strftime("%a, %b %-d, %Y %-I:%M %p"),
|
||||
value=user.joined_at.strftime(format_string),
|
||||
),
|
||||
Field(
|
||||
EmbedField(
|
||||
name="Registered",
|
||||
value=user.created_at.strftime("%a, %b %-d, %Y %-I:%M %p"),
|
||||
value=user.created_at.strftime(format_string),
|
||||
),
|
||||
Field(
|
||||
EmbedField(
|
||||
name=f"Roles [{len(user_roles)}]",
|
||||
value=" ".join([x.mention for x in user_roles]) if user_roles else "None",
|
||||
inline=False,
|
||||
|
@ -220,80 +204,82 @@ class UtilCog(commands.Cog):
|
|||
title="",
|
||||
description=user.mention,
|
||||
fields=fields,
|
||||
color=str(user_roles[0].color) if user_roles else "#FF0000",
|
||||
color=str(user_roles[0].color) if user_roles else "#3498db",
|
||||
)
|
||||
|
||||
embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=user.avatar_url)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_author(
|
||||
name=f"{user.display_name}#{user.discriminator}", icon_url=user.display_avatar.url
|
||||
)
|
||||
embed.set_thumbnail(url=user.display_avatar.url)
|
||||
embed.set_footer(text=f"ID: {user.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(name="serverinfo", description="Get server info")
|
||||
async def _server_info(self, ctx: SlashContext) -> None:
|
||||
@slash_command(name="serverinfo", description="Get server info")
|
||||
async def _server_info(self, ctx: InteractionContext) -> None:
|
||||
guild: Guild = ctx.guild
|
||||
|
||||
owner = f"{guild.owner.name}#{guild.owner.discriminator}" if guild.owner else "||`[redacted]`||"
|
||||
owner = await guild.get_owner()
|
||||
|
||||
region = guild.region
|
||||
categories = len(guild.categories)
|
||||
text_channels = len(guild.text_channels)
|
||||
voice_channels = len(guild.voice_channels)
|
||||
owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
|
||||
|
||||
categories = len([x for x in guild.channels if isinstance(x, GuildCategory)])
|
||||
text_channels = len([x for x in guild.channels if isinstance(x, GuildText)])
|
||||
voice_channels = len([x for x in guild.channels if isinstance(x, GuildVoice)])
|
||||
threads = len(guild.threads)
|
||||
members = guild.member_count
|
||||
roles = len(guild.roles)
|
||||
role_list = ", ".join(role.name for role in guild.roles)
|
||||
role_list = sorted(guild.roles, key=lambda x: x.position, reverse=True)
|
||||
role_list = ", ".join(role.mention for role in role_list)
|
||||
|
||||
fields = [
|
||||
Field(name="Owner", value=owner),
|
||||
Field(name="Region", value=region),
|
||||
Field(name="Channel Categories", value=categories),
|
||||
Field(name="Text Channels", value=text_channels),
|
||||
Field(name="Voice Channels", value=voice_channels),
|
||||
Field(name="Members", value=members),
|
||||
Field(name="Roles", value=roles),
|
||||
EmbedField(name="Owner", value=owner, inline=True),
|
||||
EmbedField(name="Channel Categories", value=str(categories), inline=True),
|
||||
EmbedField(name="Text Channels", value=str(text_channels), inline=True),
|
||||
EmbedField(name="Voice Channels", value=str(voice_channels), inline=True),
|
||||
EmbedField(name="Threads", value=str(threads), inline=True),
|
||||
EmbedField(name="Members", value=str(members), inline=True),
|
||||
EmbedField(name="Roles", value=str(roles), inline=True),
|
||||
]
|
||||
if len(role_list) < 1024:
|
||||
fields.append(Field(name="Role List", value=role_list, inline=False))
|
||||
fields.append(EmbedField(name="Role List", value=role_list, inline=False))
|
||||
|
||||
embed = build_embed(title="", description="", fields=fields, timestamp=guild.created_at)
|
||||
|
||||
embed.set_author(name=guild.name, icon_url=guild.icon_url)
|
||||
embed.set_thumbnail(url=guild.icon_url)
|
||||
embed.set_author(name=guild.name, icon_url=guild.icon.url)
|
||||
embed.set_thumbnail(url=guild.icon.url)
|
||||
embed.set_footer(text=f"ID: {guild.id} | Server Created")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="pw",
|
||||
name="gen",
|
||||
base_desc="Password utilites",
|
||||
@slash_command(
|
||||
name="pw",
|
||||
sub_cmd_name="gen",
|
||||
description="Generate a secure password",
|
||||
guild_ids=[862402786116763668],
|
||||
options=[
|
||||
create_option(
|
||||
name="length",
|
||||
description="Password length (default 32)",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
create_option(
|
||||
name="chars",
|
||||
description="Characters to include (default last option)",
|
||||
option_type=4,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(name="A-Za-z", value=0),
|
||||
create_choice(name="A-Fa-f0-9", value=1),
|
||||
create_choice(name="A-Za-z0-9", value=2),
|
||||
create_choice(name="A-Za-z0-9!@#$%^&*", value=3),
|
||||
],
|
||||
),
|
||||
scopes=[862402786116763668],
|
||||
)
|
||||
@slash_option(
|
||||
name="length",
|
||||
description="Password length (default 32)",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
)
|
||||
@slash_option(
|
||||
name="chars",
|
||||
description="Characters to include (default last option)",
|
||||
opt_type=OptionTypes.INTEGER,
|
||||
required=False,
|
||||
choices=[
|
||||
SlashCommandChoice(name="A-Za-z", value=0),
|
||||
SlashCommandChoice(name="A-Fa-f0-9", value=1),
|
||||
SlashCommandChoice(name="A-Za-z0-9", value=2),
|
||||
SlashCommandChoice(name="A-Za-z0-9!@#$%^&*", value=3),
|
||||
],
|
||||
)
|
||||
@commands.cooldown(1, 15, type=commands.BucketType.user)
|
||||
async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3) -> None:
|
||||
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
|
||||
async def _pw_gen(self, ctx: InteractionContext, length: int = 32, chars: int = 3) -> None:
|
||||
if length > 256:
|
||||
await ctx.send("Please limit password to 256 characters", hidden=True)
|
||||
await ctx.send("Please limit password to 256 characters", ephemeral=True)
|
||||
return
|
||||
choices = [
|
||||
string.ascii_letters,
|
||||
|
@ -307,15 +293,14 @@ class UtilCog(commands.Cog):
|
|||
f"Generated password:\n`{pw}`\n\n"
|
||||
'**WARNING: Once you press "Dismiss Message", '
|
||||
"*the password is lost forever***",
|
||||
hidden=True,
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="pigpen",
|
||||
description="Encode a string into pigpen",
|
||||
options=[create_option(name="text", description="Text to encode", option_type=3, required=True)],
|
||||
@slash_command(name="pigpen", description="Encode a string into pigpen")
|
||||
@slash_option(
|
||||
name="text", description="Text to encode", opt_type=OptionTypes.STRING, required=True
|
||||
)
|
||||
async def _pigpen(self, ctx: SlashContext, text: str) -> None:
|
||||
async def _pigpen(self, ctx: InteractionContext, text: str) -> None:
|
||||
outp = "`"
|
||||
for c in text:
|
||||
c = c.lower()
|
||||
|
@ -330,6 +315,6 @@ class UtilCog(commands.Cog):
|
|||
await ctx.send(outp[:2000])
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add UtilCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(UtilCog(bot))
|
||||
UtilCog(bot)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
"""J.A.R.V.I.S. Verify Cog."""
|
||||
import asyncio
|
||||
from random import randint
|
||||
|
||||
from discord.ext import commands
|
||||
from discord_slash import ComponentContext, SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.utils import manage_components
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
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
|
||||
|
||||
|
@ -16,36 +18,32 @@ def create_layout() -> list:
|
|||
for i in range(3):
|
||||
label = "YES" if i == yes else "NO"
|
||||
id = f"no_{i}" if not i == yes else "yes"
|
||||
color = ButtonStyle.green if i == yes else ButtonStyle.red
|
||||
color = ButtonStyles.GREEN if i == yes else ButtonStyles.RED
|
||||
buttons.append(
|
||||
manage_components.create_button(
|
||||
Button(
|
||||
style=color,
|
||||
label=label,
|
||||
custom_id=f"verify_button||{id}",
|
||||
)
|
||||
)
|
||||
action_row = manage_components.spread_to_rows(*buttons, max_in_row=3)
|
||||
return action_row
|
||||
return spread_to_rows(*buttons, max_in_row=3)
|
||||
|
||||
|
||||
class VerifyCog(commands.Cog):
|
||||
class VerifyCog(Scale):
|
||||
"""J.A.R.V.I.S. Verify Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="verify",
|
||||
description="Verify that you've read the rules",
|
||||
)
|
||||
@commands.cooldown(1, 15, commands.BucketType.user)
|
||||
async def _verify(self, ctx: SlashContext) -> None:
|
||||
@slash_command(name="verify", description="Verify that you've read the rules")
|
||||
@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()
|
||||
if not role:
|
||||
await ctx.send("This guild has not enabled verification", delete_after=5)
|
||||
return
|
||||
if ctx.guild.get_role(role.value) in ctx.author.roles:
|
||||
if await ctx.guild.get_role(role.value) in ctx.author.roles:
|
||||
await ctx.send("You are already verified.", delete_after=5)
|
||||
return
|
||||
components = create_layout()
|
||||
|
@ -53,40 +51,41 @@ class VerifyCog(commands.Cog):
|
|||
content=f"{ctx.author.mention}, please press the button that says `YES`.",
|
||||
components=components,
|
||||
)
|
||||
await message.delete(delay=15)
|
||||
|
||||
@cog_ext.cog_component(components=create_layout())
|
||||
async def _process(self, ctx: ComponentContext) -> None:
|
||||
await ctx.defer(edit_origin=True)
|
||||
try:
|
||||
if ctx.author.id != ctx.origin_message.mentions[0].id:
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
correct = ctx.custom_id.split("||")[-1] == "yes"
|
||||
if correct:
|
||||
components = ctx.origin_message.components
|
||||
for c in components:
|
||||
for c2 in c["components"]:
|
||||
c2["disabled"] = True
|
||||
setting = Setting.objects(guild=ctx.guild.id, setting="verified").first()
|
||||
role = 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()
|
||||
if setting:
|
||||
role = ctx.guild.get_role(setting.value)
|
||||
await ctx.author.remove_roles(role, reason="Verification passed")
|
||||
await ctx.edit_origin(
|
||||
content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.",
|
||||
components=manage_components.spread_to_rows(*components, max_in_row=5),
|
||||
)
|
||||
await ctx.origin_message.delete(delay=5)
|
||||
else:
|
||||
await ctx.edit_origin(
|
||||
content=f"{ctx.author.mention}, incorrect. Please press the button that says `YES`",
|
||||
context = await self.bot.wait_for_component(
|
||||
messages=message, check=lambda x: ctx.author.id == x.author.id, timeout=30
|
||||
)
|
||||
|
||||
correct = context.context.custom_id.split("||")[-1] == "yes"
|
||||
if correct:
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
setting = Setting.objects(guild=ctx.guild.id, setting="verified").first()
|
||||
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()
|
||||
if setting:
|
||||
role = await ctx.guild.get_role(setting.value)
|
||||
await ctx.author.remove_roles(role, reason="Verification passed")
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
await context.context.edit_origin(
|
||||
content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.",
|
||||
components=components,
|
||||
)
|
||||
await context.context.message.delete(delay=5)
|
||||
else:
|
||||
await context.context.edit_origin(
|
||||
content=(
|
||||
f"{ctx.author.mention}, incorrect. "
|
||||
"Please press the button that says `YES`"
|
||||
)
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
await message.delete(delay=30)
|
||||
|
||||
|
||||
def setup(bot: Snake) -> None:
|
||||
"""Add VerifyCog to J.A.R.V.I.S."""
|
||||
bot.add_cog(VerifyCog(bot))
|
||||
VerifyCog(bot)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Load the config for J.A.R.V.I.S."""
|
||||
import os
|
||||
|
||||
from pymongo import MongoClient
|
||||
from yaml import load
|
||||
|
||||
|
@ -27,6 +29,7 @@ class Config(object):
|
|||
logo: str,
|
||||
mongo: dict,
|
||||
urls: dict,
|
||||
sync: bool = False,
|
||||
log_level: str = "WARNING",
|
||||
cogs: list = None,
|
||||
events: bool = True,
|
||||
|
@ -46,6 +49,7 @@ class Config(object):
|
|||
self.max_messages = max_messages
|
||||
self.gitlab_token = gitlab_token
|
||||
self.twitter = twitter
|
||||
self.sync = sync or os.environ.get("SYNC_COMMANDS", False)
|
||||
self.__db_loaded = False
|
||||
self.__mongo = MongoClient(**self.mongo["connect"])
|
||||
|
||||
|
@ -61,8 +65,7 @@ class Config(object):
|
|||
@classmethod
|
||||
def from_yaml(cls, y: dict) -> "Config":
|
||||
"""Load the yaml config file."""
|
||||
instance = cls(**y)
|
||||
return instance
|
||||
return cls(**y)
|
||||
|
||||
|
||||
def get_config(path: str = "config.yaml") -> Config:
|
||||
|
|
|
@ -222,6 +222,7 @@ 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)
|
||||
|
@ -229,6 +230,7 @@ class Twitter(Document):
|
|||
retweets = BooleanField(default=True)
|
||||
admin = SnowflakeField(required=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
last_sync = DateTimeField()
|
||||
|
||||
meta = {"db_alias": "main"}
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
"""J.A.R.V.I.S. guild event handler."""
|
||||
import asyncio
|
||||
|
||||
from discord import Guild
|
||||
from discord.ext.commands import Bot
|
||||
from discord.utils import find
|
||||
|
||||
from jarvis.db.models import Setting
|
||||
|
||||
|
||||
class GuildEventHandler(object):
|
||||
"""J.A.R.V.I.S. guild event handler."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
self.bot = bot
|
||||
self.bot.add_listener(self.on_guild_join)
|
||||
|
||||
async def on_guild_join(self, guild: Guild) -> None:
|
||||
"""Handle on_guild_join event."""
|
||||
general = find(lambda x: x.name == "general", guild.channels)
|
||||
if general and general.permissions_for(guild.me).send_messages:
|
||||
user = self.bot.user
|
||||
await general.send(
|
||||
f"Allow me to introduce myself. I am {user.mention}, a virtual "
|
||||
"artificial intelligence, and I'm here to assist you with a "
|
||||
"variety of tasks as best I can, "
|
||||
"24 hours a day, seven days a week."
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
await general.send("Importing all preferences from home interface...")
|
||||
|
||||
# Set some default settings
|
||||
_ = Setting(guild=guild.id, setting="massmention", value=5).save()
|
||||
_ = Setting(guild=guild.id, setting="noinvite", value=True).save()
|
||||
|
||||
await general.send("Systems are now fully operational")
|
|
@ -1,6 +1,6 @@
|
|||
"""J.A.R.V.I.S. Member event handler."""
|
||||
from discord import Member
|
||||
from discord.ext.commands import Bot
|
||||
from dis_snek import Snake, listen
|
||||
from dis_snek.models.discord.user import Member
|
||||
|
||||
from jarvis.db.models import Mute, Setting
|
||||
|
||||
|
@ -8,10 +8,11 @@ from jarvis.db.models import Mute, Setting
|
|||
class MemberEventHandler(object):
|
||||
"""J.A.R.V.I.S. Member event handler."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
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
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
"""J.A.R.V.I.S. Message event handler."""
|
||||
import re
|
||||
|
||||
from discord import DMChannel, Message
|
||||
from discord.ext.commands import Bot
|
||||
from discord.utils import find
|
||||
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
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils import build_embed, find
|
||||
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
@ -19,7 +19,7 @@ invites = re.compile(
|
|||
class MessageEventHandler(object):
|
||||
"""J.A.R.V.I.S. Message event handler."""
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.bot.add_listener(self.on_message)
|
||||
self.bot.add_listener(self.on_message_edit)
|
||||
|
@ -46,7 +46,7 @@ class MessageEventHandler(object):
|
|||
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"
|
||||
content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501
|
||||
)
|
||||
content = re.sub(r"\s+", "", message.content)
|
||||
match = invites.search(content)
|
||||
|
@ -72,10 +72,10 @@ class MessageEventHandler(object):
|
|||
user=message.author.id,
|
||||
).save()
|
||||
fields = [
|
||||
Field(
|
||||
"Reason",
|
||||
"Sent an invite link",
|
||||
False,
|
||||
EmbedField(
|
||||
name="Reason",
|
||||
value="Sent an invite link",
|
||||
inline=False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
|
@ -85,9 +85,11 @@ class MessageEventHandler(object):
|
|||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick if message.author.nick else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501
|
||||
)
|
||||
embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}")
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
async def massmention(self, message: Message) -> None:
|
||||
|
@ -99,7 +101,8 @@ class MessageEventHandler(object):
|
|||
if (
|
||||
massmention
|
||||
and massmention.value > 0 # noqa: W503
|
||||
and len(message.mentions) - (1 if message.author in message.mentions else 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(
|
||||
|
@ -110,7 +113,7 @@ class MessageEventHandler(object):
|
|||
reason="Mass Mention",
|
||||
user=message.author.id,
|
||||
).save()
|
||||
fields = [Field("Reason", "Mass Mention", False)]
|
||||
fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
|
@ -118,9 +121,11 @@ class MessageEventHandler(object):
|
|||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick if message.author.nick else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}"
|
||||
)
|
||||
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:
|
||||
|
@ -173,10 +178,10 @@ class MessageEventHandler(object):
|
|||
user=message.author.id,
|
||||
).save()
|
||||
fields = [
|
||||
Field(
|
||||
"Reason",
|
||||
"Pinged a blocked role/user with a blocked role",
|
||||
False,
|
||||
EmbedField(
|
||||
name="Reason",
|
||||
value="Pinged a blocked role/user with a blocked role",
|
||||
inline=False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
|
@ -186,11 +191,14 @@ class MessageEventHandler(object):
|
|||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick if message.author.nick else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
icon_url=message.author.display_avatar.url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}"
|
||||
)
|
||||
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:
|
||||
|
@ -200,6 +208,7 @@ class MessageEventHandler(object):
|
|||
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:
|
||||
|
@ -208,3 +217,10 @@ class MessageEventHandler(object):
|
|||
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)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
"""J.A.R.V.I.S. background task handlers."""
|
||||
from jarvis.tasks import unban, unlock, unmute, unwarn
|
||||
from jarvis.tasks import twitter, unban, unlock, unwarn
|
||||
|
||||
|
||||
def init() -> None:
|
||||
"""Start the background task handlers."""
|
||||
unban.unban.start()
|
||||
unlock.unlock.start()
|
||||
unmute.unmute.start()
|
||||
unwarn.unwarn.start()
|
||||
twitter.tweets.start()
|
||||
|
|
45
jarvis/tasks/reminder.py
Normal file
45
jarvis/tasks/reminder.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
"""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)
|
70
jarvis/tasks/twitter.py
Normal file
70
jarvis/tasks/twitter.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
"""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,7 +1,9 @@
|
|||
"""J.A.R.V.I.S. unban background task handler."""
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from discord.ext.tasks import loop
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
import jarvis
|
||||
from jarvis.config import get_config
|
||||
|
@ -10,17 +12,18 @@ from jarvis.db.models import Ban, Unban
|
|||
jarvis_id = get_config().client_id
|
||||
|
||||
|
||||
@loop(minutes=10)
|
||||
async def unban() -> None:
|
||||
"""J.A.R.V.I.S. unban background task."""
|
||||
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.fetch_guild(ban.guild)
|
||||
user = await jarvis.jarvis.fetch_user(ban.user)
|
||||
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:
|
||||
guild.unban(user)
|
||||
await guild.unban(user=user, reason="Ban expired")
|
||||
ban.active = False
|
||||
ban.save()
|
||||
unbans.append(
|
||||
|
@ -34,4 +37,10 @@ async def unban() -> None:
|
|||
)
|
||||
)
|
||||
if unbans:
|
||||
Ban.objects().insert(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,25 +1,37 @@
|
|||
"""J.A.R.V.I.S. unlock background task handler."""
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from discord.ext.tasks import loop
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.models import Lock
|
||||
|
||||
|
||||
@loop(minutes=1)
|
||||
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."""
|
||||
locks = Lock.objects(active=True)
|
||||
for lock in locks:
|
||||
if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow():
|
||||
guild = await jarvis.jarvis.fetch_guild(lock.guild)
|
||||
channel = await jarvis.jarvis.fetch_channel(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()
|
||||
await to_thread(_unlock)
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
"""J.A.R.V.I.S. unmute background task handler."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from discord.ext.tasks import loop
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.models import Mute, Setting
|
||||
|
||||
|
||||
@loop(minutes=1)
|
||||
async def unmute() -> None:
|
||||
"""J.A.R.V.I.S. unmute background task."""
|
||||
mutes = Mute.objects(duration__gt=0, active=True)
|
||||
mute_roles = Setting.objects(setting="mute")
|
||||
for mute in mutes:
|
||||
if mute.created_at + timedelta(minutes=mute.duration) < datetime.utcnow():
|
||||
mute_role = [x.value for x in mute_roles if x.guild == mute.guild][0]
|
||||
guild = await jarvis.jarvis.fetch_guild(mute.guild)
|
||||
role = guild.get_role(mute_role)
|
||||
user = await guild.fetch_member(mute.user)
|
||||
if user:
|
||||
if role in user.roles:
|
||||
await user.remove_roles(role, reason="Mute expired")
|
||||
|
||||
# Objects can't handle bulk_write, so handle it via raw methods
|
||||
mute.active = False
|
||||
mute.save
|
|
@ -1,16 +1,23 @@
|
|||
"""J.A.R.V.I.S. unwarn background task handler."""
|
||||
from asyncio import to_thread
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from discord.ext.tasks import loop
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
from jarvis.db.models import Warning
|
||||
|
||||
|
||||
@loop(hours=1)
|
||||
async def unwarn() -> None:
|
||||
"""J.A.R.V.I.S. unwarn background task."""
|
||||
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,10 +1,10 @@
|
|||
"""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 discord import Color, Embed, Message
|
||||
from discord.ext import commands
|
||||
from dis_snek.models.discord.embed import Embed
|
||||
|
||||
import jarvis.cogs
|
||||
import jarvis.db
|
||||
|
@ -12,6 +12,31 @@ from jarvis.config import get_config
|
|||
|
||||
__all__ = ["field", "db", "cachecog", "permissions"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def build_embed(
|
||||
title: str,
|
||||
description: str,
|
||||
fields: list,
|
||||
color: str = "#FF0000",
|
||||
timestamp: datetime = None,
|
||||
**kwargs: dict,
|
||||
) -> Embed:
|
||||
"""Embed builder utility function."""
|
||||
if not timestamp:
|
||||
timestamp = datetime.now()
|
||||
embed = Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
color=color,
|
||||
timestamp=timestamp,
|
||||
**kwargs,
|
||||
)
|
||||
for field in fields:
|
||||
embed.add_field(**field.to_dict())
|
||||
return embed
|
||||
|
||||
|
||||
def convert_bytesize(b: int) -> str:
|
||||
"""Convert bytes amount to human readable."""
|
||||
|
@ -34,15 +59,6 @@ def unconvert_bytesize(size: int, ending: str) -> int:
|
|||
return round(size * (1024 ** sizes.index(ending)))
|
||||
|
||||
|
||||
def get_prefix(bot: commands.Bot, message: Message) -> list:
|
||||
"""Get bot prefixes."""
|
||||
prefixes = ["!", "-", "%"]
|
||||
# if not message.guild:
|
||||
# return "?"
|
||||
|
||||
return commands.when_mentioned_or(*prefixes)(bot, message)
|
||||
|
||||
|
||||
def get_extensions(path: str = jarvis.cogs.__path__) -> list:
|
||||
"""Get J.A.R.V.I.S. cogs."""
|
||||
config = get_config()
|
||||
|
@ -50,36 +66,6 @@ def get_extensions(path: str = jarvis.cogs.__path__) -> list:
|
|||
return ["jarvis.cogs.{}".format(x) for x in vals]
|
||||
|
||||
|
||||
def parse_color_hex(hex: str) -> Color:
|
||||
"""Convert a hex color to a d.py Color."""
|
||||
hex = hex.lstrip("#")
|
||||
rgb = tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4)) # noqa: E203
|
||||
return Color.from_rgb(*rgb)
|
||||
|
||||
|
||||
def build_embed(
|
||||
title: str,
|
||||
description: str,
|
||||
fields: list,
|
||||
color: str = "#FF0000",
|
||||
timestamp: datetime = None,
|
||||
**kwargs: dict,
|
||||
) -> Embed:
|
||||
"""Embed builder utility function."""
|
||||
if not timestamp:
|
||||
timestamp = datetime.utcnow()
|
||||
embed = Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
color=parse_color_hex(color),
|
||||
timestamp=timestamp,
|
||||
**kwargs,
|
||||
)
|
||||
for field in fields:
|
||||
embed.add_field(**field.to_dict())
|
||||
return embed
|
||||
|
||||
|
||||
def update() -> int:
|
||||
"""J.A.R.V.I.S. update utility."""
|
||||
repo = git.Repo(".")
|
||||
|
@ -99,3 +85,103 @@ 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
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
"""Cog wrapper for command caching."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from discord.ext import commands
|
||||
from discord.ext.tasks import loop
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashContext
|
||||
from dis_snek import InteractionContext, Scale, Snake
|
||||
from dis_snek.ext.tasks.task import Task
|
||||
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||
|
||||
from jarvis.utils import find
|
||||
|
||||
|
||||
class CacheCog(commands.Cog):
|
||||
class CacheCog(Scale):
|
||||
"""Cog wrapper for command caching."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
def __init__(self, bot: Snake):
|
||||
self.bot = bot
|
||||
self.cache = {}
|
||||
self._expire_interaction.start()
|
||||
|
||||
def check_cache(self, ctx: SlashContext, **kwargs: dict) -> dict:
|
||||
def check_cache(self, ctx: InteractionContext, **kwargs: dict) -> dict:
|
||||
"""Check the cache."""
|
||||
if not kwargs:
|
||||
kwargs = {}
|
||||
|
@ -27,7 +28,7 @@ class CacheCog(commands.Cog):
|
|||
self.cache.values(),
|
||||
)
|
||||
|
||||
@loop(minutes=1)
|
||||
@Task.create(IntervalTrigger(minutes=1))
|
||||
async def _expire_interaction(self) -> None:
|
||||
keys = list(self.cache.keys())
|
||||
for key in keys:
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
"""Embed field helper."""
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Field:
|
||||
"""Embed Field."""
|
||||
|
||||
name: Any
|
||||
value: Any
|
||||
inline: bool = True
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert Field to d.py field dict."""
|
||||
return {"name": self.name, "value": self.value, "inline": self.inline}
|
|
@ -1,5 +1,5 @@
|
|||
"""Permissions wrappers."""
|
||||
from discord.ext import commands
|
||||
from dis_snek import InteractionContext, Permissions
|
||||
|
||||
from jarvis.config import get_config
|
||||
|
||||
|
@ -7,22 +7,23 @@ from jarvis.config import get_config
|
|||
def user_is_bot_admin() -> bool:
|
||||
"""Check if a user is a J.A.R.V.I.S. admin."""
|
||||
|
||||
def predicate(ctx: commands.Context) -> bool:
|
||||
async def predicate(ctx: InteractionContext) -> bool:
|
||||
"""Command check predicate."""
|
||||
if getattr(get_config(), "admins", None):
|
||||
return ctx.author.id in get_config().admins
|
||||
else:
|
||||
return False
|
||||
|
||||
return commands.check(predicate)
|
||||
return predicate
|
||||
|
||||
|
||||
def admin_or_permissions(**perms: dict) -> bool:
|
||||
def admin_or_permissions(*perms: list) -> bool:
|
||||
"""Check if a user is an admin or has other perms."""
|
||||
original = commands.has_permissions(**perms).predicate
|
||||
|
||||
async def extended_check(ctx: commands.Context) -> bool:
|
||||
async def predicate(ctx: InteractionContext) -> bool:
|
||||
"""Extended check predicate.""" # noqa: D401
|
||||
return await commands.has_permissions(administrator=True).predicate(ctx) or await original(ctx)
|
||||
is_admin = ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
||||
has_other = any(ctx.author.has_permission(perm) for perm in perms)
|
||||
return is_admin or has_other
|
||||
|
||||
return commands.check(extended_check)
|
||||
return predicate
|
||||
|
|
BIN
jarvis_small.png
BIN
jarvis_small.png
Binary file not shown.
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 37 KiB |
1568
poetry.lock
generated
Normal file
1568
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
27
pyproject.toml
Normal file
27
pyproject.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[tool.poetry]
|
||||
name = "jarvis"
|
||||
version = "2.0.0a0"
|
||||
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"
|
||||
GitPython = "^3.1.26"
|
||||
mongoengine = "^0.23.1"
|
||||
opencv-python = "^4.5.5"
|
||||
Pillow = "^9.0.0"
|
||||
psutil = "^5.9.0"
|
||||
python-gitlab = "^3.1.1"
|
||||
ulid-py = "^1.1.0"
|
||||
tweepy = "^4.5.0"
|
||||
orjson = "^3.6.6"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
python-lsp-server = {extras = ["all"], version = "^1.3.3"}
|
||||
black = "^22.1.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
118
run.py
118
run.py
|
@ -1,117 +1,5 @@
|
|||
#!/bin/python3
|
||||
# flake8: noqa
|
||||
from importlib import reload as ireload
|
||||
from multiprocessing import Process, Value, freeze_support
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
|
||||
import git
|
||||
|
||||
import jarvis
|
||||
from jarvis.config import get_config
|
||||
|
||||
|
||||
def run():
|
||||
ctx = None
|
||||
while True:
|
||||
ireload(jarvis)
|
||||
ctx = jarvis.run(ctx)
|
||||
|
||||
|
||||
def restart():
|
||||
global jarvis_process
|
||||
Path(get_pid_file()).unlink()
|
||||
jarvis_process.kill()
|
||||
jarvis_process = Process(target=run, name="jarvis")
|
||||
jarvis_process.start()
|
||||
|
||||
|
||||
def update():
|
||||
repo = git.Repo(".")
|
||||
dirty = repo.is_dirty()
|
||||
if dirty:
|
||||
print(" Local system has uncommitted changes.")
|
||||
current_hash = repo.head.object.hexsha
|
||||
origin = repo.remotes.origin
|
||||
origin.fetch()
|
||||
if current_hash != origin.refs["main"].object.hexsha:
|
||||
if dirty:
|
||||
return 2
|
||||
origin.pull()
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def get_pid_file():
|
||||
return f"jarvis.{get_pid()}.pid"
|
||||
|
||||
|
||||
def get_pid():
|
||||
global jarvis_process
|
||||
return jarvis_process.pid
|
||||
|
||||
|
||||
def cli():
|
||||
pfile = Path(get_pid_file())
|
||||
while not pfile.exists():
|
||||
sleep(0.2)
|
||||
print(
|
||||
"""
|
||||
All systems online.
|
||||
|
||||
Command List:
|
||||
(R)eload
|
||||
(U)pdate
|
||||
(Q)uit
|
||||
"""
|
||||
)
|
||||
while True:
|
||||
cmd = input("> ")
|
||||
if cmd.lower() in ["q", "quit", "e", "exit"]:
|
||||
print(" Shutting down core systems...")
|
||||
pfile.unlink()
|
||||
break
|
||||
if cmd.lower() in ["u", "update"]:
|
||||
print(" Updating core systems...")
|
||||
status = update()
|
||||
if status == 0:
|
||||
restart()
|
||||
pfile = Path(get_pid_file())
|
||||
while not pfile.exists():
|
||||
sleep(0.2)
|
||||
print(" Core systems successfully updated.")
|
||||
elif status == 1:
|
||||
print(" No core updates available.")
|
||||
elif status == 2:
|
||||
print(" Core system update available, but core is dirty.")
|
||||
if cmd.lower() in ["r", "reload"]:
|
||||
print(" Reloading core systems...")
|
||||
restart()
|
||||
pfile = Path(get_pid_file())
|
||||
while not pfile.exists():
|
||||
sleep(0.2)
|
||||
print(" All systems reloaded.")
|
||||
|
||||
"""Main run file for J.A.R.V.I.S."""
|
||||
from jarvis import run
|
||||
|
||||
if __name__ == "__main__":
|
||||
freeze_support()
|
||||
config = get_config()
|
||||
pid_file = Value("i", 0)
|
||||
jarvis_process = Process(target=run, name="jarvis")
|
||||
logo = jarvis.logo.get_logo(config.logo)
|
||||
print(logo)
|
||||
print("Initializing....")
|
||||
print(" Updating core systems...")
|
||||
status = update()
|
||||
if status == 0:
|
||||
print(" Core systems successfully updated")
|
||||
elif status == 1:
|
||||
print(" No core updates available.")
|
||||
elif status == 2:
|
||||
print(" Core updates available, but not applied.")
|
||||
print(" Starting core systems...")
|
||||
jarvis_process.start()
|
||||
cli()
|
||||
if jarvis_process.is_alive():
|
||||
jarvis_process.kill()
|
||||
print("All systems shut down.")
|
||||
run()
|
||||
|
|
Loading…
Add table
Reference in a new issue