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
160 changes: 160 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
15 changes: 15 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
discord="==2.3.2"
python-dotenv="==1.0.1"
requests = "==2.31.0"
pytz = "==2024.1"

[dev-packages]

[requires]
python_version = "3.9"
562 changes: 562 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,46 @@
# ReppedBot-Discord
# ReppedBot - A Discord bot

ReppedBot chat bot built with Python. There are two scripts that can run, one is a generic Bot script that listens for events and commands, the other is a webhook that is scheduled to post a message every Monday, Wednesday and Friday at noon.

## Commands for the Bot
**These Are example commands to test if the bot works**

- `!hello` - bot will respond with 'HEY'
- `!saymyname` - bot will respond with 'what up `{your discord name}`'

When you join The server for the first time, you will receive a DM from ReppedBot welcoming you to the test server.

## To Test Locally

- ```shell
git clone git@github.com:Repped-In-Tech/ReppedBot-Discord.git
```
- Create a virtual environment

- ```shell
pipenv install
```

- create a .env according to the sample env with [these](https://docs.google.com/spreadsheets/d/1IUZ4BoJgRJnbOfyMGNxHUXswgyZaCz1BD15bkJvQpi4/edit#gid=190460805) variables. If you don't have access to this sheet, get access from someone on the dev team with access.

#### To run the bot:

from the main directory:
`python main.py`

If you see this in your terminal, your bot is active:

![Terminal Message](https://res.cloudinary.com/dts9iifle/image/upload/v1712941707/Screen_Shot_2024-04-12_at_12.06.51_PM_bvv1fq.png)

Navigate to [Justin's Test Server](https://discord.gg/tHMkMbqVad) and join to see the DM and interact with the bot with commands

#### To run the webhook:

from the main directory:
`python webhook.py`

You should see a message in the terminal that says, 'I am running'

You can test the standup messenger by changing the date times in `standups.py` to test the message

![Standup Test](https://res.cloudinary.com/dts9iifle/image/upload/v1712942360/Screen_Shot_2024-04-12_at_12.19.09_PM_vnukn1.png)
6 changes: 6 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from reppedBot import ReppedBot, initialize_commands

if __name__ == '__main__':
bot = ReppedBot()
initialize_commands(bot)
bot.run(bot.discord_token)
3 changes: 3 additions & 0 deletions reppedBot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .config import BotConfigMixin, WebhookConfigMixin
from .webhooks import StandupwWebhook
from .bot import ReppedBot, initialize_commands
3 changes: 3 additions & 0 deletions reppedBot/bot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .bot import ReppedBot
from .commands import initialize_commands
from .ui_components.button import AuthorizeButton
2 changes: 2 additions & 0 deletions reppedBot/bot/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .users import remove_member_from_db
from .roles import update_user_roles_in_db
8 changes: 8 additions & 0 deletions reppedBot/bot/api/roles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import requests

def update_user_roles_in_db(member_id, roles, endpoint):
data = {
"member_id": member_id[0],
"roles": [role.id for role in roles]
}
return requests.request('POST', endpoint, data=data, timeout=3)
4 changes: 4 additions & 0 deletions reppedBot/bot/api/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import requests

def remove_member_from_db(member_id, url):
return requests.request(method='GET', url=url, params={"member_id": member_id}, timeout=3)
36 changes: 36 additions & 0 deletions reppedBot/bot/bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import requests
from discord.ext import commands
from reppedBot import BotConfigMixin
from .ui_components.button import AuthorizeButton
from .api import remove_member_from_db, update_user_roles_in_db

class ReppedBot(BotConfigMixin, commands.Bot):
"""
base class to interact with Discord server. Event listeners are defined within the class, commands are defined separately.
"""

async def on_member_join(self, member):
"""
sends a welcome message via DM when a member joins a server, and prompts to authorize with Oauth
"""
await member.create_dm()
await member.dm_channel.send(
f'Hi {member.name}, in order to access all the features of {member.guild.name}, click this button!',
view=AuthorizeButton(self.auth_url)
)

async def on_raw_member_remove(self, payload):
"""
sends the member id to an external api to sync info about leaving the server
"""
remove_member_from_db(payload.user.id, self.remove_url)

async def on_member_update(self, before, after):
"""
sends the member id and roles to an external api for processing
"""
update_user_roles_in_db(after.id, after.roles, self.update_url)

async def on_command_error(self, ctx, error):
if isinstance(error, commands.CommandNotFound):
await ctx.send("Command not recognized")
31 changes: 31 additions & 0 deletions reppedBot/bot/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from discord.utils import get
from .data import ROLE_IDS

def initialize_commands(bot):
"""function that takes an instance of a bot and adds commands to the bot"""

@bot.command(name="hello")
async def say_hey(ctx):
"""bot responds with 'HEY' when !hello is input in Discord"""
response = 'HEY'
await ctx.send(response)

@bot.command(name="saymyname")
async def say_my_name(ctx):
response = f'What up {ctx.author}'
await ctx.send(response)

@bot.command(name="superstar")
async def add_superstar_role(ctx):
author = ctx.author
role = get(ctx.guild.roles, id=ROLE_IDS["Superstar"])
await author.add_roles(role)
await ctx.send("role added!")

@bot.command(name="email")
async def save_email(ctx, email=None):
if email:
print(email)
await ctx.send("Thanks for signing up!")
else:
await ctx.send("please send a valid email")
1 change: 1 addition & 0 deletions reppedBot/bot/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .roles import ROLE_IDS
7 changes: 7 additions & 0 deletions reppedBot/bot/data/roles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Role IDs so the bot can assign users a role programmatically
"""

ROLE_IDS = {
"Superstar": 1239597930739142780
}
1 change: 1 addition & 0 deletions reppedBot/bot/ui_components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .button import AuthorizeButton
7 changes: 7 additions & 0 deletions reppedBot/bot/ui_components/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import discord

class AuthorizeButton(discord.ui.View):
def __init__(self, url):
super().__init__(timeout=30)
button = discord.ui.Button(label='Unlock the goods!', style=discord.ButtonStyle.url, url=url)
self.add_item(button)
1 change: 1 addition & 0 deletions reppedBot/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .setup import BotConfigMixin, WebhookConfigMixin
27 changes: 27 additions & 0 deletions reppedBot/config/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import os
from dotenv import load_dotenv
import discord

class BotConfigMixin:
"""loader for environment variables for ReppedBot"""
def __init__(self):
load_dotenv()
self.discord_token=os.getenv('DISCORD_TOKEN')
self.guild_id=os.getenv('GUILD_ID')
self.command_prefix=os.getenv('COMMAND_PREFIX')
intents = discord.Intents.default()
self.auth_url = os.getenv('AUTHORIZATION_URL')
self.remove_url = os.getenv('API_REMOVE_ENDPOINT')
self.update_url = os.getenv('API_UPDATE_ROLES_ENDPOINT')
for intent in os.getenv('INTENTS').split(','):
if hasattr(intents, intent):
setattr(intents, intent, True)
super().__init__(command_prefix=self.command_prefix, intents=intents)

class WebhookConfigMixin:
"""loader for environment variables for Webhook"""
def __init__(self):
load_dotenv()
self.discord_token=os.getenv('DISCORD_TOKEN')
self.webhook_url=os.getenv('WEBHOOK_URL')
self.standup_message=os.getenv('STANDUP_MESSAGE')
1 change: 1 addition & 0 deletions reppedBot/webhooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .standups import StandupwWebhook
Loading