-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
76a903c
commit a19e9a6
Showing
32 changed files
with
1,977 additions
and
945 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,13 +10,55 @@ on: | |
workflow_dispatch: | ||
|
||
jobs: | ||
release: | ||
name: Release | ||
build: | ||
name: Build | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: 3.11 | ||
|
||
- name: Install Python Dependencies | ||
run: | | ||
python -m pip install --upgrade pip setuptools wheel | ||
python -m pip install --upgrade -r requirements.txt | ||
python -m pip install --upgrade -r requirements-dev.txt | ||
- name: Test with pytest | ||
id: test | ||
env: | ||
GITHUB_PYTEST: "true" | ||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_TEST_BOT_TOKEN }} | ||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_TEST_BOT_WEBHOOK }} | ||
GRAVATAR_EMAIL: ${{ secrets.GRAVATAR_EMAIL }} | ||
PRAW_CLIENT_ID: ${{ secrets.REDDIT_CLIENT_ID }} | ||
PRAW_CLIENT_SECRET: ${{ secrets.REDDIT_CLIENT_SECRET }} | ||
REDDIT_USERNAME: ${{ secrets.REDDIT_USERNAME }} | ||
REDDIT_PASSWORD: ${{ secrets.REDDIT_PASSWORD }} | ||
shell: bash | ||
run: | | ||
python -m pytest \ | ||
-rxXs \ | ||
--tb=native \ | ||
--verbose \ | ||
--cov=src \ | ||
tests | ||
- name: Upload coverage | ||
# any except canceled or skipped | ||
if: >- | ||
always() && | ||
(steps.test.outcome == 'success' || steps.test.outcome == 'failure') && | ||
startsWith(github.repository, 'LizardByte/') | ||
uses: codecov/codecov-action@v4 | ||
with: | ||
fail_ci_if_error: true | ||
token: ${{ secrets.CODECOV_TOKEN }} | ||
|
||
- name: Setup Release | ||
id: setup_release | ||
uses: LizardByte/[email protected] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
--- | ||
codecov: | ||
branch: master | ||
|
||
coverage: | ||
status: | ||
project: | ||
default: | ||
target: auto | ||
threshold: 10% | ||
|
||
comment: | ||
layout: "diff, flags, files" | ||
behavior: default | ||
require_changes: false # if true: only post the comment if coverage changes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
betamax==0.9.0 | ||
betamax-serializers==0.2.1 | ||
pytest==8.1.1 | ||
pytest-asyncio==0.23.6 | ||
pytest-cov==5.0.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# standard imports | ||
import asyncio | ||
import os | ||
import threading | ||
|
||
# lib imports | ||
import discord | ||
|
||
# local imports | ||
from src.common import bot_name, get_avatar_bytes, org_name | ||
from src.discord.tasks import daily_task | ||
from src.discord.views import DonateCommandView | ||
|
||
|
||
class Bot(discord.Bot): | ||
""" | ||
Discord bot class. | ||
This class extends the discord.Bot class to include additional functionality. The class will automatically | ||
enable all intents and sync commands on startup. The class will also update the bot presence, username, and avatar | ||
when the bot is ready. | ||
""" | ||
def __init__(self, *args, **kwargs): | ||
if 'intents' not in kwargs: | ||
intents = discord.Intents.all() | ||
kwargs['intents'] = intents | ||
if 'auto_sync_commands' not in kwargs: | ||
kwargs['auto_sync_commands'] = True | ||
super().__init__(*args, **kwargs) | ||
|
||
self.bot_thread = threading.Thread(target=lambda: None) | ||
self.token = os.environ['DISCORD_BOT_TOKEN'] | ||
|
||
self.load_extension( | ||
name='src.discord.cogs', | ||
recursive=True, | ||
store=False, | ||
) | ||
|
||
async def on_ready(self): | ||
""" | ||
Bot on ready event. | ||
This function runs when the discord bot is ready. The function will update the bot presence, update the username | ||
and avatar, and start daily tasks. | ||
""" | ||
print(f'py-cord version: {discord.__version__}') | ||
print(f'Logged in as {self.user.name} (ID: {self.user.id})') | ||
print(f'Servers connected to: {self.guilds}') | ||
|
||
# update the username and avatar | ||
avatar_img = get_avatar_bytes() | ||
if await self.user.avatar.read() != avatar_img or self.user.name != bot_name: | ||
await self.user.edit(username=bot_name, avatar=avatar_img) | ||
|
||
await self.change_presence( | ||
activity=discord.Activity(type=discord.ActivityType.watching, name=f"the {org_name} server") | ||
) | ||
|
||
self.add_view(DonateCommandView()) # register view for persistent listening | ||
|
||
await self.sync_commands() | ||
|
||
try: | ||
os.environ['DAILY_TASKS'] | ||
except KeyError: | ||
daily_task.start(bot=self) | ||
else: | ||
if os.environ['DAILY_TASKS'].lower() == 'true': | ||
daily_task.start(bot=self) | ||
else: | ||
print("'DAILY_TASKS' environment variable is disabled") | ||
|
||
def start_threaded(self): | ||
try: | ||
# Login the bot in a separate thread | ||
self.bot_thread = threading.Thread( | ||
target=self.loop.run_until_complete, | ||
args=(self.start(token=self.token),), | ||
daemon=True | ||
) | ||
self.bot_thread.start() | ||
except KeyboardInterrupt: | ||
print("Keyboard Interrupt Detected") | ||
self.stop() | ||
|
||
def stop(self, future: asyncio.Future = None): | ||
print("Attempting to stop daily tasks") | ||
daily_task.stop() | ||
print("Attempting to close bot connection") | ||
if self.bot_thread is not None and self.bot_thread.is_alive(): | ||
asyncio.run_coroutine_threadsafe(self.close(), self.loop) | ||
self.bot_thread.join() | ||
print("Closed bot") | ||
|
||
# Set a result for the future to mark it as done (unit testing) | ||
if future and not future.done(): | ||
future.set_result(None) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# lib imports | ||
import discord | ||
from discord.commands import Option | ||
|
||
# local imports | ||
from src.common import avatar, bot_name, org_name | ||
from src.discord.views import DonateCommandView | ||
from src.discord import cogs_common | ||
|
||
|
||
class BaseCommandsCog(discord.Cog): | ||
def __init__(self, bot): | ||
self.bot = bot | ||
|
||
@discord.slash_command( | ||
name="help", | ||
description=f"Get help with {bot_name}" | ||
) | ||
async def help_command( | ||
self, | ||
ctx: discord.ApplicationContext, | ||
): | ||
""" | ||
Get help with the bot. | ||
Parameters | ||
---------- | ||
ctx : discord.ApplicationContext | ||
Request message context. | ||
""" | ||
description = "" | ||
|
||
for cmd in self.bot.commands: | ||
if isinstance(cmd, discord.SlashCommandGroup): | ||
for sub_cmd in cmd.subcommands: | ||
description += await self.get_command_help(ctx=ctx, cmd=sub_cmd, group_name=cmd.name) | ||
else: | ||
description += await self.get_command_help(ctx=ctx, cmd=cmd) | ||
|
||
embed = discord.Embed(description=description, color=0xE5A00D) | ||
embed.set_footer(text=bot_name, icon_url=avatar) | ||
|
||
await ctx.respond(embed=embed, ephemeral=True) | ||
|
||
@staticmethod | ||
async def get_command_help( | ||
ctx: discord.ApplicationContext, | ||
cmd: discord.command, | ||
group_name=None, | ||
) -> str: | ||
description = "" | ||
permissions = cmd.default_member_permissions | ||
has_permissions = True | ||
if permissions: | ||
permissions_dict = {perm[0]: perm[1] for perm in permissions} | ||
has_permissions = all(getattr(ctx.author.guild_permissions, perm, False) for perm in permissions_dict) | ||
if has_permissions: | ||
doc_help = cmd.description | ||
if not doc_help: | ||
doc_lines = cmd.callback.__doc__.split('\n') | ||
doc_help = '\n'.join(line.strip() for line in doc_lines).split('\nParameters\n----------')[0].strip() | ||
if group_name: | ||
description = f"### `/{group_name} {cmd.name}`\n" | ||
else: | ||
description = f"### `/{cmd.name}`\n" | ||
description += f"{doc_help}\n" | ||
if cmd.options: | ||
description += "\n**Options:**\n" | ||
for option in cmd.options: | ||
description += (f"`{option.name}`: {option.description} " | ||
f"({'Required' if option.required else 'Optional'})\n") | ||
description += "\n" | ||
return description | ||
|
||
@discord.slash_command( | ||
name="donate", | ||
description=f"Support the development of {org_name}" | ||
) | ||
async def donate_command( | ||
self, | ||
ctx: discord.ApplicationContext, | ||
user: Option( | ||
discord.Member, | ||
description=cogs_common.user_mention_desc, | ||
required=False, | ||
), | ||
): | ||
""" | ||
Sends a discord view, with various donation urls, to the server and channel where the | ||
command was issued. | ||
Parameters | ||
---------- | ||
ctx : discord.ApplicationContext | ||
Request message context. | ||
user : discord.Member | ||
Username to mention in response. | ||
""" | ||
if user: | ||
await ctx.respond(f'Thank you for your support {user.mention}!', view=DonateCommandView()) | ||
else: | ||
await ctx.respond('Thank you for your support!', view=DonateCommandView()) | ||
|
||
|
||
def setup(bot: discord.Bot): | ||
bot.add_cog(BaseCommandsCog(bot=bot)) |
Oops, something went wrong.