Massive update to beanie, ipy5, re-work and update of many commands
This commit is contained in:
parent
910ac2c304
commit
f594f02b1b
34 changed files with 2656 additions and 2686 deletions
|
@ -8,6 +8,8 @@
|
||||||
!/jarvis_small.png
|
!/jarvis_small.png
|
||||||
!/run.py
|
!/run.py
|
||||||
!/config.yaml
|
!/config.yaml
|
||||||
|
# Needed for jarvis-compose
|
||||||
|
!/.git
|
||||||
|
|
||||||
# Block other files
|
# Block other files
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
|
|
22
README.md
22
README.md
|
@ -2,8 +2,8 @@
|
||||||
<img width=15% alt="JARVIS" src="https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/raw/main/jarvis_small.png">
|
<img width=15% alt="JARVIS" src="https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/raw/main/jarvis_small.png">
|
||||||
|
|
||||||
# Just Another Rather Very Intelligent System
|
# Just Another Rather Very Intelligent System
|
||||||
<br />
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
[]()
|
[]()
|
||||||
[](https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot)
|
[](https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot)
|
||||||
|
@ -12,28 +12,22 @@
|
||||||
[](https://ko-fi.com/zevaryx)
|
[](https://ko-fi.com/zevaryx)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Welcome to the JARVIS Initiative, an open-source multi-purpose bot
|
||||||
Welcome to the JARVIS 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.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
JARVIS currently offers:
|
JARVIS currently offers:
|
||||||
|
|
||||||
- 👩💼 **Administration**: `verify`, `ban/unban`, `kick`, `purge`, `mute/unmute` and more!
|
- 👩💼 **Administration**: `verify`, `ban/unban`, `kick`, `purge`, `mute/unmute` and more!
|
||||||
- 🚓 **Moderation**: `lock/unlock`, `lockdown`, `warn`, `autoreact`, and also more!
|
- 🚓 **Moderation**: `lock/unlock`, `lockdown`, `warn`, `autoreact`, and also more!
|
||||||
- 🔗 **Social Media**: `reddit` and `twitter` syncing!
|
|
||||||
- 🔧 **Utilities**: `remindme`, `rolegiver`, `temprole`, `image`, and so many more!
|
- 🔧 **Utilities**: `remindme`, `rolegiver`, `temprole`, `image`, and so many more!
|
||||||
- 🏷️ **Tags**: Custom `tag`s! Useful for custom messages without the hassle!
|
- 🏷️ **Tags**: Custom `tag`s! Useful for custom messages without the hassle!
|
||||||
- 👑 **dbrand**: `ctc2` and other dbrand-specific commands. [Join their server](https://discord.gg/dbrand) to see them in action!
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Before **creating an issue**, please ensure that it hasn't already been reported/suggested.
|
Before **creating an issue**, please ensure that it hasn't already been reported/suggested.
|
||||||
If you have a question, please join the community Discord before opening an issue.
|
If you have a question, please join the community Discord before opening an issue.
|
||||||
|
|
||||||
|
|
||||||
If you wish to contribute to the JARVIS codebase or documentation, join the Discord! The recognized developers there will help you get started.
|
If you wish to contribute to the JARVIS codebase or documentation, join the Discord! The recognized developers there will help you get started.
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
@ -41,12 +35,12 @@ If you wish to contribute to the JARVIS codebase or documentation, join the Disc
|
||||||
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 5.0 or higher
|
|
||||||
|
- MongoDB 6.0 or higher
|
||||||
- Python 3.10 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
|
||||||
- Everything in `requirements.txt`
|
- Everything in `requirements.txt`
|
||||||
|
|
||||||
|
|
||||||
## JARVIS Cogs
|
## JARVIS Cogs
|
||||||
|
|
||||||
Current cogs that are implemented:
|
Current cogs that are implemented:
|
||||||
|
@ -57,10 +51,6 @@ Current cogs that are implemented:
|
||||||
- Handles autoreaction configuration
|
- Handles autoreaction configuration
|
||||||
- `BotutilCog`
|
- `BotutilCog`
|
||||||
- Handles internal bot utilities (private use only)
|
- Handles internal bot utilities (private use only)
|
||||||
- `CTC2Cog`
|
|
||||||
- dbrand Complete the Code utilities
|
|
||||||
- `DbrandCog`
|
|
||||||
- dbrand-specific functions and utilities
|
|
||||||
- `DevCog`
|
- `DevCog`
|
||||||
- Developer utilities, such as hashing, encoding, and UUID generation
|
- Developer utilities, such as hashing, encoding, and UUID generation
|
||||||
- `GitlabCog`
|
- `GitlabCog`
|
||||||
|
@ -88,7 +78,6 @@ Current cogs that are implemented:
|
||||||
- `VerifyCog`
|
- `VerifyCog`
|
||||||
- Guild verification
|
- Guild verification
|
||||||
|
|
||||||
|
|
||||||
## Directories
|
## Directories
|
||||||
|
|
||||||
### `jarvis`
|
### `jarvis`
|
||||||
|
@ -102,6 +91,7 @@ All of the cogs listed above are stored in this directory
|
||||||
##### `jarvis.cogs.admin`
|
##### `jarvis.cogs.admin`
|
||||||
|
|
||||||
Contains all AdminCogs, including:
|
Contains all AdminCogs, including:
|
||||||
|
|
||||||
- `BanCog`
|
- `BanCog`
|
||||||
- `KickCog`
|
- `KickCog`
|
||||||
- `LockCog`
|
- `LockCog`
|
||||||
|
|
|
@ -61,18 +61,31 @@ async def run() -> None:
|
||||||
config = load_config()
|
config = load_config()
|
||||||
logger = get_logger("jarvis", show_locals=False) # jconfig.log_level == "DEBUG")
|
logger = get_logger("jarvis", show_locals=False) # jconfig.log_level == "DEBUG")
|
||||||
logger.setLevel(config.log_level)
|
logger.setLevel(config.log_level)
|
||||||
file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w")
|
file_handler = logging.FileHandler(
|
||||||
file_handler.setFormatter(logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s"))
|
filename="jarvis.log", encoding="UTF-8", mode="w"
|
||||||
|
)
|
||||||
|
file_handler.setFormatter(
|
||||||
|
logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s")
|
||||||
|
)
|
||||||
logger.addHandler(file_handler)
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
# Configure client
|
# Configure client
|
||||||
intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES
|
intents = (
|
||||||
|
Intents.DEFAULT
|
||||||
|
| Intents.MESSAGES
|
||||||
|
| Intents.GUILD_MEMBERS
|
||||||
|
| Intents.GUILD_MESSAGES
|
||||||
|
)
|
||||||
redis_config = config.redis.dict()
|
redis_config = config.redis.dict()
|
||||||
redis_host = redis_config.pop("host")
|
redis_host = redis_config.pop("host")
|
||||||
|
|
||||||
redis = await aioredis.from_url(redis_host, decode_responses=True, **redis_config)
|
redis = await aioredis.from_url(redis_host, decode_responses=True, **redis_config)
|
||||||
|
|
||||||
await connect(**config.mongo.dict(), testing=config.environment.value == "develop", extra_models=[StaticStat, Stat])
|
await connect(
|
||||||
|
**config.mongo.dict(),
|
||||||
|
testing=config.environment.value == "develop",
|
||||||
|
extra_models=[StaticStat, Stat],
|
||||||
|
)
|
||||||
|
|
||||||
jarvis = Jarvis(
|
jarvis = Jarvis(
|
||||||
intents=intents,
|
intents=intents,
|
||||||
|
@ -81,6 +94,7 @@ async def run() -> None:
|
||||||
send_command_tracebacks=False,
|
send_command_tracebacks=False,
|
||||||
redis=redis,
|
redis=redis,
|
||||||
logger=logger,
|
logger=logger,
|
||||||
|
erapi=config.erapi,
|
||||||
)
|
)
|
||||||
|
|
||||||
# External modules
|
# External modules
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from erapi import ERAPI
|
||||||
from interactions.ext.prefixed_commands.context import PrefixedContext
|
from interactions.ext.prefixed_commands.context import PrefixedContext
|
||||||
from interactions.models.internal.context import BaseContext, InteractionContext
|
from interactions.models.internal.context import BaseContext, InteractionContext
|
||||||
from jarvis_core.util.ansi import Fore, Format, fmt
|
from jarvis_core.util.ansi import Fore, Format, fmt
|
||||||
|
@ -20,13 +21,16 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD)
|
||||||
|
|
||||||
|
|
||||||
class Jarvis(StatipyClient, ErrorMixin, EventMixin, TaskMixin):
|
class Jarvis(StatipyClient, ErrorMixin, EventMixin, TaskMixin):
|
||||||
def __init__(self, redis: "aioredis.Redis", *args, **kwargs): # noqa: ANN002 ANN003
|
def __init__(
|
||||||
|
self, redis: "aioredis.Redis", erapi: str, *args, **kwargs
|
||||||
|
): # noqa: ANN002 ANN003
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.redis = redis
|
self.redis = redis
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.phishing_domains = []
|
self.phishing_domains = []
|
||||||
self.pre_run_callback = self._prerun
|
self.pre_run_callback = self._prerun
|
||||||
self.synced = False
|
self.synced = False
|
||||||
|
self.erapi = ERAPI(erapi)
|
||||||
|
|
||||||
async def _prerun(self, ctx: BaseContext, *args, **kwargs) -> None:
|
async def _prerun(self, ctx: BaseContext, *args, **kwargs) -> None:
|
||||||
name = ctx.invoke_target
|
name = ctx.invoke_target
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from interactions import listen
|
||||||
|
from interactions.api.events import Error
|
||||||
from interactions.client.errors import (
|
from interactions.client.errors import (
|
||||||
CommandCheckFailure,
|
CommandCheckFailure,
|
||||||
CommandOnCooldown,
|
CommandOnCooldown,
|
||||||
|
@ -31,16 +33,24 @@ Callback:
|
||||||
|
|
||||||
|
|
||||||
class ErrorMixin:
|
class ErrorMixin:
|
||||||
async def on_error(self, source: str, error: Exception, *args, **kwargs) -> None:
|
@listen()
|
||||||
|
async def on_error(self, event: Error, *args, **kwargs) -> None:
|
||||||
"""NAFF on_error override."""
|
"""NAFF on_error override."""
|
||||||
|
source = event.source
|
||||||
|
error = event.error
|
||||||
if isinstance(error, HTTPException):
|
if isinstance(error, HTTPException):
|
||||||
errors = error.search_for_message(error.errors)
|
errors = error.search_for_message(error.errors)
|
||||||
out = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
|
out = (
|
||||||
|
f"HTTPException: {error.status}|{error.response.reason}: "
|
||||||
|
+ "\n".join(errors)
|
||||||
|
)
|
||||||
self.logger.error(out, exc_info=error)
|
self.logger.error(out, exc_info=error)
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Ignoring exception in {source}", exc_info=error)
|
self.logger.error(f"Ignoring exception in {source}", exc_info=error)
|
||||||
|
|
||||||
async def on_command_error(self, ctx: BaseContext, error: Exception, *args: list, **kwargs: dict) -> None:
|
async def on_command_error(
|
||||||
|
self, ctx: BaseContext, error: Exception, *args: list, **kwargs: dict
|
||||||
|
) -> None:
|
||||||
"""NAFF on_command_error override."""
|
"""NAFF on_command_error override."""
|
||||||
name = ctx.invoke_target
|
name = ctx.invoke_target
|
||||||
self.logger.debug(f"Handling error in {name}: {error}")
|
self.logger.debug(f"Handling error in {name}: {error}")
|
||||||
|
@ -70,7 +80,11 @@ class ErrorMixin:
|
||||||
v = v[97] + "..."
|
v = v[97] + "..."
|
||||||
arg_str += f" - {v}"
|
arg_str += f" - {v}"
|
||||||
callback_args = "\n".join(f" - {i}" for i in args) if args else " None"
|
callback_args = "\n".join(f" - {i}" for i in args) if args else " None"
|
||||||
callback_kwargs = "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None"
|
callback_kwargs = (
|
||||||
|
"\n".join(f" {k}: {v}" for k, v in kwargs.items())
|
||||||
|
if kwargs
|
||||||
|
else " None"
|
||||||
|
)
|
||||||
full_message = ERROR_MSG.format(
|
full_message = ERROR_MSG.format(
|
||||||
guild_name=ctx.guild.name,
|
guild_name=ctx.guild.name,
|
||||||
error_time=error_time,
|
error_time=error_time,
|
||||||
|
@ -82,7 +96,11 @@ class ErrorMixin:
|
||||||
tb = traceback.format_exception(error)
|
tb = traceback.format_exception(error)
|
||||||
if isinstance(error, HTTPException):
|
if isinstance(error, HTTPException):
|
||||||
errors = error.search_for_message(error.errors)
|
errors = error.search_for_message(error.errors)
|
||||||
tb[-1] = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
|
tb[
|
||||||
|
-1
|
||||||
|
] = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(
|
||||||
|
errors
|
||||||
|
)
|
||||||
error_message = "".join(traceback.format_exception(error))
|
error_message = "".join(traceback.format_exception(error))
|
||||||
if len(full_message + error_message) >= 1800:
|
if len(full_message + error_message) >= 1800:
|
||||||
error_message = "\n ".join(error_message.split("\n"))
|
error_message = "\n ".join(error_message.split("\n"))
|
||||||
|
@ -101,7 +119,9 @@ class ErrorMixin:
|
||||||
f"\n```yaml\n{full_message}\n```"
|
f"\n```yaml\n{full_message}\n```"
|
||||||
f"\nException:\n```py\n{error_message}\n```"
|
f"\nException:\n```py\n{error_message}\n```"
|
||||||
)
|
)
|
||||||
await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Whoops! Encountered an error. The error has been logged.", ephemeral=True
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await ctx.defer(ephemeral=True)
|
await ctx.defer(ephemeral=True)
|
||||||
return await super().on_command_error(ctx, error, *args, **kwargs)
|
return await super().on_command_error(ctx, error, *args, **kwargs)
|
||||||
|
|
|
@ -35,16 +35,22 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
||||||
|
|
||||||
async def _sync_domains(self) -> None:
|
async def _sync_domains(self) -> None:
|
||||||
self.logger.debug("Loading phishing domains")
|
self.logger.debug("Loading phishing domains")
|
||||||
async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session:
|
async with ClientSession(
|
||||||
|
headers={"X-Identity": "Discord: zevaryx#5779"}
|
||||||
|
) as session:
|
||||||
response = await session.get("https://phish.sinking.yachts/v2/all")
|
response = await session.get("https://phish.sinking.yachts/v2/all")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
self.phishing_domains = await response.json()
|
self.phishing_domains = await response.json()
|
||||||
self.logger.info(f"Protected from {len(self.phishing_domains)} phishing domains")
|
self.logger.info(
|
||||||
|
f"Protected from {len(self.phishing_domains)} phishing domains"
|
||||||
|
)
|
||||||
|
|
||||||
@listen()
|
@listen()
|
||||||
async def on_startup(self) -> None:
|
async def on_startup(self) -> None:
|
||||||
"""NAFF on_startup override. Prometheus info generated here."""
|
"""NAFF on_startup override. Prometheus info generated here."""
|
||||||
await StaticStat.find_one(StaticStat.name == "jarvis_version", StaticStat.client_id == self.user.id).upsert(
|
await StaticStat.find_one(
|
||||||
|
StaticStat.name == "jarvis_version", StaticStat.client_id == self.user.id
|
||||||
|
).upsert(
|
||||||
Set(
|
Set(
|
||||||
{
|
{
|
||||||
StaticStat.client_name: self.client_name,
|
StaticStat.client_name: self.client_name,
|
||||||
|
@ -67,7 +73,9 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Failed to load anti-phishing", exc_info=e)
|
self.logger.error("Failed to load anti-phishing", exc_info=e)
|
||||||
self.logger.info("Logged in as {}".format(self.user)) # noqa: T001
|
self.logger.info("Logged in as {}".format(self.user)) # noqa: T001
|
||||||
self.logger.info("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001
|
self.logger.info(
|
||||||
|
"Connected to {} guild(s)".format(len(self.guilds))
|
||||||
|
) # noqa: T001
|
||||||
self.logger.info("Current version: {}".format(const.__version__))
|
self.logger.info("Current version: {}".format(const.__version__))
|
||||||
self.logger.info( # noqa: T001
|
self.logger.info( # noqa: T001
|
||||||
"https://discord.com/api/oauth2/authorize?client_id="
|
"https://discord.com/api/oauth2/authorize?client_id="
|
||||||
|
@ -87,7 +95,9 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
||||||
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
|
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||||
)
|
)
|
||||||
global_context_menus = sum(
|
global_context_menus = sum(
|
||||||
1 for _ in self.interaction_tree[cid] if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
1
|
||||||
|
for _ in self.interaction_tree[cid]
|
||||||
|
if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
guild_base_commands += sum(
|
guild_base_commands += sum(
|
||||||
|
@ -96,25 +106,42 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
||||||
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
|
if not isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||||
)
|
)
|
||||||
guild_context_menus += sum(
|
guild_context_menus += sum(
|
||||||
1 for _ in self.interaction_tree[cid] if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
1
|
||||||
|
for _ in self.interaction_tree[cid]
|
||||||
|
if isinstance(self.interaction_tree[cid][_], ContextMenu)
|
||||||
|
)
|
||||||
|
self.logger.info(
|
||||||
|
"Loaded {:>3} global base slash commands".format(global_base_commands)
|
||||||
|
)
|
||||||
|
self.logger.info(
|
||||||
|
"Loaded {:>3} global context menus".format(global_context_menus)
|
||||||
|
)
|
||||||
|
self.logger.info(
|
||||||
|
"Loaded {:>3} guild base slash commands".format(guild_base_commands)
|
||||||
|
)
|
||||||
|
self.logger.info(
|
||||||
|
"Loaded {:>3} guild context menus".format(guild_context_menus)
|
||||||
)
|
)
|
||||||
self.logger.info("Loaded {:>3} global base slash commands".format(global_base_commands))
|
|
||||||
self.logger.info("Loaded {:>3} global context menus".format(global_context_menus))
|
|
||||||
self.logger.info("Loaded {:>3} guild base slash commands".format(guild_base_commands))
|
|
||||||
self.logger.info("Loaded {:>3} guild context menus".format(guild_context_menus))
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.error("interaction_tree not found, try updating NAFF")
|
self.logger.error("interaction_tree not found, try updating NAFF")
|
||||||
|
|
||||||
|
self.logger.debug(self.interaction_tree)
|
||||||
self.logger.debug("Hitting Reminders for faster loads")
|
self.logger.debug("Hitting Reminders for faster loads")
|
||||||
_ = await Reminder.find().to_list(None)
|
_ = await Reminder.find().to_list(None)
|
||||||
|
self.logger.debug("Updating ERAPI")
|
||||||
|
await self.erapi.update_async()
|
||||||
|
|
||||||
# Modlog
|
# Modlog
|
||||||
async def on_command(self, ctx: BaseContext) -> None:
|
async def on_command(self, ctx: BaseContext) -> None:
|
||||||
"""NAFF on_command override."""
|
"""NAFF on_command override."""
|
||||||
name = ctx.invoke_target
|
name = ctx.invoke_target
|
||||||
if not isinstance(ctx.channel, DMChannel) and name not in ["pw"]:
|
if not isinstance(ctx.channel, DMChannel) and name not in ["pw"]:
|
||||||
modlog = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "activitylog")
|
modlog = await Setting.find_one(
|
||||||
ignore = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "log_ignore")
|
Setting.guild == ctx.guild.id, Setting.setting == "activitylog"
|
||||||
|
)
|
||||||
|
ignore = await Setting.find_one(
|
||||||
|
Setting.guild == ctx.guild.id, Setting.setting == "log_ignore"
|
||||||
|
)
|
||||||
if modlog and (ignore and ctx.channel.id not in ignore.value):
|
if modlog and (ignore and ctx.channel.id not in ignore.value):
|
||||||
channel = await ctx.guild.fetch_channel(modlog.value)
|
channel = await ctx.guild.fetch_channel(modlog.value)
|
||||||
args = []
|
args = []
|
||||||
|
@ -146,10 +173,14 @@ class EventMixin(MemberEventMixin, MessageEventMixin, ComponentEventMixin):
|
||||||
fields=fields,
|
fields=fields,
|
||||||
color="#fc9e3f",
|
color="#fc9e3f",
|
||||||
)
|
)
|
||||||
embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url)
|
embed.set_author(
|
||||||
embed.set_footer(text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
name=ctx.author.username, icon_url=ctx.author.display_avatar.url
|
||||||
|
)
|
||||||
|
embed.set_footer(text=f"{ctx.author.user.username} | {ctx.author.id}")
|
||||||
if channel:
|
if channel:
|
||||||
await channel.send(embeds=embed)
|
await channel.send(embeds=embed)
|
||||||
else:
|
else:
|
||||||
self.logger.warning(f"Activitylog channel no longer exists in {ctx.guild.name}, removing")
|
self.logger.warning(
|
||||||
|
f"Activitylog channel no longer exists in {ctx.guild.name}, removing"
|
||||||
|
)
|
||||||
await modlog.delete()
|
await modlog.delete()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""JARVIS component event mixin."""
|
"""JARVIS component event mixin."""
|
||||||
|
from beanie import PydanticObjectId
|
||||||
from interactions import listen
|
from interactions import listen
|
||||||
from interactions.api.events.internal import ButtonPressed
|
from interactions.api.events.internal import ButtonPressed
|
||||||
from interactions.models.discord.embed import EmbedField
|
from interactions.models.discord.embed import EmbedField
|
||||||
|
@ -32,13 +33,21 @@ class ComponentEventMixin:
|
||||||
):
|
):
|
||||||
name, parent = action_data.split("|")[:2]
|
name, parent = action_data.split("|")[:2]
|
||||||
action = Action(action_type=name, parent=parent)
|
action = Action(action_type=name, parent=parent)
|
||||||
note = Note(admin=context.author.id, content="Moderation case opened via message")
|
note = Note(
|
||||||
|
admin=context.author.id,
|
||||||
|
content="Moderation case opened via message",
|
||||||
|
)
|
||||||
modlog = await Modlog.find_one(
|
modlog = await Modlog.find_one(
|
||||||
Modlog.user == user.id, Modlog.guild == context.guild.id, Modlog.open == True
|
Modlog.user == user.id,
|
||||||
|
Modlog.guild == context.guild.id,
|
||||||
|
Modlog.open == True,
|
||||||
)
|
)
|
||||||
if modlog:
|
if modlog:
|
||||||
self.logger.debug("User already has active case in guild")
|
self.logger.debug("User already has active case in guild")
|
||||||
await context.send(f"User already has open case: {modlog.nanoid}", ephemeral=True)
|
await context.send(
|
||||||
|
f"User already has open case: {modlog.nanoid}",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
modlog = Modlog(
|
modlog = Modlog(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
|
@ -59,7 +68,7 @@ class ComponentEventMixin:
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=user.username + "#" + user.discriminator,
|
name=user.username,
|
||||||
icon_url=user.display_avatar.url,
|
icon_url=user.display_avatar.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,7 +84,11 @@ class ComponentEventMixin:
|
||||||
for component in row.components:
|
for component in row.components:
|
||||||
component.disabled = True
|
component.disabled = True
|
||||||
await context.message.edit(components=context.message.components)
|
await context.message.edit(components=context.message.components)
|
||||||
msg = "Cancelled" if context.custom_id == "modcase|no" else "Moderation case opened"
|
msg = (
|
||||||
|
"Cancelled"
|
||||||
|
if context.custom_id == "modcase|no"
|
||||||
|
else "Moderation case opened"
|
||||||
|
)
|
||||||
await context.send(msg)
|
await context.send(msg)
|
||||||
await self.redis.delete(user_key)
|
await self.redis.delete(user_key)
|
||||||
await self.redis.delete(action_key)
|
await self.redis.delete(action_key)
|
||||||
|
@ -101,7 +114,9 @@ class ComponentEventMixin:
|
||||||
await context.send("I'm afraid I can't let you do that", ephemeral=True)
|
await context.send("I'm afraid I can't let you do that", ephemeral=True)
|
||||||
return True # User does not have perms to delete
|
return True # User does not have perms to delete
|
||||||
|
|
||||||
if pin := await Pin.find_one(Pin.pin == context.message.id, Pin.guild == context.guild.id):
|
if pin := await Pin.find_one(
|
||||||
|
Pin.pin == context.message.id, Pin.guild == context.guild.id
|
||||||
|
):
|
||||||
await pin.delete()
|
await pin.delete()
|
||||||
|
|
||||||
await context.message.delete()
|
await context.message.delete()
|
||||||
|
@ -119,8 +134,18 @@ class ComponentEventMixin:
|
||||||
|
|
||||||
what, rid = context.custom_id.split("|")[1:]
|
what, rid = context.custom_id.split("|")[1:]
|
||||||
if what == "rme":
|
if what == "rme":
|
||||||
reminder = await Reminder.find_one(Reminder.id == rid)
|
reminder = await Reminder.find_one(Reminder.id == PydanticObjectId(rid))
|
||||||
if reminder:
|
if reminder:
|
||||||
|
if await Reminder.find_one(
|
||||||
|
Reminder.parent == str(reminder.id),
|
||||||
|
Reminder.user == context.author.id,
|
||||||
|
) or await Reminder.find_one(
|
||||||
|
Reminder.id == reminder.id, Reminder.user == context.author.id
|
||||||
|
):
|
||||||
|
await context.send(
|
||||||
|
"You've already copied this reminder!", ephemeral=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
new_reminder = Reminder(
|
new_reminder = Reminder(
|
||||||
user=context.author.id,
|
user=context.author.id,
|
||||||
channel=context.channel.id,
|
channel=context.channel.id,
|
||||||
|
@ -129,10 +154,15 @@ class ComponentEventMixin:
|
||||||
remind_at=reminder.remind_at,
|
remind_at=reminder.remind_at,
|
||||||
private=reminder.private,
|
private=reminder.private,
|
||||||
active=reminder.active,
|
active=reminder.active,
|
||||||
|
parent=str(reminder.id),
|
||||||
)
|
)
|
||||||
await new_reminder.save()
|
await new_reminder.save()
|
||||||
|
|
||||||
await context.send("Reminder copied!", ephemeral=True)
|
await context.send("Reminder copied!", ephemeral=True)
|
||||||
|
else:
|
||||||
|
await context.send(
|
||||||
|
"That reminder doesn't exist anymore", ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -38,11 +38,11 @@ class MemberEventMixin:
|
||||||
channel = await guild.fetch_channel(log.value)
|
channel = await guild.fetch_channel(log.value)
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Member Left",
|
title="Member Left",
|
||||||
description=f"{user.username}#{user.discriminator} left {guild.name}",
|
description=f"{user.username} left {guild.name}",
|
||||||
fields=[],
|
fields=[],
|
||||||
)
|
)
|
||||||
embed.set_author(name=user.username, icon_url=user.avatar.url)
|
embed.set_author(name=user.username, icon_url=user.avatar.url)
|
||||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
embed.set_footer(text=f"{user.username} | {user.id}")
|
||||||
await channel.send(embeds=embed)
|
await channel.send(embeds=embed)
|
||||||
|
|
||||||
async def process_verify(self, before: Member, after: Member) -> Embed:
|
async def process_verify(self, before: Member, after: Member) -> Embed:
|
||||||
|
@ -56,7 +56,7 @@ class MemberEventMixin:
|
||||||
admin_text = "[N/A]"
|
admin_text = "[N/A]"
|
||||||
if admin := await after.guild.fet_member(audit_event.user_id):
|
if admin := await after.guild.fet_member(audit_event.user_id):
|
||||||
admin_mention = admin.mention
|
admin_mention = admin.mention
|
||||||
admin_text = f"{admin.username}#{admin.discriminator}"
|
admin_text = f"{admin.username}"
|
||||||
fields = (
|
fields = (
|
||||||
EmbedField(name="Moderator", value=f"{admin_mention} ({admin_text})"),
|
EmbedField(name="Moderator", value=f"{admin_mention} ({admin_text})"),
|
||||||
EmbedField(name="Reason", value=audit_event.reason),
|
EmbedField(name="Reason", value=audit_event.reason),
|
||||||
|
@ -67,7 +67,7 @@ class MemberEventMixin:
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
embed.set_author(name=after.display_name, icon_url=after.display_avatar.url)
|
embed.set_author(name=after.display_name, icon_url=after.display_avatar.url)
|
||||||
embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}")
|
embed.set_footer(text=f"{after.username} | {after.id}")
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
async def process_rolechange(self, before: Member, after: Member) -> Embed:
|
async def process_rolechange(self, before: Member, after: Member) -> Embed:
|
||||||
|
@ -98,14 +98,13 @@ class MemberEventMixin:
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
embed.set_author(name=after.display_name, icon_url=after.display_avatar.url)
|
embed.set_author(name=after.display_name, icon_url=after.display_avatar.url)
|
||||||
embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}")
|
embed.set_footer(text=f"{after.username} | {after.id}")
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
async def process_rename(self, before: Member, after: Member) -> None:
|
async def process_rename(self, before: Member, after: Member) -> None:
|
||||||
"""Process name change."""
|
"""Process name change."""
|
||||||
if (
|
if (
|
||||||
before.nickname == after.nickname
|
before.nickname == after.nickname
|
||||||
and before.discriminator == after.discriminator
|
|
||||||
and before.username == after.username
|
and before.username == after.username
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
@ -113,9 +112,9 @@ class MemberEventMixin:
|
||||||
fields = (
|
fields = (
|
||||||
EmbedField(
|
EmbedField(
|
||||||
name="Before",
|
name="Before",
|
||||||
value=f"{before.display_name} ({before.username}#{before.discriminator})",
|
value=f"{before.display_name} ({before.username})",
|
||||||
),
|
),
|
||||||
EmbedField(name="After", value=f"{after.display_name} ({after.username}#{after.discriminator})"),
|
EmbedField(name="After", value=f"{after.display_name} ({after.username})"),
|
||||||
)
|
)
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="User Renamed",
|
title="User Renamed",
|
||||||
|
@ -124,7 +123,7 @@ class MemberEventMixin:
|
||||||
color="#fc9e3f",
|
color="#fc9e3f",
|
||||||
)
|
)
|
||||||
embed.set_author(name=after.display_name, icon_url=after.display_avatar.url)
|
embed.set_author(name=after.display_name, icon_url=after.display_avatar.url)
|
||||||
embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}")
|
embed.set_footer(text=f"{after.username} | {after.id}")
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
@listen()
|
@listen()
|
||||||
|
|
|
@ -40,7 +40,9 @@ class MessageEventMixin:
|
||||||
)
|
)
|
||||||
if autopurge:
|
if autopurge:
|
||||||
if not message.author.has_permission(Permissions.ADMINISTRATOR):
|
if not message.author.has_permission(Permissions.ADMINISTRATOR):
|
||||||
self.logger.debug(f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}")
|
self.logger.debug(
|
||||||
|
f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}"
|
||||||
|
)
|
||||||
await message.delete(delay=autopurge.delay)
|
await message.delete(delay=autopurge.delay)
|
||||||
|
|
||||||
async def autoreact(self, message: Message) -> None:
|
async def autoreact(self, message: Message) -> None:
|
||||||
|
@ -50,13 +52,15 @@ class MessageEventMixin:
|
||||||
Autoreact.channel == message.channel.id,
|
Autoreact.channel == message.channel.id,
|
||||||
)
|
)
|
||||||
if autoreact:
|
if autoreact:
|
||||||
self.logger.debug(f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}")
|
self.logger.debug(
|
||||||
|
f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}"
|
||||||
|
)
|
||||||
for reaction in autoreact.reactions:
|
for reaction in autoreact.reactions:
|
||||||
await message.add_reaction(reaction)
|
await message.add_reaction(reaction)
|
||||||
if autoreact.thread:
|
if autoreact.thread:
|
||||||
name = message.content.replace("\n", " ")
|
name = message.content.replace("\n", " ")
|
||||||
name = re.sub(r"<:\w+:(\d+)>", "", name)
|
name = re.sub(r"<:\w+:(\d+)>", "", name)
|
||||||
if len(name) > 100:
|
if len(name) >= 100:
|
||||||
name = name[:97] + "..."
|
name = name[:97] + "..."
|
||||||
await message.create_thread(name=message.content, reason="Autoreact")
|
await message.create_thread(name=message.content, reason="Autoreact")
|
||||||
|
|
||||||
|
@ -70,7 +74,9 @@ class MessageEventMixin:
|
||||||
# )
|
# )
|
||||||
content = re.sub(r"\s+", "", message.content)
|
content = re.sub(r"\s+", "", message.content)
|
||||||
match = invites.search(content)
|
match = invites.search(content)
|
||||||
setting = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "noinvite")
|
setting = await Setting.find_one(
|
||||||
|
Setting.guild == message.guild.id, Setting.setting == "noinvite"
|
||||||
|
)
|
||||||
if not setting:
|
if not setting:
|
||||||
setting = Setting(guild=message.guild.id, setting="noinvite", value=True)
|
setting = Setting(guild=message.guild.id, setting="noinvite", value=True)
|
||||||
await setting.save()
|
await setting.save()
|
||||||
|
@ -78,12 +84,20 @@ class MessageEventMixin:
|
||||||
guild_invites = [x.code for x in await message.guild.fetch_invites()]
|
guild_invites = [x.code for x in await message.guild.fetch_invites()]
|
||||||
if message.guild.vanity_url_code:
|
if message.guild.vanity_url_code:
|
||||||
guild_invites.append(message.guild.vanity_url_code)
|
guild_invites.append(message.guild.vanity_url_code)
|
||||||
allowed = guild_invites + ["dbrand", "VtgZntXcnZ", "gPfYGbvTCE", "interactions", "NTSHu97tHg"]
|
allowed = guild_invites + [
|
||||||
is_mod = message.author.has_permission(Permissions.MANAGE_GUILD) or message.author.has_permission(
|
"dbrand",
|
||||||
Permissions.ADMINISTRATOR
|
"VtgZntXcnZ",
|
||||||
)
|
"gPfYGbvTCE",
|
||||||
|
"interactions",
|
||||||
|
"NTSHu97tHg",
|
||||||
|
]
|
||||||
|
is_mod = message.author.has_permission(
|
||||||
|
Permissions.MANAGE_GUILD
|
||||||
|
) or message.author.has_permission(Permissions.ADMINISTRATOR)
|
||||||
if (m := match.group(1)) not in allowed and setting.value and not is_mod:
|
if (m := match.group(1)) not in allowed and setting.value and not is_mod:
|
||||||
self.logger.debug(f"Removing non-allowed invite `{m}` from {message.guild.id}")
|
self.logger.debug(
|
||||||
|
f"Removing non-allowed invite `{m}` from {message.guild.id}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -116,8 +130,7 @@ class MessageEventMixin:
|
||||||
|
|
||||||
async def filters(self, message: Message) -> None:
|
async def filters(self, message: Message) -> None:
|
||||||
"""Handle filter evennts."""
|
"""Handle filter evennts."""
|
||||||
filters = Filter.find(Filter.guild == message.guild.id)
|
async for item in Filter.find(Filter.guild == message.guild.id):
|
||||||
async for item in filters:
|
|
||||||
for f in item.filters:
|
for f in item.filters:
|
||||||
if re.search(f, message.content, re.IGNORECASE):
|
if re.search(f, message.content, re.IGNORECASE):
|
||||||
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||||
|
@ -139,7 +152,9 @@ class MessageEventMixin:
|
||||||
value=1,
|
value=1,
|
||||||
)
|
)
|
||||||
await Stat(meta=md, name="warning").insert()
|
await Stat(meta=md, name="warning").insert()
|
||||||
embed = warning_embed(message.author, "Sent a message with a filtered word", self.user)
|
embed = warning_embed(
|
||||||
|
message.author, "Sent a message with a filtered word", self.user
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await message.reply(embeds=embed)
|
await message.reply(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -153,24 +168,26 @@ class MessageEventMixin:
|
||||||
|
|
||||||
async def massmention(self, message: Message) -> None:
|
async def massmention(self, message: Message) -> None:
|
||||||
"""Handle massmention events."""
|
"""Handle massmention events."""
|
||||||
massmention = await Setting.find_one(
|
massmention: Setting = await Setting.find_one(
|
||||||
Setting.guild == message.guild.id,
|
Setting.guild == message.guild.id,
|
||||||
Setting.setting == "massmention",
|
Setting.setting == "massmention",
|
||||||
)
|
)
|
||||||
|
|
||||||
is_mod = message.author.has_permission(Permissions.MANAGE_GUILD) or message.author.has_permission(
|
is_mod = message.author.has_permission(
|
||||||
Permissions.ADMINISTRATOR
|
Permissions.MANAGE_GUILD
|
||||||
)
|
) or message.author.has_permission(Permissions.ADMINISTRATOR)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
massmention
|
massmention
|
||||||
and massmention.value > 0 # noqa: W503
|
and int(massmention.value) > 0 # noqa: W503
|
||||||
and len(message._mention_ids + message._mention_roles) # noqa: W503
|
and len(message._mention_ids + message._mention_roles) # noqa: W503
|
||||||
- (1 if message.author.id in message._mention_ids else 0) # noqa: W503
|
- (1 if message.author.id in message._mention_ids else 0) # noqa: W503
|
||||||
> massmention.value # noqa: W503
|
> massmention.value # noqa: W503
|
||||||
and not is_mod # noqa: W503
|
and not is_mod # noqa: W503
|
||||||
):
|
):
|
||||||
self.logger.debug(f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}")
|
self.logger.debug(
|
||||||
|
f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}"
|
||||||
|
)
|
||||||
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||||
await Warning(
|
await Warning(
|
||||||
active=True,
|
active=True,
|
||||||
|
@ -202,11 +219,20 @@ class MessageEventMixin:
|
||||||
if message.author.has_permission(Permissions.MANAGE_GUILD):
|
if message.author.has_permission(Permissions.MANAGE_GUILD):
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Failed to get permissions, pretending check failed", exc_info=e)
|
self.logger.error(
|
||||||
|
"Failed to get permissions, pretending check failed", exc_info=e
|
||||||
|
)
|
||||||
|
|
||||||
if await Roleping.find(Roleping.guild == message.guild.id, Roleping.active == True).count() == 0:
|
if (
|
||||||
|
await Roleping.find(
|
||||||
|
Roleping.guild == message.guild.id, Roleping.active == True
|
||||||
|
).count()
|
||||||
|
== 0
|
||||||
|
):
|
||||||
return
|
return
|
||||||
rolepings = await Roleping.find(Roleping.guild == message.guild.id, Roleping.active == True).to_list()
|
rolepings = await Roleping.find(
|
||||||
|
Roleping.guild == message.guild.id, Roleping.active == True
|
||||||
|
).to_list()
|
||||||
|
|
||||||
# Get all role IDs involved with message
|
# Get all role IDs involved with message
|
||||||
roles = [x.id async for x in message.mention_roles]
|
roles = [x.id async for x in message.mention_roles]
|
||||||
|
@ -230,7 +256,9 @@ class MessageEventMixin:
|
||||||
|
|
||||||
# Check if user in a bypass list
|
# Check if user in a bypass list
|
||||||
def check_has_role(roleping: Roleping) -> bool:
|
def check_has_role(roleping: Roleping) -> bool:
|
||||||
return any(role.id in roleping.bypass.roles for role in message.author.roles)
|
return any(
|
||||||
|
role.id in roleping.bypass.roles for role in message.author.roles
|
||||||
|
)
|
||||||
|
|
||||||
user_has_bypass = False
|
user_has_bypass = False
|
||||||
for roleping in rolepings:
|
for roleping in rolepings:
|
||||||
|
@ -241,8 +269,15 @@ class MessageEventMixin:
|
||||||
user_has_bypass = True
|
user_has_bypass = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass:
|
if (
|
||||||
self.logger.debug(f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}")
|
role_in_rolepings
|
||||||
|
and user_missing_role
|
||||||
|
and not user_is_admin
|
||||||
|
and not user_has_bypass
|
||||||
|
):
|
||||||
|
self.logger.debug(
|
||||||
|
f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}"
|
||||||
|
)
|
||||||
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||||
await Warning(
|
await Warning(
|
||||||
active=True,
|
active=True,
|
||||||
|
@ -262,7 +297,11 @@ class MessageEventMixin:
|
||||||
value=1,
|
value=1,
|
||||||
)
|
)
|
||||||
await Stat(meta=md, name="warning").insert()
|
await Stat(meta=md, name="warning").insert()
|
||||||
embed = warning_embed(message.author, "Pinged a blocked role/user with a blocked role", self.user)
|
embed = warning_embed(
|
||||||
|
message.author,
|
||||||
|
"Pinged a blocked role/user with a blocked role",
|
||||||
|
self.user,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await message.channel.send(embeds=embed)
|
await message.channel.send(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -318,8 +357,16 @@ class MessageEventMixin:
|
||||||
fields=[EmbedField(name="URL", value=m)],
|
fields=[EmbedField(name="URL", value=m)],
|
||||||
)
|
)
|
||||||
|
|
||||||
valid_button = Button(style=ButtonStyle.GREEN, emoji="✔️", custom_id=f"pl|valid|{pl.id}")
|
valid_button = Button(
|
||||||
invalid_button = Button(style=ButtonStyle.RED, emoji="✖️", custom_id=f"pl|invalid|{pl.id}")
|
style=ButtonStyle.GREEN,
|
||||||
|
emoji="✔️",
|
||||||
|
custom_id=f"pl|valid|{pl.id}",
|
||||||
|
)
|
||||||
|
invalid_button = Button(
|
||||||
|
style=ButtonStyle.RED,
|
||||||
|
emoji="✖️",
|
||||||
|
custom_id=f"pl|invalid|{pl.id}",
|
||||||
|
)
|
||||||
|
|
||||||
channel = await self.fetch_channel(1026918337554423868)
|
channel = await self.fetch_channel(1026918337554423868)
|
||||||
|
|
||||||
|
@ -372,7 +419,9 @@ class MessageEventMixin:
|
||||||
value=1,
|
value=1,
|
||||||
)
|
)
|
||||||
await Stat(meta=md, name="warning").insert()
|
await Stat(meta=md, name="warning").insert()
|
||||||
reasons = ", ".join(f"{m['source']}: {m['type']}" for m in data["matches"])
|
reasons = ", ".join(
|
||||||
|
f"{m['source']}: {m['type']}" for m in data["matches"]
|
||||||
|
)
|
||||||
embed = warning_embed(message.author, reasons, self.user)
|
embed = warning_embed(message.author, reasons, self.user)
|
||||||
try:
|
try:
|
||||||
await message.channel.send(embeds=embed)
|
await message.channel.send(embeds=embed)
|
||||||
|
@ -394,8 +443,16 @@ class MessageEventMixin:
|
||||||
fields=[EmbedField(name="URL", value=m)],
|
fields=[EmbedField(name="URL", value=m)],
|
||||||
)
|
)
|
||||||
|
|
||||||
valid_button = Button(style=ButtonStyle.GREEN, emoji="✔️", custom_id=f"pl|valid|{pl.id}")
|
valid_button = Button(
|
||||||
invalid_button = Button(style=ButtonStyle.RED, emoji="✖️", custom_id=f"pl|invalid|{pl.id}")
|
style=ButtonStyle.GREEN,
|
||||||
|
emoji="✔️",
|
||||||
|
custom_id=f"pl|valid|{pl.id}",
|
||||||
|
)
|
||||||
|
invalid_button = Button(
|
||||||
|
style=ButtonStyle.RED,
|
||||||
|
emoji="✖️",
|
||||||
|
custom_id=f"pl|invalid|{pl.id}",
|
||||||
|
)
|
||||||
|
|
||||||
channel = await self.fetch_channel(1026918337554423868)
|
channel = await self.fetch_channel(1026918337554423868)
|
||||||
|
|
||||||
|
@ -410,7 +467,9 @@ class MessageEventMixin:
|
||||||
"""Timeout a user."""
|
"""Timeout a user."""
|
||||||
expires_at = datetime.now(tz=timezone.utc) + timedelta(minutes=30)
|
expires_at = datetime.now(tz=timezone.utc) + timedelta(minutes=30)
|
||||||
try:
|
try:
|
||||||
await user.timeout(communication_disabled_until=expires_at, reason="Phishing link")
|
await user.timeout(
|
||||||
|
communication_disabled_until=expires_at, reason="Phishing link"
|
||||||
|
)
|
||||||
await Mute(
|
await Mute(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
reason="Auto mute for harmful link",
|
reason="Auto mute for harmful link",
|
||||||
|
@ -431,7 +490,7 @@ class MessageEventMixin:
|
||||||
)
|
)
|
||||||
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
|
||||||
embed.set_thumbnail(url=user.display_avatar.url)
|
embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}")
|
embed.set_footer(text=f"{user.username} | {user.id}")
|
||||||
await channel.send(embeds=embed)
|
await channel.send(embeds=embed)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -459,10 +518,20 @@ class MessageEventMixin:
|
||||||
before = event.before
|
before = event.before
|
||||||
after = event.after
|
after = event.after
|
||||||
if not after.author.bot:
|
if not after.author.bot:
|
||||||
modlog = await Setting.find_one(Setting.guild == after.guild.id, Setting.setting == "activitylog")
|
modlog = await Setting.find_one(
|
||||||
ignore = await Setting.find_one(Setting.guild == after.guild.id, Setting.setting == "log_ignore")
|
Setting.guild == after.guild.id, Setting.setting == "activitylog"
|
||||||
if modlog and (not ignore or (ignore and after.channel.id not in ignore.value)):
|
)
|
||||||
if not before or before.content == after.content or before.content is None:
|
ignore = await Setting.find_one(
|
||||||
|
Setting.guild == after.guild.id, Setting.setting == "log_ignore"
|
||||||
|
)
|
||||||
|
if modlog and (
|
||||||
|
not ignore or (ignore and after.channel.id not in ignore.value)
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
not before
|
||||||
|
or before.content == after.content
|
||||||
|
or before.content is None
|
||||||
|
):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
channel = before.guild.get_channel(modlog.value)
|
channel = before.guild.get_channel(modlog.value)
|
||||||
|
@ -491,7 +560,9 @@ class MessageEventMixin:
|
||||||
icon_url=after.author.display_avatar.url,
|
icon_url=after.author.display_avatar.url,
|
||||||
url=after.jump_url,
|
url=after.jump_url,
|
||||||
)
|
)
|
||||||
embed.set_footer(text=f"{after.author.username}#{after.author.discriminator} | {after.author.id}")
|
embed.set_footer(
|
||||||
|
text=f"{after.author.username} | {after.author.id}"
|
||||||
|
)
|
||||||
await channel.send(embeds=embed)
|
await channel.send(embeds=embed)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
|
@ -512,9 +583,15 @@ class MessageEventMixin:
|
||||||
async def on_message_delete(self, event: MessageDelete) -> None:
|
async def on_message_delete(self, event: MessageDelete) -> None:
|
||||||
"""Process on_message_delete events."""
|
"""Process on_message_delete events."""
|
||||||
message = event.message
|
message = event.message
|
||||||
modlog = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "activitylog")
|
modlog = await Setting.find_one(
|
||||||
ignore = await Setting.find_one(Setting.guild == message.guild.id, Setting.setting == "log_ignore")
|
Setting.guild == message.guild.id, Setting.setting == "activitylog"
|
||||||
if modlog and (not ignore or (ignore and message.channel.id not in ignore.value)):
|
)
|
||||||
|
ignore = await Setting.find_one(
|
||||||
|
Setting.guild == message.guild.id, Setting.setting == "log_ignore"
|
||||||
|
)
|
||||||
|
if modlog and (
|
||||||
|
not ignore or (ignore and message.channel.id not in ignore.value)
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
content = message.content or "N/A"
|
content = message.content or "N/A"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -523,7 +600,9 @@ class MessageEventMixin:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if message.attachments:
|
if message.attachments:
|
||||||
value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments])
|
value = "\n".join(
|
||||||
|
[f"[{x.filename}]({x.url})" for x in message.attachments]
|
||||||
|
)
|
||||||
fields.append(
|
fields.append(
|
||||||
EmbedField(
|
EmbedField(
|
||||||
name="Attachments",
|
name="Attachments",
|
||||||
|
@ -533,7 +612,9 @@ class MessageEventMixin:
|
||||||
)
|
)
|
||||||
|
|
||||||
if message.sticker_items:
|
if message.sticker_items:
|
||||||
value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items])
|
value = "\n".join(
|
||||||
|
[f"Sticker: {x.name}" for x in message.sticker_items]
|
||||||
|
)
|
||||||
fields.append(
|
fields.append(
|
||||||
EmbedField(
|
EmbedField(
|
||||||
name="Stickers",
|
name="Stickers",
|
||||||
|
@ -566,8 +647,10 @@ class MessageEventMixin:
|
||||||
url=message.jump_url,
|
url=message.jump_url,
|
||||||
)
|
)
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text=(f"{message.author.username}#{message.author.discriminator} | " f"{message.author.id}")
|
text=(f"{message.author.username} | " f"{message.author.id}")
|
||||||
)
|
)
|
||||||
await channel.send(embeds=embed)
|
await channel.send(embeds=embed)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}")
|
self.logger.warning(
|
||||||
|
f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}"
|
||||||
|
)
|
||||||
|
|
|
@ -7,7 +7,9 @@ from interactions.models.internal.tasks.triggers import IntervalTrigger
|
||||||
class TaskMixin:
|
class TaskMixin:
|
||||||
@Task.create(IntervalTrigger(minutes=1))
|
@Task.create(IntervalTrigger(minutes=1))
|
||||||
async def _update_domains(self) -> None:
|
async def _update_domains(self) -> None:
|
||||||
async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session:
|
async with ClientSession(
|
||||||
|
headers={"X-Identity": "Discord: zevaryx#5779"}
|
||||||
|
) as session:
|
||||||
response = await session.get("https://phish.sinking.yachts/v2/recent/60")
|
response = await session.get("https://phish.sinking.yachts/v2/recent/60")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
|
@ -31,3 +33,7 @@ class TaskMixin:
|
||||||
sub -= 1
|
sub -= 1
|
||||||
self.phishing_domains.remove(domain)
|
self.phishing_domains.remove(domain)
|
||||||
self.logger.info(f"[antiphish] {add} additions, {sub} removals")
|
self.logger.info(f"[antiphish] {add} additions, {sub} removals")
|
||||||
|
|
||||||
|
@Task.create(IntervalTrigger(minutes=30))
|
||||||
|
async def _update_currencies(self) -> None:
|
||||||
|
await self.erapi.update_async()
|
||||||
|
|
|
@ -41,10 +41,13 @@ class BanCog(ModcaseCog):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Apply a Discord ban."""
|
"""Apply a Discord ban."""
|
||||||
await ctx.guild.ban(user, reason=reason, delete_message_seconds=delete_history)
|
await ctx.guild.ban(user, reason=reason, delete_message_seconds=delete_history)
|
||||||
|
discrim = user.discriminator
|
||||||
|
if discrim == 0:
|
||||||
|
discrim = None
|
||||||
b = Ban(
|
b = Ban(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
username=user.username,
|
username=user.username,
|
||||||
discrim=user.discriminator,
|
discrim=discrim,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
|
@ -65,13 +68,18 @@ class BanCog(ModcaseCog):
|
||||||
|
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
async def discord_apply_unban(self, ctx: InteractionContext, user: User, reason: str) -> None:
|
async def discord_apply_unban(
|
||||||
|
self, ctx: InteractionContext, user: User, reason: str
|
||||||
|
) -> None:
|
||||||
"""Apply a Discord unban."""
|
"""Apply a Discord unban."""
|
||||||
await ctx.guild.unban(user, reason=reason)
|
await ctx.guild.unban(user, reason=reason)
|
||||||
|
discrim = user.discriminator
|
||||||
|
if discrim == 0:
|
||||||
|
discrim = None
|
||||||
u = Unban(
|
u = Unban(
|
||||||
user=user.id,
|
user=user.id,
|
||||||
username=user.username,
|
username=user.username,
|
||||||
discrim=user.discriminator,
|
discrim=discrim,
|
||||||
guild=ctx.guild.id,
|
guild=ctx.guild.id,
|
||||||
admin=ctx.author.id,
|
admin=ctx.author.id,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
|
@ -83,8 +91,15 @@ class BanCog(ModcaseCog):
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
@slash_command(name="ban", description="Ban a user")
|
@slash_command(name="ban", description="Ban a user")
|
||||||
@slash_option(name="user", description="User to ban", opt_type=OptionType.USER, required=True)
|
@slash_option(
|
||||||
@slash_option(name="reason", description="Ban reason", opt_type=OptionType.STRING, required=True)
|
name="user", description="User to ban", opt_type=OptionType.USER, required=True
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="reason",
|
||||||
|
description="Ban reason",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="btype",
|
name="btype",
|
||||||
description="Ban type",
|
description="Ban type",
|
||||||
|
@ -131,14 +146,23 @@ class BanCog(ModcaseCog):
|
||||||
await ctx.send("You cannot set a temp ban to > 1 month", ephemeral=True)
|
await ctx.send("You cannot set a temp ban to > 1 month", ephemeral=True)
|
||||||
return
|
return
|
||||||
if delete_history and not time_pattern.match(delete_history):
|
if delete_history and not time_pattern.match(delete_history):
|
||||||
await ctx.send("Invalid time string, please follow example: 1w 3d 7h 5m 20s", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Invalid time string, please follow example: 1w 3d 7h 5m 20s",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if len(reason) > 100:
|
if len(reason) > 100:
|
||||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if delete_history:
|
if delete_history:
|
||||||
units = {"w": "weeks", "d": "days", "h": "hours", "m": "minutes", "s": "seconds"}
|
units = {
|
||||||
|
"w": "weeks",
|
||||||
|
"d": "days",
|
||||||
|
"h": "hours",
|
||||||
|
"m": "minutes",
|
||||||
|
"s": "seconds",
|
||||||
|
}
|
||||||
delta = {"weeks": 0, "days": 0, "hours": 0, "minutes": 0, "seconds": 0}
|
delta = {"weeks": 0, "days": 0, "hours": 0, "minutes": 0, "seconds": 0}
|
||||||
delete_history = delete_history.strip().lower()
|
delete_history = delete_history.strip().lower()
|
||||||
if delete_history:
|
if delete_history:
|
||||||
|
@ -148,7 +172,10 @@ class BanCog(ModcaseCog):
|
||||||
delete_history = int(timedelta(**delta).total_seconds())
|
delete_history = int(timedelta(**delta).total_seconds())
|
||||||
|
|
||||||
if delete_history > 604800:
|
if delete_history > 604800:
|
||||||
await ctx.send("Delete history cannot be greater than 7 days (604800 seconds)", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Delete history cannot be greater than 7 days (604800 seconds)",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -158,7 +185,9 @@ class BanCog(ModcaseCog):
|
||||||
mtype = "perma"
|
mtype = "perma"
|
||||||
|
|
||||||
guild_name = ctx.guild.name
|
guild_name = ctx.guild.name
|
||||||
user_message = f"You have been {mtype}banned from {guild_name}." + f" Reason:\n{reason}"
|
user_message = (
|
||||||
|
f"You have been {mtype}banned from {guild_name}." + f" Reason:\n{reason}"
|
||||||
|
)
|
||||||
if mtype == "temp":
|
if mtype == "temp":
|
||||||
user_message += f"\nDuration: {duration} hours"
|
user_message += f"\nDuration: {duration} hours"
|
||||||
|
|
||||||
|
@ -187,7 +216,9 @@ class BanCog(ModcaseCog):
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.warn(f"Failed to send ban embed to {user.id}")
|
self.logger.warn(f"Failed to send ban embed to {user.id}")
|
||||||
try:
|
try:
|
||||||
await self.discord_apply_ban(ctx, reason, user, duration, active, mtype, delete_history or 0)
|
await self.discord_apply_ban(
|
||||||
|
ctx, reason, user, duration, active, mtype, delete_history or 0
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", ephemeral=True)
|
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
@ -196,8 +227,18 @@ class BanCog(ModcaseCog):
|
||||||
await ctx.guild.unban(user, reason="Ban was softban")
|
await ctx.guild.unban(user, reason="Ban was softban")
|
||||||
|
|
||||||
@slash_command(name="unban", description="Unban a user")
|
@slash_command(name="unban", description="Unban a user")
|
||||||
@slash_option(name="user", description="User to unban", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
@slash_option(name="reason", description="Unban reason", opt_type=OptionType.STRING, required=True)
|
name="user",
|
||||||
|
description="User to unban",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="reason",
|
||||||
|
description="Unban reason",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _unban(
|
async def _unban(
|
||||||
self,
|
self,
|
||||||
|
@ -226,7 +267,8 @@ class BanCog(ModcaseCog):
|
||||||
user, discrim = user.split("#")
|
user, discrim = user.split("#")
|
||||||
if discrim:
|
if discrim:
|
||||||
discord_ban_info = find(
|
discord_ban_info = find(
|
||||||
lambda x: x.user.username == user and x.user.discriminator == discrim,
|
lambda x: x.user.username == user
|
||||||
|
and x.user.discriminator == discrim,
|
||||||
bans,
|
bans,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -235,9 +277,16 @@ class BanCog(ModcaseCog):
|
||||||
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.username, ban.user.id, ban.reason))
|
active_bans.append(
|
||||||
|
"{0} ({1}): {2}".format(
|
||||||
|
ban.user.username, ban.user.id, ban.reason
|
||||||
|
)
|
||||||
|
)
|
||||||
ab_message = "\n".join(active_bans)
|
ab_message = "\n".join(active_bans)
|
||||||
message = "More than one result. " f"Please use one of the following IDs:\n```{ab_message}\n```"
|
message = (
|
||||||
|
"More than one result. "
|
||||||
|
f"Please use one of the following IDs:\n```{ab_message}\n```"
|
||||||
|
)
|
||||||
await ctx.send(message)
|
await ctx.send(message)
|
||||||
return
|
return
|
||||||
discord_ban_info = results[0]
|
discord_ban_info = results[0]
|
||||||
|
@ -259,7 +308,7 @@ class BanCog(ModcaseCog):
|
||||||
if discrim:
|
if discrim:
|
||||||
search["discrim"] = discrim
|
search["discrim"] = discrim
|
||||||
|
|
||||||
database_ban_info = await Ban.find_one(*[getattr(Ban, k) == v for k, v in search.items])
|
database_ban_info = await Ban.find_one(search)
|
||||||
|
|
||||||
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}", ephemeral=True)
|
await ctx.send(f"Unable to find user {orig_user}", ephemeral=True)
|
||||||
|
@ -282,7 +331,9 @@ class BanCog(ModcaseCog):
|
||||||
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."
|
||||||
|
)
|
||||||
|
|
||||||
bans = SlashCommand(name="bans", description="User bans")
|
bans = SlashCommand(name="bans", description="User bans")
|
||||||
|
|
||||||
|
@ -306,14 +357,16 @@ class BanCog(ModcaseCog):
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: bool = True) -> None:
|
async def _bans_list(
|
||||||
|
self, ctx: InteractionContext, btype: int = 0, active: bool = True
|
||||||
|
) -> None:
|
||||||
types = [0, "perm", "temp", "soft"]
|
types = [0, "perm", "temp", "soft"]
|
||||||
search = {"guild": ctx.guild.id}
|
search = {"guild": ctx.guild.id}
|
||||||
if active:
|
if active:
|
||||||
search["active"] = True
|
search["active"] = True
|
||||||
if btype > 0:
|
if btype > 0:
|
||||||
search["type"] = types[btype]
|
search["type"] = types[btype]
|
||||||
bans = await Ban.find(*[getattr(Ban, k) == v for k, v in search.items]).sort(-Ban.created_at).to_list()
|
bans = await Ban.find(search).sort(-Ban.created_at).to_list()
|
||||||
db_bans = []
|
db_bans = []
|
||||||
fields = []
|
fields = []
|
||||||
for ban in bans:
|
for ban in bans:
|
||||||
|
@ -322,7 +375,7 @@ class BanCog(ModcaseCog):
|
||||||
ban.username = user.username if user else "[deleted user]"
|
ban.username = user.username if user else "[deleted user]"
|
||||||
fields.append(
|
fields.append(
|
||||||
EmbedField(
|
EmbedField(
|
||||||
name=f"Username: {ban.username}#{ban.discrim}",
|
name=f"Username: {ban.username}",
|
||||||
value=(
|
value=(
|
||||||
f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
|
f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
|
||||||
f"User ID: {ban.user}\n"
|
f"User ID: {ban.user}\n"
|
||||||
|
@ -339,7 +392,7 @@ class BanCog(ModcaseCog):
|
||||||
if ban.user.id not in db_bans:
|
if ban.user.id not in db_bans:
|
||||||
fields.append(
|
fields.append(
|
||||||
EmbedField(
|
EmbedField(
|
||||||
name=f"Username: {ban.user.username}#" + f"{ban.user.discriminator}",
|
name=f"Username: {ban.user.username}",
|
||||||
value=(
|
value=(
|
||||||
f"Date: [unknown]\n"
|
f"Date: [unknown]\n"
|
||||||
f"User ID: {ban.user.id}\n"
|
f"User ID: {ban.user.id}\n"
|
||||||
|
@ -368,7 +421,9 @@ class BanCog(ModcaseCog):
|
||||||
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])
|
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)
|
||||||
|
|
||||||
|
|
|
@ -31,18 +31,22 @@ class FilterCog(Extension):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.cache: Dict[int, List[str]] = {}
|
self.cache: Dict[int, List[str]] = {}
|
||||||
|
|
||||||
async def _edit_filter(self, ctx: InteractionContext, name: str, search: bool = False) -> None:
|
async def _edit_filter(
|
||||||
|
self, ctx: InteractionContext, name: str, search: bool = False
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
content = ""
|
content = ""
|
||||||
f: Filter = None
|
f: Filter = None
|
||||||
if search:
|
if search:
|
||||||
if f := await Filter.find_one(Filter.name == name, Filter.guild == ctx.guild.id):
|
if f := await Filter.find_one(
|
||||||
|
Filter.name == name, Filter.guild == ctx.guild.id
|
||||||
|
):
|
||||||
content = "\n".join(f.filters)
|
content = "\n".join(f.filters)
|
||||||
|
|
||||||
kw = "Updating" if search else "Creating"
|
kw = "Updating" if search else "Creating"
|
||||||
|
|
||||||
modal = Modal(
|
modal = Modal(
|
||||||
title=f'{kw} filter "{name}"',
|
*[
|
||||||
components=[
|
|
||||||
InputText(
|
InputText(
|
||||||
label="Filter (one statement per line)",
|
label="Filter (one statement per line)",
|
||||||
placeholder="" if content else "i.e. $bad_word^",
|
placeholder="" if content else "i.e. $bad_word^",
|
||||||
|
@ -52,10 +56,13 @@ class FilterCog(Extension):
|
||||||
style=TextStyles.PARAGRAPH,
|
style=TextStyles.PARAGRAPH,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
title=f'{kw} filter "{name}"',
|
||||||
)
|
)
|
||||||
await ctx.send_modal(modal)
|
await ctx.send_modal(modal)
|
||||||
try:
|
try:
|
||||||
data = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
data = await self.bot.wait_for_modal(
|
||||||
|
modal, author=ctx.author.id, timeout=60 * 5
|
||||||
|
)
|
||||||
filters = data.responses.get("filters").split("\n")
|
filters = data.responses.get("filters").split("\n")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return
|
return
|
||||||
|
@ -75,7 +82,9 @@ class FilterCog(Extension):
|
||||||
content = content.splitlines()
|
content = content.splitlines()
|
||||||
diff = "\n".join(difflib.ndiff(content, filters)).replace("`", "\u200b`")
|
diff = "\n".join(difflib.ndiff(content, filters)).replace("`", "\u200b`")
|
||||||
|
|
||||||
await data.send(f"Filter `{new_name}` has been updated:\n\n```diff\n{diff}\n```")
|
await data.send(
|
||||||
|
f"Filter `{new_name}` has been updated:\n\n```diff\n{diff}\n```"
|
||||||
|
)
|
||||||
|
|
||||||
if ctx.guild.id not in self.cache:
|
if ctx.guild.id not in self.cache:
|
||||||
self.cache[ctx.guild.id] = []
|
self.cache[ctx.guild.id] = []
|
||||||
|
@ -83,11 +92,20 @@ class FilterCog(Extension):
|
||||||
self.cache[ctx.guild.id].append(new_name)
|
self.cache[ctx.guild.id].append(new_name)
|
||||||
if name != new_name:
|
if name != new_name:
|
||||||
self.cache[ctx.guild.id].remove(name)
|
self.cache[ctx.guild.id].remove(name)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(e, exc_info=True)
|
||||||
|
|
||||||
filter_ = SlashCommand(name="filter", description="Manage keyword filters")
|
filter_ = SlashCommand(name="filter", description="Manage keyword filters")
|
||||||
|
|
||||||
@filter_.subcommand(sub_cmd_name="create", sub_cmd_description="Create a new filter")
|
@filter_.subcommand(
|
||||||
@slash_option(name="name", description="Name of new filter", required=True, opt_type=OptionType.STRING)
|
sub_cmd_name="create", sub_cmd_description="Create a new filter"
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="name",
|
||||||
|
description="Name of new filter",
|
||||||
|
required=True,
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
|
@check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
|
||||||
async def _filter_create(self, ctx: InteractionContext, name: str) -> None:
|
async def _filter_create(self, ctx: InteractionContext, name: str) -> None:
|
||||||
return await self._edit_filter(ctx, name)
|
return await self._edit_filter(ctx, name)
|
||||||
|
@ -148,11 +166,13 @@ class FilterCog(Extension):
|
||||||
@_filter_edit.autocomplete("name")
|
@_filter_edit.autocomplete("name")
|
||||||
@_filter_view.autocomplete("name")
|
@_filter_view.autocomplete("name")
|
||||||
@_filter_delete.autocomplete("name")
|
@_filter_delete.autocomplete("name")
|
||||||
async def _autocomplete(self, ctx: AutocompleteContext, name: str) -> None:
|
async def _autocomplete(self, ctx: AutocompleteContext) -> None:
|
||||||
if not self.cache.get(ctx.guild.id):
|
if not self.cache.get(ctx.guild.id):
|
||||||
filters = await Filter.find(Filter.guild == ctx.guild.id).to_list()
|
filters = await Filter.find(Filter.guild == ctx.guild.id).to_list()
|
||||||
self.cache[ctx.guild.id] = [f.name for f in filters]
|
self.cache[ctx.guild.id] = [f.name for f in filters]
|
||||||
results = process.extract(name, self.cache.get(ctx.guild.id), limit=25)
|
results = process.extract(
|
||||||
|
ctx.input_text, self.cache.get(ctx.guild.id), limit=25
|
||||||
|
)
|
||||||
choices = [{"name": r[0], "value": r[0]} for r in results]
|
choices = [{"name": r[0], "value": r[0]} for r in results]
|
||||||
await ctx.send(choices=choices)
|
await ctx.send(choices=choices)
|
||||||
|
|
||||||
|
|
|
@ -42,12 +42,18 @@ class CaseCog(Extension):
|
||||||
guild: Originating guild
|
guild: Originating guild
|
||||||
"""
|
"""
|
||||||
action_table = Table()
|
action_table = Table()
|
||||||
action_table.add_column(header="Type", justify="left", style="orange4", no_wrap=True)
|
action_table.add_column(
|
||||||
action_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True)
|
header="Type", justify="left", style="orange4", no_wrap=True
|
||||||
|
)
|
||||||
|
action_table.add_column(
|
||||||
|
header="Admin", justify="left", style="cyan", no_wrap=True
|
||||||
|
)
|
||||||
action_table.add_column(header="Reason", justify="left", style="white")
|
action_table.add_column(header="Reason", justify="left", style="white")
|
||||||
|
|
||||||
note_table = Table()
|
note_table = Table()
|
||||||
note_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True)
|
note_table.add_column(
|
||||||
|
header="Admin", justify="left", style="cyan", no_wrap=True
|
||||||
|
)
|
||||||
note_table.add_column(header="Content", justify="left", style="white")
|
note_table.add_column(header="Content", justify="left", style="white")
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
@ -64,14 +70,18 @@ class CaseCog(Extension):
|
||||||
admin = await self.bot.fetch_user(parent_action.admin)
|
admin = await self.bot.fetch_user(parent_action.admin)
|
||||||
admin_text = "[N/A]"
|
admin_text = "[N/A]"
|
||||||
if admin:
|
if admin:
|
||||||
admin_text = f"{admin.username}#{admin.discriminator}"
|
admin_text = f"{admin.username}"
|
||||||
action_table.add_row(action.action_type.title(), admin_text, parent_action.reason)
|
action_table.add_row(
|
||||||
|
action.action_type.title(), admin_text, parent_action.reason
|
||||||
|
)
|
||||||
with console.capture() as cap:
|
with console.capture() as cap:
|
||||||
console.print(action_table)
|
console.print(action_table)
|
||||||
|
|
||||||
tmp_output = cap.get()
|
tmp_output = cap.get()
|
||||||
if len(tmp_output) >= 800:
|
if len(tmp_output) >= 800:
|
||||||
action_output_extra = f"... and {len(mod_case.actions[idx:])} more actions"
|
action_output_extra = (
|
||||||
|
f"... and {len(mod_case.actions[idx:])} more actions"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
action_output = tmp_output
|
action_output = tmp_output
|
||||||
|
@ -83,7 +93,7 @@ class CaseCog(Extension):
|
||||||
admin = await self.bot.fetch_user(note.admin)
|
admin = await self.bot.fetch_user(note.admin)
|
||||||
admin_text = "[N/A]"
|
admin_text = "[N/A]"
|
||||||
if admin:
|
if admin:
|
||||||
admin_text = f"{admin.username}#{admin.discriminator}"
|
admin_text = f"{admin.username}"
|
||||||
note_table.add_row(admin_text, note.content)
|
note_table.add_row(admin_text, note.content)
|
||||||
|
|
||||||
with console.capture() as cap:
|
with console.capture() as cap:
|
||||||
|
@ -102,7 +112,7 @@ class CaseCog(Extension):
|
||||||
username = "[N/A]"
|
username = "[N/A]"
|
||||||
user_text = "[N/A]"
|
user_text = "[N/A]"
|
||||||
if user:
|
if user:
|
||||||
username = f"{user.username}#{user.discriminator}"
|
username = f"{user.username}"
|
||||||
user_text = user.mention
|
user_text = user.mention
|
||||||
|
|
||||||
admin = await self.bot.fetch_user(mod_case.admin)
|
admin = await self.bot.fetch_user(mod_case.admin)
|
||||||
|
@ -114,8 +124,13 @@ class CaseCog(Extension):
|
||||||
note_output = f"```ansi\n{note_output}\n{note_output_extra}\n```"
|
note_output = f"```ansi\n{note_output}\n{note_output_extra}\n```"
|
||||||
|
|
||||||
fields = (
|
fields = (
|
||||||
EmbedField(name="Actions", value=action_output if mod_case.actions else "No Actions Found"),
|
EmbedField(
|
||||||
EmbedField(name="Notes", value=note_output if mod_case.notes else "No Notes Found"),
|
name="Actions",
|
||||||
|
value=action_output if mod_case.actions else "No Actions Found",
|
||||||
|
),
|
||||||
|
EmbedField(
|
||||||
|
name="Notes", value=note_output if mod_case.notes else "No Notes Found"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -148,7 +163,7 @@ class CaseCog(Extension):
|
||||||
user_mention = "[N/A]"
|
user_mention = "[N/A]"
|
||||||
avatar_url = None
|
avatar_url = None
|
||||||
if user:
|
if user:
|
||||||
username = f"{user.username}#{user.discriminator}"
|
username = f"{user.username}"
|
||||||
avatar_url = user.avatar.url
|
avatar_url = user.avatar.url
|
||||||
user_mention = user.mention
|
user_mention = user.mention
|
||||||
|
|
||||||
|
@ -166,7 +181,9 @@ class CaseCog(Extension):
|
||||||
if admin:
|
if admin:
|
||||||
admin_text = admin.mention
|
admin_text = admin.mention
|
||||||
|
|
||||||
fields = (EmbedField(name=action.action_type.title(), value=parent_action.reason),)
|
fields = (
|
||||||
|
EmbedField(name=action.action_type.title(), value=parent_action.reason),
|
||||||
|
)
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Moderation Case Action",
|
title="Moderation Case Action",
|
||||||
description=f"{admin_text} initiated an action against {user_mention}",
|
description=f"{admin_text} initiated an action against {user_mention}",
|
||||||
|
@ -195,7 +212,12 @@ class CaseCog(Extension):
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _cases_list(self, ctx: InteractionContext, user: Optional[Member] = None, closed: bool = False) -> None:
|
async def _cases_list(
|
||||||
|
self,
|
||||||
|
ctx: InteractionContext,
|
||||||
|
user: Optional[Member] = None,
|
||||||
|
closed: bool = False,
|
||||||
|
) -> None:
|
||||||
query = [Modlog.guild == ctx.guild.id]
|
query = [Modlog.guild == ctx.guild.id]
|
||||||
if not closed:
|
if not closed:
|
||||||
query.append(Modlog.open == True)
|
query.append(Modlog.open == True)
|
||||||
|
@ -214,8 +236,12 @@ class CaseCog(Extension):
|
||||||
case = SlashCommand(name="case", description="Manage a moderation case")
|
case = SlashCommand(name="case", description="Manage a moderation case")
|
||||||
show = case.group(name="show", description="Show information about a specific case")
|
show = case.group(name="show", description="Show information about a specific case")
|
||||||
|
|
||||||
@show.subcommand(sub_cmd_name="summary", sub_cmd_description="Summarize a specific case")
|
@show.subcommand(
|
||||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
sub_cmd_name="summary", sub_cmd_description="Summarize a specific case"
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _case_show_summary(self, ctx: InteractionContext, cid: str) -> None:
|
async def _case_show_summary(self, ctx: InteractionContext, cid: str) -> None:
|
||||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||||
|
@ -227,7 +253,9 @@ class CaseCog(Extension):
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
@show.subcommand(sub_cmd_name="actions", sub_cmd_description="Get case actions")
|
@show.subcommand(sub_cmd_name="actions", sub_cmd_description="Get case actions")
|
||||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
|
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _case_show_actions(self, ctx: InteractionContext, cid: str) -> None:
|
async def _case_show_actions(self, ctx: InteractionContext, cid: str) -> None:
|
||||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||||
|
@ -240,7 +268,9 @@ class CaseCog(Extension):
|
||||||
await paginator.send(ctx)
|
await paginator.send(ctx)
|
||||||
|
|
||||||
@case.subcommand(sub_cmd_name="close", sub_cmd_description="Show a specific case")
|
@case.subcommand(sub_cmd_name="close", sub_cmd_description="Show a specific case")
|
||||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
|
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _case_close(self, ctx: InteractionContext, cid: str) -> None:
|
async def _case_close(self, ctx: InteractionContext, cid: str) -> None:
|
||||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||||
|
@ -254,8 +284,12 @@ class CaseCog(Extension):
|
||||||
embed = await self.get_summary_embed(case, ctx.guild)
|
embed = await self.get_summary_embed(case, ctx.guild)
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
@case.subcommand(sub_cmd_name="repoen", sub_cmd_description="Reopen a specific case")
|
@case.subcommand(
|
||||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
sub_cmd_name="repoen", sub_cmd_description="Reopen a specific case"
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _case_reopen(self, ctx: InteractionContext, cid: str) -> None:
|
async def _case_reopen(self, ctx: InteractionContext, cid: str) -> None:
|
||||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||||
|
@ -269,9 +303,18 @@ class CaseCog(Extension):
|
||||||
embed = await self.get_summary_embed(case, ctx.guild)
|
embed = await self.get_summary_embed(case, ctx.guild)
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
@case.subcommand(sub_cmd_name="note", sub_cmd_description="Add a note to a specific case")
|
@case.subcommand(
|
||||||
@slash_option(name="cid", description="Case ID", opt_type=OptionType.STRING, required=True)
|
sub_cmd_name="note", sub_cmd_description="Add a note to a specific case"
|
||||||
@slash_option(name="note", description="Note to add", opt_type=OptionType.STRING, required=True)
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="cid", description="Case ID", opt_type=OptionType.STRING, required=True
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="note",
|
||||||
|
description="Note to add",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _case_note(self, ctx: InteractionContext, cid: str, note: str) -> None:
|
async def _case_note(self, ctx: InteractionContext, cid: str, note: str) -> None:
|
||||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.nanoid == cid)
|
||||||
|
@ -280,7 +323,9 @@ class CaseCog(Extension):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not case.open:
|
if not case.open:
|
||||||
await ctx.send("Case is closed, please re-open to add a new comment", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Case is closed, please re-open to add a new comment", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(note) > 50:
|
if len(note) > 50:
|
||||||
|
@ -296,11 +341,20 @@ class CaseCog(Extension):
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
@case.subcommand(sub_cmd_name="new", sub_cmd_description="Open a new case")
|
@case.subcommand(sub_cmd_name="new", sub_cmd_description="Open a new case")
|
||||||
@slash_option(name="user", description="Target user", opt_type=OptionType.USER, required=True)
|
@slash_option(
|
||||||
@slash_option(name="note", description="Note to add", opt_type=OptionType.STRING, required=True)
|
name="user", description="Target user", opt_type=OptionType.USER, required=True
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="note",
|
||||||
|
description="Note to add",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
@check(admin_or_permissions(Permissions.BAN_MEMBERS))
|
||||||
async def _case_new(self, ctx: InteractionContext, user: Member, note: str) -> None:
|
async def _case_new(self, ctx: InteractionContext, user: Member, note: str) -> None:
|
||||||
case = await Modlog.find_one(Modlog.guild == ctx.guild.id, Modlog.user == user.id, Modlog.open == True)
|
case = await Modlog.find_one(
|
||||||
|
Modlog.guild == ctx.guild.id, Modlog.user == user.id, Modlog.open == True
|
||||||
|
)
|
||||||
if case:
|
if case:
|
||||||
await ctx.send(f"Case already open with ID `{case.nanoid}`", ephemeral=True)
|
await ctx.send(f"Case already open with ID `{case.nanoid}`", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
@ -315,7 +369,13 @@ class CaseCog(Extension):
|
||||||
|
|
||||||
note = Note(admin=ctx.author.id, content=note)
|
note = Note(admin=ctx.author.id, content=note)
|
||||||
|
|
||||||
case = Modlog(user=user.id, guild=ctx.guild.id, admin=ctx.author.id, notes=[note], actions=[])
|
case = Modlog(
|
||||||
|
user=user.id,
|
||||||
|
guild=ctx.guild.id,
|
||||||
|
admin=ctx.author.id,
|
||||||
|
notes=[note],
|
||||||
|
actions=[],
|
||||||
|
)
|
||||||
await case.save()
|
await case.save()
|
||||||
|
|
||||||
embed = await self.get_summary_embed(case, ctx.guild)
|
embed = await self.get_summary_embed(case, ctx.guild)
|
||||||
|
|
|
@ -27,7 +27,9 @@ from jarvis.utils.permissions import admin_or_permissions
|
||||||
class MuteCog(ModcaseCog):
|
class MuteCog(ModcaseCog):
|
||||||
"""JARVIS MuteCog."""
|
"""JARVIS MuteCog."""
|
||||||
|
|
||||||
async def _apply_timeout(self, ctx: InteractionContext, user: Member, reason: str, until: datetime) -> None:
|
async def _apply_timeout(
|
||||||
|
self, ctx: InteractionContext, user: Member, reason: str, until: datetime
|
||||||
|
) -> None:
|
||||||
await user.timeout(communication_disabled_until=until, reason=reason)
|
await user.timeout(communication_disabled_until=until, reason=reason)
|
||||||
duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60)
|
duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60)
|
||||||
await Mute(
|
await Mute(
|
||||||
|
@ -42,11 +44,14 @@ class MuteCog(ModcaseCog):
|
||||||
return mute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
|
return mute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
|
||||||
|
|
||||||
@context_menu(name="Mute User", context_type=CommandType.USER)
|
@context_menu(name="Mute User", context_type=CommandType.USER)
|
||||||
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
|
@check(
|
||||||
|
admin_or_permissions(
|
||||||
|
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||||
|
)
|
||||||
|
)
|
||||||
async def _timeout_cm(self, ctx: InteractionContext) -> None:
|
async def _timeout_cm(self, ctx: InteractionContext) -> None:
|
||||||
modal = Modal(
|
modal = Modal(
|
||||||
title=f"Muting {ctx.target.mention}",
|
*[
|
||||||
components=[
|
|
||||||
InputText(
|
InputText(
|
||||||
label="Reason?",
|
label="Reason?",
|
||||||
placeholder="Spamming, harrassment, etc",
|
placeholder="Spamming, harrassment, etc",
|
||||||
|
@ -62,10 +67,13 @@ class MuteCog(ModcaseCog):
|
||||||
max_length=100,
|
max_length=100,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
title=f"Muting {ctx.target.mention}",
|
||||||
)
|
)
|
||||||
await ctx.send_modal(modal)
|
await ctx.send_modal(modal)
|
||||||
try:
|
try:
|
||||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
response = await self.bot.wait_for_modal(
|
||||||
|
modal, author=ctx.author.id, timeout=60 * 5
|
||||||
|
)
|
||||||
reason = response.responses.get("reason")
|
reason = response.responses.get("reason")
|
||||||
until = response.responses.get("until")
|
until = response.responses.get("until")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
@ -76,7 +84,9 @@ class MuteCog(ModcaseCog):
|
||||||
"RETURN_AS_TIMEZONE_AWARE": True,
|
"RETURN_AS_TIMEZONE_AWARE": True,
|
||||||
}
|
}
|
||||||
rt_settings = base_settings.copy()
|
rt_settings = base_settings.copy()
|
||||||
rt_settings["PARSERS"] = [x for x in default_parsers if x not in ["absolute-time", "timestamp"]]
|
rt_settings["PARSERS"] = [
|
||||||
|
x for x in default_parsers if x not in ["absolute-time", "timestamp"]
|
||||||
|
]
|
||||||
|
|
||||||
rt_until = parse(until, settings=rt_settings)
|
rt_until = parse(until, settings=rt_settings)
|
||||||
|
|
||||||
|
@ -91,10 +101,14 @@ class MuteCog(ModcaseCog):
|
||||||
until = at_until
|
until = at_until
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"Failed to parse delay: {until}")
|
self.logger.debug(f"Failed to parse delay: {until}")
|
||||||
await response.send(f"`{until}` is not a parsable date, please try again", ephemeral=True)
|
await response.send(
|
||||||
|
f"`{until}` is not a parsable date, please try again", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if until < datetime.now(tz=timezone.utc):
|
if until < datetime.now(tz=timezone.utc):
|
||||||
await response.send(f"`{old_until}` is in the past, which isn't allowed", ephemeral=True)
|
await response.send(
|
||||||
|
f"`{old_until}` is in the past, which isn't allowed", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
embed = await self._apply_timeout(ctx, ctx.target, reason, until)
|
embed = await self._apply_timeout(ctx, ctx.target, reason, until)
|
||||||
|
@ -103,7 +117,9 @@ class MuteCog(ModcaseCog):
|
||||||
await response.send("Unable to mute this user", ephemeral=True)
|
await response.send("Unable to mute this user", ephemeral=True)
|
||||||
|
|
||||||
@slash_command(name="mute", description="Mute a user")
|
@slash_command(name="mute", description="Mute a user")
|
||||||
@slash_option(name="user", description="User to mute", opt_type=OptionType.USER, required=True)
|
@slash_option(
|
||||||
|
name="user", description="User to mute", opt_type=OptionType.USER, required=True
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="reason",
|
name="reason",
|
||||||
description="Reason for mute",
|
description="Reason for mute",
|
||||||
|
@ -128,9 +144,18 @@ class MuteCog(ModcaseCog):
|
||||||
SlashCommandChoice(name="Week(s)", value=10080),
|
SlashCommandChoice(name="Week(s)", value=10080),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
|
@check(
|
||||||
|
admin_or_permissions(
|
||||||
|
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||||
|
)
|
||||||
|
)
|
||||||
async def _timeout(
|
async def _timeout(
|
||||||
self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60
|
self,
|
||||||
|
ctx: InteractionContext,
|
||||||
|
user: Member,
|
||||||
|
reason: str,
|
||||||
|
time: int = 1,
|
||||||
|
scale: int = 60,
|
||||||
) -> None:
|
) -> None:
|
||||||
if user == ctx.author:
|
if user == ctx.author:
|
||||||
await ctx.send("You cannot mute yourself.", ephemeral=True)
|
await ctx.send("You cannot mute yourself.", ephemeral=True)
|
||||||
|
@ -148,7 +173,9 @@ class MuteCog(ModcaseCog):
|
||||||
# Max 4 weeks (2419200 seconds) per API
|
# Max 4 weeks (2419200 seconds) per API
|
||||||
duration = time * scale
|
duration = time * scale
|
||||||
if duration > 40320:
|
if duration > 40320:
|
||||||
await ctx.send("Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration)
|
until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration)
|
||||||
|
@ -159,13 +186,28 @@ class MuteCog(ModcaseCog):
|
||||||
await ctx.send("Unable to mute this user", ephemeral=True)
|
await ctx.send("Unable to mute this user", ephemeral=True)
|
||||||
|
|
||||||
@slash_command(name="unmute", description="Unmute a user")
|
@slash_command(name="unmute", description="Unmute a user")
|
||||||
@slash_option(name="user", description="User to unmute", opt_type=OptionType.USER, required=True)
|
@slash_option(
|
||||||
@slash_option(name="reason", description="Reason for unmute", opt_type=OptionType.STRING, required=True)
|
name="user",
|
||||||
@check(admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS))
|
description="User to unmute",
|
||||||
|
opt_type=OptionType.USER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="reason",
|
||||||
|
description="Reason for unmute",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
@check(
|
||||||
|
admin_or_permissions(
|
||||||
|
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
|
||||||
|
)
|
||||||
|
)
|
||||||
async def _unmute(self, ctx: InteractionContext, user: Member, reason: str) -> None:
|
async def _unmute(self, ctx: InteractionContext, user: Member, reason: str) -> None:
|
||||||
if (
|
if (
|
||||||
not user.communication_disabled_until
|
not user.communication_disabled_until
|
||||||
or user.communication_disabled_until.timestamp() < datetime.now(tz=timezone.utc).timestamp() # noqa: W503
|
or user.communication_disabled_until.timestamp()
|
||||||
|
< datetime.now(tz=timezone.utc).timestamp() # noqa: W503
|
||||||
):
|
):
|
||||||
await ctx.send("User is not muted", ephemeral=True)
|
await ctx.send("User is not muted", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
@ -176,6 +218,8 @@ class MuteCog(ModcaseCog):
|
||||||
|
|
||||||
await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc))
|
await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc))
|
||||||
|
|
||||||
embed = unmute_embed(user=user, admin=ctx.author, reason=reason, guild=ctx.guild)
|
embed = unmute_embed(
|
||||||
|
user=user, admin=ctx.author, reason=reason, guild=ctx.guild
|
||||||
|
)
|
||||||
|
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
|
@ -24,7 +24,9 @@ class WarningCog(ModcaseCog):
|
||||||
"""JARVIS WarningCog."""
|
"""JARVIS WarningCog."""
|
||||||
|
|
||||||
@slash_command(name="warn", description="Warn a user")
|
@slash_command(name="warn", description="Warn a user")
|
||||||
@slash_option(name="user", description="User to warn", opt_type=OptionType.USER, required=True)
|
@slash_option(
|
||||||
|
name="user", description="User to warn", opt_type=OptionType.USER, required=True
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="reason",
|
name="reason",
|
||||||
description="Reason for warning",
|
description="Reason for warning",
|
||||||
|
@ -38,7 +40,9 @@ class WarningCog(ModcaseCog):
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _warn(self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24) -> None:
|
async def _warn(
|
||||||
|
self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24
|
||||||
|
) -> None:
|
||||||
if len(reason) > 100:
|
if len(reason) > 100:
|
||||||
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
await ctx.send("Reason must be < 100 characters", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
@ -66,15 +70,19 @@ class WarningCog(ModcaseCog):
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
@slash_command(name="warnings", description="Get count of user warnings")
|
@slash_command(name="warnings", description="Get count of user warnings")
|
||||||
@slash_option(name="user", description="User to view", opt_type=OptionType.USER, required=True)
|
@slash_option(
|
||||||
|
name="user", description="User to view", opt_type=OptionType.USER, required=True
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="active",
|
name="active",
|
||||||
description="View active only",
|
description="View active only",
|
||||||
opt_type=OptionType.BOOLEAN,
|
opt_type=OptionType.BOOLEAN,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
# @check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _warnings(self, ctx: InteractionContext, user: Member, active: bool = True) -> None:
|
async def _warnings(
|
||||||
|
self, ctx: InteractionContext, user: Member, active: bool = True
|
||||||
|
) -> None:
|
||||||
warnings = (
|
warnings = (
|
||||||
await Warning.find(
|
await Warning.find(
|
||||||
Warning.user == user.id,
|
Warning.user == user.id,
|
||||||
|
@ -84,6 +92,7 @@ class WarningCog(ModcaseCog):
|
||||||
.to_list()
|
.to_list()
|
||||||
)
|
)
|
||||||
if len(warnings) == 0:
|
if len(warnings) == 0:
|
||||||
|
await ctx.defer(ephemeral=True)
|
||||||
await ctx.send("That user has no warnings.", ephemeral=True)
|
await ctx.send("That user has no warnings.", ephemeral=True)
|
||||||
return
|
return
|
||||||
active_warns = get_all(warnings, active=True)
|
active_warns = get_all(warnings, active=True)
|
||||||
|
@ -117,7 +126,9 @@ class WarningCog(ModcaseCog):
|
||||||
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"{len(warnings)} total | {len(active_warns)} currently active"),
|
description=(
|
||||||
|
f"{len(warnings)} total | {len(active_warns)} currently active"
|
||||||
|
),
|
||||||
fields=fields[i : i + 5],
|
fields=fields[i : i + 5],
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
|
@ -125,7 +136,9 @@ class WarningCog(ModcaseCog):
|
||||||
icon_url=user.display_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.username}#{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 = []
|
||||||
|
@ -143,10 +156,15 @@ class WarningCog(ModcaseCog):
|
||||||
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"{len(warnings)} total | {len(active_warns)} currently active"),
|
description=(
|
||||||
|
f"{len(warnings)} total | {len(active_warns)} currently active"
|
||||||
|
),
|
||||||
fields=fields[i : i + 5],
|
fields=fields[i : i + 5],
|
||||||
)
|
)
|
||||||
embed.set_author(name=user.username + "#" + user.discriminator, icon_url=user.display_avatar.url)
|
embed.set_author(
|
||||||
|
name=user.username + "#" + user.discriminator,
|
||||||
|
icon_url=user.display_avatar.url,
|
||||||
|
)
|
||||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
pages.append(embed)
|
pages.append(embed)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import re
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
from croniter import croniter
|
||||||
from dateparser import parse
|
from dateparser import parse
|
||||||
from dateparser_data.settings import default_parsers
|
from dateparser_data.settings import default_parsers
|
||||||
from interactions import AutocompleteContext, Client, Extension, InteractionContext
|
from interactions import AutocompleteContext, Client, Extension, InteractionContext
|
||||||
|
@ -23,7 +25,7 @@ from thefuzz import process
|
||||||
|
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
|
|
||||||
valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>:'\",\u0080-\U000E0FFF]*")
|
valid = re.compile(r"[\w\s\-\\/.!@?#$%^*()+=<>:'\",\u0080-\U000E0FFF]*")
|
||||||
time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?", flags=re.IGNORECASE)
|
time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?", flags=re.IGNORECASE)
|
||||||
invites = re.compile(
|
invites = re.compile(
|
||||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501
|
||||||
|
@ -41,6 +43,13 @@ class RemindmeCog(Extension):
|
||||||
reminders = SlashCommand(name="reminders", description="Manage reminders")
|
reminders = SlashCommand(name="reminders", description="Manage reminders")
|
||||||
|
|
||||||
@reminders.subcommand(sub_cmd_name="set", sub_cmd_description="Set a reminder")
|
@reminders.subcommand(sub_cmd_name="set", sub_cmd_description="Set a reminder")
|
||||||
|
@slash_option(
|
||||||
|
name="timezone",
|
||||||
|
description="Timezone to use",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=False,
|
||||||
|
autocomplete=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="private",
|
name="private",
|
||||||
description="Send as DM?",
|
description="Send as DM?",
|
||||||
|
@ -50,15 +59,16 @@ class RemindmeCog(Extension):
|
||||||
async def _remindme(
|
async def _remindme(
|
||||||
self,
|
self,
|
||||||
ctx: InteractionContext,
|
ctx: InteractionContext,
|
||||||
|
timezone: str = "UTC",
|
||||||
private: bool = None,
|
private: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if private is None and ctx.guild:
|
if private is None and ctx.guild:
|
||||||
private = ctx.guild.member_count >= 5000
|
private = ctx.guild.member_count >= 5000
|
||||||
elif private is None and not ctx.guild:
|
elif private is None and not ctx.guild:
|
||||||
private = False
|
private = False
|
||||||
|
timezone = pytz.timezone(timezone)
|
||||||
modal = Modal(
|
modal = Modal(
|
||||||
title="Set your reminder!",
|
*[
|
||||||
components=[
|
|
||||||
InputText(
|
InputText(
|
||||||
label="What to remind you?",
|
label="What to remind you?",
|
||||||
placeholder="Reminder",
|
placeholder="Reminder",
|
||||||
|
@ -72,14 +82,26 @@ class RemindmeCog(Extension):
|
||||||
style=TextStyles.SHORT,
|
style=TextStyles.SHORT,
|
||||||
custom_id="delay",
|
custom_id="delay",
|
||||||
),
|
),
|
||||||
|
InputText(
|
||||||
|
label="Cron pattern for repeating",
|
||||||
|
placeholder="0 12 * * *",
|
||||||
|
style=TextStyles.SHORT,
|
||||||
|
max_length=40,
|
||||||
|
custom_id="cron",
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
title="Set your reminder!",
|
||||||
)
|
)
|
||||||
|
|
||||||
await ctx.send_modal(modal)
|
await ctx.send_modal(modal)
|
||||||
try:
|
try:
|
||||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
response = await self.bot.wait_for_modal(
|
||||||
|
modal, author=ctx.author.id, timeout=60 * 5
|
||||||
|
)
|
||||||
message = response.responses.get("message").strip()
|
message = response.responses.get("message").strip()
|
||||||
delay = response.responses.get("delay").strip()
|
delay = response.responses.get("delay").strip()
|
||||||
|
cron = response.responses.get("cron").strip()
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return
|
return
|
||||||
if len(message) > 500:
|
if len(message) > 500:
|
||||||
|
@ -91,20 +113,32 @@ class RemindmeCog(Extension):
|
||||||
ephemeral=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
elif not valid.fullmatch(message):
|
# elif not valid.fullmatch(message):
|
||||||
await response.send("Hey, you should probably make this readable", ephemeral=True)
|
# await response.send(
|
||||||
return
|
# "Hey, you should probably make this readable", ephemeral=True
|
||||||
|
# )
|
||||||
|
# return
|
||||||
elif len(message) == 0:
|
elif len(message) == 0:
|
||||||
await response.send("Hey, you should probably add content to your reminder", ephemeral=True)
|
await response.send(
|
||||||
|
"Hey, you should probably add content to your reminder", ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
elif cron and not croniter.is_valid(cron):
|
||||||
|
await response.send(
|
||||||
|
f"Invalid cron: {cron}\n\nUse https://crontab.guru to help",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
base_settings = {
|
base_settings = {
|
||||||
"PREFER_DATES_FROM": "future",
|
"PREFER_DATES_FROM": "future",
|
||||||
"TIMEZONE": "UTC",
|
"TIMEZONE": str(timezone),
|
||||||
"RETURN_AS_TIMEZONE_AWARE": True,
|
"RETURN_AS_TIMEZONE_AWARE": True,
|
||||||
}
|
}
|
||||||
rt_settings = base_settings.copy()
|
rt_settings = base_settings.copy()
|
||||||
rt_settings["PARSERS"] = [x for x in default_parsers if x not in ["absolute-time", "timestamp"]]
|
rt_settings["PARSERS"] = [
|
||||||
|
x for x in default_parsers if x not in ["absolute-time", "timestamp"]
|
||||||
|
]
|
||||||
|
|
||||||
rt_remind_at = parse(delay, settings=rt_settings)
|
rt_remind_at = parse(delay, settings=rt_settings)
|
||||||
|
|
||||||
|
@ -118,14 +152,20 @@ class RemindmeCog(Extension):
|
||||||
remind_at = at_remind_at
|
remind_at = at_remind_at
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"Failed to parse delay: {delay}")
|
self.logger.debug(f"Failed to parse delay: {delay}")
|
||||||
await response.send(f"`{delay}` is not a parsable date, please try again", ephemeral=True)
|
await response.send(
|
||||||
|
f"`{delay}` is not a parsable date, please try again",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if remind_at < datetime.now(tz=timezone.utc):
|
if remind_at < datetime.now(tz=timezone):
|
||||||
await response.send(f"`{delay}` is in the past. Past reminders aren't allowed", ephemeral=True)
|
await response.send(
|
||||||
|
f"`{delay}` is in the past. Past reminders aren't allowed",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif remind_at < datetime.now(tz=timezone.utc):
|
elif remind_at < datetime.now(tz=timezone):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
r = Reminder(
|
r = Reminder(
|
||||||
|
@ -135,14 +175,13 @@ class RemindmeCog(Extension):
|
||||||
message=message,
|
message=message,
|
||||||
remind_at=remind_at,
|
remind_at=remind_at,
|
||||||
private=private,
|
private=private,
|
||||||
|
repeat=cron,
|
||||||
|
timezone=str(timezone),
|
||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
await r.save()
|
await r.save()
|
||||||
|
|
||||||
embed = build_embed(
|
|
||||||
title="Reminder Set",
|
|
||||||
description=f"{ctx.author.mention} set a reminder",
|
|
||||||
fields = [
|
fields = [
|
||||||
EmbedField(name="Message", value=message),
|
EmbedField(name="Message", value=message),
|
||||||
EmbedField(
|
EmbedField(
|
||||||
|
@ -150,24 +189,45 @@ class RemindmeCog(Extension):
|
||||||
value=f"<t:{int(remind_at.timestamp())}:F> (<t:{int(remind_at.timestamp())}:R>)",
|
value=f"<t:{int(remind_at.timestamp())}:F> (<t:{int(remind_at.timestamp())}:R>)",
|
||||||
inline=False,
|
inline=False,
|
||||||
),
|
),
|
||||||
],
|
]
|
||||||
|
|
||||||
|
if r.repeat:
|
||||||
|
c = croniter(cron, remind_at)
|
||||||
|
fields.append(EmbedField(name="Repeat Schedule", value=f"`{cron}`"))
|
||||||
|
next_5 = [c.get_next() for _ in range(5)]
|
||||||
|
|
||||||
|
next_5_str = "\n".join(f"<t:{int(x)}:F> (<t:{int(x)}:R>)" for x in next_5)
|
||||||
|
fields.append(EmbedField(name="Next 5 runs", value=next_5_str))
|
||||||
|
|
||||||
|
embed = build_embed(
|
||||||
|
title="Reminder Set",
|
||||||
|
description=f"{ctx.author.mention} set a reminder",
|
||||||
|
fields=fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
name=ctx.author.username,
|
||||||
icon_url=ctx.author.display_avatar.url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||||
delete_button = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
delete_button = Button(
|
||||||
|
style=ButtonStyle.DANGER,
|
||||||
|
emoji="🗑️",
|
||||||
|
custom_id=f"delete|{ctx.author.id}",
|
||||||
|
)
|
||||||
components = [delete_button]
|
components = [delete_button]
|
||||||
if not r.guild == ctx.author.id:
|
if not r.guild == ctx.author.id:
|
||||||
copy_button = Button(style=ButtonStyle.GREEN, emoji="📋", custom_id=f"copy|rme|{r.id}")
|
copy_button = Button(
|
||||||
|
style=ButtonStyle.GREEN, emoji="📋", custom_id=f"copy|rme|{r.id}"
|
||||||
|
)
|
||||||
components.append(copy_button)
|
components.append(copy_button)
|
||||||
private = private if private is not None else False
|
private = private if private is not None else False
|
||||||
components = [ActionRow(*components)]
|
components = [ActionRow(*components)]
|
||||||
await response.send(embeds=embed, components=components, ephemeral=private)
|
await response.send(embeds=embed, components=components, ephemeral=private)
|
||||||
|
|
||||||
async def get_reminders_embed(self, ctx: InteractionContext, reminders: List[Reminder]) -> Embed:
|
async def get_reminders_embed(
|
||||||
|
self, ctx: InteractionContext, reminders: List[Reminder]
|
||||||
|
) -> Embed:
|
||||||
"""Build embed for paginator."""
|
"""Build embed for paginator."""
|
||||||
fields = []
|
fields = []
|
||||||
for reminder in reminders:
|
for reminder in reminders:
|
||||||
|
@ -195,7 +255,7 @@ class RemindmeCog(Extension):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
name=ctx.author.username,
|
||||||
icon_url=ctx.author.display_avatar.url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||||
|
@ -204,16 +264,22 @@ class RemindmeCog(Extension):
|
||||||
|
|
||||||
@reminders.subcommand(sub_cmd_name="list", sub_cmd_description="List reminders")
|
@reminders.subcommand(sub_cmd_name="list", sub_cmd_description="List reminders")
|
||||||
async def _list(self, ctx: InteractionContext) -> None:
|
async def _list(self, ctx: InteractionContext) -> None:
|
||||||
reminders = await Reminder.find(Reminder.user == ctx.author.id, Reminder.active == True).to_list()
|
reminders = await Reminder.find(
|
||||||
|
Reminder.user == ctx.author.id, Reminder.active == True
|
||||||
|
).to_list()
|
||||||
if not reminders:
|
if not reminders:
|
||||||
await ctx.send("You have no reminders set.", ephemeral=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)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@reminders.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a reminder")
|
@reminders.subcommand(
|
||||||
|
sub_cmd_name="delete", sub_cmd_description="Delete a reminder"
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="content",
|
name="content",
|
||||||
description="Content of the reminder",
|
description="Content of the reminder",
|
||||||
|
@ -222,7 +288,7 @@ class RemindmeCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _delete(self, ctx: InteractionContext, content: str) -> None:
|
async def _delete(self, ctx: InteractionContext, content: str) -> None:
|
||||||
reminder = await Reminder.find_one(Reminder.id == content)
|
reminder = await Reminder.get(content)
|
||||||
if not reminder:
|
if not reminder:
|
||||||
await ctx.send(f"Reminder `{content}` does not exist", ephemeral=True)
|
await ctx.send(f"Reminder `{content}` does not exist", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
@ -238,12 +304,14 @@ class RemindmeCog(Extension):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.display_name + "#" + ctx.author.discriminator,
|
name=ctx.author.display_name,
|
||||||
icon_url=ctx.author.display_avatar.url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||||
|
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await reminder.delete()
|
await reminder.delete()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -275,14 +343,18 @@ class RemindmeCog(Extension):
|
||||||
EmbedField(name="Created At", value=f"<t:{cts}:F> (<t:{cts}:R>)"),
|
EmbedField(name="Created At", value=f"<t:{cts}:F> (<t:{cts}:R>)"),
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(title="You have a reminder!", description=reminder.message, fields=fields)
|
embed = build_embed(
|
||||||
|
title="You have a reminder!", description=reminder.message, fields=fields
|
||||||
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=ctx.author.display_name + "#" + ctx.author.discriminator,
|
name=ctx.author.display_name,
|
||||||
icon_url=ctx.author.display_avatar.url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, ephemeral=reminder.private, components=components)
|
await ctx.send(embeds=embed, ephemeral=reminder.private, components=components)
|
||||||
if reminder.remind_at <= datetime.now(tz=timezone.utc) and not reminder.active:
|
if reminder.remind_at <= datetime.now(tz=timezone.utc) and not reminder.active:
|
||||||
try:
|
try:
|
||||||
|
@ -292,13 +364,22 @@ class RemindmeCog(Extension):
|
||||||
|
|
||||||
@_fetch.autocomplete("content")
|
@_fetch.autocomplete("content")
|
||||||
@_delete.autocomplete("content")
|
@_delete.autocomplete("content")
|
||||||
async def _search_reminders(self, ctx: AutocompleteContext, content: str) -> None:
|
async def _search_reminders(self, ctx: AutocompleteContext) -> None:
|
||||||
reminders = await Reminder.find(Reminder.user == ctx.author.id).to_list()
|
reminders = await Reminder.find(Reminder.user == ctx.author.id).to_list()
|
||||||
lookup = {r.message: str(r.id) for r in reminders}
|
lookup = {
|
||||||
results = process.extract(content, list(lookup.keys()), limit=5)
|
f"[{r.created_at.strftime('%d/%m/%Y %H:%M.%S')}] {r.message}": str(r.id)
|
||||||
|
for r in reminders
|
||||||
|
}
|
||||||
|
results = process.extract(ctx.input_text, list(lookup.keys()), limit=5)
|
||||||
choices = [{"name": r[0], "value": lookup[r[0]]} for r in results]
|
choices = [{"name": r[0], "value": lookup[r[0]]} for r in results]
|
||||||
await ctx.send(choices=choices)
|
await ctx.send(choices=choices)
|
||||||
|
|
||||||
|
@_remindme.autocomplete("timezone")
|
||||||
|
async def _timezone_autocomplete(self, ctx: AutocompleteContext):
|
||||||
|
results = process.extract(ctx.input_text, pytz.all_timezones_set, limit=5)
|
||||||
|
choices = [{"name": r[0], "value": r[0]} for r in results if r[1] > 80.0]
|
||||||
|
await ctx.send(choices)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: Client) -> None:
|
def setup(bot: Client) -> None:
|
||||||
"""Add RemindmeCog to JARVIS"""
|
"""Add RemindmeCog to JARVIS"""
|
||||||
|
|
|
@ -2,10 +2,8 @@
|
||||||
|
|
||||||
from interactions import Client
|
from interactions import Client
|
||||||
|
|
||||||
from jarvis.cogs.core.socials import reddit, twitter
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: Client) -> None:
|
def setup(bot: Client) -> None:
|
||||||
"""Add social cogs to JARVIS"""
|
"""Add social cogs to JARVIS"""
|
||||||
reddit.RedditCog(bot)
|
# Unfortunately there's no social cogs anymore
|
||||||
twitter.TwitterCog(bot)
|
# Mastodon will come in the future
|
||||||
|
|
|
@ -1,568 +0,0 @@
|
||||||
"""JARVIS Reddit cog."""
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from asyncpraw import Reddit
|
|
||||||
from asyncpraw.models.reddit.submission import Submission
|
|
||||||
from asyncpraw.models.reddit.submission import Subreddit as Sub
|
|
||||||
from asyncprawcore.exceptions import Forbidden, NotFound, Redirect
|
|
||||||
from interactions import Client, Extension, InteractionContext, Permissions
|
|
||||||
from interactions.client.utils.misc_utils import get
|
|
||||||
from interactions.models.discord.channel import ChannelType, GuildText
|
|
||||||
from interactions.models.discord.components import (
|
|
||||||
ActionRow,
|
|
||||||
StringSelectMenu,
|
|
||||||
StringSelectOption,
|
|
||||||
)
|
|
||||||
from interactions.models.discord.embed import Embed, EmbedField
|
|
||||||
from interactions.models.internal.application_commands import (
|
|
||||||
OptionType,
|
|
||||||
SlashCommand,
|
|
||||||
SlashCommandChoice,
|
|
||||||
slash_option,
|
|
||||||
)
|
|
||||||
from interactions.models.internal.command import check
|
|
||||||
from jarvis_core.db.models import Subreddit, SubredditFollow, UserSetting
|
|
||||||
|
|
||||||
from jarvis import const
|
|
||||||
from jarvis.config import load_config
|
|
||||||
from jarvis.utils import build_embed
|
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
|
||||||
|
|
||||||
DEFAULT_USER_AGENT = f"python:JARVIS:{const.__version__} (by u/zevaryx)"
|
|
||||||
sub_name = re.compile(r"\A[A-Za-z0-9][A-Za-z0-9_]{2,20}\Z")
|
|
||||||
user_name = re.compile(r"[A-Za-z0-9_-]+")
|
|
||||||
image_link = re.compile(r"https?://(?:www)?\.?preview\.redd\.it\/(.*\..*)\?.*")
|
|
||||||
|
|
||||||
|
|
||||||
class RedditCog(Extension):
|
|
||||||
"""JARVIS Reddit Cog."""
|
|
||||||
|
|
||||||
def __init__(self, bot: Client):
|
|
||||||
self.bot = bot
|
|
||||||
self.logger = logging.getLogger(__name__)
|
|
||||||
config = load_config()
|
|
||||||
config.reddit.user_agent = config.reddit.user_agent or DEFAULT_USER_AGENT
|
|
||||||
self.api = Reddit(**config.reddit.dict())
|
|
||||||
|
|
||||||
async def post_embeds(self, sub: Sub, post: Submission) -> Optional[List[Embed]]:
|
|
||||||
"""
|
|
||||||
Build a post embeds.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
post: Post to build embeds
|
|
||||||
"""
|
|
||||||
url = f"https://redd.it/{post.id}"
|
|
||||||
await post.author.load()
|
|
||||||
author_url = f"https://reddit.com/u/{post.author.name}"
|
|
||||||
author_icon = post.author.icon_img
|
|
||||||
images = []
|
|
||||||
title = post.title
|
|
||||||
if len(title) > 256:
|
|
||||||
title = title[:253] + "..."
|
|
||||||
fields = []
|
|
||||||
content = ""
|
|
||||||
og_post = None
|
|
||||||
if "crosspost_parent_list" in vars(post):
|
|
||||||
og_post = post # noqa: F841
|
|
||||||
post = await self.api.submission(post.crosspost_parent_list[0]["id"])
|
|
||||||
await post.load()
|
|
||||||
fields.append(EmbedField(name="Crossposted From", value=post.subreddit_name_prefixed))
|
|
||||||
content = f"> **{post.title}**"
|
|
||||||
if "url" in vars(post):
|
|
||||||
if any(post.url.endswith(x) for x in ["jpeg", "jpg", "png", "gif"]):
|
|
||||||
images = [post.url]
|
|
||||||
if "media_metadata" in vars(post):
|
|
||||||
for k, v in post.media_metadata.items():
|
|
||||||
if v["status"] != "valid" or v["m"] not in ["image/jpg", "image/png", "image/gif"]:
|
|
||||||
continue
|
|
||||||
ext = v["m"].split("/")[-1]
|
|
||||||
i_url = f"https://i.redd.it/{k}.{ext}"
|
|
||||||
images.append(i_url)
|
|
||||||
if len(images) == 4:
|
|
||||||
break
|
|
||||||
|
|
||||||
if "selftext" in vars(post) and post.selftext:
|
|
||||||
text = post.selftext
|
|
||||||
if post.spoiler:
|
|
||||||
text = "||" + text + "||"
|
|
||||||
content += "\n\n" + post.selftext
|
|
||||||
if len(content) > 900:
|
|
||||||
content = content[:900] + "..."
|
|
||||||
if post.spoiler:
|
|
||||||
content += "||"
|
|
||||||
content += f"\n\n[View this post]({url})"
|
|
||||||
content = "\n".join(image_link.sub(r"https://i.redd.it/\1", x) for x in content.split("\n"))
|
|
||||||
|
|
||||||
if not images and not content:
|
|
||||||
self.logger.debug(f"Post {post.id} had neither content nor images?")
|
|
||||||
return None
|
|
||||||
|
|
||||||
color = "#FF4500"
|
|
||||||
if "primary_color" in vars(sub):
|
|
||||||
color = sub.primary_color
|
|
||||||
base_embed = build_embed(
|
|
||||||
title=title,
|
|
||||||
description=content,
|
|
||||||
fields=fields,
|
|
||||||
timestamp=post.created_utc,
|
|
||||||
url=url,
|
|
||||||
color=color,
|
|
||||||
)
|
|
||||||
base_embed.set_author(name="u/" + post.author.name, url=author_url, icon_url=author_icon)
|
|
||||||
base_embed.set_footer(
|
|
||||||
text=f"r/{sub.display_name}",
|
|
||||||
icon_url="https://www.redditinc.com/assets/images/site/reddit-logo.png",
|
|
||||||
)
|
|
||||||
|
|
||||||
embeds = [base_embed]
|
|
||||||
|
|
||||||
if len(images) > 0:
|
|
||||||
embeds[0].set_image(url=images[0])
|
|
||||||
for image in images[1:4]:
|
|
||||||
embed = Embed(url=url)
|
|
||||||
embed.set_image(url=image)
|
|
||||||
embeds.append(embed)
|
|
||||||
|
|
||||||
return embeds
|
|
||||||
|
|
||||||
reddit = SlashCommand(name="reddit", description="Manage Reddit follows")
|
|
||||||
follow = reddit.group(name="follow", description="Add a follow")
|
|
||||||
unfollow = reddit.group(name="unfollow", description="Remove a follow")
|
|
||||||
|
|
||||||
# Due to bugs and missing models, this section is commented out for the time being
|
|
||||||
# TODO:
|
|
||||||
# 1. Fix bugs
|
|
||||||
# 2. Migrate to beanie
|
|
||||||
#
|
|
||||||
# @follow.subcommand(sub_cmd_name="redditor", sub_cmd_description="Follow a Redditor")
|
|
||||||
# @slash_option(
|
|
||||||
# name="name",
|
|
||||||
# description="Redditor name",
|
|
||||||
# opt_type=OptionType.STRING,
|
|
||||||
# required=True,
|
|
||||||
# )
|
|
||||||
# @slash_option(
|
|
||||||
# name="channel",
|
|
||||||
# description="Channel to post to",
|
|
||||||
# opt_type=OptionType.CHANNEL,
|
|
||||||
# channel_types=[ChannelType.GUILD_TEXT],
|
|
||||||
# required=True,
|
|
||||||
# )
|
|
||||||
# @check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
|
||||||
# async def _redditor_follow(self, ctx: InteractionContext, name: str, channel: GuildText) -> None:
|
|
||||||
# if not user_name.match(name):
|
|
||||||
# await ctx.send("Invalid Redditor name", ephemeral=True)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# if not isinstance(channel, GuildText):
|
|
||||||
# await ctx.send("Channel must be a text channel", ephemeral=True)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# redditor = await self.api.redditor(name)
|
|
||||||
# await redditor.load()
|
|
||||||
# except (NotFound, Forbidden, Redirect) as e:
|
|
||||||
# self.logger.debug(f"Redditor {name} raised {e.__class__.__name__} on add")
|
|
||||||
# await ctx.send("Redditor may be deleted or nonexistent.", ephemeral=True)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# exists = await RedditorFollow.find_one(q(name=redditor.name, guild=ctx.guild.id))
|
|
||||||
# if exists:
|
|
||||||
# await ctx.send("Redditor already being followed in this guild", ephemeral=True)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# count = len([i async for i in SubredditFollow.find(q(guild=ctx.guild.id))])
|
|
||||||
# if count >= 12:
|
|
||||||
# await ctx.send("Cannot follow more than 12 Redditors", ephemeral=True)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# sr = await Redditor.find_one(q(name=redditor.name))
|
|
||||||
# if not sr:
|
|
||||||
# sr = Redditor(name=redditor.name)
|
|
||||||
# await sr.commit()
|
|
||||||
|
|
||||||
# srf = RedditorFollow(
|
|
||||||
# name=redditor.name,
|
|
||||||
# channel=channel.id,
|
|
||||||
# guild=ctx.guild.id,
|
|
||||||
# admin=ctx.author.id,
|
|
||||||
# )
|
|
||||||
# await srf.commit()
|
|
||||||
|
|
||||||
# await ctx.send(f"Now following `u/{name}` in {channel.mention}")
|
|
||||||
|
|
||||||
# @unfollow.subcommand(sub_cmd_name="redditor", sub_cmd_description="Unfollow Redditor")
|
|
||||||
# @check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
|
||||||
# async def _redditor_unfollow(self, ctx: InteractionContext) -> None:
|
|
||||||
# subs = RedditorFollow.find(q(guild=ctx.guild.id))
|
|
||||||
# redditors = []
|
|
||||||
# async for sub in subs:
|
|
||||||
# redditors.append(sub)
|
|
||||||
# if not redditors:
|
|
||||||
# await ctx.send("You need to follow a redditor first", ephemeral=True)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# options = []
|
|
||||||
# names = []
|
|
||||||
# for idx, redditor in enumerate(redditors):
|
|
||||||
# sub = await Redditor.find_one(q(name=redditor.name))
|
|
||||||
# names.append(sub.name)
|
|
||||||
# option = StringSelectOption(label=sub.name, value=str(idx))
|
|
||||||
# options.append(option)
|
|
||||||
|
|
||||||
# select = StringSelectMenu(options=options, custom_id="to_delete", min_values=1, max_values=len(redditors))
|
|
||||||
|
|
||||||
# components = [ActionRow(select)]
|
|
||||||
# block = "\n".join(x for x in names)
|
|
||||||
# message = await ctx.send(
|
|
||||||
# content=f"You are following the following redditors:\n```\n{block}\n```\n\n"
|
|
||||||
# "Please choose redditors to unfollow",
|
|
||||||
# components=components,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# context = await self.bot.wait_for_component(
|
|
||||||
# check=lambda x: ctx.author.id == x.ctx.author.id,
|
|
||||||
# messages=message,
|
|
||||||
# timeout=60 * 5,
|
|
||||||
# )
|
|
||||||
# for to_delete in context.ctx.values:
|
|
||||||
# follow = get(redditors, guild=ctx.guild.id, name=names[int(to_delete)])
|
|
||||||
# try:
|
|
||||||
# await follow.delete()
|
|
||||||
# except Exception:
|
|
||||||
# self.logger.debug("Ignoring deletion error")
|
|
||||||
# for row in components:
|
|
||||||
# for component in row.components:
|
|
||||||
# component.disabled = True
|
|
||||||
|
|
||||||
# block = "\n".join(names[int(x)] for x in context.ctx.values)
|
|
||||||
# await context.ctx.edit_origin(
|
|
||||||
# content=f"Unfollowed the following:\n```\n{block}\n```", components=components
|
|
||||||
# )
|
|
||||||
# except asyncio.TimeoutError:
|
|
||||||
# for row in components:
|
|
||||||
# for component in row.components:
|
|
||||||
# component.disabled = True
|
|
||||||
# await message.edit(components=components)
|
|
||||||
|
|
||||||
@follow.subcommand(sub_cmd_name="subreddit", sub_cmd_description="Follow a Subreddit")
|
|
||||||
@slash_option(
|
|
||||||
name="name",
|
|
||||||
description="Subreddit display name",
|
|
||||||
opt_type=OptionType.STRING,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@slash_option(
|
|
||||||
name="channel",
|
|
||||||
description="Channel to post to",
|
|
||||||
opt_type=OptionType.CHANNEL,
|
|
||||||
channel_types=[ChannelType.GUILD_TEXT],
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
|
||||||
async def _subreddit_follow(self, ctx: InteractionContext, name: str, channel: GuildText) -> None:
|
|
||||||
if not sub_name.match(name):
|
|
||||||
await ctx.send("Invalid Subreddit name", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not isinstance(channel, GuildText):
|
|
||||||
await ctx.send("Channel must be a text channel", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
subreddit = await self.api.subreddit(name)
|
|
||||||
await subreddit.load()
|
|
||||||
except (NotFound, Forbidden, Redirect) as e:
|
|
||||||
self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} on add")
|
|
||||||
await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
exists = await SubredditFollow.find_one(
|
|
||||||
SubredditFollow.display_name == subreddit.display_name, SubredditFollow.guild == ctx.guild.id
|
|
||||||
)
|
|
||||||
if exists:
|
|
||||||
await ctx.send("Subreddit already being followed in this guild", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
count = await SubredditFollow.find(SubredditFollow.guild == ctx.guild.id).count()
|
|
||||||
if count >= 12:
|
|
||||||
await ctx.send("Cannot follow more than 12 Subreddits", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
if subreddit.over18 and not channel.nsfw:
|
|
||||||
await ctx.send(
|
|
||||||
"Subreddit is nsfw, but channel is not. Mark the channel NSFW first.",
|
|
||||||
ephemeral=True,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
sr = await Subreddit.find_one(Subreddit.display_name == subreddit.display_name)
|
|
||||||
if not sr:
|
|
||||||
sr = Subreddit(display_name=subreddit.display_name, over18=subreddit.over18)
|
|
||||||
await sr.save()
|
|
||||||
|
|
||||||
srf = SubredditFollow(
|
|
||||||
display_name=subreddit.display_name,
|
|
||||||
channel=channel.id,
|
|
||||||
guild=ctx.guild.id,
|
|
||||||
admin=ctx.author.id,
|
|
||||||
)
|
|
||||||
await srf.save()
|
|
||||||
|
|
||||||
await ctx.send(f"Now following `r/{name}` in {channel.mention}")
|
|
||||||
|
|
||||||
@unfollow.subcommand(sub_cmd_name="subreddit", sub_cmd_description="Unfollow Subreddits")
|
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
|
||||||
async def _subreddit_unfollow(self, ctx: InteractionContext) -> None:
|
|
||||||
subreddits = await SubredditFollow.find(SubredditFollow.guild == ctx.guild.id).to_list()
|
|
||||||
if not subreddits:
|
|
||||||
await ctx.send("You need to follow a Subreddit first", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
options = []
|
|
||||||
names = []
|
|
||||||
for idx, subreddit in enumerate(subreddits):
|
|
||||||
sub = await Subreddit.find_one(Subreddit.display_name == subreddit.display_name)
|
|
||||||
names.append(sub.display_name)
|
|
||||||
option = StringSelectOption(label=sub.display_name, value=str(idx))
|
|
||||||
options.append(option)
|
|
||||||
|
|
||||||
select = StringSelectMenu(options=options, custom_id="to_delete", min_values=1, max_values=len(subreddits))
|
|
||||||
|
|
||||||
components = [ActionRow(select)]
|
|
||||||
block = "\n".join(x for x in names)
|
|
||||||
message = await ctx.send(
|
|
||||||
content=f"You are following the following subreddits:\n```\n{block}\n```\n\n"
|
|
||||||
"Please choose subreddits to unfollow",
|
|
||||||
components=components,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
context = await self.bot.wait_for_component(
|
|
||||||
check=lambda x: ctx.author.id == x.ctx.author.id,
|
|
||||||
messages=message,
|
|
||||||
timeout=60 * 5,
|
|
||||||
)
|
|
||||||
for to_delete in context.ctx.values:
|
|
||||||
follow = get(subreddits, guild=ctx.guild.id, display_name=names[int(to_delete)])
|
|
||||||
try:
|
|
||||||
await follow.delete()
|
|
||||||
except Exception:
|
|
||||||
self.logger.debug("Ignoring deletion error")
|
|
||||||
for row in components:
|
|
||||||
for component in row.components:
|
|
||||||
component.disabled = True
|
|
||||||
|
|
||||||
block = "\n".join(names[int(x)] for x in context.ctx.values)
|
|
||||||
await context.ctx.edit_origin(
|
|
||||||
content=f"Unfollowed the following:\n```\n{block}\n```", components=components
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
for row in components:
|
|
||||||
for component in row.components:
|
|
||||||
component.disabled = True
|
|
||||||
await message.edit(components=components)
|
|
||||||
|
|
||||||
@reddit.subcommand(sub_cmd_name="hot", sub_cmd_description="Get the hot post of a subreddit")
|
|
||||||
@slash_option(name="name", description="Subreddit name", opt_type=OptionType.STRING, required=True)
|
|
||||||
async def _subreddit_hot(self, ctx: InteractionContext, name: str) -> None:
|
|
||||||
if not sub_name.match(name):
|
|
||||||
await ctx.send("Invalid Subreddit name", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await ctx.defer()
|
|
||||||
subreddit = await self.api.subreddit(name)
|
|
||||||
await subreddit.load()
|
|
||||||
except (NotFound, Forbidden, Redirect) as e:
|
|
||||||
self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in hot")
|
|
||||||
await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
post = [x async for x in subreddit.hot(limit=1)][0]
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to get post from {name}", exc_info=e)
|
|
||||||
await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
embeds = await self.post_embeds(subreddit, post)
|
|
||||||
if post.over_18 and not ctx.channel.nsfw:
|
|
||||||
setting = await UserSetting.find_one(
|
|
||||||
UserSetting.user == ctx.author.id, UserSetting.type == "reddit", UserSetting.setting == "dm_nsfw"
|
|
||||||
)
|
|
||||||
if setting and setting.value:
|
|
||||||
try:
|
|
||||||
await ctx.author.send(embeds=embeds)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
await ctx.send("Hey! Due to content, I cannot share the result")
|
|
||||||
else:
|
|
||||||
await ctx.send(embeds=embeds)
|
|
||||||
|
|
||||||
@reddit.subcommand(sub_cmd_name="top", sub_cmd_description="Get the top post of a subreddit")
|
|
||||||
@slash_option(name="name", description="Subreddit name", opt_type=OptionType.STRING, required=True)
|
|
||||||
@slash_option(
|
|
||||||
name="time",
|
|
||||||
description="Top time",
|
|
||||||
opt_type=OptionType.STRING,
|
|
||||||
required=False,
|
|
||||||
choices=[
|
|
||||||
SlashCommandChoice(name="All", value="all"),
|
|
||||||
SlashCommandChoice(name="Day", value="day"),
|
|
||||||
SlashCommandChoice(name="Hour", value="hour"),
|
|
||||||
SlashCommandChoice(name="Month", value="month"),
|
|
||||||
SlashCommandChoice(name="Week", value="week"),
|
|
||||||
SlashCommandChoice(name="Year", value="year"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def _subreddit_top(self, ctx: InteractionContext, name: str, time: str = "all") -> None:
|
|
||||||
if not sub_name.match(name):
|
|
||||||
await ctx.send("Invalid Subreddit name", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await ctx.defer()
|
|
||||||
subreddit = await self.api.subreddit(name)
|
|
||||||
await subreddit.load()
|
|
||||||
except (NotFound, Forbidden, Redirect) as e:
|
|
||||||
self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in top")
|
|
||||||
await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
post = [x async for x in subreddit.top(time_filter=time, limit=1)][0]
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to get post from {name}", exc_info=e)
|
|
||||||
await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
embeds = await self.post_embeds(subreddit, post)
|
|
||||||
if post.over_18 and not ctx.channel.nsfw:
|
|
||||||
setting = await UserSetting.find_one(
|
|
||||||
UserSetting.user == ctx.author.id, UserSetting.type == "reddit", UserSetting.setting == "dm_nsfw"
|
|
||||||
)
|
|
||||||
if setting and setting.value:
|
|
||||||
try:
|
|
||||||
await ctx.author.send(embeds=embeds)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
await ctx.send("Hey! Due to content, I cannot share the result")
|
|
||||||
else:
|
|
||||||
await ctx.send(embeds=embeds)
|
|
||||||
|
|
||||||
@reddit.subcommand(sub_cmd_name="random", sub_cmd_description="Get a random post of a subreddit")
|
|
||||||
@slash_option(name="name", description="Subreddit name", opt_type=OptionType.STRING, required=True)
|
|
||||||
async def _subreddit_random(self, ctx: InteractionContext, name: str) -> None:
|
|
||||||
if not sub_name.match(name):
|
|
||||||
await ctx.send("Invalid Subreddit name", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await ctx.defer()
|
|
||||||
subreddit = await self.api.subreddit(name)
|
|
||||||
await subreddit.load()
|
|
||||||
except (NotFound, Forbidden, Redirect) as e:
|
|
||||||
self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in random")
|
|
||||||
await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
post = await subreddit.random()
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to get post from {name}", exc_info=e)
|
|
||||||
await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
embeds = await self.post_embeds(subreddit, post)
|
|
||||||
if post.over_18 and not ctx.channel.nsfw:
|
|
||||||
setting = await UserSetting.find_one(
|
|
||||||
UserSetting.user == ctx.author.id, UserSetting.type == "reddit", UserSetting.setting == "dm_nsfw"
|
|
||||||
)
|
|
||||||
if setting and setting.value:
|
|
||||||
try:
|
|
||||||
await ctx.author.send(embeds=embeds)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
await ctx.send("Hey! Due to content, I cannot share the result")
|
|
||||||
else:
|
|
||||||
await ctx.send(embeds=embeds)
|
|
||||||
|
|
||||||
@reddit.subcommand(sub_cmd_name="rising", sub_cmd_description="Get a rising post of a subreddit")
|
|
||||||
@slash_option(name="name", description="Subreddit name", opt_type=OptionType.STRING, required=True)
|
|
||||||
async def _subreddit_rising(self, ctx: InteractionContext, name: str) -> None:
|
|
||||||
if not sub_name.match(name):
|
|
||||||
await ctx.send("Invalid Subreddit name", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await ctx.defer()
|
|
||||||
subreddit = await self.api.subreddit(name)
|
|
||||||
await subreddit.load()
|
|
||||||
except (NotFound, Forbidden, Redirect) as e:
|
|
||||||
self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in rising")
|
|
||||||
await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
post = [x async for x in subreddit.rising(limit=1)][0]
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to get post from {name}", exc_info=e)
|
|
||||||
await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
embeds = await self.post_embeds(subreddit, post)
|
|
||||||
if post.over_18 and not ctx.channel.nsfw:
|
|
||||||
setting = await UserSetting.find_one(
|
|
||||||
UserSetting.user == ctx.author.id, UserSetting.type == "reddit", UserSetting.setting == "dm_nsfw"
|
|
||||||
)
|
|
||||||
if setting and setting.value:
|
|
||||||
try:
|
|
||||||
await ctx.author.send(embeds=embeds)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
await ctx.send("Hey! Due to content, I cannot share the result")
|
|
||||||
else:
|
|
||||||
await ctx.send(embeds=embeds)
|
|
||||||
|
|
||||||
@reddit.subcommand(sub_cmd_name="post", sub_cmd_description="Get a specific submission")
|
|
||||||
@slash_option(name="sid", description="Submission ID", opt_type=OptionType.STRING, required=True)
|
|
||||||
async def _reddit_post(self, ctx: InteractionContext, sid: str) -> None:
|
|
||||||
await ctx.defer()
|
|
||||||
try:
|
|
||||||
post = await self.api.submission(sid)
|
|
||||||
await post.load()
|
|
||||||
except (NotFound, Forbidden, Redirect) as e:
|
|
||||||
self.logger.debug(f"Submission {sid} raised {e.__class__.__name__} in post")
|
|
||||||
await ctx.send("Post could not be found.", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
embeds = await self.post_embeds(post.subreddit, post)
|
|
||||||
if post.over_18 and not ctx.channel.nsfw:
|
|
||||||
setting = await UserSetting.find_one(
|
|
||||||
UserSetting.user == ctx.author.id, UserSetting.type == "reddit", UserSetting.setting == "dm_nsfw"
|
|
||||||
)
|
|
||||||
if setting and setting.value:
|
|
||||||
try:
|
|
||||||
await ctx.author.send(embeds=embeds)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
await ctx.send("Hey! Due to content, I cannot share the result")
|
|
||||||
else:
|
|
||||||
await ctx.send(embeds=embeds)
|
|
||||||
|
|
||||||
@reddit.subcommand(sub_cmd_name="dm_nsfw", sub_cmd_description="DM NSFW posts if channel isn't NSFW")
|
|
||||||
@slash_option(name="dm", description="Send DM?", opt_type=OptionType.BOOLEAN, required=True)
|
|
||||||
async def _reddit_dm(self, ctx: InteractionContext, dm: bool) -> None:
|
|
||||||
setting = await UserSetting.find_one(
|
|
||||||
UserSetting.user == ctx.author.id, UserSetting.type == "reddit", UserSetting.setting == "dm_nsfw"
|
|
||||||
)
|
|
||||||
if not setting:
|
|
||||||
setting = UserSetting(user=ctx.author.id, type="reddit", setting="dm_nsfw", value=dm)
|
|
||||||
setting.value = dm
|
|
||||||
await setting.save()
|
|
||||||
await ctx.send(f"Reddit DM NSFW setting is now set to {dm}", ephemeral=True)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: Client) -> None:
|
|
||||||
"""Add RedditCog to JARVIS"""
|
|
||||||
if load_config().reddit:
|
|
||||||
RedditCog(bot)
|
|
||||||
else:
|
|
||||||
bot.logger.info("Missing Reddit configuration, not loading")
|
|
|
@ -1,246 +0,0 @@
|
||||||
"""JARVIS Twitter Cog."""
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import tweepy
|
|
||||||
from interactions import Client, Extension, InteractionContext, Permissions
|
|
||||||
from interactions.client.utils.misc_utils import get
|
|
||||||
from interactions.models.discord.channel import GuildText
|
|
||||||
from interactions.models.discord.components import (
|
|
||||||
ActionRow,
|
|
||||||
StringSelectMenu,
|
|
||||||
StringSelectOption,
|
|
||||||
)
|
|
||||||
from interactions.models.internal.application_commands import (
|
|
||||||
OptionType,
|
|
||||||
SlashCommand,
|
|
||||||
slash_option,
|
|
||||||
)
|
|
||||||
from interactions.models.internal.command import check
|
|
||||||
from jarvis_core.db.models import TwitterAccount, TwitterFollow
|
|
||||||
|
|
||||||
from jarvis.config import load_config
|
|
||||||
from jarvis.utils.permissions import admin_or_permissions
|
|
||||||
|
|
||||||
|
|
||||||
class TwitterCog(Extension):
|
|
||||||
"""JARVIS Twitter Cog."""
|
|
||||||
|
|
||||||
def __init__(self, bot: Client):
|
|
||||||
self.bot = bot
|
|
||||||
self.logger = logging.getLogger(__name__)
|
|
||||||
config = load_config()
|
|
||||||
auth = tweepy.AppAuthHandler(config.twitter.consumer_key, config.twitter.consumer_secret)
|
|
||||||
self.api = tweepy.API(auth)
|
|
||||||
self._guild_cache = {}
|
|
||||||
self._channel_cache = {}
|
|
||||||
|
|
||||||
twitter = SlashCommand(
|
|
||||||
name="twitter",
|
|
||||||
description="Manage Twitter follows",
|
|
||||||
)
|
|
||||||
|
|
||||||
@twitter.subcommand(
|
|
||||||
sub_cmd_name="follow",
|
|
||||||
sub_cmd_description="Follow a Twitter acount",
|
|
||||||
)
|
|
||||||
@slash_option(name="handle", description="Twitter account", opt_type=OptionType.STRING, required=True)
|
|
||||||
@slash_option(
|
|
||||||
name="channel",
|
|
||||||
description="Channel to post tweets to",
|
|
||||||
opt_type=OptionType.CHANNEL,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@slash_option(
|
|
||||||
name="retweets",
|
|
||||||
description="Mirror re-tweets?",
|
|
||||||
opt_type=OptionType.BOOLEAN,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
|
||||||
async def _twitter_follow(
|
|
||||||
self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: bool = True
|
|
||||||
) -> None:
|
|
||||||
handle = handle.lower()
|
|
||||||
if len(handle) > 15 or len(handle) < 4:
|
|
||||||
await ctx.send("Invalid Twitter handle", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not isinstance(channel, GuildText):
|
|
||||||
await ctx.send("Channel must be a text channel", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
account = await asyncio.to_thread(self.api.get_user, screen_name=handle)
|
|
||||||
latest_tweet = (await asyncio.to_thread(self.api.user_timeline, screen_name=handle))[0]
|
|
||||||
except Exception:
|
|
||||||
await ctx.send("Unable to get user timeline. Are you sure the handle is correct?", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
exists = await TwitterFollow.find_one(
|
|
||||||
TwitterFollow.twitter_id == account.id, TwitterFollow.guild == ctx.guild.id
|
|
||||||
)
|
|
||||||
if exists:
|
|
||||||
await ctx.send("Twitter account already being followed in this guild", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
count = await TwitterFollow.find(TwitterFollow.guild == ctx.guild.id).count()
|
|
||||||
if count >= 12:
|
|
||||||
await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
ta = await TwitterAccount.find_one(TwitterAccount.twitter_id == account.id)
|
|
||||||
if not ta:
|
|
||||||
ta = TwitterAccount(
|
|
||||||
handle=account.screen_name,
|
|
||||||
twitter_id=account.id,
|
|
||||||
last_tweet=latest_tweet.id,
|
|
||||||
)
|
|
||||||
await ta.save()
|
|
||||||
|
|
||||||
tf = TwitterFollow(
|
|
||||||
twitter_id=account.id,
|
|
||||||
guild=ctx.guild.id,
|
|
||||||
channel=channel.id,
|
|
||||||
admin=ctx.author.id,
|
|
||||||
retweets=retweets,
|
|
||||||
)
|
|
||||||
|
|
||||||
await tf.save()
|
|
||||||
|
|
||||||
await ctx.send(f"Now following `@{handle}` in {channel.mention}")
|
|
||||||
|
|
||||||
@twitter.subcommand(sub_cmd_name="unfollow", sub_cmd_description="Unfollow Twitter accounts")
|
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
|
||||||
async def _twitter_unfollow(self, ctx: InteractionContext) -> None:
|
|
||||||
t = TwitterFollow.find(TwitterFollow.guild == ctx.guild.id)
|
|
||||||
twitters = []
|
|
||||||
async for twitter in t:
|
|
||||||
twitters.append(twitter)
|
|
||||||
if not twitters:
|
|
||||||
await ctx.send("You need to follow a Twitter account first", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
options = []
|
|
||||||
handlemap = {}
|
|
||||||
for twitter in twitters:
|
|
||||||
account = await TwitterAccount.find_one(TwitterAccount.twitter_id == twitter.twitter_id)
|
|
||||||
handlemap[str(twitter.twitter_id)] = account.handle
|
|
||||||
option = StringSelectOption(label=account.handle, value=str(twitter.twitter_id))
|
|
||||||
options.append(option)
|
|
||||||
|
|
||||||
select = StringSelectMenu(options=options, custom_id="to_delete", min_values=1, max_values=len(twitters))
|
|
||||||
|
|
||||||
components = [ActionRow(select)]
|
|
||||||
block = "\n".join(x for x in handlemap.values())
|
|
||||||
message = await ctx.send(
|
|
||||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
|
||||||
"Please choose accounts to unfollow",
|
|
||||||
components=components,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
context = await self.bot.wait_for_component(
|
|
||||||
check=lambda x: ctx.author.id == x.ctx.author.id,
|
|
||||||
messages=message,
|
|
||||||
timeout=60 * 5,
|
|
||||||
)
|
|
||||||
for to_delete in context.ctx.values:
|
|
||||||
follow = get(twitters, guild=ctx.guild.id, twitter_id=int(to_delete))
|
|
||||||
try:
|
|
||||||
await follow.delete()
|
|
||||||
except Exception:
|
|
||||||
self.logger.debug("Ignoring deletion error")
|
|
||||||
for row in components:
|
|
||||||
for component in row.components:
|
|
||||||
component.disabled = True
|
|
||||||
|
|
||||||
block = "\n".join(handlemap[x] for x in context.ctx.values)
|
|
||||||
await context.ctx.edit_origin(
|
|
||||||
content=f"Unfollowed the following:\n```\n{block}\n```", components=components
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
for row in components:
|
|
||||||
for component in row.components:
|
|
||||||
component.disabled = True
|
|
||||||
await message.edit(components=components)
|
|
||||||
|
|
||||||
@twitter.subcommand(
|
|
||||||
sub_cmd_name="retweets",
|
|
||||||
sub_cmd_description="Modify followed Twitter accounts",
|
|
||||||
)
|
|
||||||
@slash_option(
|
|
||||||
name="retweets",
|
|
||||||
description="Mirror re-tweets?",
|
|
||||||
opt_type=OptionType.BOOLEAN,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
|
||||||
async def _twitter_modify(self, ctx: InteractionContext, retweets: bool = True) -> None:
|
|
||||||
t = TwitterFollow.find(TwitterFollow.guild == ctx.guild.id)
|
|
||||||
twitters = []
|
|
||||||
async for twitter in t:
|
|
||||||
twitters.append(twitter)
|
|
||||||
if not twitters:
|
|
||||||
await ctx.send("You need to follow a Twitter account first", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
options = []
|
|
||||||
handlemap = {}
|
|
||||||
for twitter in twitters:
|
|
||||||
account = await TwitterAccount.find_one(TwitterAccount.twitter_id == twitter.id)
|
|
||||||
handlemap[str(twitter.twitter_id)] = account.handle
|
|
||||||
option = StringSelectOption(label=account.handle, value=str(twitter.twitter_id))
|
|
||||||
options.append(option)
|
|
||||||
|
|
||||||
select = StringSelectMenu(options=options, custom_id="to_update", min_values=1, max_values=len(twitters))
|
|
||||||
|
|
||||||
components = [ActionRow(select)]
|
|
||||||
block = "\n".join(x for x in handlemap.values())
|
|
||||||
message = await ctx.send(
|
|
||||||
content=f"You are following the following accounts:\n```\n{block}\n```\n\n"
|
|
||||||
f"Please choose which accounts to {'un' if not retweets else ''}follow retweets from",
|
|
||||||
components=components,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
context = await self.bot.wait_for_component(
|
|
||||||
check=lambda x: ctx.author.id == x.ctx.author.id,
|
|
||||||
messages=message,
|
|
||||||
timeout=60 * 5,
|
|
||||||
)
|
|
||||||
|
|
||||||
handlemap = {}
|
|
||||||
for to_update in context.ctx.values:
|
|
||||||
account = await TwitterAccount.find_one(TwitterAccount.twitter_id == int(to_update))
|
|
||||||
handlemap[str(twitter.twitter_id)] = account.handle
|
|
||||||
t = get(twitters, guild=ctx.guild.id, twitter_id=int(to_update))
|
|
||||||
t.retweets = True
|
|
||||||
await t.save()
|
|
||||||
|
|
||||||
for row in components:
|
|
||||||
for component in row.components:
|
|
||||||
component.disabled = True
|
|
||||||
|
|
||||||
block = "\n".join(handlemap[x] for x in context.ctx.values)
|
|
||||||
await context.ctx.edit_origin(
|
|
||||||
content=(
|
|
||||||
f"{'Unfollowed' if not retweets else 'Followed'} "
|
|
||||||
"retweets from the following:"
|
|
||||||
f"\n```\n{block}\n```"
|
|
||||||
),
|
|
||||||
components=components,
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
for row in components:
|
|
||||||
for component in row.components:
|
|
||||||
component.disabled = True
|
|
||||||
await message.edit(components=components)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: Client) -> None:
|
|
||||||
"""Add TwitterCog to JARVIS"""
|
|
||||||
if load_config().twitter:
|
|
||||||
TwitterCog(bot)
|
|
||||||
else:
|
|
||||||
bot.logger.info("No Twitter configuration, not loading")
|
|
|
@ -92,16 +92,35 @@ class UtilCog(Extension):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fields.append(
|
fields.append(
|
||||||
EmbedField(name="interactions", value=f"[{ipyv}](https://interactionspy.readthedocs.io)", inline=True)
|
EmbedField(
|
||||||
|
name="interactions",
|
||||||
|
value=f"[{ipyv}](https://interactionspy.readthedocs.io)",
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
self.bot.logger.debug("Getting repo information")
|
self.bot.logger.debug("Getting repo information")
|
||||||
repo_url = f"https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/tree/{get_repo_hash()}"
|
repo_url = f"https://git.zevaryx.com/stark-industries/jarvis/jarvis-bot/-/tree/{get_repo_hash()}"
|
||||||
fields.append(EmbedField(name="Git Hash", value=f"[{get_repo_hash()[:7]}]({repo_url})", inline=True))
|
fields.append(
|
||||||
fields.append(EmbedField(name="Online Since", value=f"<t:{uptime}:F>", inline=False))
|
EmbedField(
|
||||||
|
name="Git Hash",
|
||||||
|
value=f"[{get_repo_hash()[:7]}]({repo_url})",
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fields.append(
|
||||||
|
EmbedField(name="Online Since", value=f"<t:{uptime}:F>", inline=False)
|
||||||
|
)
|
||||||
num_domains = len(self.bot.phishing_domains)
|
num_domains = len(self.bot.phishing_domains)
|
||||||
fields.append(EmbedField(name="Phishing Protection", value=f"Detecting {num_domains} phishing domains"))
|
fields.append(
|
||||||
|
EmbedField(
|
||||||
|
name="Phishing Protection",
|
||||||
|
value=f"Detecting {num_domains} phishing domains",
|
||||||
|
)
|
||||||
|
)
|
||||||
embed = build_embed(title=title, description=desc, fields=fields, color=color)
|
embed = build_embed(title=title, description=desc, fields=fields, color=color)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@bot.subcommand(
|
@bot.subcommand(
|
||||||
|
@ -114,7 +133,11 @@ class UtilCog(Extension):
|
||||||
JARVIS_LOGO.save(image_bytes, "PNG")
|
JARVIS_LOGO.save(image_bytes, "PNG")
|
||||||
image_bytes.seek(0)
|
image_bytes.seek(0)
|
||||||
logo = File(image_bytes, file_name="logo.png")
|
logo = File(image_bytes, file_name="logo.png")
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER,
|
||||||
|
emoji="🗑️",
|
||||||
|
custom_id=f"delete|{ctx.author.id}",
|
||||||
|
)
|
||||||
await ctx.send(file=logo, components=components)
|
await ctx.send(file=logo, components=components)
|
||||||
|
|
||||||
rc = SlashCommand(name="rc", description="Robot Camo emoji commands")
|
rc = SlashCommand(name="rc", description="Robot Camo emoji commands")
|
||||||
|
@ -159,8 +182,10 @@ class UtilCog(Extension):
|
||||||
|
|
||||||
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.username}#{user.discriminator}", icon_url=avatar)
|
embed.set_author(name=f"{user.username}", icon_url=avatar)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@slash_command(
|
@slash_command(
|
||||||
|
@ -179,11 +204,19 @@ class UtilCog(Extension):
|
||||||
EmbedField(name="Name", value=role.mention, inline=True),
|
EmbedField(name="Name", value=role.mention, inline=True),
|
||||||
EmbedField(name="Color", value=str(role.color.hex), inline=True),
|
EmbedField(name="Color", value=str(role.color.hex), inline=True),
|
||||||
EmbedField(name="Mention", value=f"`{role.mention}`", inline=True),
|
EmbedField(name="Mention", value=f"`{role.mention}`", inline=True),
|
||||||
EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True),
|
EmbedField(
|
||||||
|
name="Hoisted", value="Yes" if role.hoist else "No", inline=True
|
||||||
|
),
|
||||||
EmbedField(name="Position", value=str(role.position), inline=True),
|
EmbedField(name="Position", value=str(role.position), inline=True),
|
||||||
EmbedField(name="Mentionable", value="Yes" if role.mentionable else "No", inline=True),
|
EmbedField(
|
||||||
|
name="Mentionable",
|
||||||
|
value="Yes" if role.mentionable else "No",
|
||||||
|
inline=True,
|
||||||
|
),
|
||||||
EmbedField(name="Member Count", value=str(len(role.members)), inline=True),
|
EmbedField(name="Member Count", value=str(len(role.members)), inline=True),
|
||||||
EmbedField(name="Created At", value=f"<t:{int(role.created_at.timestamp())}:F>"),
|
EmbedField(
|
||||||
|
name="Created At", value=f"<t:{int(role.created_at.timestamp())}:F>"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="",
|
title="",
|
||||||
|
@ -208,7 +241,11 @@ class UtilCog(Extension):
|
||||||
im.save(image_bytes, "PNG")
|
im.save(image_bytes, "PNG")
|
||||||
image_bytes.seek(0)
|
image_bytes.seek(0)
|
||||||
color_show = File(image_bytes, file_name="color_show.png")
|
color_show = File(image_bytes, file_name="color_show.png")
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER,
|
||||||
|
emoji="🗑️",
|
||||||
|
custom_id=f"delete|{ctx.author.id}",
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, file=color_show, components=components)
|
await ctx.send(embeds=embed, file=color_show, components=components)
|
||||||
|
|
||||||
@slash_command(name="avatar", description="Get a user avatar")
|
@slash_command(name="avatar", description="Get a user avatar")
|
||||||
|
@ -252,14 +289,18 @@ class UtilCog(Extension):
|
||||||
),
|
),
|
||||||
EmbedField(
|
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,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
if muted:
|
if muted:
|
||||||
ts = int(user.communication_disabled_until.timestamp())
|
ts = int(user.communication_disabled_until.timestamp())
|
||||||
fields.append(EmbedField(name="Muted Until", value=f"<t:{ts}:F> (<t:{ts}:R>)"))
|
fields.append(
|
||||||
|
EmbedField(name="Muted Until", value=f"<t:{ts}:F> (<t:{ts}:R>)")
|
||||||
|
)
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="",
|
title="",
|
||||||
|
@ -269,16 +310,23 @@ class UtilCog(Extension):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=f"{'🔇 ' if muted else ''}{user.display_name}#{user.discriminator}",
|
name=f"{'🔇 ' if muted else ''}{user.display_name}",
|
||||||
icon_url=user.display_avatar.url,
|
icon_url=user.display_avatar.url,
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(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}")
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@slash_command(name="lmgtfy", description="Let me Google that for you")
|
@slash_command(name="lmgtfy", description="Let me Google that for you")
|
||||||
@slash_option(name="search", description="What to search", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
|
name="search",
|
||||||
|
description="What to search",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
async def _lmgtfy(self, ctx: SlashContext, search: str) -> None:
|
async def _lmgtfy(self, ctx: SlashContext, search: str) -> None:
|
||||||
url = "https://letmegooglethat.com/?q=" + urllib.parse.quote_plus(search)
|
url = "https://letmegooglethat.com/?q=" + urllib.parse.quote_plus(search)
|
||||||
await ctx.send(url)
|
await ctx.send(url)
|
||||||
|
@ -306,7 +354,7 @@ class UtilCog(Extension):
|
||||||
|
|
||||||
owner = await guild.fetch_owner()
|
owner = await guild.fetch_owner()
|
||||||
|
|
||||||
owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
|
owner = f"{owner.username}" if owner else "||`[redacted]`||"
|
||||||
|
|
||||||
categories = len([x for x in guild.channels if isinstance(x, GuildCategory)])
|
categories = len([x for x in guild.channels if isinstance(x, GuildCategory)])
|
||||||
text_channels = len([x for x in guild.channels if isinstance(x, GuildText)])
|
text_channels = len([x for x in guild.channels if isinstance(x, GuildText)])
|
||||||
|
@ -325,24 +373,29 @@ class UtilCog(Extension):
|
||||||
EmbedField(name="Threads", value=str(threads), inline=True),
|
EmbedField(name="Threads", value=str(threads), inline=True),
|
||||||
EmbedField(name="Members", value=str(members), inline=True),
|
EmbedField(name="Members", value=str(members), inline=True),
|
||||||
EmbedField(name="Roles", value=str(roles), inline=True),
|
EmbedField(name="Roles", value=str(roles), inline=True),
|
||||||
EmbedField(name="Created At", value=f"<t:{int(guild.created_at.timestamp())}:F>"),
|
EmbedField(
|
||||||
|
name="Created At", value=f"<t:{int(guild.created_at.timestamp())}:F>"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if len(role_list) < 1024:
|
if len(role_list) < 1024:
|
||||||
fields.append(EmbedField(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")
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@slash_command(
|
@slash_command(
|
||||||
name="pw",
|
name="pw",
|
||||||
sub_cmd_name="gen",
|
sub_cmd_name="gen",
|
||||||
description="Generate a secure password",
|
description="Generate a secure password",
|
||||||
scopes=[862402786116763668],
|
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="length",
|
name="length",
|
||||||
|
@ -363,7 +416,9 @@ class UtilCog(Extension):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=15)
|
||||||
async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3) -> None:
|
async def _pw_gen(
|
||||||
|
self, ctx: SlashContext, length: int = 32, chars: int = 3
|
||||||
|
) -> None:
|
||||||
if length > 256:
|
if length > 256:
|
||||||
await ctx.send("Please limit password to 256 characters", ephemeral=True)
|
await ctx.send("Please limit password to 256 characters", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
@ -384,7 +439,12 @@ class UtilCog(Extension):
|
||||||
)
|
)
|
||||||
|
|
||||||
@slash_command(name="pigpen", description="Encode a string into pigpen")
|
@slash_command(name="pigpen", description="Encode a string into pigpen")
|
||||||
@slash_option(name="text", description="Text to encode", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
|
name="text",
|
||||||
|
description="Text to encode",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
async def _pigpen(self, ctx: SlashContext, text: str) -> None:
|
async def _pigpen(self, ctx: SlashContext, text: str) -> None:
|
||||||
outp = "`"
|
outp = "`"
|
||||||
for c in text:
|
for c in text:
|
||||||
|
@ -399,17 +459,34 @@ class UtilCog(Extension):
|
||||||
outp += "`"
|
outp += "`"
|
||||||
await ctx.send(outp[:2000])
|
await ctx.send(outp[:2000])
|
||||||
|
|
||||||
@slash_command(name="timestamp", description="Convert a datetime or timestamp into it's counterpart")
|
@slash_command(
|
||||||
@slash_option(name="string", description="String to convert", opt_type=OptionType.STRING, required=True)
|
name="timestamp",
|
||||||
@slash_option(name="private", description="Respond quietly?", opt_type=OptionType.BOOLEAN, required=False)
|
description="Convert a datetime or timestamp into it's counterpart",
|
||||||
async def _timestamp(self, ctx: SlashContext, string: str, private: bool = False) -> None:
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="string",
|
||||||
|
description="String to convert",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="private",
|
||||||
|
description="Respond quietly?",
|
||||||
|
opt_type=OptionType.BOOLEAN,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
async def _timestamp(
|
||||||
|
self, ctx: SlashContext, string: str, private: bool = False
|
||||||
|
) -> None:
|
||||||
timestamp = parse(string)
|
timestamp = parse(string)
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
await ctx.send("Valid time not found, try again", ephemeral=True)
|
await ctx.send("Valid time not found, try again", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not timestamp.tzinfo:
|
if not timestamp.tzinfo:
|
||||||
timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(tz=timezone.utc)
|
timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(
|
||||||
|
tz=timezone.utc
|
||||||
|
)
|
||||||
|
|
||||||
timestamp_utc = timestamp.astimezone(tz=timezone.utc)
|
timestamp_utc = timestamp.astimezone(tz=timezone.utc)
|
||||||
|
|
||||||
|
@ -422,8 +499,12 @@ class UtilCog(Extension):
|
||||||
EmbedField(name="Relative Time", value=f"<t:{ts_utc}:R>\n`<t:{ts_utc}:R>`"),
|
EmbedField(name="Relative Time", value=f"<t:{ts_utc}:R>\n`<t:{ts_utc}:R>`"),
|
||||||
EmbedField(name="ISO8601", value=timestamp.isoformat()),
|
EmbedField(name="ISO8601", value=timestamp.isoformat()),
|
||||||
]
|
]
|
||||||
embed = build_embed(title="Converted Time", description=f"`{string}`", fields=fields)
|
embed = build_embed(
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
title="Converted Time", description=f"`{string}`", fields=fields
|
||||||
|
)
|
||||||
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, ephemeral=private, components=components)
|
await ctx.send(embeds=embed, ephemeral=private, components=components)
|
||||||
|
|
||||||
@bot.subcommand(sub_cmd_name="support", sub_cmd_description="Got issues?")
|
@bot.subcommand(sub_cmd_name="support", sub_cmd_description="Got issues?")
|
||||||
|
@ -438,7 +519,10 @@ We'll help as best we can with whatever issues you encounter.
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@bot.subcommand(sub_cmd_name="privacy_terms", sub_cmd_description="View Privacy and Terms of Use")
|
@bot.subcommand(
|
||||||
|
sub_cmd_name="privacy_terms",
|
||||||
|
sub_cmd_description="View Privacy and Terms of Use",
|
||||||
|
)
|
||||||
async def _privacy_terms(self, ctx: SlashContext) -> None:
|
async def _privacy_terms(self, ctx: SlashContext) -> None:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
"""JARVIS Calculator Cog."""
|
"""JARVIS Calculator Cog."""
|
||||||
import json
|
|
||||||
|
|
||||||
from aiohttp import ClientSession
|
|
||||||
from calculator import calculate
|
from calculator import calculate
|
||||||
|
from erapi import const
|
||||||
from interactions import AutocompleteContext, Client, Extension, InteractionContext
|
from interactions import AutocompleteContext, Client, Extension, InteractionContext
|
||||||
from interactions.models.discord.components import Button
|
from interactions.models.discord.components import Button
|
||||||
from interactions.models.discord.embed import Embed, EmbedField
|
from interactions.models.discord.embed import Embed, EmbedField
|
||||||
|
@ -24,39 +22,8 @@ TEMP_CHOICES = (
|
||||||
SlashCommandChoice(name="Kelvin", value=2),
|
SlashCommandChoice(name="Kelvin", value=2),
|
||||||
)
|
)
|
||||||
TEMP_LOOKUP = {0: "F", 1: "C", 2: "K"}
|
TEMP_LOOKUP = {0: "F", 1: "C", 2: "K"}
|
||||||
CURRENCIES = (
|
CURRENCY_BY_NAME = {x["name"]: x["code"] for x in const.VALID_CODES}
|
||||||
"AUD",
|
CURRENCY_BY_CODE = {x["code"]: x["name"] for x in const.VALID_CODES}
|
||||||
"BGN",
|
|
||||||
"BRL",
|
|
||||||
"CAD",
|
|
||||||
"CHF",
|
|
||||||
"CNY",
|
|
||||||
"CZK",
|
|
||||||
"DKK",
|
|
||||||
"EUR",
|
|
||||||
"GBP",
|
|
||||||
"HKD",
|
|
||||||
"HRK",
|
|
||||||
"HUF",
|
|
||||||
"IDR",
|
|
||||||
"INR",
|
|
||||||
"ISK",
|
|
||||||
"JPY",
|
|
||||||
"KRW",
|
|
||||||
"MXN",
|
|
||||||
"MYR",
|
|
||||||
"NOK",
|
|
||||||
"NZD",
|
|
||||||
"PHP",
|
|
||||||
"PLN",
|
|
||||||
"RON",
|
|
||||||
"SEK",
|
|
||||||
"SGD",
|
|
||||||
"THB",
|
|
||||||
"TRY",
|
|
||||||
"USD",
|
|
||||||
"ZAR",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CalcCog(Extension):
|
class CalcCog(Extension):
|
||||||
|
@ -67,22 +34,32 @@ class CalcCog(Extension):
|
||||||
|
|
||||||
async def _get_currency_conversion(self, from_: str, to: str) -> int:
|
async def _get_currency_conversion(self, from_: str, to: str) -> int:
|
||||||
"""Get the conversion rate."""
|
"""Get the conversion rate."""
|
||||||
async with ClientSession() as session:
|
return self.bot.erapi.get_conversion_rate(from_, to)
|
||||||
async with session.get("https://theforexapi.com/api/latest", params={"base": from_, "symbols": to}) as resp:
|
|
||||||
raw = await resp.content.read()
|
|
||||||
data = json.loads(raw.decode("UTF8"))
|
|
||||||
return data["rates"][to]
|
|
||||||
|
|
||||||
calc = SlashCommand(name="calc", description="Calculate some things")
|
calc = SlashCommand(name="calc", description="Calculate some things")
|
||||||
|
|
||||||
@calc.subcommand(sub_cmd_name="math", sub_cmd_description="Do a basic math calculation")
|
@calc.subcommand(
|
||||||
@slash_option(name="expression", description="Expression to calculate", required=True, opt_type=OptionType.STRING)
|
sub_cmd_name="math", sub_cmd_description="Do a basic math calculation"
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="expression",
|
||||||
|
description="Expression to calculate",
|
||||||
|
required=True,
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
)
|
||||||
async def _calc_math(self, ctx: InteractionContext, expression: str) -> None:
|
async def _calc_math(self, ctx: InteractionContext, expression: str) -> None:
|
||||||
if expression == "The answer to life, the universe, and everything":
|
if expression == "The answer to life, the universe, and everything":
|
||||||
fields = (EmbedField(name="Expression", value=f"`{expression}`"), EmbedField(name="Result", value=str(42)))
|
fields = (
|
||||||
|
EmbedField(name="Expression", value=f"`{expression}`"),
|
||||||
|
EmbedField(name="Result", value=str(42)),
|
||||||
|
)
|
||||||
embed = build_embed(title="Calculator", description=None, fields=fields)
|
embed = build_embed(title="Calculator", description=None, fields=fields)
|
||||||
|
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER,
|
||||||
|
emoji="🗑️",
|
||||||
|
custom_id=f"delete|{ctx.author.id}",
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -94,21 +71,41 @@ class CalcCog(Extension):
|
||||||
await ctx.send("No value? Try a valid expression", ephemeral=True)
|
await ctx.send("No value? Try a valid expression", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
fields = (EmbedField(name="Expression", value=f"`{expression}`"), EmbedField(name="Result", value=str(value)))
|
fields = (
|
||||||
|
EmbedField(name="Expression", value=f"`{expression}`"),
|
||||||
|
EmbedField(name="Result", value=str(value)),
|
||||||
|
)
|
||||||
embed = build_embed(title="Calculator", description=None, fields=fields)
|
embed = build_embed(title="Calculator", description=None, fields=fields)
|
||||||
|
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
convert = calc.group(name="convert", description="Conversion helpers")
|
convert = calc.group(name="convert", description="Conversion helpers")
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="temperature", sub_cmd_description="Convert between temperatures")
|
@convert.subcommand(
|
||||||
@slash_option(name="value", description="Value to convert", required=True, opt_type=OptionType.NUMBER)
|
sub_cmd_name="temperature", sub_cmd_description="Convert between temperatures"
|
||||||
@slash_option(
|
|
||||||
name="from_unit", description="From unit", required=True, opt_type=OptionType.INTEGER, choices=TEMP_CHOICES
|
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="To unit", required=True, opt_type=OptionType.INTEGER, choices=TEMP_CHOICES
|
name="value",
|
||||||
|
description="Value to convert",
|
||||||
|
required=True,
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="from_unit",
|
||||||
|
description="From unit",
|
||||||
|
required=True,
|
||||||
|
opt_type=OptionType.INTEGER,
|
||||||
|
choices=TEMP_CHOICES,
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="to_unit",
|
||||||
|
description="To unit",
|
||||||
|
required=True,
|
||||||
|
opt_type=OptionType.INTEGER,
|
||||||
|
choices=TEMP_CHOICES,
|
||||||
)
|
)
|
||||||
async def _calc_convert_temperature(
|
async def _calc_convert_temperature(
|
||||||
self, ctx: InteractionContext, value: int, from_unit: int, to_unit: int
|
self, ctx: InteractionContext, value: int, from_unit: int, to_unit: int
|
||||||
|
@ -139,11 +136,21 @@ class CalcCog(Extension):
|
||||||
description=f"°{TEMP_LOOKUP.get(from_unit)} -> °{TEMP_LOOKUP.get(to_unit)}",
|
description=f"°{TEMP_LOOKUP.get(from_unit)} -> °{TEMP_LOOKUP.get(to_unit)}",
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="currency", sub_cmd_description="Convert currency based on current rates")
|
@convert.subcommand(
|
||||||
@slash_option(name="value", description="Value of starting currency", required=True, opt_type=OptionType.NUMBER)
|
sub_cmd_name="currency",
|
||||||
|
sub_cmd_description="Convert currency based on current rates",
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Value of starting currency",
|
||||||
|
required=True,
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_currency",
|
name="from_currency",
|
||||||
description="Currency to convert from",
|
description="Currency to convert from",
|
||||||
|
@ -168,27 +175,58 @@ class CalcCog(Extension):
|
||||||
conv = value * rate
|
conv = value * rate
|
||||||
|
|
||||||
fields = (
|
fields = (
|
||||||
EmbedField(name="Conversion Rate", value=f"1 {from_currency} ~= {rate:0.4f} {to_currency}"),
|
EmbedField(
|
||||||
EmbedField(name=from_currency, value=f"{value:0.2f}"),
|
name="Conversion Rate",
|
||||||
EmbedField(name=to_currency, value=f"{conv:0.2f}"),
|
value=f"1 {from_currency} ~= {rate:0.4f} {to_currency}",
|
||||||
|
),
|
||||||
|
EmbedField(
|
||||||
|
name=f"{CURRENCY_BY_CODE[from_currency]} ({from_currency})",
|
||||||
|
value=f"{value:0.2f}",
|
||||||
|
),
|
||||||
|
EmbedField(
|
||||||
|
name=f"{CURRENCY_BY_CODE[to_currency]} ({to_currency})",
|
||||||
|
value=f"{conv:0.2f}",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
embed = build_embed(title="Conversion", description=f"{from_currency} -> {to_currency}", fields=fields)
|
embed = build_embed(
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
title="Conversion",
|
||||||
|
description=f"{from_currency} -> {to_currency}",
|
||||||
|
fields=fields,
|
||||||
|
)
|
||||||
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER,
|
||||||
|
emoji="🗑️",
|
||||||
|
custom_id=f"delete|{ctx.author.id}",
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
async def _convert(self, ctx: InteractionContext, from_: str, to: str, value: int) -> Embed:
|
async def _convert(
|
||||||
|
self, ctx: InteractionContext, from_: str, to: str, value: int
|
||||||
|
) -> Embed:
|
||||||
*_, which = ctx.invoke_target.split(" ")
|
*_, which = ctx.invoke_target.split(" ")
|
||||||
which = getattr(units, which.capitalize(), None)
|
which = getattr(units, which.capitalize(), None)
|
||||||
ratio = which.get_rate(from_, to)
|
ratio = which.get_rate(from_, to)
|
||||||
converted = value / ratio
|
converted = value / ratio
|
||||||
fields = (EmbedField(name=from_, value=f"{value:0.2f}"), EmbedField(name=to, value=f"{converted:0.2f}"))
|
fields = (
|
||||||
embed = build_embed(title="Conversion", description=f"{from_} -> {to}", fields=fields)
|
EmbedField(name=from_, value=f"{value:0.2f}"),
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
EmbedField(name=to, value=f"{converted:0.2f}"),
|
||||||
|
)
|
||||||
|
embed = build_embed(
|
||||||
|
title="Conversion", description=f"{from_} -> {to}", fields=fields
|
||||||
|
)
|
||||||
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="angle", sub_cmd_description="Convert angles")
|
@convert.subcommand(sub_cmd_name="angle", sub_cmd_description="Convert angles")
|
||||||
@slash_option(name="value", description="Angle to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Angle to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -197,13 +235,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_angle(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_angle(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="area", sub_cmd_description="Convert areas")
|
@convert.subcommand(sub_cmd_name="area", sub_cmd_description="Convert areas")
|
||||||
@slash_option(name="value", description="Area to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Area to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -212,13 +261,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_area(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_area(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="data", sub_cmd_description="Convert data sizes")
|
@convert.subcommand(sub_cmd_name="data", sub_cmd_description="Convert data sizes")
|
||||||
@slash_option(name="value", description="Data size to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Data size to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -227,13 +287,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_data(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_data(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="energy", sub_cmd_description="Convert energy")
|
@convert.subcommand(sub_cmd_name="energy", sub_cmd_description="Convert energy")
|
||||||
@slash_option(name="value", description="Energy to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Energy to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -242,13 +313,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_energy(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_energy(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="length", sub_cmd_description="Convert lengths")
|
@convert.subcommand(sub_cmd_name="length", sub_cmd_description="Convert lengths")
|
||||||
@slash_option(name="value", description="Length to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Length to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -257,13 +339,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_length(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_length(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="power", sub_cmd_description="Convert powers")
|
@convert.subcommand(sub_cmd_name="power", sub_cmd_description="Convert powers")
|
||||||
@slash_option(name="value", description="Power to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Power to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -272,13 +365,26 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_power(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_power(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="pressure", sub_cmd_description="Convert pressures")
|
@convert.subcommand(
|
||||||
@slash_option(name="value", description="Pressure to convert", opt_type=OptionType.NUMBER, required=True)
|
sub_cmd_name="pressure", sub_cmd_description="Convert pressures"
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Pressure to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -287,13 +393,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_pressure(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_pressure(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="speed", sub_cmd_description="Convert speeds")
|
@convert.subcommand(sub_cmd_name="speed", sub_cmd_description="Convert speeds")
|
||||||
@slash_option(name="value", description="Speed to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Speed to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -302,13 +419,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_speed(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_speed(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="time", sub_cmd_description="Convert times")
|
@convert.subcommand(sub_cmd_name="time", sub_cmd_description="Convert times")
|
||||||
@slash_option(name="value", description="Time to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Time to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -317,13 +445,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_time(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_time(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="volume", sub_cmd_description="Convert volumes")
|
@convert.subcommand(sub_cmd_name="volume", sub_cmd_description="Convert volumes")
|
||||||
@slash_option(name="value", description="Volume to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Volume to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -332,13 +471,24 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_volume(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_volume(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
@convert.subcommand(sub_cmd_name="weight", sub_cmd_description="Convert weights")
|
@convert.subcommand(sub_cmd_name="weight", sub_cmd_description="Convert weights")
|
||||||
@slash_option(name="value", description="Weight to convert", opt_type=OptionType.NUMBER, required=True)
|
@slash_option(
|
||||||
|
name="value",
|
||||||
|
description="Weight to convert",
|
||||||
|
opt_type=OptionType.NUMBER,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="from_unit",
|
name="from_unit",
|
||||||
description="Units to convert from",
|
description="Units to convert from",
|
||||||
|
@ -347,12 +497,20 @@ class CalcCog(Extension):
|
||||||
autocomplete=True,
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="to_unit", description="Units to convert to", opt_type=OptionType.STRING, required=True, autocomplete=True
|
name="to_unit",
|
||||||
|
description="Units to convert to",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
autocomplete=True,
|
||||||
)
|
)
|
||||||
async def _calc_convert_weight(self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str) -> None:
|
async def _calc_convert_weight(
|
||||||
|
self, ctx: InteractionContext, value: int, from_unit: str, to_unit: str
|
||||||
|
) -> None:
|
||||||
await self._convert(ctx, from_unit, to_unit, value)
|
await self._convert(ctx, from_unit, to_unit, value)
|
||||||
|
|
||||||
def _unit_autocomplete(self, which: units.Converter, unit: str) -> list[dict[str, str]]:
|
def _unit_autocomplete(
|
||||||
|
self, which: units.Converter, unit: str
|
||||||
|
) -> list[dict[str, str]]:
|
||||||
options = list(which.CONVERSIONS.keys())
|
options = list(which.CONVERSIONS.keys())
|
||||||
results = process.extract(unit, options, limit=25)
|
results = process.extract(unit, options, limit=25)
|
||||||
if any([r[1] > 0 for r in results]):
|
if any([r[1] > 0 for r in results]):
|
||||||
|
@ -392,10 +550,24 @@ class CalcCog(Extension):
|
||||||
await ctx.send(choices=self._unit_autocomplete(which, ctx.input_text))
|
await ctx.send(choices=self._unit_autocomplete(which, ctx.input_text))
|
||||||
|
|
||||||
def _currency_autocomplete(self, currency: str) -> list[dict[str, str]]:
|
def _currency_autocomplete(self, currency: str) -> list[dict[str, str]]:
|
||||||
results = process.extract(currency, CURRENCIES, limit=25)
|
lookup_name = {f"{k} ({v})": v for k, v in CURRENCY_BY_NAME.items()}
|
||||||
|
lookup_value = {v: k for k, v in lookup_name.items()}
|
||||||
|
results_name = process.extract(currency, list(lookup_name.keys()), limit=25)
|
||||||
|
results_value = process.extract(currency, list(lookup_value.keys()), limit=25)
|
||||||
|
results = {}
|
||||||
|
for r in results_value + results_name:
|
||||||
|
name = r[0]
|
||||||
|
if len(name) == 3:
|
||||||
|
name = lookup_value[name]
|
||||||
|
if name not in results:
|
||||||
|
results[name] = r[1]
|
||||||
|
if r[1] > results[name]:
|
||||||
|
results[name] = r[1]
|
||||||
|
|
||||||
|
results = sorted(list(results.items()), key=lambda x: -x[1])[:10]
|
||||||
if any([r[1] > 0 for r in results]):
|
if any([r[1] > 0 for r in results]):
|
||||||
return [{"name": r[0], "value": r[0]} for r in results if r[1] > 50]
|
return [{"name": r[0], "value": lookup_name[r[0]]} for r in results if r[1]]
|
||||||
return [{"name": r[0], "value": r[0]} for r in results]
|
return [{"name": r[0], "value": lookup_name[r[0]]} for r in results]
|
||||||
|
|
||||||
@_calc_convert_currency.autocomplete("from_currency")
|
@_calc_convert_currency.autocomplete("from_currency")
|
||||||
async def _autocomplete_from_currency(self, ctx: AutocompleteContext) -> None:
|
async def _autocomplete_from_currency(self, ctx: AutocompleteContext) -> None:
|
||||||
|
|
|
@ -5,14 +5,17 @@ import logging
|
||||||
import re
|
import re
|
||||||
import subprocess # noqa: S404
|
import subprocess # noqa: S404
|
||||||
import uuid as uuidpy
|
import uuid as uuidpy
|
||||||
|
from datetime import datetime
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import nanoid
|
import nanoid
|
||||||
|
import pytz
|
||||||
import ulid as ulidpy
|
import ulid as ulidpy
|
||||||
from aiofile import AIOFile
|
from aiofile import AIOFile
|
||||||
from ansitoimg.render import ansiToRender
|
from ansitoimg.render import ansiToRender
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
from interactions import Client, Extension, InteractionContext
|
from croniter import croniter
|
||||||
|
from interactions import Client, Extension, InteractionContext, AutocompleteContext
|
||||||
from interactions.models.discord.components import Button
|
from interactions.models.discord.components import Button
|
||||||
from interactions.models.discord.embed import EmbedField
|
from interactions.models.discord.embed import EmbedField
|
||||||
from interactions.models.discord.enums import ButtonStyle
|
from interactions.models.discord.enums import ButtonStyle
|
||||||
|
@ -30,13 +33,16 @@ from jarvis_core.filters import invites, url
|
||||||
from jarvis_core.util import convert_bytesize, hash
|
from jarvis_core.util import convert_bytesize, hash
|
||||||
from jarvis_core.util.http import get_size
|
from jarvis_core.util.http import get_size
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from thefuzz import process
|
||||||
|
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
|
|
||||||
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=[^,]+,?)+)$" # noqa: E501
|
r"^(?:(?P<cn>CN=(?P<name>[^,]*)),)?(?:(?P<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?P<domain>(?:DC=[^,]+,?)+)$" # noqa: E501
|
||||||
)
|
)
|
||||||
|
@ -60,6 +66,42 @@ class DevCog(Extension):
|
||||||
|
|
||||||
dev = SlashCommand(name="dev", description="Developer utilities")
|
dev = SlashCommand(name="dev", description="Developer utilities")
|
||||||
|
|
||||||
|
@dev.subcommand(sub_cmd_name="cron", sub_cmd_description="Test cron strings")
|
||||||
|
@slash_option(
|
||||||
|
name="cron",
|
||||||
|
description="Cron pattern",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
@slash_option(
|
||||||
|
name="timezone",
|
||||||
|
description="Timezone",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=False,
|
||||||
|
autocomplete=True,
|
||||||
|
)
|
||||||
|
async def _dev_cron(self, ctx: InteractionContext, cron: str, timezone="utc"):
|
||||||
|
try:
|
||||||
|
if not croniter.is_valid(cron):
|
||||||
|
await ctx.defer(ephemeral=True)
|
||||||
|
await ctx.send(f"Invalid cron pattern: `{cron}`", ephemeral=True)
|
||||||
|
return
|
||||||
|
base = datetime.now(tz=pytz.timezone(timezone))
|
||||||
|
parsed = croniter(cron, base)
|
||||||
|
next_5 = [parsed.get_next() for _ in range(5)]
|
||||||
|
|
||||||
|
next_5_str = "\n".join(f"<t:{int(x)}:F> (<t:{int(x)}:R>)" for x in next_5)
|
||||||
|
|
||||||
|
embed = build_embed(
|
||||||
|
title="Cron",
|
||||||
|
description=f"Pattern: `{cron}`\n\nNext 5 runs:\n{next_5_str}\n\nTimezone: `{timezone}`",
|
||||||
|
fields=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embeds=[embed])
|
||||||
|
except Exception:
|
||||||
|
self.logger.error("Encountered error", exc_info=True)
|
||||||
|
|
||||||
@dev.subcommand(sub_cmd_name="hash", sub_cmd_description="Hash some data")
|
@dev.subcommand(sub_cmd_name="hash", sub_cmd_description="Hash some data")
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="method",
|
name="method",
|
||||||
|
@ -74,9 +116,20 @@ class DevCog(Extension):
|
||||||
opt_type=OptionType.STRING,
|
opt_type=OptionType.STRING,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@slash_option(name="attach", description="File to hash", opt_type=OptionType.ATTACHMENT, required=False)
|
@slash_option(
|
||||||
|
name="attach",
|
||||||
|
description="File to hash",
|
||||||
|
opt_type=OptionType.ATTACHMENT,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _hash(self, ctx: InteractionContext, method: str, data: str = None, attach: Attachment = None) -> None:
|
async def _hash(
|
||||||
|
self,
|
||||||
|
ctx: InteractionContext,
|
||||||
|
method: str,
|
||||||
|
data: str = None,
|
||||||
|
attach: Attachment = None,
|
||||||
|
) -> None:
|
||||||
if not data and not attach:
|
if not data and not attach:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"No data to hash",
|
"No data to hash",
|
||||||
|
@ -93,8 +146,12 @@ class DevCog(Extension):
|
||||||
elif url.match(data):
|
elif url.match(data):
|
||||||
try:
|
try:
|
||||||
if (size := await get_size(data)) > MAX_FILESIZE:
|
if (size := await get_size(data)) > MAX_FILESIZE:
|
||||||
await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True)
|
await ctx.send(
|
||||||
self.logger.debug(f"Refused to hash file of size {convert_bytesize(size)}")
|
"Please hash files that are <= 5GB in size", ephemeral=True
|
||||||
|
)
|
||||||
|
self.logger.debug(
|
||||||
|
f"Refused to hash file of size {convert_bytesize(size)}"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True)
|
await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True)
|
||||||
|
@ -117,7 +174,9 @@ class DevCog(Extension):
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(title=title, description=description, fields=fields)
|
embed = build_embed(title=title, description=description, fields=fields)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@dev.subcommand(sub_cmd_name="uuid", sub_cmd_description="Generate a UUID")
|
@dev.subcommand(sub_cmd_name="uuid", sub_cmd_description="Generate a UUID")
|
||||||
|
@ -134,7 +193,9 @@ class DevCog(Extension):
|
||||||
opt_type=OptionType.STRING,
|
opt_type=OptionType.STRING,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
async def _uuid(self, ctx: InteractionContext, version: str, data: str = None) -> None:
|
async def _uuid(
|
||||||
|
self, ctx: InteractionContext, version: str, data: str = None
|
||||||
|
) -> None:
|
||||||
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.", ephemeral=True)
|
await ctx.send(f"UUID{version} requires data.", ephemeral=True)
|
||||||
|
@ -173,7 +234,12 @@ class DevCog(Extension):
|
||||||
sub_cmd_name="uuid2ulid",
|
sub_cmd_name="uuid2ulid",
|
||||||
sub_cmd_description="Convert a UUID to a ULID",
|
sub_cmd_description="Convert a UUID to a ULID",
|
||||||
)
|
)
|
||||||
@slash_option(name="uuid", description="UUID to convert", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
|
name="uuid",
|
||||||
|
description="UUID to convert",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _uuid2ulid(self, ctx: InteractionContext, uuid: str) -> None:
|
async def _uuid2ulid(self, ctx: InteractionContext, uuid: str) -> None:
|
||||||
if UUID_VERIFY.match(uuid):
|
if UUID_VERIFY.match(uuid):
|
||||||
|
@ -186,7 +252,12 @@ class DevCog(Extension):
|
||||||
sub_cmd_name="ulid2uuid",
|
sub_cmd_name="ulid2uuid",
|
||||||
sub_cmd_description="Convert a ULID to a UUID",
|
sub_cmd_description="Convert a ULID to a UUID",
|
||||||
)
|
)
|
||||||
@slash_option(name="ulid", description="ULID to convert", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
|
name="ulid",
|
||||||
|
description="ULID to convert",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _ulid2uuid(self, ctx: InteractionContext, ulid: str) -> None:
|
async def _ulid2uuid(self, ctx: InteractionContext, ulid: str) -> None:
|
||||||
if ULID_VERIFY.match(ulid):
|
if ULID_VERIFY.match(ulid):
|
||||||
|
@ -230,7 +301,9 @@ class DevCog(Extension):
|
||||||
EmbedField(name=mstr, value=f"`{encoded}`", inline=False),
|
EmbedField(name=mstr, value=f"`{encoded}`", inline=False),
|
||||||
]
|
]
|
||||||
embed = build_embed(title="Encoded Data", description="", fields=fields)
|
embed = build_embed(title="Encoded Data", description="", fields=fields)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@dev.subcommand(sub_cmd_name="decode", sub_cmd_description="Decode some data")
|
@dev.subcommand(sub_cmd_name="decode", sub_cmd_description="Decode some data")
|
||||||
|
@ -266,14 +339,18 @@ class DevCog(Extension):
|
||||||
EmbedField(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)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@dev.subcommand(sub_cmd_name="cloc", sub_cmd_description="Get JARVIS lines of code")
|
@dev.subcommand(sub_cmd_name="cloc", sub_cmd_description="Get JARVIS lines of code")
|
||||||
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||||
async def _cloc(self, ctx: InteractionContext) -> None:
|
async def _cloc(self, ctx: InteractionContext) -> None:
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8") # noqa: S603, S607
|
output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode(
|
||||||
|
"UTF-8"
|
||||||
|
) # noqa: S603, S607
|
||||||
console = Console()
|
console = Console()
|
||||||
with console.capture() as capture:
|
with console.capture() as capture:
|
||||||
console.print(output)
|
console.print(output)
|
||||||
|
@ -290,6 +367,12 @@ class DevCog(Extension):
|
||||||
tokei = File(file_bytes, file_name="tokei.png")
|
tokei = File(file_bytes, file_name="tokei.png")
|
||||||
await ctx.send(file=tokei)
|
await ctx.send(file=tokei)
|
||||||
|
|
||||||
|
@_dev_cron.autocomplete("timezone")
|
||||||
|
async def _timezone_autocomplete(self, ctx: AutocompleteContext):
|
||||||
|
results = process.extract(ctx.input_text, pytz.all_timezones_set, limit=5)
|
||||||
|
choices = [{"name": r[0], "value": r[0]} for r in results if r[1] > 80.0]
|
||||||
|
await ctx.send(choices)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: Client) -> None:
|
def setup(bot: Client) -> None:
|
||||||
"""Add DevCog to JARVIS"""
|
"""Add DevCog to JARVIS"""
|
||||||
|
|
|
@ -44,7 +44,9 @@ class PinboardCog(Extension):
|
||||||
|
|
||||||
async def _purge_starboard(self, ctx: InteractionContext, board: Pinboard) -> None:
|
async def _purge_starboard(self, ctx: InteractionContext, board: Pinboard) -> None:
|
||||||
channel = await ctx.guild.fetch_channel(board.channel)
|
channel = await ctx.guild.fetch_channel(board.channel)
|
||||||
async for pin in Pin.find(Pin.pinboard == channel.id, Pin.guild == ctx.guild.id):
|
async for pin in Pin.find(
|
||||||
|
Pin.pinboard == channel.id, Pin.guild == ctx.guild.id
|
||||||
|
):
|
||||||
if message := await channel.fetch_message(pin.message):
|
if message := await channel.fetch_message(pin.message):
|
||||||
try:
|
try:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
|
@ -89,9 +91,13 @@ class PinboardCog(Extension):
|
||||||
await ctx.send("Channel must be a GuildText", ephemeral=True)
|
await ctx.send("Channel must be a GuildText", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
exists = await Pinboard.find_one(Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id)
|
exists = await Pinboard.find_one(
|
||||||
|
Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id
|
||||||
|
)
|
||||||
if exists:
|
if exists:
|
||||||
await ctx.send(f"Pinboard already exists at {channel.mention}.", ephemeral=True)
|
await ctx.send(
|
||||||
|
f"Pinboard already exists at {channel.mention}.", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
count = await Pinboard.find(Pinboard.guild == ctx.guild.id).count()
|
count = await Pinboard.find(Pinboard.guild == ctx.guild.id).count()
|
||||||
|
@ -115,7 +121,9 @@ class PinboardCog(Extension):
|
||||||
)
|
)
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD, Permissions.MANAGE_MESSAGES))
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD, Permissions.MANAGE_MESSAGES))
|
||||||
async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None:
|
async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None:
|
||||||
found = await Pinboard.find_one(Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id)
|
found = await Pinboard.find_one(
|
||||||
|
Pinboard.channel == channel.id, Pinboard.guild == ctx.guild.id
|
||||||
|
)
|
||||||
if found:
|
if found:
|
||||||
await found.delete()
|
await found.delete()
|
||||||
asyncio.create_task(self._purge_starboard(ctx, found))
|
asyncio.create_task(self._purge_starboard(ctx, found))
|
||||||
|
@ -129,6 +137,7 @@ class PinboardCog(Extension):
|
||||||
message: str,
|
message: str,
|
||||||
channel: GuildText = None,
|
channel: GuildText = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
try:
|
||||||
if not channel:
|
if not channel:
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
pinboards = await Pinboard.find(Pinboard.guild == ctx.guild.id).to_list()
|
pinboards = await Pinboard.find(Pinboard.guild == ctx.guild.id).to_list()
|
||||||
|
@ -150,12 +159,17 @@ class PinboardCog(Extension):
|
||||||
channel_list = []
|
channel_list = []
|
||||||
to_delete: list[Pinboard] = []
|
to_delete: list[Pinboard] = []
|
||||||
|
|
||||||
|
channel_to_pinboard = {}
|
||||||
|
|
||||||
for pinboard in pinboards:
|
for pinboard in pinboards:
|
||||||
c = await ctx.guild.fetch_channel(pinboard.channel)
|
c = await ctx.guild.fetch_channel(pinboard.channel)
|
||||||
if c and isinstance(c, GuildText):
|
if c and isinstance(c, GuildText):
|
||||||
channel_list.append(c)
|
channel_list.append(c)
|
||||||
|
channel_to_pinboard[c.id] = pinboard
|
||||||
else:
|
else:
|
||||||
self.logger.warning(f"Pinboard {pinboard.channel} no longer valid in {ctx.guild.name}")
|
self.logger.warning(
|
||||||
|
f"Pinboard {pinboard.channel} no longer valid in {ctx.guild.name}"
|
||||||
|
)
|
||||||
to_delete.append(pinboard)
|
to_delete.append(pinboard)
|
||||||
|
|
||||||
for pinboard in to_delete:
|
for pinboard in to_delete:
|
||||||
|
@ -167,12 +181,17 @@ class PinboardCog(Extension):
|
||||||
select_channels = []
|
select_channels = []
|
||||||
for idx, x in enumerate(channel_list):
|
for idx, x in enumerate(channel_list):
|
||||||
if x:
|
if x:
|
||||||
select_channels.append(StringSelectOption(label=x.name, value=str(idx)))
|
select_channels.append(
|
||||||
|
StringSelectOption(label=x.name, value=str(idx))
|
||||||
|
)
|
||||||
|
|
||||||
select_channels = [StringSelectOption(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list)]
|
select_channels = [
|
||||||
|
StringSelectOption(label=x.name, value=str(idx))
|
||||||
|
for idx, x in enumerate(channel_list)
|
||||||
|
]
|
||||||
|
|
||||||
select = StringSelectMenu(
|
select = StringSelectMenu(
|
||||||
options=select_channels,
|
*select_channels,
|
||||||
min_values=1,
|
min_values=1,
|
||||||
max_values=1,
|
max_values=1,
|
||||||
)
|
)
|
||||||
|
@ -184,16 +203,16 @@ class PinboardCog(Extension):
|
||||||
com_ctx = await self.bot.wait_for_component(
|
com_ctx = await self.bot.wait_for_component(
|
||||||
messages=msg,
|
messages=msg,
|
||||||
components=components,
|
components=components,
|
||||||
check=lambda x: ctx.author.id == x.context.author.id,
|
check=lambda x: ctx.author.id == x.ctx.author.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
pinboard = channel_list[int(com_ctx.context.values[0])]
|
pinboard = channel_list[int(com_ctx.ctx.values[0])]
|
||||||
|
|
||||||
exists = await Pin.find_one(
|
exists = await Pin.find_one(
|
||||||
Pin.message == message.id,
|
Pin.message == int(message.id),
|
||||||
Pin.channel == channel.id,
|
Pin.channel == int(channel.id),
|
||||||
Pin.guild == ctx.guild.id,
|
Pin.guild == int(ctx.guild.id),
|
||||||
Pin.pinboard == pinboard.id,
|
Pin.pinboard == int(pinboard.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
if exists:
|
if exists:
|
||||||
|
@ -203,7 +222,9 @@ class PinboardCog(Extension):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
count = await Pin.find(Pin.guild == ctx.guild.id, Pin.pinboard == pinboard.id).count()
|
count = await Pin.find(
|
||||||
|
Pin.guild == ctx.guild.id, Pin.pinboard == pinboard.id
|
||||||
|
).count()
|
||||||
content = message.content
|
content = message.content
|
||||||
|
|
||||||
attachments = message.attachments
|
attachments = message.attachments
|
||||||
|
@ -231,26 +252,32 @@ class PinboardCog(Extension):
|
||||||
embed.set_footer(text=ctx.guild.name + " | " + channel.name)
|
embed.set_footer(text=ctx.guild.name + " | " + channel.name)
|
||||||
if image_url:
|
if image_url:
|
||||||
embed.set_image(url=image_url)
|
embed.set_image(url=image_url)
|
||||||
star_components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
star_components = Button(
|
||||||
|
style=ButtonStyle.DANGER,
|
||||||
|
emoji="🗑️",
|
||||||
|
custom_id=f"delete|{ctx.author.id}",
|
||||||
|
)
|
||||||
pin = await pinboard.send(embeds=embed, components=star_components)
|
pin = await pinboard.send(embeds=embed, components=star_components)
|
||||||
|
|
||||||
await Pin(
|
await Pin(
|
||||||
index=count,
|
index=count,
|
||||||
message=message.id,
|
message=int(message.id),
|
||||||
channel=channel.id,
|
channel=int(channel.id),
|
||||||
guild=ctx.guild.id,
|
guild=int(ctx.guild.id),
|
||||||
pinboard=pinboard.id,
|
pinboard=channel_to_pinboard[pinboard.id],
|
||||||
admin=ctx.author.id,
|
admin=int(ctx.author.id),
|
||||||
pin=pin.id,
|
pin=int(pin.id),
|
||||||
active=True,
|
active=True,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
components[0].components[0].disabled = True
|
components[0].components[0].disabled = True
|
||||||
|
|
||||||
await com_ctx.context.edit_origin(
|
await com_ctx.ctx.edit_origin(
|
||||||
content=f"Message saved to Pinboard.\nSee it in {pinboard.mention}",
|
content=f"Message saved to Pinboard.\nSee it in {pinboard.mention}",
|
||||||
components=components,
|
components=components,
|
||||||
)
|
)
|
||||||
|
except:
|
||||||
|
self.bot.logger.error("E", exc_info=True)
|
||||||
|
|
||||||
@context_menu(name="Pin Message", context_type=CommandType.MESSAGE)
|
@context_menu(name="Pin Message", context_type=CommandType.MESSAGE)
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD, Permissions.MANAGE_MESSAGES))
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD, Permissions.MANAGE_MESSAGES))
|
||||||
|
|
|
@ -64,13 +64,17 @@ class RolegiverCog(Extension):
|
||||||
rolegiver.roles = roles
|
rolegiver.roles = roles
|
||||||
await rolegiver.save()
|
await rolegiver.save()
|
||||||
|
|
||||||
rolegiver = SlashCommand(name="rolegiver", description="Allow users to choose their own roles")
|
rolegiver = SlashCommand(
|
||||||
|
name="rolegiver", description="Allow users to choose their own roles"
|
||||||
|
)
|
||||||
|
|
||||||
@rolegiver.subcommand(
|
@rolegiver.subcommand(
|
||||||
sub_cmd_name="add",
|
sub_cmd_name="add",
|
||||||
sub_cmd_description="Add a role to rolegiver",
|
sub_cmd_description="Add a role to rolegiver",
|
||||||
)
|
)
|
||||||
@slash_option(name="role", description="Role to add", opt_type=OptionType.ROLE, required=True)
|
@slash_option(
|
||||||
|
name="role", description="Role to add", opt_type=OptionType.ROLE, required=True
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None:
|
async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None:
|
||||||
if role.id == ctx.guild.id:
|
if role.id == ctx.guild.id:
|
||||||
|
@ -122,15 +126,19 @@ class RolegiverCog(Extension):
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
|
|
||||||
embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
if ctx.guild.id not in self.cache:
|
if ctx.guild.id not in self.cache:
|
||||||
self.cache[ctx.guild.id] = {}
|
self.cache[ctx.guild.id] = {}
|
||||||
self.cache[ctx.guild.id][role.name] = role.id
|
self.cache[ctx.guild.id][role.name] = role.id
|
||||||
|
|
||||||
@rolegiver.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver")
|
@rolegiver.subcommand(
|
||||||
|
sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver"
|
||||||
|
)
|
||||||
@slash_option(
|
@slash_option(
|
||||||
name="role",
|
name="role",
|
||||||
description="Name of role to add",
|
description="Name of role to add",
|
||||||
|
@ -149,7 +157,9 @@ class RolegiverCog(Extension):
|
||||||
if cache:
|
if cache:
|
||||||
role_id = cache.get(role)
|
role_id = cache.get(role)
|
||||||
else:
|
else:
|
||||||
await ctx.send("Something went wrong, please try a different role", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Something went wrong, please try a different role", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
setting.value.remove(role_id)
|
setting.value.remove(role_id)
|
||||||
|
@ -171,14 +181,21 @@ class RolegiverCog(Extension):
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
EmbedField(name="Removed Role", value=role.mention),
|
EmbedField(name="Removed Role", value=role.mention),
|
||||||
EmbedField(name="Remaining Role(s)", value="\n".join([x.mention for x in remaining])),
|
EmbedField(
|
||||||
|
name="Remaining Role(s)",
|
||||||
|
value="\n".join([x.mention for x in remaining]),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(title="Rolegiver Updated", description="Role removed from rolegiver", fields=fields)
|
embed = build_embed(
|
||||||
|
title="Rolegiver Updated",
|
||||||
|
description="Role removed from rolegiver",
|
||||||
|
fields=fields,
|
||||||
|
)
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
|
|
||||||
embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||||
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embeds=embed,
|
embeds=embed,
|
||||||
|
@ -187,7 +204,9 @@ class RolegiverCog(Extension):
|
||||||
if ctx.guild.id in self.cache:
|
if ctx.guild.id in self.cache:
|
||||||
self.cache[ctx.guild.id].pop(role.name)
|
self.cache[ctx.guild.id].pop(role.name)
|
||||||
|
|
||||||
@rolegiver.subcommand(sub_cmd_name="list", sub_cmd_description="List rolegiver roles")
|
@rolegiver.subcommand(
|
||||||
|
sub_cmd_name="list", sub_cmd_description="List rolegiver roles"
|
||||||
|
)
|
||||||
async def _rolegiver_list(self, ctx: InteractionContext) -> None:
|
async def _rolegiver_list(self, ctx: InteractionContext) -> None:
|
||||||
setting = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id)
|
setting = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id)
|
||||||
if not setting or (setting and not setting.roles):
|
if not setting or (setting and not setting.roles):
|
||||||
|
@ -214,14 +233,17 @@ class RolegiverCog(Extension):
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
|
|
||||||
embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@rolegiver.subcommand(sub_cmd_name="get", sub_cmd_description="Get a role")
|
@rolegiver.subcommand(sub_cmd_name="get", sub_cmd_description="Get a role")
|
||||||
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=10)
|
||||||
async def _role_get(self, ctx: InteractionContext) -> None:
|
async def _role_get(self, ctx: InteractionContext) -> None:
|
||||||
setting = await Rolegiver.find_one(Rolegiver.quild == ctx.guild.id)
|
try:
|
||||||
|
setting = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id)
|
||||||
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", ephemeral=True)
|
await ctx.send("Rolegiver has no roles", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
@ -233,7 +255,7 @@ class RolegiverCog(Extension):
|
||||||
options.append(option)
|
options.append(option)
|
||||||
|
|
||||||
select = StringSelectMenu(
|
select = StringSelectMenu(
|
||||||
options=options,
|
*options,
|
||||||
placeholder="Select roles to add",
|
placeholder="Select roles to add",
|
||||||
min_values=1,
|
min_values=1,
|
||||||
max_values=len(options),
|
max_values=len(options),
|
||||||
|
@ -241,6 +263,9 @@ class RolegiverCog(Extension):
|
||||||
components = [ActionRow(select)]
|
components = [ActionRow(select)]
|
||||||
|
|
||||||
message = await ctx.send(content="\u200b", components=components)
|
message = await ctx.send(content="\u200b", components=components)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("Encountered error", exc_info=True)
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
context = await self.bot.wait_for_component(
|
context = await self.bot.wait_for_component(
|
||||||
|
@ -255,16 +280,11 @@ class RolegiverCog(Extension):
|
||||||
added_roles.append(role)
|
added_roles.append(role)
|
||||||
await ctx.author.add_role(role, reason="Rolegiver")
|
await ctx.author.add_role(role, reason="Rolegiver")
|
||||||
|
|
||||||
roles = ctx.author.roles
|
avalue = (
|
||||||
if roles:
|
"\n".join([r.mention for r in added_roles]) if added_roles else "None"
|
||||||
roles.sort(key=lambda x: -x.position)
|
)
|
||||||
_ = roles.pop(-1)
|
|
||||||
|
|
||||||
avalue = "\n".join([r.mention for r in added_roles]) if added_roles else "None"
|
|
||||||
value = "\n".join([r.mention for r in roles]) if roles else "None"
|
|
||||||
fields = [
|
fields = [
|
||||||
EmbedField(name="Added Role(s)", value=avalue),
|
EmbedField(name="Added Role(s)", value=avalue),
|
||||||
EmbedField(name="Prior Role(s)", value=value),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -279,13 +299,15 @@ class RolegiverCog(Extension):
|
||||||
icon_url=ctx.author.display_avatar.url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||||
|
|
||||||
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 context.ctx.edit_origin(embeds=embed, content="\u200b", components=components)
|
await context.ctx.edit_origin(
|
||||||
|
embeds=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:
|
||||||
|
@ -312,7 +334,7 @@ class RolegiverCog(Extension):
|
||||||
options.append(option)
|
options.append(option)
|
||||||
|
|
||||||
select = StringSelectMenu(
|
select = StringSelectMenu(
|
||||||
options=options,
|
*options,
|
||||||
custom_id="to_remove",
|
custom_id="to_remove",
|
||||||
placeholder="Select roles to remove",
|
placeholder="Select roles to remove",
|
||||||
min_values=1,
|
min_values=1,
|
||||||
|
@ -336,14 +358,13 @@ class RolegiverCog(Extension):
|
||||||
user_roles.remove(role)
|
user_roles.remove(role)
|
||||||
removed_roles.append(role)
|
removed_roles.append(role)
|
||||||
|
|
||||||
user_roles.sort(key=lambda x: -x.position)
|
rvalue = (
|
||||||
_ = user_roles.pop(-1)
|
"\n".join([r.mention for r in removed_roles])
|
||||||
|
if removed_roles
|
||||||
value = "\n".join([r.mention for r in user_roles]) if user_roles else "None"
|
else "None"
|
||||||
rvalue = "\n".join([r.mention for r in removed_roles]) if removed_roles else "None"
|
)
|
||||||
fields = [
|
fields = [
|
||||||
EmbedField(name="Removed Role(s)", value=rvalue),
|
EmbedField(name="Removed Role(s)", value=rvalue),
|
||||||
EmbedField(name="Remaining Role(s)", value=value),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -353,15 +374,19 @@ class RolegiverCog(Extension):
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
|
embed.set_author(
|
||||||
|
name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url
|
||||||
|
)
|
||||||
|
|
||||||
embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}")
|
embed.set_footer(text=f"{ctx.author.username} | {ctx.author.id}")
|
||||||
|
|
||||||
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 context.ctx.edit_origin(embeds=embed, components=components, content="\u200b")
|
await context.ctx.edit_origin(
|
||||||
|
embeds=embed, components=components, content="\u200b"
|
||||||
|
)
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
for row in components:
|
for row in components:
|
||||||
|
@ -369,7 +394,10 @@ class RolegiverCog(Extension):
|
||||||
component.disabled = True
|
component.disabled = True
|
||||||
await message.edit(components=components)
|
await message.edit(components=components)
|
||||||
|
|
||||||
@rolegiver.subcommand(sub_cmd_name="cleanup", sub_cmd_description="Removed deleted roles from rolegiver")
|
@rolegiver.subcommand(
|
||||||
|
sub_cmd_name="cleanup",
|
||||||
|
sub_cmd_description="Removed deleted roles from rolegiver",
|
||||||
|
)
|
||||||
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
|
||||||
async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None:
|
async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None:
|
||||||
setting = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id)
|
setting = await Rolegiver.find_one(Rolegiver.guild == ctx.guild.id)
|
||||||
|
|
|
@ -50,7 +50,10 @@ class TagCog(Extension):
|
||||||
async def _get(self, ctx: InteractionContext, name: str) -> None:
|
async def _get(self, ctx: InteractionContext, name: str) -> None:
|
||||||
tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
|
tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
|
||||||
if not tag:
|
if not tag:
|
||||||
await ctx.send("Well this is awkward, looks like the tag was deleted just now", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Well this is awkward, looks like the tag was deleted just now",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await ctx.send(tag.content)
|
await ctx.send(tag.content)
|
||||||
|
@ -58,8 +61,7 @@ class TagCog(Extension):
|
||||||
@tag.subcommand(sub_cmd_name="create", sub_cmd_description="Create a tag")
|
@tag.subcommand(sub_cmd_name="create", sub_cmd_description="Create a tag")
|
||||||
async def _create(self, ctx: SlashContext) -> None:
|
async def _create(self, ctx: SlashContext) -> None:
|
||||||
modal = Modal(
|
modal = Modal(
|
||||||
title="Create a new tag!",
|
*[
|
||||||
components=[
|
|
||||||
InputText(
|
InputText(
|
||||||
label="Tag name",
|
label="Tag name",
|
||||||
placeholder="name",
|
placeholder="name",
|
||||||
|
@ -75,17 +77,22 @@ class TagCog(Extension):
|
||||||
max_length=512,
|
max_length=512,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
title="Create a new tag!",
|
||||||
)
|
)
|
||||||
|
|
||||||
await ctx.send_modal(modal)
|
await ctx.send_modal(modal)
|
||||||
try:
|
try:
|
||||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
response = await self.bot.wait_for_modal(
|
||||||
|
modal, author=ctx.author.id, timeout=60 * 5
|
||||||
|
)
|
||||||
name = response.responses.get("name").replace("`", "")
|
name = response.responses.get("name").replace("`", "")
|
||||||
content = response.responses.get("content")
|
content = response.responses.get("content")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return
|
return
|
||||||
|
|
||||||
noinvite = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "noinvite")
|
noinvite = await Setting.find_one(
|
||||||
|
Setting.guild == ctx.guild.id, Setting.setting == "noinvite"
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(invites.search(content) or invites.search(name))
|
(invites.search(content) or invites.search(name))
|
||||||
|
@ -95,13 +102,17 @@ class TagCog(Extension):
|
||||||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
await response.send("Listen, don't use this to try and bypass the rules", ephemeral=True)
|
await response.send(
|
||||||
|
"Listen, don't use this to try and bypass the rules", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
elif not content.strip() or not name.strip():
|
elif not content.strip() or not name.strip():
|
||||||
await response.send("Content and name required", ephemeral=True)
|
await response.send("Content and name required", ephemeral=True)
|
||||||
return
|
return
|
||||||
elif not tag_name.match(name):
|
elif not tag_name.match(name):
|
||||||
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
|
await response.send(
|
||||||
|
"Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
|
tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
|
||||||
|
@ -122,7 +133,10 @@ class TagCog(Extension):
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Tag Created",
|
title="Tag Created",
|
||||||
description=f"{ctx.author.mention} created a new tag",
|
description=f"{ctx.author.mention} created a new tag",
|
||||||
fields=[EmbedField(name="Name", value=name), EmbedField(name="Content", value=content)],
|
fields=[
|
||||||
|
EmbedField(name="Name", value=name),
|
||||||
|
EmbedField(name="Content", value=content),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
|
@ -130,7 +144,9 @@ class TagCog(Extension):
|
||||||
icon_url=ctx.author.display_avatar.url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
|
|
||||||
await response.send(embeds=embed, components=components)
|
await response.send(embeds=embed, components=components)
|
||||||
if ctx.guild.id not in self.cache:
|
if ctx.guild.id not in self.cache:
|
||||||
|
@ -155,12 +171,13 @@ class TagCog(Extension):
|
||||||
ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
||||||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||||
):
|
):
|
||||||
await ctx.send("You didn't create this tag, ask the creator to edit it", ephemeral=True)
|
await ctx.send(
|
||||||
|
"You didn't create this tag, ask the creator to edit it", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
modal = Modal(
|
modal = Modal(
|
||||||
title="Edit a tag!",
|
*[
|
||||||
components=[
|
|
||||||
InputText(
|
InputText(
|
||||||
label="Tag name",
|
label="Tag name",
|
||||||
value=tag.name,
|
value=tag.name,
|
||||||
|
@ -176,11 +193,14 @@ class TagCog(Extension):
|
||||||
max_length=512,
|
max_length=512,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
title="Edit a tag!",
|
||||||
)
|
)
|
||||||
|
|
||||||
await ctx.send_modal(modal)
|
await ctx.send_modal(modal)
|
||||||
try:
|
try:
|
||||||
response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5)
|
response = await self.bot.wait_for_modal(
|
||||||
|
modal, author=ctx.author.id, timeout=60 * 5
|
||||||
|
)
|
||||||
name = response.responses.get("name").replace("`", "")
|
name = response.responses.get("name").replace("`", "")
|
||||||
content = response.responses.get("content")
|
content = response.responses.get("content")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
@ -188,10 +208,15 @@ class TagCog(Extension):
|
||||||
|
|
||||||
new_tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
|
new_tag = await Tag.find_one(Tag.guild == ctx.guild.id, Tag.name == name)
|
||||||
if new_tag and new_tag.id != tag.id:
|
if new_tag and new_tag.id != tag.id:
|
||||||
await ctx.send("That tag name is used by another tag, choose another name", ephemeral=True)
|
await ctx.send(
|
||||||
|
"That tag name is used by another tag, choose another name",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
noinvite = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "noinvite")
|
noinvite = await Setting.find_one(
|
||||||
|
Setting.guild == ctx.guild.id, Setting.setting == "noinvite"
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(invites.search(content) or invites.search(name))
|
(invites.search(content) or invites.search(name))
|
||||||
|
@ -201,13 +226,17 @@ class TagCog(Extension):
|
||||||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
await response.send("Listen, don't use this to try and bypass the rules", ephemeral=True)
|
await response.send(
|
||||||
|
"Listen, don't use this to try and bypass the rules", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
elif not content.strip() or not name.strip():
|
elif not content.strip() or not name.strip():
|
||||||
await response.send("Content and name required", ephemeral=True)
|
await response.send("Content and name required", ephemeral=True)
|
||||||
return
|
return
|
||||||
elif not tag_name.match(name):
|
elif not tag_name.match(name):
|
||||||
await response.send("Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True)
|
await response.send(
|
||||||
|
"Tag name must only contain: [A-Za-z0-9_- ]", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
tag.content = re.sub(r"\\?([@<])", r"\\\g<1>", content)
|
tag.content = re.sub(r"\\?([@<])", r"\\\g<1>", content)
|
||||||
|
@ -230,7 +259,9 @@ class TagCog(Extension):
|
||||||
name=ctx.author.username + "#" + ctx.author.discriminator,
|
name=ctx.author.username + "#" + ctx.author.discriminator,
|
||||||
icon_url=ctx.author.display_avatar.url,
|
icon_url=ctx.author.display_avatar.url,
|
||||||
)
|
)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await response.send(embeds=embed, components=components)
|
await response.send(embeds=embed, components=components)
|
||||||
if tag.name not in self.cache[ctx.guild.id]:
|
if tag.name not in self.cache[ctx.guild.id]:
|
||||||
self.cache[ctx.guild.id].remove(old_name)
|
self.cache[ctx.guild.id].remove(old_name)
|
||||||
|
@ -253,7 +284,10 @@ class TagCog(Extension):
|
||||||
ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
ctx.author.has_permission(Permissions.ADMINISTRATOR)
|
||||||
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
or ctx.author.has_permission(Permissions.MANAGE_MESSAGES)
|
||||||
):
|
):
|
||||||
await ctx.send("You didn't create this tag, ask the creator to delete it", ephemeral=True)
|
await ctx.send(
|
||||||
|
"You didn't create this tag, ask the creator to delete it",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await tag.delete()
|
await tag.delete()
|
||||||
|
@ -307,7 +341,9 @@ class TagCog(Extension):
|
||||||
name=f"{username}#{discrim}" if username else "Unknown User",
|
name=f"{username}#{discrim}" if username else "Unknown User",
|
||||||
icon_url=url,
|
icon_url=url,
|
||||||
)
|
)
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@tag.subcommand(sub_cmd_name="list", sub_cmd_description="List tag names")
|
@tag.subcommand(sub_cmd_name="list", sub_cmd_description="List tag names")
|
||||||
|
@ -315,7 +351,9 @@ class TagCog(Extension):
|
||||||
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
|
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
|
||||||
names = "\n".join(f"`{t.name}`" for t in tags)
|
names = "\n".join(f"`{t.name}`" for t in tags)
|
||||||
embed = build_embed(title="All Tags", description=names, fields=[])
|
embed = build_embed(title="All Tags", description=names, fields=[])
|
||||||
components = Button(style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}")
|
components = Button(
|
||||||
|
style=ButtonStyle.DANGER, emoji="🗑️", custom_id=f"delete|{ctx.author.id}"
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed, components=components)
|
await ctx.send(embeds=embed, components=components)
|
||||||
|
|
||||||
@_get.autocomplete("name")
|
@_get.autocomplete("name")
|
||||||
|
@ -326,7 +364,9 @@ class TagCog(Extension):
|
||||||
if not self.cache.get(ctx.guild.id):
|
if not self.cache.get(ctx.guild.id):
|
||||||
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
|
tags = await Tag.find(Tag.guild == ctx.guild.id).to_list()
|
||||||
self.cache[ctx.guild.id] = [tag.name for tag in tags]
|
self.cache[ctx.guild.id] = [tag.name for tag in tags]
|
||||||
results = process.extract(ctx.input_text, self.cache.get(ctx.guild.id), limit=25)
|
results = process.extract(
|
||||||
|
ctx.input_text, self.cache.get(ctx.guild.id), limit=25
|
||||||
|
)
|
||||||
choices = [{"name": r[0], "value": r[0]} for r in results]
|
choices = [{"name": r[0], "value": r[0]} for r in results]
|
||||||
await ctx.send(choices=choices)
|
await ctx.send(choices=choices)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
"""JARVIS Complete the Code 2 Cog."""
|
"""
|
||||||
|
JARVIS Complete the Code 2 Cog.
|
||||||
|
|
||||||
|
This cog is now maintenance-only due to conflict with the dbrand moderators.
|
||||||
|
|
||||||
|
Please do not file feature requests related to this cog; they will be closed.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -19,7 +25,7 @@ from jarvis_core.db.models import Guess
|
||||||
|
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
|
|
||||||
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(
|
||||||
|
@ -40,30 +46,54 @@ class CTCCog(Extension):
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self._session.close()
|
self._session.close()
|
||||||
|
|
||||||
ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopes=guild_ids)
|
ctc2 = SlashCommand(
|
||||||
|
name="ctc2", description="CTC2 related commands", scopes=guild_ids
|
||||||
|
)
|
||||||
|
|
||||||
@ctc2.subcommand(sub_cmd_name="about")
|
@ctc2.subcommand(sub_cmd_name="about")
|
||||||
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=30)
|
||||||
async def _about(self, ctx: InteractionContext) -> None:
|
async def _about(self, ctx: InteractionContext) -> None:
|
||||||
components = [ActionRow(Button(style=ButtonStyle.URL, url="https://completethecode.com", label="More Info"))]
|
components = [
|
||||||
await ctx.send("See https://completethecode.com for more information", components=components)
|
ActionRow(
|
||||||
|
Button(
|
||||||
|
style=ButtonStyle.URL,
|
||||||
|
url="https://completethecode.com",
|
||||||
|
label="More Info",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
await ctx.send(
|
||||||
|
"See https://completethecode.com for more information",
|
||||||
|
components=components,
|
||||||
|
)
|
||||||
|
|
||||||
@ctc2.subcommand(
|
@ctc2.subcommand(
|
||||||
sub_cmd_name="pw",
|
sub_cmd_name="pw",
|
||||||
sub_cmd_description="Guess a password for https://completethecodetwo.cards",
|
sub_cmd_description="Guess a password for https://completethecodetwo.cards",
|
||||||
)
|
)
|
||||||
@slash_option(name="guess", description="Guess a password", opt_type=OptionType.STRING, required=True)
|
@slash_option(
|
||||||
|
name="guess",
|
||||||
|
description="Guess a password",
|
||||||
|
opt_type=OptionType.STRING,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
@cooldown(bucket=Buckets.USER, rate=1, interval=2)
|
||||||
async def _pw(self, ctx: InteractionContext, 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."),
|
(
|
||||||
|
"Listen here, dipshit. Don't be like <@256110768724901889>. "
|
||||||
|
"Make your guesses < 800 characters."
|
||||||
|
),
|
||||||
ephemeral=True,
|
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*."),
|
(
|
||||||
|
"Listen here, dipshit. Don't be like <@256110768724901889>. "
|
||||||
|
"Make your guesses *readable*."
|
||||||
|
),
|
||||||
ephemeral=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
@ -102,7 +132,7 @@ class CTCCog(Extension):
|
||||||
if not user:
|
if not user:
|
||||||
user = "[redacted]"
|
user = "[redacted]"
|
||||||
if isinstance(user, (Member, User)):
|
if isinstance(user, (Member, User)):
|
||||||
user = user.username + "#" + user.discriminator
|
user = user.username
|
||||||
cache[guess.user] = user
|
cache[guess.user] = user
|
||||||
name = "Correctly" if guess["correct"] else "Incorrectly"
|
name = "Correctly" if guess["correct"] else "Incorrectly"
|
||||||
name += " guessed by: " + user
|
name += " guessed by: " + user
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
"""JARVIS dbrand cog."""
|
"""
|
||||||
|
JARVIS dbrand cog.
|
||||||
|
|
||||||
|
This cog is now maintenance-only due to conflict with the dbrand moderators.
|
||||||
|
|
||||||
|
Please do not file feature requests related to this cog; they will be closed.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
@ -63,7 +69,9 @@ async def parse_db_status() -> dict:
|
||||||
else:
|
else:
|
||||||
cell = cell.get_text().strip()
|
cell = cell.get_text().strip()
|
||||||
row_data.append(cell)
|
row_data.append(cell)
|
||||||
data[data_key].append({headers[idx]: value for idx, value in enumerate(row_data)})
|
data[data_key].append(
|
||||||
|
{headers[idx]: value for idx, value in enumerate(row_data)}
|
||||||
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,7 +96,9 @@ class DbrandCog(Extension):
|
||||||
|
|
||||||
db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids)
|
db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids)
|
||||||
|
|
||||||
@db.subcommand(sub_cmd_name="status", sub_cmd_description="Get dbrand operational status")
|
@db.subcommand(
|
||||||
|
sub_cmd_name="status", sub_cmd_description="Get dbrand operational status"
|
||||||
|
)
|
||||||
async def _status(self, ctx: InteractionContext) -> None:
|
async def _status(self, ctx: InteractionContext) -> None:
|
||||||
status = self.cache.get("status")
|
status = self.cache.get("status")
|
||||||
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
|
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
|
||||||
|
@ -97,7 +107,10 @@ class DbrandCog(Extension):
|
||||||
self.cache["status"] = status
|
self.cache["status"] = status
|
||||||
status = status.get("operations")
|
status = status.get("operations")
|
||||||
emojies = [x["Status"] for x in status]
|
emojies = [x["Status"] for x in status]
|
||||||
fields = [EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"]) for x in status]
|
fields = [
|
||||||
|
EmbedField(name=f'{x["Status"]} {x["Service"]}', value=x["Detail"])
|
||||||
|
for x in status
|
||||||
|
]
|
||||||
color = "#FBBD1E"
|
color = "#FBBD1E"
|
||||||
if all("green" in x for x in emojies):
|
if all("green" in x for x in emojies):
|
||||||
color = "#38F657"
|
color = "#38F657"
|
||||||
|
@ -169,7 +182,9 @@ class DbrandCog(Extension):
|
||||||
async def _support(self, ctx: InteractionContext) -> None:
|
async def _support(self, ctx: InteractionContext) -> None:
|
||||||
return await self._db_support_cmd(ctx)
|
return await self._db_support_cmd(ctx)
|
||||||
|
|
||||||
@db.subcommand(sub_cmd_name="gripcheck", sub_cmd_description="Watch a dbrand grip get thrown")
|
@db.subcommand(
|
||||||
|
sub_cmd_name="gripcheck", sub_cmd_description="Watch a dbrand grip get thrown"
|
||||||
|
)
|
||||||
async def _gripcheck(self, ctx: InteractionContext) -> None:
|
async def _gripcheck(self, ctx: InteractionContext) -> None:
|
||||||
video_url = "https://cdn.discordapp.com/attachments/599068193339736096/890679742263623751/video0.mov"
|
video_url = "https://cdn.discordapp.com/attachments/599068193339736096/890679742263623751/video0.mov"
|
||||||
image_url = "https://cdn.discordapp.com/attachments/599068193339736096/890680198306095104/image0.jpg"
|
image_url = "https://cdn.discordapp.com/attachments/599068193339736096/890680198306095104/image0.jpg"
|
||||||
|
@ -188,13 +203,22 @@ class DbrandCog(Extension):
|
||||||
f"[Be (not) extorted]({self.base_url + 'not-extortion'})",
|
f"[Be (not) extorted]({self.base_url + 'not-extortion'})",
|
||||||
"[Robot Camo Wallpapers](https://db.io/wallpapers)",
|
"[Robot Camo Wallpapers](https://db.io/wallpapers)",
|
||||||
]
|
]
|
||||||
embed = build_embed(title="Useful Links", description="\n\n".join(urls), fields=[], color="#FFBB00")
|
embed = build_embed(
|
||||||
|
title="Useful Links",
|
||||||
|
description="\n\n".join(urls),
|
||||||
|
fields=[],
|
||||||
|
color="#FFBB00",
|
||||||
|
)
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text="dbrand.com",
|
text="dbrand.com",
|
||||||
icon_url="https://dev.zevaryx.com/db_logo.png",
|
icon_url="https://dev.zevaryx.com/db_logo.png",
|
||||||
)
|
)
|
||||||
embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png")
|
embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png")
|
||||||
embed.set_author(name="dbrand", url=self.base_url, icon_url="https://dev.zevaryx.com/db_logo.png")
|
embed.set_author(
|
||||||
|
name="dbrand",
|
||||||
|
url=self.base_url,
|
||||||
|
icon_url="https://dev.zevaryx.com/db_logo.png",
|
||||||
|
)
|
||||||
await ctx.send(embeds=embed)
|
await ctx.send(embeds=embed)
|
||||||
|
|
||||||
@db.subcommand(
|
@db.subcommand(
|
||||||
|
@ -217,11 +241,15 @@ class DbrandCog(Extension):
|
||||||
):
|
):
|
||||||
# Magic number, subtract from flag char to get ascii char
|
# Magic number, subtract from flag char to get ascii char
|
||||||
uni2ascii = 127365
|
uni2ascii = 127365
|
||||||
search = chr(ord(search[0]) - uni2ascii) + chr(ord(search[1]) - uni2ascii)
|
search = chr(ord(search[0]) - uni2ascii) + chr(
|
||||||
|
ord(search[1]) - uni2ascii
|
||||||
|
)
|
||||||
elif search == "🏳️":
|
elif search == "🏳️":
|
||||||
search = "fr"
|
search = "fr"
|
||||||
else:
|
else:
|
||||||
await ctx.send("Please use text to search for shipping.", ephemeral=True)
|
await ctx.send(
|
||||||
|
"Please use text to search for shipping.", ephemeral=True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if len(search) > 3:
|
if len(search) > 3:
|
||||||
countries = {x["country"]: x["alpha-2"] for x in shipping_lookup}
|
countries = {x["country"]: x["alpha-2"] for x in shipping_lookup}
|
||||||
|
@ -246,7 +274,9 @@ class DbrandCog(Extension):
|
||||||
data = await self._session.get(api_link)
|
data = await self._session.get(api_link)
|
||||||
if 200 <= data.status < 400:
|
if 200 <= data.status < 400:
|
||||||
data = await data.json()
|
data = await data.json()
|
||||||
data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
|
||||||
|
hours=24
|
||||||
|
)
|
||||||
self.cache[dest] = data
|
self.cache[dest] = data
|
||||||
else:
|
else:
|
||||||
data = None
|
data = None
|
||||||
|
@ -255,12 +285,18 @@ class DbrandCog(Extension):
|
||||||
fields = []
|
fields = []
|
||||||
for service in data["shipping_services_available"]:
|
for service in data["shipping_services_available"]:
|
||||||
service_data = self.cache.get(f"{dest}-{service}")
|
service_data = self.cache.get(f"{dest}-{service}")
|
||||||
if not service_data or service_data["cache_expiry"] < datetime.now(tz=timezone.utc):
|
if not service_data or service_data["cache_expiry"] < datetime.now(
|
||||||
service_data = await self._session.get(self.api_url + dest + "/" + service["url"])
|
tz=timezone.utc
|
||||||
|
):
|
||||||
|
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()
|
||||||
service_data["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
service_data["cache_expiry"] = datetime.now(
|
||||||
|
tz=timezone.utc
|
||||||
|
) + timedelta(hours=24)
|
||||||
self.cache[f"{dest}-{service}"] = service_data
|
self.cache[f"{dest}-{service}"] = service_data
|
||||||
title = f'{service_data["carrier"]} {service_data["tier-title"]} | {service_data["costs-min"]}'
|
title = f'{service_data["carrier"]} {service_data["tier-title"]} | {service_data["costs-min"]}'
|
||||||
message = service_data["time-title"]
|
message = service_data["time-title"]
|
||||||
|
@ -271,7 +307,9 @@ class DbrandCog(Extension):
|
||||||
status = self.cache.get("status")
|
status = self.cache.get("status")
|
||||||
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
|
if not status or status["cache_expiry"] <= datetime.now(tz=timezone.utc):
|
||||||
status = await parse_db_status()
|
status = await parse_db_status()
|
||||||
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(hours=2)
|
status["cache_expiry"] = datetime.now(tz=timezone.utc) + timedelta(
|
||||||
|
hours=2
|
||||||
|
)
|
||||||
self.cache["status"] = status
|
self.cache["status"] = status
|
||||||
status = status["countries"]
|
status = status["countries"]
|
||||||
|
|
||||||
|
@ -284,10 +322,10 @@ class DbrandCog(Extension):
|
||||||
description = ""
|
description = ""
|
||||||
color = "#FFBB00"
|
color = "#FFBB00"
|
||||||
if shipping_info:
|
if shipping_info:
|
||||||
description = (
|
description = f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}'
|
||||||
f'{shipping_info["Status"]}\u200b \u200b {shipping_info["Est. Delivery Time"].split(":")[0]}'
|
created = self.cache.get("status").get("cache_expiry") - timedelta(
|
||||||
|
hours=2
|
||||||
)
|
)
|
||||||
created = self.cache.get("status").get("cache_expiry") - timedelta(hours=2)
|
|
||||||
ts = int(created.timestamp())
|
ts = int(created.timestamp())
|
||||||
description += f" \u200b | \u200b Last updated: <t:{ts}:R>\n\u200b"
|
description += f" \u200b | \u200b Last updated: <t:{ts}:R>\n\u200b"
|
||||||
if "green" in shipping_info["Status"]:
|
if "green" in shipping_info["Status"]:
|
||||||
|
@ -316,7 +354,8 @@ class DbrandCog(Extension):
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Check Shipping Times",
|
title="Check Shipping Times",
|
||||||
description=(
|
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",
|
||||||
|
|
|
@ -40,24 +40,6 @@ class Mastodon(BaseModel):
|
||||||
url: str
|
url: str
|
||||||
|
|
||||||
|
|
||||||
class Reddit(BaseModel):
|
|
||||||
"""Reddit config."""
|
|
||||||
|
|
||||||
user_agent: Optional[str] = None
|
|
||||||
client_secret: str
|
|
||||||
client_id: str
|
|
||||||
|
|
||||||
|
|
||||||
class Twitter(BaseModel):
|
|
||||||
"""Twitter config."""
|
|
||||||
|
|
||||||
consumer_key: str
|
|
||||||
consumer_secret: str
|
|
||||||
access_token: str
|
|
||||||
access_secret: str
|
|
||||||
bearer_token: str
|
|
||||||
|
|
||||||
|
|
||||||
class Environment(Enum):
|
class Environment(Enum):
|
||||||
"""JARVIS running environment."""
|
"""JARVIS running environment."""
|
||||||
|
|
||||||
|
@ -69,12 +51,13 @@ class Config(BaseModel):
|
||||||
"""JARVIS config model."""
|
"""JARVIS config model."""
|
||||||
|
|
||||||
token: str
|
token: str
|
||||||
|
"""Bot token"""
|
||||||
|
erapi: str
|
||||||
|
"""exchangerate-api.org API token"""
|
||||||
environment: Environment = Environment.develop
|
environment: Environment = Environment.develop
|
||||||
mongo: Mongo
|
mongo: Mongo
|
||||||
redis: Redis
|
redis: Redis
|
||||||
mastodon: Optional[Mastodon] = None
|
mastodon: Optional[Mastodon] = None
|
||||||
reddit: Optional[Reddit] = None
|
|
||||||
twitter: Optional[Twitter] = None
|
|
||||||
urls: Optional[dict[str, str]] = None
|
urls: Optional[dict[str, str]] = None
|
||||||
sync: bool = False
|
sync: bool = False
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
|
@ -112,22 +95,16 @@ def _load_env() -> Config | None:
|
||||||
mongo = {}
|
mongo = {}
|
||||||
redis = {}
|
redis = {}
|
||||||
mastodon = {}
|
mastodon = {}
|
||||||
twitter = {}
|
|
||||||
reddit = {}
|
|
||||||
urls = {}
|
urls = {}
|
||||||
mongo_keys = find_all(lambda x: x.upper().startswith("MONGO"), environ.keys())
|
mongo_keys = find_all(lambda x: x.upper().startswith("MONGO"), environ.keys())
|
||||||
redis_keys = find_all(lambda x: x.upper().startswith("REDIS"), environ.keys())
|
redis_keys = find_all(lambda x: x.upper().startswith("REDIS"), environ.keys())
|
||||||
mastodon_keys = find_all(lambda x: x.upper().startswith("MASTODON"), environ.keys())
|
mastodon_keys = find_all(lambda x: x.upper().startswith("MASTODON"), environ.keys())
|
||||||
reddit_keys = find_all(lambda x: x.upper().startswith("REDDIT"), environ.keys())
|
|
||||||
twitter_keys = find_all(lambda x: x.upper().startswith("TWITTER"), environ.keys())
|
|
||||||
url_keys = find_all(lambda x: x.upper().startswith("URLS"), environ.keys())
|
url_keys = find_all(lambda x: x.upper().startswith("URLS"), environ.keys())
|
||||||
|
|
||||||
config_keys = (
|
config_keys = (
|
||||||
mongo_keys
|
mongo_keys
|
||||||
+ redis_keys
|
+ redis_keys
|
||||||
+ mastodon_keys
|
+ mastodon_keys
|
||||||
+ reddit_keys
|
|
||||||
+ twitter_keys
|
|
||||||
+ url_keys
|
+ url_keys
|
||||||
+ ["TOKEN", "SYNC", "LOG_LEVEL", "JURIGGED"]
|
+ ["TOKEN", "SYNC", "LOG_LEVEL", "JURIGGED"]
|
||||||
)
|
)
|
||||||
|
@ -145,12 +122,6 @@ def _load_env() -> Config | None:
|
||||||
elif item in mastodon_keys:
|
elif item in mastodon_keys:
|
||||||
key = "_".join(item.split("_")[1:]).lower()
|
key = "_".join(item.split("_")[1:]).lower()
|
||||||
mastodon[key] = value
|
mastodon[key] = value
|
||||||
elif item in twitter_keys:
|
|
||||||
key = "_".join(item.split("_")[1:]).lower()
|
|
||||||
twitter[key] = value
|
|
||||||
elif item in reddit_keys:
|
|
||||||
key = "_".join(item.split("_")[1:]).lower()
|
|
||||||
reddit[key] = value
|
|
||||||
elif item in url_keys:
|
elif item in url_keys:
|
||||||
key = "_".join(item.split("_")[1:]).lower()
|
key = "_".join(item.split("_")[1:]).lower()
|
||||||
urls[key] = value
|
urls[key] = value
|
||||||
|
@ -161,10 +132,6 @@ def _load_env() -> Config | None:
|
||||||
|
|
||||||
data["mongo"] = mongo
|
data["mongo"] = mongo
|
||||||
data["redis"] = redis
|
data["redis"] = redis
|
||||||
if all(x is not None for x in reddit.values()):
|
|
||||||
data["reddit"] = reddit
|
|
||||||
if all(x is not None for x in twitter.values()):
|
|
||||||
data["twitter"] = twitter
|
|
||||||
if all(x is not None for x in mastodon.values()):
|
if all(x is not None for x in mastodon.values()):
|
||||||
data["mastodon"] = mastodon
|
data["mastodon"] = mastodon
|
||||||
data["urls"] = {k: v for k, v in urls if v}
|
data["urls"] = {k: v for k, v in urls if v}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""dbrand-specific data."""
|
"""dbrand-specific data."""
|
||||||
shipping_lookup = [
|
shipping_lookup = [
|
||||||
{"country": "Afghanistan", "alpha-2": "AF", "alpha-3": "AFG", "numeric": "0004"},
|
{"country": "Afghanistan", "alpha-2": "AF", "alpha-3": "AFG", "numeric": "0004"},
|
||||||
{"country": "Ã…land Islands", "alpha-2": "AX", "alpha-3": "ALA", "numeric": "0248"},
|
{"country": "Aland Islands", "alpha-2": "AX", "alpha-3": "ALA", "numeric": "0248"},
|
||||||
{"country": "Albania", "alpha-2": "AL", "alpha-3": "ALB", "numeric": "0008"},
|
{"country": "Albania", "alpha-2": "AL", "alpha-3": "ALB", "numeric": "0008"},
|
||||||
{"country": "Algeria", "alpha-2": "DZ", "alpha-3": "DZA", "numeric": "0012"},
|
{"country": "Algeria", "alpha-2": "DZ", "alpha-3": "DZA", "numeric": "0012"},
|
||||||
{"country": "American Samoa", "alpha-2": "AS", "alpha-3": "ASM", "numeric": "0016"},
|
{"country": "American Samoa", "alpha-2": "AS", "alpha-3": "ASM", "numeric": "0016"},
|
||||||
|
@ -9,7 +9,12 @@ shipping_lookup = [
|
||||||
{"country": "Angola", "alpha-2": "AO", "alpha-3": "AGO", "numeric": "0024"},
|
{"country": "Angola", "alpha-2": "AO", "alpha-3": "AGO", "numeric": "0024"},
|
||||||
{"country": "Anguilla", "alpha-2": "AI", "alpha-3": "AIA", "numeric": "0660"},
|
{"country": "Anguilla", "alpha-2": "AI", "alpha-3": "AIA", "numeric": "0660"},
|
||||||
{"country": "Antarctica", "alpha-2": "AQ", "alpha-3": "ATA", "numeric": "0010"},
|
{"country": "Antarctica", "alpha-2": "AQ", "alpha-3": "ATA", "numeric": "0010"},
|
||||||
{"country": "Antigua and Barbuda", "alpha-2": "AG", "alpha-3": "ATG", "numeric": "0028"},
|
{
|
||||||
|
"country": "Antigua and Barbuda",
|
||||||
|
"alpha-2": "AG",
|
||||||
|
"alpha-3": "ATG",
|
||||||
|
"numeric": "0028",
|
||||||
|
},
|
||||||
{"country": "Argentina", "alpha-2": "AR", "alpha-3": "ARG", "numeric": "0032"},
|
{"country": "Argentina", "alpha-2": "AR", "alpha-3": "ARG", "numeric": "0032"},
|
||||||
{"country": "Armenia", "alpha-2": "AM", "alpha-3": "ARM", "numeric": "0051"},
|
{"country": "Armenia", "alpha-2": "AM", "alpha-3": "ARM", "numeric": "0051"},
|
||||||
{"country": "Aruba", "alpha-2": "AW", "alpha-3": "ABW", "numeric": "0533"},
|
{"country": "Aruba", "alpha-2": "AW", "alpha-3": "ABW", "numeric": "0533"},
|
||||||
|
@ -38,7 +43,12 @@ shipping_lookup = [
|
||||||
"alpha-3": "BES",
|
"alpha-3": "BES",
|
||||||
"numeric": "0535",
|
"numeric": "0535",
|
||||||
},
|
},
|
||||||
{"country": "Bosnia and Herzegovina", "alpha-2": "BA", "alpha-3": "BIH", "numeric": "0070"},
|
{
|
||||||
|
"country": "Bosnia and Herzegovina",
|
||||||
|
"alpha-2": "BA",
|
||||||
|
"alpha-3": "BIH",
|
||||||
|
"numeric": "0070",
|
||||||
|
},
|
||||||
{"country": "Botswana", "alpha-2": "BW", "alpha-3": "BWA", "numeric": "0072"},
|
{"country": "Botswana", "alpha-2": "BW", "alpha-3": "BWA", "numeric": "0072"},
|
||||||
{"country": "Bouvet Island", "alpha-2": "BV", "alpha-3": "BVT", "numeric": "0074"},
|
{"country": "Bouvet Island", "alpha-2": "BV", "alpha-3": "BVT", "numeric": "0074"},
|
||||||
{"country": "Brazil", "alpha-2": "BR", "alpha-3": "BRA", "numeric": "0076"},
|
{"country": "Brazil", "alpha-2": "BR", "alpha-3": "BRA", "numeric": "0076"},
|
||||||
|
@ -48,7 +58,12 @@ shipping_lookup = [
|
||||||
"alpha-3": "IOT",
|
"alpha-3": "IOT",
|
||||||
"numeric": "0086",
|
"numeric": "0086",
|
||||||
},
|
},
|
||||||
{"country": "Brunei Darussalam", "alpha-2": "BN", "alpha-3": "BRN", "numeric": "0096"},
|
{
|
||||||
|
"country": "Brunei Darussalam",
|
||||||
|
"alpha-2": "BN",
|
||||||
|
"alpha-3": "BRN",
|
||||||
|
"numeric": "0096",
|
||||||
|
},
|
||||||
{"country": "Bulgaria", "alpha-2": "BG", "alpha-3": "BGR", "numeric": "0100"},
|
{"country": "Bulgaria", "alpha-2": "BG", "alpha-3": "BGR", "numeric": "0100"},
|
||||||
{"country": "Burkina Faso", "alpha-2": "BF", "alpha-3": "BFA", "numeric": "0854"},
|
{"country": "Burkina Faso", "alpha-2": "BF", "alpha-3": "BFA", "numeric": "0854"},
|
||||||
{"country": "Burundi", "alpha-2": "BI", "alpha-3": "BDI", "numeric": "0108"},
|
{"country": "Burundi", "alpha-2": "BI", "alpha-3": "BDI", "numeric": "0108"},
|
||||||
|
@ -56,7 +71,12 @@ shipping_lookup = [
|
||||||
{"country": "Cambodia", "alpha-2": "KH", "alpha-3": "KHM", "numeric": "0116"},
|
{"country": "Cambodia", "alpha-2": "KH", "alpha-3": "KHM", "numeric": "0116"},
|
||||||
{"country": "Cameroon", "alpha-2": "CM", "alpha-3": "CMR", "numeric": "0120"},
|
{"country": "Cameroon", "alpha-2": "CM", "alpha-3": "CMR", "numeric": "0120"},
|
||||||
{"country": "Canada", "alpha-2": "CA", "alpha-3": "CAN", "numeric": "0124"},
|
{"country": "Canada", "alpha-2": "CA", "alpha-3": "CAN", "numeric": "0124"},
|
||||||
{"country": "Cayman Islands (the)", "alpha-2": "KY", "alpha-3": "CYM", "numeric": "0136"},
|
{
|
||||||
|
"country": "Cayman Islands (the)",
|
||||||
|
"alpha-2": "KY",
|
||||||
|
"alpha-3": "CYM",
|
||||||
|
"numeric": "0136",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"country": "Central African Republic (the)",
|
"country": "Central African Republic (the)",
|
||||||
"alpha-2": "CF",
|
"alpha-2": "CF",
|
||||||
|
@ -66,7 +86,12 @@ shipping_lookup = [
|
||||||
{"country": "Chad", "alpha-2": "TD", "alpha-3": "TCD", "numeric": "0148"},
|
{"country": "Chad", "alpha-2": "TD", "alpha-3": "TCD", "numeric": "0148"},
|
||||||
{"country": "Chile", "alpha-2": "CL", "alpha-3": "CHL", "numeric": "0152"},
|
{"country": "Chile", "alpha-2": "CL", "alpha-3": "CHL", "numeric": "0152"},
|
||||||
{"country": "China", "alpha-2": "CN", "alpha-3": "CHN", "numeric": "0156"},
|
{"country": "China", "alpha-2": "CN", "alpha-3": "CHN", "numeric": "0156"},
|
||||||
{"country": "Christmas Island", "alpha-2": "CX", "alpha-3": "CXR", "numeric": "0162"},
|
{
|
||||||
|
"country": "Christmas Island",
|
||||||
|
"alpha-2": "CX",
|
||||||
|
"alpha-3": "CXR",
|
||||||
|
"numeric": "0162",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"country": "Cocos (Keeling) Islands (the)",
|
"country": "Cocos (Keeling) Islands (the)",
|
||||||
"alpha-2": "CC",
|
"alpha-2": "CC",
|
||||||
|
@ -82,22 +107,37 @@ shipping_lookup = [
|
||||||
"numeric": "0180",
|
"numeric": "0180",
|
||||||
},
|
},
|
||||||
{"country": "Congo (the)", "alpha-2": "CG", "alpha-3": "COG", "numeric": "0178"},
|
{"country": "Congo (the)", "alpha-2": "CG", "alpha-3": "COG", "numeric": "0178"},
|
||||||
{"country": "Cook Islands (the)", "alpha-2": "CK", "alpha-3": "COK", "numeric": "0184"},
|
{
|
||||||
|
"country": "Cook Islands (the)",
|
||||||
|
"alpha-2": "CK",
|
||||||
|
"alpha-3": "COK",
|
||||||
|
"numeric": "0184",
|
||||||
|
},
|
||||||
{"country": "Costa Rica", "alpha-2": "CR", "alpha-3": "CRI", "numeric": "0188"},
|
{"country": "Costa Rica", "alpha-2": "CR", "alpha-3": "CRI", "numeric": "0188"},
|
||||||
{"country": "Côte d'Ivoire", "alpha-2": "CI", "alpha-3": "CIV", "numeric": "0384"},
|
{"country": "Ivory Coast", "alpha-2": "CI", "alpha-3": "CIV", "numeric": "0384"},
|
||||||
{"country": "Croatia", "alpha-2": "HR", "alpha-3": "HRV", "numeric": "0191"},
|
{"country": "Croatia", "alpha-2": "HR", "alpha-3": "HRV", "numeric": "0191"},
|
||||||
{"country": "Cuba", "alpha-2": "CU", "alpha-3": "CUB", "numeric": "0192"},
|
{"country": "Cuba", "alpha-2": "CU", "alpha-3": "CUB", "numeric": "0192"},
|
||||||
{"country": "Curaçao", "alpha-2": "CW", "alpha-3": "CUW", "numeric": "0531"},
|
{"country": "Curacao", "alpha-2": "CW", "alpha-3": "CUW", "numeric": "0531"},
|
||||||
{"country": "Cyprus", "alpha-2": "CY", "alpha-3": "CYP", "numeric": "0196"},
|
{"country": "Cyprus", "alpha-2": "CY", "alpha-3": "CYP", "numeric": "0196"},
|
||||||
{"country": "Czechia", "alpha-2": "CZ", "alpha-3": "CZE", "numeric": "0203"},
|
{"country": "Czechia", "alpha-2": "CZ", "alpha-3": "CZE", "numeric": "0203"},
|
||||||
{"country": "Denmark", "alpha-2": "DK", "alpha-3": "DNK", "numeric": "0208"},
|
{"country": "Denmark", "alpha-2": "DK", "alpha-3": "DNK", "numeric": "0208"},
|
||||||
{"country": "Djibouti", "alpha-2": "DJ", "alpha-3": "DJI", "numeric": "0262"},
|
{"country": "Djibouti", "alpha-2": "DJ", "alpha-3": "DJI", "numeric": "0262"},
|
||||||
{"country": "Dominica", "alpha-2": "DM", "alpha-3": "DMA", "numeric": "0212"},
|
{"country": "Dominica", "alpha-2": "DM", "alpha-3": "DMA", "numeric": "0212"},
|
||||||
{"country": "Dominican Republic (the)", "alpha-2": "DO", "alpha-3": "DOM", "numeric": "0214"},
|
{
|
||||||
|
"country": "Dominican Republic (the)",
|
||||||
|
"alpha-2": "DO",
|
||||||
|
"alpha-3": "DOM",
|
||||||
|
"numeric": "0214",
|
||||||
|
},
|
||||||
{"country": "Ecuador", "alpha-2": "EC", "alpha-3": "ECU", "numeric": "0218"},
|
{"country": "Ecuador", "alpha-2": "EC", "alpha-3": "ECU", "numeric": "0218"},
|
||||||
{"country": "Egypt", "alpha-2": "EG", "alpha-3": "EGY", "numeric": "0818"},
|
{"country": "Egypt", "alpha-2": "EG", "alpha-3": "EGY", "numeric": "0818"},
|
||||||
{"country": "El Salvador", "alpha-2": "SV", "alpha-3": "SLV", "numeric": "0222"},
|
{"country": "El Salvador", "alpha-2": "SV", "alpha-3": "SLV", "numeric": "0222"},
|
||||||
{"country": "Equatorial Guinea", "alpha-2": "GQ", "alpha-3": "GNQ", "numeric": "0226"},
|
{
|
||||||
|
"country": "Equatorial Guinea",
|
||||||
|
"alpha-2": "GQ",
|
||||||
|
"alpha-3": "GNQ",
|
||||||
|
"numeric": "0226",
|
||||||
|
},
|
||||||
{"country": "Eritrea", "alpha-2": "ER", "alpha-3": "ERI", "numeric": "0232"},
|
{"country": "Eritrea", "alpha-2": "ER", "alpha-3": "ERI", "numeric": "0232"},
|
||||||
{"country": "Estonia", "alpha-2": "EE", "alpha-3": "EST", "numeric": "0233"},
|
{"country": "Estonia", "alpha-2": "EE", "alpha-3": "EST", "numeric": "0233"},
|
||||||
{"country": "Eswatini", "alpha-2": "SZ", "alpha-3": "SWZ", "numeric": "0748"},
|
{"country": "Eswatini", "alpha-2": "SZ", "alpha-3": "SWZ", "numeric": "0748"},
|
||||||
|
@ -108,12 +148,22 @@ shipping_lookup = [
|
||||||
"alpha-3": "FLK",
|
"alpha-3": "FLK",
|
||||||
"numeric": "0238",
|
"numeric": "0238",
|
||||||
},
|
},
|
||||||
{"country": "Faroe Islands (the)", "alpha-2": "FO", "alpha-3": "FRO", "numeric": "0234"},
|
{
|
||||||
|
"country": "Faroe Islands (the)",
|
||||||
|
"alpha-2": "FO",
|
||||||
|
"alpha-3": "FRO",
|
||||||
|
"numeric": "0234",
|
||||||
|
},
|
||||||
{"country": "Fiji", "alpha-2": "FJ", "alpha-3": "FJI", "numeric": "0242"},
|
{"country": "Fiji", "alpha-2": "FJ", "alpha-3": "FJI", "numeric": "0242"},
|
||||||
{"country": "Finland", "alpha-2": "FI", "alpha-3": "FIN", "numeric": "0246"},
|
{"country": "Finland", "alpha-2": "FI", "alpha-3": "FIN", "numeric": "0246"},
|
||||||
{"country": "France", "alpha-2": "FR", "alpha-3": "FRA", "numeric": "0250"},
|
{"country": "France", "alpha-2": "FR", "alpha-3": "FRA", "numeric": "0250"},
|
||||||
{"country": "French Guiana", "alpha-2": "GF", "alpha-3": "GUF", "numeric": "0254"},
|
{"country": "French Guiana", "alpha-2": "GF", "alpha-3": "GUF", "numeric": "0254"},
|
||||||
{"country": "French Polynesia", "alpha-2": "PF", "alpha-3": "PYF", "numeric": "0258"},
|
{
|
||||||
|
"country": "French Polynesia",
|
||||||
|
"alpha-2": "PF",
|
||||||
|
"alpha-3": "PYF",
|
||||||
|
"numeric": "0258",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"country": "French Southern Territories (the)",
|
"country": "French Southern Territories (the)",
|
||||||
"alpha-2": "TF",
|
"alpha-2": "TF",
|
||||||
|
@ -150,7 +200,12 @@ shipping_lookup = [
|
||||||
{"country": "Iceland", "alpha-2": "IS", "alpha-3": "ISL", "numeric": "0352"},
|
{"country": "Iceland", "alpha-2": "IS", "alpha-3": "ISL", "numeric": "0352"},
|
||||||
{"country": "India", "alpha-2": "IN", "alpha-3": "IND", "numeric": "0356"},
|
{"country": "India", "alpha-2": "IN", "alpha-3": "IND", "numeric": "0356"},
|
||||||
{"country": "Indonesia", "alpha-2": "ID", "alpha-3": "IDN", "numeric": "0360"},
|
{"country": "Indonesia", "alpha-2": "ID", "alpha-3": "IDN", "numeric": "0360"},
|
||||||
{"country": "Iran (Islamic Republic of)", "alpha-2": "IR", "alpha-3": "IRN", "numeric": "0364"},
|
{
|
||||||
|
"country": "Iran (Islamic Republic of)",
|
||||||
|
"alpha-2": "IR",
|
||||||
|
"alpha-3": "IRN",
|
||||||
|
"numeric": "0364",
|
||||||
|
},
|
||||||
{"country": "Iraq", "alpha-2": "IQ", "alpha-3": "IRQ", "numeric": "0368"},
|
{"country": "Iraq", "alpha-2": "IQ", "alpha-3": "IRQ", "numeric": "0368"},
|
||||||
{"country": "Ireland", "alpha-2": "IE", "alpha-3": "IRL", "numeric": "0372"},
|
{"country": "Ireland", "alpha-2": "IE", "alpha-3": "IRL", "numeric": "0372"},
|
||||||
{"country": "Isle of Man", "alpha-2": "IM", "alpha-3": "IMN", "numeric": "0833"},
|
{"country": "Isle of Man", "alpha-2": "IM", "alpha-3": "IMN", "numeric": "0833"},
|
||||||
|
@ -169,7 +224,12 @@ shipping_lookup = [
|
||||||
"alpha-3": "PRK",
|
"alpha-3": "PRK",
|
||||||
"numeric": "0408",
|
"numeric": "0408",
|
||||||
},
|
},
|
||||||
{"country": "Korea (the Republic of)", "alpha-2": "KR", "alpha-3": "KOR", "numeric": "0410"},
|
{
|
||||||
|
"country": "Korea (the Republic of)",
|
||||||
|
"alpha-2": "KR",
|
||||||
|
"alpha-3": "KOR",
|
||||||
|
"numeric": "0410",
|
||||||
|
},
|
||||||
{"country": "Kuwait", "alpha-2": "KW", "alpha-3": "KWT", "numeric": "0414"},
|
{"country": "Kuwait", "alpha-2": "KW", "alpha-3": "KWT", "numeric": "0414"},
|
||||||
{"country": "Kyrgyzstan", "alpha-2": "KG", "alpha-3": "KGZ", "numeric": "0417"},
|
{"country": "Kyrgyzstan", "alpha-2": "KG", "alpha-3": "KGZ", "numeric": "0417"},
|
||||||
{
|
{
|
||||||
|
@ -199,7 +259,12 @@ shipping_lookup = [
|
||||||
{"country": "Maldives", "alpha-2": "MV", "alpha-3": "MDV", "numeric": "0462"},
|
{"country": "Maldives", "alpha-2": "MV", "alpha-3": "MDV", "numeric": "0462"},
|
||||||
{"country": "Mali", "alpha-2": "ML", "alpha-3": "MLI", "numeric": "0466"},
|
{"country": "Mali", "alpha-2": "ML", "alpha-3": "MLI", "numeric": "0466"},
|
||||||
{"country": "Malta", "alpha-2": "MT", "alpha-3": "MLT", "numeric": "0470"},
|
{"country": "Malta", "alpha-2": "MT", "alpha-3": "MLT", "numeric": "0470"},
|
||||||
{"country": "Marshall Islands (the)", "alpha-2": "MH", "alpha-3": "MHL", "numeric": "0584"},
|
{
|
||||||
|
"country": "Marshall Islands (the)",
|
||||||
|
"alpha-2": "MH",
|
||||||
|
"alpha-3": "MHL",
|
||||||
|
"numeric": "0584",
|
||||||
|
},
|
||||||
{"country": "Martinique", "alpha-2": "MQ", "alpha-3": "MTQ", "numeric": "0474"},
|
{"country": "Martinique", "alpha-2": "MQ", "alpha-3": "MTQ", "numeric": "0474"},
|
||||||
{"country": "Mauritania", "alpha-2": "MR", "alpha-3": "MRT", "numeric": "0478"},
|
{"country": "Mauritania", "alpha-2": "MR", "alpha-3": "MRT", "numeric": "0478"},
|
||||||
{"country": "Mauritius", "alpha-2": "MU", "alpha-3": "MUS", "numeric": "0480"},
|
{"country": "Mauritius", "alpha-2": "MU", "alpha-3": "MUS", "numeric": "0480"},
|
||||||
|
@ -211,7 +276,12 @@ shipping_lookup = [
|
||||||
"alpha-3": "FSM",
|
"alpha-3": "FSM",
|
||||||
"numeric": "0583",
|
"numeric": "0583",
|
||||||
},
|
},
|
||||||
{"country": "Moldova (the Republic of)", "alpha-2": "MD", "alpha-3": "MDA", "numeric": "0498"},
|
{
|
||||||
|
"country": "Moldova (the Republic of)",
|
||||||
|
"alpha-2": "MD",
|
||||||
|
"alpha-3": "MDA",
|
||||||
|
"numeric": "0498",
|
||||||
|
},
|
||||||
{"country": "Monaco", "alpha-2": "MC", "alpha-3": "MCO", "numeric": "0492"},
|
{"country": "Monaco", "alpha-2": "MC", "alpha-3": "MCO", "numeric": "0492"},
|
||||||
{"country": "Mongolia", "alpha-2": "MN", "alpha-3": "MNG", "numeric": "0496"},
|
{"country": "Mongolia", "alpha-2": "MN", "alpha-3": "MNG", "numeric": "0496"},
|
||||||
{"country": "Montenegro", "alpha-2": "ME", "alpha-3": "MNE", "numeric": "0499"},
|
{"country": "Montenegro", "alpha-2": "ME", "alpha-3": "MNE", "numeric": "0499"},
|
||||||
|
@ -222,7 +292,12 @@ shipping_lookup = [
|
||||||
{"country": "Namibia", "alpha-2": "NA", "alpha-3": "NAM", "numeric": "0516"},
|
{"country": "Namibia", "alpha-2": "NA", "alpha-3": "NAM", "numeric": "0516"},
|
||||||
{"country": "Nauru", "alpha-2": "NR", "alpha-3": "NRU", "numeric": "0520"},
|
{"country": "Nauru", "alpha-2": "NR", "alpha-3": "NRU", "numeric": "0520"},
|
||||||
{"country": "Nepal", "alpha-2": "NP", "alpha-3": "NPL", "numeric": "0524"},
|
{"country": "Nepal", "alpha-2": "NP", "alpha-3": "NPL", "numeric": "0524"},
|
||||||
{"country": "Netherlands (the)", "alpha-2": "NL", "alpha-3": "NLD", "numeric": "0528"},
|
{
|
||||||
|
"country": "Netherlands (the)",
|
||||||
|
"alpha-2": "NL",
|
||||||
|
"alpha-3": "NLD",
|
||||||
|
"numeric": "0528",
|
||||||
|
},
|
||||||
{"country": "New Caledonia", "alpha-2": "NC", "alpha-3": "NCL", "numeric": "0540"},
|
{"country": "New Caledonia", "alpha-2": "NC", "alpha-3": "NCL", "numeric": "0540"},
|
||||||
{"country": "New Zealand", "alpha-2": "NZ", "alpha-3": "NZL", "numeric": "0554"},
|
{"country": "New Zealand", "alpha-2": "NZ", "alpha-3": "NZL", "numeric": "0554"},
|
||||||
{"country": "Nicaragua", "alpha-2": "NI", "alpha-3": "NIC", "numeric": "0558"},
|
{"country": "Nicaragua", "alpha-2": "NI", "alpha-3": "NIC", "numeric": "0558"},
|
||||||
|
@ -240,32 +315,72 @@ shipping_lookup = [
|
||||||
{"country": "Oman", "alpha-2": "OM", "alpha-3": "OMN", "numeric": "0512"},
|
{"country": "Oman", "alpha-2": "OM", "alpha-3": "OMN", "numeric": "0512"},
|
||||||
{"country": "Pakistan", "alpha-2": "PK", "alpha-3": "PAK", "numeric": "0586"},
|
{"country": "Pakistan", "alpha-2": "PK", "alpha-3": "PAK", "numeric": "0586"},
|
||||||
{"country": "Palau", "alpha-2": "PW", "alpha-3": "PLW", "numeric": "0585"},
|
{"country": "Palau", "alpha-2": "PW", "alpha-3": "PLW", "numeric": "0585"},
|
||||||
{"country": "Palestine, State of", "alpha-2": "PS", "alpha-3": "PSE", "numeric": "0275"},
|
{
|
||||||
|
"country": "Palestine, State of",
|
||||||
|
"alpha-2": "PS",
|
||||||
|
"alpha-3": "PSE",
|
||||||
|
"numeric": "0275",
|
||||||
|
},
|
||||||
{"country": "Panama", "alpha-2": "PA", "alpha-3": "PAN", "numeric": "0591"},
|
{"country": "Panama", "alpha-2": "PA", "alpha-3": "PAN", "numeric": "0591"},
|
||||||
{"country": "Papua New Guinea", "alpha-2": "PG", "alpha-3": "PNG", "numeric": "0598"},
|
{
|
||||||
|
"country": "Papua New Guinea",
|
||||||
|
"alpha-2": "PG",
|
||||||
|
"alpha-3": "PNG",
|
||||||
|
"numeric": "0598",
|
||||||
|
},
|
||||||
{"country": "Paraguay", "alpha-2": "PY", "alpha-3": "PRY", "numeric": "0600"},
|
{"country": "Paraguay", "alpha-2": "PY", "alpha-3": "PRY", "numeric": "0600"},
|
||||||
{"country": "Peru", "alpha-2": "PE", "alpha-3": "PER", "numeric": "0604"},
|
{"country": "Peru", "alpha-2": "PE", "alpha-3": "PER", "numeric": "0604"},
|
||||||
{"country": "Philippines (the)", "alpha-2": "PH", "alpha-3": "PHL", "numeric": "0608"},
|
{
|
||||||
|
"country": "Philippines (the)",
|
||||||
|
"alpha-2": "PH",
|
||||||
|
"alpha-3": "PHL",
|
||||||
|
"numeric": "0608",
|
||||||
|
},
|
||||||
{"country": "Pitcairn", "alpha-2": "PN", "alpha-3": "PCN", "numeric": "0612"},
|
{"country": "Pitcairn", "alpha-2": "PN", "alpha-3": "PCN", "numeric": "0612"},
|
||||||
{"country": "Poland", "alpha-2": "PL", "alpha-3": "POL", "numeric": "0616"},
|
{"country": "Poland", "alpha-2": "PL", "alpha-3": "POL", "numeric": "0616"},
|
||||||
{"country": "Portugal", "alpha-2": "PT", "alpha-3": "PRT", "numeric": "0620"},
|
{"country": "Portugal", "alpha-2": "PT", "alpha-3": "PRT", "numeric": "0620"},
|
||||||
{"country": "Puerto Rico", "alpha-2": "PR", "alpha-3": "PRI", "numeric": "0630"},
|
{"country": "Puerto Rico", "alpha-2": "PR", "alpha-3": "PRI", "numeric": "0630"},
|
||||||
{"country": "Qatar", "alpha-2": "QA", "alpha-3": "QAT", "numeric": "0634"},
|
{"country": "Qatar", "alpha-2": "QA", "alpha-3": "QAT", "numeric": "0634"},
|
||||||
{"country": "Réunion", "alpha-2": "RE", "alpha-3": "REU", "numeric": "0638"},
|
{"country": "Reunion", "alpha-2": "RE", "alpha-3": "REU", "numeric": "0638"},
|
||||||
{"country": "Romania", "alpha-2": "RO", "alpha-3": "ROU", "numeric": "0642"},
|
{"country": "Romania", "alpha-2": "RO", "alpha-3": "ROU", "numeric": "0642"},
|
||||||
{"country": "Russian Federation (the)", "alpha-2": "RU", "alpha-3": "RUS", "numeric": "0643"},
|
{
|
||||||
|
"country": "Russian Federation (the)",
|
||||||
|
"alpha-2": "RU",
|
||||||
|
"alpha-3": "RUS",
|
||||||
|
"numeric": "0643",
|
||||||
|
},
|
||||||
{"country": "Rwanda", "alpha-2": "RW", "alpha-3": "RWA", "numeric": "0646"},
|
{"country": "Rwanda", "alpha-2": "RW", "alpha-3": "RWA", "numeric": "0646"},
|
||||||
{"country": "Saint Barthélemy", "alpha-2": "BL", "alpha-3": "BLM", "numeric": "0652"},
|
{
|
||||||
|
"country": "Saint Barthelemy",
|
||||||
|
"alpha-2": "BL",
|
||||||
|
"alpha-3": "BLM",
|
||||||
|
"numeric": "0652",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"country": "Saint Helena, Ascension and Tristan da Cunha",
|
"country": "Saint Helena, Ascension and Tristan da Cunha",
|
||||||
"alpha-2": "SH",
|
"alpha-2": "SH",
|
||||||
"alpha-3": "SHN",
|
"alpha-3": "SHN",
|
||||||
"numeric": "0654",
|
"numeric": "0654",
|
||||||
},
|
},
|
||||||
{"country": "Saint Kitts and Nevis", "alpha-2": "KN", "alpha-3": "KNA", "numeric": "0659"},
|
{
|
||||||
|
"country": "Saint Kitts and Nevis",
|
||||||
|
"alpha-2": "KN",
|
||||||
|
"alpha-3": "KNA",
|
||||||
|
"numeric": "0659",
|
||||||
|
},
|
||||||
{"country": "Saint Lucia", "alpha-2": "LC", "alpha-3": "LCA", "numeric": "0662"},
|
{"country": "Saint Lucia", "alpha-2": "LC", "alpha-3": "LCA", "numeric": "0662"},
|
||||||
{"country": "Saint Martin (French part)", "alpha-2": "MF", "alpha-3": "MAF", "numeric": "0663"},
|
{
|
||||||
{"country": "Saint Pierre and Miquelon", "alpha-2": "PM", "alpha-3": "SPM", "numeric": "0666"},
|
"country": "Saint Martin (French part)",
|
||||||
|
"alpha-2": "MF",
|
||||||
|
"alpha-3": "MAF",
|
||||||
|
"numeric": "0663",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country": "Saint Pierre and Miquelon",
|
||||||
|
"alpha-2": "PM",
|
||||||
|
"alpha-3": "SPM",
|
||||||
|
"numeric": "0666",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"country": "Saint Vincent and the Grenadines",
|
"country": "Saint Vincent and the Grenadines",
|
||||||
"alpha-2": "VC",
|
"alpha-2": "VC",
|
||||||
|
@ -274,17 +389,32 @@ shipping_lookup = [
|
||||||
},
|
},
|
||||||
{"country": "Samoa", "alpha-2": "WS", "alpha-3": "WSM", "numeric": "0882"},
|
{"country": "Samoa", "alpha-2": "WS", "alpha-3": "WSM", "numeric": "0882"},
|
||||||
{"country": "San Marino", "alpha-2": "SM", "alpha-3": "SMR", "numeric": "0674"},
|
{"country": "San Marino", "alpha-2": "SM", "alpha-3": "SMR", "numeric": "0674"},
|
||||||
{"country": "Sao Tome and Principe", "alpha-2": "ST", "alpha-3": "STP", "numeric": "0678"},
|
{
|
||||||
|
"country": "Sao Tome and Principe",
|
||||||
|
"alpha-2": "ST",
|
||||||
|
"alpha-3": "STP",
|
||||||
|
"numeric": "0678",
|
||||||
|
},
|
||||||
{"country": "Saudi Arabia", "alpha-2": "SA", "alpha-3": "SAU", "numeric": "0682"},
|
{"country": "Saudi Arabia", "alpha-2": "SA", "alpha-3": "SAU", "numeric": "0682"},
|
||||||
{"country": "Senegal", "alpha-2": "SN", "alpha-3": "SEN", "numeric": "0686"},
|
{"country": "Senegal", "alpha-2": "SN", "alpha-3": "SEN", "numeric": "0686"},
|
||||||
{"country": "Serbia", "alpha-2": "RS", "alpha-3": "SRB", "numeric": "0688"},
|
{"country": "Serbia", "alpha-2": "RS", "alpha-3": "SRB", "numeric": "0688"},
|
||||||
{"country": "Seychelles", "alpha-2": "SC", "alpha-3": "SYC", "numeric": "0690"},
|
{"country": "Seychelles", "alpha-2": "SC", "alpha-3": "SYC", "numeric": "0690"},
|
||||||
{"country": "Sierra Leone", "alpha-2": "SL", "alpha-3": "SLE", "numeric": "0694"},
|
{"country": "Sierra Leone", "alpha-2": "SL", "alpha-3": "SLE", "numeric": "0694"},
|
||||||
{"country": "Singapore", "alpha-2": "SG", "alpha-3": "SGP", "numeric": "0702"},
|
{"country": "Singapore", "alpha-2": "SG", "alpha-3": "SGP", "numeric": "0702"},
|
||||||
{"country": "Sint Maarten (Dutch part)", "alpha-2": "SX", "alpha-3": "SXM", "numeric": "0534"},
|
{
|
||||||
|
"country": "Sint Maarten (Dutch part)",
|
||||||
|
"alpha-2": "SX",
|
||||||
|
"alpha-3": "SXM",
|
||||||
|
"numeric": "0534",
|
||||||
|
},
|
||||||
{"country": "Slovakia", "alpha-2": "SK", "alpha-3": "SVK", "numeric": "0703"},
|
{"country": "Slovakia", "alpha-2": "SK", "alpha-3": "SVK", "numeric": "0703"},
|
||||||
{"country": "Slovenia", "alpha-2": "SI", "alpha-3": "SVN", "numeric": "0705"},
|
{"country": "Slovenia", "alpha-2": "SI", "alpha-3": "SVN", "numeric": "0705"},
|
||||||
{"country": "Solomon Islands", "alpha-2": "SB", "alpha-3": "SLB", "numeric": "0090"},
|
{
|
||||||
|
"country": "Solomon Islands",
|
||||||
|
"alpha-2": "SB",
|
||||||
|
"alpha-3": "SLB",
|
||||||
|
"numeric": "0090",
|
||||||
|
},
|
||||||
{"country": "Somalia", "alpha-2": "SO", "alpha-3": "SOM", "numeric": "0706"},
|
{"country": "Somalia", "alpha-2": "SO", "alpha-3": "SOM", "numeric": "0706"},
|
||||||
{"country": "South Africa", "alpha-2": "ZA", "alpha-3": "ZAF", "numeric": "0710"},
|
{"country": "South Africa", "alpha-2": "ZA", "alpha-3": "ZAF", "numeric": "0710"},
|
||||||
{
|
{
|
||||||
|
@ -298,11 +428,26 @@ shipping_lookup = [
|
||||||
{"country": "Sri Lanka", "alpha-2": "LK", "alpha-3": "LKA", "numeric": "0144"},
|
{"country": "Sri Lanka", "alpha-2": "LK", "alpha-3": "LKA", "numeric": "0144"},
|
||||||
{"country": "Sudan (the)", "alpha-2": "SD", "alpha-3": "SDN", "numeric": "0729"},
|
{"country": "Sudan (the)", "alpha-2": "SD", "alpha-3": "SDN", "numeric": "0729"},
|
||||||
{"country": "Suriname", "alpha-2": "SR", "alpha-3": "SUR", "numeric": "0740"},
|
{"country": "Suriname", "alpha-2": "SR", "alpha-3": "SUR", "numeric": "0740"},
|
||||||
{"country": "Svalbard and Jan Mayen", "alpha-2": "SJ", "alpha-3": "SJM", "numeric": "0744"},
|
{
|
||||||
|
"country": "Svalbard and Jan Mayen",
|
||||||
|
"alpha-2": "SJ",
|
||||||
|
"alpha-3": "SJM",
|
||||||
|
"numeric": "0744",
|
||||||
|
},
|
||||||
{"country": "Sweden", "alpha-2": "SE", "alpha-3": "SWE", "numeric": "0752"},
|
{"country": "Sweden", "alpha-2": "SE", "alpha-3": "SWE", "numeric": "0752"},
|
||||||
{"country": "Switzerland", "alpha-2": "CH", "alpha-3": "CHE", "numeric": "0756"},
|
{"country": "Switzerland", "alpha-2": "CH", "alpha-3": "CHE", "numeric": "0756"},
|
||||||
{"country": "Syrian Arab Republic", "alpha-2": "SY", "alpha-3": "SYR", "numeric": "0760"},
|
{
|
||||||
{"country": "Taiwan (Province of China)", "alpha-2": "TW", "alpha-3": "TWN", "numeric": "0158"},
|
"country": "Syrian Arab Republic",
|
||||||
|
"alpha-2": "SY",
|
||||||
|
"alpha-3": "SYR",
|
||||||
|
"numeric": "0760",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country": "Taiwan (Province of China)",
|
||||||
|
"alpha-2": "TW",
|
||||||
|
"alpha-3": "TWN",
|
||||||
|
"numeric": "0158",
|
||||||
|
},
|
||||||
{"country": "Tajikistan", "alpha-2": "TJ", "alpha-3": "TJK", "numeric": "0762"},
|
{"country": "Tajikistan", "alpha-2": "TJ", "alpha-3": "TJK", "numeric": "0762"},
|
||||||
{
|
{
|
||||||
"country": "Tanzania, United Republic of",
|
"country": "Tanzania, United Republic of",
|
||||||
|
@ -315,7 +460,12 @@ shipping_lookup = [
|
||||||
{"country": "Togo", "alpha-2": "TG", "alpha-3": "TGO", "numeric": "0768"},
|
{"country": "Togo", "alpha-2": "TG", "alpha-3": "TGO", "numeric": "0768"},
|
||||||
{"country": "Tokelau", "alpha-2": "TK", "alpha-3": "TKL", "numeric": "0772"},
|
{"country": "Tokelau", "alpha-2": "TK", "alpha-3": "TKL", "numeric": "0772"},
|
||||||
{"country": "Tonga", "alpha-2": "TO", "alpha-3": "TON", "numeric": "0776"},
|
{"country": "Tonga", "alpha-2": "TO", "alpha-3": "TON", "numeric": "0776"},
|
||||||
{"country": "Trinidad and Tobago", "alpha-2": "TT", "alpha-3": "TTO", "numeric": "0780"},
|
{
|
||||||
|
"country": "Trinidad and Tobago",
|
||||||
|
"alpha-2": "TT",
|
||||||
|
"alpha-3": "TTO",
|
||||||
|
"numeric": "0780",
|
||||||
|
},
|
||||||
{"country": "Tunisia", "alpha-2": "TN", "alpha-3": "TUN", "numeric": "0788"},
|
{"country": "Tunisia", "alpha-2": "TN", "alpha-3": "TUN", "numeric": "0788"},
|
||||||
{"country": "Turkey", "alpha-2": "TR", "alpha-3": "TUR", "numeric": "0792"},
|
{"country": "Turkey", "alpha-2": "TR", "alpha-3": "TUR", "numeric": "0792"},
|
||||||
{"country": "Turkmenistan", "alpha-2": "TM", "alpha-3": "TKM", "numeric": "0795"},
|
{"country": "Turkmenistan", "alpha-2": "TM", "alpha-3": "TKM", "numeric": "0795"},
|
||||||
|
@ -328,7 +478,12 @@ shipping_lookup = [
|
||||||
{"country": "Tuvalu", "alpha-2": "TV", "alpha-3": "TUV", "numeric": "0798"},
|
{"country": "Tuvalu", "alpha-2": "TV", "alpha-3": "TUV", "numeric": "0798"},
|
||||||
{"country": "Uganda", "alpha-2": "UG", "alpha-3": "UGA", "numeric": "0800"},
|
{"country": "Uganda", "alpha-2": "UG", "alpha-3": "UGA", "numeric": "0800"},
|
||||||
{"country": "Ukraine", "alpha-2": "UA", "alpha-3": "UKR", "numeric": "0804"},
|
{"country": "Ukraine", "alpha-2": "UA", "alpha-3": "UKR", "numeric": "0804"},
|
||||||
{"country": "United Arab Emirates (the)", "alpha-2": "AE", "alpha-3": "ARE", "numeric": "0784"},
|
{
|
||||||
|
"country": "United Arab Emirates (the)",
|
||||||
|
"alpha-2": "AE",
|
||||||
|
"alpha-3": "ARE",
|
||||||
|
"numeric": "0784",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"country": "United Kingdom of Great Britain and Northern Ireland (the)",
|
"country": "United Kingdom of Great Britain and Northern Ireland (the)",
|
||||||
"alpha-2": "GB",
|
"alpha-2": "GB",
|
||||||
|
@ -357,258 +512,26 @@ shipping_lookup = [
|
||||||
"numeric": "0862",
|
"numeric": "0862",
|
||||||
},
|
},
|
||||||
{"country": "Viet Nam", "alpha-2": "VN", "alpha-3": "VNM", "numeric": "0704"},
|
{"country": "Viet Nam", "alpha-2": "VN", "alpha-3": "VNM", "numeric": "0704"},
|
||||||
{"country": "Virgin Islands (British)", "alpha-2": "VG", "alpha-3": "VGB", "numeric": "0092"},
|
{
|
||||||
{"country": "Virgin Islands (U.S.)", "alpha-2": "VI", "alpha-3": "VIR", "numeric": "0850"},
|
"country": "Virgin Islands (British)",
|
||||||
{"country": "Wallis and Futuna", "alpha-2": "WF", "alpha-3": "WLF", "numeric": "0876"},
|
"alpha-2": "VG",
|
||||||
|
"alpha-3": "VGB",
|
||||||
|
"numeric": "0092",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country": "Virgin Islands (U.S.)",
|
||||||
|
"alpha-2": "VI",
|
||||||
|
"alpha-3": "VIR",
|
||||||
|
"numeric": "0850",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country": "Wallis and Futuna",
|
||||||
|
"alpha-2": "WF",
|
||||||
|
"alpha-3": "WLF",
|
||||||
|
"numeric": "0876",
|
||||||
|
},
|
||||||
{"country": "Western Sahara", "alpha-2": "EH", "alpha-3": "ESH", "numeric": "0732"},
|
{"country": "Western Sahara", "alpha-2": "EH", "alpha-3": "ESH", "numeric": "0732"},
|
||||||
{"country": "Yemen", "alpha-2": "YE", "alpha-3": "YEM", "numeric": "0887"},
|
{"country": "Yemen", "alpha-2": "YE", "alpha-3": "YEM", "numeric": "0887"},
|
||||||
{"country": "Zambia", "alpha-2": "ZM", "alpha-3": "ZMB", "numeric": "0894"},
|
{"country": "Zambia", "alpha-2": "ZM", "alpha-3": "ZMB", "numeric": "0894"},
|
||||||
{"country": "Zimbabwe", "alpha-2": "ZW", "alpha-3": "ZWE", "numeric": "0716"},
|
{"country": "Zimbabwe", "alpha-2": "ZW", "alpha-3": "ZWE", "numeric": "0716"},
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO: Implement lookup for this. Currently not doable
|
|
||||||
how_to_array = [
|
|
||||||
"https://dbrand.com/how-to-apply/airpower",
|
|
||||||
"https://dbrand.com/how-to-apply/alienware-13-r3",
|
|
||||||
"https://dbrand.com/how-to-apply/alienware-15-r3",
|
|
||||||
"https://dbrand.com/how-to-apply/alienware-17-r4-eye-tracking",
|
|
||||||
"https://dbrand.com/how-to-apply/alienware-17-r4-no-eye-tracking",
|
|
||||||
"https://dbrand.com/how-to-apply/alienware-17-r5-eye-tracking",
|
|
||||||
"https://dbrand.com/how-to-apply/alienware-17-r5-no-eye-tracking",
|
|
||||||
"https://dbrand.com/how-to-apply/anker-powercore-13000-usb-c",
|
|
||||||
"https://dbrand.com/how-to-apply/anker-powercore-plus-20100-usb-c",
|
|
||||||
"https://dbrand.com/how-to-apply/anker-powercore-plus-26800-pd",
|
|
||||||
"https://dbrand.com/how-to-apply/anker-powercore-slim-10000-pd",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-18w-usb-c-power-adapter",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-5w-usb-power-adapter",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-airpods-gen-1",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-airpods-gen-2-no-wireless-charging",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-airpods-gen-2-wireless-charging",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-airpods-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-card",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-pencil",
|
|
||||||
"https://dbrand.com/how-to-apply/apple-pencil-2",
|
|
||||||
"https://dbrand.com/how-to-apply/axon-7",
|
|
||||||
"https://dbrand.com/how-to-apply/blade-14-2014-2016-gtx-970m",
|
|
||||||
"https://dbrand.com/how-to-apply/blade-14-2016-2017-gtx-1060",
|
|
||||||
"https://dbrand.com/how-to-apply/blade-stealth-125-early-2016-skylake",
|
|
||||||
"https://dbrand.com/how-to-apply/blade-stealth-125-late-2016-2017-kaby-lake",
|
|
||||||
"https://dbrand.com/how-to-apply/blade-stealth-13.3-2017-2018",
|
|
||||||
"https://dbrand.com/how-to-apply/blade-stealth-13.3-early-2019",
|
|
||||||
"https://dbrand.com/how-to-apply/blade-stealth-13.3-late-2019",
|
|
||||||
"https://dbrand.com/how-to-apply/dell-xps-13-2-in-1-7390",
|
|
||||||
"https://dbrand.com/how-to-apply/dell-xps-13-2-in-1-9365",
|
|
||||||
"https://dbrand.com/how-to-apply/dell-xps-13-7390",
|
|
||||||
"https://dbrand.com/how-to-apply/dell-xps-13-9350-9360",
|
|
||||||
"https://dbrand.com/how-to-apply/dell-xps-15-9550",
|
|
||||||
"https://dbrand.com/how-to-apply/dell-xps-15-9560",
|
|
||||||
"https://dbrand.com/how-to-apply/eluktronics-mag-15",
|
|
||||||
"https://dbrand.com/how-to-apply/essential-phone",
|
|
||||||
"https://dbrand.com/how-to-apply/eve-v",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-a50",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-a70",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-buds",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-fold",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-10",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-10-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-10-plus-5g",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-4",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-5",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-7",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-8",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-9",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-note-fe",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s10",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s10-5g",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s10e",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s10-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s6",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s6-active",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s6-edge",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s6-edge-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s7",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s7-active",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s7-edge",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s8",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s8-active",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s8-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s9",
|
|
||||||
"https://dbrand.com/how-to-apply/galaxy-s9-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/google-home",
|
|
||||||
"https://dbrand.com/how-to-apply/honor-8",
|
|
||||||
"https://dbrand.com/how-to-apply/htc-10",
|
|
||||||
"https://dbrand.com/how-to-apply/htc-one-m7",
|
|
||||||
"https://dbrand.com/how-to-apply/htc-one-m8",
|
|
||||||
"https://dbrand.com/how-to-apply/htc-one-m9",
|
|
||||||
"https://dbrand.com/how-to-apply/htc-u-ultra",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-mate-10",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-mate-10-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-mate-20",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-mate-20-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-mate-30-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-matebook-x-pro-2018",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-matebook-x-pro-2019",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-p10",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-p10-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-p20",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-p20-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-p30",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-p30-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/huawei-p9",
|
|
||||||
"https://dbrand.com/how-to-apply/intel-nuc-mainstream-mini-pc",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-10.2-2019-gen-7",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-9.7-skins-2017-2018",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-air-2",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-air-3",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-mini-4",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-mini-5",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-pro-105",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-pro-11",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-pro-12.9-2018-gen-3",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-pro-129-2016-gen-1",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-pro-129-2017-gen-2",
|
|
||||||
"https://dbrand.com/how-to-apply/ipad-pro-97-2016",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-11",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-11-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-11-pro-max",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-4-4s",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-5",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-5s",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-6",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-6-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-6s",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-6s-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-7",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-7-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-8",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-8-plus",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-se",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-x",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-xr",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-xs",
|
|
||||||
"https://dbrand.com/how-to-apply/iphone-xs-max",
|
|
||||||
"https://dbrand.com/how-to-apply/juul",
|
|
||||||
"https://dbrand.com/how-to-apply/juul-c1",
|
|
||||||
"https://dbrand.com/how-to-apply/lenovo-thinkpad-x1-carbon-6th-gen",
|
|
||||||
"https://dbrand.com/how-to-apply/lenovo-thinkpad-x1-carbon-7th-gen",
|
|
||||||
"https://dbrand.com/how-to-apply/lg-g3",
|
|
||||||
"https://dbrand.com/how-to-apply/lg-g4",
|
|
||||||
"https://dbrand.com/how-to-apply/lg-g5",
|
|
||||||
"https://dbrand.com/how-to-apply/lg-g6",
|
|
||||||
"https://dbrand.com/how-to-apply/lg-g7",
|
|
||||||
"https://dbrand.com/how-to-apply/lg-v20",
|
|
||||||
"https://dbrand.com/how-to-apply/lg-v30",
|
|
||||||
"https://dbrand.com/how-to-apply/m40x",
|
|
||||||
"https://dbrand.com/how-to-apply/m50",
|
|
||||||
"https://dbrand.com/how-to-apply/m50x",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-12-2015-2018-retina",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-air-11",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-air-13",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-air-13-2018-2019",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-13-2013-2015-retina",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-13-skins-2016-2018-four-thunderbolt",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-13-skins-2016-2018-two-thunderbolt",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-13-skins-2019-four-thunderbolt",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-13-skins-2019-two-thunderbolt",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-15-2013-2015-retina",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-15-touch-bar",
|
|
||||||
"https://dbrand.com/how-to-apply/macbook-pro-16-2019",
|
|
||||||
"https://dbrand.com/how-to-apply/mac-pro-and-pro-display-xdr",
|
|
||||||
"https://dbrand.com/how-to-apply/maingear-element",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-g-2013",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-g-2014",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-x-2013",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-x-2014",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-x4",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-x-play",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-x-style-pure",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-z",
|
|
||||||
"https://dbrand.com/how-to-apply/moto-z-force",
|
|
||||||
"https://dbrand.com/how-to-apply/nextbit-robin",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-4",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-5",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-5x",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-6",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-6p",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-7-2012",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-7-2013",
|
|
||||||
"https://dbrand.com/how-to-apply/nexus-9",
|
|
||||||
"https://dbrand.com/how-to-apply/nintendo-switch",
|
|
||||||
"https://dbrand.com/how-to-apply/nintendo-switch-lite",
|
|
||||||
"https://dbrand.com/how-to-apply/nintendo-switch-pro-controller",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-2",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-3",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-3t",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-5",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-5t",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-6",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-6t",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-7",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-7-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-7t",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-7t-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-one",
|
|
||||||
"https://dbrand.com/how-to-apply/oneplus-x",
|
|
||||||
"https://dbrand.com/how-to-apply/pebble-time",
|
|
||||||
"https://dbrand.com/how-to-apply/pebble-watch",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-2",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-2-xl",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-3",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-3a",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-3a-xl",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-3-xl",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-4",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-4-xl",
|
|
||||||
"https://dbrand.com/how-to-apply/pixelbook",
|
|
||||||
"https://dbrand.com/how-to-apply/pixelbook-go",
|
|
||||||
"https://dbrand.com/how-to-apply/pixel-xl",
|
|
||||||
"https://dbrand.com/how-to-apply/playstation-3",
|
|
||||||
"https://dbrand.com/how-to-apply/playstation-4",
|
|
||||||
"https://dbrand.com/how-to-apply/playstation-4-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/playstation-4-slim",
|
|
||||||
"https://dbrand.com/how-to-apply/playstation-vita",
|
|
||||||
"https://dbrand.com/how-to-apply/pocophone-f1",
|
|
||||||
"https://dbrand.com/how-to-apply/razer-blade-15.6-skins-2018-advanced-no-ethernet-gtx",
|
|
||||||
"https://dbrand.com/how-to-apply/razer-blade-15.6-skins-2018-base-with-ethernet-gtx",
|
|
||||||
"https://dbrand.com/how-to-apply/razer-blade-15.6-skins-2019-advanced-no-ethernet-rtx",
|
|
||||||
"https://dbrand.com/how-to-apply/razer-blade-pro-17-2019",
|
|
||||||
"https://dbrand.com/how-to-apply/razer-phone",
|
|
||||||
"https://dbrand.com/how-to-apply/razer-phone-2",
|
|
||||||
"https://dbrand.com/how-to-apply/redmi-k20",
|
|
||||||
"https://dbrand.com/how-to-apply/redmi-k20-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-book",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-book-2-13",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-book-2-15",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-go",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-laptop",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-laptop-2",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-laptop-3-13",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-laptop-3-15",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-pro-2017",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-pro-4",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-pro-6",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-pro-7",
|
|
||||||
"https://dbrand.com/how-to-apply/surface-pro-x",
|
|
||||||
"https://dbrand.com/how-to-apply/tesla-cybertruck",
|
|
||||||
"https://dbrand.com/how-to-apply/xbox-360",
|
|
||||||
"https://dbrand.com/how-to-apply/xbox-one",
|
|
||||||
"https://dbrand.com/how-to-apply/xbox-one-s",
|
|
||||||
"https://dbrand.com/how-to-apply/xbox-one-x",
|
|
||||||
"https://dbrand.com/how-to-apply/xiaomi-mi-9t",
|
|
||||||
"https://dbrand.com/how-to-apply/xiaomi-mi-9t-pro",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z1",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z2",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z3",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z3-compact",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z5",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z5-compact",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z5-premium",
|
|
||||||
"https://dbrand.com/how-to-apply/xperia-z-ultra",
|
|
||||||
"https://dbrand.com/how-to-apply/xps-13-9370",
|
|
||||||
"https://dbrand.com/how-to-apply/xps-13-9380",
|
|
||||||
"https://dbrand.com/how-to-apply/xps-15-2-in-1-9575",
|
|
||||||
"https://dbrand.com/how-to-apply/xps-15-7590",
|
|
||||||
"https://dbrand.com/how-to-apply/xps-15-9570",
|
|
||||||
"https://dbrand.com/how-to-apply/zenfone-2",
|
|
||||||
]
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ def modlog_embed(
|
||||||
fields = [
|
fields = [
|
||||||
EmbedField(
|
EmbedField(
|
||||||
name="Moderator",
|
name="Moderator",
|
||||||
value=f"{admin.mention} ({admin.username}#{admin.discriminator})",
|
value=f"{admin.mention} ({admin.username})",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
if log and log.reason:
|
if log and log.reason:
|
||||||
|
@ -59,7 +59,7 @@ def modlog_embed(
|
||||||
timestamp=log.created_at,
|
timestamp=log.created_at,
|
||||||
)
|
)
|
||||||
embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url)
|
embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url)
|
||||||
embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}")
|
embed.set_footer(text=f"{member.username} | {member.id}")
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,19 +68,31 @@ class ModcaseCog(Extension):
|
||||||
return
|
return
|
||||||
|
|
||||||
action = await coll.find_one(
|
action = await coll.find_one(
|
||||||
coll.user == user.id, coll.guild == ctx.guild.id, coll.active == True, sort=[("_id", -1)]
|
coll.user == user.id,
|
||||||
|
coll.guild == ctx.guild.id,
|
||||||
|
coll.active == True,
|
||||||
|
sort=[("_id", -1)],
|
||||||
)
|
)
|
||||||
if not action:
|
if not action:
|
||||||
self.logger.warning("Missing action %s, exiting", name)
|
self.logger.warning("Missing action %s, exiting", name)
|
||||||
return
|
return
|
||||||
|
|
||||||
notify = await Setting.find_one(
|
notify = await Setting.find_one(
|
||||||
Setting.guild == ctx.guild.id, Setting.setting == "notify", Setting.value == True
|
Setting.guild == ctx.guild.id,
|
||||||
|
Setting.setting == "notify",
|
||||||
|
Setting.value == True,
|
||||||
)
|
)
|
||||||
if notify and name not in ("Kick", "Ban"): # Ignore Kick and Ban, as these are unique
|
if notify and name not in (
|
||||||
|
"Kick",
|
||||||
|
"Ban",
|
||||||
|
): # Ignore Kick and Ban, as these are unique
|
||||||
fields = (
|
fields = (
|
||||||
EmbedField(name="Action Type", value=name, inline=False),
|
EmbedField(name="Action Type", value=name, inline=False),
|
||||||
EmbedField(name="Reason", value=kwargs.get("reason", None) or "N/A", inline=False),
|
EmbedField(
|
||||||
|
name="Reason",
|
||||||
|
value=kwargs.get("reason", None) or "N/A",
|
||||||
|
inline=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
title="Admin action taken",
|
title="Admin action taken",
|
||||||
|
@ -89,16 +101,24 @@ class ModcaseCog(Extension):
|
||||||
)
|
)
|
||||||
if name == "Mute":
|
if name == "Mute":
|
||||||
mts = int(user.communication_disabled_until.timestamp())
|
mts = int(user.communication_disabled_until.timestamp())
|
||||||
embed.add_field(name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)")
|
embed.add_field(
|
||||||
|
name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)"
|
||||||
|
)
|
||||||
guild_url = f"https://discord.com/channels/{ctx.guild.id}"
|
guild_url = f"https://discord.com/channels/{ctx.guild.id}"
|
||||||
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url)
|
embed.set_author(
|
||||||
|
name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url
|
||||||
|
)
|
||||||
embed.set_thumbnail(url=ctx.guild.icon.url)
|
embed.set_thumbnail(url=ctx.guild.icon.url)
|
||||||
try:
|
try:
|
||||||
await user.send(embeds=embed)
|
await user.send(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.debug("User not warned of action due to closed DMs")
|
self.logger.debug("User not warned of action due to closed DMs")
|
||||||
|
|
||||||
modlog = await Modlog.find_one(Modlog.user == user.id, Modlog.guild == ctx.guild.id, Modlog.open == True)
|
modlog = await Modlog.find_one(
|
||||||
|
Modlog.user == user.id,
|
||||||
|
Modlog.guild == ctx.guild.id,
|
||||||
|
Modlog.open == True,
|
||||||
|
)
|
||||||
|
|
||||||
if modlog:
|
if modlog:
|
||||||
m_action = Action(action_type=name.lower(), parent=action.id)
|
m_action = Action(action_type=name.lower(), parent=action.id)
|
||||||
|
@ -106,7 +126,9 @@ class ModcaseCog(Extension):
|
||||||
await modlog.save()
|
await modlog.save()
|
||||||
return
|
return
|
||||||
|
|
||||||
modlog = await Setting.find_one(Setting.guild == ctx.guild.id, Setting.setting == "modlog")
|
modlog = await Setting.find_one(
|
||||||
|
Setting.guild == ctx.guild.id, Setting.setting == "modlog"
|
||||||
|
)
|
||||||
if not modlog:
|
if not modlog:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -114,7 +136,11 @@ class ModcaseCog(Extension):
|
||||||
if channel:
|
if channel:
|
||||||
fields = (
|
fields = (
|
||||||
EmbedField(name="Action Type", value=name, inline=False),
|
EmbedField(name="Action Type", value=name, inline=False),
|
||||||
EmbedField(name="Reason", value=kwargs.get("reason", None) or "N/A", inline=False),
|
EmbedField(
|
||||||
|
name="Reason",
|
||||||
|
value=kwargs.get("reason", None) or "N/A",
|
||||||
|
inline=False,
|
||||||
|
),
|
||||||
EmbedField(name="Admin", value=ctx.author.mention, inline=False),
|
EmbedField(name="Admin", value=ctx.author.mention, inline=False),
|
||||||
)
|
)
|
||||||
embed = build_embed(
|
embed = build_embed(
|
||||||
|
@ -122,23 +148,31 @@ class ModcaseCog(Extension):
|
||||||
description=f"Admin action has been taken against {user.mention}",
|
description=f"Admin action has been taken against {user.mention}",
|
||||||
fields=fields,
|
fields=fields,
|
||||||
)
|
)
|
||||||
embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=user.display_avatar.url)
|
embed.set_author(
|
||||||
|
name=f"{user.username}", icon_url=user.display_avatar.url
|
||||||
|
)
|
||||||
embed.set_footer(text=f"User ID: {user.id}")
|
embed.set_footer(text=f"User ID: {user.id}")
|
||||||
if name == "Mute":
|
if name == "Mute":
|
||||||
mts = int(user.communication_disabled_until.timestamp())
|
mts = int(user.communication_disabled_until.timestamp())
|
||||||
embed.add_field(name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)")
|
embed.add_field(
|
||||||
|
name="Muted Until", value=f"<t:{mts}:F> (<t:{mts}:R>)"
|
||||||
|
)
|
||||||
await channel.send(embeds=embed)
|
await channel.send(embeds=embed)
|
||||||
|
|
||||||
lookup_key = f"{user.id}|{ctx.guild.id}"
|
lookup_key = f"{user.id}|{ctx.guild.id}"
|
||||||
|
|
||||||
async with self.bot.redis.lock("lock|" + lookup_key):
|
async with self.bot.redis.lock("lock|" + lookup_key):
|
||||||
if await self.bot.redis.get(lookup_key):
|
if await self.bot.redis.get(lookup_key):
|
||||||
self.logger.debug(f"User {user.id} in {ctx.guild.id} already has pending case")
|
self.logger.debug(
|
||||||
|
f"User {user.id} in {ctx.guild.id} already has pending case"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
channel = await ctx.guild.fetch_channel(modlog.value)
|
channel = await ctx.guild.fetch_channel(modlog.value)
|
||||||
if not channel:
|
if not channel:
|
||||||
self.logger.warn(f"Guild {ctx.guild.id} modlog channel no longer exists, deleting")
|
self.logger.warn(
|
||||||
|
f"Guild {ctx.guild.id} modlog channel no longer exists, deleting"
|
||||||
|
)
|
||||||
await modlog.delete()
|
await modlog.delete()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -150,14 +184,22 @@ class ModcaseCog(Extension):
|
||||||
avatar_url = user.avatar.url
|
avatar_url = user.avatar.url
|
||||||
if isinstance(user, Member):
|
if isinstance(user, Member):
|
||||||
avatar_url = user.display_avatar.url
|
avatar_url = user.display_avatar.url
|
||||||
embed.set_author(name=user.username + "#" + user.discriminator, icon_url=avatar_url)
|
embed.set_author(name=user.username, icon_url=avatar_url)
|
||||||
components = [
|
components = [
|
||||||
ActionRow(
|
ActionRow(
|
||||||
Button(style=ButtonStyle.RED, emoji="✖️", custom_id="modcase|no"),
|
Button(
|
||||||
Button(style=ButtonStyle.GREEN, emoji="✔️", custom_id="modcase|yes"),
|
style=ButtonStyle.RED, emoji="✖️", custom_id="modcase|no"
|
||||||
|
),
|
||||||
|
Button(
|
||||||
|
style=ButtonStyle.GREEN, emoji="✔️", custom_id="modcase|yes"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
message = await channel.send(embeds=embed, components=components)
|
message = await channel.send(embeds=embed, components=components)
|
||||||
|
|
||||||
await self.bot.redis.set(lookup_key, f"{name.lower()}|{action.id}", ex=timedelta(days=7))
|
await self.bot.redis.set(
|
||||||
await self.bot.redis.set(f"msg|{message.id}", user.id, ex=timedelta(days=7))
|
lookup_key, f"{name.lower()}|{action.id}", ex=timedelta(days=7)
|
||||||
|
)
|
||||||
|
await self.bot.redis.set(
|
||||||
|
f"msg|{message.id}", user.id, ex=timedelta(days=7)
|
||||||
|
)
|
||||||
|
|
1612
poetry.lock
generated
1612
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -5,7 +5,7 @@ description = "JARVIS admin bot"
|
||||||
authors = ["Zevaryx <zevaryx@gmail.com>"]
|
authors = ["Zevaryx <zevaryx@gmail.com>"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.10,<4"
|
python = ">=3.11,<4"
|
||||||
PyYAML = "^6.0"
|
PyYAML = "^6.0"
|
||||||
GitPython = "^3.1.26"
|
GitPython = "^3.1.26"
|
||||||
opencv-python = "^4.5.5"
|
opencv-python = "^4.5.5"
|
||||||
|
@ -14,7 +14,7 @@ psutil = "^5.9.0"
|
||||||
python-gitlab = "^3.1.1"
|
python-gitlab = "^3.1.1"
|
||||||
ulid-py = "^1.1.0"
|
ulid-py = "^1.1.0"
|
||||||
tweepy = "^4.5.0"
|
tweepy = "^4.5.0"
|
||||||
jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "main"} # Mine
|
jarvis-core = { git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "beanie" } # Mine
|
||||||
aiohttp = "^3.8.3"
|
aiohttp = "^3.8.3"
|
||||||
pastypy = "^1.0.3.post1" # Mine
|
pastypy = "^1.0.3.post1" # Mine
|
||||||
dateparser = "^1.1.1"
|
dateparser = "^1.1.1"
|
||||||
|
@ -24,15 +24,19 @@ rich = "^12.3.0"
|
||||||
jurigged = "^0.5.3" # Contributed
|
jurigged = "^0.5.3" # Contributed
|
||||||
ansitoimg = "^2022.1"
|
ansitoimg = "^2022.1"
|
||||||
nest-asyncio = "^1.5.5"
|
nest-asyncio = "^1.5.5"
|
||||||
thefuzz = {extras = ["speedup"], git = "https://github.com/zevaryx/thefuzz.git", rev = "master"} # Forked
|
thefuzz = { extras = [
|
||||||
|
"speedup",
|
||||||
|
], git = "https://github.com/zevaryx/thefuzz.git", rev = "master" } # Forked
|
||||||
beautifulsoup4 = "^4.11.1"
|
beautifulsoup4 = "^4.11.1"
|
||||||
calculator = { git = "https://git.zevaryx.com/zevaryx/calculator.git" } # Mine
|
calculator = { git = "https://git.zevaryx.com/zevaryx/calculator.git" } # Mine
|
||||||
redis = "^4.4.0"
|
redis = "^4.4.0"
|
||||||
interactions = {git = "https://github.com/interactions-py/interactions.py", rev = "5.x"}
|
interactions-py = ">=5.3,<6"
|
||||||
statipy = { git = "https://github.com/zevaryx/statipy", rev = "main" }
|
statipy = { git = "https://github.com/zevaryx/statipy", rev = "main" }
|
||||||
beanie = "^1.17.0"
|
beanie = "^1.17.0"
|
||||||
pydantic = "^1.10.7"
|
pydantic = ">=2.3.0,<3"
|
||||||
orjson = "^3.8.8"
|
orjson = "^3.8.8"
|
||||||
|
croniter = "^1.4.1"
|
||||||
|
erapi = { git = "https://git.zevaryx.com/zevaryx-technologies/erapi.git" }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pre-commit = "^2.21.0"
|
pre-commit = "^2.21.0"
|
||||||
|
|
38
sample.env
Normal file
38
sample.env
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Base Config, required
|
||||||
|
TOKEN=
|
||||||
|
|
||||||
|
# Base Config, optional
|
||||||
|
ENVIRONMENT=develop
|
||||||
|
SYNC=false
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
JURIGGED=false
|
||||||
|
|
||||||
|
# MongoDB, required
|
||||||
|
MONGO_HOST=localhost
|
||||||
|
MONGO_USERNAME=
|
||||||
|
MONGO_PASSWORD=
|
||||||
|
MONGO_PORT=27017
|
||||||
|
|
||||||
|
# Redis, required
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_USERNAME=
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# Mastodon, optional
|
||||||
|
MASTODON_TOKEN=
|
||||||
|
MASTODON_URL=
|
||||||
|
|
||||||
|
# Reddit, optional
|
||||||
|
REDDIT_USER_AGENT=
|
||||||
|
REDDIT_CLIENT_SECRET=
|
||||||
|
REDDIT_CLIENT_ID=
|
||||||
|
|
||||||
|
# Twitter, optional
|
||||||
|
TWITTER_CONSUMER_KEY=
|
||||||
|
TWITTER_CONSUMER_SECRET=
|
||||||
|
TWITTER_ACCESS_TOKEN=
|
||||||
|
TWITTER_ACCESS_SECRET=
|
||||||
|
TWITTER_BEARER_TOKEN=
|
||||||
|
|
||||||
|
# URLs, optional
|
||||||
|
URL_DBRAND=
|
Loading…
Add table
Reference in a new issue