diff --git a/jarvis/__init__.py b/jarvis/__init__.py index b67872e..5cc6493 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -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,11 +18,6 @@ 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 @@ -50,6 +30,8 @@ __version__ = "1.6.0" db = DBManager(get_config().mongo).mongo jarvis_db = db.jarvis +logo = logo.get_logo(get_config().logo) + @jarvis.event async def on_ready(): @@ -74,307 +56,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 +69,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) diff --git a/jarvis/events/guild.py b/jarvis/events/guild.py new file mode 100644 index 0000000..15528ce --- /dev/null +++ b/jarvis/events/guild.py @@ -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") diff --git a/jarvis/events/member.py b/jarvis/events/member.py new file mode 100644 index 0000000..d706f62 --- /dev/null +++ b/jarvis/events/member.py @@ -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") diff --git a/jarvis/events/message.py b/jarvis/events/message.py new file mode 100644 index 0000000..d148b57 --- /dev/null +++ b/jarvis/events/message.py @@ -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) diff --git a/jarvis/tasks/__init__.py b/jarvis/tasks/__init__.py new file mode 100644 index 0000000..bbde0a8 --- /dev/null +++ b/jarvis/tasks/__init__.py @@ -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() diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py new file mode 100644 index 0000000..fb3524c --- /dev/null +++ b/jarvis/tasks/unban.py @@ -0,0 +1,34 @@ +from datetime import datetime, timedelta + +import pymongo +from discord.ext.tasks import loop + +from jarvis import jarvis, jarvis_db +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.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) diff --git a/jarvis/tasks/unlock.py b/jarvis/tasks/unlock.py new file mode 100644 index 0000000..536a27b --- /dev/null +++ b/jarvis/tasks/unlock.py @@ -0,0 +1,40 @@ +from datetime import datetime, timedelta + +import pymongo +from discord.ext.tasks import loop + +from jarvis import jarvis, jarvis_db +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.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) diff --git a/jarvis/tasks/unmute.py b/jarvis/tasks/unmute.py new file mode 100644 index 0000000..4070e66 --- /dev/null +++ b/jarvis/tasks/unmute.py @@ -0,0 +1,42 @@ +from datetime import datetime, timedelta + +import pymongo +from discord.ext.tasks import loop + +from jarvis import jarvis, jarvis_db +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.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) diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py new file mode 100644 index 0000000..50a29be --- /dev/null +++ b/jarvis/tasks/unwarn.py @@ -0,0 +1,25 @@ +from datetime import datetime, timedelta + +import pymongo +from discord.ext.tasks import loop + +from jarvis import jarvis_db +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_db.warns.bulk_write(updates)