From f5b96a8202f1732bfdf3631d6a581ba263825dc9 Mon Sep 17 00:00:00 2001
From: dnns01 <git@dnns01.de>
Date: Sat, 15 May 2021 21:35:41 +0200
Subject: [PATCH] Add github integration to add issues from discord, similar to
 "Decky"

---
 .env.template  |  7 +++-
 .gitignore     |  1 +
 fernuni_bot.py |  6 +--
 github.py      | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 109 insertions(+), 4 deletions(-)
 create mode 100644 github.py

diff --git a/.env.template b/.env.template
index 32adcbc..6577977 100644
--- a/.env.template
+++ b/.env.template
@@ -2,6 +2,9 @@
 DISCORD_TOKEN=<Bot Token>
 DISCORD_GUILD=<ID of Guild, this Bot should be used at>
 DISCORD_ACTIVITY=<What should be shown, Bot is playing right now>
+DISCORD_GITHUB_USER=<Github username used to create issues>
+DISCORD_GITHUB_TOKEN=<Github personal access token, can be created in Settings > Developer settings > Personal access tokens (repo scope neccessary)>
+DISCORD_GITHUB_ISSUE_URL=<URL of Github API to create Issues in a repo>
 
 # IDs
 DISCORD_OWNER=<ID of Server owner>
@@ -28,6 +31,8 @@ DISCORD_LEARNINGGROUPS_OPEN=<ID of Channel category for open learning groups>
 DISCORD_LEARNINGGROUPS_CLOSE=<ID of Channel category for closed learning groups>
 DISCORD_LEARNINGGROUPS_REQUEST=<ID of Channel category for learning group requests>
 DISCORD_LEARNINGGROUPS_INFO=<ID of Channel category for open learning groups>
+DISCORD_IDEE_CHANNEL=<ID of Channel, where bot ideas can be submitted>
+DISCORD_IDEE_EMOJI=<ID of Idee Emoji, used for reactions>
 
 # JSON Files
 DISCORD_ROLES_FILE=<File name for roles JSON file>
@@ -40,7 +45,7 @@ DISCORD_LEARNINGGROUPS_COURSE_FILE=<File name for leaarning groups courses JSON
 
 # Misc
 DISCORD_DATE_TIME_FORMAT=<Date and time format used for commands like %d.%m.%Y %H:%M>
-
+DISCORD_IDEE_REACT_QTY=<Amount of reactions to a submitted idea, neccessary to create a github issue (amount is including botys own reaction)>
 
 
 
diff --git a/.gitignore b/.gitignore
index 3a140b1..03d424b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,3 +124,4 @@ GitHub.sublime-settings
 /easter.json
 /learninggroups.json
 /courses.json
+/github.json
diff --git a/fernuni_bot.py b/fernuni_bot.py
index 2a62c44..98395c7 100644
--- a/fernuni_bot.py
+++ b/fernuni_bot.py
@@ -1,4 +1,3 @@
-import json
 import os
 
 import discord
@@ -6,11 +5,12 @@ from discord.ext import commands
 from dotenv import load_dotenv
 
 # from welcome_cog import WelcomeCog
-import utils
 from appointments_cog import AppointmentsCog
 from armin import Armin
 from christmas_cog import ChristmasCog
 from easter_cog import EasterCog
+from github import Github
+from help.help import Help
 from learninggroups import LearningGroups
 from links_cog import LinksCog
 from news_cog import NewsCog
@@ -21,7 +21,6 @@ from text_commands_cog import TextCommandsCog
 # from change_log import ChangeLogCog
 from voice_cog import VoiceCog
 from welcome_cog import WelcomeCog
-from help.help import Help
 
 # .env file is necessary in the same directory, that contains several strings.
 load_dotenv()
@@ -53,6 +52,7 @@ bot.add_cog(EasterCog(bot))
 bot.add_cog(Armin(bot))
 bot.add_cog(LearningGroups(bot))
 bot.add_cog(Help(bot))
+bot.add_cog(Github(bot))
 
 
 def get_reaction(reactions):
diff --git a/github.py b/github.py
new file mode 100644
index 0000000..79e3f72
--- /dev/null
+++ b/github.py
@@ -0,0 +1,99 @@
+import base64
+import json
+import os
+
+from aiohttp import ClientSession
+from discord.ext import commands
+
+import utils
+from help.help import help, handle_error, help_category
+
+
+@help_category("github", "Github", "Github Integration in Discord.")
+class Github(commands.Cog):
+    def __init__(self, bot):
+        self.bot = bot
+        self.github_file = "github.json"
+        self.data = self.load()
+
+    def load(self):
+        github_file = open(self.github_file, 'r')
+        return json.load(github_file)
+
+    def save(self):
+        github_file = open(self.github_file, 'w')
+        json.dump(self.data, github_file)
+
+    @help(
+        category="github",
+        syntax="!idee <text>",
+        brief="Stellt eine Idee für Boty zur Abstimmung.",
+        parameters={
+            "text": "Text der Idee.",
+        },
+        description="Mit diesem Kommando kannst du eine Idee für Boty zur Abstimmung einreichen. Sobald genug "
+                    "Reaktionen von anderen Mitgliedern vorhanden sind, wird aus deiner Idee ein Issue in Github "
+                    "erstellt, und sobald möglich kümmert sich jemand darum."
+    )
+    @commands.command(name="idee")
+    async def cmd_idee(self, ctx):
+        if ctx.channel.id == int(os.getenv("DISCORD_IDEE_CHANNEL")):
+            self.data[str(ctx.message.id)] = {"created": False}
+            await ctx.message.add_reaction(self.bot.get_emoji(int(os.getenv("DISCORD_IDEE_EMOJI"))))
+            self.save()
+
+    @help(
+        category="github",
+        syntax="!card <text>",
+        brief="Erstellt einen Issue in Github.",
+        parameters={
+            "text": "Text der Idee.",
+        },
+        description="Mit diesem Kommando kannst du einen Issue in Github anlegen.",
+        mod=True
+    )
+    @commands.command(name="card")
+    @commands.check(utils.is_mod)
+    async def cmd_card(self, ctx):
+        self.data[str(ctx.message.id)] = {"created": False}
+        await self.create_issue(self.data[str(ctx.message.id)], ctx.message)
+        self.save()
+
+    @commands.Cog.listener()
+    async def on_raw_reaction_add(self, payload):
+        if payload.member == self.bot.user:
+            return
+
+        if idea := self.data.get(str(payload.message_id)):
+            if payload.emoji.id == int(os.getenv("DISCORD_IDEE_EMOJI")):
+                channel = await self.bot.fetch_channel(payload.channel_id)
+                message = await channel.fetch_message(payload.message_id)
+                for reaction in message.reactions:
+                    if reaction.emoji.id == int(os.getenv("DISCORD_IDEE_EMOJI")):
+                        if reaction.count >= int(os.getenv("DISCORD_IDEE_REACT_QTY")) and not idea.get("created"):
+                            await self.create_issue(idea, message)
+
+                self.save()
+
+    async def cog_command_error(self, ctx, error):
+        await handle_error(ctx, error)
+
+    async def create_issue(self, idea, message):
+        async with ClientSession() as session:
+            auth = base64.b64encode(
+                f'{os.getenv("DISCORD_GITHUB_USER")}:{os.getenv("DISCORD_GITHUB_TOKEN")}'.encode('utf-8')).decode(
+                "utf-8")
+            headers = {"Authorization": f"Basic {auth}", "Content-Type": "application/json"}
+
+            async with session.post(os.getenv("DISCORD_GITHUB_ISSUE_URL"),
+                                    headers=headers,
+                                    json={'title': message.content[6:]}) as r:
+                if r.status == 201:
+                    js = await r.json()
+
+                    idea["created"] = True
+                    idea["number"] = js["number"]
+                    idea["html_url"] = js["html_url"]
+
+                    await message.reply(
+                        f"Danke <@!{message.author.id}> für deinen Vorschlag. Ich habe für dich gerade folgenden Issue in Github erstellt: {idea['html_url']}")
-- 
GitLab