Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reaction role functionality #105

Merged
merged 5 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]
python-version: ["3.13"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -18,6 +18,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r ./requirements.txt
pip install pylint
cp base_configs.py configs.py
- name: Analysing the code with pylint
run: |
Expand Down
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ max-line-length=120
min-public-methods=0

[MESSAGES CONTROL]
# Disable the message "C0114: Missing module docstring"
# Disabled the message "C0114: Missing module docstring"
# Disabled the message "W0511: fixme"
# Disabled the message "R0913: Too many arguments"
# Disabled the message "W0212: Access to a protected member _ of a client class"
Expand Down
10 changes: 8 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"python.analysis.importFormat": "absolute",
"editor.defaultFormatter": "ms-python.black-formatter",
"files.trimTrailingWhitespace": true,
"editor.formatOnSave": true,
"black-formatter.args": ["--line-length", "120", "--target-version", "py311"]

"isort.args": ["--profile", "black"],
"python.analysis.importFormat": "absolute",
"editor.codeActionsOnSave": {
"source.organizeImports": "always",
"source.fixAll": "always"
}
}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ A discord bot for managing your server

### Requirements

- Python 3.10+
- MongoDB 5.0+
- Python 3.13+
- MongoDB 8.0+
- Dependencies (`pip3 install -r requirements.txt`)

### Setting up the bot
Expand Down Expand Up @@ -46,4 +46,4 @@ mongo> db.createUser({user: "DB_USER", pwd: "DB_PWD", roles: [{role: "root", db:

### Using python

> `python3.10 run.py`
> `python3 run.py`
1 change: 1 addition & 0 deletions bot/botcommands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from .bot_configs import BotConfigsCog
from .member import MemberCog
from .moderation import ModerationCog
from .reaction_role import ReactionRoleCog
3 changes: 2 additions & 1 deletion bot/botcommands/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from bot import tickets, util, welcome
from bot.botcommands.utils.validators import has_at_least_role
from bot.db.models.user import AdeptMember
from bot.db.services.user_service import UserService
from bot.db.services import ReactionRoleService, UserService
from bot.interactions import TicketOpeningInteraction
from bot.interactions import ticket as ticket_interactions
from bot.util import AdeptBotException
Expand All @@ -19,6 +19,7 @@ class MemberCog(commands.Cog):

def __init__(self) -> None:
self.user_service = UserService()
self.reaction_role_service = ReactionRoleService()

@commands.command()
async def ticket(self, ctx: Context, ticket: tickets.TicketConverter):
Expand Down
1 change: 0 additions & 1 deletion bot/botcommands/moderation.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ def __init__(
author: discord.Member,
reason: str,
parsed_time: ParsedTime = None,
/,
):
self.strike = strike
self.target = target
Expand Down
79 changes: 79 additions & 0 deletions bot/botcommands/reaction_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""This module contains the commands related to the members of the server."""

import discord
from discord.ext import commands
from discord.ext.commands.context import Context

import configs
from bot.botcommands.utils.validators import has_at_least_role
from bot.db.services.reaction_role_service import ReactionRoleService
from bot.util import AdeptBotException


class ReactionRoleCog(commands.Cog):
"""This class contains the commands related to the members of the server."""

def __init__(self, bot: discord.Client) -> None:
self.bot = bot
self.reaction_role_service = ReactionRoleService()

@commands.command()
@has_at_least_role(configs.ADMIN_ROLE)
async def addreactionrole(self, ctx: Context, message_id: int, emoji: str, role: discord.Role):
"""
Cette commande permet d'ajouter une réaction à un message et de la lier à un rôle.

Utilisation:
!addreactionrole <message_id> <emoji> <role_id>
"""
message = await ctx.fetch_message(message_id)

if not message or not role:
raise AdeptBotException("Message ou rôle invalide!")

await message.add_reaction(emoji)
await self.reaction_role_service.add_reaction_role(message_id, emoji, role.id)
await ctx.send(f"Réaction {emoji} ajoutée au message {message.jump_url} et liée au rôle {role.name}.")

@commands.command()
@has_at_least_role(configs.ADMIN_ROLE)
async def removereactionrole(self, ctx: Context, message_id: int, emoji: str):
"""
Cette commande permet de retirer une réaction d'un message et de supprimer le lien avec un rôle.

Utilisation:
!removereactionrole <message_id> <emoji>
"""
message = await ctx.fetch_message(message_id)

if not message:
raise AdeptBotException("Message invalide!")

await message.clear_reaction(emoji)
await self.reaction_role_service.remove_reaction_role(message_id, emoji)
await ctx.send(f"Réaction {emoji} retirée du message {message.jump_url}.")

@commands.Cog.listener()
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
"""This event is called when a reaction is added to a message."""
if payload.member.bot:
return

reaction_role = await self.reaction_role_service.get_reaction_role(payload.message_id, str(payload.emoji))
if reaction_role:
guild = self.bot.get_guild(payload.guild_id)
role = guild.get_role(reaction_role.role_id)
await payload.member.add_roles(role)

@commands.Cog.listener()
async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent):
"""This event is called when a reaction is removed from a message."""
guild = self.bot.get_guild(payload.guild_id)
member = guild.get_member(payload.user_id)
if member.bot:
return

reaction_role = await self.reaction_role_service.get_reaction_role(payload.message_id, str(payload.emoji))
if reaction_role:
role = guild.get_role(reaction_role.role_id)
await member.remove_roles(role)
1 change: 1 addition & 0 deletions bot/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

from .bot_configs import GlobalConfig, SpamConfigs
from .entity import Entity
from .reaction_role import ReactionRole
from .user import AdeptMember
9 changes: 7 additions & 2 deletions bot/db/models/entity.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Base entity class for all models"""

from datetime import datetime
from datetime import datetime, timezone


class Entity:
Expand All @@ -19,7 +19,12 @@ class Entity:

__slots__ = ("_id", "created_at", "updated_at")

def __init__(self, _id: int, created_at: datetime = datetime.utcnow(), updated_at: datetime = datetime.utcnow()):
def __init__(
self,
_id: int,
created_at: datetime = datetime.now(timezone.utc),
updated_at: datetime = datetime.now(timezone.utc),
):
self._id = _id
self.created_at = created_at
self.updated_at = updated_at
Expand Down
37 changes: 37 additions & 0 deletions bot/db/models/reaction_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""ReactionRole model for linking reactions to roles."""

from bot.db.models.entity import Entity


class ReactionRole(Entity):
"""
ReactionRole model for linking reactions to roles.

Attributes
----------
`message_id` : int
The ID of the message.
`emoji` : str
The emoji used for the reaction.
`role_id` : int
The ID of the role to assign.
"""

__slots__ = ("message_id", "emoji", "role_id")

def __init__(self, _id: int, message_id: int, emoji: str, role_id: int):
super().__init__(_id)
self.message_id = message_id
self.emoji = emoji
self.role_id = role_id

def __getstate__(self):
state = super().__getstate__()
state.update(
{
"message_id": self.message_id,
"emoji": self.emoji,
"role_id": self.role_id,
}
)
return state
2 changes: 1 addition & 1 deletion bot/db/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(
name: str,
email: str,
is_student: bool,
/,
*,
is_teacher: bool = False,
is_it_student: bool = False,
student_id: int = None,
Expand Down
1 change: 1 addition & 0 deletions bot/db/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

from .base_service import BaseService
from .configs_service import ConfigsService
from .reaction_role_service import ReactionRoleService
from .user_service import UserService
73 changes: 73 additions & 0 deletions bot/db/services/reaction_role_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Service class for ReactionRole model."""

from bot.db.models.reaction_role import ReactionRole
from bot.db.services.base_service import BaseService


class ReactionRoleService(BaseService):
"""
Service class for ReactionRole model.

Methods
-------
`add_reaction_role` : None
Add a reaction role to the database.
`remove_reaction_role` : None
Remove a reaction role from the database.
`get_reaction_role` : ReactionRole
Get a reaction role from the database.
"""

@property
def collection_name(self):
return "reaction_roles"

async def add_reaction_role(self, message_id: int, emoji: str, role_id: int):
"""
Add a reaction role to the database.

Parameters
----------
`message_id` : int
The ID of the message.
`emoji` : str
The emoji used for the reaction.
`role_id` : int
The ID of the role to assign.
"""
reaction_role = ReactionRole(None, message_id, emoji, role_id)
self.insert_one(reaction_role.__getstate__())

async def remove_reaction_role(self, message_id: int, emoji: str):
"""
Remove a reaction role from the database.

Parameters
----------
`message_id` : int
The ID of the message.
`emoji` : str
The emoji used for the reaction.
"""
self.delete_one({"message_id": message_id, "emoji": emoji})

async def get_reaction_role(self, message_id: int, emoji: str) -> ReactionRole | None:
"""
Get a reaction role from the database.

Parameters
----------
`message_id` : int
The ID of the message.
`emoji` : str
The emoji used for the reaction.

Returns
-------
ReactionRole
The reaction role.
"""
result = self.find_one({"message_id": message_id, "emoji": emoji})
if result:
return ReactionRole(result["_id"], result["message_id"], result["emoji"], result["role_id"])
return None
5 changes: 5 additions & 0 deletions bot/management/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@

import configs
from bot import util
from bot.db.services.reaction_role_service import ReactionRoleService


class LoggingCog(commands.Cog):
"""This class contains the events related to logging."""

def __init__(self, bot: discord.Client):
self.reaction_role_service = ReactionRoleService()
self.bot = bot

@commands.Cog.listener()
async def on_message_edit(self, before: discord.Message, after: discord.Message):
"""This event is called when a message is edited."""
Expand Down
7 changes: 4 additions & 3 deletions bot/management/welcome.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
from discord.ext import commands

import configs
from bot import util, welcome
from bot import welcome
from bot.db.services import UserService


class WelcomeCog(commands.Cog):
"""This class contains the events related to welcome."""

def __init__(self) -> None:
def __init__(self, bot: discord.Client) -> None:
self.user_service = UserService()
self.bot = bot

@commands.command()
@commands.guild_only()
Expand All @@ -38,7 +39,7 @@ async def on_member_join(self, member: discord.Member):
if adept_member:
return await welcome.process_welcome_result(member, adept_member)

await util.say(configs.WELCOME_CHANNEL, configs.WELCOME_SERVER.format(name=member.mention))
await self.bot.say(configs.WELCOME_CHANNEL, configs.WELCOME_SERVER.format(name=member.mention))
result = await welcome.walk_through_welcome(member)
if not result:
return
Expand Down
4 changes: 2 additions & 2 deletions bot/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async def process_mutes():
process_mutes.stop()


def load_tasks():
async def load_tasks(bot: discord.Client):
"""
Loads all tasks from the database.

Expand All @@ -102,7 +102,7 @@ def load_tasks():

for task in to_process:
# Just to make the code more readable
member = util.get_member(task["guild"], task["member"])
member = bot.get_guild(task["guild"]).get_member(task["member"])
end_date = datetime.datetime.strptime(task["end_date"], "%Y-%m-%d %H:%M:%S.%f")

TASK_LIST.append(Task(member, end_date, task["type"]))
Expand Down
Loading
Loading