Skip to content

Commit

Permalink
Add bot code
Browse files Browse the repository at this point in the history
  • Loading branch information
No767 committed Oct 9, 2023
1 parent fb85568 commit 706d132
Show file tree
Hide file tree
Showing 22 changed files with 1,106 additions and 1 deletion.
51 changes: 51 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: "CodeQL"

on:
push:
branches:
- main

pull_request:
branches:
- main
schedule:
- cron: '36 7 * * 0'

jobs:
Analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Python 3.11
id: setup-python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Set up Poetry
uses: Gr1N/setup-poetry@v8
- name: Cache Poetry
id: cache-poetry
uses: actions/cache@v3
with:
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-codeql-python-${{ hashFiles('**/poetry.lock') }}
- name: Install Poetry Dependencies
if: steps.cache-poetry.outputs.cache-hit != 'true'
run: |
poetry install
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: python
setup-python-dependencies: false
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
upload: true
51 changes: 51 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Lint
on:
push:
branches:
- main

pull_request:
branches:
- main

jobs:
Analyze:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
version: [3.9, '3.10', '3.11']

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.version }}
id: setup-python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.version }}

- name: Set up Poetry
uses: Gr1N/setup-poetry@v8

- name: Cache Poetry
id: cache-poetry
uses: actions/cache@v3
with:
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-lint-${{ matrix.version }}-${{ hashFiles('**/poetry.lock') }}

- name: Install Poetry Dependencies
if: steps.cache-poetry.outputs.cache-hit != 'true'
run: |
poetry install --with dev
- name: Run Pyright
run: |
poetry run pyright bot
- name: Run Ruff
run: |
poetry run ruff bot
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Release
on:
push:
branches:
- main
jobs:
Release:
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '#major') || contains(github.event.head_commit.message, '#minor') || contains(github.event.head_commit.message, '#patch')
steps:
- uses: actions/checkout@v4
with:
fetch-depth: '0'

- name: Bump version and push tag
uses: anothrNick/[email protected]
id: tag_version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WITH_V: true
RELEASE_BRANCHES: main

- name: Release New Version
uses: ncipollo/release-action@v1
with:
bodyFile: "changelog.md"
token: ${{ secrets.PAT_TOKEN }}
tag: ${{ steps.tag_version.outputs.new_tag }}
name: ${{ steps.tag_version.outputs.new_tag }}
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,62 @@
# rodhaj
proto-repo for discord app

**later down the road, pls do not push to main branch directly**

## Stuff that needs to be done

- [x] Paginators
- [ ] R. Danny migrations or asyncpg-trek
- [ ] The features

## Getting Started

### Preface on Slash Commands

Unlike other frameworks, discord.py does not automatically sync slash commands (if you want to learn more why, see [this and why Noelle is heavily against it](https://github.com/No767/Zoee#preface-on-slash-commands-and-syncing)). So the way to sync is by using an prefixed commands, which is [Umbra's Sync Command](https://about.abstractumbra.dev/discord.py/2023/01/29/sync-command-example.html). More than likely you'll need to read up on how this slash command works in order to get started. In short, you'll probably want to sync your test bot to the guild instead (as demostrated here):

```
# Replace 1235 with your guild id
r>sync 1235
```


### Setup Instructions

You must have these installed:

- Poetry
- Python
- Git
- PostgreSQL

In order to run pg in a docker container, spin up the docker compose file
located in the root of the repo (`sudo docker compose up -d`).

1. Clone the repo or use it as a template.
2. Copy over the ENV file template to the `bot` directory

```bash
cp envs/dev.env bot/.env
```
3. Install the dependencies

```bash
poetry install
```

4. Configure the settings in the ENV (note that configuring the postgres uri is required)

5. Run the bot

```bash
poetry run python bot/launcher.py
```

6. Once your bot is running, sync the commands to your guild. You might have to wait a while because the syncing process usually takes some time. Once completed, you should now have the `CommandTree` synced to that guild.

```
# Replace 12345 with your guild id
r>sync 12345
```
7. Now go ahead and play around with the default commands. Add your own, delete some, do whatever you want now.
16 changes: 16 additions & 0 deletions bot/cogs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pkgutil import iter_modules
from typing import Literal, NamedTuple


class VersionInfo(NamedTuple):
major: int
minor: int
micro: int
releaselevel: Literal["alpha", "beta", "final"]

def __str__(self) -> str:
return f"{self.major}.{self.minor}.{self.micro}-{self.releaselevel}"


EXTENSIONS = [module.name for module in iter_modules(__path__, f"{__package__}.")]
VERSION: VersionInfo = VersionInfo(major=0, minor=1, micro=0, releaselevel="alpha")
81 changes: 81 additions & 0 deletions bot/cogs/dev_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from typing import Literal, Optional

import discord
from cogs import EXTENSIONS
from discord.ext import commands
from discord.ext.commands import Context, Greedy

from rodhaj import Rodhaj


class DevTools(commands.Cog, command_attrs=dict(hidden=True)):
"""Tools for developing RodHaj"""

def __init__(self, bot: Rodhaj):
self.bot = bot

# Umbra's sync command
# To learn more about it, see the link below (and ?tag ass on the dpy server):
# https://about.abstractumbra.dev/discord.py/2023/01/29/sync-command-example.html
@commands.guild_only()
@commands.is_owner()
@commands.command(name="sync", hidden=True)
async def sync(
self,
ctx: Context,
guilds: Greedy[discord.Object],
spec: Optional[Literal["~", "*", "^"]] = None,
) -> None:
"""Performs a sync of the tree. This will sync, copy globally, or clear the tree.
Args:
ctx (Context): Context of the command
guilds (Greedy[discord.Object]): Which guilds to sync to. Greedily accepts a number of guilds
spec (Optional[Literal["~", "*", "^"], optional): Specs to sync.
"""
await ctx.defer()
if not guilds:
if spec == "~":
synced = await self.bot.tree.sync(guild=ctx.guild)
elif spec == "*":
self.bot.tree.copy_global_to(guild=ctx.guild) # type: ignore
synced = await self.bot.tree.sync(guild=ctx.guild)
elif spec == "^":
self.bot.tree.clear_commands(guild=ctx.guild)
await self.bot.tree.sync(guild=ctx.guild)
synced = []
else:
synced = await self.bot.tree.sync()

await ctx.send(
f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}"
)
return

ret = 0
for guild in guilds:
try:
await self.bot.tree.sync(guild=guild)
except discord.HTTPException:
pass
else:
ret += 1

await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.")

@commands.guild_only()
@commands.is_owner()
@commands.command(name="reload-all", hidden=True)
async def reload_all(self, ctx: commands.Context) -> None:
"""Reloads all cogs. Used in production to not produce any downtime"""
if not hasattr(self.bot, "uptime"):
await ctx.send("Bot + exts must be up and loaded before doing this")
return

for extension in EXTENSIONS:
await self.bot.reload_extension(extension)
await ctx.send("Successfully reloaded all extensions live")


async def setup(bot: Rodhaj):
await bot.add_cog(DevTools(bot))
101 changes: 101 additions & 0 deletions bot/cogs/meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import datetime
import itertools
import platform

import discord
import psutil
import pygit2
from discord import app_commands
from discord.ext import commands
from discord.utils import format_dt
from libs.utils import Embed, human_timedelta

from rodhaj import Rodhaj


# A cog houses a category of commands
# Unlike djs, think of commands being stored as a category,
# which the cog is that category
class Meta(commands.Cog):
def __init__(self, bot: Rodhaj) -> None:
self.bot = bot
self.process = psutil.Process()

def get_bot_uptime(self, *, brief: bool = False) -> str:
return human_timedelta(
self.bot.uptime, accuracy=None, brief=brief, suffix=False
)

def format_commit(self, commit: pygit2.Commit) -> str:
short, _, _ = commit.message.partition("\n")
short_sha2 = commit.hex[0:6]
commit_tz = datetime.timezone(
datetime.timedelta(minutes=commit.commit_time_offset)
)
commit_time = datetime.datetime.fromtimestamp(commit.commit_time).astimezone(
commit_tz
)

# [`hash`](url) message (offset)
offset = format_dt(commit_time.astimezone(datetime.timezone.utc), "R")
return f"[`{short_sha2}`](https://github.com/transprogrammer/rodhaj/commit/{commit.hex}) {short} ({offset})"

def get_last_commits(self, count: int = 5):
repo = pygit2.Repository(".git")
commits = list(
itertools.islice(
repo.walk(repo.head.target, pygit2.GIT_SORT_TOPOLOGICAL), count
)
)
return "\n".join(self.format_commit(c) for c in commits)

@app_commands.command(name="about")
async def about(self, interaction: discord.Interaction) -> None:
"""Shows some stats for Rodhaj"""
total_members = 0
total_unique = len(self.bot.users)

for guild in self.bot.guilds:
total_members += guild.member_count or 0

# For Kumiko, it's done differently
# R. Danny's way of doing it is probably close enough anyways
memory_usage = self.process.memory_full_info().uss / 1024**2
cpu_usage = self.process.cpu_percent() / psutil.cpu_count()

revisions = self.get_last_commits()
embed = Embed()
embed.set_author(name=self.bot.user.name, icon_url=self.bot.user.display_avatar.url) # type: ignore
embed.title = "About Me"
embed.description = f"Latest Changes:\n {revisions}"
embed.set_footer(
text=f"Made with discord.py v{discord.__version__}",
icon_url="https://cdn.discordapp.com/emojis/596577034537402378.png?size=128",
)
embed.add_field(name="Servers Count", value=len(self.bot.guilds))
embed.add_field(
name="User Count", value=f"{total_members} total\n{total_unique} unique"
)
embed.add_field(
name="Process", value=f"{memory_usage:.2f} MiB\n{cpu_usage:.2f}% CPU"
)
embed.add_field(name="Python Version", value=platform.python_version())
embed.add_field(name="Version", value=str(self.bot.version))
embed.add_field(name="Uptime", value=self.get_bot_uptime(brief=True))
await interaction.response.send_message(embed=embed)

@app_commands.command(name="uptime")
async def uptime(self, interaction: discord.Interaction) -> None:
"""Displays the bot's uptime"""
uptime_message = f"Uptime: {self.get_bot_uptime()}"
await interaction.response.send_message(uptime_message)

@app_commands.command(name="version")
async def version(self, interaction: discord.Interaction) -> None:
"""Displays the current build version"""
version_message = f"Version: {self.bot.version}"
await interaction.response.send_message(version_message)


async def setup(bot: Rodhaj) -> None:
await bot.add_cog(Meta(bot))
Loading

0 comments on commit 706d132

Please sign in to comment.