Merge branch 'refactoring'
This commit is contained in:
commit
408ef87f43
28 changed files with 2135 additions and 1826 deletions
104
README.md
104
README.md
|
@ -43,6 +43,108 @@ On top of the above requirements, the following pip packages are also required:
|
|||
- `psutil>=5.8, <6`
|
||||
- `GitPython>=3.1, <4`
|
||||
- `PyYaml>=5.4, <6`
|
||||
- `discord-py-slash-command>=2.3, <3`
|
||||
- `discord-py-slash-command>=2.3.2, <3`
|
||||
- `pymongo>=3.12.0, <4`
|
||||
- `opencv-python>=4.5, <5`
|
||||
- `ButtonPaginator>=0.0.3`
|
||||
- `Pillow>=8.2.0, <9`
|
||||
- `python-gitlab>=2.9.0, <3`
|
||||
- `ulid-py>=1.1.0, <2`
|
||||
|
||||
|
||||
## J.A.R.V.I.S. Cogs
|
||||
|
||||
Current cogs that are implemented:
|
||||
|
||||
- `AdminCog`
|
||||
- Handles all admin commands
|
||||
- `ModlogCog`
|
||||
- Handles modlog events
|
||||
- `AutoreactCog`
|
||||
- Handles autoreaction configuration
|
||||
- `CTC2Cog`
|
||||
- dbrand Complete the Code utilities
|
||||
- `DbrandCog`
|
||||
- dbrand-specific functions and utilities
|
||||
- `DevCog`
|
||||
- Developer utilities, such as hashing, encoding, and UUID generation
|
||||
- `ErrorCog`
|
||||
- Handles all bot errors
|
||||
- `GitlabCog`
|
||||
- Shows Gitlab information about J.A.R.V.I.S.
|
||||
- `ImageCog`
|
||||
- Image-processing cog. Only cog with no slash commands
|
||||
- `JokesCog`
|
||||
- Get a joke, have a laugh
|
||||
- `OwnerCog`
|
||||
- For the bot owner. Bot management commands
|
||||
- `RemindmeCog`
|
||||
- Manage reminders
|
||||
- `RolegiverCog`
|
||||
- Configure selectable roles
|
||||
- `SettingsCog`
|
||||
- Manage Guild settings
|
||||
- `StarboardCog`
|
||||
- Configure and add starboards and stars
|
||||
- `UtilCog`
|
||||
- Generic utilities, like userinfo and roleinfo
|
||||
- `VerifyCog`
|
||||
- Guild verification
|
||||
|
||||
|
||||
## Directories
|
||||
|
||||
### `jarvis`
|
||||
|
||||
The bot itself
|
||||
|
||||
#### `jarvis.cogs`
|
||||
|
||||
All of the cogs listed above are stored in this directory
|
||||
|
||||
##### `jarvis.cogs.admin`
|
||||
|
||||
Contains all AdminCogs, including:
|
||||
- `BanCog`
|
||||
- `KickCog`
|
||||
- `LockCog`
|
||||
- `LockdownCog`
|
||||
- `MuteCog`
|
||||
- `PurgeCog`
|
||||
- `RolepingCog`
|
||||
- `WarningCog`
|
||||
|
||||
##### `jarvis.cogs.modlog`
|
||||
|
||||
Contains all ModlogCogs, including:
|
||||
- `ModlogCommandCog`
|
||||
- `ModlogMemberCog`
|
||||
- `ModlogMessageCog`
|
||||
|
||||
`jarvis.cogs.modlog.utils` includes modlog-specific utilities
|
||||
|
||||
#### `jarvis.data`
|
||||
|
||||
Contains data relevant to J.A.R.V.I.S., such as emoji lookups and dbrand data
|
||||
|
||||
##### `jarvis.data.json`
|
||||
|
||||
Any JSON files that are needed are stored here
|
||||
|
||||
#### `jarvis.db`
|
||||
|
||||
All database-related files.
|
||||
|
||||
`jarvis.db.types` handles almost all of the database conections
|
||||
|
||||
#### `jarvis.events`
|
||||
|
||||
Containers for `@on_` d.py events
|
||||
|
||||
#### `jarvis.tasks`
|
||||
|
||||
All background tasks run from this folder
|
||||
|
||||
#### `jarvis.utils`
|
||||
|
||||
Generic utilities
|
||||
|
|
|
@ -1,30 +1,15 @@
|
|||
import asyncio
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pymongo
|
||||
from discord import DMChannel, Intents, Member, Message
|
||||
from discord import Intents
|
||||
from discord.ext import commands
|
||||
from discord.ext.tasks import loop
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashCommand
|
||||
from psutil import Process
|
||||
|
||||
from jarvis import logo, utils
|
||||
from jarvis import logo, tasks, utils
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db import DBManager
|
||||
from jarvis.db.types import (
|
||||
Autopurge,
|
||||
Autoreact,
|
||||
Ban,
|
||||
Lock,
|
||||
Mute,
|
||||
Setting,
|
||||
Warning,
|
||||
)
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
if asyncio.get_event_loop().is_closed():
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
|
@ -33,18 +18,13 @@ intents = Intents.default()
|
|||
intents.members = True
|
||||
restart_ctx = None
|
||||
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
jarvis = commands.Bot(
|
||||
command_prefix=utils.get_prefix, intents=intents, help_command=None
|
||||
)
|
||||
slash = SlashCommand(jarvis, sync_commands=True, sync_on_cog_reload=True)
|
||||
jarvis_self = Process()
|
||||
__version__ = "1.6.1"
|
||||
__version__ = "1.7.0"
|
||||
|
||||
|
||||
db = DBManager(get_config().mongo).mongo
|
||||
|
@ -74,307 +54,6 @@ async def on_ready():
|
|||
restart_ctx = None
|
||||
|
||||
|
||||
@jarvis.event
|
||||
async def on_member_join(user: Member):
|
||||
guild = user.guild
|
||||
mutes = Mute.get_active(guild=guild.id)
|
||||
if mutes and len(mutes) >= 1:
|
||||
mute_role = Setting.get(guild=guild.id, setting="mute")
|
||||
role = guild.get_role(mute_role.value)
|
||||
await user.add_roles(
|
||||
role, reason="User is muted still muted from prior mute"
|
||||
)
|
||||
unverified = Setting.get(guild=guild.id, setting="unverified")
|
||||
if unverified:
|
||||
role = guild.get_role(unverified.value)
|
||||
await user.add_roles(role, reason="User just joined and is unverified")
|
||||
|
||||
|
||||
@jarvis.event
|
||||
async def on_message(message: Message):
|
||||
channel = find(
|
||||
lambda x: x.id == 599068193339736096, message.channel_mentions
|
||||
)
|
||||
if channel and message.author.id == 293795462752894976:
|
||||
await channel.send(
|
||||
content="https://cdn.discordapp.com/attachments/"
|
||||
+ "664621130044407838/805218508866453554/tech.gif"
|
||||
)
|
||||
if (
|
||||
not isinstance(message.channel, DMChannel)
|
||||
and message.author.id != jarvis.user.id
|
||||
):
|
||||
autoreact = Autoreact.get(
|
||||
guild=message.guild.id,
|
||||
channel=message.channel.id,
|
||||
)
|
||||
if autoreact:
|
||||
for reaction in autoreact.reactions:
|
||||
await message.add_reaction(reaction)
|
||||
massmention = Setting.get(
|
||||
guild=message.guild.id,
|
||||
setting="massmention",
|
||||
)
|
||||
if (
|
||||
massmention.value > 0
|
||||
and len(message.mentions)
|
||||
- (1 if message.author in message.mentions else 0)
|
||||
> massmention.value
|
||||
):
|
||||
warning = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Mass Mention",
|
||||
user=message.author.id,
|
||||
)
|
||||
warning.insert()
|
||||
fields = [Field("Reason", "Mass Mention", False)]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick
|
||||
if message.author.nick
|
||||
else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} "
|
||||
+ f"| {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
roleping = Setting.get(guild=message.guild.id, setting="roleping")
|
||||
roles = []
|
||||
for mention in message.role_mentions:
|
||||
roles.append(mention.id)
|
||||
for mention in message.mentions:
|
||||
for role in mention.roles:
|
||||
roles.append(role.id)
|
||||
if (
|
||||
roleping
|
||||
and any(x in roleping.value for x in roles)
|
||||
and not any(x.id in roleping.value for x in message.author.roles)
|
||||
):
|
||||
warning = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Pinged a blocked role/user with a blocked role",
|
||||
user=message.author.id,
|
||||
)
|
||||
warning.insert()
|
||||
fields = [
|
||||
Field(
|
||||
"Reason",
|
||||
"Pinged a blocked role/user with a blocked role",
|
||||
False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick
|
||||
if message.author.nick
|
||||
else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} "
|
||||
+ f"| {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
autopurge = Autopurge.get(
|
||||
guild=message.guild.id, channel=message.channel.id
|
||||
)
|
||||
if autopurge:
|
||||
await message.delete(delay=autopurge.delay)
|
||||
content = re.sub(r"\s+", "", message.content)
|
||||
match = invites.search(content)
|
||||
if match:
|
||||
guild_invites = await message.guild.invites()
|
||||
allowed = [x.code for x in guild_invites] + [
|
||||
"dbrand",
|
||||
"VtgZntXcnZ",
|
||||
]
|
||||
if match.group(1) not in allowed:
|
||||
await message.delete()
|
||||
warning = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Sent an invite link",
|
||||
user=message.author.id,
|
||||
)
|
||||
warning.insert()
|
||||
fields = [
|
||||
Field(
|
||||
"Reason",
|
||||
"Sent an invite link",
|
||||
False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick
|
||||
if message.author.nick
|
||||
else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#"
|
||||
+ f"{message.author.discriminator} "
|
||||
+ f"| {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
await jarvis.process_commands(message)
|
||||
|
||||
|
||||
@jarvis.event
|
||||
async def on_guild_join(guild):
|
||||
general = find(lambda x: x.name == "general", guild.channels)
|
||||
if general and general.permissions_for(guild.me).send_messages:
|
||||
await general.send(
|
||||
"Allow me to introduce myself. I am J.A.R.V.I.S., a virtual "
|
||||
+ "artificial intelligence, and I'm here to assist you with a "
|
||||
+ "variety of tasks as best I can, "
|
||||
+ "24 hours a day, seven days a week."
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
await general.send("Importing all preferences from home interface...")
|
||||
|
||||
# Set some default settings
|
||||
setting = Setting(guild=guild.id, setting="massmention", value=5)
|
||||
setting.insert()
|
||||
|
||||
await general.send("Systems are now fully operational")
|
||||
|
||||
|
||||
@loop(minutes=1)
|
||||
async def unmute():
|
||||
mutes = Mute.get_active(duration={"$gt": 0})
|
||||
mute_roles = Setting.get_many(setting="mute")
|
||||
updates = []
|
||||
for mute in mutes:
|
||||
if (
|
||||
mute.created_at + timedelta(minutes=mute.duration)
|
||||
< datetime.utcnow()
|
||||
):
|
||||
mute_role = [x.value for x in mute_roles if x.guild == mute.guild][
|
||||
0
|
||||
]
|
||||
guild = await jarvis.fetch_guild(mute.guild)
|
||||
role = guild.get_role(mute_role)
|
||||
user = await guild.fetch_member(mute.user)
|
||||
if user:
|
||||
if role in user.roles:
|
||||
await user.remove_roles(role, reason="Mute expired")
|
||||
|
||||
# Objects can't handle bulk_write, so handle it via raw methods
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{
|
||||
"user": user.id,
|
||||
"guild": guild.id,
|
||||
"created_at": mute.created_at,
|
||||
},
|
||||
{"$set": {"active": False}},
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis_db.mutes.bulk_write(updates)
|
||||
|
||||
|
||||
@loop(minutes=10)
|
||||
async def unban():
|
||||
bans = Ban.get_active(type="temp")
|
||||
updates = []
|
||||
for ban in bans:
|
||||
if ban.created_at + timedelta(
|
||||
hours=ban.duration
|
||||
) < datetime.utcnow() + timedelta(minutes=10):
|
||||
guild = await jarvis.fetch_guild(ban.guild)
|
||||
user = await jarvis.fetch_user(ban.user)
|
||||
if user:
|
||||
guild.unban(user)
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{
|
||||
"user": user.id,
|
||||
"guild": guild.id,
|
||||
"created_at": ban.created_at,
|
||||
"type": "temp",
|
||||
},
|
||||
{"$set": {"active": False}},
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis_db.bans.bulk_write(updates)
|
||||
|
||||
|
||||
@loop(minutes=1)
|
||||
async def unlock():
|
||||
locks = Lock.get_active()
|
||||
updates = []
|
||||
for lock in locks:
|
||||
if (
|
||||
lock.created_at + timedelta(minutes=lock.duration)
|
||||
< datetime.utcnow()
|
||||
):
|
||||
guild = await jarvis.fetch_guild(lock.guild)
|
||||
channel = await jarvis.fetch_channel(lock.channel)
|
||||
if channel:
|
||||
roles = await guild.fetch_roles()
|
||||
for role in roles:
|
||||
overrides = channel.overwrites_for(role)
|
||||
overrides.send_messages = None
|
||||
await channel.set_permissions(
|
||||
role, overwrite=overrides, reason="Lock expired"
|
||||
)
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{
|
||||
"channel": channel.id,
|
||||
"guild": guild.id,
|
||||
"created_at": lock.created_at,
|
||||
},
|
||||
{"$set": {"active": False}},
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis_db.locks.bulk_write(updates)
|
||||
|
||||
|
||||
@loop(hours=1)
|
||||
async def unwarn():
|
||||
warns = Warning.get_active()
|
||||
updates = []
|
||||
for warn in warns:
|
||||
if (
|
||||
warn.created_at + timedelta(hours=warn.duration)
|
||||
< datetime.utcnow()
|
||||
):
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{"_id": warn._id}, {"$set": {"active": False}}
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis_db.warns.bulk_write(updates)
|
||||
|
||||
|
||||
def run(ctx=None):
|
||||
global restart_ctx
|
||||
if ctx:
|
||||
|
@ -388,11 +67,9 @@ def run(ctx=None):
|
|||
config.client_id
|
||||
)
|
||||
)
|
||||
unmute.start()
|
||||
unban.start()
|
||||
unlock.start()
|
||||
unwarn.start()
|
||||
|
||||
jarvis.max_messages = config.max_messages
|
||||
tasks.init()
|
||||
jarvis.run(config.token, bot=True, reconnect=True)
|
||||
for cog in jarvis.cogs:
|
||||
session = getattr(cog, "_session", None)
|
||||
|
|
1316
jarvis/cogs/admin.py
1316
jarvis/cogs/admin.py
File diff suppressed because it is too large
Load diff
21
jarvis/cogs/admin/__init__.py
Normal file
21
jarvis/cogs/admin/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from jarvis.cogs.admin import (
|
||||
ban,
|
||||
kick,
|
||||
lock,
|
||||
lockdown,
|
||||
mute,
|
||||
purge,
|
||||
roleping,
|
||||
warning,
|
||||
)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(ban.BanCog(bot))
|
||||
bot.add_cog(kick.KickCog(bot))
|
||||
bot.add_cog(lock.LockCog(bot))
|
||||
bot.add_cog(lockdown.LockdownCog(bot))
|
||||
bot.add_cog(mute.MuteCog(bot))
|
||||
bot.add_cog(purge.PurgeCog(bot))
|
||||
bot.add_cog(roleping.RolepingCog(bot))
|
||||
bot.add_cog(warning.WarningCog(bot))
|
455
jarvis/cogs/admin/ban.py
Normal file
455
jarvis/cogs/admin/ban.py
Normal file
|
@ -0,0 +1,455 @@
|
|||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ButtonPaginator import Paginator
|
||||
from discord import User
|
||||
from discord.ext import commands
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
|
||||
from jarvis.db.types import Ban, Unban
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class BanCog(CacheCog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
super().__init__(bot)
|
||||
|
||||
async def discord_apply_ban(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
reason: str,
|
||||
user: User,
|
||||
duration: int,
|
||||
active: bool,
|
||||
fields: list,
|
||||
):
|
||||
await ctx.guild.ban(user, reason=reason)
|
||||
_ = Ban(
|
||||
user=user.id,
|
||||
username=user.name,
|
||||
discrim=user.discriminator,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
type=type,
|
||||
duration=duration,
|
||||
active=active,
|
||||
).insert()
|
||||
|
||||
embed = build_embed(
|
||||
title="User Banned",
|
||||
description=f"Reason: {reason}",
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
async def discord_apply_unban(
|
||||
self, ctx: SlashContext, user: User, reason: str
|
||||
):
|
||||
await ctx.guild.unban(user, reason=reason)
|
||||
_ = Unban(
|
||||
user=user.id,
|
||||
username=user.name,
|
||||
discrim=user.discriminator,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
reason=reason,
|
||||
).insert()
|
||||
|
||||
embed = build_embed(
|
||||
title="User Unbanned",
|
||||
description=f"<@{user.id}> was unbanned",
|
||||
fields=[Field(name="Reason", value=reason)],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="ban",
|
||||
description="Ban a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to ban",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Ban reason",
|
||||
required=True,
|
||||
option_type=3,
|
||||
),
|
||||
create_option(
|
||||
name="type",
|
||||
description="Ban type",
|
||||
option_type=3,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(value="perm", name="Permanent"),
|
||||
create_choice(value="temp", name="Temporary"),
|
||||
create_choice(value="soft", name="Soft"),
|
||||
],
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Ban duration in hours if temporary",
|
||||
required=False,
|
||||
option_type=4,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(ban_members=True)
|
||||
async def _ban(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
user: User = None,
|
||||
reason: str = None,
|
||||
type: str = "perm",
|
||||
duration: int = 4,
|
||||
):
|
||||
if not user or user == ctx.author:
|
||||
await ctx.send("You cannot ban yourself.", hidden=True)
|
||||
return
|
||||
if user == self.bot.user:
|
||||
await ctx.send("I'm afraid I can't let you do that", hidden=True)
|
||||
return
|
||||
if type == "temp" and duration < 0:
|
||||
await ctx.send(
|
||||
"You cannot set a temp ban to < 0 hours.", hidden=True
|
||||
)
|
||||
return
|
||||
elif type == "temp" and duration > 744:
|
||||
await ctx.send(
|
||||
"You cannot set a temp ban to > 1 month", hidden=True
|
||||
)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
return
|
||||
if not reason:
|
||||
reason = (
|
||||
"Mr. Stark is displeased with your presence. Please leave."
|
||||
)
|
||||
|
||||
mtype = type
|
||||
if mtype == "perm":
|
||||
mtype = "perma"
|
||||
|
||||
guild_name = ctx.guild.name
|
||||
user_message = (
|
||||
f"You have been {mtype}banned from {guild_name}."
|
||||
+ f" Reason:\n{reason}"
|
||||
)
|
||||
if mtype == "temp":
|
||||
user_message += f"\nDuration: {duration} hours"
|
||||
|
||||
fields = [Field(name="Type", value=mtype)]
|
||||
|
||||
if mtype == "temp":
|
||||
fields.append(Field(name="Duration", value=f"{duration} hour(s)"))
|
||||
|
||||
user_embed = build_embed(
|
||||
title="You have been banned",
|
||||
description=f"Reason: {reason}",
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
user_embed.set_author(
|
||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
)
|
||||
user_embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
|
||||
try:
|
||||
await user.send(embed=user_embed)
|
||||
except Exception:
|
||||
send_failed = True
|
||||
try:
|
||||
await ctx.guild.ban(user, reason=reason)
|
||||
except Exception as e:
|
||||
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", hidden=True)
|
||||
return
|
||||
send_failed = False
|
||||
if mtype == "soft":
|
||||
await ctx.guild.unban(user, reason="Ban was softban")
|
||||
|
||||
fields.append(Field(name="DM Sent?", value=str(not send_failed)))
|
||||
if type != "temp":
|
||||
duration = None
|
||||
active = True
|
||||
if type == "soft":
|
||||
active = False
|
||||
|
||||
self.discord_apply_ban(ctx, reason, user, duration, active, fields)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="unban",
|
||||
description="Unban a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to unban",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Unban reason",
|
||||
required=True,
|
||||
option_type=3,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(ban_members=True)
|
||||
async def _unban(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
user: str,
|
||||
reason: str,
|
||||
):
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
return
|
||||
|
||||
orig_user = user
|
||||
discrim = None
|
||||
discord_ban_info = None
|
||||
database_ban_info = None
|
||||
|
||||
bans = await ctx.guild.bans()
|
||||
|
||||
# Try to get ban information out of Discord
|
||||
if re.match("^[0-9]{1,}$", user): # User ID
|
||||
user = int(user)
|
||||
discord_ban_info = find(lambda x: x.user.id == user, bans)
|
||||
else: # User name
|
||||
if re.match("#[0-9]{4}$", user): # User name has discrim
|
||||
user, discrim = user.split("#")
|
||||
if discrim:
|
||||
discord_ban_info = find(
|
||||
lambda x: x.user.name == user
|
||||
and x.user.discriminator == discrim,
|
||||
bans,
|
||||
)
|
||||
else:
|
||||
results = [
|
||||
x for x in filter(lambda x: x.user.name == user, bans)
|
||||
]
|
||||
if results:
|
||||
if len(results) > 1:
|
||||
active_bans = []
|
||||
for ban in bans:
|
||||
active_bans.append(
|
||||
"{0} ({1}): {2}".format(
|
||||
ban.user.name, ban.user.id, ban.reason
|
||||
)
|
||||
)
|
||||
message = (
|
||||
"More than one result. "
|
||||
+ "Please use one of the following IDs:\n```"
|
||||
+ "\n".join(active_bans)
|
||||
+ "\n```"
|
||||
)
|
||||
await ctx.send(message)
|
||||
return
|
||||
else:
|
||||
discord_ban_info = results[0]
|
||||
|
||||
# If we don't have the ban information in Discord,
|
||||
# try to find the relevant information in the database.
|
||||
# We take advantage of the previous checks to save CPU cycles
|
||||
if not discord_ban_info:
|
||||
if isinstance(user, int):
|
||||
database_ban_info = Ban.get(
|
||||
guild=ctx.guild.id, user=user, active=True
|
||||
)
|
||||
else:
|
||||
search = {
|
||||
"guild": ctx.guild.id,
|
||||
"username": user,
|
||||
"active": True,
|
||||
}
|
||||
if discrim:
|
||||
search["discrim"] = discrim
|
||||
database_ban_info = Ban.get(**search)
|
||||
|
||||
if not discord_ban_info and not database_ban_info:
|
||||
await ctx.send(f"Unable to find user {orig_user}", hidden=True)
|
||||
|
||||
elif discord_ban_info:
|
||||
await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
|
||||
else:
|
||||
discord_ban_info = find(
|
||||
lambda x: x.user.id == database_ban_info["id"], bans
|
||||
)
|
||||
if discord_ban_info:
|
||||
await self.discord_apply_unban(
|
||||
ctx, discord_ban_info.user, reason
|
||||
)
|
||||
else:
|
||||
database_ban_info.active = False
|
||||
database_ban_info.update()
|
||||
_ = Unban(
|
||||
user=database_ban_info.user,
|
||||
username=database_ban_info.username,
|
||||
discrim=database_ban_info.discrim,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
reason=reason,
|
||||
).insert()
|
||||
await ctx.send(
|
||||
"Unable to find user in Discord, "
|
||||
+ "but removed entry from database."
|
||||
)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="bans",
|
||||
name="list",
|
||||
description="List bans",
|
||||
options=[
|
||||
create_option(
|
||||
name="type",
|
||||
description="Ban type",
|
||||
option_type=4,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(value=0, name="All"),
|
||||
create_choice(value=1, name="Permanent"),
|
||||
create_choice(value=2, name="Temporary"),
|
||||
create_choice(value=3, name="Soft"),
|
||||
],
|
||||
),
|
||||
create_option(
|
||||
name="active",
|
||||
description="Active bans",
|
||||
option_type=4,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(value=1, name="Yes"),
|
||||
create_choice(value=0, name="No"),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(ban_members=True)
|
||||
async def _bans_list(
|
||||
self, ctx: SlashContext, type: int = 0, active: int = 1
|
||||
):
|
||||
active = bool(active)
|
||||
exists = self.check_cache(ctx, type=type, active=active)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.send(
|
||||
"Please use existing interaction: "
|
||||
+ f"{exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
)
|
||||
return
|
||||
types = [0, "perm", "temp", "soft"]
|
||||
search = {"guild": ctx.guild.id}
|
||||
if active:
|
||||
search["active"] = True
|
||||
if type > 0:
|
||||
search["type"] = types[type]
|
||||
bans = Ban.get_many(**search)
|
||||
bans.sort(key=lambda x: x.created_at, reverse=True)
|
||||
db_bans = []
|
||||
fields = []
|
||||
for ban in bans:
|
||||
if not ban.username:
|
||||
user = await self.bot.fetch_user(ban.user)
|
||||
ban.username = user.name if user else "[deleted user]"
|
||||
fields.append(
|
||||
Field(
|
||||
name=f"Username: {ban.username}#{ban.discrim}",
|
||||
value=f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n"
|
||||
+ f"User ID: {ban.user}\n"
|
||||
+ f"Reason: {ban.reason}\n"
|
||||
+ f"Type: {ban.type}\n\u200b",
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
db_bans.append(ban.user)
|
||||
if type == 0 and active:
|
||||
bans = await ctx.guild.bans()
|
||||
for ban in bans:
|
||||
if ban.user.id not in db_bans:
|
||||
fields.append(
|
||||
Field(
|
||||
name=f"Username: {ban.user.name}#"
|
||||
+ f"{ban.user.discriminator}",
|
||||
value="Date: [unknown]\n"
|
||||
+ f"User ID: {ban.user.id}\n"
|
||||
+ f"Reason: {ban.reason}\n"
|
||||
+ "Type: manual\n\u200b",
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
|
||||
pages = []
|
||||
title = "Active " if active else "Inactive "
|
||||
if type > 0:
|
||||
title += types[type]
|
||||
if type == 1:
|
||||
title += "a"
|
||||
title += "bans"
|
||||
if len(fields) == 0:
|
||||
embed = build_embed(
|
||||
title=title,
|
||||
description=f"No {'in' if not active else ''}active bans",
|
||||
fields=[],
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
pages.append(embed)
|
||||
else:
|
||||
for i in range(0, len(bans), 5):
|
||||
embed = build_embed(
|
||||
title=title, description="", fields=fields[i : i + 5]
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
pages.append(embed)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"guild": ctx.guild.id,
|
||||
"user": ctx.author.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"type": type,
|
||||
"active": active,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
88
jarvis/cogs/admin/kick.py
Normal file
88
jarvis/cogs/admin/kick.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
from discord import User
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
|
||||
from jarvis.db.types import Kick
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class KickCog(CacheCog):
|
||||
def __init__(self, bot):
|
||||
super().__init__(bot)
|
||||
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="kick",
|
||||
description="Kick a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to kick",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Kick reason",
|
||||
required=False,
|
||||
option_type=3,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(kick_members=True)
|
||||
async def _kick(self, ctx: SlashContext, user: User, reason=None):
|
||||
if not user or user == ctx.author:
|
||||
await ctx.send("You cannot kick yourself.", hidden=True)
|
||||
return
|
||||
if user == self.bot.user:
|
||||
await ctx.send("I'm afraid I can't let you do that", hidden=True)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
return
|
||||
if not reason:
|
||||
reason = "Mr. Stark is displeased with your presence. Please leave."
|
||||
guild_name = ctx.guild.name
|
||||
embed = build_embed(
|
||||
title=f"You have been kicked from {guild_name}",
|
||||
description=f"Reason: {reason}",
|
||||
fields=[],
|
||||
)
|
||||
|
||||
embed.set_author(
|
||||
name=ctx.author.name + "#" + ctx.author.discriminator,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(ctx.guild.icon_url)
|
||||
|
||||
send_failed = False
|
||||
try:
|
||||
await user.send(embed=embed)
|
||||
except Exception:
|
||||
send_failed = True
|
||||
await ctx.guild.kick(user, reason=reason)
|
||||
|
||||
fields = [Field(name="DM Sent?", value=str(not send_failed))]
|
||||
embed = build_embed(
|
||||
title="User Kicked",
|
||||
description=f"Reason: {reason}",
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
_ = Kick(
|
||||
user=user.id,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
).insert()
|
133
jarvis/cogs/admin/lock.py
Normal file
133
jarvis/cogs/admin/lock.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
from typing import Union
|
||||
|
||||
from discord import Role, TextChannel, User, VoiceChannel
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
|
||||
from jarvis.db.types import Lock
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
|
||||
|
||||
class LockCog(CacheCog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
super().__init__(bot)
|
||||
|
||||
async def _lock_channel(
|
||||
self,
|
||||
channel: Union[TextChannel, VoiceChannel],
|
||||
role: Role,
|
||||
admin: User,
|
||||
reason: str,
|
||||
allow_send=False,
|
||||
):
|
||||
overrides = channel.overwrites_for(role)
|
||||
if isinstance(channel, TextChannel):
|
||||
overrides.send_messages = allow_send
|
||||
elif isinstance(channel, VoiceChannel):
|
||||
overrides.speak = allow_send
|
||||
await channel.set_permissions(role, overwrite=overrides, reason=reason)
|
||||
|
||||
async def _unlock_channel(
|
||||
self,
|
||||
channel: Union[TextChannel, VoiceChannel],
|
||||
role: Role,
|
||||
admin: User,
|
||||
):
|
||||
overrides = channel.overwrites_for(role)
|
||||
if isinstance(channel, TextChannel):
|
||||
overrides.send_messages = None
|
||||
elif isinstance(channel, VoiceChannel):
|
||||
overrides.speak = None
|
||||
await channel.set_permissions(role, overwrite=overrides)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="lock",
|
||||
description="Locks a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Lock Reason",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Lock duration in minutes (default 10)",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to lock",
|
||||
option_type=7,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _lock(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
reason: str,
|
||||
duration: int = 10,
|
||||
channel: Union[TextChannel, VoiceChannel] = None,
|
||||
):
|
||||
await ctx.defer(hidden=True)
|
||||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", hidden=True)
|
||||
return
|
||||
elif duration >= 300:
|
||||
await ctx.send("Duration must be < 5 hours", hidden=True)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
return
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
for role in ctx.guild.roles:
|
||||
try:
|
||||
await self._lock_channel(channel, role, ctx.author, reason)
|
||||
except Exception:
|
||||
continue # Just continue on error
|
||||
_ = Lock(
|
||||
channel=channel.id,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
reason=reason,
|
||||
duration=duration,
|
||||
).insert()
|
||||
await ctx.send(f"{channel.mention} locked for {duration} minute(s)")
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="unlock",
|
||||
description="Unlocks a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to lock",
|
||||
option_type=7,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _unlock(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
channel: Union[TextChannel, VoiceChannel] = None,
|
||||
):
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
lock = Lock.get(guild=ctx.guild.id, channel=channel.id, active=True)
|
||||
if not lock:
|
||||
await ctx.send(f"{channel.mention} not locked.", hidden=True)
|
||||
return
|
||||
for role in ctx.guild.roles:
|
||||
try:
|
||||
await self._unlock_channel(channel, role, ctx.author)
|
||||
except Exception:
|
||||
continue # Just continue on error
|
||||
lock.active = False
|
||||
lock.update()
|
||||
await ctx.send(f"{channel.mention} unlocked")
|
114
jarvis/cogs/admin/lockdown.py
Normal file
114
jarvis/cogs/admin/lockdown.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
from datetime import datetime
|
||||
|
||||
import pymongo
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db import DBManager
|
||||
from jarvis.db.types import Lock
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
|
||||
|
||||
class LockdownCog(CacheCog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
super().__init__(bot)
|
||||
self.db = DBManager(get_config().mongo).mongo.jarvis
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="lockdown",
|
||||
name="start",
|
||||
description="Locks a server",
|
||||
options=[
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Lockdown Reason",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Lockdown duration in minutes (default 10)",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _lockdown_start(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
reason: str,
|
||||
duration: int = 10,
|
||||
):
|
||||
await ctx.defer(hidden=True)
|
||||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", hidden=True)
|
||||
return
|
||||
elif duration >= 300:
|
||||
await ctx.send("Duration must be < 5 hours", hidden=True)
|
||||
return
|
||||
channels = ctx.guild.channels
|
||||
roles = ctx.guild.roles
|
||||
updates = []
|
||||
for channel in channels:
|
||||
for role in roles:
|
||||
try:
|
||||
await self._lock_channel(channel, role, ctx.author, reason)
|
||||
except Exception:
|
||||
continue # Just continue on error
|
||||
updates.append(
|
||||
pymongo.InsertOne(
|
||||
{
|
||||
"channel": channel.id,
|
||||
"guild": ctx.guild.id,
|
||||
"admin": ctx.author.id,
|
||||
"reason": reason,
|
||||
"duration": duration,
|
||||
"active": True,
|
||||
"created_at": datetime.utcnow(),
|
||||
}
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
self.db.locks.bulk_write(updates)
|
||||
await ctx.send(f"Server locked for {duration} minute(s)")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="lockdown",
|
||||
name="end",
|
||||
description="Unlocks a server",
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _lockdown_end(
|
||||
self,
|
||||
ctx: SlashContext,
|
||||
):
|
||||
channels = ctx.guild.channels
|
||||
roles = ctx.guild.roles
|
||||
updates = []
|
||||
locks = Lock.get_many(guild=ctx.guild.id, active=True)
|
||||
if not locks:
|
||||
await ctx.send("No lockdown detected.", hidden=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
for channel in channels:
|
||||
for role in roles:
|
||||
try:
|
||||
await self._unlock_channel(channel, role, ctx.author)
|
||||
except Exception:
|
||||
continue # Just continue on error
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{
|
||||
"channel": channel.id,
|
||||
"guild": ctx.guild.id,
|
||||
"admin": ctx.author.id,
|
||||
},
|
||||
{"$set": {"active": False}},
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
self.db.locks.bulk_write(updates)
|
||||
await ctx.send("Server unlocked")
|
136
jarvis/cogs/admin/mute.py
Normal file
136
jarvis/cogs/admin/mute.py
Normal file
|
@ -0,0 +1,136 @@
|
|||
from discord import Member
|
||||
from discord.ext import commands
|
||||
from discord.utils import get
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
|
||||
from jarvis.db.types import Mute, Setting
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class MuteCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="mute",
|
||||
description="Mute a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to mute",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Reason for mute",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Mute duration",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(mute_members=True)
|
||||
async def _mute(
|
||||
self, ctx: SlashContext, user: Member, reason: str, duration: int = 30
|
||||
):
|
||||
if user == ctx.author:
|
||||
await ctx.send("You cannot mute yourself.", hidden=True)
|
||||
return
|
||||
if user == self.bot.user:
|
||||
await ctx.send("I'm afraid I can't let you do that", hidden=True)
|
||||
return
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
return
|
||||
mute_setting = Setting.get(guild=ctx.guild.id, setting="mute")
|
||||
if not mute_setting:
|
||||
await ctx.send(
|
||||
"Please configure a mute role "
|
||||
+ "with /settings mute <role> first",
|
||||
hidden=True,
|
||||
)
|
||||
return
|
||||
role = get(ctx.guild.roles, id=mute_setting.value)
|
||||
if role in user.roles:
|
||||
await ctx.send("User already muted", hidden=True)
|
||||
return
|
||||
await user.add_roles(role, reason=reason)
|
||||
if duration < 0 or duration > 300:
|
||||
duration = -1
|
||||
_ = Mute(
|
||||
user=user.id,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
duration=duration,
|
||||
active=True if duration >= 0 else False,
|
||||
).insert()
|
||||
|
||||
embed = build_embed(
|
||||
title="User Muted",
|
||||
description=f"{user.mention} has been muted",
|
||||
fields=[Field(name="Reason", value=reason)],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="unmute",
|
||||
description="Unmute a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to unmute",
|
||||
option_type=6,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(mute_members=True)
|
||||
async def _unmute(self, ctx: SlashContext, user: Member):
|
||||
mute_setting = Setting.get(guild=ctx.guild.id, setting="mute")
|
||||
if not mute_setting:
|
||||
await ctx.send(
|
||||
"Please configure a mute role with "
|
||||
+ "/settings mute <role> first.",
|
||||
hidden=True,
|
||||
)
|
||||
return
|
||||
|
||||
role = get(ctx.guild.roles, id=mute_setting.value)
|
||||
if role in user.roles:
|
||||
await user.remove_roles(role, reason="Unmute")
|
||||
else:
|
||||
await ctx.send("User is not muted.", hidden=True)
|
||||
return
|
||||
|
||||
mutes = Mute.get_many(guild=ctx.guild.id, user=user.id)
|
||||
for mute in mutes:
|
||||
mute.active = False
|
||||
mute.update()
|
||||
embed = build_embed(
|
||||
title="User Unmwaruted",
|
||||
description=f"{user.mention} has been unmuted",
|
||||
fields=[],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=user.avatar_url)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
await ctx.send(embed=embed)
|
145
jarvis/cogs/admin/purge.py
Normal file
145
jarvis/cogs/admin/purge.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
from discord import TextChannel
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
|
||||
from jarvis.db.types import Autopurge, Purge
|
||||
from jarvis.utils.permissions import admin_or_permissions
|
||||
|
||||
|
||||
class PurgeCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="purge",
|
||||
description="Purge messages from channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="amount",
|
||||
description="Amount of messages to purge",
|
||||
required=False,
|
||||
option_type=4,
|
||||
)
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _purge(self, ctx: SlashContext, amount: int = 10):
|
||||
if amount < 1:
|
||||
await ctx.send("Amount must be >= 1", hidden=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
channel = ctx.channel
|
||||
messages = []
|
||||
async for message in channel.history(limit=amount + 1):
|
||||
messages.append(message)
|
||||
await channel.delete_messages(messages)
|
||||
_ = Purge(
|
||||
channel=ctx.channel.id,
|
||||
guild=ctx.guild.id,
|
||||
admin=ctx.author.id,
|
||||
count=amount,
|
||||
).insert()
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autopurge",
|
||||
name="add",
|
||||
description="Automatically purge messages after x seconds",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to autopurge",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="delay",
|
||||
description="Seconds to keep message before purge, default 30",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _autopurge_add(
|
||||
self, ctx: SlashContext, channel: TextChannel, delay: int = 30
|
||||
):
|
||||
if not isinstance(channel, TextChannel):
|
||||
await ctx.send("Channel must be a TextChannel", hidden=True)
|
||||
return
|
||||
if delay <= 0:
|
||||
await ctx.send("Delay must be > 0", hidden=True)
|
||||
return
|
||||
elif delay > 300:
|
||||
await ctx.send("Delay must be < 5 minutes", hidden=True)
|
||||
return
|
||||
autopurge = Autopurge.get(guild=ctx.guild.id, channel=channel.id)
|
||||
if autopurge:
|
||||
await ctx.send("Autopurge already exists.", hidden=True)
|
||||
return
|
||||
autopurge = Autopurge(
|
||||
guild=ctx.guild.id,
|
||||
channel=channel.id,
|
||||
admin=ctx.author.id,
|
||||
delay=delay,
|
||||
)
|
||||
autopurge.insert()
|
||||
await ctx.send(
|
||||
f"Autopurge set up on {channel.mention}, "
|
||||
+ f"delay is {delay} seconds"
|
||||
)
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autopurge",
|
||||
name="remove",
|
||||
description="Remove an autopurge",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to remove from autopurge",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _autopurge_remove(self, ctx: SlashContext, channel: TextChannel):
|
||||
autopurge = Autopurge.get(guild=ctx.guild.id, channel=channel.id)
|
||||
if not autopurge:
|
||||
await ctx.send("Autopurge does not exist.", hidden=True)
|
||||
return
|
||||
autopurge.delete()
|
||||
await ctx.send(f"Autopurge removed from {channel.mention}.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="autopurge",
|
||||
name="update",
|
||||
description="Update autopurge on a channel",
|
||||
options=[
|
||||
create_option(
|
||||
name="channel",
|
||||
description="Channel to update",
|
||||
option_type=7,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="delay",
|
||||
description="New time to save",
|
||||
option_type=4,
|
||||
required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@admin_or_permissions(manage_messages=True)
|
||||
async def _autopurge_update(
|
||||
self, ctx: SlashContext, channel: TextChannel, delay: int
|
||||
):
|
||||
autopurge = Autopurge.get(guild=ctx.guild.id, channel=channel.id)
|
||||
if not autopurge:
|
||||
await ctx.send("Autopurge does not exist.", hidden=True)
|
||||
return
|
||||
autopurge.delay = delay
|
||||
autopurge.update()
|
||||
await ctx.send(
|
||||
f"Autopurge delay updated to {delay} seconds on {channel.mention}."
|
||||
)
|
91
jarvis/cogs/admin/roleping.py
Normal file
91
jarvis/cogs/admin/roleping.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
from discord import Role
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.utils.manage_commands import create_option
|
||||
|
||||
from jarvis.db.types import Setting
|
||||
|
||||
|
||||
class RolepingCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
name="block",
|
||||
description="Add a role to the roleping blocklist",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to add to blocklist",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _roleping_block(self, ctx: SlashContext, role: Role):
|
||||
roles = Setting.get(guild=ctx.guild.id, setting="roleping")
|
||||
if not roles:
|
||||
roles = Setting(guild=ctx.guild.id, setting="roleping", value=[])
|
||||
|
||||
if role.id in roles.value:
|
||||
await ctx.send(
|
||||
f"Role `{role.name}` already in blocklist.", hidden=True
|
||||
)
|
||||
return
|
||||
roles.value.append(role.id)
|
||||
roles.update()
|
||||
await ctx.send(f"Role `{role.name}` added to blocklist.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
name="allow",
|
||||
description="Remove a role from the roleping blocklist",
|
||||
options=[
|
||||
create_option(
|
||||
name="role",
|
||||
description="Role to remove from blocklist",
|
||||
option_type=8,
|
||||
required=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _roleping_allow(self, ctx: SlashContext, role: Role):
|
||||
roles = Setting.get(guild=ctx.guild.id, setting="roleping")
|
||||
if not roles:
|
||||
await ctx.send("No blocklist configured.", hidden=True)
|
||||
return
|
||||
|
||||
if role.id not in roles.value:
|
||||
await ctx.send(
|
||||
f"Role `{role.name}` not in blocklist.", hidden=True
|
||||
)
|
||||
return
|
||||
roles.value.remove(role.id)
|
||||
roles.update()
|
||||
await ctx.send(f"Role `{role.name}` removed blocklist.")
|
||||
|
||||
@cog_ext.cog_subcommand(
|
||||
base="roleping",
|
||||
name="list",
|
||||
description="List all blocklisted roles",
|
||||
)
|
||||
async def _roleping_list(self, ctx: SlashContext):
|
||||
roles = Setting.get(guild=ctx.guild.id, setting="roleping")
|
||||
if not roles:
|
||||
await ctx.send("No blocklist configured.", hidden=True)
|
||||
return
|
||||
|
||||
message = "Blocklisted Roles:\n```\n"
|
||||
if not roles.value:
|
||||
await ctx.send("No roles blocklisted.", hidden=True)
|
||||
return
|
||||
for role in roles.value:
|
||||
role = ctx.guild.get_role(role)
|
||||
if not role:
|
||||
continue
|
||||
message += role.name + "\n"
|
||||
message += "```"
|
||||
await ctx.send(message)
|
215
jarvis/cogs/admin/warning.py
Normal file
215
jarvis/cogs/admin/warning.py
Normal file
|
@ -0,0 +1,215 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from ButtonPaginator import Paginator
|
||||
from discord import User
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext, cog_ext
|
||||
from discord_slash.model import ButtonStyle
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option
|
||||
|
||||
from jarvis.db.types import MongoSort, Warning
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.cachecog import CacheCog
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class WarningCog(CacheCog):
|
||||
def __init__(self, bot):
|
||||
super().__init__(bot)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="warn",
|
||||
description="Warn a user",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to warn",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="reason",
|
||||
description="Reason for warning",
|
||||
option_type=3,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="duration",
|
||||
description="Duration of warning in hours, default 24",
|
||||
option_type=4,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _warn(
|
||||
self, ctx: SlashContext, user: User, reason: str, duration: int = 24
|
||||
):
|
||||
if len(reason) > 100:
|
||||
await ctx.send("Reason must be < 100 characters", hidden=True)
|
||||
return
|
||||
if duration <= 0:
|
||||
await ctx.send("Duration must be > 0", hidden=True)
|
||||
return
|
||||
elif duration >= 120:
|
||||
await ctx.send("Duration must be < 5 days", hidden=True)
|
||||
return
|
||||
await ctx.defer()
|
||||
_ = Warning(
|
||||
user=user.id,
|
||||
reason=reason,
|
||||
admin=ctx.author.id,
|
||||
guild=ctx.guild.id,
|
||||
duration=duration,
|
||||
active=True,
|
||||
).insert()
|
||||
fields = [Field("Reason", reason, False)]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{user.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.nick if user.nick else user.name,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@cog_ext.cog_slash(
|
||||
name="warnings",
|
||||
description="Get count of user warnings",
|
||||
options=[
|
||||
create_option(
|
||||
name="user",
|
||||
description="User to view",
|
||||
option_type=6,
|
||||
required=True,
|
||||
),
|
||||
create_option(
|
||||
name="active",
|
||||
description="View only active",
|
||||
option_type=4,
|
||||
required=False,
|
||||
choices=[
|
||||
create_choice(name="Yes", value=1),
|
||||
create_choice(name="No", value=0),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def _warnings(self, ctx: SlashContext, user: User, active: bool = 1):
|
||||
active = bool(active)
|
||||
exists = self.check_cache(ctx, user_id=user.id, active=active)
|
||||
if exists:
|
||||
await ctx.defer(hidden=True)
|
||||
await ctx.send(
|
||||
"Please use existing interaction: "
|
||||
+ f"{exists['paginator']._message.jump_url}",
|
||||
hidden=True,
|
||||
)
|
||||
return
|
||||
warnings = Warning.get_many(
|
||||
user=user.id,
|
||||
guild=ctx.guild.id,
|
||||
sort=MongoSort(direction="desc", key="created_at"),
|
||||
)
|
||||
active_warns = list(filter(lambda x: x.active, warnings))
|
||||
|
||||
pages = []
|
||||
if active:
|
||||
if len(active_warns) == 0:
|
||||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=f"{len(warnings)} total | 0 currently active",
|
||||
fields=[],
|
||||
)
|
||||
embed.set_author(name=user.name, icon_url=user.avatar_url)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
pages.append(embed)
|
||||
else:
|
||||
fields = []
|
||||
for warn in active_warns:
|
||||
admin = ctx.guild.get(warn.admin)
|
||||
admin_name = "||`[redacted]`||"
|
||||
if admin:
|
||||
admin_name = f"{admin.name}#{admin.discriminator}"
|
||||
fields.append(
|
||||
Field(
|
||||
name=warn.created_at.strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
),
|
||||
value=f"{warn.reason}\n"
|
||||
+ f"Admin: {admin_name}\n"
|
||||
+ "\u200b",
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
for i in range(0, len(fields), 5):
|
||||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=f"{len(warnings)} total | "
|
||||
+ f"{len(active_warns)} currently active",
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.name + "#" + user.discriminator,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
embed.set_footer(
|
||||
text=f"{user.name}#{user.discriminator} | {user.id}"
|
||||
)
|
||||
pages.append(embed)
|
||||
else:
|
||||
fields = []
|
||||
for warn in warnings:
|
||||
title = "[A] " if warn.active else "[I] "
|
||||
title += warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
fields.append(
|
||||
Field(
|
||||
name=title,
|
||||
value=warn.reason + "\n\u200b",
|
||||
inline=False,
|
||||
)
|
||||
)
|
||||
for i in range(0, len(fields), 5):
|
||||
embed = build_embed(
|
||||
title="Warnings",
|
||||
description=f"{len(warnings)} total | "
|
||||
+ f"{len(active_warns)} currently active",
|
||||
fields=fields[i : i + 5],
|
||||
)
|
||||
embed.set_author(
|
||||
name=user.name + "#" + user.discriminator,
|
||||
icon_url=user.avatar_url,
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.guild.icon_url)
|
||||
pages.append(embed)
|
||||
|
||||
paginator = Paginator(
|
||||
bot=self.bot,
|
||||
ctx=ctx,
|
||||
embeds=pages,
|
||||
only=ctx.author,
|
||||
timeout=60 * 5, # 5 minute timeout
|
||||
disable_after_timeout=True,
|
||||
use_extend=len(pages) > 2,
|
||||
left_button_style=ButtonStyle.grey,
|
||||
right_button_style=ButtonStyle.grey,
|
||||
basic_buttons=["◀", "▶"],
|
||||
)
|
||||
|
||||
self.cache[hash(paginator)] = {
|
||||
"guild": ctx.guild.id,
|
||||
"user": ctx.author.id,
|
||||
"timeout": datetime.utcnow() + timedelta(minutes=5),
|
||||
"command": ctx.subcommand_name,
|
||||
"user_id": user.id,
|
||||
"active": active,
|
||||
"paginator": paginator,
|
||||
}
|
||||
|
||||
await paginator.start()
|
7
jarvis/cogs/modlog/__init__.py
Normal file
7
jarvis/cogs/modlog/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from jarvis.cogs.modlog import command, member, message
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(command.ModlogCommandCog(bot))
|
||||
bot.add_cog(member.ModlogMemberCog(bot))
|
||||
bot.add_cog(message.ModlogMessageCog(bot))
|
58
jarvis/cogs/modlog/command.py
Normal file
58
jarvis/cogs/modlog/command.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from discord import DMChannel
|
||||
from discord.ext import commands
|
||||
from discord_slash import SlashContext
|
||||
|
||||
from jarvis.db.types import Setting
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class ModlogCommandCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_slash_command(self, ctx: SlashContext):
|
||||
if not isinstance(ctx.channel, DMChannel):
|
||||
modlog = Setting.get(guild=ctx.guild.id, setting="modlog")
|
||||
if modlog:
|
||||
channel = ctx.guild.get_channel(modlog.value)
|
||||
fields = [
|
||||
Field("Command", ctx.name),
|
||||
]
|
||||
if ctx.args:
|
||||
fields.append(
|
||||
Field(
|
||||
"Args",
|
||||
" ".join(ctx.args),
|
||||
False,
|
||||
)
|
||||
)
|
||||
if ctx.kwargs:
|
||||
kwargs_string = " ".join(
|
||||
f"{k}: {ctx.kwargs[k]}" for k in ctx.kwargs
|
||||
)
|
||||
fields.append(
|
||||
Field(
|
||||
"Keyword Args",
|
||||
kwargs_string,
|
||||
False,
|
||||
)
|
||||
)
|
||||
if ctx.subcommand_name:
|
||||
fields.insert(1, Field("Subcommand", ctx.subcommand_name))
|
||||
embed = build_embed(
|
||||
title="Command Invoked",
|
||||
description=f"{ctx.author.mention} invoked a command",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
embed.set_author(
|
||||
name=ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{ctx.author.name}#{ctx.author.discriminator}"
|
||||
+ f" | {ctx.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
|
@ -2,65 +2,20 @@ import asyncio
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import discord
|
||||
from discord import DMChannel
|
||||
from discord.ext import commands
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashContext
|
||||
|
||||
from jarvis.cogs.modlog.utils import get_latest_log, modlog_embed
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.types import Ban, Kick, MongoSort, Mute, Setting
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class ModlogCog(commands.Cog):
|
||||
"""
|
||||
A hybrid user/modlog functionality for J.A.R.V.I.S.
|
||||
"""
|
||||
|
||||
def __init__(self, bot: discord.ext.commands.Bot):
|
||||
class ModlogMemberCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
def get_latest_log(self, auditlog, target):
|
||||
before = datetime.utcnow() - timedelta(seconds=10)
|
||||
return find(
|
||||
lambda x: x.target.id == target.id and x.created_at > before,
|
||||
auditlog,
|
||||
)
|
||||
|
||||
async def modlog_embed(
|
||||
self,
|
||||
member: discord.Member,
|
||||
admin: discord.Member,
|
||||
log: discord.AuditLogEntry,
|
||||
title: str,
|
||||
desc: str,
|
||||
) -> discord.Embed:
|
||||
fields = [
|
||||
Field(
|
||||
name="Moderator",
|
||||
value=f"{admin.mention} ({admin.name}"
|
||||
+ f"#{admin.discriminator})",
|
||||
),
|
||||
]
|
||||
if log.reason:
|
||||
fields.append(Field(name="Reason", value=log.reason, inline=False))
|
||||
embed = build_embed(
|
||||
title=title,
|
||||
description=desc,
|
||||
color="#fc9e3f",
|
||||
fields=fields,
|
||||
timestamp=log.created_at,
|
||||
)
|
||||
embed.set_author(
|
||||
name=f"{member.name}",
|
||||
icon_url=member.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{member.name}#{member.discriminator} | {member.id}"
|
||||
)
|
||||
return embed
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_member_ban(self, guild: discord.Guild, user: discord.User):
|
||||
modlog = Setting.get(guild=guild.id, setting="modlog")
|
||||
|
@ -73,7 +28,7 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(auditlog, user)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, user)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
ban = Ban.get(
|
||||
|
@ -83,7 +38,7 @@ class ModlogCog(commands.Cog):
|
|||
sort=MongoSort(key="created_at", type="desc"),
|
||||
)
|
||||
admin = guild.get_member(ban.admin)
|
||||
embed = await self.modlog_embed(
|
||||
embed = await modlog_embed(
|
||||
user,
|
||||
admin,
|
||||
log,
|
||||
|
@ -105,7 +60,7 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(auditlog, user)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, user)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
ban = Ban.get(
|
||||
|
@ -115,7 +70,7 @@ class ModlogCog(commands.Cog):
|
|||
sort=MongoSort(key="created_at", type="desc"),
|
||||
)
|
||||
admin = guild.get_member(ban.admin)
|
||||
embed = await self.modlog_embed(
|
||||
embed = await modlog_embed(
|
||||
user,
|
||||
admin,
|
||||
log,
|
||||
|
@ -137,7 +92,7 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(auditlog, user)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, user)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
kick = Kick.get(
|
||||
|
@ -146,7 +101,7 @@ class ModlogCog(commands.Cog):
|
|||
sort=MongoSort(key="created_at", type="desc"),
|
||||
)
|
||||
admin = user.guild.get_member(kick.admin)
|
||||
embed = await self.modlog_embed(
|
||||
embed = await modlog_embed(
|
||||
user,
|
||||
admin,
|
||||
log,
|
||||
|
@ -163,7 +118,7 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
mute = Mute.get(
|
||||
|
@ -173,7 +128,7 @@ class ModlogCog(commands.Cog):
|
|||
sort=MongoSort(key="created_at", type="desc"),
|
||||
)
|
||||
admin = before.guild.get_member(mute.admin)
|
||||
return await self.modlog_embed(
|
||||
return await modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
|
@ -188,7 +143,7 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
if admin.id == get_config().client_id:
|
||||
mute = Mute.get(
|
||||
|
@ -199,7 +154,7 @@ class ModlogCog(commands.Cog):
|
|||
)
|
||||
mute = Mute(**mute)
|
||||
admin = before.guild.get_member(mute.admin)
|
||||
return await self.modlog_embed(
|
||||
return await modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
|
@ -214,9 +169,9 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
return await self.modlog_embed(
|
||||
return await modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
|
@ -231,7 +186,7 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
admin: discord.User = log.user
|
||||
role = None
|
||||
title = "User Given Role"
|
||||
|
@ -243,7 +198,7 @@ class ModlogCog(commands.Cog):
|
|||
elif len(before.roles) < len(after.roles):
|
||||
role = find(lambda x: x not in before.roles, after.roles)
|
||||
role_text = role.mention if role else "||`[redacted]`||"
|
||||
return await self.modlog_embed(
|
||||
return await modlog_embed(
|
||||
member=before,
|
||||
admin=admin,
|
||||
log=log,
|
||||
|
@ -279,9 +234,7 @@ class ModlogCog(commands.Cog):
|
|||
after=datetime.utcnow() - timedelta(seconds=15),
|
||||
oldest_first=False,
|
||||
).flatten()
|
||||
log: discord.AuditLogEntry = self.get_latest_log(
|
||||
auditlog, before
|
||||
)
|
||||
log: discord.AuditLogEntry = get_latest_log(auditlog, before)
|
||||
bname = before.nick if before.nick else before.name
|
||||
aname = after.nick if after.nick else after.name
|
||||
fields = [
|
||||
|
@ -327,117 +280,3 @@ class ModlogCog(commands.Cog):
|
|||
embed = await self.process_rolechange(before, after)
|
||||
if embed:
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message_edit(
|
||||
self, before: discord.Message, after: discord.Message
|
||||
):
|
||||
if before.author != get_config().client_id:
|
||||
modlog = Setting.get(guild=after.guild.id, setting="modlog")
|
||||
if modlog:
|
||||
if before.content == after.content or before.content is None:
|
||||
return
|
||||
channel = before.guild.get_channel(modlog.value)
|
||||
fields = [
|
||||
Field(
|
||||
"Original Message",
|
||||
before.content if before.content else "N/A",
|
||||
False,
|
||||
),
|
||||
Field(
|
||||
"New Message",
|
||||
after.content if after.content else "N/A",
|
||||
False,
|
||||
),
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Message Edited",
|
||||
description=f"{before.author.mention} edited a message",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
timestamp=after.edited_at,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_author(
|
||||
name=before.author.name,
|
||||
icon_url=before.author.avatar_url,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{before.author.name}#{before.author.discriminator}"
|
||||
+ f" | {before.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message_delete(self, message: discord.Message):
|
||||
modlog = Setting.get(guild=message.guild.id, setting="modlog")
|
||||
if modlog:
|
||||
fields = [Field("Original Message", message.content, False)]
|
||||
channel = message.guild.get_channel(modlog.value)
|
||||
embed = build_embed(
|
||||
title="Message Deleted",
|
||||
description=f"{message.author.mention}'s message was deleted",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
url=message.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator}"
|
||||
+ f" | {message.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_slash_command(self, ctx: SlashContext):
|
||||
if not isinstance(ctx.channel, DMChannel):
|
||||
modlog = Setting.get(guild=ctx.guild.id, setting="modlog")
|
||||
if modlog:
|
||||
channel = ctx.guild.get_channel(modlog.value)
|
||||
fields = [
|
||||
Field("Command", ctx.name),
|
||||
]
|
||||
if ctx.args:
|
||||
fields.append(
|
||||
Field(
|
||||
"Args",
|
||||
" ".join(ctx.args),
|
||||
False,
|
||||
)
|
||||
)
|
||||
if ctx.kwargs:
|
||||
kwargs_string = " ".join(
|
||||
f"{k}: {ctx.kwargs[k]}" for k in ctx.kwargs
|
||||
)
|
||||
fields.append(
|
||||
Field(
|
||||
"Keyword Args",
|
||||
kwargs_string,
|
||||
False,
|
||||
)
|
||||
)
|
||||
if ctx.subcommand_name:
|
||||
fields.insert(1, Field("Subcommand", ctx.subcommand_name))
|
||||
embed = build_embed(
|
||||
title="Command Invoked",
|
||||
description=f"{ctx.author.mention} invoked a command",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
embed.set_author(
|
||||
name=ctx.author.name,
|
||||
icon_url=ctx.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{ctx.author.name}#{ctx.author.discriminator}"
|
||||
+ f" | {ctx.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(ModlogCog(bot))
|
75
jarvis/cogs/modlog/message.py
Normal file
75
jarvis/cogs/modlog/message.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from jarvis.db.types import Setting
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
class ModlogMessageCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message_edit(
|
||||
self, before: discord.Message, after: discord.Message
|
||||
):
|
||||
if not before.author.bot:
|
||||
modlog = Setting.get(guild=after.guild.id, setting="modlog")
|
||||
if modlog:
|
||||
if before.content == after.content or before.content is None:
|
||||
return
|
||||
channel = before.guild.get_channel(modlog.value)
|
||||
fields = [
|
||||
Field(
|
||||
"Original Message",
|
||||
before.content if before.content else "N/A",
|
||||
False,
|
||||
),
|
||||
Field(
|
||||
"New Message",
|
||||
after.content if after.content else "N/A",
|
||||
False,
|
||||
),
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Message Edited",
|
||||
description=f"{before.author.mention} edited a message",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
timestamp=after.edited_at,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_author(
|
||||
name=before.author.name,
|
||||
icon_url=before.author.avatar_url,
|
||||
url=after.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{before.author.name}#{before.author.discriminator}"
|
||||
+ f" | {before.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message_delete(self, message: discord.Message):
|
||||
modlog = Setting.get(guild=message.guild.id, setting="modlog")
|
||||
if modlog:
|
||||
fields = [Field("Original Message", message.content, False)]
|
||||
channel = message.guild.get_channel(modlog.value)
|
||||
embed = build_embed(
|
||||
title="Message Deleted",
|
||||
description=f"{message.author.mention}'s message was deleted",
|
||||
fields=fields,
|
||||
color="#fc9e3f",
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
url=message.jump_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator}"
|
||||
+ f" | {message.author.id}"
|
||||
)
|
||||
await channel.send(embed=embed)
|
49
jarvis/cogs/modlog/utils.py
Normal file
49
jarvis/cogs/modlog/utils.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import discord
|
||||
from discord.utils import find
|
||||
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
|
||||
def modlog_embed(
|
||||
self,
|
||||
member: discord.Member,
|
||||
admin: discord.Member,
|
||||
log: discord.AuditLogEntry,
|
||||
title: str,
|
||||
desc: str,
|
||||
) -> discord.Embed:
|
||||
fields = [
|
||||
Field(
|
||||
name="Moderator",
|
||||
value=f"{admin.mention} ({admin.name}"
|
||||
+ f"#{admin.discriminator})",
|
||||
),
|
||||
]
|
||||
if log.reason:
|
||||
fields.append(Field(name="Reason", value=log.reason, inline=False))
|
||||
embed = build_embed(
|
||||
title=title,
|
||||
description=desc,
|
||||
color="#fc9e3f",
|
||||
fields=fields,
|
||||
timestamp=log.created_at,
|
||||
)
|
||||
embed.set_author(
|
||||
name=f"{member.name}",
|
||||
icon_url=member.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{member.name}#{member.discriminator} | {member.id}"
|
||||
)
|
||||
return embed
|
||||
|
||||
|
||||
def get_latest_log(self, auditlog, target):
|
||||
before = datetime.utcnow() - timedelta(seconds=10)
|
||||
return find(
|
||||
lambda x: x.target.id == target.id and x.created_at > before,
|
||||
auditlog,
|
||||
)
|
26
jarvis/events/guild.py
Normal file
26
jarvis/events/guild.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import asyncio
|
||||
|
||||
from discord.utils import find
|
||||
|
||||
from jarvis import jarvis
|
||||
from jarvis.db.types import Setting
|
||||
|
||||
|
||||
@jarvis.event
|
||||
async def on_guild_join(guild):
|
||||
general = find(lambda x: x.name == "general", guild.channels)
|
||||
if general and general.permissions_for(guild.me).send_messages:
|
||||
await general.send(
|
||||
"Allow me to introduce myself. I am J.A.R.V.I.S., a virtual "
|
||||
+ "artificial intelligence, and I'm here to assist you with a "
|
||||
+ "variety of tasks as best I can, "
|
||||
+ "24 hours a day, seven days a week."
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
await general.send("Importing all preferences from home interface...")
|
||||
|
||||
# Set some default settings
|
||||
setting = Setting(guild=guild.id, setting="massmention", value=5)
|
||||
setting.insert()
|
||||
|
||||
await general.send("Systems are now fully operational")
|
20
jarvis/events/member.py
Normal file
20
jarvis/events/member.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from discord import Member
|
||||
|
||||
from jarvis import jarvis
|
||||
from jarvis.db.types import Mute, Setting
|
||||
|
||||
|
||||
@jarvis.event
|
||||
async def on_member_join(user: Member):
|
||||
guild = user.guild
|
||||
mutes = Mute.get_active(guild=guild.id)
|
||||
if mutes and len(mutes) >= 1:
|
||||
mute_role = Setting.get(guild=guild.id, setting="mute")
|
||||
role = guild.get_role(mute_role.value)
|
||||
await user.add_roles(
|
||||
role, reason="User is muted still muted from prior mute"
|
||||
)
|
||||
unverified = Setting.get(guild=guild.id, setting="unverified")
|
||||
if unverified:
|
||||
role = guild.get_role(unverified.value)
|
||||
await user.add_roles(role, reason="User just joined and is unverified")
|
188
jarvis/events/message.py
Normal file
188
jarvis/events/message.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
import re
|
||||
|
||||
from discord import DMChannel, Message
|
||||
from discord.utils import find
|
||||
|
||||
from jarvis import jarvis
|
||||
from jarvis.config import get_config
|
||||
from jarvis.db.types import Autopurge, Autoreact, Setting
|
||||
from jarvis.utils import build_embed
|
||||
from jarvis.utils.field import Field
|
||||
|
||||
invites = re.compile(
|
||||
r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)",
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
async def autopurge(message):
|
||||
autopurge = Autopurge.get(
|
||||
guild=message.guild.id, channel=message.channel.id
|
||||
)
|
||||
if autopurge:
|
||||
await message.delete(delay=autopurge.delay)
|
||||
|
||||
|
||||
async def autoreact(message):
|
||||
autoreact = Autoreact.get(
|
||||
guild=message.guild.id,
|
||||
channel=message.channel.id,
|
||||
)
|
||||
if autoreact:
|
||||
for reaction in autoreact.reactions:
|
||||
await message.add_reaction(reaction)
|
||||
|
||||
|
||||
async def checks(message):
|
||||
# #tech
|
||||
channel = find(
|
||||
lambda x: x.id == 599068193339736096, message.channel_mentions
|
||||
)
|
||||
if channel and message.author.id == 293795462752894976:
|
||||
await channel.send(
|
||||
content="https://cdn.discordapp.com/attachments/"
|
||||
+ "664621130044407838/805218508866453554/tech.gif"
|
||||
)
|
||||
content = re.sub(r"\s+", "", message.content)
|
||||
match = invites.search(content)
|
||||
if match:
|
||||
guild_invites = await message.guild.invites()
|
||||
allowed = [x.code for x in guild_invites] + [
|
||||
"dbrand",
|
||||
"VtgZntXcnZ",
|
||||
]
|
||||
if match.group(1) not in allowed:
|
||||
await message.delete()
|
||||
warning = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Sent an invite link",
|
||||
user=message.author.id,
|
||||
)
|
||||
warning.insert()
|
||||
fields = [
|
||||
Field(
|
||||
"Reason",
|
||||
"Sent an invite link",
|
||||
False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick
|
||||
if message.author.nick
|
||||
else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#"
|
||||
+ f"{message.author.discriminator} "
|
||||
+ f"| {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
|
||||
async def massmention(message):
|
||||
massmention = Setting.get(
|
||||
guild=message.guild.id,
|
||||
setting="massmention",
|
||||
)
|
||||
if (
|
||||
massmention.value > 0
|
||||
and len(message.mentions)
|
||||
- (1 if message.author in message.mentions else 0)
|
||||
> massmention.value
|
||||
):
|
||||
warning = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Mass Mention",
|
||||
user=message.author.id,
|
||||
)
|
||||
warning.insert()
|
||||
fields = [Field("Reason", "Mass Mention", False)]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick
|
||||
if message.author.nick
|
||||
else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} "
|
||||
+ f"| {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
|
||||
async def roleping(message):
|
||||
roleping = Setting.get(guild=message.guild.id, setting="roleping")
|
||||
roles = []
|
||||
for mention in message.role_mentions:
|
||||
roles.append(mention.id)
|
||||
for mention in message.mentions:
|
||||
for role in mention.roles:
|
||||
roles.append(role.id)
|
||||
if (
|
||||
roleping
|
||||
and any(x in roleping.value for x in roles)
|
||||
and not any(x.id in roleping.value for x in message.author.roles)
|
||||
):
|
||||
warning = Warning(
|
||||
active=True,
|
||||
admin=get_config().client_id,
|
||||
duration=24,
|
||||
guild=message.guild.id,
|
||||
reason="Pinged a blocked role/user with a blocked role",
|
||||
user=message.author.id,
|
||||
)
|
||||
warning.insert()
|
||||
fields = [
|
||||
Field(
|
||||
"Reason",
|
||||
"Pinged a blocked role/user with a blocked role",
|
||||
False,
|
||||
)
|
||||
]
|
||||
embed = build_embed(
|
||||
title="Warning",
|
||||
description=f"{message.author.mention} has been warned",
|
||||
fields=fields,
|
||||
)
|
||||
embed.set_author(
|
||||
name=message.author.nick
|
||||
if message.author.nick
|
||||
else message.author.name,
|
||||
icon_url=message.author.avatar_url,
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"{message.author.name}#{message.author.discriminator} "
|
||||
+ f"| {message.author.id}"
|
||||
)
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
|
||||
@jarvis.event
|
||||
async def on_message(message: Message):
|
||||
if (
|
||||
not isinstance(message.channel, DMChannel)
|
||||
and message.author.id != jarvis.user.id
|
||||
):
|
||||
await autoreact(message)
|
||||
await massmention(message)
|
||||
await roleping(message)
|
||||
await autopurge(message)
|
||||
await checks(message)
|
||||
await jarvis.process_commands(message)
|
8
jarvis/tasks/__init__.py
Normal file
8
jarvis/tasks/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from jarvis.tasks import unban, unlock, unmute, unwarn
|
||||
|
||||
|
||||
def init():
|
||||
unban.unban.start()
|
||||
unlock.unlock.start()
|
||||
unmute.unmute.start()
|
||||
unwarn.unwarn.start()
|
34
jarvis/tasks/unban.py
Normal file
34
jarvis/tasks/unban.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import pymongo
|
||||
from discord.ext.tasks import loop
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.types import Ban
|
||||
|
||||
|
||||
@loop(minutes=10)
|
||||
async def unban():
|
||||
bans = Ban.get_active(type="temp")
|
||||
updates = []
|
||||
for ban in bans:
|
||||
if ban.created_at + timedelta(
|
||||
hours=ban.duration
|
||||
) < datetime.utcnow() + timedelta(minutes=10):
|
||||
guild = await jarvis.jarvis.fetch_guild(ban.guild)
|
||||
user = await jarvis.jarvis.fetch_user(ban.user)
|
||||
if user:
|
||||
guild.unban(user)
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{
|
||||
"user": user.id,
|
||||
"guild": guild.id,
|
||||
"created_at": ban.created_at,
|
||||
"type": "temp",
|
||||
},
|
||||
{"$set": {"active": False}},
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis.jarvis_db.bans.bulk_write(updates)
|
40
jarvis/tasks/unlock.py
Normal file
40
jarvis/tasks/unlock.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import pymongo
|
||||
from discord.ext.tasks import loop
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.types import Lock
|
||||
|
||||
|
||||
@loop(minutes=1)
|
||||
async def unlock():
|
||||
locks = Lock.get_active()
|
||||
updates = []
|
||||
for lock in locks:
|
||||
if (
|
||||
lock.created_at + timedelta(minutes=lock.duration)
|
||||
< datetime.utcnow()
|
||||
):
|
||||
guild = await jarvis.jarvis.fetch_guild(lock.guild)
|
||||
channel = await jarvis.jarvis.fetch_channel(lock.channel)
|
||||
if channel:
|
||||
roles = await guild.fetch_roles()
|
||||
for role in roles:
|
||||
overrides = channel.overwrites_for(role)
|
||||
overrides.send_messages = None
|
||||
await channel.set_permissions(
|
||||
role, overwrite=overrides, reason="Lock expired"
|
||||
)
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{
|
||||
"channel": channel.id,
|
||||
"guild": guild.id,
|
||||
"created_at": lock.created_at,
|
||||
},
|
||||
{"$set": {"active": False}},
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis.jarvis_db.locks.bulk_write(updates)
|
42
jarvis/tasks/unmute.py
Normal file
42
jarvis/tasks/unmute.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import pymongo
|
||||
from discord.ext.tasks import loop
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.types import Mute, Setting
|
||||
|
||||
|
||||
@loop(minutes=1)
|
||||
async def unmute():
|
||||
mutes = Mute.get_active(duration={"$gt": 0})
|
||||
mute_roles = Setting.get_many(setting="mute")
|
||||
updates = []
|
||||
for mute in mutes:
|
||||
if (
|
||||
mute.created_at + timedelta(minutes=mute.duration)
|
||||
< datetime.utcnow()
|
||||
):
|
||||
mute_role = [x.value for x in mute_roles if x.guild == mute.guild][
|
||||
0
|
||||
]
|
||||
guild = await jarvis.jarvis.fetch_guild(mute.guild)
|
||||
role = guild.get_role(mute_role)
|
||||
user = await guild.fetch_member(mute.user)
|
||||
if user:
|
||||
if role in user.roles:
|
||||
await user.remove_roles(role, reason="Mute expired")
|
||||
|
||||
# Objects can't handle bulk_write, so handle it via raw methods
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{
|
||||
"user": user.id,
|
||||
"guild": guild.id,
|
||||
"created_at": mute.created_at,
|
||||
},
|
||||
{"$set": {"active": False}},
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis.jarvis_db.mutes.bulk_write(updates)
|
25
jarvis/tasks/unwarn.py
Normal file
25
jarvis/tasks/unwarn.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import pymongo
|
||||
from discord.ext.tasks import loop
|
||||
|
||||
import jarvis
|
||||
from jarvis.db.types import Warning
|
||||
|
||||
|
||||
@loop(hours=1)
|
||||
async def unwarn():
|
||||
warns = Warning.get_active()
|
||||
updates = []
|
||||
for warn in warns:
|
||||
if (
|
||||
warn.created_at + timedelta(hours=warn.duration)
|
||||
< datetime.utcnow()
|
||||
):
|
||||
updates.append(
|
||||
pymongo.UpdateOne(
|
||||
{"_id": warn._id}, {"$set": {"active": False}}
|
||||
)
|
||||
)
|
||||
if updates:
|
||||
jarvis.jarvis_db.warns.bulk_write(updates)
|
|
@ -9,7 +9,7 @@ import jarvis.cogs
|
|||
import jarvis.config
|
||||
import jarvis.db
|
||||
|
||||
__all__ = ["field", "db"]
|
||||
__all__ = ["field", "db", "cachecog", "permissions"]
|
||||
|
||||
|
||||
def convert_bytesize(bytes: int) -> str:
|
||||
|
|
33
jarvis/utils/cachecog.py
Normal file
33
jarvis/utils/cachecog.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from discord.ext import commands
|
||||
from discord.ext.tasks import loop
|
||||
from discord.utils import find
|
||||
from discord_slash import SlashContext
|
||||
|
||||
|
||||
class CacheCog(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
self.cache = {}
|
||||
self._expire_interaction.start()
|
||||
|
||||
def check_cache(self, ctx: SlashContext, **kwargs):
|
||||
if not kwargs:
|
||||
kwargs = {}
|
||||
return find(
|
||||
lambda x: x["command"] == ctx.subcommand_name
|
||||
and x["user"] == ctx.author.id
|
||||
and x["guild"] == ctx.guild.id
|
||||
and all(x[k] == v for k, v in kwargs.items()),
|
||||
self.cache.values(),
|
||||
)
|
||||
|
||||
@loop(minutes=1)
|
||||
async def _expire_interaction(self):
|
||||
keys = list(self.cache.keys())
|
||||
for key in keys:
|
||||
if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(
|
||||
minutes=1
|
||||
):
|
||||
del self.cache[key]
|
|
@ -2,6 +2,10 @@ discord-py>=1.7, <2
|
|||
psutil>=5.8, <6
|
||||
GitPython>=3.1, <4
|
||||
PyYaml>=5.4, <6
|
||||
discord-py-slash-command>=2.3, <3
|
||||
discord-py-slash-command>=2.3.2, <3
|
||||
pymongo>=3.12.0, <4
|
||||
opencv-python>=4.5, <5
|
||||
ButtonPaginator>=0.0.3
|
||||
Pillow>=8.2.0, <9
|
||||
python-gitlab>=2.9.0, <3
|
||||
ulid-py>=1.1.0, <2
|
||||
|
|
Loading…
Add table
Reference in a new issue