Skip to content

Commit

Permalink
feat: unify discord and reddit bots (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReenigneArcher authored Apr 21, 2024
1 parent fdec679 commit 6300eff
Show file tree
Hide file tree
Showing 17 changed files with 570 additions and 57 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ tests/

# ignore venv when building locally
venv/

data/
sample.env
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,6 @@ cython_debug/
# 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/

# project specific ignores
data/
3 changes: 1 addition & 2 deletions .replit
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# The command that is executed when the run button is clicked.
run = ["python", "discord_bot.py"]
run = ["python", "src/main.py"]

# The default file opened in the editor.
entrypoint = "README.md"
Expand All @@ -14,4 +14,3 @@ language = "python3"
packageSearch = true
# Enabled package guessing
guessImports = false

60 changes: 41 additions & 19 deletions DOCKER_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,35 @@ Create and run the container (substitute your `<values>`):

```bash
docker run -d \
--name=lizardbyte-discord-bot \
--name=lizardbyte-support-bot \
--restart=unless-stopped \
-e BOT_TOKEN=<BOT_TOKEN> \
-e DISCORD_BOT_TOKEN=<DISCORD_BOT_TOKEN> \
-e DAILY_CHANNEL_ID=<DAILY_CHANNEL_ID> \
-e DAILY_RELEASES=<DAILY_RELEASES> \
-e DAILY_TASKS=<DAILY_TASKS> \
-e DAILY_TASKS_UTC_HOUR=<DAILY_TASKS_UTC_HOUR> \
-e GRAVATAR_EMAIL=<GRAVATAR_EMAIL> \
-e IGDB_CLIENT_ID=<IGDB_CLIENT_ID> \
-e IGDB_CLIENT_SECRET=<IGDB_CLIENT_SECRET> \
lizardbyte/discord-bot
-e PRAW_CLIENT_ID=<PRAW_CLIENT_ID> \
-e PRAW_CLIENT_SECRET=<PRAW_CLIENT_SECRET> \
-e PRAW_SUBREDDIT=<PRAW_SUBREDDIT> \
-e DISCORD_WEBHOOK=<DISCORD_WEBHOOK> \
-e GRAVATAR_EMAIL=<GRAVATAR_EMAIL> \
-e REDIRECT_URI=<REDIRECT_URI> \
-p 8080:8080 \
lizardbyte/support-bot
```

To update the container it must be removed and recreated:

```bash
# Stop the container
docker stop lizardbyte-discord-bot
docker stop lizardbyte-support-bot
# Remove the container
docker rm lizardbyte-discord-bot
docker rm lizardbyte-support-bot
# Pull the latest update
docker pull lizardbyte/discord-bot
docker pull lizardbyte/support-bot
# Run the container with the same parameters as before
docker run -d ...
```
Expand All @@ -39,18 +46,26 @@ Create a `docker-compose.yml` file with the following contents (substitute your
version: '3'
services:
lizardbyte-discord-bot:
image: lizardbyte/discord-bot
container_name: lizardbyte-discord-bot
image: lizardbyte/support-bot
container_name: lizardbyte-support-bot
restart: unless-stopped
environment:
- BOT_TOKEN=<BOT_TOKEN>
- DISCORD_BOT_TOKEN=<DISCORD_BOT_TOKEN>
- DAILY_CHANNEL_ID=<DAILY_CHANNEL_ID>
- DAILY_RELEASES=<DAILY_RELEASES>
- DAILY_TASKS=<DAILY_TASKS>
- DAILY_TASKS_UTC_HOUR=<DAILY_TASKS_UTC_HOUR>
- GRAVATAR_EMAIL=<GRAVATAR_EMAIL>
- IGDB_CLIENT_ID=<IGDB_CLIENT_ID>
- IGDB_CLIENT_SECRET=<IGDB_CLIENT_SECRET>
- PRAW_CLIENT_ID=<PRAW_CLIENT_ID>
- PRAW_CLIENT_SECRET=<PRAW_CLIENT_SECRET>
- PRAW_SUBREDDIT=<PRAW_SUBREDDIT>
- DISCORD_WEBHOOK=<DISCORD_WEBHOOK>
- GRAVATAR_EMAIL=<GRAVATAR_EMAIL>
- REDIRECT_URI=<REDIRECT_URI>
ports:
- 8080:8080
```
Create and start the container (run the command from the same folder as your `docker-compose.yml` file):
Expand All @@ -70,13 +85,20 @@ docker-compose up -d
## Parameters
You must substitute the `<values>` with your own settings.

| Parameter | Required | Default | Description |
|----------------------|----------|---------|---------------------------------------------------------------|
| BOT_TOKEN | True | None | Token from Bot page on discord developer portal. |
| DAILY_TASKS | False | true | Daily tasks on or off. |
| DAILY_RELEASES | False | true | Send a message for each game released on this day in history. |
| DAILY_CHANNEL_ID | False | None | Required if daily_tasks is enabled. |
| DAILY_TASKS_UTC_HOUR | False | 12 | The hour to run daily tasks. |
| GRAVATAR_EMAIL | False | None | Gravatar email address for bot avatar. |
| IGDB_CLIENT_ID | False | None | Required if daily_releases is enabled. |
| IGDB_CLIENT_SECRET | False | None | Required if daily_releases is enabled. |
| Parameter | Required | Default | Description |
|----------------------|----------|---------|-------------------------------------------------------------------------|
| DISCORD_BOT_TOKEN | True | None | Token from Bot page on discord developer portal. |
| DAILY_TASKS | False | true | Daily tasks on or off. |
| DAILY_RELEASES | False | true | Send a message for each game released on this day in history. |
| DAILY_CHANNEL_ID | False | None | Required if daily_tasks is enabled. |
| DAILY_TASKS_UTC_HOUR | False | 12 | The hour to run daily tasks. |
| GRAVATAR_EMAIL | False | None | Gravatar email address for bot avatar. |
| IGDB_CLIENT_ID | False | None | Required if daily_releases is enabled. |
| IGDB_CLIENT_SECRET | False | None | Required if daily_releases is enabled. |
| PRAW_CLIENT_ID | True | None | `client_id` from reddit app setup page. |
| PRAW_CLIENT_SECRET | True | None | `client_secret` from reddit app setup page. |
| PRAW_SUBREDDIT | True | None | Subreddit to monitor (reddit user should be moderator of the subreddit) |
| DISCORD_WEBHOOK | False | None | URL of webhook to send discord notifications to |
| REDIRECT_URI | True | None | The redirect URI entered during the reddit application setup |

Further instructions can be found in the main [readme](https://github.com/LizardByte/support-bot/blob/master/README.md).
25 changes: 19 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,39 @@ ARG DAILY_RELEASES=true
ARG DAILY_TASKS_UTC_HOUR=12

# Secret config
ARG BOT_TOKEN
ARG DISCORD_BOT_TOKEN
ARG DAILY_CHANNEL_ID
ARG GRAVATAR_EMAIL
ARG IGDB_CLIENT_ID
ARG IGDB_CLIENT_SECRET
ARG PRAW_CLIENT_ID
ARG PRAW_CLIENT_SECRET
ARG PRAW_SUBREDDIT
ARG DISCORD_WEBHOOK
ARG GRAVATAR_EMAIL
ARG REDIRECT_URI

# Environment variables
ENV DAILY_TASKS=$DAILY_TASKS
ENV DAILY_RELEASES=$DAILY_RELEASES
ENV DAILY_CHANNEL_ID=$DAILY_CHANNEL_ID
ENV DAILY_TASKS_UTC_HOUR=$DAILY_TASKS_UTC_HOUR
ENV BOT_TOKEN=$BOT_TOKEN
ENV DISCORD_BOT_TOKEN=$DISCORD_BOT_TOKEN
ENV GRAVATAR_EMAIL=$GRAVATAR_EMAIL
ENV IGDB_CLIENT_ID=$IGDB_CLIENT_ID
ENV IGDB_CLIENT_SECRET=$IGDB_CLIENT_SECRET
ENV PRAW_CLIENT_ID=$PRAW_CLIENT_ID
ENV PRAW_CLIENT_SECRET=$PRAW_CLIENT_SECRET
ENV PRAW_SUBREDDIT=$PRAW_SUBREDDIT
ENV DISCORD_WEBHOOK=$DISCORD_WEBHOOK
ENV GRAVATAR_EMAIL=$GRAVATAR_EMAIL
ENV REDIRECT_URI=$REDIRECT_URI

VOLUME /data

WORKDIR /app/

COPY requirements.txt .
COPY *.py .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python -m pip install --no-cache-dir -r requirements.txt

CMD ["python", "discord_bot.py"]
CMD ["python", "./src/main.py"]
43 changes: 38 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# discord-bot
Discord bot written in python to help manage the LizardByte discord server.
# support-bot
Support bot written in python to help manage LizardByte communities. The current focus is discord and reddit, but other
platforms such as GitHub discussions/issues could be added.


## Overview
This is a custom discord bot with some slash commands to help with support on the LizardByte discord server.

### Discord Slash Commands

| command | description | argument 1 |
|----------|---------------------------------------------------|---------------------|
Expand All @@ -15,6 +17,9 @@ This is a custom discord bot with some slash commands to help with support on th


## Instructions

### Discord

* Setup an application at [discord developer portal](https://discord.com/developers/applications).
* On `Bot` page enabled these:
* Presence Intent
Expand All @@ -26,7 +31,7 @@ This is a custom discord bot with some slash commands to help with support on th

| variable | required | default | description |
|----------------------|----------|---------|---------------------------------------------------------------|
| BOT_TOKEN | True | None | Token from Bot page on discord developer portal. |
| DISCORD_BOT_TOKEN | True | None | Token from Bot page on discord developer portal. |
| DAILY_TASKS | False | true | Daily tasks on or off. |
| DAILY_RELEASES | False | true | Send a message for each game released on this day in history. |
| DAILY_CHANNEL_ID | False | None | Required if daily_tasks is enabled. |
Expand All @@ -36,6 +41,34 @@ This is a custom discord bot with some slash commands to help with support on th
| IGDB_CLIENT_SECRET | False | None | Required if daily_releases is enabled. |

* Running bot:
* `python discord_bot.py`
* `python ./src/main.py`
* Invite bot to server:
* `https://discord.com/api/oauth2/authorize?client_id=<the client id of the bot>&permissions=8&scope=bot%20applications.commands`


### Reddit

* Set up an application at [reddit apps](https://www.reddit.com/prefs/apps/).
* The redirect uri must be publicly accessible.
* If using Replit, enter `https://<REPL_SLUG>.<REPL_OWNER>.repl.co`
* Otherwise, it is recommended to use [Nginx Proxy Manager](https://nginxproxymanager.com/) and [Duck DNS](https://www.duckdns.org/)
* Take note of the `client_id` and `client_secret`
* Enter the following as environment variables

| Parameter | Required | Default | Description |
|--------------------|----------|---------|-------------------------------------------------------------------------|
| PRAW_CLIENT_ID | True | None | `client_id` from reddit app setup page. |
| PRAW_CLIENT_SECRET | True | None | `client_secret` from reddit app setup page. |
| PRAW_SUBREDDIT | True | None | Subreddit to monitor (reddit user should be moderator of the subreddit) |
| DISCORD_WEBHOOK | False | None | URL of webhook to send discord notifications to |
| GRAVATAR_EMAIL | False | None | Gravatar email address to get avatar from |
| REDIRECT_URI | True | None | The redirect URI entered during the reddit application setup |

* First run (or manually get a new refresh token):
* Delete `./data/refresh_token` file if needed
* `python ./src/main.py`
* Open browser and login to reddit account to use with bot
* Navigate to URL printed in console and accept
* `./data/refresh_token` file is written
* Running after refresh_token already obtained:
* `python ./src/main.py`
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ beautifulsoup4==4.12.3
Flask==3.0.2
igdb-api-v4==0.3.2
libgravatar==1.0.4
praw==7.7.1
py-cord==2.4.1
python-dotenv==1.0.1
requests==2.31.0
10 changes: 9 additions & 1 deletion sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@ DAILY_CHANNEL_ID=
DAILY_TASKS_UTC_HOUR=12

# Secret settings
BOT_TOKEN=
DISCORD_BOT_TOKEN=
GRAVATAR_EMAIL=
IGDB_CLIENT_ID=
IGDB_CLIENT_SECRET=
READTHEDOCS_TOKEN=

# reddit bot
PRAW_CLIENT_ID=
PRAW_CLIENT_SECRET=
PRAW_SUBREDDIT=AskReddit
DISCORD_WEBHOOK=
GRAVATAR_EMAIL=
REDIRECT_URI=
File renamed without changes.
51 changes: 27 additions & 24 deletions discord_bot.py → src/discord_bot.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# standard imports
import asyncio
from datetime import datetime
import json
import os
import random
import threading
from typing import Union

# lib imports
Expand All @@ -15,18 +17,11 @@
# local imports
from discord_constants import org_name, bot_name, bot_url
from discord_helpers import igdb_authorization, month_dictionary
import keep_alive

# development imports
from dotenv import load_dotenv
load_dotenv(override=False) # environment secrets take priority over .env file

if True: # hack for flake8
from discord_avatar import avatar, avatar_img
from discord_views import DocsCommandView, DonateCommandView, RefundCommandView
from discord_avatar import avatar, avatar_img
from discord_views import DocsCommandView, DonateCommandView, RefundCommandView

# constants
bot_token = os.environ['BOT_TOKEN']
bot_token = os.environ['DISCORD_BOT_TOKEN']
bot = discord.Bot(intents=discord.Intents.all(), auto_sync_commands=True)

user_mention_desc = 'Select the user to mention'
Expand Down Expand Up @@ -502,21 +497,29 @@ async def daily_task():

print(f'thread created: {thread.name}')

# to run in replit
try:
os.environ['REPL_SLUG']
except KeyError:
pass # not running in replit
else:
keep_alive.keep_alive() # Start the web server

try:
bot.loop.run_until_complete(future=bot.start(token=bot_token)) # Login the bot
except KeyboardInterrupt:
print("Keyboard Interrupt Detected")
finally:
bot_thread = threading.Thread(target=lambda: None)


def start():
global bot_thread
try:
# Login the bot in a separate thread
bot_thread = threading.Thread(
target=bot.loop.run_until_complete,
args=(bot.start(token=bot_token),),
daemon=True
)
bot_thread.start()
except KeyboardInterrupt:
print("Keyboard Interrupt Detected")
stop()


def stop():
print("Attempting to stop daily tasks")
daily_task.stop()
print("Attempting to close bot connection")
bot.loop.run_until_complete(future=bot.close())
if bot_thread is not None and bot_thread.is_alive():
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop)
bot_thread.join()
print("Closed bot")
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
38 changes: 38 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# standard imports
import os
import time

# development imports
from dotenv import load_dotenv
load_dotenv(override=False) # environment secrets take priority over .env file

# local imports
if True: # hack for flake8
import discord_bot
import keep_alive
import reddit_bot


def main():
# to run in replit
try:
os.environ['REPL_SLUG']
except KeyError:
pass # not running in replit
else:
keep_alive.keep_alive() # Start the web server

discord_bot.start() # Start the discord bot
reddit_bot.start() # Start the reddit bot

try:
while discord_bot.bot_thread.is_alive() or reddit_bot.bot_thread.is_alive():
time.sleep(0.5)
except KeyboardInterrupt:
print("Keyboard Interrupt Detected")
discord_bot.stop() # Stop the discord bot
reddit_bot.stop()


if __name__ == '__main__':
main()
Loading

0 comments on commit 6300eff

Please sign in to comment.