Skip to content

Commit

Permalink
Merge pull request #46 from Code4GovTech/main
Browse files Browse the repository at this point in the history
Bring development alongside main
  • Loading branch information
KDwevedi authored Nov 30, 2023
2 parents d831f42 + 8ca71c3 commit 4a8f19d
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 22 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
### C4GT Discord Bot
## C4GT Discord Bot

Features
### Running the Bot locally
1. Clone the repository onto your local system
2. [Optional+Recommended] Set up a python [virtual environment](https://docs.python.org/3/library/venv.html#:~:text=Creating%20virtual-,environments,-%C2%B6) and [activate](https://python.land/virtual-environments/virtualenv#Python_venv_activation) it before installing dependencies. A Python venv is an independent collection of python packages and is used for creating replicable dev environments and preventing versioning conflicts.
3. Use the [`pip install -r requirements.txt`](https://learnpython.com/blog/python-requirements-file/#:~:text=document%20and%20exit!-,Installing,-Python%20Packages%20From) command to install all dependencies.
4. Add the requisite `.env` file in the repository root.
5. Run the bot using `python3 main.py` or `python main.py` command in the terminal.

### Reference
[how to build a simple discord bot](https://realpython.com/how-to-make-a-discord-bot-python/)
[discord.py](https://discordpy.readthedocs.io/en/stable/)


### Features
- [ ] Allow tagging a `discordId` to a `githubId` so that contributions can be managed. Everyone coming to the server will be required to register themselves on the bot. Ask every contributor to connect their Github to Discord. The bot will take this publicly available info and add it to the database.
- [ ] Seeing the list of projects and their quick links. Should allow for navigation to projects.
- [ ] Documentation links
Expand Down
39 changes: 39 additions & 0 deletions cogs/listeners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from discord.ext import commands
import discord
from utils.db import SupabaseInterface

class Listeners(commands.Cog):
def __init__(self, bot) -> None:
super().__init__()
self.bot = bot

async def grantVerifiedRole(self, member: discord.Member):
verifiedContributorRoleID = 1123967402175119482
try:
verifiedContributorRole = member.guild.get_role(verifiedContributorRoleID)
if verifiedContributorRole:
if verifiedContributorRole not in member.roles:
await member.add_roles(verifiedContributorRole, reason="Completed Auth and Introduction")
else:
print("Verified Contributor Role not found")
except Exception as e:
print("Exception while granting Role:", e)

async def isAuthenticated(self, memberID: int) -> bool:
if SupabaseInterface("contributors").read("discord_id", memberID):
return True
else:
return False


@commands.Cog.listener("on_message")
async def listenForIntroduction(self, message: discord.Message):
if message.channel.id == 1107343423167541328: #intro channel
if await self.isAuthenticated(message.author.id):
await self.grantVerifiedRole(message.author)
else:
return


async def setup(bot: commands.Bot):
await bot.add_cog(Listeners(bot))
38 changes: 21 additions & 17 deletions cogs/user_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from discord.ext import commands, tasks
import time, csv
from utils.db import SupabaseInterface
from utils.api import GithubAPI

VERIFIED_CONTRIBUTOR_ROLE_ID = 1123967402175119482
NON_CONTRIBUTOR_ROLES = [973852321870118914, 976345770477387788, 973852439054782464]
Expand Down Expand Up @@ -54,34 +53,39 @@ async def create_embed(self):

#This is a Discord View that is a set of UI elements that can be sent together in a message in discord.
#This view send a link to Github Auth through c4gt flask app in the form of a button.
class RegistrationModal(discord.ui.Modal, title="Contributor Registration"):
name = discord.ui.TextInput(label='Name')
class AuthenticationView(discord.ui.View):
def __init__(self, discord_userdata):
super().__init__()
self.timeout = None
button = discord.ui.Button(label='Authenticate Github', style=discord.ButtonStyle.url, url=f'https://github-app.c4gt.samagra.io/authenticate/{discord_userdata}')
self.add_item(button)
self.message = None

class UserHandler(commands.Cog):
def __init__(self, bot) -> None:
self.bot = bot
self.update_contributors.start()
# self.update_contributors.start()



#Executing this command sends a link to Github OAuth App via a Flask Server in the DM channel of the one executing the command
@commands.command(aliases=['join'])
async def join_as_contributor(self, ctx):
#create a direct messaging channel with the one who executed the command
if isinstance(ctx.channel, discord.DMChannel):
userdata = str(ctx.author.id)
view = AuthenticationView(userdata)
await ctx.send("Please authenticate your github account to register in the C4GT Community", view=view)
# Command logic for DMs
else:
# Command logic for other channels (e.g., servers, groups)
await ctx.send("Please use this command in Bot DMs.")
# Command logic for DMs
userdata = str(ctx.author.id)
view = AuthenticationView(userdata)
# await dmchannel.send("Please authenticate your github account to register for Code for GovTech 2023", view=view)
# @commands.command(aliases=['join'])
# async def join_as_contributor(self, ctx):
# #create a direct messaging channel with the one who executed the command
# if isinstance(ctx.channel, discord.DMChannel):
# userdata = str(ctx.author.id)
# view = AuthenticationView(userdata)
# await ctx.send("Please authenticate your github account to register in the C4GT Community", view=view)
# # Command logic for DMs
# else:
# # Command logic for other channels (e.g., servers, groups)
# await ctx.send("Please use this command in Bot DMs.")
# # Command logic for DMs
# userdata = str(ctx.author.id)
# view = AuthenticationView(userdata)
# # await dmchannel.send("Please authenticate your github account to register for Code for GovTech 2023", view=view)

@commands.command(aliases=["badges"])
async def list_badges(self, ctx):
Expand Down
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
{
"channels": {
"introduction": {
"id": "",
"name": ""
}
},
"roles": [],

"CONTRIBUTOR_ROLE_ID": 973852365188907048,
"INTRODUCTIONS_CHANNEL_ID": 1107343423167541328,
"ERROR_CHANNEL_ID": 0,
Expand Down
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ services:
container_name: discord-bot
image: ghcr.io/code4govtech/discord-bot:main
restart: always
logging:
driver: syslog
options:
syslog-address: "udp://172.26.0.1:12201"
tag: discord-bot
networks:
- logstash_common
environment:
TOKEN: ${TOKEN}
SERVER_ID: ${SERVER_ID}
Expand All @@ -12,3 +19,7 @@ services:
FLASK_HOST: ${FLASK_HOST}
SUPABASE_URL: ${SUPABASE_URL}
SUPABASE_KEY: ${SUPABASE_KEY}

networks:
logstash_common:
external: true
174 changes: 171 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,187 @@
from typing import Optional, Union
import discord
from discord.ext import commands
import os, sys
import asyncio
import dotenv
from discord.utils import MISSING
import dotenv, aiohttp, json
from utils.db import SupabaseInterface

#Since there are user defined packages, adding current directory to python path
current_directory = os.getcwd()
sys.path.append(current_directory)

dotenv.load_dotenv(".env")
intents = discord.Intents.all()

client = commands.Bot(command_prefix='!', intents=intents)
# class GithubAuthModal(discord.ui.Modal):
# def __init__(self, *,userID, title: str = None, timeout: float | None = None, custom_id: str = None) -> None:
# super().__init__(title=title, timeout=timeout, custom_id=custom_id)
# self.add_item(discord.ui.Button(label='Authenticate Github', style=discord.ButtonStyle.url, url=f'https://github-app.c4gt.samagra.io/authenticate/{userID}'))
class AuthenticationView(discord.ui.View):
def __init__(self, discord_userdata):
super().__init__()
button = discord.ui.Button(label='Authenticate Github', style=discord.ButtonStyle.url, url=f'https://github-app.c4gt.samagra.io/authenticate/{discord_userdata}')
self.add_item(button)
self.message = None

# class ChapterSelect(discord.ui.Select):
# def __init__(self, affiliation, data):
# collegeOptions = [discord.SelectOption(label=option["label"], emoji=option["emoji"] ) for option in [
# {
# "label": "NIT Kurukshetra",
# "emoji": "\N{GRADUATION CAP}"
# },
# {
# "label": "ITER, Siksha 'O' Anusandhan",
# "emoji": "\N{GRADUATION CAP}"
# },
# {
# "label": "IIITDM Jabalpur",
# "emoji": "\N{GRADUATION CAP}"
# },
# {
# "label": "KIIT, Bhubaneswar",
# "emoji": "\N{GRADUATION CAP}"
# }

# ]]
# corporateOptions = []
# self.data = data
# super().__init__(placeholder="Please select your institute",max_values=1,min_values=1,options=collegeOptions if affiliation=="College Chapter" else corporateOptions)
# async def callback(self, interaction:discord.Interaction):
# self.data["chapter"] = self.values[0]
# self.data["discord_id"]= interaction.user.id

# await interaction.response.send_message("Now please Authenticate using Github so we can start awarding your points!",view=AuthenticationView(interaction.user.id), ephemeral=True)

# class AffiliationSelect(discord.ui.Select):
# def __init__(self, data):
# options = [discord.SelectOption(label=option["label"], emoji=option["emoji"] ) for option in [
# {
# "label": "College Chapter",
# "emoji": "\N{OPEN BOOK}"
# },
# {
# "label": "Corporate Chapter",
# "emoji": "\N{OFFICE BUILDING}"
# },
# {
# "label": "Individual Contributor",
# "emoji": "\N{BRIEFCASE}"
# }
# ]]
# super().__init__(placeholder="Please select applicable affliliation",max_values=1,min_values=1,options=options)
# self.data = data
# async def callback(self, interaction:discord.Interaction):
# self.data["affiliation"] = self.values[0]
# if self.values[0] == "College Chapter":
# chapterView = discord.ui.View()
# chapterView.add_item(ChapterSelect(self.values[0], self.data))
# await interaction.response.send_message("Please select your institute!", view=chapterView, ephemeral=True)
# elif self.values[0] == "Corporate Chapter":
# await interaction.response.send_message("We currently don't have any active Corporate Chapters!", ephemeral=True)
# elif self.values[0] == "Individual Contributor":
# await interaction.response.send_message("Now please Authenticate using Github so we can start awarding your points!",view=AuthenticationView(interaction.user.id), ephemeral=True)

# class AffiliationView(discord.ui.View):
# def __init__(self, data):
# super().__init__()
# self.timeout = None
# self.add_item(AffiliationSelect(data))

class RegistrationModal(discord.ui.Modal):
def __init__(self, *, title: str = None, timeout: Union[float, None] = None, custom_id: str = None) -> None:
super().__init__(title=title, timeout=timeout, custom_id=custom_id)

async def post_data(self, data):
url = 'https://kcavhjwafgtoqkqbbqrd.supabase.co/rest/v1/contributor_names'
headers = {
"apikey": f"{os.getenv('SUPABASE_KEY')}",
"Authorization": f"Bearer {os.getenv('SUPABASE_KEY')}",
"Content-Type": "application/json",
"Prefer": "return=minimal"
}

async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, data=json.dumps(data)) as response:
if response.status == 200:
print("Data posted successfully")
else:
print("Failed to post data")
print("Status Code:", response.status)


name = discord.ui.TextInput(label='Please Enter Your Name', placeholder='To give you the recognition you deserve, could you please share your full name for the certificates!')
async def on_submit(self, interaction: discord.Interaction):
user = interaction.user
await interaction.response.send_message("Thanks! Now please sign in via Github!",view=AuthenticationView(user.id), ephemeral=True)
await self.post_data(
{
"name": self.name.value,
"discord_id": user.id
}
)

verifiedContributorRoleID = 1123967402175119482
print("User:", type(user))
if verifiedContributorRoleID in [role.id for role in user.roles]:
return
else:
async def hasIntroduced():
print("Checking...")
authentication = SupabaseInterface("contributors").read("discord_id", user.id)
while not authentication:
await asyncio.sleep(30)
print("Found!")
discordEngagement = SupabaseInterface("discord_engagement").read("contributor", user.id)[0]
return discordEngagement["has_introduced"]
try:
await asyncio.wait_for(hasIntroduced(), timeout=1000)
verifiedContributorRole = user.guild.get_role(verifiedContributorRoleID)
if verifiedContributorRole:
if verifiedContributorRole not in user.roles:
await user.add_roles(verifiedContributorRole, reason="Completed Auth and Introduction")
except asyncio.TimeoutError:
print("Timed out waiting for authentication")



class RegistrationView(discord.ui.View):
def __init__(self):
super().__init__(timeout = None)

@discord.ui.button(label="Register", style=discord.enums.ButtonStyle.blurple, custom_id='registration_view:blurple')
async def reg(self, interaction: discord.Interaction, button: discord.ui.Button):
modal = RegistrationModal(title="Contributor Registration", custom_id="registration:modal")
await interaction.response.send_modal(modal)

class C4GTBot(commands.Bot):
def __init__(self):
intents = discord.Intents.all()
intents.message_content = True

super().__init__(command_prefix=commands.when_mentioned_or('!'), intents=intents)

async def setup_hook(self) -> None:
# Register the persistent view for listening here.
# Note that this does not send the view to any message.
# In order to do this you need to first send a message with the View, which is shown below.
# If you have the message_id you can also pass it as a keyword argument, but for this example
# we don't have one.
self.add_view(RegistrationView())

client = C4GTBot()

@client.command(aliases=['registration'])
async def registerAsContributor(ctx, channel: discord.TextChannel):
# guild = ctx.guild
# channelID = 1167054801385820240
# channel = guild.get_channel_or_thread(channelID)
await channel.send("Please register using Github to sign up as a C4GT Contributor", view=RegistrationView())


#alert message on commandline that bot has successfully logged in

@client.event
async def on_ready():
print(f'We have logged in as {client.user}')
Expand Down

0 comments on commit 4a8f19d

Please sign in to comment.