Skip to content
Open
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
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
.github/
.vscode/
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,6 @@ dmypy.json

# Pyre type checker
.pyre/

# Editors
.vscode/
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN python3 -m pip install -r requirements.txt

COPY bot/ .

ENV token=$TOKEN

CMD ["python3", "./bot.py"]
31 changes: 19 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
# lovelace
oSTEM's custom Discord bot, Lovelace. Currently in an alpha state with major rewrites and reorganization to come.
# Lovelace
oSTEM's custom Discord bot, Lovelace. Happily accepting contributions!

## Set-up Requirements
This is a Python Discord bot that uses the discord.py framework.
- Python 3.6+
This bot uses the discord.py library for the basic bot functionality and uses dislash.py for Slash Commands support.

The main purpose of the bot is to provide functionality for joining Affinity and Working groups securely

# Set-up Requirements
This is a Python Discord bot that uses the discord.py and dislash.py frameworks.
It requires python 3.6+ but it currently using what is specified in the `Dockerfile`.

Additional, this bot uses Docker to run the application.
The commands to build the docker image and to run the container are as follows:

```bash
docker build -t lovelace .
```

Run the command
```bash
python -m pip install -r requirements.txt
docker run -e "TOKEN=token_goes_here" lovelace
```
to install all of the dependencies of this project

The `register_command.py` file is required to run once and requires an adjustment to the `Dockerfile`, specifically the file for `CMD`. This registers the Slash Commands for the specified Guild.

## Running the bot on a test server
This bot is tightly coupled with how we set-up our server.
- The affinity and working groups require that the specific channel names tied to those dictionary constants are present.
- You will need to update the `ACTIVE_GUILD` constant to match the test server.
Once the commands are registered for the guild, the bot can be re-run normally with the Docker commands listed above.
40 changes: 40 additions & 0 deletions bot/bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import os

import discord
from dislash import SlashClient
from discord.ext import commands

from constants import COMMAND_PREFIX, LOG_CHANNEL


class Lovelace(commands.Bot):
"""
This is the base bot instance.
"""

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.load_extension("exts.affinity_working_groups")

async def on_ready(self, *args, **kwargs):
await self.get_channel(LOG_CHANNEL).send("I have connected.")


if __name__ == "__main__":
TOKEN = os.getenv('TOKEN')

# Intents
intents = discord.Intents.default()
intents.members = True


bot = Lovelace(
command_prefix=COMMAND_PREFIX,
acitivty=None,
intents=intents,
allowed_mentions=discord.AllowedMentions(everyone=False)
)

slash = SlashClient(bot)

bot.run(TOKEN)
3 changes: 3 additions & 0 deletions bot/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
COMMAND_PREFIX = "!"
GUILD_ID = 754379784460566569
LOG_CHANNEL = 784878993160929310
Empty file added bot/exts/__init__.py
Empty file.
88 changes: 88 additions & 0 deletions bot/exts/affinity_working_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import discord
from discord.ext import commands
from dislash import slash_commands


class WorkingGroups(commands.Cog):
"""A cog that manages users adding themslves and leaving Working Groups"""

def __init__(self, bot: commands.Bot):
self.bot = bot

@slash_commands.command(name="working-group")
async def affinity_group(self, interaction):
for k, v in interaction.data.options.items():
if v.name == "join":
add = True
action = "joined"
else:
add = False
action = "left"

result = await _add_remove_from_group(interaction.guild, interaction.author, v.value, "working groups", add)

if result is True:
msg_content = f":white_check_mark: You have successfully {action} the {v.value} working group channel.\
\nIf there was an error, please contact an admin."
else:
msg_content = ":x: Sorry, there was an error. Please try again or contact an admin."

await interaction.reply(
content=msg_content,
hide_user_input=True,
ephemeral=True, # Only visible to the invoker of the command
type=4, # Immediate response with acknowledge
)


class AffinityGroups(commands.Cog):
"""A cog that manages users adding themselves and leaving Affinity Groups"""

def __init__(self, bot: commands.Bot):
self.bot = bot

@slash_commands.command(name="affinity-group")
async def affinity_group(self, interaction):
for k, v in interaction.data.options.items():
if v.name == "join":
add = True
action = "joined"
else:
add = False
action = "left"

result = await _add_remove_from_group(interaction.guild, interaction.author, v.value, "affinity groups", add)

if result is True:
msg_content = f":white_check_mark: You have successfully {action} the {v.value} affinity group channel.\
\nIf there was an error, please contact an admin."
else:
msg_content = ":x: Sorry, there was an error. Please try again or contact an admin."

await interaction.reply(
content=msg_content,
hide_user_input=True,
ephemeral=True, # Only visible to the invoker of the command
type=4, # Immediate response with acknowledge
)


async def _add_remove_from_group(guild, user, channel_name: str, category_name: str, add: bool) -> bool:
"""Helper function to add or remove a user from a specific channel.
If add is false, it will remove the permissions."""

category = discord.utils.get(guild.categories, name=category_name)
channel = discord.utils.get(guild.channels, category_id=category.id, name=channel_name)
if add is True:
await channel.set_permissions(user, read_messages=add, send_messages=add, add_reactions=add,
read_message_history=add, external_emojis=add, attach_files=add, embed_links=add)
else:
# Resets the permissions for the user and removes the channel-specific override
await channel.set_permissions(user, overwrite=None)
return True


def setup(bot: commands.Bot) -> None:
"""Load Affinity and Working Groups cogs"""
bot.add_cog(AffinityGroups(bot))
bot.add_cog(WorkingGroups(bot))
99 changes: 99 additions & 0 deletions bot/register_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import os

import discord
from dislash import SlashClient, Option, Type, SlashCommand, OptionChoice
from discord.ext import commands
from dislash.slash_commands import slash_client

from constants import COMMAND_PREFIX, GUILD_ID


class Lovelace(commands.Bot):
"""
This is the base bot instance.
"""

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.load_extension("exts.affinity_working_groups")

async def on_ready(self, *args, **kwargs):
print(f'{bot.user} has connected to Discord')


if __name__ == "__main__":
TOKEN = os.getenv('TOKEN')

# Intents
intents = discord.Intents.default()
intents.members = True


bot = Lovelace(
command_prefix=COMMAND_PREFIX,
intents=intents,
allowed_mentions=discord.AllowedMentions(everyone=False)
)

slash = SlashClient(bot)


@slash.event
async def on_ready():
sc = SlashCommand(name="working-group",
description="Join or leave a working group",
options=[
Option(name="join", description="Join a working group", type=Type.STRING,
choices=[
OptionChoice(name="Beyond the Binary", value="beyondthebinary"),
OptionChoice(name="Black & Queer", value="black_queer"),
OptionChoice(name="Queer Enabled", value="queer-enabled"),
]
),
Option(name="leave", description="Leave a working group", type=Type.STRING,
choices=[
OptionChoice(name="Beyond the Binary", value="beyondthebinary"),
OptionChoice(name="Black & Queer", value="black_queer"),
OptionChoice(name="Queer Enabled", value="queer-enabled"),
]
)
]
)

sc1 = SlashCommand(name="affinity-group",
description="Join or leave an affinity group",
options=[
Option(name="join", description="Join an affinity group", type=Type.STRING,
choices=[
OptionChoice(name="AAPI", value="aapi"),
OptionChoice(name="Ace/Aro", value="acearo"),
OptionChoice(name="(Dis)Ability", value="disability"),
OptionChoice(name="InQueery", value="inqueery"),
OptionChoice(name="Middle Sexualities", value="middle-sexualities"),
OptionChoice(name="Race and Ethnicity", value="race-ethnicity"),
OptionChoice(name="Trans and Non-binary", value="transnon-binary"),
OptionChoice(name="Women", value="women")

]
),
Option(name="leave", description="Leave an affinity group", type=Type.STRING,
choices=[
OptionChoice(name="AAPI", value="aapi"),
OptionChoice(name="Ace/Aro", value="acearo"),
OptionChoice(name="(Dis)Ability", value="disability"),
OptionChoice(name="InQueery", value="inqueery"),
OptionChoice(name="Middle Sexualities", value="middle-sexualities"),
OptionChoice(name="Race and Ethnicity", value="race-ethnicity"),
OptionChoice(name="Trans and Non-binary", value="transnon-binary"),
OptionChoice(name="Women", value="women")

]
)
]
)

await slash.register_guild_slash_command(GUILD_ID, sc)
await slash.register_guild_slash_command(GUILD_ID, sc1)


bot.run(TOKEN)
Loading