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:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.0.1
|
rev: v4.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
|
args: [--unsafe]
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
- id: debug-statements
|
||||||
|
language_version: python3.10
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
args: [--markdown-linebreak-ext=md]
|
args: [--markdown-linebreak-ext=md]
|
||||||
|
|
||||||
|
@ -16,23 +19,31 @@ repos:
|
||||||
- id: python-check-blanket-noqa
|
- id: python-check-blanket-noqa
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.7b0
|
rev: 22.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- 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
|
- repo: https://github.com/pre-commit/mirrors-isort
|
||||||
rev: V5.9.3
|
rev: V5.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
args: ["--profile", "black"]
|
args: ["--profile", "black"]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/flake8
|
- repo: https://github.com/pycqa/flake8
|
||||||
rev: 3.9.2
|
rev: 4.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- flake8-annotations~=2.0
|
- flake8-annotations~=2.0
|
||||||
- flake8-bandit~=2.1
|
- flake8-bandit~=2.1
|
||||||
- flake8-docstrings~=1.5
|
- 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://git.zevaryx.com/stark-industries/j.a.r.v.i.s.)
|
||||||
[](https://discord.gg/VtgZntXcnZ)
|
[](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.
|
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.
|
**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.
|
Join the [Stark R&D Department Discord server](https://discord.gg/VtgZntXcnZ) to be kept up-to-date on code updates and issues.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- MongoDB 4.4 or higher
|
- MongoDB 5.0 or higher
|
||||||
- Python 3.8 or higher
|
- Python 3.10 or higher
|
||||||
- [tokei](https://github.com/XAMPPRocky/tokei) 12.1 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:
|
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`
|
- `psutil>=5.8, <6`
|
||||||
- `GitPython>=3.1, <4`
|
- `GitPython>=3.1, <4`
|
||||||
- `PyYaml>=5.4, <6`
|
- `PyYaml>=5.4, <6`
|
||||||
- `discord-py-slash-command>=2.3.2, <3`
|
|
||||||
- `pymongo>=3.12.0, <4`
|
- `pymongo>=3.12.0, <4`
|
||||||
- `opencv-python>=4.5, <5`
|
- `opencv-python>=4.5, <5`
|
||||||
- `ButtonPaginator>=0.0.3`
|
|
||||||
- `Pillow>=8.2.0, <9`
|
- `Pillow>=8.2.0, <9`
|
||||||
- `python-gitlab>=2.9.0, <3`
|
- `python-gitlab>=2.9.0, <3`
|
||||||
- `ulid-py>=1.1.0, <2`
|
- `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>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
.a {
|
.a {
|
||||||
fill: #3498DB;
|
fill: #3498db;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</defs>
|
</defs>
|
||||||
<title>logotests</title>
|
<title>jarvis</title>
|
||||||
<g>
|
<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="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="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="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="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="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="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="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>
|
</g>
|
||||||
</svg>
|
</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."""
|
"""Main J.A.R.V.I.S. package."""
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from discord import Intents
|
from dis_snek import Intents, Snake, listen
|
||||||
from discord.ext import commands
|
|
||||||
from discord.utils import find
|
|
||||||
from discord_slash import SlashCommand
|
|
||||||
from mongoengine import connect
|
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 import tasks, utils
|
||||||
from jarvis.config import get_config
|
from jarvis.config import get_config
|
||||||
from jarvis.events import guild, member, message
|
from jarvis.events import member, message
|
||||||
|
|
||||||
jconfig = get_config()
|
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"))
|
file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s"))
|
||||||
logger.addHandler(file_handler)
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
if asyncio.get_event_loop().is_closed():
|
intents = Intents.DEFAULT
|
||||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
|
||||||
|
|
||||||
intents = Intents.default()
|
|
||||||
intents.members = True
|
intents.members = True
|
||||||
restart_ctx = None
|
restart_ctx = None
|
||||||
|
|
||||||
|
|
||||||
jarvis = commands.Bot(
|
jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync)
|
||||||
command_prefix=utils.get_prefix,
|
|
||||||
intents=intents,
|
|
||||||
help_command=None,
|
|
||||||
max_messages=jconfig.max_messages,
|
|
||||||
)
|
|
||||||
|
|
||||||
slash = SlashCommand(jarvis, sync_commands=False, sync_on_cog_reload=True)
|
__version__ = "2.0.0a0"
|
||||||
jarvis_self = Process()
|
|
||||||
__version__ = "1.11.4"
|
|
||||||
|
|
||||||
|
|
||||||
@jarvis.event
|
@listen()
|
||||||
async def on_ready() -> None:
|
async def on_ready() -> None:
|
||||||
"""d.py on_ready override."""
|
"""Lepton on_ready override."""
|
||||||
global restart_ctx
|
global restart_ctx
|
||||||
print(" Logged in as {0.user}".format(jarvis))
|
print(" Logged in as {0.user}".format(jarvis)) # noqa: T001
|
||||||
print(" Connected to {} guild(s)".format(len(jarvis.guilds)))
|
print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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."""
|
"""Run J.A.R.V.I.S."""
|
||||||
global restart_ctx
|
|
||||||
if ctx:
|
|
||||||
restart_ctx = ctx
|
|
||||||
connect(
|
connect(
|
||||||
db="ctc2",
|
db="ctc2",
|
||||||
alias="ctc2",
|
alias="ctc2",
|
||||||
|
@ -84,27 +56,21 @@ def run(ctx: dict = None) -> Optional[dict]:
|
||||||
**jconfig.mongo["connect"],
|
**jconfig.mongo["connect"],
|
||||||
)
|
)
|
||||||
jconfig.get_db_config()
|
jconfig.get_db_config()
|
||||||
|
|
||||||
for extension in utils.get_extensions():
|
for extension in utils.get_extensions():
|
||||||
jarvis.load_extension(extension)
|
jarvis.load_extension(extension)
|
||||||
print(
|
|
||||||
|
print( # noqa: T001
|
||||||
" https://discord.com/api/oauth2/authorize?client_id="
|
" 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
|
jarvis.max_messages = jconfig.max_messages
|
||||||
tasks.init()
|
|
||||||
|
|
||||||
# Add event listeners
|
# Add event listeners
|
||||||
if jconfig.events:
|
if jconfig.events:
|
||||||
_ = [
|
_ = [
|
||||||
guild.GuildEventHandler(jarvis),
|
|
||||||
member.MemberEventHandler(jarvis),
|
member.MemberEventHandler(jarvis),
|
||||||
message.MessageEventHandler(jarvis),
|
message.MessageEventHandler(jarvis),
|
||||||
]
|
]
|
||||||
jarvis.run(jconfig.token, bot=True, reconnect=True)
|
jarvis.start(jconfig.token)
|
||||||
for cog in jarvis.cogs:
|
|
||||||
session = getattr(cog, "_session", None)
|
|
||||||
if session:
|
|
||||||
session.close()
|
|
||||||
if restart_ctx:
|
|
||||||
return restart_ctx
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
"""J.A.R.V.I.S. Admin Cogs."""
|
"""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."""
|
"""Add admin cogs to J.A.R.V.I.S."""
|
||||||
bot.add_cog(ban.BanCog(bot))
|
ban.BanCog(bot)
|
||||||
bot.add_cog(kick.KickCog(bot))
|
kick.KickCog(bot)
|
||||||
bot.add_cog(lock.LockCog(bot))
|
# lock.LockCog(bot)
|
||||||
bot.add_cog(lockdown.LockdownCog(bot))
|
# lockdown.LockdownCog(bot)
|
||||||
bot.add_cog(mute.MuteCog(bot))
|
mute.MuteCog(bot)
|
||||||
bot.add_cog(purge.PurgeCog(bot))
|
purge.PurgeCog(bot)
|
||||||
bot.add_cog(roleping.RolepingCog(bot))
|
roleping.RolepingCog(bot)
|
||||||
bot.add_cog(warning.WarningCog(bot))
|
warning.WarningCog(bot)
|
||||||
|
|
|
@ -2,30 +2,33 @@
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from ButtonPaginator import Paginator
|
from dis_snek import InteractionContext, Permissions, Snake
|
||||||
from discord import User
|
from dis_snek.ext.paginators import Paginator
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord.utils import find
|
from dis_snek.models.discord.user import User
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash.model import ButtonStyle
|
OptionTypes,
|
||||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
SlashCommandChoice,
|
||||||
|
slash_command,
|
||||||
|
slash_option,
|
||||||
|
)
|
||||||
|
from dis_snek.models.snek.command import check
|
||||||
|
|
||||||
from jarvis.db.models import Ban, Unban
|
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.cachecog import CacheCog
|
||||||
from jarvis.utils.field import Field
|
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
|
|
||||||
class BanCog(CacheCog):
|
class BanCog(CacheCog):
|
||||||
"""J.A.R.V.I.S. BanCog."""
|
"""J.A.R.V.I.S. BanCog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
|
|
||||||
async def discord_apply_ban(
|
async def discord_apply_ban(
|
||||||
self,
|
self,
|
||||||
ctx: SlashContext,
|
ctx: InteractionContext,
|
||||||
reason: str,
|
reason: str,
|
||||||
user: User,
|
user: User,
|
||||||
duration: int,
|
duration: int,
|
||||||
|
@ -37,7 +40,7 @@ class BanCog(CacheCog):
|
||||||
await ctx.guild.ban(user, reason=reason)
|
await ctx.guild.ban(user, reason=reason)
|
||||||
_ = Ban(
|
_ = Ban(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
username=user.name,
|
username=user.username,
|
||||||
discrim=user.discriminator,
|
discrim=user.discriminator,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
|
@ -54,20 +57,20 @@ class BanCog(CacheCog):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=user.nick if user.nick else user.name,
|
name=user.display_name,
|
||||||
icon_url=user.avatar_url,
|
icon_url=user.avatar,
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=user.avatar_url)
|
embed.set_thumbnail(url=user.avatar)
|
||||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
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."""
|
"""Apply a Discord unban."""
|
||||||
await ctx.guild.unban(user, reason=reason)
|
await ctx.guild.unban(user, reason=reason)
|
||||||
_ = Unban(
|
_ = Unban(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
username=user.name,
|
username=user.username,
|
||||||
discrim=user.discriminator,
|
discrim=user.discriminator,
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
|
@ -77,77 +80,56 @@ class BanCog(CacheCog):
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="User Unbanned",
|
title="User Unbanned",
|
||||||
description=f"<@{user.id}> was unbanned",
|
description=f"<@{user.id}> was unbanned",
|
||||||
fields=[Field(name="Reason", value=reason)],
|
fields=[EmbedField(name="Reason", value=reason)],
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=user.name,
|
name=user.username,
|
||||||
icon_url=user.avatar_url,
|
icon_url=user.avatar,
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=user.avatar_url)
|
embed.set_thumbnail(url=user.avatar)
|
||||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="ban", description="Ban a user")
|
||||||
name="ban",
|
@slash_option(name="user", description="User to ban", opt_type=OptionTypes.USER, required=True)
|
||||||
description="Ban a user",
|
@slash_option(
|
||||||
options=[
|
name="reason", description="Ban reason", opt_type=OptionTypes.STRING, required=True
|
||||||
create_option(
|
)
|
||||||
name="user",
|
@slash_option(
|
||||||
description="User to ban",
|
name="btype",
|
||||||
option_type=6,
|
description="Ban type",
|
||||||
required=True,
|
opt_type=OptionTypes.STRING,
|
||||||
),
|
required=True,
|
||||||
create_option(
|
choices=[
|
||||||
name="reason",
|
SlashCommandChoice(name="Permanent", value="perm"),
|
||||||
description="Ban reason",
|
SlashCommandChoice(name="Temporary", value="temp"),
|
||||||
required=True,
|
SlashCommandChoice(name="Soft", value="soft"),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@admin_or_permissions(ban_members=True)
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _ban(
|
async def _ban(
|
||||||
self,
|
self,
|
||||||
ctx: SlashContext,
|
ctx: InteractionContext,
|
||||||
|
reason: str,
|
||||||
user: User = None,
|
user: User = None,
|
||||||
reason: str = None,
|
|
||||||
btype: str = "perm",
|
btype: str = "perm",
|
||||||
duration: int = 4,
|
duration: int = 4,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not user or user == ctx.author:
|
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
|
return
|
||||||
if user == self.bot.user:
|
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
|
return
|
||||||
if btype == "temp" and duration < 0:
|
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
|
return
|
||||||
elif btype == "temp" and duration > 744:
|
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
|
return
|
||||||
if len(reason) > 100:
|
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
|
return
|
||||||
if not reason:
|
|
||||||
reason = "Mr. Stark is displeased with your presence. Please leave."
|
|
||||||
|
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
|
||||||
|
@ -160,10 +142,10 @@ class BanCog(CacheCog):
|
||||||
if mtype == "temp":
|
if mtype == "temp":
|
||||||
user_message += f"\nDuration: {duration} hours"
|
user_message += f"\nDuration: {duration} hours"
|
||||||
|
|
||||||
fields = [Field(name="Type", value=mtype)]
|
fields = [EmbedField(name="Type", value=mtype)]
|
||||||
|
|
||||||
if mtype == "temp":
|
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(
|
user_embed = build_embed(
|
||||||
title=f"You have been banned from {ctx.guild.name}",
|
title=f"You have been banned from {ctx.guild.name}",
|
||||||
|
@ -172,10 +154,10 @@ class BanCog(CacheCog):
|
||||||
)
|
)
|
||||||
|
|
||||||
user_embed.set_author(
|
user_embed.set_author(
|
||||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||||
icon_url=ctx.author.avatar_url,
|
icon_url=ctx.author.avatar,
|
||||||
)
|
)
|
||||||
user_embed.set_thumbnail(url=ctx.guild.icon_url)
|
user_embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await user.send(embed=user_embed)
|
await user.send(embed=user_embed)
|
||||||
|
@ -184,13 +166,13 @@ class BanCog(CacheCog):
|
||||||
try:
|
try:
|
||||||
await ctx.guild.ban(user, reason=reason)
|
await ctx.guild.ban(user, reason=reason)
|
||||||
except Exception as e:
|
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
|
return
|
||||||
send_failed = False
|
send_failed = False
|
||||||
if mtype == "soft":
|
if mtype == "soft":
|
||||||
await ctx.guild.unban(user, reason="Ban was softban")
|
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":
|
if btype != "temp":
|
||||||
duration = None
|
duration = None
|
||||||
active = True
|
active = True
|
||||||
|
@ -199,33 +181,22 @@ class BanCog(CacheCog):
|
||||||
|
|
||||||
await self.discord_apply_ban(ctx, reason, user, duration, active, fields, mtype)
|
await self.discord_apply_ban(ctx, reason, user, duration, active, fields, mtype)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="unban", description="Unban a user")
|
||||||
name="unban",
|
@slash_option(
|
||||||
description="Unban a user",
|
name="user", description="User to unban", opt_type=OptionTypes.STRING, required=True
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@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(
|
async def _unban(
|
||||||
self,
|
self,
|
||||||
ctx: SlashContext,
|
ctx: InteractionContext,
|
||||||
user: str,
|
user: str,
|
||||||
reason: str,
|
reason: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
if len(reason) > 100:
|
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
|
return
|
||||||
|
|
||||||
orig_user = user
|
orig_user = user
|
||||||
|
@ -236,26 +207,31 @@ class BanCog(CacheCog):
|
||||||
bans = await ctx.guild.bans()
|
bans = await ctx.guild.bans()
|
||||||
|
|
||||||
# Try to get ban information out of Discord
|
# 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)
|
user = int(user)
|
||||||
discord_ban_info = find(lambda x: x.user.id == user, bans)
|
discord_ban_info = find(lambda x: x.user.id == user, bans)
|
||||||
else: # User name
|
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("#")
|
user, discrim = user.split("#")
|
||||||
if discrim:
|
if discrim:
|
||||||
discord_ban_info = find(
|
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,
|
bans,
|
||||||
)
|
)
|
||||||
else:
|
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 results:
|
||||||
if len(results) > 1:
|
if len(results) > 1:
|
||||||
active_bans = []
|
active_bans = []
|
||||||
for ban in 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)
|
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)
|
await ctx.send(message)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -278,7 +254,7 @@ class BanCog(CacheCog):
|
||||||
database_ban_info = Ban.objects(**search).first()
|
database_ban_info = Ban.objects(**search).first()
|
||||||
|
|
||||||
if not discord_ban_info and not database_ban_info:
|
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:
|
elif discord_ban_info:
|
||||||
await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
|
await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
|
||||||
|
@ -297,46 +273,41 @@ class BanCog(CacheCog):
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
).save()
|
).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(
|
@slash_command(
|
||||||
base="bans",
|
name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans"
|
||||||
name="list",
|
)
|
||||||
description="List bans",
|
@slash_option(
|
||||||
options=[
|
name="btype",
|
||||||
create_option(
|
description="Ban type",
|
||||||
name="type",
|
opt_type=OptionTypes.INTEGER,
|
||||||
description="Ban type",
|
required=False,
|
||||||
option_type=4,
|
choices=[
|
||||||
required=False,
|
SlashCommandChoice(name="All", value=0),
|
||||||
choices=[
|
SlashCommandChoice(name="Permanent", value=1),
|
||||||
create_choice(value=0, name="All"),
|
SlashCommandChoice(name="Temporary", value=2),
|
||||||
create_choice(value=1, name="Permanent"),
|
SlashCommandChoice(name="Soft", value=3),
|
||||||
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"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@admin_or_permissions(ban_members=True)
|
@slash_option(
|
||||||
async def _bans_list(self, ctx: SlashContext, type: int = 0, active: int = 1) -> None:
|
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)
|
active = bool(active)
|
||||||
exists = self.check_cache(ctx, type=type, active=active)
|
exists = self.check_cache(ctx, type=type, active=active)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
types = [0, "perm", "temp", "soft"]
|
types = [0, "perm", "temp", "soft"]
|
||||||
|
@ -351,9 +322,9 @@ class BanCog(CacheCog):
|
||||||
for ban in bans:
|
for ban in bans:
|
||||||
if not ban.username:
|
if not ban.username:
|
||||||
user = await self.bot.fetch_user(ban.user)
|
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(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name=f"Username: {ban.username}#{ban.discrim}",
|
name=f"Username: {ban.username}#{ban.discrim}",
|
||||||
value=(
|
value=(
|
||||||
f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
|
f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
|
||||||
|
@ -370,8 +341,8 @@ class BanCog(CacheCog):
|
||||||
for ban in bans:
|
for ban in bans:
|
||||||
if ban.user.id not in db_bans:
|
if ban.user.id not in db_bans:
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name=f"Username: {ban.user.name}#" + f"{ban.user.discriminator}",
|
name=f"Username: {ban.user.username}#" + f"{ban.user.discriminator}",
|
||||||
value=(
|
value=(
|
||||||
f"Date: [unknown]\n"
|
f"Date: [unknown]\n"
|
||||||
f"User ID: {ban.user.id}\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",
|
description=f"No {'in' if not active else ''}active bans",
|
||||||
fields=[],
|
fields=[],
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
pages.append(embed)
|
pages.append(embed)
|
||||||
else:
|
else:
|
||||||
for i in range(0, len(bans), 5):
|
for i in range(0, len(bans), 5):
|
||||||
embed = build_embed(title=title, description="", fields=fields[i : i + 5]) # noqa: E203
|
embed = build_embed(title=title, description="", fields=fields[i : i + 5])
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
pages.append(embed)
|
pages.append(embed)
|
||||||
|
|
||||||
paginator = Paginator(
|
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||||
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=["◀", "▶"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache[hash(paginator)] = {
|
self.cache[hash(paginator)] = {
|
||||||
"guild": ctx.guild.id,
|
"guild": ctx.guild.id,
|
||||||
|
@ -426,4 +386,4 @@ class BanCog(CacheCog):
|
||||||
"paginator": paginator,
|
"paginator": paginator,
|
||||||
}
|
}
|
||||||
|
|
||||||
await paginator.start()
|
await paginator.send(ctx)
|
||||||
|
|
|
@ -1,53 +1,38 @@
|
||||||
"""J.A.R.V.I.S. KickCog."""
|
"""J.A.R.V.I.S. KickCog."""
|
||||||
from discord import User
|
from dis_snek import InteractionContext, Permissions, Scale
|
||||||
from discord.ext.commands import Bot
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.user import User
|
||||||
from discord_slash.utils.manage_commands import create_option
|
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.db.models import Kick
|
||||||
from jarvis.utils import build_embed
|
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
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
|
|
||||||
class KickCog(CacheCog):
|
class KickCog(Scale):
|
||||||
"""J.A.R.V.I.S. KickCog."""
|
"""J.A.R.V.I.S. KickCog."""
|
||||||
|
|
||||||
def __init__(self, bot: Bot):
|
@slash_command(name="kick", description="Kick a user")
|
||||||
super().__init__(bot)
|
@slash_option(name="user", description="User to kick", opt_type=OptionTypes.USER, required=True)
|
||||||
|
@slash_option(
|
||||||
@cog_ext.cog_slash(
|
name="reason", description="Kick reason", opt_type=OptionTypes.STRING, required=True
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(kick_members=True)
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _kick(self, ctx: SlashContext, user: User, reason: str = None) -> None:
|
async def _kick(self, ctx: InteractionContext, user: User, reason: str) -> None:
|
||||||
if not user or user == ctx.author:
|
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
|
return
|
||||||
if user == self.bot.user:
|
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
|
return
|
||||||
if len(reason) > 100:
|
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
|
return
|
||||||
if not reason:
|
|
||||||
reason = "Mr. Stark is displeased with your presence. Please leave."
|
|
||||||
guild_name = ctx.guild.name
|
guild_name = ctx.guild.name
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title=f"You have been kicked from {guild_name}",
|
title=f"You have been kicked from {guild_name}",
|
||||||
|
@ -56,10 +41,10 @@ class KickCog(CacheCog):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||||
icon_url=ctx.author.avatar_url,
|
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
|
send_failed = False
|
||||||
try:
|
try:
|
||||||
|
@ -68,19 +53,16 @@ class KickCog(CacheCog):
|
||||||
send_failed = True
|
send_failed = True
|
||||||
await ctx.guild.kick(user, reason=reason)
|
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(
|
embed = build_embed(
|
||||||
title="User Kicked",
|
title="User Kicked",
|
||||||
description=f"Reason: {reason}",
|
description=f"Reason: {reason}",
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||||
name=user.nick if user.nick else user.name,
|
embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
icon_url=user.avatar_url,
|
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||||
)
|
|
||||||
embed.set_thumbnail(url=user.avatar_url)
|
|
||||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
_ = Kick(
|
_ = Kick(
|
||||||
|
|
|
@ -1,134 +1,113 @@
|
||||||
"""J.A.R.V.I.S. LockCog."""
|
"""J.A.R.V.I.S. LockCog."""
|
||||||
from contextlib import suppress
|
from dis_snek import Scale
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from discord import Role, TextChannel, User, VoiceChannel
|
# TODO: Uncomment 99% of code once implementation is figured out
|
||||||
from discord.ext.commands import Bot
|
# from contextlib import suppress
|
||||||
from discord_slash import SlashContext, cog_ext
|
# from typing import Union
|
||||||
from discord_slash.utils.manage_commands import create_option
|
#
|
||||||
|
# from dis_snek import InteractionContext, Scale, Snake
|
||||||
from jarvis.db.models import Lock
|
# from dis_snek.models.discord.enums import Permissions
|
||||||
from jarvis.utils.cachecog import CacheCog
|
# from dis_snek.models.discord.role import Role
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
# 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."""
|
"""J.A.R.V.I.S. LockCog."""
|
||||||
|
|
||||||
def __init__(self, bot: Bot):
|
# @slash_command(name="lock", description="Lock a channel")
|
||||||
super().__init__(bot)
|
# @slash_option(name="reason",
|
||||||
|
# description="Lock Reason",
|
||||||
async def _lock_channel(
|
# opt_type=3,
|
||||||
self,
|
# required=True,)
|
||||||
channel: Union[TextChannel, VoiceChannel],
|
# @slash_option(name="duration",
|
||||||
role: Role,
|
# description="Lock duration in minutes (default 10)",
|
||||||
admin: User,
|
# opt_type=4,
|
||||||
reason: str,
|
# required=False,)
|
||||||
allow_send: bool = False,
|
# @slash_option(name="channel",
|
||||||
) -> None:
|
# description="Channel to lock",
|
||||||
overrides = channel.overwrites_for(role)
|
# opt_type=7,
|
||||||
if isinstance(channel, TextChannel):
|
# required=False,)
|
||||||
overrides.send_messages = allow_send
|
# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS))
|
||||||
elif isinstance(channel, VoiceChannel):
|
# async def _lock(
|
||||||
overrides.speak = allow_send
|
# self,
|
||||||
await channel.set_permissions(role, overwrite=overrides, reason=reason)
|
# ctx: InteractionContext,
|
||||||
|
# reason: str,
|
||||||
async def _unlock_channel(
|
# duration: int = 10,
|
||||||
self,
|
# channel: Union[GuildText, GuildVoice] = None,
|
||||||
channel: Union[TextChannel, VoiceChannel],
|
# ) -> None:
|
||||||
role: Role,
|
# await ctx.defer(ephemeral=True)
|
||||||
admin: User,
|
# if duration <= 0:
|
||||||
) -> None:
|
# await ctx.send("Duration must be > 0", ephemeral=True)
|
||||||
overrides = channel.overwrites_for(role)
|
# return
|
||||||
if isinstance(channel, TextChannel):
|
#
|
||||||
overrides.send_messages = None
|
# elif duration > 60 * 12:
|
||||||
elif isinstance(channel, VoiceChannel):
|
# await ctx.send("Duration must be <= 12 hours", ephemeral=True)
|
||||||
overrides.speak = None
|
# return
|
||||||
await channel.set_permissions(role, overwrite=overrides)
|
#
|
||||||
|
# if len(reason) > 100:
|
||||||
@cog_ext.cog_slash(
|
# await ctx.send("Reason must be <= 100 characters", ephemeral=True)
|
||||||
name="lock",
|
# return
|
||||||
description="Locks a channel",
|
# if not channel:
|
||||||
options=[
|
# channel = ctx.channel
|
||||||
create_option(
|
#
|
||||||
name="reason",
|
# # role = ctx.guild.default_role # Uncomment once implemented
|
||||||
description="Lock Reason",
|
# if isinstance(channel, GuildText):
|
||||||
option_type=3,
|
# to_deny = Permissions.SEND_MESSAGES
|
||||||
required=True,
|
# elif isinstance(channel, GuildVoice):
|
||||||
),
|
# to_deny = Permissions.CONNECT | Permissions.SPEAK
|
||||||
create_option(
|
#
|
||||||
name="duration",
|
# overwrite = PermissionOverwrite(type=PermissionTypes.ROLE, deny=to_deny)
|
||||||
description="Lock duration in minutes (default 10)",
|
# # TODO: Get original permissions
|
||||||
option_type=4,
|
# # TODO: Apply overwrite
|
||||||
required=False,
|
# overwrite = overwrite
|
||||||
),
|
# _ = Lock(
|
||||||
create_option(
|
# channel=channel.id,
|
||||||
name="channel",
|
# guild=ctx.guild.id,
|
||||||
description="Channel to lock",
|
# admin=ctx.author.id,
|
||||||
option_type=7,
|
# reason=reason,
|
||||||
required=False,
|
# 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)
|
||||||
@admin_or_permissions(manage_channels=True)
|
#
|
||||||
async def _lock(
|
# @cog_ext.cog_slash(
|
||||||
self,
|
# name="unlock",
|
||||||
ctx: SlashContext,
|
# description="Unlocks a channel",
|
||||||
reason: str,
|
# choices=[
|
||||||
duration: int = 10,
|
# create_option(
|
||||||
channel: Union[TextChannel, VoiceChannel] = None,
|
# name="channel",
|
||||||
) -> None:
|
# description="Channel to lock",
|
||||||
await ctx.defer(hidden=True)
|
# opt_type=7,
|
||||||
if duration <= 0:
|
# required=False,
|
||||||
await ctx.send("Duration must be > 0", hidden=True)
|
# ),
|
||||||
return
|
# ],
|
||||||
elif duration >= 300:
|
# )
|
||||||
await ctx.send("Duration must be < 5 hours", hidden=True)
|
# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS))
|
||||||
return
|
# async def _unlock(
|
||||||
if len(reason) > 100:
|
# self,
|
||||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
# ctx: InteractionContext,
|
||||||
return
|
# channel: Union[GuildText, GuildVoice] = None,
|
||||||
if not channel:
|
# ) -> None:
|
||||||
channel = ctx.channel
|
# if not channel:
|
||||||
for role in ctx.guild.roles:
|
# channel = ctx.channel
|
||||||
with suppress(Exception):
|
# lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first()
|
||||||
await self._lock_channel(channel, role, ctx.author, reason)
|
# if not lock:
|
||||||
_ = Lock(
|
# await ctx.send(f"{channel.mention} not locked.", ephemeral=True)
|
||||||
channel=channel.id,
|
# return
|
||||||
guild=ctx.guild.id,
|
# for role in ctx.guild.roles:
|
||||||
admin=ctx.author.id,
|
# with suppress(Exception):
|
||||||
reason=reason,
|
# await self._unlock_channel(channel, role, ctx.author)
|
||||||
duration=duration,
|
# lock.active = False
|
||||||
).save()
|
# lock.save()
|
||||||
await ctx.send(f"{channel.mention} locked for {duration} minute(s)")
|
# await ctx.send(f"{channel.mention} unlocked")
|
||||||
|
|
||||||
@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")
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ from discord_slash.utils.manage_commands import create_option
|
||||||
|
|
||||||
from jarvis.db.models import Lock
|
from jarvis.db.models import Lock
|
||||||
from jarvis.utils.cachecog import CacheCog
|
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):
|
class LockdownCog(CacheCog):
|
||||||
|
@ -21,34 +22,34 @@ class LockdownCog(CacheCog):
|
||||||
base="lockdown",
|
base="lockdown",
|
||||||
name="start",
|
name="start",
|
||||||
description="Locks a server",
|
description="Locks a server",
|
||||||
options=[
|
choices=[
|
||||||
create_option(
|
create_option(
|
||||||
name="reason",
|
name="reason",
|
||||||
description="Lockdown Reason",
|
description="Lockdown Reason",
|
||||||
option_type=3,
|
opt_type=3,
|
||||||
required=True,
|
required=True,
|
||||||
),
|
),
|
||||||
create_option(
|
create_option(
|
||||||
name="duration",
|
name="duration",
|
||||||
description="Lockdown duration in minutes (default 10)",
|
description="Lockdown duration in minutes (default 10)",
|
||||||
option_type=4,
|
opt_type=4,
|
||||||
required=False,
|
required=False,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_channels=True)
|
# @check(admin_or_permissions(manage_channels=True))
|
||||||
async def _lockdown_start(
|
async def _lockdown_start(
|
||||||
self,
|
self,
|
||||||
ctx: SlashContext,
|
ctx: SlashContext,
|
||||||
reason: str,
|
reason: str,
|
||||||
duration: int = 10,
|
duration: int = 10,
|
||||||
) -> None:
|
) -> None:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
if duration <= 0:
|
if duration <= 0:
|
||||||
await ctx.send("Duration must be > 0", hidden=True)
|
await ctx.send("Duration must be > 0", ephemeral=True)
|
||||||
return
|
return
|
||||||
elif duration >= 300:
|
elif duration >= 300:
|
||||||
await ctx.send("Duration must be < 5 hours", hidden=True)
|
await ctx.send("Duration must be < 5 hours", ephemeral=True)
|
||||||
return
|
return
|
||||||
channels = ctx.guild.channels
|
channels = ctx.guild.channels
|
||||||
roles = ctx.guild.roles
|
roles = ctx.guild.roles
|
||||||
|
@ -87,7 +88,7 @@ class LockdownCog(CacheCog):
|
||||||
update = False
|
update = False
|
||||||
locks = Lock.objects(guild=ctx.guild.id, active=True)
|
locks = Lock.objects(guild=ctx.guild.id, active=True)
|
||||||
if not locks:
|
if not locks:
|
||||||
await ctx.send("No lockdown detected.", hidden=True)
|
await ctx.send("No lockdown detected.", ephemeral=True)
|
||||||
return
|
return
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
|
|
|
@ -1,132 +1,125 @@
|
||||||
"""J.A.R.V.I.S. MuteCog."""
|
"""J.A.R.V.I.S. MuteCog."""
|
||||||
from discord import Member
|
from datetime import datetime
|
||||||
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 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 import build_embed
|
||||||
from jarvis.utils.field import Field
|
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
|
|
||||||
class MuteCog(commands.Cog):
|
class MuteCog(Scale):
|
||||||
"""J.A.R.V.I.S. MuteCog."""
|
"""J.A.R.V.I.S. MuteCog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="mute", description="Mute a user")
|
||||||
name="mute",
|
@slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True)
|
||||||
description="Mute a user",
|
@slash_option(
|
||||||
options=[
|
name="reason",
|
||||||
create_option(
|
description="Reason for mute",
|
||||||
name="user",
|
opt_type=OptionTypes.STRING,
|
||||||
description="User to mute",
|
required=True,
|
||||||
option_type=6,
|
)
|
||||||
required=True,
|
@slash_option(
|
||||||
),
|
name="time",
|
||||||
create_option(
|
description="Duration of mute, default 1",
|
||||||
name="reason",
|
opt_type=OptionTypes.INTEGER,
|
||||||
description="Reason for mute",
|
required=False,
|
||||||
option_type=3,
|
)
|
||||||
required=True,
|
@slash_option(
|
||||||
),
|
name="scale",
|
||||||
create_option(
|
description="Time scale, default Hour(s)",
|
||||||
name="duration",
|
opt_type=OptionTypes.INTEGER,
|
||||||
description="Duration of mute in minutes, default 30",
|
required=False,
|
||||||
option_type=4,
|
choices=[
|
||||||
required=False,
|
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)
|
@check(
|
||||||
async def _mute(self, ctx: SlashContext, user: Member, reason: str, duration: int = 30) -> None:
|
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:
|
if user == ctx.author:
|
||||||
await ctx.send("You cannot mute yourself.", hidden=True)
|
await ctx.send("You cannot mute yourself.", ephemeral=True)
|
||||||
return
|
return
|
||||||
if user == self.bot.user:
|
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
|
return
|
||||||
if len(reason) > 100:
|
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
|
return
|
||||||
mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first()
|
|
||||||
if not mute_setting:
|
# Max 4 weeks (2419200 seconds) per API
|
||||||
await ctx.send(
|
duration = time * scale
|
||||||
"Please configure a mute role with /settings mute <role> first",
|
if duration > 2419200:
|
||||||
hidden=True,
|
await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True)
|
||||||
)
|
|
||||||
return
|
return
|
||||||
role = get(ctx.guild.roles, id=mute_setting.value)
|
|
||||||
if role in user.roles:
|
await user.timeout(communication_disabled_until=duration, reason=reason)
|
||||||
await ctx.send("User already muted", hidden=True)
|
|
||||||
return
|
|
||||||
await user.add_roles(role, reason=reason)
|
|
||||||
if duration < 0 or duration > 300:
|
|
||||||
duration = -1
|
|
||||||
_ = Mute(
|
_ = Mute(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
duration=duration,
|
duration=duration,
|
||||||
active=True if duration >= 0 else False,
|
active=True,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="User Muted",
|
title="User Muted",
|
||||||
description=f"{user.mention} has been muted",
|
description=f"{user.mention} has been muted",
|
||||||
fields=[Field(name="Reason", value=reason)],
|
fields=[EmbedField(name="Reason", value=reason)],
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||||
name=user.nick if user.nick else user.name,
|
embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
icon_url=user.avatar_url,
|
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||||
)
|
|
||||||
embed.set_thumbnail(url=user.avatar_url)
|
|
||||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="unmute", description="Unmute a user")
|
||||||
name="unmute",
|
@slash_option(
|
||||||
description="Unmute a user",
|
name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="user",
|
|
||||||
description="User to unmute",
|
|
||||||
option_type=6,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(mute_members=True)
|
@check(
|
||||||
async def _unmute(self, ctx: SlashContext, user: Member) -> None:
|
admin_or_permissions(
|
||||||
mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first()
|
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||||
if not mute_setting:
|
)
|
||||||
await ctx.send(
|
)
|
||||||
"Please configure a mute role with /settings mute <role> first.",
|
async def _unmute(self, ctx: InteractionContext, user: Member) -> None:
|
||||||
hidden=True,
|
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
|
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(
|
embed = build_embed(
|
||||||
title="User Unmuted",
|
title="User Unmuted",
|
||||||
description=f"{user.mention} has been unmuted",
|
description=f"{user.mention} has been unmuted",
|
||||||
fields=[],
|
fields=[],
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||||
name=user.nick if user.nick else user.name,
|
embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
icon_url=user.avatar_url,
|
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||||
)
|
await ctx.send(embed=embed)
|
||||||
embed.set_thumbnail(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}")
|
embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
|
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
"""J.A.R.V.I.S. PurgeCog."""
|
"""J.A.R.V.I.S. PurgeCog."""
|
||||||
from discord import TextChannel
|
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.channel import GuildText
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash.utils.manage_commands import create_option
|
OptionTypes,
|
||||||
|
slash_command,
|
||||||
|
slash_option,
|
||||||
|
)
|
||||||
|
from dis_snek.models.snek.command import check
|
||||||
|
|
||||||
from jarvis.db.models import Autopurge, Purge
|
from jarvis.db.models import Autopurge, Purge
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
|
|
||||||
class PurgeCog(commands.Cog):
|
class PurgeCog(Scale):
|
||||||
"""J.A.R.V.I.S. PurgeCog."""
|
"""J.A.R.V.I.S. PurgeCog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="purge", description="Purge messages from channel")
|
||||||
name="purge",
|
@slash_option(
|
||||||
description="Purge messages from channel",
|
name="amount",
|
||||||
options=[
|
description="Amount of messages to purge, default 10",
|
||||||
create_option(
|
opt_type=OptionTypes.INTEGER,
|
||||||
name="amount",
|
required=False,
|
||||||
description="Amount of messages to purge",
|
|
||||||
required=False,
|
|
||||||
option_type=4,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_messages=True)
|
@check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
|
||||||
async def _purge(self, ctx: SlashContext, amount: int = 10) -> None:
|
async def _purge(self, ctx: InteractionContext, amount: int = 10) -> None:
|
||||||
if amount < 1:
|
if amount < 1:
|
||||||
await ctx.send("Amount must be >= 1", hidden=True)
|
await ctx.send("Amount must be >= 1", ephemeral=True)
|
||||||
return
|
return
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
|
@ -44,39 +43,37 @@ class PurgeCog(commands.Cog):
|
||||||
count=amount,
|
count=amount,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="autopurge",
|
name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages"
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_messages=True)
|
@slash_option(
|
||||||
async def _autopurge_add(self, ctx: SlashContext, channel: TextChannel, delay: int = 30) -> None:
|
name="channel",
|
||||||
if not isinstance(channel, TextChannel):
|
description="Channel to autopurge",
|
||||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
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
|
return
|
||||||
if delay <= 0:
|
if delay <= 0:
|
||||||
await ctx.send("Delay must be > 0", hidden=True)
|
await ctx.send("Delay must be > 0", ephemeral=True)
|
||||||
return
|
return
|
||||||
elif delay > 300:
|
elif delay > 300:
|
||||||
await ctx.send("Delay must be < 5 minutes", hidden=True)
|
await ctx.send("Delay must be < 5 minutes", ephemeral=True)
|
||||||
return
|
return
|
||||||
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first()
|
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||||
if autopurge:
|
if autopurge:
|
||||||
await ctx.send("Autopurge already exists.", hidden=True)
|
await ctx.send("Autopurge already exists.", ephemeral=True)
|
||||||
return
|
return
|
||||||
_ = Autopurge(
|
_ = Autopurge(
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
|
@ -86,52 +83,48 @@ class PurgeCog(commands.Cog):
|
||||||
).save()
|
).save()
|
||||||
await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds")
|
await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="autopurge",
|
name="autopurge", sub_cmd_name="remove", sub_cmd_description="Remove an autopurge"
|
||||||
name="remove",
|
|
||||||
description="Remove an autopurge",
|
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="channel",
|
|
||||||
description="Channel to remove from autopurge",
|
|
||||||
option_type=7,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_messages=True)
|
@slash_option(
|
||||||
async def _autopurge_remove(self, ctx: SlashContext, channel: TextChannel) -> None:
|
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)
|
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
|
||||||
if not autopurge:
|
if not autopurge:
|
||||||
await ctx.send("Autopurge does not exist.", hidden=True)
|
await ctx.send("Autopurge does not exist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
autopurge.delete()
|
autopurge.delete()
|
||||||
await ctx.send(f"Autopurge removed from {channel.mention}.")
|
await ctx.send(f"Autopurge removed from {channel.mention}.")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="autopurge",
|
name="autopurge",
|
||||||
name="update",
|
sub_cmd_name="update",
|
||||||
description="Update autopurge on a channel",
|
sub_cmd_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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_messages=True)
|
@slash_option(
|
||||||
async def _autopurge_update(self, ctx: SlashContext, channel: TextChannel, delay: int) -> None:
|
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)
|
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
|
||||||
if not autopurge:
|
if not autopurge:
|
||||||
await ctx.send("Autopurge does not exist.", hidden=True)
|
await ctx.send("Autopurge does not exist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
autopurge.delay = delay
|
autopurge.delay = delay
|
||||||
autopurge.save()
|
autopurge.save()
|
||||||
|
|
|
@ -1,44 +1,39 @@
|
||||||
"""J.A.R.V.I.S. RolepingCog."""
|
"""J.A.R.V.I.S. RolepingCog."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from ButtonPaginator import Paginator
|
from dis_snek import InteractionContext, Permissions, Snake
|
||||||
from discord import Member, Role
|
from dis_snek.ext.paginators import Paginator
|
||||||
from discord.ext.commands import Bot
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.role import Role
|
||||||
from discord_slash.model import ButtonStyle
|
from dis_snek.models.discord.user import Member
|
||||||
from discord_slash.utils.manage_commands import create_option
|
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.db.models import Roleping
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
from jarvis.utils.cachecog import CacheCog
|
from jarvis.utils.cachecog import CacheCog
|
||||||
from jarvis.utils.field import Field
|
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
|
|
||||||
class RolepingCog(CacheCog):
|
class RolepingCog(CacheCog):
|
||||||
"""J.A.R.V.I.S. RolepingCog."""
|
"""J.A.R.V.I.S. RolepingCog."""
|
||||||
|
|
||||||
def __init__(self, bot: Bot):
|
def __init__(self, bot: Snake):
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="roleping",
|
name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to 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,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
|
||||||
async def _roleping_add(self, ctx: SlashContext, role: Role) -> None:
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
|
async def _roleping_add(self, ctx: InteractionContext, role: Role) -> None:
|
||||||
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first()
|
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first()
|
||||||
if roleping:
|
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
|
return
|
||||||
_ = Roleping(
|
_ = Roleping(
|
||||||
role=role.id,
|
role=role.id,
|
||||||
|
@ -49,55 +44,45 @@ class RolepingCog(CacheCog):
|
||||||
).save()
|
).save()
|
||||||
await ctx.send(f"Role `{role.name}` added to roleping.")
|
await ctx.send(f"Role `{role.name}` added to roleping.")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role")
|
||||||
base="roleping",
|
@slash_option(
|
||||||
name="remove",
|
name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True
|
||||||
description="Remove a role from the roleping",
|
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="role",
|
|
||||||
description="Role to remove from roleping",
|
|
||||||
option_type=8,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _roleping_remove(self, ctx: SlashContext, role: Role) -> None:
|
async def _roleping_remove(self, ctx: InteractionContext, role: Role) -> None:
|
||||||
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id)
|
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id)
|
||||||
if not roleping:
|
if not roleping:
|
||||||
await ctx.send("Roleping does not exist", hidden=True)
|
await ctx.send("Roleping does not exist", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
roleping.delete()
|
roleping.delete()
|
||||||
await ctx.send(f"Role `{role.name}` removed from roleping.")
|
await ctx.send(f"Role `{role.name}` removed from roleping.")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="roleping", sub_cmd_name="list", description="Lick all blocklisted roles")
|
||||||
base="roleping",
|
async def _roleping_list(self, ctx: InteractionContext) -> None:
|
||||||
name="list",
|
|
||||||
description="List all blocklisted roles",
|
|
||||||
)
|
|
||||||
async def _roleping_list(self, ctx: SlashContext) -> None:
|
|
||||||
exists = self.check_cache(ctx)
|
exists = self.check_cache(ctx)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
rolepings = Roleping.objects(guild=ctx.guild.id)
|
rolepings = Roleping.objects(guild=ctx.guild.id)
|
||||||
if not rolepings:
|
if not rolepings:
|
||||||
await ctx.send("No rolepings configured", hidden=True)
|
await ctx.send("No rolepings configured", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
embeds = []
|
embeds = []
|
||||||
for roleping in rolepings:
|
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 = 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_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_roles = bypass_roles or ["None"]
|
||||||
bypass_users = bypass_users or ["None"]
|
bypass_users = bypass_users or ["None"]
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -105,44 +90,33 @@ class RolepingCog(CacheCog):
|
||||||
description=role.mention,
|
description=role.mention,
|
||||||
color=str(role.color),
|
color=str(role.color),
|
||||||
fields=[
|
fields=[
|
||||||
Field(
|
EmbedField(
|
||||||
name="Created At",
|
name="Created At",
|
||||||
value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"),
|
value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"),
|
||||||
inline=False,
|
inline=False,
|
||||||
),
|
),
|
||||||
Field(name="Active", value=str(roleping.active)),
|
EmbedField(name="Active", value=str(roleping.active)),
|
||||||
Field(
|
EmbedField(
|
||||||
name="Bypass Users",
|
name="Bypass Users",
|
||||||
value="\n".join(bypass_users),
|
value="\n".join(bypass_users),
|
||||||
),
|
),
|
||||||
Field(
|
EmbedField(
|
||||||
name="Bypass Roles",
|
name="Bypass Roles",
|
||||||
value="\n".join(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:
|
if not admin:
|
||||||
admin = self.bot.user
|
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}")
|
embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}")
|
||||||
|
|
||||||
embeds.append(embed)
|
embeds.append(embed)
|
||||||
|
|
||||||
paginator = Paginator(
|
paginator = Paginator.create_from_embeds(self.bot, *embeds, timeout=300)
|
||||||
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=["◀", "▶"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache[hash(paginator)] = {
|
self.cache[hash(paginator)] = {
|
||||||
"user": ctx.author.id,
|
"user": ctx.author.id,
|
||||||
|
@ -152,45 +126,37 @@ class RolepingCog(CacheCog):
|
||||||
"paginator": paginator,
|
"paginator": paginator,
|
||||||
}
|
}
|
||||||
|
|
||||||
await paginator.start()
|
await paginator.send(ctx)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="roleping",
|
name="roleping",
|
||||||
subcommand_group="bypass",
|
description="Block roles from being pinged",
|
||||||
name="user",
|
group_name="bypass",
|
||||||
description="Add a user as a bypass to a roleping",
|
group_description="Allow specific users/roles to ping rolepings",
|
||||||
base_desc="Block roles from being pinged",
|
sub_cmd_name="user",
|
||||||
sub_group_desc="Allow specific users/roles to ping rolepings",
|
sub_cmd_description="Add a user as a bypass to a roleping",
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(name="user", description="User to add", opt_type=OptionTypes.USER, required=True)
|
||||||
async def _roleping_bypass_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
|
@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()
|
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||||
if not roleping:
|
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
|
return
|
||||||
|
|
||||||
if user.id in roleping.bypass["users"]:
|
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
|
return
|
||||||
|
|
||||||
if len(roleping.bypass["users"]) == 10:
|
if len(roleping.bypass["users"]) == 10:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Already have 10 users in bypass. Please consider using roles for roleping bypass",
|
"Already have 10 users in bypass. Please consider using roles for roleping bypass",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -199,51 +165,40 @@ class RolepingCog(CacheCog):
|
||||||
if matching_role:
|
if matching_role:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"{user.mention} already has bypass via {matching_role[0].mention}",
|
f"{user.mention} already has bypass via {matching_role[0].mention}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
roleping.bypass["users"].append(user.id)
|
roleping.bypass["users"].append(user.id)
|
||||||
roleping.save()
|
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(
|
@slash_command(
|
||||||
base="roleping",
|
name="roleping",
|
||||||
subcommand_group="bypass",
|
group_name="bypass",
|
||||||
name="role",
|
sub_cmd_name="role",
|
||||||
description="Add a role as a bypass to a roleping",
|
description="Add a role as a bypass to 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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
|
||||||
async def _roleping_bypass_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
|
@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()
|
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||||
if not roleping:
|
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
|
return
|
||||||
|
|
||||||
if role.id in roleping.bypass["roles"]:
|
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
|
return
|
||||||
|
|
||||||
if len(roleping.bypass["roles"]) == 10:
|
if len(roleping.bypass["roles"]) == 10:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass",
|
"Already have 10 roles in bypass. "
|
||||||
hidden=True,
|
"Please consider consolidating roles for roleping bypass",
|
||||||
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -251,80 +206,67 @@ class RolepingCog(CacheCog):
|
||||||
roleping.save()
|
roleping.save()
|
||||||
await ctx.send(f"{role.name} role bypass added for `{rping.name}`")
|
await ctx.send(f"{role.name} role bypass added for `{rping.name}`")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="roleping",
|
name="roleping",
|
||||||
subcommand_group="restore",
|
description="Block roles from being pinged",
|
||||||
name="user",
|
group_name="restore",
|
||||||
description="Remove a role bypass",
|
group_description="Remove a roleping bypass",
|
||||||
base_desc="Block roles from being pinged",
|
sub_cmd_name="user",
|
||||||
sub_group_desc="Remove a bypass from a roleping (restoring it)",
|
sub_cmd_description="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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _roleping_restore_user(self, ctx: SlashContext, user: Member, rping: Role) -> None:
|
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()
|
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||||
if not roleping:
|
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
|
return
|
||||||
|
|
||||||
if user.id not in roleping.bypass["users"]:
|
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
|
return
|
||||||
|
|
||||||
roleping.bypass["users"].delete(user.id)
|
roleping.bypass["users"].delete(user.id)
|
||||||
roleping.save()
|
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(
|
@slash_command(
|
||||||
base="roleping",
|
name="roleping",
|
||||||
subcommand_group="restore",
|
group_name="restore",
|
||||||
name="role",
|
sub_cmd_name="role",
|
||||||
description="Remove a role bypass",
|
description="Remove a bypass from a roleping (restoring it)",
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _roleping_restore_role(self, ctx: SlashContext, role: Role, rping: Role) -> None:
|
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()
|
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
|
||||||
if not roleping:
|
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
|
return
|
||||||
|
|
||||||
if role.id in roleping.bypass["roles"]:
|
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
|
return
|
||||||
|
|
||||||
if len(roleping.bypass["roles"]) == 10:
|
if len(roleping.bypass["roles"]) == 10:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass",
|
"Already have 10 roles in bypass. "
|
||||||
hidden=True,
|
"Please consider consolidating roles for roleping bypass",
|
||||||
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
"""J.A.R.V.I.S. WarningCog."""
|
"""J.A.R.V.I.S. WarningCog."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from ButtonPaginator import Paginator
|
from dis_snek import InteractionContext, Permissions, Snake
|
||||||
from discord import User
|
from dis_snek.ext.paginators import Paginator
|
||||||
from discord.ext.commands import Bot
|
from dis_snek.models.discord.user import User
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash.model import ButtonStyle
|
OptionTypes,
|
||||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
SlashCommandChoice,
|
||||||
|
slash_command,
|
||||||
|
slash_option,
|
||||||
|
)
|
||||||
|
from dis_snek.models.snek.command import check
|
||||||
|
|
||||||
from jarvis.db.models import Warning
|
from jarvis.db.models import Warning
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
|
@ -18,43 +22,35 @@ from jarvis.utils.permissions import admin_or_permissions
|
||||||
class WarningCog(CacheCog):
|
class WarningCog(CacheCog):
|
||||||
"""J.A.R.V.I.S. WarningCog."""
|
"""J.A.R.V.I.S. WarningCog."""
|
||||||
|
|
||||||
def __init__(self, bot: Bot):
|
def __init__(self, bot: Snake):
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="warn", description="Warn a user")
|
||||||
name="warn",
|
@slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True)
|
||||||
description="Warn a user",
|
@slash_option(
|
||||||
options=[
|
name="reason",
|
||||||
create_option(
|
description="Reason for warning",
|
||||||
name="user",
|
opt_type=OptionTypes.STRING,
|
||||||
description="User to warn",
|
required=True,
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _warn(self, ctx: SlashContext, user: User, reason: str, duration: int = 24) -> None:
|
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:
|
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
|
return
|
||||||
if duration <= 0:
|
if duration <= 0:
|
||||||
await ctx.send("Duration must be > 0", hidden=True)
|
await ctx.send("Duration must be > 0", ephemeral=True)
|
||||||
return
|
return
|
||||||
elif duration >= 120:
|
elif duration >= 120:
|
||||||
await ctx.send("Duration must be < 5 days", hidden=True)
|
await ctx.send("Duration must be < 5 days", ephemeral=True)
|
||||||
return
|
return
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
_ = Warning(
|
_ = Warning(
|
||||||
|
@ -71,52 +67,41 @@ class WarningCog(CacheCog):
|
||||||
description=f"{user.mention} has been warned",
|
description=f"{user.mention} has been warned",
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||||
name=user.nick if user.nick else user.name,
|
|
||||||
icon_url=user.avatar_url,
|
|
||||||
)
|
|
||||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="warnings", description="Get count of user warnings")
|
||||||
name="warnings",
|
@slash_option(name="user", description="User to view", opt_type=OptionTypes.USER, required=True)
|
||||||
description="Get count of user warnings",
|
@slash_option(
|
||||||
options=[
|
name="active",
|
||||||
create_option(
|
description="View active only",
|
||||||
name="user",
|
opt_type=OptionTypes.INTEGER,
|
||||||
description="User to view",
|
required=False,
|
||||||
option_type=6,
|
choices=[
|
||||||
required=True,
|
SlashCommandChoice(name="Yes", value=1),
|
||||||
),
|
SlashCommandChoice(name="No", value=0),
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _warnings(self, ctx: SlashContext, user: User, active: bool = 1) -> None:
|
async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None:
|
||||||
active = bool(active)
|
active = bool(active)
|
||||||
exists = self.check_cache(ctx, user_id=user.id, active=active)
|
exists = self.check_cache(ctx, user_id=user.id, active=active)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
warnings = Warning.objects(
|
warnings = Warning.objects(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
).order_by("-created_at")
|
).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 = []
|
pages = []
|
||||||
if active:
|
if active:
|
||||||
|
@ -126,16 +111,16 @@ class WarningCog(CacheCog):
|
||||||
description=f"{warnings.count()} total | 0 currently active",
|
description=f"{warnings.count()} total | 0 currently active",
|
||||||
fields=[],
|
fields=[],
|
||||||
)
|
)
|
||||||
embed.set_author(name=user.name, icon_url=user.avatar_url)
|
embed.set_author(name=user.username, 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)
|
pages.append(embed)
|
||||||
else:
|
else:
|
||||||
fields = []
|
fields = []
|
||||||
for warn in active_warns:
|
for warn in active_warns:
|
||||||
admin = ctx.guild.get_member(warn.admin)
|
admin = await ctx.guild.get_member(warn.admin)
|
||||||
admin_name = "||`[redacted]`||"
|
admin_name = "||`[redacted]`||"
|
||||||
if admin:
|
if admin:
|
||||||
admin_name = f"{admin.name}#{admin.discriminator}"
|
admin_name = f"{admin.username}#{admin.discriminator}"
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
Field(
|
||||||
name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
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):
|
for i in range(0, len(fields), 5):
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Warnings",
|
title="Warnings",
|
||||||
description=f"{warnings.count()} total | {active_warns.count()} currently active",
|
description=(
|
||||||
fields=fields[i : i + 5], # noqa: E203
|
f"{warnings.count()} total | {active_warns.count()} currently active"
|
||||||
|
),
|
||||||
|
fields=fields[i : i + 5],
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=user.name + "#" + user.discriminator,
|
name=user.username + "#" + user.discriminator,
|
||||||
icon_url=user.avatar_url,
|
icon_url=user.display_avatar.url,
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
||||||
pages.append(embed)
|
pages.append(embed)
|
||||||
else:
|
else:
|
||||||
fields = []
|
fields = []
|
||||||
|
@ -171,28 +158,18 @@ class WarningCog(CacheCog):
|
||||||
for i in range(0, len(fields), 5):
|
for i in range(0, len(fields), 5):
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Warnings",
|
title="Warnings",
|
||||||
description=f"{warnings.count()} total | {active_warns.count()} currently active",
|
description=(
|
||||||
fields=fields[i : i + 5], # noqa: E203
|
f"{warnings.count()} total | {active_warns.count()} currently active"
|
||||||
|
),
|
||||||
|
fields=fields[i : i + 5],
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=user.name + "#" + user.discriminator,
|
name=user.username + "#" + user.discriminator, icon_url=user.display_avatar.url
|
||||||
icon_url=user.avatar_url,
|
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
pages.append(embed)
|
pages.append(embed)
|
||||||
|
|
||||||
paginator = Paginator(
|
paginator = Paginator(bot=self.bot, *pages, timeout=300)
|
||||||
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=["◀", "▶"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache[hash(paginator)] = {
|
self.cache[hash(paginator)] = {
|
||||||
"guild": ctx.guild.id,
|
"guild": ctx.guild.id,
|
||||||
|
@ -204,4 +181,4 @@ class WarningCog(CacheCog):
|
||||||
"paginator": paginator,
|
"paginator": paginator,
|
||||||
}
|
}
|
||||||
|
|
||||||
await paginator.start()
|
await paginator.send(ctx)
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
"""J.A.R.V.I.S. Autoreact Cog."""
|
"""J.A.R.V.I.S. Autoreact Cog."""
|
||||||
import re
|
import re
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from discord import TextChannel
|
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.channel import GuildText
|
||||||
from discord.utils import find
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash import SlashContext, cog_ext
|
OptionTypes,
|
||||||
from discord_slash.utils.manage_commands import create_option
|
slash_command,
|
||||||
|
slash_option,
|
||||||
|
)
|
||||||
|
from dis_snek.models.snek.command import check
|
||||||
|
|
||||||
from jarvis.data.unicode import emoji_list
|
from jarvis.data.unicode import emoji_list
|
||||||
from jarvis.db.models import Autoreact
|
from jarvis.db.models import Autoreact
|
||||||
|
from jarvis.utils import find
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
|
|
||||||
class AutoReactCog(commands.Cog):
|
class AutoReactCog(Scale):
|
||||||
"""J.A.R.V.I.S. Autoreact Cog."""
|
"""J.A.R.V.I.S. Autoreact Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.custom_emote = re.compile(r"^<:\w+:(\d+)>$")
|
self.custom_emote = re.compile(r"^<:\w+:(\d+)>$")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
async def create_autoreact(
|
||||||
base="autoreact",
|
self, ctx: InteractionContext, channel: GuildText
|
||||||
name="create",
|
) -> Tuple[bool, Optional[str]]:
|
||||||
description="Add an autoreact to a channel",
|
"""
|
||||||
options=[
|
Create an autoreact monitor on a channel.
|
||||||
create_option(
|
|
||||||
name="channel",
|
Args:
|
||||||
description="Channel to monitor",
|
ctx: Interaction context of command
|
||||||
option_type=7,
|
channel: Channel to monitor
|
||||||
required=True,
|
|
||||||
)
|
Returns:
|
||||||
],
|
Tuple of success? and error message
|
||||||
)
|
"""
|
||||||
@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
|
|
||||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.send(f"Autoreact already exists for {channel.mention}.", hidden=True)
|
return False, f"Autoreact already exists for {channel.mention}."
|
||||||
return
|
|
||||||
|
|
||||||
_ = Autoreact(
|
_ = Autoreact(
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
|
@ -48,152 +47,145 @@ class AutoReactCog(commands.Cog):
|
||||||
reactions=[],
|
reactions=[],
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
).save()
|
).save()
|
||||||
await ctx.send(f"Autoreact created for {channel.mention}!")
|
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
return True, None
|
||||||
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)
|
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
async def delete_autoreact(self, ctx: InteractionContext, channel: GuildText) -> bool:
|
||||||
base="autoreact",
|
"""
|
||||||
name="add",
|
Remove an autoreact monitor on a channel.
|
||||||
description="Add an autoreact emote to an existing autoreact",
|
|
||||||
options=[
|
Args:
|
||||||
create_option(
|
ctx: Interaction context of command
|
||||||
name="channel",
|
channel: Channel to stop monitoring
|
||||||
description="Autoreact channel to add emote to",
|
|
||||||
option_type=7,
|
Returns:
|
||||||
required=True,
|
Success?
|
||||||
),
|
"""
|
||||||
create_option(
|
return Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete() is not None
|
||||||
name="emote",
|
|
||||||
description="Emote to add",
|
@slash_command(
|
||||||
option_type=3,
|
name="autoreact",
|
||||||
required=True,
|
sub_cmd_name="add",
|
||||||
),
|
sub_cmd_description="Add an autoreact emote to a channel",
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _autoreact_add(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None:
|
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()
|
await ctx.defer()
|
||||||
custom_emoji = self.custom_emote.match(emote)
|
custom_emoji = self.custom_emote.match(emote)
|
||||||
standard_emoji = emote in emoji_list
|
standard_emoji = emote in emoji_list
|
||||||
if not custom_emoji and not standard_emoji:
|
if not custom_emoji and not standard_emoji:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Please use either an emote from this server or a unicode emoji.",
|
"Please use either an emote from this server or a unicode emoji.",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if custom_emoji:
|
if custom_emoji:
|
||||||
emoji_id = int(custom_emoji.group(1))
|
emoji_id = int(custom_emoji.group(1))
|
||||||
if not find(lambda x: x.id == emoji_id, ctx.guild.emojis):
|
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
|
return
|
||||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||||
if not exists:
|
if not autoreact:
|
||||||
await ctx.send(f"Please create autoreact first with /autoreact create {channel.mention}")
|
self.create_autoreact(ctx, channel)
|
||||||
return
|
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||||
if emote in exists.reactions:
|
if emote in autoreact.reactions:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Emote already added to {channel.mention} autoreactions.",
|
f"Emote already added to {channel.mention} autoreactions.",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if len(exists.reactions) >= 5:
|
if len(autoreact.reactions) >= 5:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Max number of reactions hit. Remove a different one to add this one",
|
"Max number of reactions hit. Remove a different one to add this one",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
exists.reactions.append(emote)
|
autoreact.reactions.append(emote)
|
||||||
exists.save()
|
autoreact.save()
|
||||||
await ctx.send(f"Added {emote} to {channel.mention} autoreact.")
|
await ctx.send(f"Added {emote} to {channel.mention} autoreact.")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="autoreact",
|
name="autoreact",
|
||||||
name="remove",
|
sub_cmd_name="remove",
|
||||||
description="Remove an autoreact emote from an existing autoreact",
|
sub_cmd_description="Remove an autoreact emote to a channel",
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _autoreact_remove(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None:
|
name="channel",
|
||||||
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
description="Autoreact channel to remove emote from",
|
||||||
if not exists:
|
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(
|
await ctx.send(
|
||||||
f"Please create autoreact first with /autoreact create {channel.mention}",
|
f"Please create autoreact first with /autoreact add {channel.mention} {emote}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
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(
|
await ctx.send(
|
||||||
f"{emote} not used in {channel.mention} autoreactions.",
|
f"{emote} not used in {channel.mention} autoreactions.",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
exists.reactions.remove(emote)
|
else:
|
||||||
exists.save()
|
autoreact.reactions.remove(emote)
|
||||||
await ctx.send(f"Removed {emote} from {channel.mention} autoreact.")
|
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(
|
@slash_command(
|
||||||
base="autoreact",
|
name="autoreact",
|
||||||
name="list",
|
sub_cmd_name="list",
|
||||||
description="List all autoreacts on a channel",
|
sub_cmd_description="List all autoreacts on a channel",
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="channel",
|
|
||||||
description="Autoreact channel to list",
|
|
||||||
option_type=7,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _autoreact_list(self, ctx: SlashContext, channel: TextChannel) -> None:
|
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()
|
exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
|
||||||
if not exists:
|
if not exists:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Please create autoreact first with /autoreact create {channel.mention}",
|
f"Please create autoreact first with /autoreact add {channel.mention} <emote>",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
message = ""
|
message = ""
|
||||||
if len(exists.reactions) > 0:
|
if len(exists.reactions) > 0:
|
||||||
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(exists.reactions)
|
message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(
|
||||||
|
exists.reactions
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
message = f"No reactions set on {channel.mention}"
|
message = f"No reactions set on {channel.mention}"
|
||||||
await ctx.send(message)
|
await ctx.send(message)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot) -> None:
|
def setup(bot: Snake) -> None:
|
||||||
"""Add AutoReactCog to J.A.R.V.I.S."""
|
"""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
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from ButtonPaginator import Paginator
|
from dis_snek import InteractionContext, Snake
|
||||||
from discord import Member, User
|
from dis_snek.ext.paginators import Paginator
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.user import Member, User
|
||||||
from discord_slash.model import ButtonStyle
|
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.db.models import Guess
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
from jarvis.utils.cachecog import CacheCog
|
from jarvis.utils.cachecog import CacheCog
|
||||||
from jarvis.utils.field import Field
|
|
||||||
|
|
||||||
guild_ids = [578757004059738142, 520021794380447745, 862402786116763668]
|
guild_ids = [578757004059738142, 520021794380447745, 862402786116763668]
|
||||||
|
|
||||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
||||||
invites = re.compile(
|
invites = re.compile(
|
||||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ invites = re.compile(
|
||||||
class CTCCog(CacheCog):
|
class CTCCog(CacheCog):
|
||||||
"""J.A.R.V.I.S. Complete the Code 2 Cog."""
|
"""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)
|
super().__init__(bot)
|
||||||
self._session = aiohttp.ClientSession()
|
self._session = aiohttp.ClientSession()
|
||||||
self.url = "https://completethecodetwo.cards/pw"
|
self.url = "https://completethecodetwo.cards/pw"
|
||||||
|
@ -34,45 +35,48 @@ class CTCCog(CacheCog):
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self._session.close()
|
self._session.close()
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="ctc2",
|
name="ctc2", sub_cmd_name="about", description="CTC2 related commands", scopes=guild_ids
|
||||||
name="about",
|
|
||||||
description="CTC2 related commands",
|
|
||||||
guild_ids=guild_ids,
|
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _about(self, ctx: SlashContext) -> None:
|
async def _about(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send("See https://completethecode.com for more information")
|
await ctx.send("See https://completethecode.com for more information")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="ctc2",
|
name="ctc2",
|
||||||
name="pw",
|
sub_cmd_name="pw",
|
||||||
description="Guess a password for https://completethecodetwo.cards",
|
sub_cmd_description="Guess a password for https://completethecodetwo.cards",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _pw(self, ctx: SlashContext, guess: str) -> None:
|
async def _pw(self, ctx: InteractionContext, guess: str) -> None:
|
||||||
if len(guess) > 800:
|
if len(guess) > 800:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses < 800 characters.",
|
(
|
||||||
hidden=True,
|
"Listen here, dipshit. Don't be like <@256110768724901889>. "
|
||||||
|
"Make your guesses < 800 characters."
|
||||||
|
),
|
||||||
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
elif not valid.fullmatch(guess):
|
elif not valid.fullmatch(guess):
|
||||||
await ctx.send(
|
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
|
return
|
||||||
elif invites.search(guess):
|
elif invites.search(guess):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Listen here, dipshit. No using this to bypass sending invite links.",
|
"Listen here, dipshit. No using this to bypass sending invite links.",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
guessed = Guess.objects(guess=guess).first()
|
guessed = Guess.objects(guess=guess).first()
|
||||||
if guessed:
|
if guessed:
|
||||||
await ctx.send("Already guessed, dipshit.", hidden=True)
|
await ctx.send("Already guessed, dipshit.", ephemeral=True)
|
||||||
return
|
return
|
||||||
result = await self._session.post(self.url, data=guess)
|
result = await self._session.post(self.url, data=guess)
|
||||||
correct = False
|
correct = False
|
||||||
|
@ -80,30 +84,30 @@ class CTCCog(CacheCog):
|
||||||
await ctx.send(f"{ctx.author.mention} got it! Password is {guess}!")
|
await ctx.send(f"{ctx.author.mention} got it! Password is {guess}!")
|
||||||
correct = True
|
correct = True
|
||||||
else:
|
else:
|
||||||
await ctx.send("Nope.", hidden=True)
|
await ctx.send("Nope.", ephemeral=True)
|
||||||
_ = Guess(guess=guess, user=ctx.author.id, correct=correct).save()
|
_ = Guess(guess=guess, user=ctx.author.id, correct=correct).save()
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="ctc2",
|
name="ctc2",
|
||||||
name="guesses",
|
sub_cmd_name="guesses",
|
||||||
description="Show guesses made for https://completethecodetwo.cards",
|
sub_cmd_description="Show guesses made for https://completethecodetwo.cards",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _guesses(self, ctx: SlashContext) -> None:
|
async def _guesses(self, ctx: InteractionContext) -> None:
|
||||||
exists = self.check_cache(ctx)
|
exists = self.check_cache(ctx)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
guesses = Guess.objects().order_by("-correct", "-id")
|
guesses = Guess.objects().order_by("-correct", "-id")
|
||||||
fields = []
|
fields = []
|
||||||
for guess in guesses:
|
for guess in guesses:
|
||||||
user = ctx.guild.get_member(guess["user"])
|
user = await ctx.guild.get_member(guess["user"])
|
||||||
if not user:
|
if not user:
|
||||||
user = await self.bot.fetch_user(guess["user"])
|
user = await self.bot.fetch_user(guess["user"])
|
||||||
if not user:
|
if not user:
|
||||||
|
@ -113,7 +117,7 @@ class CTCCog(CacheCog):
|
||||||
name = "Correctly" if guess["correct"] else "Incorrectly"
|
name = "Correctly" if guess["correct"] else "Incorrectly"
|
||||||
name += " guessed by: " + user
|
name += " guessed by: " + user
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name=name,
|
name=name,
|
||||||
value=guess["guess"] + "\n\u200b",
|
value=guess["guess"] + "\n\u200b",
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -124,7 +128,7 @@ class CTCCog(CacheCog):
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="completethecodetwo.cards guesses",
|
title="completethecodetwo.cards guesses",
|
||||||
description=f"{len(fields)} guesses so far",
|
description=f"{len(fields)} guesses so far",
|
||||||
fields=fields[i : i + 5], # noqa: E203
|
fields=fields[i : i + 5],
|
||||||
url="https://completethecodetwo.cards",
|
url="https://completethecodetwo.cards",
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png")
|
embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png")
|
||||||
|
@ -134,18 +138,7 @@ class CTCCog(CacheCog):
|
||||||
)
|
)
|
||||||
pages.append(embed)
|
pages.append(embed)
|
||||||
|
|
||||||
paginator = Paginator(
|
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||||
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=["◀", "▶"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache[hash(paginator)] = {
|
self.cache[hash(paginator)] = {
|
||||||
"guild": ctx.guild.id,
|
"guild": ctx.guild.id,
|
||||||
|
@ -155,9 +148,9 @@ class CTCCog(CacheCog):
|
||||||
"paginator": paginator,
|
"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."""
|
"""Add CTCCog to J.A.R.V.I.S."""
|
||||||
bot.add_cog(CTCCog(bot))
|
CTCCog(bot)
|
||||||
|
|
|
@ -2,26 +2,31 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from discord.ext import commands
|
from dis_snek import InteractionContext, Scale, Snake
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord_slash.utils.manage_commands import create_option
|
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.config import get_config
|
||||||
from jarvis.data.dbrand import shipping_lookup
|
from jarvis.data.dbrand import shipping_lookup
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
from jarvis.utils.field import Field
|
|
||||||
|
|
||||||
guild_ids = [578757004059738142, 520021794380447745, 862402786116763668]
|
guild_ids = [578757004059738142, 520021794380447745, 862402786116763668]
|
||||||
|
|
||||||
|
|
||||||
class DbrandCog(commands.Cog):
|
class DbrandCog(Scale):
|
||||||
"""
|
"""
|
||||||
dbrand functions for J.A.R.V.I.S.
|
dbrand functions for J.A.R.V.I.S.
|
||||||
|
|
||||||
Mostly support functions. Credit @cpixl for the shipping API
|
Mostly support functions. Credit @cpixl for the shipping API
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.base_url = "https://dbrand.com/"
|
self.base_url = "https://dbrand.com/"
|
||||||
self._session = aiohttp.ClientSession()
|
self._session = aiohttp.ClientSession()
|
||||||
|
@ -32,134 +37,130 @@ class DbrandCog(commands.Cog):
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self._session.close()
|
self._session.close()
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="skin",
|
sub_cmd_name="skin",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="See what skins are available",
|
sub_cmd_description="See what skins are available",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _skin(self, ctx: SlashContext) -> None:
|
async def _skin(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(self.base_url + "/skins")
|
await ctx.send(self.base_url + "/skins")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="robotcamo",
|
sub_cmd_name="robotcamo",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="Get some robot camo. Make Tony Stark proud",
|
sub_cmd_description="Get some robot camo. Make Tony Stark proud",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _camo(self, ctx: SlashContext) -> None:
|
async def _camo(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(self.base_url + "robot-camo")
|
await ctx.send(self.base_url + "robot-camo")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="grip",
|
sub_cmd_name="grip",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="See devices with Grip support",
|
sub_cmd_description="See devices with Grip support",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _grip(self, ctx: SlashContext) -> None:
|
async def _grip(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(self.base_url + "grip")
|
await ctx.send(self.base_url + "grip")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="contact",
|
sub_cmd_name="contact",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="Contact support",
|
sub_cmd_description="Contact support",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _contact(self, ctx: SlashContext) -> None:
|
async def _contact(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
|
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="support",
|
sub_cmd_name="support",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="Contact support",
|
sub_cmd_description="Contact support",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _support(self, ctx: SlashContext) -> None:
|
async def _support(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
|
await ctx.send("Contact dbrand support here: " + self.base_url + "contact")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="orderstat",
|
sub_cmd_name="orderstat",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="Get your order status",
|
sub_cmd_description="Get your order status",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _orderstat(self, ctx: SlashContext) -> None:
|
async def _orderstat(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(self.base_url + "order-status")
|
await ctx.send(self.base_url + "order-status")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="orders",
|
sub_cmd_name="orders",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="Get your order status",
|
sub_cmd_description="Get your order status",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _orders(self, ctx: SlashContext) -> None:
|
async def _orders(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(self.base_url + "order-status")
|
await ctx.send(self.base_url + "order-status")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="status",
|
sub_cmd_name="status",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="dbrand status",
|
sub_cmd_description="dbrand status",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _status(self, ctx: SlashContext) -> None:
|
async def _status(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(self.base_url + "status")
|
await ctx.send(self.base_url + "status")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="buy",
|
sub_cmd_name="buy",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="Give us your money!",
|
sub_cmd_description="Give us your money!",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _buy(self, ctx: SlashContext) -> None:
|
async def _buy(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send("Give us your money! " + self.base_url + "shop")
|
await ctx.send("Give us your money! " + self.base_url + "shop")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="extortion",
|
sub_cmd_name="extortion",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
description="(not) extortion",
|
sub_cmd_description="(not) extortion",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _extort(self, ctx: SlashContext) -> None:
|
async def _extort(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion")
|
await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="wallpapers",
|
sub_cmd_name="wallpapers",
|
||||||
description="Robot Camo Wallpapers",
|
sub_cmd_description="Robot Camo Wallpapers",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _wallpapers(self, ctx: SlashContext) -> None:
|
async def _wallpapers(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers")
|
await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="db",
|
name="db",
|
||||||
name="ship",
|
sub_cmd_name="ship",
|
||||||
description="Get shipping information for your country",
|
sub_cmd_description="Get shipping information for your country",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
options=[
|
|
||||||
(
|
|
||||||
create_option(
|
|
||||||
name="search",
|
|
||||||
description="Country search query (2 character code, country name, emoji)",
|
|
||||||
option_type=3,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@slash_option(
|
||||||
async def _shipping(self, ctx: SlashContext, search: str) -> None:
|
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()
|
await ctx.defer()
|
||||||
if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
|
if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE):
|
||||||
if re.match(
|
if re.match(
|
||||||
|
@ -173,7 +174,6 @@ class DbrandCog(commands.Cog):
|
||||||
elif search == "🏳️":
|
elif search == "🏳️":
|
||||||
search = "fr"
|
search = "fr"
|
||||||
else:
|
else:
|
||||||
print(search)
|
|
||||||
await ctx.send("Please use text to search for shipping.")
|
await ctx.send("Please use text to search for shipping.")
|
||||||
return
|
return
|
||||||
if len(search) > 2:
|
if len(search) > 2:
|
||||||
|
@ -193,14 +193,14 @@ class DbrandCog(commands.Cog):
|
||||||
fields = None
|
fields = None
|
||||||
if data is not None and data["is_valid"] and data["shipping_available"]:
|
if data is not None and data["is_valid"] and data["shipping_available"]:
|
||||||
fields = []
|
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:]:
|
for service in data["shipping_services_available"][1:]:
|
||||||
service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
|
service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
|
||||||
if service_data.status > 400:
|
if service_data.status > 400:
|
||||||
continue
|
continue
|
||||||
service_data = await service_data.json()
|
service_data = await service_data.json()
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
service_data["short-name"],
|
service_data["short-name"],
|
||||||
service_data["time-title"],
|
service_data["time-title"],
|
||||||
)
|
)
|
||||||
|
@ -215,7 +215,7 @@ class DbrandCog(commands.Cog):
|
||||||
)
|
)
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Shipping to {}".format(data["country"]),
|
title="Shipping to {}".format(data["country"]),
|
||||||
description=description,
|
sub_cmd_description=description,
|
||||||
color="#FFBB00",
|
color="#FFBB00",
|
||||||
fields=fields,
|
fields=fields,
|
||||||
url=self.base_url + "shipping/" + country,
|
url=self.base_url + "shipping/" + country,
|
||||||
|
@ -229,8 +229,9 @@ class DbrandCog(commands.Cog):
|
||||||
elif not data["is_valid"]:
|
elif not data["is_valid"]:
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Check Shipping Times",
|
title="Check Shipping Times",
|
||||||
description=(
|
sub_cmd_description=(
|
||||||
"Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)"
|
"Country not found.\nYou can [view all shipping "
|
||||||
|
"destinations here](https://dbrand.com/shipping)"
|
||||||
),
|
),
|
||||||
fields=[],
|
fields=[],
|
||||||
url="https://dbrand.com/shipping",
|
url="https://dbrand.com/shipping",
|
||||||
|
@ -245,7 +246,7 @@ class DbrandCog(commands.Cog):
|
||||||
elif not data["shipping_available"]:
|
elif not data["shipping_available"]:
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Shipping to {}".format(data["country"]),
|
title="Shipping to {}".format(data["country"]),
|
||||||
description=(
|
sub_cmd_description=(
|
||||||
"No shipping available.\nTime to move to a country"
|
"No shipping available.\nTime to move to a country"
|
||||||
" that has shipping available.\nYou can [find a new country "
|
" that has shipping available.\nYou can [find a new country "
|
||||||
"to live in here](https://dbrand.com/shipping)"
|
"to live in here](https://dbrand.com/shipping)"
|
||||||
|
@ -262,6 +263,6 @@ class DbrandCog(commands.Cog):
|
||||||
await ctx.send(embed=embed)
|
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."""
|
"""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
|
import ulid as ulidpy
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
from discord.ext import commands
|
from dis_snek import InteractionContext, Scale, Snake
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
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 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}
|
supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x}
|
||||||
|
|
||||||
OID_VERIFY = re.compile(r"^([1-9][0-9]{0,3}|0)(\.([1-9][0-9]{0,3}|0)){5,13}$")
|
OID_VERIFY = re.compile(r"^([1-9][0-9]{0,3}|0)(\.([1-9][0-9]{0,3}|0)){5,13}$")
|
||||||
URL_VERIFY = re.compile(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(
|
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)
|
ULID_VERIFY = re.compile(r"^[0-9a-z]{26}$", re.IGNORECASE)
|
||||||
UUID_VERIFY = re.compile(
|
UUID_VERIFY = re.compile(
|
||||||
|
@ -29,7 +37,7 @@ UUID_VERIFY = re.compile(
|
||||||
)
|
)
|
||||||
|
|
||||||
invites = 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,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,43 +55,35 @@ def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str:
|
||||||
BSIZE = 65536
|
BSIZE = 65536
|
||||||
block_idx = 0
|
block_idx = 0
|
||||||
while block_idx * BSIZE < len(data):
|
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)
|
hash.update(block)
|
||||||
block_idx += 1
|
block_idx += 1
|
||||||
return hash.hexdigest()
|
return hash.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
class DevCog(commands.Cog):
|
class DevCog(Scale):
|
||||||
"""J.A.R.V.I.S. Developer Cog."""
|
"""J.A.R.V.I.S. Developer Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
@slash_command(name="hash", description="Hash some data")
|
||||||
self.bot = bot
|
@slash_option(
|
||||||
|
name="method",
|
||||||
@cog_ext.cog_slash(
|
description="Hash method",
|
||||||
name="hash",
|
opt_type=OptionTypes.STRING,
|
||||||
description="Hash some data",
|
required=True,
|
||||||
options=[
|
choices=[SlashCommandChoice(name=x, value=x) for x in supported_hashes],
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@slash_option(
|
||||||
async def _hash(self, ctx: SlashContext, method: str, data: str) -> None:
|
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:
|
if not data:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"No data to hash",
|
"No data to hash",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
text = True
|
text = True
|
||||||
|
@ -94,36 +94,31 @@ class DevCog(commands.Cog):
|
||||||
title = data if text else ctx.message.attachments[0].filename
|
title = data if text else ctx.message.attachments[0].filename
|
||||||
description = "Hashed using " + method
|
description = "Hashed using " + method
|
||||||
fields = [
|
fields = [
|
||||||
Field("Data Size", data_size, False),
|
EmbedField("Data Size", data_size, False),
|
||||||
Field("Hash", f"`{hex}`", False),
|
EmbedField("Hash", f"`{hex}`", False),
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(title=title, description=description, fields=fields)
|
embed = build_embed(title=title, description=description, fields=fields)
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="uuid", description="Generate a UUID")
|
||||||
name="uuid",
|
@slash_option(
|
||||||
description="Generate a UUID",
|
name="version",
|
||||||
options=[
|
description="UUID version",
|
||||||
create_option(
|
opt_type=OptionTypes.STRING,
|
||||||
name="version",
|
required=True,
|
||||||
description="UUID version",
|
choices=[SlashCommandChoice(name=x, value=x) for x in ["3", "4", "5"]],
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
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)
|
version = int(version)
|
||||||
if version in [3, 5] and not data:
|
if version in [3, 5] and not data:
|
||||||
await ctx.send(f"UUID{version} requires data.", hidden=True)
|
await ctx.send(f"UUID{version} requires data.", ephemeral=True)
|
||||||
return
|
return
|
||||||
if version == 4:
|
if version == 4:
|
||||||
await ctx.send(f"UUID4: `{uuidpy.uuid4()}`")
|
await ctx.send(f"UUID4: `{uuidpy.uuid4()}`")
|
||||||
|
@ -139,40 +134,40 @@ class DevCog(commands.Cog):
|
||||||
to_send = UUID_GET[version](uuidpy.NAMESPACE_DNS, data)
|
to_send = UUID_GET[version](uuidpy.NAMESPACE_DNS, data)
|
||||||
await ctx.send(f"UUID{version}: `{to_send}`")
|
await ctx.send(f"UUID{version}: `{to_send}`")
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="objectid",
|
name="objectid",
|
||||||
description="Generate an ObjectID",
|
description="Generate an ObjectID",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _objectid(self, ctx: SlashContext) -> None:
|
async def _objectid(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(f"ObjectId: `{str(ObjectId())}`")
|
await ctx.send(f"ObjectId: `{str(ObjectId())}`")
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="ulid",
|
name="ulid",
|
||||||
description="Generate a ULID",
|
description="Generate a ULID",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _ulid(self, ctx: SlashContext) -> None:
|
async def _ulid(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(f"ULID: `{ulidpy.new().str}`")
|
await ctx.send(f"ULID: `{ulidpy.new().str}`")
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="uuid2ulid",
|
name="uuid2ulid",
|
||||||
description="Convert a UUID to a ULID",
|
description="Convert a UUID to a ULID",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _uuid2ulid(self, ctx: SlashContext, uuid: str) -> None:
|
async def _uuid2ulid(self, ctx: InteractionContext, uuid: str) -> None:
|
||||||
if UUID_VERIFY.match(uuid):
|
if UUID_VERIFY.match(uuid):
|
||||||
u = ulidpy.parse(uuid)
|
u = ulidpy.parse(uuid)
|
||||||
await ctx.send(f"ULID: `{u.str}`")
|
await ctx.send(f"ULID: `{u.str}`")
|
||||||
else:
|
else:
|
||||||
await ctx.send("Invalid UUID")
|
await ctx.send("Invalid UUID")
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="ulid2uuid",
|
name="ulid2uuid",
|
||||||
description="Convert a ULID to a UUID",
|
description="Convert a ULID to a UUID",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 2, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _ulid2uuid(self, ctx: SlashContext, ulid: str) -> None:
|
async def _ulid2uuid(self, ctx: InteractionContext, ulid: str) -> None:
|
||||||
if ULID_VERIFY.match(ulid):
|
if ULID_VERIFY.match(ulid):
|
||||||
ulid = ulidpy.parse(ulid)
|
ulid = ulidpy.parse(ulid)
|
||||||
await ctx.send(f"UUID: `{ulid.uuid}`")
|
await ctx.send(f"UUID: `{ulid.uuid}`")
|
||||||
|
@ -181,82 +176,71 @@ class DevCog(commands.Cog):
|
||||||
|
|
||||||
base64_methods = ["b64", "b16", "b32", "a85", "b85"]
|
base64_methods = ["b64", "b16", "b32", "a85", "b85"]
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="encode", description="Encode some data")
|
||||||
name="encode",
|
@slash_option(
|
||||||
description="Encode some data",
|
name="method",
|
||||||
options=[
|
description="Encode method",
|
||||||
create_option(
|
opt_type=OptionTypes.STRING,
|
||||||
name="method",
|
required=True,
|
||||||
description="Encode method",
|
choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods],
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
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
|
mstr = method
|
||||||
method = getattr(base64, method + "encode")
|
method = getattr(base64, method + "encode")
|
||||||
encoded = method(data.encode("UTF-8")).decode("UTF-8")
|
encoded = method(data.encode("UTF-8")).decode("UTF-8")
|
||||||
fields = [
|
fields = [
|
||||||
Field(name="Plaintext", value=f"`{data}`", inline=False),
|
EmbedField(name="Plaintext", value=f"`{data}`", inline=False),
|
||||||
Field(name=mstr, value=f"`{encoded}`", inline=False),
|
EmbedField(name=mstr, value=f"`{encoded}`", inline=False),
|
||||||
]
|
]
|
||||||
embed = build_embed(title="Decoded Data", description="", fields=fields)
|
embed = build_embed(title="Decoded Data", description="", fields=fields)
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="decode", description="Decode some data")
|
||||||
name="decode",
|
@slash_option(
|
||||||
description="Decode some data",
|
name="method",
|
||||||
options=[
|
description="Decode method",
|
||||||
create_option(
|
opt_type=OptionTypes.STRING,
|
||||||
name="method",
|
required=True,
|
||||||
description="Decode method",
|
choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods],
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
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
|
mstr = method
|
||||||
method = getattr(base64, method + "decode")
|
method = getattr(base64, method + "decode")
|
||||||
decoded = method(data.encode("UTF-8")).decode("UTF-8")
|
decoded = method(data.encode("UTF-8")).decode("UTF-8")
|
||||||
if invites.search(decoded):
|
if invites.search(decoded):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Please don't use this to bypass invite restrictions",
|
"Please don't use this to bypass invite restrictions",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
fields = [
|
fields = [
|
||||||
Field(name="Plaintext", value=f"`{data}`", inline=False),
|
EmbedField(name="Plaintext", value=f"`{data}`", inline=False),
|
||||||
Field(name=mstr, value=f"`{decoded}`", inline=False),
|
EmbedField(name=mstr, value=f"`{decoded}`", inline=False),
|
||||||
]
|
]
|
||||||
embed = build_embed(title="Decoded Data", description="", fields=fields)
|
embed = build_embed(title="Decoded Data", description="", fields=fields)
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="cloc", description="Get J.A.R.V.I.S. lines of code")
|
||||||
name="cloc",
|
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||||
description="Get J.A.R.V.I.S. lines of code",
|
async def _cloc(self, ctx: InteractionContext) -> None:
|
||||||
)
|
output = subprocess.check_output( # noqa: S603, S607
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
["tokei", "-C", "--sort", "code"]
|
||||||
async def _cloc(self, ctx: SlashContext) -> None:
|
).decode("UTF-8")
|
||||||
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8") # noqa: S603, S607
|
|
||||||
await ctx.send(f"```\n{output}\n```")
|
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."""
|
"""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
|
return
|
||||||
elif isinstance(error, commands.errors.CommandOnCooldown):
|
elif isinstance(error, commands.errors.CommandOnCooldown):
|
||||||
await ctx.send(
|
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:
|
else:
|
||||||
await ctx.send(f"Error processing command:\n```{error}```")
|
await ctx.send(f"Error processing command:\n```{error}```")
|
||||||
|
@ -29,19 +30,22 @@ class ErrorHandlerCog(commands.Cog):
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_slash_command_error(self, ctx: SlashContext, error: Exception) -> None:
|
async def on_slash_command_error(self, ctx: SlashContext, error: Exception) -> None:
|
||||||
"""discord_slash on_slash_command_error override."""
|
"""discord_slash on_slash_command_error override."""
|
||||||
if isinstance(error, commands.errors.MissingPermissions) or isinstance(error, commands.errors.CheckFailure):
|
if isinstance(error, commands.errors.MissingPermissions) or isinstance(
|
||||||
await ctx.send("I'm afraid I can't let you do that.", hidden=True)
|
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):
|
elif isinstance(error, commands.errors.CommandNotFound):
|
||||||
return
|
return
|
||||||
elif isinstance(error, commands.errors.CommandOnCooldown):
|
elif isinstance(error, commands.errors.CommandOnCooldown):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Command on cooldown. " + f"Please wait {error.retry_after:0.2f}s before trying again",
|
"Command on cooldown. "
|
||||||
hidden=True,
|
f"Please wait {error.retry_after:0.2f}s before trying again",
|
||||||
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Error processing command:\n```{error}```",
|
f"Error processing command:\n```{error}```",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
raise error
|
raise error
|
||||||
slash.commands[ctx.command].reset_cooldown(ctx)
|
slash.commands[ctx.command].reset_cooldown(ctx)
|
||||||
|
@ -49,4 +53,4 @@ class ErrorHandlerCog(commands.Cog):
|
||||||
|
|
||||||
def setup(bot: commands.Bot) -> None:
|
def setup(bot: commands.Bot) -> None:
|
||||||
"""Add ErrorHandlerCog to J.A.R.V.I.S."""
|
"""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
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import gitlab
|
import gitlab
|
||||||
from ButtonPaginator import Paginator
|
from dis_snek import InteractionContext, Snake
|
||||||
from discord import Embed
|
from dis_snek.ext.paginators import Paginator
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.embed import Embed, EmbedField
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash.model import ButtonStyle
|
OptionTypes,
|
||||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
SlashCommandChoice,
|
||||||
|
slash_command,
|
||||||
|
slash_option,
|
||||||
|
)
|
||||||
|
|
||||||
from jarvis.config import get_config
|
from jarvis.config import get_config
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
from jarvis.utils.cachecog import CacheCog
|
from jarvis.utils.cachecog import CacheCog
|
||||||
from jarvis.utils.field import Field
|
|
||||||
|
|
||||||
guild_ids = [862402786116763668]
|
guild_ids = [862402786116763668]
|
||||||
|
|
||||||
|
@ -20,25 +22,22 @@ guild_ids = [862402786116763668]
|
||||||
class GitlabCog(CacheCog):
|
class GitlabCog(CacheCog):
|
||||||
"""J.A.R.V.I.S. GitLab Cog."""
|
"""J.A.R.V.I.S. GitLab Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
config = get_config()
|
config = get_config()
|
||||||
self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token)
|
self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token)
|
||||||
# J.A.R.V.I.S. GitLab ID is 29
|
# J.A.R.V.I.S. GitLab ID is 29
|
||||||
self.project = self._gitlab.projects.get(29)
|
self.project = self._gitlab.projects.get(29)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="gl",
|
name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids
|
||||||
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)],
|
|
||||||
)
|
)
|
||||||
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:
|
try:
|
||||||
issue = self.project.issues.get(int(id))
|
issue = self.project.issues.get(int(id))
|
||||||
except gitlab.exceptions.GitlabGetError:
|
except gitlab.exceptions.GitlabGetError:
|
||||||
await ctx.send("Issue does not exist.", hidden=True)
|
await ctx.send("Issue does not exist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
assignee = issue.assignee
|
assignee = issue.assignee
|
||||||
if assignee:
|
if assignee:
|
||||||
|
@ -46,7 +45,9 @@ class GitlabCog(CacheCog):
|
||||||
else:
|
else:
|
||||||
assignee = "None"
|
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
|
labels = issue.labels
|
||||||
if labels:
|
if labels:
|
||||||
|
@ -55,18 +56,20 @@ class GitlabCog(CacheCog):
|
||||||
labels = "None"
|
labels = "None"
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Field(name="State", value=issue.state[0].upper() + issue.state[1:]),
|
EmbedField(name="State", value=issue.state[0].upper() + issue.state[1:]),
|
||||||
Field(name="Assignee", value=assignee),
|
EmbedField(name="Assignee", value=assignee),
|
||||||
Field(name="Labels", value=labels),
|
EmbedField(name="Labels", value=labels),
|
||||||
]
|
]
|
||||||
color = self.project.labels.get(issue.labels[0]).color
|
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":
|
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")
|
closed_at = datetime.strptime(issue.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||||
fields.append(Field(name="Closed At", value=closed_at))
|
"%Y-%m-%d %H:%M:%S UTC"
|
||||||
|
)
|
||||||
|
fields.append(EmbedField(name="Closed At", value=closed_at))
|
||||||
if issue.milestone:
|
if issue.milestone:
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name="Milestone",
|
name="Milestone",
|
||||||
value=f"[{issue.milestone['title']}]({issue.milestone['web_url']})",
|
value=f"[{issue.milestone['title']}]({issue.milestone['web_url']})",
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -83,50 +86,49 @@ class GitlabCog(CacheCog):
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=issue.author["name"],
|
name=issue.author["name"],
|
||||||
icon_url=issue.author["avatar_url"],
|
icon_url=issue.author["display_avatar"],
|
||||||
url=issue.author["web_url"],
|
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)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="gl",
|
name="gl",
|
||||||
name="milestone",
|
sub_cmd_name="milestone",
|
||||||
description="Get a milestone from GitLab",
|
description="Get a milestone from GitLab",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="id",
|
|
||||||
description="Milestone ID",
|
|
||||||
option_type=4,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
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:
|
try:
|
||||||
milestone = self.project.milestones.get(int(id))
|
milestone = self.project.milestones.get(int(id))
|
||||||
except gitlab.exceptions.GitlabGetError:
|
except gitlab.exceptions.GitlabGetError:
|
||||||
await ctx.send("Milestone does not exist.", hidden=True)
|
await ctx.send("Milestone does not exist.", ephemeral=True)
|
||||||
return
|
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 = [
|
fields = [
|
||||||
Field(
|
EmbedField(
|
||||||
name="State",
|
name="State",
|
||||||
value=milestone.state[0].upper() + milestone.state[1:],
|
value=milestone.state[0].upper() + milestone.state[1:],
|
||||||
),
|
),
|
||||||
Field(name="Start Date", value=milestone.start_date),
|
EmbedField(name="Start Date", value=milestone.start_date),
|
||||||
Field(name="Due Date", value=milestone.due_date),
|
EmbedField(name="Due Date", value=milestone.due_date),
|
||||||
Field(name="Created At", value=created_at),
|
EmbedField(name="Created At", value=created_at),
|
||||||
]
|
]
|
||||||
|
|
||||||
if milestone.updated_at:
|
if milestone.updated_at:
|
||||||
updated_at = datetime.strptime(milestone.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
updated_at = datetime.strptime(milestone.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||||
"%Y-%m-%d %H:%M:%S UTC"
|
"%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:
|
if len(milestone.title) > 200:
|
||||||
milestone.title = milestone.title[:200] + "..."
|
milestone.title = milestone.title[:200] + "..."
|
||||||
|
@ -143,28 +145,25 @@ class GitlabCog(CacheCog):
|
||||||
url="https://git.zevaryx.com/jarvis",
|
url="https://git.zevaryx.com/jarvis",
|
||||||
icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png",
|
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)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="gl",
|
name="gl",
|
||||||
name="mergerequest",
|
sub_cmd_name="mr",
|
||||||
description="Get an merge request from GitLab",
|
description="Get a merge request from GitLab",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="id",
|
|
||||||
description="Merge Request ID",
|
|
||||||
option_type=4,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
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:
|
try:
|
||||||
mr = self.project.mergerequests.get(int(id))
|
mr = self.project.mergerequests.get(int(id))
|
||||||
except gitlab.exceptions.GitlabGetError:
|
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
|
return
|
||||||
assignee = mr.assignee
|
assignee = mr.assignee
|
||||||
if assignee:
|
if assignee:
|
||||||
|
@ -172,7 +171,9 @@ class GitlabCog(CacheCog):
|
||||||
else:
|
else:
|
||||||
assignee = "None"
|
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
|
labels = mr.labels
|
||||||
if labels:
|
if labels:
|
||||||
|
@ -181,24 +182,28 @@ class GitlabCog(CacheCog):
|
||||||
labels = "None"
|
labels = "None"
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Field(name="State", value=mr.state[0].upper() + mr.state[1:]),
|
EmbedField(name="State", value=mr.state[0].upper() + mr.state[1:]),
|
||||||
Field(name="Assignee", value=assignee),
|
EmbedField(name="Assignee", value=assignee),
|
||||||
Field(name="Labels", value=labels),
|
EmbedField(name="Labels", value=labels),
|
||||||
]
|
]
|
||||||
if mr.labels:
|
if mr.labels:
|
||||||
color = self.project.labels.get(mr.labels[0]).color
|
color = self.project.labels.get(mr.labels[0]).color
|
||||||
else:
|
else:
|
||||||
color = "#00FFEE"
|
color = "#00FFEE"
|
||||||
fields.append(Field(name="Created At", value=created_at))
|
fields.append(EmbedField(name="Created At", value=created_at))
|
||||||
if mr.state == "merged":
|
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")
|
merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||||
fields.append(Field(name="Merged At", value=merged_at))
|
"%Y-%m-%d %H:%M:%S UTC"
|
||||||
|
)
|
||||||
|
fields.append(EmbedField(name="Merged At", value=merged_at))
|
||||||
elif mr.state == "closed":
|
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")
|
closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||||
fields.append(Field(name="Closed At", value=closed_at))
|
"%Y-%m-%d %H:%M:%S UTC"
|
||||||
|
)
|
||||||
|
fields.append(EmbedField(name="Closed At", value=closed_at))
|
||||||
if mr.milestone:
|
if mr.milestone:
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name="Milestone",
|
name="Milestone",
|
||||||
value=f"[{mr.milestone['title']}]({mr.milestone['web_url']})",
|
value=f"[{mr.milestone['title']}]({mr.milestone['web_url']})",
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -215,10 +220,12 @@ class GitlabCog(CacheCog):
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=mr.author["name"],
|
name=mr.author["name"],
|
||||||
icon_url=mr.author["avatar_url"],
|
icon_url=mr.author["display_avatar"],
|
||||||
url=mr.author["web_url"],
|
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)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
def build_embed_page(self, api_list: list, t_state: str, name: str) -> Embed:
|
def build_embed_page(self, api_list: list, t_state: str, name: str) -> Embed:
|
||||||
|
@ -230,7 +237,7 @@ class GitlabCog(CacheCog):
|
||||||
fields = []
|
fields = []
|
||||||
for item in api_list:
|
for item in api_list:
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name=f"[#{item.iid}] {item.title}",
|
name=f"[#{item.iid}] {item.title}",
|
||||||
value=item.description + f"\n\n[View this {name}]({item.web_url})",
|
value=item.description + f"\n\n[View this {name}]({item.web_url})",
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -248,35 +255,32 @@ class GitlabCog(CacheCog):
|
||||||
url="https://git.zevaryx.com/jarvis",
|
url="https://git.zevaryx.com/jarvis",
|
||||||
icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png",
|
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
|
return embed
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="gl",
|
name="gl", sub_cmd_name="issues", description="Get issues from GitLab", scopes=guild_ids
|
||||||
name="issues",
|
)
|
||||||
description="Get open issues from GitLab",
|
@slash_option(
|
||||||
guild_ids=guild_ids,
|
name="state",
|
||||||
options=[
|
description="State of issues to get",
|
||||||
create_option(
|
opt_type=OptionTypes.STRING,
|
||||||
name="state",
|
required=False,
|
||||||
description="State of issues to get",
|
choices=[
|
||||||
option_type=3,
|
SlashCommandChoice(name="Open", value="opened"),
|
||||||
required=False,
|
SlashCommandChoice(name="Closed", value="closed"),
|
||||||
choices=[
|
SlashCommandChoice(name="All", value="all"),
|
||||||
create_choice(name="Open", value="opened"),
|
|
||||||
create_choice(name="Closed", value="closed"),
|
|
||||||
create_choice(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)
|
exists = self.check_cache(ctx, state=state)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -311,20 +315,9 @@ class GitlabCog(CacheCog):
|
||||||
pages = []
|
pages = []
|
||||||
t_state = t_state[0].upper() + t_state[1:]
|
t_state = t_state[0].upper() + t_state[1:]
|
||||||
for i in range(0, len(issues), 5):
|
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(
|
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||||
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=["◀", "▶"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache[hash(paginator)] = {
|
self.cache[hash(paginator)] = {
|
||||||
"user": ctx.author.id,
|
"user": ctx.author.id,
|
||||||
|
@ -335,35 +328,32 @@ class GitlabCog(CacheCog):
|
||||||
"paginator": paginator,
|
"paginator": paginator,
|
||||||
}
|
}
|
||||||
|
|
||||||
await paginator.start()
|
await paginator.send(ctx)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="gl",
|
name="gl",
|
||||||
name="mergerequests",
|
sub_cmd_name="mrs",
|
||||||
description="Get open issues from GitLab",
|
description="Get merge requests from GitLab",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
options=[
|
)
|
||||||
create_option(
|
@slash_option(
|
||||||
name="state",
|
name="state",
|
||||||
description="State of issues to get",
|
description="State of merge requests to get",
|
||||||
option_type=3,
|
opt_type=OptionTypes.STRING,
|
||||||
required=False,
|
required=False,
|
||||||
choices=[
|
choices=[
|
||||||
create_choice(name="Open", value="opened"),
|
SlashCommandChoice(name="Open", value="opened"),
|
||||||
create_choice(name="Closed", value="closed"),
|
SlashCommandChoice(name="Closed", value="closed"),
|
||||||
create_choice(name="Merged", value="merged"),
|
SlashCommandChoice(name="All", value="all"),
|
||||||
create_choice(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)
|
exists = self.check_cache(ctx, state=state)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -398,20 +388,11 @@ class GitlabCog(CacheCog):
|
||||||
pages = []
|
pages = []
|
||||||
t_state = t_state[0].upper() + t_state[1:]
|
t_state = t_state[0].upper() + t_state[1:]
|
||||||
for i in range(0, len(merges), 5):
|
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(
|
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||||
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=["◀", "▶"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache[hash(paginator)] = {
|
self.cache[hash(paginator)] = {
|
||||||
"user": ctx.author.id,
|
"user": ctx.author.id,
|
||||||
|
@ -422,21 +403,21 @@ class GitlabCog(CacheCog):
|
||||||
"paginator": paginator,
|
"paginator": paginator,
|
||||||
}
|
}
|
||||||
|
|
||||||
await paginator.start()
|
await paginator.send(ctx)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="gl",
|
name="gl",
|
||||||
name="milestones",
|
sub_cmd_name="milestones",
|
||||||
description="Get open issues from GitLab",
|
description="Get milestones from GitLab",
|
||||||
guild_ids=guild_ids,
|
scopes=guild_ids,
|
||||||
)
|
)
|
||||||
async def _milestones(self, ctx: SlashContext) -> None:
|
async def _milestones(self, ctx: InteractionContext) -> None:
|
||||||
exists = self.check_cache(ctx)
|
exists = self.check_cache(ctx)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -463,20 +444,11 @@ class GitlabCog(CacheCog):
|
||||||
|
|
||||||
pages = []
|
pages = []
|
||||||
for i in range(0, len(milestones), 5):
|
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(
|
paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300)
|
||||||
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=["◀", "▶"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache[hash(paginator)] = {
|
self.cache[hash(paginator)] = {
|
||||||
"user": ctx.author.id,
|
"user": ctx.author.id,
|
||||||
|
@ -486,10 +458,10 @@ class GitlabCog(CacheCog):
|
||||||
"paginator": paginator,
|
"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."""
|
"""Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists."""
|
||||||
if get_config().gitlab_token:
|
if get_config().gitlab_token:
|
||||||
bot.add_cog(GitlabCog(bot))
|
GitlabCog(bot)
|
||||||
|
|
|
@ -5,21 +5,23 @@ from io import BytesIO
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from discord import File
|
from dis_snek import MessageContext, Scale, Snake, message_command
|
||||||
from discord.ext import commands
|
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 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.
|
Image processing functions for J.A.R.V.I.S.
|
||||||
|
|
||||||
May be categorized under util later
|
May be categorized under util later
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self._session = aiohttp.ClientSession()
|
self._session = aiohttp.ClientSession()
|
||||||
self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE)
|
self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE)
|
||||||
|
@ -27,7 +29,7 @@ class ImageCog(commands.Cog):
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self._session.close()
|
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:
|
if not target:
|
||||||
await ctx.send("Missing target size, i.e. 200KB.")
|
await ctx.send("Missing target size, i.e. 200KB.")
|
||||||
return
|
return
|
||||||
|
@ -84,23 +86,23 @@ class ImageCog(commands.Cog):
|
||||||
bufio = BytesIO(file)
|
bufio = BytesIO(file)
|
||||||
accuracy = (len(file) / tgt_size) * 100
|
accuracy = (len(file) / tgt_size) * 100
|
||||||
fields = [
|
fields = [
|
||||||
Field("Original Size", convert_bytesize(size), False),
|
EmbedField("Original Size", convert_bytesize(size), False),
|
||||||
Field("New Size", convert_bytesize(len(file)), False),
|
EmbedField("New Size", convert_bytesize(len(file)), False),
|
||||||
Field("Accuracy", f"{accuracy:.02f}%", False),
|
EmbedField("Accuracy", f"{accuracy:.02f}%", False),
|
||||||
]
|
]
|
||||||
embed = build_embed(title=filename, description="", fields=fields)
|
embed = build_embed(title=filename, description="", fields=fields)
|
||||||
embed.set_image(url="attachment://resized.png")
|
embed.set_image(url="attachment://resized.png")
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=embed,
|
embed=embed,
|
||||||
file=File(bufio, filename="resized.png"),
|
file=File(file=bufio, filename="resized.png"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@commands.command(name="resize", help="Resize an image")
|
@message_command(name="resize")
|
||||||
@commands.cooldown(1, 60, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=60)
|
||||||
async def _resize_pref(self, ctx: commands.Context, target: str, url: str = None) -> None:
|
async def _resize_pref(self, ctx: MessageContext, target: str, url: str = None) -> None:
|
||||||
await self._resize(ctx, target, url)
|
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."""
|
"""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 datetime import datetime
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from discord.ext import commands
|
from dis_snek import InteractionContext, Scale, Snake
|
||||||
from discord_slash import SlashContext, cog_ext
|
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.db.models import Joke
|
||||||
from jarvis.utils import build_embed
|
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.
|
Joke library for J.A.R.V.I.S.
|
||||||
|
|
||||||
May adapt over time to create jokes using machine learning
|
May adapt over time to create jokes using machine learning
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
# TODO: Make this a command group with subcommands
|
# TODO: Make this a command group with subcommands
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="joke",
|
name="joke",
|
||||||
description="Hear a joke",
|
description="Hear a joke",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 10, commands.BucketType.channel)
|
@slash_option(name="id", description="Joke ID", required=False, opt_type=OptionTypes.INTEGER)
|
||||||
async def _joke(self, ctx: SlashContext, id: str = None) -> None:
|
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=10)
|
||||||
|
async def _joke(self, ctx: InteractionContext, id: str = None) -> None:
|
||||||
"""Get a joke from the database."""
|
"""Get a joke from the database."""
|
||||||
try:
|
try:
|
||||||
if randint(1, 100_000) == 5779 and id is None: # noqa: S311
|
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()
|
result = Joke.objects().aggregate(pipeline).next()
|
||||||
|
|
||||||
if result is None:
|
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
|
return
|
||||||
emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"])
|
emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"])
|
||||||
for match in emotes:
|
for match in emotes:
|
||||||
|
@ -63,7 +70,7 @@ class JokeCog(commands.Cog):
|
||||||
body = ""
|
body = ""
|
||||||
for word in result["body"].split(" "):
|
for word in result["body"].split(" "):
|
||||||
if len(body) + 1 + len(word) > 1024:
|
if len(body) + 1 + len(word) > 1024:
|
||||||
body_chunks.append(Field("", body, False))
|
body_chunks.append(EmbedField("", body, False))
|
||||||
body = ""
|
body = ""
|
||||||
if word == "\n" and body == "":
|
if word == "\n" and body == "":
|
||||||
continue
|
continue
|
||||||
|
@ -87,15 +94,15 @@ class JokeCog(commands.Cog):
|
||||||
else:
|
else:
|
||||||
desc += word + " "
|
desc += word + " "
|
||||||
|
|
||||||
body_chunks.append(Field("", body, False))
|
body_chunks.append(EmbedField("", body, False))
|
||||||
|
|
||||||
fields = body_chunks
|
fields = body_chunks
|
||||||
fields.append(Field("Score", result["score"]))
|
fields.append(EmbedField("Score", result["score"]))
|
||||||
# Field(
|
# Field(
|
||||||
# "Created At",
|
# "Created At",
|
||||||
# str(datetime.fromtimestamp(result["created_utc"])),
|
# str(datetime.fromtimestamp(result["created_utc"])),
|
||||||
# ),
|
# ),
|
||||||
fields.append(Field("ID", result["rid"]))
|
fields.append(EmbedField("ID", result["rid"]))
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title=title,
|
title=title,
|
||||||
description=desc,
|
description=desc,
|
||||||
|
@ -109,6 +116,6 @@ class JokeCog(commands.Cog):
|
||||||
# await ctx.send(f"**{result['title']}**\n\n{result['body']}")
|
# 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."""
|
"""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:
|
def setup(bot: Bot) -> None:
|
||||||
"""Add modlog cogs to J.A.R.V.I.S."""
|
"""Add modlog cogs to J.A.R.V.I.S."""
|
||||||
bot.add_cog(command.ModlogCommandCog(bot))
|
command.ModlogCommandCog(bot)
|
||||||
bot.add_cog(member.ModlogMemberCog(bot))
|
member.ModlogMemberCog(bot)
|
||||||
bot.add_cog(message.ModlogMessageCog(bot))
|
message.ModlogMessageCog(bot)
|
||||||
|
|
|
@ -41,9 +41,8 @@ class ModlogCommandCog(commands.Cog):
|
||||||
fields=fields,
|
fields=fields,
|
||||||
color="#fc9e3f",
|
color="#fc9e3f",
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url)
|
||||||
name=ctx.author.name,
|
embed.set_footer(
|
||||||
icon_url=ctx.author.avatar_url,
|
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)
|
await channel.send(embed=embed)
|
||||||
|
|
|
@ -223,7 +223,9 @@ class ModlogMemberCog(commands.Cog):
|
||||||
desc=f"{before.mention} was verified",
|
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."""
|
"""Process rolechange event."""
|
||||||
await asyncio.sleep(0.5) # Need to wait for audit log
|
await asyncio.sleep(0.5) # Need to wait for audit log
|
||||||
auditlog = await before.guild.audit_logs(
|
auditlog = await before.guild.audit_logs(
|
||||||
|
@ -320,10 +322,7 @@ class ModlogMemberCog(commands.Cog):
|
||||||
fields=fields,
|
fields=fields,
|
||||||
timestamp=log.created_at,
|
timestamp=log.created_at,
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(name=f"{after.name}", icon_url=after.display_avatar.url)
|
||||||
name=f"{after.name}",
|
|
||||||
icon_url=after.avatar_url,
|
|
||||||
)
|
|
||||||
embed.set_footer(text=f"{after.name}#{after.discriminator} | {after.id}")
|
embed.set_footer(text=f"{after.name}#{after.discriminator} | {after.id}")
|
||||||
elif len(before.roles) != len(after.roles):
|
elif len(before.roles) != len(after.roles):
|
||||||
# TODO: User got a new role
|
# TODO: User got a new role
|
||||||
|
|
|
@ -44,10 +44,12 @@ class ModlogMessageCog(commands.Cog):
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=before.author.name,
|
name=before.author.name,
|
||||||
icon_url=before.author.avatar_url,
|
icon_url=before.author.display_avatar.url,
|
||||||
url=after.jump_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)
|
await channel.send(embed=embed)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
|
@ -97,8 +99,10 @@ class ModlogMessageCog(commands.Cog):
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=message.author.name,
|
name=message.author.name,
|
||||||
icon_url=message.author.avatar_url,
|
icon_url=message.author.display_avatar.url,
|
||||||
url=message.jump_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)
|
await channel.send(embed=embed)
|
||||||
|
|
|
@ -33,10 +33,7 @@ def modlog_embed(
|
||||||
fields=fields,
|
fields=fields,
|
||||||
timestamp=log.created_at,
|
timestamp=log.created_at,
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(name=f"{member.name}", icon_url=member.display_avatar.url)
|
||||||
name=f"{member.name}",
|
|
||||||
icon_url=member.avatar_url,
|
|
||||||
)
|
|
||||||
embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}")
|
embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}")
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
|
@ -1,163 +1,27 @@
|
||||||
"""J.A.R.V.I.S. Owner Cog."""
|
"""J.A.R.V.I.S. Owner Cog."""
|
||||||
import os
|
from dis_snek import MessageContext, Scale, Snake, message_command
|
||||||
import sys
|
from dis_snek.models.discord.user import User
|
||||||
import traceback
|
from dis_snek.models.snek.checks import is_owner
|
||||||
from inspect import getsource
|
from dis_snek.models.snek.command import check
|
||||||
from time import time
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord import DMChannel, User
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
import jarvis
|
|
||||||
from jarvis.config import reload_config
|
from jarvis.config import reload_config
|
||||||
from jarvis.db.models import 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.
|
J.A.R.V.I.S. management cog.
|
||||||
|
|
||||||
Used by admins to control core J.A.R.V.I.S. systems
|
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.bot = bot
|
||||||
self.admins = Config.objects(key="admins").first()
|
self.admins = Config.objects(key="admins").first()
|
||||||
|
|
||||||
@commands.command(name="load", hidden=True)
|
@message_command(name="addadmin")
|
||||||
@user_is_bot_admin()
|
@check(is_owner())
|
||||||
async def _load_cog(self, ctx: commands.Context, *, cog: str) -> None:
|
async def _add(self, ctx: MessageContext, user: User) -> 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:
|
|
||||||
if user.id in self.admins.value:
|
if user.id in self.admins.value:
|
||||||
await ctx.send(f"{user.mention} is already an admin.")
|
await ctx.send(f"{user.mention} is already an admin.")
|
||||||
return
|
return
|
||||||
|
@ -166,9 +30,9 @@ class OwnerCog(commands.Cog):
|
||||||
reload_config()
|
reload_config()
|
||||||
await ctx.send(f"{user.mention} is now an admin. Use this power carefully.")
|
await ctx.send(f"{user.mention} is now an admin. Use this power carefully.")
|
||||||
|
|
||||||
@_admin.command(name="remove", hidden=True)
|
@message_command(name="deladmin")
|
||||||
@commands.is_owner()
|
@is_owner()
|
||||||
async def _remove(self, ctx: commands.Context, user: User) -> None:
|
async def _remove(self, ctx: MessageContext, user: User) -> None:
|
||||||
if user.id not in self.admins.value:
|
if user.id not in self.admins.value:
|
||||||
await ctx.send(f"{user.mention} is not an admin.")
|
await ctx.send(f"{user.mention} is not an admin.")
|
||||||
return
|
return
|
||||||
|
@ -177,70 +41,7 @@ class OwnerCog(commands.Cog):
|
||||||
reload_config()
|
reload_config()
|
||||||
await ctx.send(f"{user.mention} is no longer an admin.")
|
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)):
|
def setup(bot: Snake) -> None:
|
||||||
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:
|
|
||||||
"""Add OwnerCog to J.A.R.V.I.S."""
|
"""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 typing import List, Optional
|
||||||
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
from discord import Embed
|
from dis_snek import InteractionContext, Snake
|
||||||
from discord.ext.commands import Bot
|
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||||
from discord.ext.tasks import loop
|
from dis_snek.models.discord.embed import Embed, EmbedField
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash.utils.manage_commands import create_option
|
OptionTypes,
|
||||||
from discord_slash.utils.manage_components import (
|
slash_command,
|
||||||
create_actionrow,
|
slash_option,
|
||||||
create_select,
|
|
||||||
create_select_option,
|
|
||||||
wait_for_component,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from jarvis.db.models import Reminder
|
from jarvis.db.models import Reminder
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
from jarvis.utils.cachecog import CacheCog
|
from jarvis.utils.cachecog import CacheCog
|
||||||
from jarvis.utils.field import Field
|
|
||||||
|
|
||||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*")
|
||||||
invites = re.compile(
|
invites = re.compile(
|
||||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,49 +28,40 @@ invites = re.compile(
|
||||||
class RemindmeCog(CacheCog):
|
class RemindmeCog(CacheCog):
|
||||||
"""J.A.R.V.I.S. Remind Me Cog."""
|
"""J.A.R.V.I.S. Remind Me Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: Bot):
|
def __init__(self, bot: Snake):
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
self._remind.start()
|
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="remindme", description="Set a reminder")
|
||||||
name="remindme",
|
@slash_option(
|
||||||
description="Set a reminder",
|
name="message",
|
||||||
options=[
|
description="What to remind you of?",
|
||||||
create_option(
|
opt_type=OptionTypes.STRING,
|
||||||
name="message",
|
required=True,
|
||||||
description="What to remind you of",
|
)
|
||||||
option_type=3,
|
@slash_option(
|
||||||
required=True,
|
name="weeks",
|
||||||
),
|
description="Number of weeks?",
|
||||||
create_option(
|
opt_type=OptionTypes.INTEGER,
|
||||||
name="weeks",
|
required=False,
|
||||||
description="Number of weeks?",
|
)
|
||||||
option_type=4,
|
@slash_option(
|
||||||
required=False,
|
name="days", description="Number of days?", opt_type=OptionTypes.INTEGER, required=False
|
||||||
),
|
)
|
||||||
create_option(
|
@slash_option(
|
||||||
name="days",
|
name="hours",
|
||||||
description="Number of days?",
|
description="Number of hours?",
|
||||||
option_type=4,
|
opt_type=OptionTypes.INTEGER,
|
||||||
required=False,
|
required=False,
|
||||||
),
|
)
|
||||||
create_option(
|
@slash_option(
|
||||||
name="hours",
|
name="minutes",
|
||||||
description="Number of hours?",
|
description="Number of minutes?",
|
||||||
option_type=4,
|
opt_type=OptionTypes.INTEGER,
|
||||||
required=False,
|
required=False,
|
||||||
),
|
|
||||||
create_option(
|
|
||||||
name="minutes",
|
|
||||||
description="Number of minutes?",
|
|
||||||
option_type=4,
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
async def _remindme(
|
async def _remindme(
|
||||||
self,
|
self,
|
||||||
ctx: SlashContext,
|
ctx: InteractionContext,
|
||||||
message: Optional[str] = None,
|
message: Optional[str] = None,
|
||||||
weeks: Optional[int] = 0,
|
weeks: Optional[int] = 0,
|
||||||
days: Optional[int] = 0,
|
days: Optional[int] = 0,
|
||||||
|
@ -82,20 +69,20 @@ class RemindmeCog(CacheCog):
|
||||||
minutes: Optional[int] = 0,
|
minutes: Optional[int] = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
if len(message) > 100:
|
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
|
return
|
||||||
elif invites.search(message):
|
elif invites.search(message):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Listen, don't use this to try and bypass the rules",
|
"Listen, don't use this to try and bypass the rules",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
elif not valid.fullmatch(message):
|
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
|
return
|
||||||
|
|
||||||
if not any([weeks, days, hours, minutes]):
|
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
|
return
|
||||||
|
|
||||||
weeks = abs(weeks)
|
weeks = abs(weeks)
|
||||||
|
@ -104,19 +91,19 @@ class RemindmeCog(CacheCog):
|
||||||
minutes = abs(minutes)
|
minutes = abs(minutes)
|
||||||
|
|
||||||
if weeks and weeks > 4:
|
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
|
return
|
||||||
|
|
||||||
elif days and days > 6:
|
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
|
return
|
||||||
|
|
||||||
elif hours and hours > 23:
|
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
|
return
|
||||||
|
|
||||||
elif minutes and minutes > 59:
|
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
|
return
|
||||||
|
|
||||||
reminders = Reminder.objects(user=ctx.author.id, active=True).count()
|
reminders = Reminder.objects(user=ctx.author.id, active=True).count()
|
||||||
|
@ -124,7 +111,7 @@ class RemindmeCog(CacheCog):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"You already have 5 (or more) active reminders. "
|
"You already have 5 (or more) active reminders. "
|
||||||
"Please either remove an old one, or wait for one to pass",
|
"Please either remove an old one, or wait for one to pass",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -148,8 +135,8 @@ class RemindmeCog(CacheCog):
|
||||||
title="Reminder Set",
|
title="Reminder Set",
|
||||||
description=f"{ctx.author.mention} set a reminder",
|
description=f"{ctx.author.mention} set a reminder",
|
||||||
fields=[
|
fields=[
|
||||||
Field(name="Message", value=message),
|
EmbedField(name="Message", value=message),
|
||||||
Field(
|
EmbedField(
|
||||||
name="When",
|
name="When",
|
||||||
value=remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
value=remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -158,19 +145,21 @@ class RemindmeCog(CacheCog):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||||
icon_url=ctx.author.avatar_url,
|
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)
|
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."""
|
"""Build embed for paginator."""
|
||||||
fields = []
|
fields = []
|
||||||
for reminder in reminders:
|
for reminder in reminders:
|
||||||
fields.append(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||||
value=f"{reminder.message}\n\u200b",
|
value=f"{reminder.message}\n\u200b",
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -184,57 +173,49 @@ class RemindmeCog(CacheCog):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||||
icon_url=ctx.author.avatar_url,
|
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
|
return embed
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="reminders", sub_cmd_name="list", sub_cmd_description="List reminders")
|
||||||
base="reminders",
|
async def _list(self, ctx: InteractionContext) -> None:
|
||||||
name="list",
|
|
||||||
description="List reminders for a user",
|
|
||||||
)
|
|
||||||
async def _list(self, ctx: SlashContext) -> None:
|
|
||||||
exists = self.check_cache(ctx)
|
exists = self.check_cache(ctx)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.defer(hidden=True)
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
f"Please use existing interaction: {exists['paginator']._message.jump_url}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
reminders = Reminder.objects(user=ctx.author.id, active=True)
|
reminders = Reminder.objects(user=ctx.author.id, active=True)
|
||||||
if not reminders:
|
if not reminders:
|
||||||
await ctx.send("You have no reminders set.", hidden=True)
|
await ctx.send("You have no reminders set.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
embed = await self.get_reminders_embed(ctx, reminders)
|
embed = await self.get_reminders_embed(ctx, reminders)
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder")
|
||||||
base="reminders",
|
async def _delete(self, ctx: InteractionContext) -> None:
|
||||||
name="delete",
|
|
||||||
description="Delete a reminder",
|
|
||||||
)
|
|
||||||
async def _delete(self, ctx: SlashContext) -> None:
|
|
||||||
reminders = Reminder.objects(user=ctx.author.id, active=True)
|
reminders = Reminder.objects(user=ctx.author.id, active=True)
|
||||||
if not reminders:
|
if not reminders:
|
||||||
await ctx.send("You have no reminders set", hidden=True)
|
await ctx.send("You have no reminders set", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
for reminder in reminders:
|
for reminder in reminders:
|
||||||
option = create_select_option(
|
option = SelectOption(
|
||||||
label=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
label=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||||
value=str(reminder.id),
|
value=str(reminder.id),
|
||||||
emoji="⏰",
|
emoji="⏰",
|
||||||
)
|
)
|
||||||
options.append(option)
|
options.append(option)
|
||||||
|
|
||||||
select = create_select(
|
select = Select(
|
||||||
options=options,
|
options=options,
|
||||||
custom_id="to_delete",
|
custom_id="to_delete",
|
||||||
placeholder="Select reminders to delete",
|
placeholder="Select reminders to delete",
|
||||||
|
@ -242,7 +223,7 @@ class RemindmeCog(CacheCog):
|
||||||
max_values=len(reminders),
|
max_values=len(reminders),
|
||||||
)
|
)
|
||||||
|
|
||||||
components = [create_actionrow(select)]
|
components = [ActionRow(select)]
|
||||||
embed = await self.get_reminders_embed(ctx, reminders)
|
embed = await self.get_reminders_embed(ctx, reminders)
|
||||||
message = await ctx.send(
|
message = await ctx.send(
|
||||||
content=f"You have {len(reminders)} reminder(s) set:",
|
content=f"You have {len(reminders)} reminder(s) set:",
|
||||||
|
@ -251,23 +232,22 @@ class RemindmeCog(CacheCog):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
context = await wait_for_component(
|
context = await self.bot.wait_for_component(
|
||||||
self.bot,
|
check=lambda x: ctx.author.id == x.context.author.id,
|
||||||
check=lambda x: ctx.author.id == x.author_id,
|
|
||||||
messages=message,
|
messages=message,
|
||||||
timeout=60 * 5,
|
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()
|
_ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete()
|
||||||
|
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
|
|
||||||
fields = []
|
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(
|
fields.append(
|
||||||
Field(
|
EmbedField(
|
||||||
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"),
|
||||||
value=reminder.message,
|
value=reminder.message,
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -280,52 +260,23 @@ class RemindmeCog(CacheCog):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||||
icon_url=ctx.author.avatar_url,
|
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(
|
await context.context.edit_origin(
|
||||||
content=f"Deleted {len(context.selected_options)} reminder(s)",
|
content=f"Deleted {len(context.context.values)} reminder(s)",
|
||||||
components=components,
|
components=components,
|
||||||
embed=embed,
|
embed=embed,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
await message.edit(components=components)
|
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: Snake) -> None:
|
||||||
def setup(bot: Bot) -> None:
|
|
||||||
"""Add RemindmeCog to J.A.R.V.I.S."""
|
"""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."""
|
"""J.A.R.V.I.S. Role Giver Cog."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from discord import Role
|
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord_slash.utils.manage_commands import create_option
|
from dis_snek.models.discord.role import Role
|
||||||
from discord_slash.utils.manage_components import (
|
from dis_snek.models.snek.application_commands import (
|
||||||
create_actionrow,
|
OptionTypes,
|
||||||
create_select,
|
slash_command,
|
||||||
create_select_option,
|
slash_option,
|
||||||
wait_for_component,
|
|
||||||
)
|
)
|
||||||
|
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.db.models import Rolegiver
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed, get
|
||||||
from jarvis.utils.field import Field
|
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
|
|
||||||
class RolegiverCog(commands.Cog):
|
class RolegiverCog(Scale):
|
||||||
"""J.A.R.V.I.S. Role Giver Cog."""
|
"""J.A.R.V.I.S. Role Giver Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="rolegiver",
|
name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver"
|
||||||
name="add",
|
|
||||||
description="Add a role to rolegiver",
|
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="role",
|
|
||||||
description="Role to add",
|
|
||||||
option_type=8,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
|
||||||
async def _rolegiver_add(self, ctx: SlashContext, role: Role) -> None:
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
|
async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None:
|
||||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||||
if setting and role.id in setting.roles:
|
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
|
return
|
||||||
|
|
||||||
if not setting:
|
if not setting:
|
||||||
setting = Rolegiver(guild=ctx.guild.id, roles=[])
|
setting = Rolegiver(guild=ctx.guild.id, roles=[])
|
||||||
|
|
||||||
if len(setting.roles) >= 20:
|
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
|
return
|
||||||
|
|
||||||
setting.roles.append(role.id)
|
setting.roles.append(role.id)
|
||||||
|
@ -58,7 +49,7 @@ class RolegiverCog(commands.Cog):
|
||||||
for role_id in setting.roles:
|
for role_id in setting.roles:
|
||||||
if role_id == role.id:
|
if role_id == role.id:
|
||||||
continue
|
continue
|
||||||
e_role = ctx.guild.get_role(role_id)
|
e_role = await ctx.guild.get_role(role_id)
|
||||||
if not e_role:
|
if not e_role:
|
||||||
continue
|
continue
|
||||||
roles.append(e_role)
|
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"
|
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
||||||
fields = [
|
fields = [
|
||||||
Field(name="New Role", value=f"{role.mention}"),
|
EmbedField(name="New Role", value=f"{role.mention}"),
|
||||||
Field(name="Existing Role(s)", value=value),
|
EmbedField(name="Existing Role(s)", value=value),
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -77,61 +68,60 @@ class RolegiverCog(commands.Cog):
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
embed.set_author(
|
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
|
||||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
|
||||||
icon_url=ctx.author.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)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="rolegiver",
|
name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver"
|
||||||
name="remove",
|
|
||||||
description="Remove a role from rolegiver",
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _rolegiver_remove(self, ctx: SlashContext) -> None:
|
async def _rolegiver_remove(self, ctx: InteractionContext) -> None:
|
||||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||||
if not setting or (setting and not setting.roles):
|
if not setting or (setting and not setting.roles):
|
||||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
for role in setting.roles:
|
for role in setting.roles:
|
||||||
role: Role = ctx.guild.get_role(role)
|
role: Role = await ctx.guild.get_role(role)
|
||||||
option = create_select_option(label=role.name, value=str(role.id))
|
option = SelectOption(label=role.name, value=str(role.id))
|
||||||
options.append(option)
|
options.append(option)
|
||||||
|
|
||||||
select = create_select(
|
select = Select(
|
||||||
options=options,
|
options=options,
|
||||||
custom_id="to_delete",
|
custom_id="to_delete",
|
||||||
placeholder="Select roles to remove",
|
placeholder="Select roles to remove",
|
||||||
min_values=1,
|
min_values=1,
|
||||||
max_values=len(options),
|
max_values=len(options),
|
||||||
)
|
)
|
||||||
components = [create_actionrow(select)]
|
components = [ActionRow(select)]
|
||||||
|
|
||||||
message = await ctx.send(content="\u200b", components=components)
|
message = await ctx.send(content="\u200b", components=components)
|
||||||
try:
|
try:
|
||||||
context = await wait_for_component(
|
context = await self.bot.wait_for_component(
|
||||||
self.bot,
|
check=lambda x: ctx.author.id == x.context.author.id,
|
||||||
check=lambda x: ctx.author.id == x.author.id,
|
messages=message,
|
||||||
message=message,
|
|
||||||
timeout=60 * 1,
|
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.roles.remove(int(to_delete))
|
||||||
setting.save()
|
setting.save()
|
||||||
|
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
|
|
||||||
roles = []
|
roles = []
|
||||||
for role_id in setting.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:
|
if not e_role:
|
||||||
continue
|
continue
|
||||||
roles.append(e_role)
|
roles.append(e_role)
|
||||||
|
@ -140,9 +130,10 @@ class RolegiverCog(commands.Cog):
|
||||||
roles.sort(key=lambda x: -x.position)
|
roles.sort(key=lambda x: -x.position)
|
||||||
|
|
||||||
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
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 = [
|
fields = [
|
||||||
Field(name="Removed Role", value=f"{role.mention}"),
|
EmbedField(name="Removed Role(s)", value=rvalue),
|
||||||
Field(name="Remaining Role(s)", value=value),
|
EmbedField(name="Remaining Role(s)", value=value),
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -151,39 +142,34 @@ class RolegiverCog(commands.Cog):
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
embed.set_author(
|
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
|
||||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
|
||||||
icon_url=ctx.author.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.context.edit_origin(
|
||||||
|
content=f"Removed {len(context.context.values)} role(s)",
|
||||||
await context.edit_origin(
|
|
||||||
content=f"Removed {len(context.selected_options)} role(s)",
|
|
||||||
embed=embed,
|
embed=embed,
|
||||||
components=components,
|
components=components,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
await message.edit(components=components)
|
await message.edit(components=components)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="rolegiver", sub_cmd_name="list", description="List rolegiver roles")
|
||||||
base="rolegiver",
|
async def _rolegiver_list(self, ctx: InteractionContext) -> None:
|
||||||
name="list",
|
|
||||||
description="List roles rolegiver",
|
|
||||||
)
|
|
||||||
async def _rolegiver_list(self, ctx: SlashContext) -> None:
|
|
||||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||||
if not setting or (setting and not setting.roles):
|
if not setting or (setting and not setting.roles):
|
||||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
roles = []
|
roles = []
|
||||||
for role_id in setting.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:
|
if not e_role:
|
||||||
continue
|
continue
|
||||||
roles.append(e_role)
|
roles.append(e_role)
|
||||||
|
@ -199,59 +185,52 @@ class RolegiverCog(commands.Cog):
|
||||||
fields=[],
|
fields=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
name=ctx.author.display_name,
|
||||||
icon_url=ctx.author.avatar_url,
|
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)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="role", sub_cmd_name="get", sub_cmd_description="Get a role")
|
||||||
base="role",
|
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
||||||
name="get",
|
async def _role_get(self, ctx: InteractionContext) -> None:
|
||||||
description="Get a role from rolegiver",
|
|
||||||
)
|
|
||||||
@commands.cooldown(1, 10, commands.BucketType.user)
|
|
||||||
async def _role_get(self, ctx: SlashContext) -> None:
|
|
||||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||||
if not setting or (setting and not setting.roles):
|
if not setting or (setting and not setting.roles):
|
||||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
for role in setting.roles:
|
for role in setting.roles:
|
||||||
role: Role = ctx.guild.get_role(role)
|
role: Role = await ctx.guild.get_role(role)
|
||||||
option = create_select_option(label=role.name, value=str(role.id))
|
option = SelectOption(label=role.name, value=str(role.id))
|
||||||
options.append(option)
|
options.append(option)
|
||||||
|
|
||||||
select = create_select(
|
select = Select(
|
||||||
options=options,
|
options=options,
|
||||||
custom_id="to_delete",
|
|
||||||
placeholder="Select roles to add",
|
placeholder="Select roles to add",
|
||||||
min_values=1,
|
min_values=1,
|
||||||
max_values=len(options),
|
max_values=len(options),
|
||||||
)
|
)
|
||||||
components = [create_actionrow(select)]
|
components = [ActionRow(select)]
|
||||||
|
|
||||||
message = await ctx.send(content="\u200b", components=components)
|
message = await ctx.send(content="\u200b", components=components)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
context = await self.bot.wait_for_component(
|
||||||
context = await wait_for_component(
|
check=lambda x: ctx.author.id == x.context.author.id,
|
||||||
self.bot,
|
|
||||||
check=lambda x: ctx.author.id == x.author.id,
|
|
||||||
messages=message,
|
messages=message,
|
||||||
timeout=60 * 5,
|
timeout=60 * 5,
|
||||||
)
|
)
|
||||||
|
|
||||||
added_roles = []
|
added_roles = []
|
||||||
for role in context.selected_options:
|
for role in context.context.values:
|
||||||
role = ctx.guild.get_role(int(role))
|
role = await ctx.guild.get_role(int(role))
|
||||||
added_roles.append(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
|
roles = ctx.author.roles
|
||||||
if 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"
|
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"
|
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
||||||
fields = [
|
fields = [
|
||||||
Field(name="Added Role(s)", value=avalue),
|
EmbedField(name="Added Role(s)", value=avalue),
|
||||||
Field(name="Prior Role(s)", value=value),
|
EmbedField(name="Prior Role(s)", value=value),
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="User Given Role",
|
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,
|
fields=fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
name=ctx.author.display_name,
|
||||||
icon_url=ctx.author.avatar_url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}")
|
embed.set_footer(
|
||||||
for row in components:
|
text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}"
|
||||||
for component in row["components"]:
|
)
|
||||||
component["disabled"] = True
|
|
||||||
|
|
||||||
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:
|
except asyncio.TimeoutError:
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
await message.edit(components=components)
|
await message.edit(components=components)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="role", sub_cmd_name="remove", sub_cmd_description="Remove a role")
|
||||||
base="role",
|
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
||||||
name="forfeit",
|
async def _role_remove(self, ctx: InteractionContext) -> None:
|
||||||
description="Have rolegiver take away role",
|
user_roles = ctx.author.roles
|
||||||
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:
|
|
||||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||||
if not setting or (setting and not setting.roles):
|
if not setting or (setting and not setting.roles):
|
||||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||||
return
|
return
|
||||||
elif role.id not in setting.roles:
|
elif not any(x.id in setting.roles for x in user_roles):
|
||||||
await ctx.send("Role not in rolegiver", hidden=True)
|
await ctx.send("You have no rolegiver roles", ephemeral=True)
|
||||||
return
|
|
||||||
elif role not in ctx.author.roles:
|
|
||||||
await ctx.send("You do not have that role", hidden=True)
|
|
||||||
return
|
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
|
select = Select(
|
||||||
if roles:
|
options=options,
|
||||||
roles.sort(key=lambda x: -x.position)
|
custom_id="to_remove",
|
||||||
_ = roles.pop(-1)
|
placeholder="Select roles to remove",
|
||||||
|
min_values=1,
|
||||||
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
max_values=len(options),
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
components = [ActionRow(select)]
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
message = await ctx.send(content="\u200b", components=components)
|
||||||
embed.set_author(
|
|
||||||
name=ctx.author.nick if ctx.author.nick else ctx.author.name,
|
|
||||||
icon_url=ctx.author.avatar_url,
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
user_roles.sort(key=lambda x: -x.position)
|
||||||
base="rolegiver",
|
_ = user_roles.pop(-1)
|
||||||
name="cleanup",
|
|
||||||
description="Cleanup rolegiver roles",
|
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)
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _rolegiver_cleanup(self, ctx: SlashContext) -> None:
|
async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None:
|
||||||
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
setting = Rolegiver.objects(guild=ctx.guild.id).first()
|
||||||
if not setting or not setting.roles:
|
if not setting or not setting.roles:
|
||||||
await ctx.send("Rolegiver has no roles", hidden=True)
|
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||||
guild_roles = await ctx.guild.fetch_roles()
|
guild_role_ids = [r.id for r in ctx.guild.roles]
|
||||||
guild_role_ids = [x.id for x in guild_roles]
|
|
||||||
for role_id in setting.roles:
|
for role_id in setting.roles:
|
||||||
if role_id not in guild_role_ids:
|
if role_id not in guild_role_ids:
|
||||||
setting.roles.remove(role_id)
|
setting.roles.remove(role_id)
|
||||||
|
@ -364,6 +367,6 @@ class RolegiverCog(commands.Cog):
|
||||||
await ctx.send("Rolegiver cleanup finished")
|
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."""
|
"""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."""
|
"""J.A.R.V.I.S. Settings Management Cog."""
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from dis_snek.models.snek.command import check
|
||||||
from discord import Role, TextChannel
|
from discord import Role, TextChannel
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.utils import find
|
from discord.utils import find
|
||||||
|
@ -33,46 +34,24 @@ class SettingsCog(commands.Cog):
|
||||||
"""Delete a guild setting."""
|
"""Delete a guild setting."""
|
||||||
return Setting.objects(setting=setting, guild=guild).delete()
|
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(
|
@cog_ext.cog_subcommand(
|
||||||
base="settings",
|
base="settings",
|
||||||
subcommand_group="set",
|
subcommand_group="set",
|
||||||
name="modlog",
|
name="modlog",
|
||||||
description="Set modlog channel",
|
description="Set modlog channel",
|
||||||
options=[
|
choices=[
|
||||||
create_option(
|
create_option(
|
||||||
name="channel",
|
name="channel",
|
||||||
description="Modlog channel",
|
description="Modlog channel",
|
||||||
option_type=7,
|
opt_type=7,
|
||||||
required=True,
|
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:
|
async def _set_modlog(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||||
if not isinstance(channel, TextChannel):
|
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
|
return
|
||||||
self.update_settings("modlog", channel.id, ctx.guild.id)
|
self.update_settings("modlog", channel.id, ctx.guild.id)
|
||||||
await ctx.send(f"Settings applied. New modlog channel is {channel.mention}")
|
await ctx.send(f"Settings applied. New modlog channel is {channel.mention}")
|
||||||
|
@ -82,19 +61,19 @@ class SettingsCog(commands.Cog):
|
||||||
subcommand_group="set",
|
subcommand_group="set",
|
||||||
name="userlog",
|
name="userlog",
|
||||||
description="Set userlog channel",
|
description="Set userlog channel",
|
||||||
options=[
|
choices=[
|
||||||
create_option(
|
create_option(
|
||||||
name="channel",
|
name="channel",
|
||||||
description="Userlog channel",
|
description="Userlog channel",
|
||||||
option_type=7,
|
opt_type=7,
|
||||||
required=True,
|
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:
|
async def _set_userlog(self, ctx: SlashContext, channel: TextChannel) -> None:
|
||||||
if not isinstance(channel, TextChannel):
|
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
|
return
|
||||||
self.update_settings("userlog", channel.id, ctx.guild.id)
|
self.update_settings("userlog", channel.id, ctx.guild.id)
|
||||||
await ctx.send(f"Settings applied. New userlog channel is {channel.mention}")
|
await ctx.send(f"Settings applied. New userlog channel is {channel.mention}")
|
||||||
|
@ -104,16 +83,16 @@ class SettingsCog(commands.Cog):
|
||||||
subcommand_group="set",
|
subcommand_group="set",
|
||||||
name="massmention",
|
name="massmention",
|
||||||
description="Set massmention amount",
|
description="Set massmention amount",
|
||||||
options=[
|
choices=[
|
||||||
create_option(
|
create_option(
|
||||||
name="amount",
|
name="amount",
|
||||||
description="Amount of mentions (0 to disable)",
|
description="Amount of mentions (0 to disable)",
|
||||||
option_type=4,
|
opt_type=4,
|
||||||
required=True,
|
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:
|
async def _set_massmention(self, ctx: SlashContext, amount: int) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
self.update_settings("massmention", amount, ctx.guild.id)
|
self.update_settings("massmention", amount, ctx.guild.id)
|
||||||
|
@ -124,16 +103,16 @@ class SettingsCog(commands.Cog):
|
||||||
subcommand_group="set",
|
subcommand_group="set",
|
||||||
name="verified",
|
name="verified",
|
||||||
description="Set verified role",
|
description="Set verified role",
|
||||||
options=[
|
choices=[
|
||||||
create_option(
|
create_option(
|
||||||
name="role",
|
name="role",
|
||||||
description="verified role",
|
description="verified role",
|
||||||
option_type=8,
|
opt_type=8,
|
||||||
required=True,
|
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:
|
async def _set_verified(self, ctx: SlashContext, role: Role) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
self.update_settings("verified", role.id, ctx.guild.id)
|
self.update_settings("verified", role.id, ctx.guild.id)
|
||||||
|
@ -144,16 +123,16 @@ class SettingsCog(commands.Cog):
|
||||||
subcommand_group="set",
|
subcommand_group="set",
|
||||||
name="unverified",
|
name="unverified",
|
||||||
description="Set unverified role",
|
description="Set unverified role",
|
||||||
options=[
|
choices=[
|
||||||
create_option(
|
create_option(
|
||||||
name="role",
|
name="role",
|
||||||
description="Unverified role",
|
description="Unverified role",
|
||||||
option_type=8,
|
opt_type=8,
|
||||||
required=True,
|
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:
|
async def _set_unverified(self, ctx: SlashContext, role: Role) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
self.update_settings("unverified", role.id, ctx.guild.id)
|
self.update_settings("unverified", role.id, ctx.guild.id)
|
||||||
|
@ -164,41 +143,28 @@ class SettingsCog(commands.Cog):
|
||||||
subcommand_group="set",
|
subcommand_group="set",
|
||||||
name="noinvite",
|
name="noinvite",
|
||||||
description="Set if invite deletion should happen",
|
description="Set if invite deletion should happen",
|
||||||
options=[
|
choices=[
|
||||||
create_option(
|
create_option(
|
||||||
name="active",
|
name="active",
|
||||||
description="Active?",
|
description="Active?",
|
||||||
option_type=4,
|
opt_type=4,
|
||||||
required=True,
|
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:
|
async def _set_invitedel(self, ctx: SlashContext, active: int) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
self.update_settings("noinvite", bool(active), ctx.guild.id)
|
self.update_settings("noinvite", bool(active), ctx.guild.id)
|
||||||
await ctx.send(f"Settings applied. Automatic invite active: {bool(active)}")
|
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(
|
@cog_ext.cog_subcommand(
|
||||||
base="settings",
|
base="settings",
|
||||||
subcommand_group="unset",
|
subcommand_group="unset",
|
||||||
name="modlog",
|
name="modlog",
|
||||||
description="Unset modlog channel",
|
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:
|
async def _unset_modlog(self, ctx: SlashContext) -> None:
|
||||||
self.delete_settings("modlog", ctx.guild.id)
|
self.delete_settings("modlog", ctx.guild.id)
|
||||||
await ctx.send("Setting removed.")
|
await ctx.send("Setting removed.")
|
||||||
|
@ -209,7 +175,7 @@ class SettingsCog(commands.Cog):
|
||||||
name="userlog",
|
name="userlog",
|
||||||
description="Unset userlog channel",
|
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:
|
async def _unset_userlog(self, ctx: SlashContext) -> None:
|
||||||
self.delete_settings("userlog", ctx.guild.id)
|
self.delete_settings("userlog", ctx.guild.id)
|
||||||
await ctx.send("Setting removed.")
|
await ctx.send("Setting removed.")
|
||||||
|
@ -220,7 +186,7 @@ class SettingsCog(commands.Cog):
|
||||||
name="massmention",
|
name="massmention",
|
||||||
description="Unet massmention amount",
|
description="Unet massmention amount",
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(manage_guild=True))
|
||||||
async def _massmention(self, ctx: SlashContext) -> None:
|
async def _massmention(self, ctx: SlashContext) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
self.delete_settings("massmention", ctx.guild.id)
|
self.delete_settings("massmention", ctx.guild.id)
|
||||||
|
@ -232,7 +198,7 @@ class SettingsCog(commands.Cog):
|
||||||
name="verified",
|
name="verified",
|
||||||
description="Unset verified role",
|
description="Unset verified role",
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(manage_guild=True))
|
||||||
async def _verified(self, ctx: SlashContext) -> None:
|
async def _verified(self, ctx: SlashContext) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
self.delete_settings("verified", ctx.guild.id)
|
self.delete_settings("verified", ctx.guild.id)
|
||||||
|
@ -244,14 +210,14 @@ class SettingsCog(commands.Cog):
|
||||||
name="unverified",
|
name="unverified",
|
||||||
description="Unset unverified role",
|
description="Unset unverified role",
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(manage_guild=True))
|
||||||
async def _unverified(self, ctx: SlashContext) -> None:
|
async def _unverified(self, ctx: SlashContext) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
self.delete_settings("unverified", ctx.guild.id)
|
self.delete_settings("unverified", ctx.guild.id)
|
||||||
await ctx.send("Setting removed.")
|
await ctx.send("Setting removed.")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(base="settings", name="view", description="View settings")
|
@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:
|
async def _view(self, ctx: SlashContext) -> None:
|
||||||
settings = Setting.objects(guild=ctx.guild.id)
|
settings = Setting.objects(guild=ctx.guild.id)
|
||||||
|
|
||||||
|
@ -272,7 +238,7 @@ class SettingsCog(commands.Cog):
|
||||||
value = "||`[redacted]`||"
|
value = "||`[redacted]`||"
|
||||||
elif setting.setting == "rolegiver":
|
elif setting.setting == "rolegiver":
|
||||||
value = ""
|
value = ""
|
||||||
for role in setting.value:
|
for _role in setting.value:
|
||||||
nvalue = find(lambda x: x.id == value, ctx.guild.roles)
|
nvalue = find(lambda x: x.id == value, ctx.guild.roles)
|
||||||
if value:
|
if value:
|
||||||
value += "\n" + nvalue.mention
|
value += "\n" + nvalue.mention
|
||||||
|
@ -285,7 +251,7 @@ class SettingsCog(commands.Cog):
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(base="settings", name="clear", description="Clear all settings")
|
@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:
|
async def _clear(self, ctx: SlashContext) -> None:
|
||||||
deleted = Setting.objects(guild=ctx.guild.id).delete()
|
deleted = Setting.objects(guild=ctx.guild.id).delete()
|
||||||
await ctx.send(f"Guild settings cleared: `{deleted is not None}`")
|
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:
|
def setup(bot: commands.Bot) -> None:
|
||||||
"""Add SettingsCog to J.A.R.V.I.S."""
|
"""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."""
|
"""J.A.R.V.I.S. Starboard Cog."""
|
||||||
from discord import TextChannel
|
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.channel import GuildText
|
||||||
from discord.utils import find
|
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.message import Message
|
||||||
from discord_slash.context import MenuContext
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash.model import ContextMenuType, SlashMessage
|
CommandTypes,
|
||||||
from discord_slash.utils.manage_commands import create_option
|
OptionTypes,
|
||||||
from discord_slash.utils.manage_components import (
|
context_menu,
|
||||||
create_actionrow,
|
slash_command,
|
||||||
create_select,
|
slash_option,
|
||||||
create_select_option,
|
|
||||||
wait_for_component,
|
|
||||||
)
|
)
|
||||||
|
from dis_snek.models.snek.command import check
|
||||||
|
|
||||||
from jarvis.db.models import Star, Starboard
|
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
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
supported_images = [
|
supported_images = [
|
||||||
|
@ -26,19 +25,15 @@ supported_images = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StarboardCog(commands.Cog):
|
class StarboardCog(Scale):
|
||||||
"""J.A.R.V.I.S. Starboard Cog."""
|
"""J.A.R.V.I.S. Starboard Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards")
|
||||||
base="starboard",
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
name="list",
|
async def _list(self, ctx: InteractionContext) -> None:
|
||||||
description="Lists all Starboards",
|
|
||||||
)
|
|
||||||
@admin_or_permissions(manage_guild=True)
|
|
||||||
async def _list(self, ctx: SlashContext) -> None:
|
|
||||||
starboards = Starboard.objects(guild=ctx.guild.id)
|
starboards = Starboard.objects(guild=ctx.guild.id)
|
||||||
if starboards != []:
|
if starboards != []:
|
||||||
message = "Available Starboards:\n"
|
message = "Available Starboards:\n"
|
||||||
|
@ -48,39 +43,35 @@ class StarboardCog(commands.Cog):
|
||||||
else:
|
else:
|
||||||
await ctx.send("No Starboards available.")
|
await ctx.send("No Starboards available.")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="starboard",
|
name="starboard", sub_cmd_name="create", sub_cmd_description="Create a starboard"
|
||||||
name="create",
|
|
||||||
description="Create a starboard",
|
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="channel",
|
|
||||||
description="Starboard channel",
|
|
||||||
option_type=7,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _create(self, ctx: SlashContext, channel: TextChannel) -> None:
|
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:
|
if channel not in ctx.guild.channels:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Channel not in guild. Choose an existing channel.",
|
"Channel not in guild. Choose an existing channel.",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if not isinstance(channel, TextChannel):
|
if not isinstance(channel, GuildText):
|
||||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
await ctx.send("Channel must be a GuildText", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first()
|
exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first()
|
||||||
if exists:
|
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
|
return
|
||||||
|
|
||||||
count = Starboard.objects(guild=ctx.guild.id).count()
|
count = Starboard.objects(guild=ctx.guild.id).count()
|
||||||
if count >= 25:
|
if count >= 25:
|
||||||
await ctx.send("25 starboard limit reached", hidden=True)
|
await ctx.send("25 starboard limit reached", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
_ = Starboard(
|
_ = Starboard(
|
||||||
|
@ -90,95 +81,89 @@ class StarboardCog(commands.Cog):
|
||||||
).save()
|
).save()
|
||||||
await ctx.send(f"Starboard created. Check it out at {channel.mention}.")
|
await ctx.send(f"Starboard created. Check it out at {channel.mention}.")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="starboard",
|
name="starboard", sub_cmd_name="delete", sub_cmd_description="Delete a starboard"
|
||||||
name="delete",
|
|
||||||
description="Delete a starboard",
|
|
||||||
options=[
|
|
||||||
create_option(
|
|
||||||
name="channel",
|
|
||||||
description="Starboard channel",
|
|
||||||
option_type=7,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@slash_option(
|
||||||
async def _delete(self, ctx: SlashContext, channel: TextChannel) -> None:
|
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()
|
deleted = Starboard.objects(channel=channel.id, guild=ctx.guild.id).delete()
|
||||||
if deleted:
|
if deleted:
|
||||||
_ = Star.objects(starboard=channel.id).delete()
|
_ = 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:
|
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)
|
@context_menu(name="Star Message", context_type=CommandTypes.MESSAGE)
|
||||||
async def _star_message(self, ctx: MenuContext) -> None:
|
async def _star_message(self, ctx: InteractionContext) -> None:
|
||||||
await self._star_add.invoke(ctx, ctx.target_message)
|
await self._star_add._can_run(ctx)
|
||||||
|
await self._star_add.callback(ctx, message=str(ctx.target_id))
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="star", sub_cmd_name="add", description="Star a message")
|
||||||
base="star",
|
@slash_option(
|
||||||
name="add",
|
name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@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(
|
async def _star_add(
|
||||||
self,
|
self,
|
||||||
ctx: SlashContext,
|
ctx: InteractionContext,
|
||||||
message: str,
|
message: str,
|
||||||
channel: TextChannel = None,
|
channel: GuildText = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not channel:
|
if not channel:
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
starboards = Starboard.objects(guild=ctx.guild.id)
|
starboards = Starboard.objects(guild=ctx.guild.id)
|
||||||
if not starboards:
|
if not starboards:
|
||||||
await ctx.send("No starboards exist.", hidden=True)
|
await ctx.send("No starboards exist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
await ctx.defer()
|
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 = []
|
channel_list = []
|
||||||
for starboard in starboards:
|
for starboard in starboards:
|
||||||
channel_list.append(find(lambda x: x.id == starboard.channel, ctx.guild.channels))
|
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,
|
options=select_channels,
|
||||||
min_values=1,
|
min_values=1,
|
||||||
max_values=1,
|
max_values=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
components = [create_actionrow(select)]
|
components = [ActionRow(select)]
|
||||||
|
|
||||||
msg = await ctx.send(content="Choose a starboard", components=components)
|
msg = await ctx.send(content="Choose a starboard", components=components)
|
||||||
|
|
||||||
com_ctx = await wait_for_component(
|
com_ctx = await self.bot.wait_for_component(
|
||||||
self.bot,
|
|
||||||
messages=msg,
|
messages=msg,
|
||||||
components=components,
|
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])]
|
starboard = channel_list[int(com_ctx.context.values[0])]
|
||||||
|
|
||||||
if not isinstance(message, SlashMessage):
|
|
||||||
if message.startswith("https://"):
|
|
||||||
message = message.split("/")[-1]
|
|
||||||
message = await channel.fetch_message(message)
|
|
||||||
|
|
||||||
exists = Star.objects(
|
exists = Star.objects(
|
||||||
message=message.id,
|
message=message.id,
|
||||||
|
@ -190,7 +175,7 @@ class StarboardCog(commands.Cog):
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Message already sent to Starboard {starboard.mention}",
|
f"Message already sent to Starboard {starboard.mention}",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -215,9 +200,9 @@ class StarboardCog(commands.Cog):
|
||||||
timestamp=message.created_at,
|
timestamp=message.created_at,
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=message.author.name,
|
name=message.author.display_name,
|
||||||
url=message.jump_url,
|
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)
|
embed.set_footer(text=message.guild.name + " | " + message.channel.name)
|
||||||
if image_url:
|
if image_url:
|
||||||
|
@ -236,47 +221,38 @@ class StarboardCog(commands.Cog):
|
||||||
active=True,
|
active=True,
|
||||||
).save()
|
).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}",
|
content=f"Message saved to Starboard.\nSee it in {starboard.mention}",
|
||||||
components=components,
|
components=components,
|
||||||
)
|
)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message")
|
||||||
base="star",
|
@slash_option(
|
||||||
name="delete",
|
name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@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(
|
async def _star_delete(
|
||||||
self,
|
self,
|
||||||
ctx: SlashContext,
|
ctx: InteractionContext,
|
||||||
id: int,
|
id: int,
|
||||||
starboard: TextChannel,
|
starboard: GuildText,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not isinstance(starboard, TextChannel):
|
if not isinstance(starboard, GuildText):
|
||||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
await ctx.send("Channel must be a GuildText channel", ephemeral=True)
|
||||||
return
|
return
|
||||||
exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first()
|
exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first()
|
||||||
if not exists:
|
if not exists:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"Starboard does not exist in {starboard.mention}. Please create it first",
|
f"Starboard does not exist in {starboard.mention}. Please create it first",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -287,19 +263,19 @@ class StarboardCog(commands.Cog):
|
||||||
active=True,
|
active=True,
|
||||||
).first()
|
).first()
|
||||||
if not star:
|
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
|
return
|
||||||
|
|
||||||
message = await starboard.fetch_message(star.star)
|
message = await starboard.get_message(star.star)
|
||||||
if message:
|
if message:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
|
|
||||||
star.active = False
|
star.active = False
|
||||||
star.save()
|
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."""
|
"""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."""
|
"""J.A.R.V.I.S. Twitter Cog."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
|
||||||
|
|
||||||
import tweepy
|
import tweepy
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
from discord import TextChannel
|
from dis_snek import InteractionContext, Permissions, Scale, Snake
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.channel import GuildText
|
||||||
from discord.ext.tasks import loop
|
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
|
||||||
from discord.utils import find
|
from dis_snek.models.snek.application_commands import (
|
||||||
from discord_slash import SlashContext, cog_ext
|
OptionTypes,
|
||||||
from discord_slash.model import SlashCommandOptionType as COptionType
|
SlashCommandChoice,
|
||||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
slash_command,
|
||||||
from discord_slash.utils.manage_components import (
|
slash_option,
|
||||||
create_actionrow,
|
|
||||||
create_select,
|
|
||||||
create_select_option,
|
|
||||||
wait_for_component,
|
|
||||||
)
|
)
|
||||||
|
from dis_snek.models.snek.command import check
|
||||||
|
|
||||||
from jarvis.config import get_config
|
from jarvis.config import get_config
|
||||||
from jarvis.db.models import Twitter
|
from jarvis.db.models import Twitter
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
from jarvis.utils.permissions import admin_or_permissions
|
||||||
|
|
||||||
logger = logging.getLogger("discord")
|
|
||||||
|
|
||||||
|
class TwitterCog(Scale):
|
||||||
class TwitterCog(commands.Cog):
|
|
||||||
"""J.A.R.V.I.S. Twitter Cog."""
|
"""J.A.R.V.I.S. Twitter Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
config = get_config()
|
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.api = tweepy.API(auth)
|
||||||
self._tweets.start()
|
|
||||||
self._guild_cache = {}
|
self._guild_cache = {}
|
||||||
self._channel_cache = {}
|
self._channel_cache = {}
|
||||||
|
|
||||||
@loop(seconds=30)
|
@slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount")
|
||||||
async def _tweets(self) -> None:
|
@slash_option(
|
||||||
twitters = Twitter.objects(active=True)
|
name="handle", description="Twitter account", opt_type=OptionTypes.STRING, required=True
|
||||||
handles = Twitter.objects.distinct("handle")
|
)
|
||||||
twitter_data = {}
|
@slash_option(
|
||||||
for handle in handles:
|
name="channel",
|
||||||
try:
|
description="Channel to post tweets to",
|
||||||
twitter_data[handle] = self.api.user_timeline(screen_name=handle)
|
opt_type=OptionTypes.CHANNEL,
|
||||||
except Exception as e:
|
required=True,
|
||||||
logger.error(f"Error with fetching: {e}")
|
)
|
||||||
for twitter in twitters:
|
@slash_option(
|
||||||
try:
|
name="retweets",
|
||||||
tweets = list(filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle]))
|
description="Mirror re-tweets?",
|
||||||
if tweets:
|
opt_type=OptionTypes.STRING,
|
||||||
tweets = sorted(tweets, key=lambda x: x.id)
|
required=False,
|
||||||
if twitter.guild not in self._guild_cache:
|
choices=[
|
||||||
self._guild_cache[twitter.guild] = await self.bot.fetch_guild(twitter.guild)
|
SlashCommandChoice(name="Yes", value="Yes"),
|
||||||
guild = self._guild_cache[twitter.guild]
|
SlashCommandChoice(name="No", value="No"),
|
||||||
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")],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _twitter_follow(
|
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:
|
) -> None:
|
||||||
|
handle = handle.lower()
|
||||||
retweets = retweets == "Yes"
|
retweets = retweets == "Yes"
|
||||||
if len(handle) > 15:
|
if len(handle) > 15:
|
||||||
await ctx.send("Invalid Twitter handle", hidden=True)
|
await ctx.send("Invalid Twitter handle", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(channel, TextChannel):
|
if not isinstance(channel, GuildText):
|
||||||
await ctx.send("Channel must be a text channel", hidden=True)
|
await ctx.send("Channel must be a text channel", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
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:
|
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
|
return
|
||||||
|
|
||||||
count = Twitter.objects(guild=ctx.guild.id).count()
|
count = Twitter.objects(guild=ctx.guild.id).count()
|
||||||
if count >= 12:
|
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
|
return
|
||||||
|
|
||||||
exists = Twitter.objects(handle=handle, guild=ctx.guild.id)
|
exists = Twitter.objects(twitter_id=account.id, guild=ctx.guild.id)
|
||||||
if exists:
|
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
|
return
|
||||||
|
|
||||||
t = Twitter(
|
t = Twitter(
|
||||||
handle=handle,
|
handle=account.screen_name,
|
||||||
|
twitter_id=account.id,
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
channel=channel.id,
|
channel=channel.id,
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
|
@ -137,27 +99,25 @@ class TwitterCog(commands.Cog):
|
||||||
|
|
||||||
await ctx.send(f"Now following `@{handle}` in {channel.mention}")
|
await ctx.send(f"Now following `@{handle}` in {channel.mention}")
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts")
|
||||||
base="twitter",
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
name="unfollow",
|
async def _twitter_unfollow(self, ctx: InteractionContext) -> None:
|
||||||
description="Unfollow Twitter accounts",
|
|
||||||
)
|
|
||||||
@admin_or_permissions(manage_guild=True)
|
|
||||||
async def _twitter_unfollow(self, ctx: SlashContext) -> None:
|
|
||||||
twitters = Twitter.objects(guild=ctx.guild.id)
|
twitters = Twitter.objects(guild=ctx.guild.id)
|
||||||
if not twitters:
|
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
|
return
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
handlemap = {str(x.id): x.handle for x in twitters}
|
handlemap = {str(x.id): x.handle for x in twitters}
|
||||||
for twitter 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)
|
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)
|
block = "\n".join(x.handle for x in twitters)
|
||||||
message = await ctx.send(
|
message = await ctx.send(
|
||||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
||||||
|
@ -166,52 +126,58 @@ class TwitterCog(commands.Cog):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
context = await wait_for_component(
|
context = await self.bot.wait_for_component(
|
||||||
self.bot, check=lambda x: ctx.author.id == x.author.id, messages=message, timeout=60 * 5
|
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()
|
_ = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_delete)).delete()
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
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)
|
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:
|
except asyncio.TimeoutError:
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
await message.edit(components=components)
|
await message.edit(components=components)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="twitter",
|
name="twitter", sub_cmd_name="retweets", description="Modify followed Twitter accounts"
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
name="retweets",
|
name="retweets",
|
||||||
description="Modify followed Twitter accounts",
|
description="Mirror re-tweets?",
|
||||||
options=[
|
opt_type=OptionTypes.STRING,
|
||||||
create_option(
|
required=False,
|
||||||
name="retweets",
|
choices=[
|
||||||
description="Mirror re-tweets?",
|
SlashCommandChoice(name="Yes", value="Yes"),
|
||||||
option_type=COptionType.STRING,
|
SlashCommandChoice(name="No", value="No"),
|
||||||
required=True,
|
|
||||||
choices=[create_choice(name="Yes", value="Yes"), create_choice(name="No", value="No")],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@admin_or_permissions(manage_guild=True)
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _twitter_modify(self, ctx: SlashContext, retweets: str) -> None:
|
async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None:
|
||||||
retweets = retweets == "Yes"
|
retweets = retweets == "Yes"
|
||||||
twitters = Twitter.objects(guild=ctx.guild.id)
|
twitters = Twitter.objects(guild=ctx.guild.id)
|
||||||
if not twitters:
|
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
|
return
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
for twitter 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)
|
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)
|
block = "\n".join(x.handle for x in twitters)
|
||||||
message = await ctx.send(
|
message = await ctx.send(
|
||||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
||||||
|
@ -220,30 +186,38 @@ class TwitterCog(commands.Cog):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
context = await wait_for_component(
|
context = await self.bot.wait_for_component(
|
||||||
self.bot, check=lambda x: ctx.author.id == x.author.id, messages=message, timeout=60 * 5
|
check=lambda x: ctx.author.id == x.author.id,
|
||||||
|
messages=message,
|
||||||
|
timeout=60 * 5,
|
||||||
)
|
)
|
||||||
|
|
||||||
handlemap = {str(x.id): x.handle for x in twitters}
|
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 = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_update)).first()
|
||||||
t.retweets = retweets
|
t.retweets = retweets
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
block = "\n".join(handlemap[x] for x in context.selected_options)
|
|
||||||
await context.edit_origin(
|
block = "\n".join(handlemap[x] for x in context.context.values)
|
||||||
content=f"{'Unfollowed' if not retweets else 'Followed'} retweets from the following:"
|
await context.context.edit_origin(
|
||||||
f"\n```\n{block}\n```",
|
content=(
|
||||||
|
f"{'Unfollowed' if not retweets else 'Followed'} "
|
||||||
|
"retweets from the following:"
|
||||||
|
f"\n```\n{block}\n```"
|
||||||
|
),
|
||||||
components=components,
|
components=components,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
for row in components:
|
for row in components:
|
||||||
for component in row["components"]:
|
for component in row.components:
|
||||||
component["disabled"] = True
|
component.disabled = True
|
||||||
await message.edit(components=components)
|
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."""
|
"""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."""
|
"""J.A.R.V.I.S. Utility Cog."""
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import discord
|
|
||||||
import discord_slash
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from discord import File, Guild, Role, User
|
from dis_snek import InteractionContext, Scale, Snake, const
|
||||||
from discord.ext import commands
|
from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice
|
||||||
from discord_slash import SlashContext, cog_ext
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
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
|
from PIL import Image
|
||||||
|
|
||||||
import jarvis
|
import jarvis
|
||||||
from jarvis import jarvis_self
|
|
||||||
from jarvis.config import get_config
|
from jarvis.config import get_config
|
||||||
from jarvis.data import pigpen
|
from jarvis.data import pigpen
|
||||||
from jarvis.data.robotcamo import emotes, hk, names
|
from jarvis.data.robotcamo import emotes, hk, names
|
||||||
from jarvis.utils import build_embed, convert_bytesize, get_repo_hash
|
from jarvis.utils import build_embed, get_repo_hash
|
||||||
from jarvis.utils.field import Field
|
|
||||||
|
|
||||||
JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA")
|
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.
|
Utility functions for J.A.R.V.I.S.
|
||||||
|
|
||||||
Mostly system utility functions, but may change over time
|
Mostly system utility functions, but may change over time
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Cog):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.config = get_config()
|
self.config = get_config()
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="status", description="Retrieve J.A.R.V.I.S. status")
|
||||||
name="status",
|
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||||
description="Retrieve J.A.R.V.I.S. status",
|
async def _status(self, ctx: InteractionContext) -> None:
|
||||||
)
|
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
|
||||||
async def _status(self, ctx: SlashContext) -> None:
|
|
||||||
title = "J.A.R.V.I.S. Status"
|
title = "J.A.R.V.I.S. Status"
|
||||||
desc = "All systems online"
|
desc = "All systems online"
|
||||||
color = "#98CCDA"
|
color = "#3498db"
|
||||||
fields = []
|
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",
|
name="logo",
|
||||||
description="Get the current logo",
|
description="Get the current logo",
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 30, commands.BucketType.channel)
|
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||||
async def _logo(self, ctx: SlashContext) -> None:
|
async def _logo(self, ctx: InteractionContext) -> None:
|
||||||
with BytesIO() as image_bytes:
|
with BytesIO() as image_bytes:
|
||||||
JARVIS_LOGO.save(image_bytes, "PNG")
|
JARVIS_LOGO.save(image_bytes, "PNG")
|
||||||
image_bytes.seek(0)
|
image_bytes.seek(0)
|
||||||
logo = File(image_bytes, filename="logo.png")
|
logo = File(image_bytes, file_name="logo.png")
|
||||||
|
|
||||||
await ctx.send(file=logo)
|
await ctx.send(file=logo)
|
||||||
|
|
||||||
@cog_ext.cog_slash(name="rchk", description="Robot Camo HK416")
|
@slash_command(name="rchk", description="Robot Camo HK416")
|
||||||
async def _rchk(self, ctx: SlashContext) -> None:
|
async def _rchk(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.send(content=hk)
|
await ctx.send(content=hk)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="rcauto",
|
name="rcauto",
|
||||||
description="Automates robot camo letters",
|
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 = ""
|
to_send = ""
|
||||||
if len(text) == 1 and not re.match(r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()):
|
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
|
return
|
||||||
for letter in text.upper():
|
for letter in text.upper():
|
||||||
if letter == " ":
|
if letter == " ":
|
||||||
to_send += " "
|
to_send += " "
|
||||||
elif re.match(r"^[A-Z0-9-()$@!?^'#.]$", letter):
|
elif re.match(r"^[A-Z0-9-()$@!?^'#.]$", letter):
|
||||||
id = emotes[letter]
|
id = emotes[letter]
|
||||||
if ctx.author.is_on_mobile():
|
to_send += f":{names[id]}:"
|
||||||
to_send += f":{names[id]}:"
|
|
||||||
else:
|
|
||||||
to_send += f"<:{names[id]}:{id}>"
|
|
||||||
if len(to_send) > 2000:
|
if len(to_send) > 2000:
|
||||||
await ctx.send("Too long.", hidden=True)
|
await ctx.send("Too long.", ephemeral=True)
|
||||||
else:
|
else:
|
||||||
await ctx.send(to_send)
|
await ctx.send(to_send)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="avatar", description="Get a user avatar")
|
||||||
name="avatar",
|
@slash_option(
|
||||||
description="Get a user avatar",
|
name="user",
|
||||||
options=[
|
description="User to view avatar of",
|
||||||
create_option(
|
opt_type=OptionTypes.USER,
|
||||||
name="user",
|
required=False,
|
||||||
description="User to view avatar of",
|
|
||||||
option_type=6,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 5, commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=5)
|
||||||
async def _avatar(self, ctx: SlashContext, user: User = None) -> None:
|
async def _avatar(self, ctx: InteractionContext, user: User = None) -> None:
|
||||||
if not user:
|
if not user:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
|
|
||||||
avatar = user.avatar_url
|
avatar = user.display_avatar.url
|
||||||
embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
|
embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
|
||||||
embed.set_image(url=avatar)
|
embed.set_image(url=avatar)
|
||||||
embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=avatar)
|
embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar)
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="roleinfo",
|
name="roleinfo",
|
||||||
description="Get role info",
|
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 = [
|
fields = [
|
||||||
Field(name="ID", value=role.id),
|
EmbedField(name="ID", value=str(role.id), inline=True),
|
||||||
Field(name="Name", value=role.name),
|
EmbedField(name="Name", value=role.name, inline=True),
|
||||||
Field(name="Color", value=str(role.color)),
|
EmbedField(name="Color", value=str(role.color.hex), inline=True),
|
||||||
Field(name="Mention", value=f"`{role.mention}`"),
|
EmbedField(name="Mention", value=f"`{role.mention}`", inline=True),
|
||||||
Field(name="Hoisted", value="Yes" if role.hoist else "No"),
|
EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True),
|
||||||
Field(name="Position", value=str(role.position)),
|
EmbedField(name="Position", value=str(role.position), inline=True),
|
||||||
Field(name="Mentionable", value="Yes" if role.mentionable else "No"),
|
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(
|
embed = build_embed(
|
||||||
title="",
|
title="",
|
||||||
description="",
|
description="",
|
||||||
fields=fields,
|
fields=fields,
|
||||||
color=str(role.color),
|
color=role.color,
|
||||||
timestamp=role.created_at,
|
timestamp=role.created_at,
|
||||||
)
|
)
|
||||||
embed.set_footer(text="Role Created")
|
embed.set_footer(text="Role Created")
|
||||||
|
@ -170,46 +153,47 @@ class UtilCog(commands.Cog):
|
||||||
|
|
||||||
fill = a > 0
|
fill = a > 0
|
||||||
|
|
||||||
data[..., :-1][fill.T] = list(role.color.to_rgb())
|
data[..., :-1][fill.T] = list(role.color.rgb)
|
||||||
|
|
||||||
im = Image.fromarray(data)
|
im = Image.fromarray(data)
|
||||||
|
|
||||||
with BytesIO() as image_bytes:
|
with BytesIO() as image_bytes:
|
||||||
im.save(image_bytes, "PNG")
|
im.save(image_bytes, "PNG")
|
||||||
image_bytes.seek(0)
|
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)
|
await ctx.send(embed=embed, file=color_show)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(
|
||||||
name="userinfo",
|
name="userinfo",
|
||||||
description="Get user info",
|
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:
|
if not user:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
user_roles = user.roles
|
user_roles = user.roles
|
||||||
if user_roles:
|
if user_roles:
|
||||||
user_roles = sorted(user.roles, key=lambda x: -x.position)
|
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 = [
|
fields = [
|
||||||
Field(
|
EmbedField(
|
||||||
name="Joined",
|
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",
|
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)}]",
|
name=f"Roles [{len(user_roles)}]",
|
||||||
value=" ".join([x.mention for x in user_roles]) if user_roles else "None",
|
value=" ".join([x.mention for x in user_roles]) if user_roles else "None",
|
||||||
inline=False,
|
inline=False,
|
||||||
|
@ -220,80 +204,82 @@ class UtilCog(commands.Cog):
|
||||||
title="",
|
title="",
|
||||||
description=user.mention,
|
description=user.mention,
|
||||||
fields=fields,
|
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_author(
|
||||||
embed.set_thumbnail(url=user.avatar_url)
|
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}")
|
embed.set_footer(text=f"ID: {user.id}")
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_slash(name="serverinfo", description="Get server info")
|
@slash_command(name="serverinfo", description="Get server info")
|
||||||
async def _server_info(self, ctx: SlashContext) -> None:
|
async def _server_info(self, ctx: InteractionContext) -> None:
|
||||||
guild: Guild = ctx.guild
|
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
|
owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
|
||||||
categories = len(guild.categories)
|
|
||||||
text_channels = len(guild.text_channels)
|
categories = len([x for x in guild.channels if isinstance(x, GuildCategory)])
|
||||||
voice_channels = len(guild.voice_channels)
|
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
|
members = guild.member_count
|
||||||
roles = len(guild.roles)
|
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 = [
|
fields = [
|
||||||
Field(name="Owner", value=owner),
|
EmbedField(name="Owner", value=owner, inline=True),
|
||||||
Field(name="Region", value=region),
|
EmbedField(name="Channel Categories", value=str(categories), inline=True),
|
||||||
Field(name="Channel Categories", value=categories),
|
EmbedField(name="Text Channels", value=str(text_channels), inline=True),
|
||||||
Field(name="Text Channels", value=text_channels),
|
EmbedField(name="Voice Channels", value=str(voice_channels), inline=True),
|
||||||
Field(name="Voice Channels", value=voice_channels),
|
EmbedField(name="Threads", value=str(threads), inline=True),
|
||||||
Field(name="Members", value=members),
|
EmbedField(name="Members", value=str(members), inline=True),
|
||||||
Field(name="Roles", value=roles),
|
EmbedField(name="Roles", value=str(roles), inline=True),
|
||||||
]
|
]
|
||||||
if len(role_list) < 1024:
|
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 = build_embed(title="", description="", fields=fields, timestamp=guild.created_at)
|
||||||
|
|
||||||
embed.set_author(name=guild.name, icon_url=guild.icon_url)
|
embed.set_author(name=guild.name, icon_url=guild.icon.url)
|
||||||
embed.set_thumbnail(url=guild.icon_url)
|
embed.set_thumbnail(url=guild.icon.url)
|
||||||
embed.set_footer(text=f"ID: {guild.id} | Server Created")
|
embed.set_footer(text=f"ID: {guild.id} | Server Created")
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@cog_ext.cog_subcommand(
|
@slash_command(
|
||||||
base="pw",
|
name="pw",
|
||||||
name="gen",
|
sub_cmd_name="gen",
|
||||||
base_desc="Password utilites",
|
|
||||||
description="Generate a secure password",
|
description="Generate a secure password",
|
||||||
guild_ids=[862402786116763668],
|
scopes=[862402786116763668],
|
||||||
options=[
|
)
|
||||||
create_option(
|
@slash_option(
|
||||||
name="length",
|
name="length",
|
||||||
description="Password length (default 32)",
|
description="Password length (default 32)",
|
||||||
option_type=4,
|
opt_type=OptionTypes.INTEGER,
|
||||||
required=False,
|
required=False,
|
||||||
),
|
)
|
||||||
create_option(
|
@slash_option(
|
||||||
name="chars",
|
name="chars",
|
||||||
description="Characters to include (default last option)",
|
description="Characters to include (default last option)",
|
||||||
option_type=4,
|
opt_type=OptionTypes.INTEGER,
|
||||||
required=False,
|
required=False,
|
||||||
choices=[
|
choices=[
|
||||||
create_choice(name="A-Za-z", value=0),
|
SlashCommandChoice(name="A-Za-z", value=0),
|
||||||
create_choice(name="A-Fa-f0-9", value=1),
|
SlashCommandChoice(name="A-Fa-f0-9", value=1),
|
||||||
create_choice(name="A-Za-z0-9", value=2),
|
SlashCommandChoice(name="A-Za-z0-9", value=2),
|
||||||
create_choice(name="A-Za-z0-9!@#$%^&*", value=3),
|
SlashCommandChoice(name="A-Za-z0-9!@#$%^&*", value=3),
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@commands.cooldown(1, 15, type=commands.BucketType.user)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
|
||||||
async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3) -> None:
|
async def _pw_gen(self, ctx: InteractionContext, length: int = 32, chars: int = 3) -> None:
|
||||||
if length > 256:
|
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
|
return
|
||||||
choices = [
|
choices = [
|
||||||
string.ascii_letters,
|
string.ascii_letters,
|
||||||
|
@ -307,15 +293,14 @@ class UtilCog(commands.Cog):
|
||||||
f"Generated password:\n`{pw}`\n\n"
|
f"Generated password:\n`{pw}`\n\n"
|
||||||
'**WARNING: Once you press "Dismiss Message", '
|
'**WARNING: Once you press "Dismiss Message", '
|
||||||
"*the password is lost forever***",
|
"*the password is lost forever***",
|
||||||
hidden=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="pigpen", description="Encode a string into pigpen")
|
||||||
name="pigpen",
|
@slash_option(
|
||||||
description="Encode a string into pigpen",
|
name="text", description="Text to encode", opt_type=OptionTypes.STRING, required=True
|
||||||
options=[create_option(name="text", description="Text to encode", option_type=3, required=True)],
|
|
||||||
)
|
)
|
||||||
async def _pigpen(self, ctx: SlashContext, text: str) -> None:
|
async def _pigpen(self, ctx: InteractionContext, text: str) -> None:
|
||||||
outp = "`"
|
outp = "`"
|
||||||
for c in text:
|
for c in text:
|
||||||
c = c.lower()
|
c = c.lower()
|
||||||
|
@ -330,6 +315,6 @@ class UtilCog(commands.Cog):
|
||||||
await ctx.send(outp[:2000])
|
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."""
|
"""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."""
|
"""J.A.R.V.I.S. Verify Cog."""
|
||||||
|
import asyncio
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from discord.ext import commands
|
from dis_snek import InteractionContext, Scale, Snake
|
||||||
from discord_slash import ComponentContext, SlashContext, cog_ext
|
from dis_snek.models.application_commands import slash_command
|
||||||
from discord_slash.model import ButtonStyle
|
from dis_snek.models.discord.components import Button, ButtonStyles, spread_to_rows
|
||||||
from discord_slash.utils import manage_components
|
from dis_snek.models.snek.command import cooldown
|
||||||
|
from dis_snek.models.snek.cooldowns import Buckets
|
||||||
|
|
||||||
from jarvis.db.models import Setting
|
from jarvis.db.models import Setting
|
||||||
|
|
||||||
|
@ -16,36 +18,32 @@ def create_layout() -> list:
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
label = "YES" if i == yes else "NO"
|
label = "YES" if i == yes else "NO"
|
||||||
id = f"no_{i}" if not i == yes else "yes"
|
id = f"no_{i}" if not i == yes else "yes"
|
||||||
color = ButtonStyle.green if i == yes else ButtonStyle.red
|
color = ButtonStyles.GREEN if i == yes else ButtonStyles.RED
|
||||||
buttons.append(
|
buttons.append(
|
||||||
manage_components.create_button(
|
Button(
|
||||||
style=color,
|
style=color,
|
||||||
label=label,
|
label=label,
|
||||||
custom_id=f"verify_button||{id}",
|
custom_id=f"verify_button||{id}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
action_row = manage_components.spread_to_rows(*buttons, max_in_row=3)
|
return spread_to_rows(*buttons, max_in_row=3)
|
||||||
return action_row
|
|
||||||
|
|
||||||
|
|
||||||
class VerifyCog(commands.Cog):
|
class VerifyCog(Scale):
|
||||||
"""J.A.R.V.I.S. Verify Cog."""
|
"""J.A.R.V.I.S. Verify Cog."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@cog_ext.cog_slash(
|
@slash_command(name="verify", description="Verify that you've read the rules")
|
||||||
name="verify",
|
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
|
||||||
description="Verify that you've read the rules",
|
async def _verify(self, ctx: InteractionContext) -> None:
|
||||||
)
|
|
||||||
@commands.cooldown(1, 15, commands.BucketType.user)
|
|
||||||
async def _verify(self, ctx: SlashContext) -> None:
|
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
role = Setting.objects(guild=ctx.guild.id, setting="verified").first()
|
role = Setting.objects(guild=ctx.guild.id, setting="verified").first()
|
||||||
if not role:
|
if not role:
|
||||||
await ctx.send("This guild has not enabled verification", delete_after=5)
|
await ctx.send("This guild has not enabled verification", delete_after=5)
|
||||||
return
|
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)
|
await ctx.send("You are already verified.", delete_after=5)
|
||||||
return
|
return
|
||||||
components = create_layout()
|
components = create_layout()
|
||||||
|
@ -53,40 +51,41 @@ class VerifyCog(commands.Cog):
|
||||||
content=f"{ctx.author.mention}, please press the button that says `YES`.",
|
content=f"{ctx.author.mention}, please press the button that says `YES`.",
|
||||||
components=components,
|
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:
|
try:
|
||||||
if ctx.author.id != ctx.origin_message.mentions[0].id:
|
context = await self.bot.wait_for_component(
|
||||||
return
|
messages=message, check=lambda x: ctx.author.id == x.author.id, timeout=30
|
||||||
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`",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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."""
|
"""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."""
|
"""Load the config for J.A.R.V.I.S."""
|
||||||
|
import os
|
||||||
|
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from yaml import load
|
from yaml import load
|
||||||
|
|
||||||
|
@ -27,6 +29,7 @@ class Config(object):
|
||||||
logo: str,
|
logo: str,
|
||||||
mongo: dict,
|
mongo: dict,
|
||||||
urls: dict,
|
urls: dict,
|
||||||
|
sync: bool = False,
|
||||||
log_level: str = "WARNING",
|
log_level: str = "WARNING",
|
||||||
cogs: list = None,
|
cogs: list = None,
|
||||||
events: bool = True,
|
events: bool = True,
|
||||||
|
@ -46,6 +49,7 @@ class Config(object):
|
||||||
self.max_messages = max_messages
|
self.max_messages = max_messages
|
||||||
self.gitlab_token = gitlab_token
|
self.gitlab_token = gitlab_token
|
||||||
self.twitter = twitter
|
self.twitter = twitter
|
||||||
|
self.sync = sync or os.environ.get("SYNC_COMMANDS", False)
|
||||||
self.__db_loaded = False
|
self.__db_loaded = False
|
||||||
self.__mongo = MongoClient(**self.mongo["connect"])
|
self.__mongo = MongoClient(**self.mongo["connect"])
|
||||||
|
|
||||||
|
@ -61,8 +65,7 @@ class Config(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_yaml(cls, y: dict) -> "Config":
|
def from_yaml(cls, y: dict) -> "Config":
|
||||||
"""Load the yaml config file."""
|
"""Load the yaml config file."""
|
||||||
instance = cls(**y)
|
return cls(**y)
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(path: str = "config.yaml") -> Config:
|
def get_config(path: str = "config.yaml") -> Config:
|
||||||
|
|
|
@ -222,6 +222,7 @@ class Twitter(Document):
|
||||||
"""Twitter Follow object."""
|
"""Twitter Follow object."""
|
||||||
|
|
||||||
active = BooleanField(default=True)
|
active = BooleanField(default=True)
|
||||||
|
twitter_id = IntField(required=True)
|
||||||
handle = StringField(required=True)
|
handle = StringField(required=True)
|
||||||
channel = SnowflakeField(required=True)
|
channel = SnowflakeField(required=True)
|
||||||
guild = SnowflakeField(required=True)
|
guild = SnowflakeField(required=True)
|
||||||
|
@ -229,6 +230,7 @@ class Twitter(Document):
|
||||||
retweets = BooleanField(default=True)
|
retweets = BooleanField(default=True)
|
||||||
admin = SnowflakeField(required=True)
|
admin = SnowflakeField(required=True)
|
||||||
created_at = DateTimeField(default=datetime.utcnow)
|
created_at = DateTimeField(default=datetime.utcnow)
|
||||||
|
last_sync = DateTimeField()
|
||||||
|
|
||||||
meta = {"db_alias": "main"}
|
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."""
|
"""J.A.R.V.I.S. Member event handler."""
|
||||||
from discord import Member
|
from dis_snek import Snake, listen
|
||||||
from discord.ext.commands import Bot
|
from dis_snek.models.discord.user import Member
|
||||||
|
|
||||||
from jarvis.db.models import Mute, Setting
|
from jarvis.db.models import Mute, Setting
|
||||||
|
|
||||||
|
@ -8,10 +8,11 @@ from jarvis.db.models import Mute, Setting
|
||||||
class MemberEventHandler(object):
|
class MemberEventHandler(object):
|
||||||
"""J.A.R.V.I.S. Member event handler."""
|
"""J.A.R.V.I.S. Member event handler."""
|
||||||
|
|
||||||
def __init__(self, bot: Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.bot.add_listener(self.on_member_join)
|
self.bot.add_listener(self.on_member_join)
|
||||||
|
|
||||||
|
@listen()
|
||||||
async def on_member_join(self, user: Member) -> None:
|
async def on_member_join(self, user: Member) -> None:
|
||||||
"""Handle on_member_join event."""
|
"""Handle on_member_join event."""
|
||||||
guild = user.guild
|
guild = user.guild
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
"""J.A.R.V.I.S. Message event handler."""
|
"""J.A.R.V.I.S. Message event handler."""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from discord import DMChannel, Message
|
from dis_snek import Snake, listen
|
||||||
from discord.ext.commands import Bot
|
from dis_snek.models.discord.channel import DMChannel
|
||||||
from discord.utils import find
|
from dis_snek.models.discord.embed import EmbedField
|
||||||
|
from dis_snek.models.discord.message import Message
|
||||||
|
|
||||||
from jarvis.config import get_config
|
from jarvis.config import get_config
|
||||||
from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning
|
from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed, find
|
||||||
from jarvis.utils.field import Field
|
|
||||||
|
|
||||||
invites = 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,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ invites = re.compile(
|
||||||
class MessageEventHandler(object):
|
class MessageEventHandler(object):
|
||||||
"""J.A.R.V.I.S. Message event handler."""
|
"""J.A.R.V.I.S. Message event handler."""
|
||||||
|
|
||||||
def __init__(self, bot: Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.bot.add_listener(self.on_message)
|
self.bot.add_listener(self.on_message)
|
||||||
self.bot.add_listener(self.on_message_edit)
|
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)
|
channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions)
|
||||||
if channel and message.author.id == 293795462752894976:
|
if channel and message.author.id == 293795462752894976:
|
||||||
await channel.send(
|
await channel.send(
|
||||||
content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif"
|
content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501
|
||||||
)
|
)
|
||||||
content = re.sub(r"\s+", "", message.content)
|
content = re.sub(r"\s+", "", message.content)
|
||||||
match = invites.search(content)
|
match = invites.search(content)
|
||||||
|
@ -72,10 +72,10 @@ class MessageEventHandler(object):
|
||||||
user=message.author.id,
|
user=message.author.id,
|
||||||
).save()
|
).save()
|
||||||
fields = [
|
fields = [
|
||||||
Field(
|
EmbedField(
|
||||||
"Reason",
|
name="Reason",
|
||||||
"Sent an invite link",
|
value="Sent an invite link",
|
||||||
False,
|
inline=False,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -85,9 +85,11 @@ class MessageEventHandler(object):
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=message.author.nick if message.author.nick else message.author.name,
|
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)
|
await message.channel.send(embed=embed)
|
||||||
|
|
||||||
async def massmention(self, message: Message) -> None:
|
async def massmention(self, message: Message) -> None:
|
||||||
|
@ -99,7 +101,8 @@ class MessageEventHandler(object):
|
||||||
if (
|
if (
|
||||||
massmention
|
massmention
|
||||||
and massmention.value > 0 # noqa: W503
|
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
|
> massmention.value # noqa: W503
|
||||||
):
|
):
|
||||||
_ = Warning(
|
_ = Warning(
|
||||||
|
@ -110,7 +113,7 @@ class MessageEventHandler(object):
|
||||||
reason="Mass Mention",
|
reason="Mass Mention",
|
||||||
user=message.author.id,
|
user=message.author.id,
|
||||||
).save()
|
).save()
|
||||||
fields = [Field("Reason", "Mass Mention", False)]
|
fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)]
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Warning",
|
title="Warning",
|
||||||
description=f"{message.author.mention} has been warned",
|
description=f"{message.author.mention} has been warned",
|
||||||
|
@ -118,9 +121,11 @@ class MessageEventHandler(object):
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=message.author.nick if message.author.nick else message.author.name,
|
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)
|
await message.channel.send(embed=embed)
|
||||||
|
|
||||||
async def roleping(self, message: Message) -> None:
|
async def roleping(self, message: Message) -> None:
|
||||||
|
@ -173,10 +178,10 @@ class MessageEventHandler(object):
|
||||||
user=message.author.id,
|
user=message.author.id,
|
||||||
).save()
|
).save()
|
||||||
fields = [
|
fields = [
|
||||||
Field(
|
EmbedField(
|
||||||
"Reason",
|
name="Reason",
|
||||||
"Pinged a blocked role/user with a blocked role",
|
value="Pinged a blocked role/user with a blocked role",
|
||||||
False,
|
inline=False,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -186,11 +191,14 @@ class MessageEventHandler(object):
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=message.author.nick if message.author.nick else message.author.name,
|
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)
|
await message.channel.send(embed=embed)
|
||||||
|
|
||||||
|
@listen()
|
||||||
async def on_message(self, message: Message) -> None:
|
async def on_message(self, message: Message) -> None:
|
||||||
"""Handle on_message event. Calls other event handlers."""
|
"""Handle on_message event. Calls other event handlers."""
|
||||||
if not isinstance(message.channel, DMChannel) and not message.author.bot:
|
if not isinstance(message.channel, DMChannel) and not message.author.bot:
|
||||||
|
@ -200,6 +208,7 @@ class MessageEventHandler(object):
|
||||||
await self.autopurge(message)
|
await self.autopurge(message)
|
||||||
await self.checks(message)
|
await self.checks(message)
|
||||||
|
|
||||||
|
@listen()
|
||||||
async def on_message_edit(self, before: Message, after: Message) -> None:
|
async def on_message_edit(self, before: Message, after: Message) -> None:
|
||||||
"""Handle on_message_edit event. Calls other event handlers."""
|
"""Handle on_message_edit event. Calls other event handlers."""
|
||||||
if not isinstance(after.channel, DMChannel) and not after.author.bot:
|
if not isinstance(after.channel, DMChannel) and not after.author.bot:
|
||||||
|
@ -208,3 +217,10 @@ class MessageEventHandler(object):
|
||||||
await self.checks(after)
|
await self.checks(after)
|
||||||
await self.roleping(after)
|
await self.roleping(after)
|
||||||
await self.checks(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."""
|
"""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:
|
def init() -> None:
|
||||||
"""Start the background task handlers."""
|
"""Start the background task handlers."""
|
||||||
unban.unban.start()
|
unban.unban.start()
|
||||||
unlock.unlock.start()
|
unlock.unlock.start()
|
||||||
unmute.unmute.start()
|
|
||||||
unwarn.unwarn.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."""
|
"""J.A.R.V.I.S. unban background task handler."""
|
||||||
|
from asyncio import to_thread
|
||||||
from datetime import datetime, timedelta
|
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
|
import jarvis
|
||||||
from jarvis.config import get_config
|
from jarvis.config import get_config
|
||||||
|
@ -10,17 +12,18 @@ from jarvis.db.models import Ban, Unban
|
||||||
jarvis_id = get_config().client_id
|
jarvis_id = get_config().client_id
|
||||||
|
|
||||||
|
|
||||||
@loop(minutes=10)
|
async def _unban() -> None:
|
||||||
async def unban() -> None:
|
"""J.A.R.V.I.S. unban blocking task."""
|
||||||
"""J.A.R.V.I.S. unban background task."""
|
|
||||||
bans = Ban.objects(type="temp", active=True)
|
bans = Ban.objects(type="temp", active=True)
|
||||||
unbans = []
|
unbans = []
|
||||||
for ban in bans:
|
for ban in bans:
|
||||||
if ban.created_at + timedelta(hours=ban.duration) < datetime.utcnow() + timedelta(minutes=10):
|
if ban.created_at + timedelta(hours=ban.duration) < datetime.utcnow() + timedelta(
|
||||||
guild = await jarvis.jarvis.fetch_guild(ban.guild)
|
minutes=10
|
||||||
user = await jarvis.jarvis.fetch_user(ban.user)
|
):
|
||||||
|
guild = await jarvis.jarvis.get_guild(ban.guild)
|
||||||
|
user = await jarvis.jarvis.get_user(ban.user)
|
||||||
if user:
|
if user:
|
||||||
guild.unban(user)
|
await guild.unban(user=user, reason="Ban expired")
|
||||||
ban.active = False
|
ban.active = False
|
||||||
ban.save()
|
ban.save()
|
||||||
unbans.append(
|
unbans.append(
|
||||||
|
@ -34,4 +37,10 @@ async def unban() -> None:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if unbans:
|
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."""
|
"""J.A.R.V.I.S. unlock background task handler."""
|
||||||
|
from asyncio import to_thread
|
||||||
from datetime import datetime, timedelta
|
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
|
import jarvis
|
||||||
from jarvis.db.models import Lock
|
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:
|
async def unlock() -> None:
|
||||||
"""J.A.R.V.I.S. unlock background task."""
|
"""J.A.R.V.I.S. unlock background task."""
|
||||||
locks = Lock.objects(active=True)
|
await to_thread(_unlock)
|
||||||
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()
|
|
||||||
|
|
|
@ -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."""
|
"""J.A.R.V.I.S. unwarn background task handler."""
|
||||||
|
from asyncio import to_thread
|
||||||
from datetime import datetime, timedelta
|
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
|
from jarvis.db.models import Warning
|
||||||
|
|
||||||
|
|
||||||
@loop(hours=1)
|
async def _unwarn() -> None:
|
||||||
async def unwarn() -> None:
|
"""J.A.R.V.I.S. unwarn blocking task."""
|
||||||
"""J.A.R.V.I.S. unwarn background task."""
|
|
||||||
warns = Warning.objects(active=True)
|
warns = Warning.objects(active=True)
|
||||||
for warn in warns:
|
for warn in warns:
|
||||||
if warn.created_at + timedelta(hours=warn.duration) < datetime.utcnow():
|
if warn.created_at + timedelta(hours=warn.duration) < datetime.utcnow():
|
||||||
warn.active = False
|
warn.active = False
|
||||||
warn.save()
|
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."""
|
"""J.A.R.V.I.S. Utility Functions."""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
|
from typing import Any, Callable, Iterable, List, Optional, TypeVar
|
||||||
|
|
||||||
import git
|
import git
|
||||||
from discord import Color, Embed, Message
|
from dis_snek.models.discord.embed import Embed
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
import jarvis.cogs
|
import jarvis.cogs
|
||||||
import jarvis.db
|
import jarvis.db
|
||||||
|
@ -12,6 +12,31 @@ from jarvis.config import get_config
|
||||||
|
|
||||||
__all__ = ["field", "db", "cachecog", "permissions"]
|
__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:
|
def convert_bytesize(b: int) -> str:
|
||||||
"""Convert bytes amount to human readable."""
|
"""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)))
|
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:
|
def get_extensions(path: str = jarvis.cogs.__path__) -> list:
|
||||||
"""Get J.A.R.V.I.S. cogs."""
|
"""Get J.A.R.V.I.S. cogs."""
|
||||||
config = get_config()
|
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]
|
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:
|
def update() -> int:
|
||||||
"""J.A.R.V.I.S. update utility."""
|
"""J.A.R.V.I.S. update utility."""
|
||||||
repo = git.Repo(".")
|
repo = git.Repo(".")
|
||||||
|
@ -99,3 +85,103 @@ def get_repo_hash() -> str:
|
||||||
"""J.A.R.V.I.S. current branch hash."""
|
"""J.A.R.V.I.S. current branch hash."""
|
||||||
repo = git.Repo(".")
|
repo = git.Repo(".")
|
||||||
return repo.head.object.hexsha
|
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."""
|
"""Cog wrapper for command caching."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from discord.ext import commands
|
from dis_snek import InteractionContext, Scale, Snake
|
||||||
from discord.ext.tasks import loop
|
from dis_snek.ext.tasks.task import Task
|
||||||
from discord.utils import find
|
from dis_snek.ext.tasks.triggers import IntervalTrigger
|
||||||
from discord_slash import SlashContext
|
|
||||||
|
from jarvis.utils import find
|
||||||
|
|
||||||
|
|
||||||
class CacheCog(commands.Cog):
|
class CacheCog(Scale):
|
||||||
"""Cog wrapper for command caching."""
|
"""Cog wrapper for command caching."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: Snake):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
self._expire_interaction.start()
|
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."""
|
"""Check the cache."""
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
@ -27,7 +28,7 @@ class CacheCog(commands.Cog):
|
||||||
self.cache.values(),
|
self.cache.values(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@loop(minutes=1)
|
@Task.create(IntervalTrigger(minutes=1))
|
||||||
async def _expire_interaction(self) -> None:
|
async def _expire_interaction(self) -> None:
|
||||||
keys = list(self.cache.keys())
|
keys = list(self.cache.keys())
|
||||||
for key in 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."""
|
"""Permissions wrappers."""
|
||||||
from discord.ext import commands
|
from dis_snek import InteractionContext, Permissions
|
||||||
|
|
||||||
from jarvis.config import get_config
|
from jarvis.config import get_config
|
||||||
|
|
||||||
|
@ -7,22 +7,23 @@ from jarvis.config import get_config
|
||||||
def user_is_bot_admin() -> bool:
|
def user_is_bot_admin() -> bool:
|
||||||
"""Check if a user is a J.A.R.V.I.S. admin."""
|
"""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."""
|
"""Command check predicate."""
|
||||||
if getattr(get_config(), "admins", None):
|
if getattr(get_config(), "admins", None):
|
||||||
return ctx.author.id in get_config().admins
|
return ctx.author.id in get_config().admins
|
||||||
else:
|
else:
|
||||||
return False
|
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."""
|
"""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
|
"""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
|
"""Main run file for J.A.R.V.I.S."""
|
||||||
# flake8: noqa
|
from jarvis import run
|
||||||
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.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
freeze_support()
|
run()
|
||||||
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.")
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue