diff --git a/README.md b/README.md index 35cdd6c..865fc00 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,20 @@ cp .env.example .env Then add in your tokens for discord and github. be carefull to not push those tokens to github. -### add the service +### Deploy -```bash -systemctl enable orderbot.service -systemctl start orderbot.service -``` + + +## Usage + +Once the bot is running, it will listen to discord channels. +When the :thread: reaction is added to a message, it will create a new thread on discord and a new issue on github. The issue number will be added to the thread name. +Then, when a message is sent in the thread, it will be added as a comment to the issue, this way, the whole conversation is kept in the issue. + +*Note: for older threads (before the bot was deployed), you can rename the thread to add the issue number at the end of the name. Only the new messages will be added as comments to the issue.* + +### Commands (only available to admins) + +- `!ping` : pong. +- `!close` : close the thread and the issue. +- `!help` : display the available commands. diff --git a/orderbot/__main__.py b/orderbot/__main__.py index 4a645d2..d893ac1 100644 --- a/orderbot/__main__.py +++ b/orderbot/__main__.py @@ -1,5 +1,6 @@ import logging import os +import sys from dotenv import load_dotenv @@ -9,6 +10,7 @@ logging.basicConfig( handlers=[ logging.FileHandler("orderbot.log", mode="w"), + logging.StreamHandler(sys.stdout) ], format='[%(asctime)s][%(levelname)s][%(message)s]', datefmt='%d-%b-%y %H:%M:%S', diff --git a/orderbot/src/discord_bot.py b/orderbot/src/discord_bot.py index ebaca15..c9d8888 100644 --- a/orderbot/src/discord_bot.py +++ b/orderbot/src/discord_bot.py @@ -6,7 +6,9 @@ class DiscordBot(discord.Client): COMMANDS = { - "!ping": lambda message, args: message.channel.send("Pong!"), + "!ping": lambda self, message: self.on_ping(message), + "!close": lambda self, message: self.on_close(message), + "!help": lambda self, message: self.on_help(message), } def __init__(self, token, github_bot: GithubBot): @@ -20,22 +22,49 @@ def __init__(self, token, github_bot: GithubBot): def run(self): super().run(self.token) - def repr_message(self, message): - return f"Message: {message.content} from {message.author} in {message.channel} at {message.created_at} with {message.reactions} reactions" + def repr_message(self, message: discord.Message): + # remove emojis + return f"Message: {message.content} from {message.author} in {message.channel} at {message.created_at} with {message.reactions} reactions".encode("ascii", "ignore").decode() - async def create_thread_issue(self, message): + async def on_ping(self, message: discord.Message): + message.channel.send("Pong!") + + async def on_close(self, message: discord.Message): + thread = message.channel + assert type(thread) == discord.Thread + + # try to get issue number from thread name + issue_number = thread.name.split("#")[-1] + if issue_number.isnumeric(): + issue_number = int(issue_number) + await thread.send(f"Closing issue #{issue_number}") + await self.github_bot.add_issue_comment(issue_number, f"Issue closed by {message.author}") + await self.github_bot.close_issue(issue_number) + + else: + await thread.send("Could not find issue number in thread name") + logging.error(f"Could not find issue number in thread name {thread.name}") + + # archive thread + await thread.send("Closing thread") + await thread.edit(archived=True, locked=True) + + async def on_help(self, message): + message.channel.send("Commands: " + ", ".join(self.COMMANDS.keys())) + + async def on_ready(self, *args, **kwargs): + logging.info(f"We have logged in as {self.user}") + + async def create_thread_issue(self, message: discord.Message): # create a new issue on github issue = await self.github_bot.create_issue(f"{message.channel.name} - {message.author.display_name}", f"[{message.author}]" + message.content, os.getenv("GITHUB_PROJECT_NUMBER")) # create thread issue_number = issue["createIssue"]["issue"]["number"] thread = await message.create_thread(name=f"{message.author.display_name} #{issue.number}") - # await thread.send("Issue created, please wait for a staff member to respond") - - logging.info(f"Created issue #{issue_number} for {message.author} in {message.channel.name}") - async def on_ready(self, *args, **kwargs): - logging.info(f"We have logged in as {self.user}") + logging.info( + f"Created issue #{issue_number} for {message.author} in {message.channel.name}") async def on_message(self, message: discord.Message): logging.debug(self.repr_message(message)) @@ -44,36 +73,33 @@ async def on_message(self, message: discord.Message): if message.author == self.user: return - # only reply to authorized users (to be defined later) - if "Bureau" not in [r.name for r in message.author.roles]: - return + channel_name = message.channel.name # check if the message is a command if message.content.startswith("!"): + + # only reply to authorized users (to be defined later) + if "Bureau" not in [r.name for r in message.author.roles]: + await message.channel.send("You are not authorized to use this command") + return + # split the message into command and arguments command = message.content.split(" ")[0] args = message.content.split(" ")[1:] - channel = message.channel.name # check if the command is valid if command in DiscordBot.COMMANDS: # execute the command - await DiscordBot.COMMANDS[command](message, args) + await DiscordBot.COMMANDS[command](self, message, *args) else: # send an error message await message.channel.send("Invalid command") - channel = message.channel.name - if "#" in channel: - issue_number = int(channel.split("#")[-1]) + elif "#" in channel_name: + issue_number = int(channel_name.split("#")[-1]) await self.github_bot.add_issue_comment(issue_number, f"[{message.author}] - {message.content}") - # on reaction - async def on_message_edit(self, before, after): - logging.debug(f"Message edited: {before} -> {after}") - await self.on_message(after) - async def on_raw_reaction_add(self, payload): reaction = payload.emoji channel = self.get_channel(payload.channel_id) diff --git a/orderbot/src/github_bot.py b/orderbot/src/github_bot.py index eb9eeb1..b7c1b43 100644 --- a/orderbot/src/github_bot.py +++ b/orderbot/src/github_bot.py @@ -209,7 +209,7 @@ async def add_issue_comment(self, number, body): issue = await self.fetch_issue_by_number(self.repo, number) issue_id = issue["organization"]["repository"]["issue"]["id"] except KeyError: - logging.error("Issue not found") + logging.error(f"Issue {number} not found") return # add the comment to the issue @@ -232,4 +232,33 @@ async def add_issue_comment(self, number, body): ) logging.info(f"Added Github comment: {body} in issue {number}") - return comment \ No newline at end of file + return comment + + async def close_issue(self, number): + try: + issue = await self.fetch_issue_by_number(self.repo, number) + issue_id = issue["organization"]["repository"]["issue"]["id"] + except KeyError: + logging.error(f"Issue {number} not found") + return + + # close the issue + query = gql( + """ + mutation ($issueId: ID!) { + closeIssue(input: {issueId: $issueId}) { + clientMutationId + } + } + """ + ) + + close = await self.client.execute_async( + query, + variable_values={ + "issueId": issue_id, + } + ) + + logging.info(f"Closed issue {number}") + return close \ No newline at end of file