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 check handler #29

Merged
merged 18 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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
2 changes: 1 addition & 1 deletion otter_welcome_buddy/cogs/hiring_timelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def __init__(self, bot: Bot, messages_formatter: type[timeline.Formatter]):
self.bot: Bot = bot
self.messages_formatter: type[timeline.Formatter] = messages_formatter
self.scheduler: AsyncIOScheduler = AsyncIOScheduler()

self.__configure_scheduler()

@commands.group(
Expand All @@ -47,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:
akotadi marked this conversation as resolved.
Show resolved Hide resolved
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