Skip to content

Commit

Permalink
Add reaction check handler (#29)
Browse files Browse the repository at this point in the history
* Update pre-commit and fix issues

* Fix issues

* Remove eof

* Update pre-commit and address comments

* Add readme env variables

* Linter

* Add role when react to welcome message

* Update readme

* Add tests

* Add test

* Update description

* Address comments

* Update pytest version

* Create settings file

* Add comment
  • Loading branch information
akotadi committed Dec 29, 2023
1 parent 022e176 commit 761bc38
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 34 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ You need to install all the prerequisites before following with the instalation
You need the following environmental variables either in a `.env` file under the root directory of this repository or directly added at your system (or your Docker instance):

* `DISCORD_TOKEN`: the Discord Bot Token retrieved from the [developer page](https://discord.com/developers/applications).
* `WELCOME_MESSAGES`: [`message ids`](https://discordpy.readthedocs.io/en/stable/api.html?highlight=message%20id#discord.Message.id) separated by `,` that give `OTTER_ROLE` when reacted to.


<!-- USAGE EXAMPLES -->
Expand Down
3 changes: 2 additions & 1 deletion otter_welcome_buddy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os

from discord.ext.commands import Bot
from discord.ext.commands import when_mentioned_or
from dotenv import load_dotenv

from otter_welcome_buddy.common.constants import COMMAND_PREFIX
Expand All @@ -15,7 +16,7 @@ async def main() -> None:
"""Principal function to be called by Docker"""

bot: Bot = Bot(
command_prefix=COMMAND_PREFIX,
command_prefix=when_mentioned_or(COMMAND_PREFIX),
intents=intents.get_registered_intents(),
)

Expand Down
1 change: 0 additions & 1 deletion otter_welcome_buddy/cogs/hiring_timelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ async def stop(self, _: Context) -> None:
"""Command to interact with the bot and stop cron"""
self.scheduler.stop()


def __configure_scheduler(self) -> None:
"""Configure and start scheduler"""
self.scheduler.add_job(
Expand Down
27 changes: 24 additions & 3 deletions otter_welcome_buddy/cogs/new_user_joins.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from discord.ext.commands import Bot
from discord.ext.commands import Context

from otter_welcome_buddy.common.constants import OTTER_ROLE
from otter_welcome_buddy.formatters import debug
from otter_welcome_buddy.formatters import messages
from otter_welcome_buddy.settings import WELCOME_MESSAGES


class Greetings(commands.Cog):
Expand All @@ -29,9 +31,28 @@ async def on_ready(self) -> None:
print(self.debug_formatter.bot_is_ready())

@commands.Cog.listener()
async def on_member_join(self, member: discord.Member) -> None:
"""Send Welcome message to new member"""
await member.send(self.messages_formatter.welcome_message())
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None:
"""Event fired when a user react to the welcome message, giving the entry role to him"""
# Check if the user to add the role is valid
if payload.member is None:
print(f"Missing member to add role in {__name__}")
return
if not WELCOME_MESSAGES or str(payload.message_id) in WELCOME_MESSAGES:
try:
guild = next(guild for guild in self.bot.guilds if guild.id == payload.guild_id)
member_role = discord.utils.get(guild.roles, name=OTTER_ROLE)
if member_role is None:
print(f"Not role found in {__name__} for guild {guild.name}")
return
await discord.Member.add_roles(payload.member, member_role)
except StopIteration:
print(f"Not guild found in {__name__}")
except discord.Forbidden:
print(f"Not permissions to add the role in {__name__}")
except discord.HTTPException:
print(f"Adding roles failed in {__name__}")
except Exception:
print(f"Exception in {__name__}")

@commands.command()
async def hello(self, ctx: Context) -> None:
Expand Down
8 changes: 8 additions & 0 deletions otter_welcome_buddy/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ class CronExpressions(Enum):


COMMAND_PREFIX: str = "!"

# Discord role that give access to admin role based commands
OTTER_ADMIN = "ROOT"
# Discord role that give access to moderator role based commands
OTTER_MODERATOR = "Collaborator"
# Discord role that give access to the remaining channels and is
# given when the user react to WELCOME_MESSAGES
OTTER_ROLE = "Interviewee"
8 changes: 8 additions & 0 deletions otter_welcome_buddy/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from os import getenv

from dotenv import load_dotenv

load_dotenv()


WELCOME_MESSAGES = getenv("WELCOME_MESSAGES", "").split(",")
5 changes: 3 additions & 2 deletions otter_welcome_buddy/startup/cogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from discord.ext.commands import Bot

from otter_welcome_buddy.cogs import hiring_timelines # , new_user_joins
from otter_welcome_buddy.cogs import hiring_timelines
from otter_welcome_buddy.cogs import new_user_joins


def __format_module_path_into_cog_extension(absolute_module_path: str) -> str:
Expand All @@ -15,7 +16,7 @@ def __format_module_path_into_cog_extension(absolute_module_path: str) -> str:
async def register_cogs(bot: Bot) -> None:
"""Registers all the allowed cogs for the bot"""
allowed_cogs: list[ModuleType] = [
# new_user_joins,
new_user_joins,
hiring_timelines,
]

Expand Down
1 change: 1 addition & 0 deletions otter_welcome_buddy/startup/intents.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ def get_registered_intents() -> Intents:
intents: Intents = Intents.default()
intents.message_content = True # Send DMs
intents.members = True # Detect new members
intents.reactions = True # Process reactions
return intents
39 changes: 21 additions & 18 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ APScheduler = "^3.9.1"
python-dotenv = "^0.21.0"

[tool.poetry.dev-dependencies]
pytest = "^7.1.3"
pytest = "^7.2.0"
black = "^22.8.0"
flake8 = "^4.0.1"
pre-commit = "^2.20.0"
pytest-asyncio = "^0.19.0"
pytest-mock = "^3.10.0"

[tool.pylint."MESSAGES CONTROL"]
disable = ["E0401", "C0114", "R0903"]
disable = ["E0401", "C0114", "R0903", "W0718"]

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
48 changes: 43 additions & 5 deletions tests/cogs/test_new_user_join.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock
from unittest.mock import patch

import pytest
from discord import Guild
from discord import Member
from discord import PartialEmoji
from discord import RawReactionActionEvent
from discord import Role
from discord.ext.commands import Bot
from pytest_mock import MockFixture

from otter_welcome_buddy.cogs import new_user_joins

if TYPE_CHECKING:
from discord.types.gateway import MessageReactionAddEvent


@pytest.mark.asyncio
async def test_cogSetup_registerCommand(mock_bot, mock_msg_fmt, mock_debug_fmt):
async def test_cogSetup_registerCommand(mock_bot):
# Arrange
mock_bot.add_cog = AsyncMock()

Expand All @@ -30,16 +42,42 @@ async def test_onReady_printMessage(mock_bot, mock_msg_fmt, mock_debug_fmt):


@pytest.mark.asyncio
async def test_onMemberJoins_sendMessage(mock_bot, mock_msg_fmt, mock_debug_fmt):
@patch("otter_welcome_buddy.cogs.new_user_joins.WELCOME_MESSAGES", new=["111"])
async def test_onRawReactionAdd_addRole(
mocker: MockFixture,
mock_bot: Bot,
mock_guild: Guild,
mock_member: Member,
mock_role: Role,
mock_msg_fmt,
mock_debug_fmt,
):
# Arrange
mock_member = AsyncMock()
mock_guild.id = 111
mock_bot.guilds = [mock_guild]
cog = new_user_joins.Greetings(mock_bot, mock_msg_fmt, mock_debug_fmt)
data: MessageReactionAddEvent = {
"user_id": 111,
"channel_id": 111,
"message_id": 111,
"guild_id": 111,
}
payload = RawReactionActionEvent(
data=data,
emoji=PartialEmoji(name="😀"),
event_type="REACTION_ADD",
)
payload.member = mock_member

mock_get_role = mocker.patch("discord.utils.get", return_value=mock_role)
mock_add_roles = mocker.patch.object(Member, "add_roles")

# Act
await cog.on_member_join(mock_member)
await cog.on_raw_reaction_add(payload)

# Assert
assert mock_msg_fmt.welcome_message.called
mock_get_role.assert_called_once()
mock_add_roles.assert_called_once()


def test_commandMessage_correctMessage(mock_bot, mock_msg_fmt, mock_debug_fmt):
Expand Down
25 changes: 24 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import Mock

import pytest
from discord import Guild
from discord import Member
from discord import Role
from discord.ext.commands import Bot


@pytest.fixture
def mock_bot():
def mock_bot() -> Bot:
mock_bot = AsyncMock()
return mock_bot


@pytest.fixture
def mock_guild() -> Guild:
mock_guild = Mock()
return mock_guild


@pytest.fixture
def mock_member() -> Member:
mock_member = Mock()
return mock_member


@pytest.fixture
def mock_role() -> Role:
mock_role = Mock()
return mock_role


@pytest.fixture
def mock_msg_fmt():
mock_msg_fmt = MagicMock()
Expand Down
2 changes: 1 addition & 1 deletion tests/startup/test_cogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ async def test_loadExtensions_registerCogs():
await cogs.register_cogs(mock_bot)

# Assert
assert mock_bot.load_extension.call_count == 1
assert mock_bot.load_extension.call_count == 2
1 change: 1 addition & 0 deletions tests/startup/test_intents.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ def test_getRegisteredIntents_messageNMembers():
# Assert
assert registered_intents.messages is True
assert registered_intents.members is True
assert registered_intents.reactions is True

0 comments on commit 761bc38

Please sign in to comment.