Skip to content
Merged
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 .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ensure dockerfiles are checked out with LF line endings
Dockerfile text eol=lf
*.dockerfile text eol=lf
14 changes: 13 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.4
# artifacts: false
# platforms: linux/amd64
FROM python:3.11.3-slim-bullseye
FROM python:3.11-slim-bookworm

# Basic config
ARG DAILY_TASKS=true
Expand Down Expand Up @@ -37,6 +37,18 @@ ENV DISCORD_WEBHOOK=$DISCORD_WEBHOOK
ENV GRAVATAR_EMAIL=$GRAVATAR_EMAIL
ENV REDIRECT_URI=$REDIRECT_URI

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
RUN <<_DEPS
#!/bin/bash
set -e
apt-get update -y
apt-get install -y --no-install-recommends \
git
apt-get clean
rm -rf /var/lib/apt/lists/*
_DEPS

VOLUME /data

WORKDIR /app/
Expand Down
47 changes: 19 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ platforms such as GitHub discussions/issues could be added.

### Discord Slash Commands

| command | description | argument 1 |
|----------|---------------------------------------------------|---------------------|
| /help | Return help message | |
| /channel | Suggest to move discussion to a different channel | recommended_channel |
| /docs | Return the specified docs page | user |
| /donate | Return donation links | user |
| /random | Return a random video game quote | |
| command | description |
|----------|----------------------------------------------------------|
| /help | Return help message, for a list of all possible commands |


## Instructions
Expand All @@ -32,16 +28,18 @@ platforms such as GitHub discussions/issues could be added.
:exclamation: if using Docker these can be arguments.
:warning: Never publicly expose your tokens, secrets, or ids.

| variable | 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. |
| variable | 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. |
| SUPPORT_COMMANDS_REPO | False | `https://github.com/LizardByte/support-bot-commands` | Repository for support commands. |
| SUPPORT_COMMANDS_BRANCH | False | `master` | Branch for support commands. |

* Running bot:
* `python -m src`
Expand All @@ -52,9 +50,7 @@ platforms such as GitHub discussions/issues could be added.
### 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/)
* The redirect uri should be https://localhost:8080
* Take note of the `client_id` and `client_secret`
* Enter the following as environment variables

Expand All @@ -65,13 +61,8 @@ platforms such as GitHub discussions/issues could be added.
| 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 |
| REDDIT_USERNAME | True | None | Reddit username |
* | REDDIT_PASSWORD | True | None | Reddit password |

* First run (or manually get a new refresh token):
* Delete `./data/refresh_token` file if needed
* `python -m src`
* 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:
* Running bot:
* `python -m src`
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
beautifulsoup4==4.12.3
Flask==3.0.3
GitPython==3.1.43
igdb-api-v4==0.3.2
libgravatar==1.0.4
mistletoe==1.3.0
praw==7.7.1
py-cord==2.5.0
python-dotenv==1.0.1
Expand Down
12 changes: 12 additions & 0 deletions src/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,20 @@ def get_avatar_bytes():
return avatar_img


def get_data_dir():
# parent directory name of this file, not full path
parent_dir = os.path.dirname(os.path.abspath(__file__)).split(os.sep)[-1]
if parent_dir == 'app': # running in Docker container
d = '/data'
else: # running locally
d = os.path.join(os.getcwd(), 'data')
os.makedirs(d, exist_ok=True)
return d


# constants
avatar = get_bot_avatar(gravatar=os.environ['GRAVATAR_EMAIL'])
org_name = 'LizardByte'
bot_name = f'{org_name}-Bot'
bot_url = 'https://app.lizardbyte.dev'
data_dir = get_data_dir()
133 changes: 131 additions & 2 deletions src/discord/cogs/support_commands.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,145 @@
# standard imports
import datetime
import os

# lib imports
import discord
from discord.commands import Option
from discord.ext import tasks
import git
import mistletoe
from mistletoe.markdown_renderer import MarkdownRenderer

# local imports
from src.common import avatar, bot_name
from src.common import avatar, bot_name, data_dir
from src.discord.views import DocsCommandView
from src.discord import cogs_common


class SupportCommandsCog(discord.Cog):
def __init__(self, bot):
self.bot = bot
self.bot: discord.Bot = bot

self.commands = {}
self.commands_for_removal = []

self.repo_url = os.getenv("SUPPORT_COMMANDS_REPO", "https://github.com/LizardByte/support-bot-commands")
self.repo_branch = os.getenv("SUPPORT_COMMANDS_BRANCH", "master")
self.local_dir = os.path.join(data_dir, "support-bot-commands")
self.commands_dir = os.path.join(self.local_dir, "docs")
self.relative_commands_dir = os.path.relpath(self.commands_dir, self.local_dir)

@discord.Cog.listener()
async def on_ready(self):
# Clone/update the repository
self.update_repo()

# Create commands
self.create_commands()

# Start the self update task
self.self_update.start()

@tasks.loop(minutes=15.0)
async def self_update(self):
self.update_repo()
self.create_commands()
await self.bot.sync_commands()

def update_repo(self):
# Clone or pull the repository
if not os.path.exists(self.local_dir):
repo = git.Repo.clone_from(self.repo_url, self.local_dir)
else:
repo = git.Repo(self.local_dir)
origin = repo.remotes.origin

# Fetch the latest changes from the upstream
origin.fetch()

# Reset the local branch to match the upstream
repo.git.reset('--hard', f'origin/{self.repo_branch}')

for f in repo.untracked_files:
# remove untracked files
os.remove(os.path.join(self.local_dir, f))

# Checkout the branch
repo.git.checkout(self.repo_branch)

def get_project_commands(self):
projects = []
for project in os.listdir(self.commands_dir):
project_dir = os.path.join(self.commands_dir, project)
if os.path.isdir(project_dir):
projects.append(project)
return projects

def create_commands(self):
for project in self.get_project_commands():
project_dir = os.path.join(self.commands_dir, project)
if os.path.isdir(project_dir):
self.create_project_commands(project=project, project_dir=project_dir)

def create_project_commands(self, project, project_dir):
# Get the list of commands in the project directory
command_choices = []
for cmd in os.listdir(project_dir):
cmd_path = os.path.join(project_dir, cmd)
if os.path.isfile(cmd_path) and cmd.endswith('.md'):
cmd_name = os.path.splitext(cmd)[0]
command_choices.append(discord.OptionChoice(name=cmd_name, value=cmd_name))

# Check if a command with the same name already exists
if project in self.commands:
# Update the command options
project_command = self.commands[project]
project_command.options = [
Option(
name='command',
description='The command to run',
type=discord.SlashCommandOptionType.string,
choices=command_choices,
required=True,
)
]
else:
# Create a slash command for the project
@self.bot.slash_command(name=project, description=f"Commands for the {project} project.",
options=[
Option(
name='command',
description='The command to run',
type=discord.SlashCommandOptionType.string,
choices=command_choices,
required=True,
)
])
async def project_command(ctx: discord.ApplicationContext, command: str):
# Determine the command file path
command_file = os.path.join(project_dir, f"{command}.md")

# Read the command file
with open(command_file, "r", encoding='utf-8') as file:
with MarkdownRenderer(
max_line_length=4096, # this must be set to reflow the text
normalize_whitespace=True) as renderer:
description = renderer.render(mistletoe.Document(file))

source_url = (f"{self.repo_url}/blob/{self.repo_branch}/{self.relative_commands_dir}/"
f"{project}/{command}.md")

embed = discord.Embed(
color=0xF1C232,
description=description,
timestamp=datetime.datetime.now(tz=datetime.timezone.utc),
title="See on GitHub",
url=source_url,
)
embed.set_footer(text=f"Requested by {ctx.author.display_name}")
await ctx.respond(embed=embed, ephemeral=False)

self.commands[project] = project_command

@discord.slash_command(
name="docs",
Expand Down
10 changes: 1 addition & 9 deletions src/reddit/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,7 @@ def __init__(self, **kwargs):
self.redirect_uri = kwargs['redirect_uri']

# directories
# parent directory name of this file, not full path
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).split(os.sep)[-1]
print(f'PARENT_DIR: {parent_dir}')
if parent_dir == 'app': # running in Docker container
self.data_dir = '/data'
else: # running locally
self.data_dir = os.path.join(os.getcwd(), 'data')
print(F'DATA_DIR: {self.data_dir}')
os.makedirs(self.data_dir, exist_ok=True)
self.data_dir = common.data_dir

self.last_online_file = os.path.join(self.data_dir, 'last_online')
self.reddit = praw.Reddit(
Expand Down