Skip to content

Commit

Permalink
feat: add create issue from message command (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
JacobCoffee committed Dec 11, 2023
1 parent 61e47b0 commit 18ee022
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 153 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,4 @@ dist
.pnp.*
!/src/byte/lib/
!/src/byte/lib/
*.pem
307 changes: 179 additions & 128 deletions pdm.lock

Large diffs are not rendered by default.

50 changes: 27 additions & 23 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ authors = [
]
dependencies = [
"python-dotenv>=1.0.0",
"discord-py>=2.3.1",
"pydantic>=2.5.0",
"litestar[jwt,opentelemetry,prometheus,standard,structlog]>=2.0.0b4",
"discord-py>=2.3.2",
"pydantic>=2.5.2",
"litestar[jwt,opentelemetry,prometheus,standard,structlog]>=2.4.3",
"pydantic-settings>=2.1.0",
"anyio>=3.7.1",
"advanced-alchemy>=0.5.5",
"anyio>=4.1.0",
"advanced-alchemy>=0.6.1",
"certifi>=2023.11.17",
"asyncpg>=0.29.0",
"githubkit[auth-app] @ git+https://github.com/yanyongyu/githubkit.git",
"PyJWT>=2.8.0",
]
requires-python = ">=3.11,<4.0"
readme = "README.md"
Expand Down Expand Up @@ -52,10 +54,12 @@ Youtube = 'https://www.youtube.com/@monorepo'
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.metadata]
allow-direct-references = true

[tool.hatch.version]
path = 'src/__metadata__.py'


[tool.pdm.scripts]
lint = "pre-commit run --all-files"
test = "pytest"
Expand All @@ -66,37 +70,37 @@ ci = { composite = ["lint", "test"] }

[tool.pdm.dev-dependencies]
test = [
"pytest>=7.4.0",
"coverage>=7.2.7",
"pytest>=7.4.3",
"coverage>=7.3.2",
"pytest-benchmark>=4.0.0",
"pytest-cov>=4.1.0",
"pytest-dotenv>=0.5.2",
"pytest-mock>=3.11.1",
"hypothesis>=6.82.0",
"pytest-asyncio>=0.21.1",
"pytest-mock>=3.12.0",
"hypothesis>=6.92.0",
"pytest-asyncio>=0.23.2",
]
docs = [
"sphinx>=7.0.1",
"sphinx>=7.2.6",
"sphinx-autobuild>=2021.3.14",
"sphinx-copybutton>=0.5.2",
"pydata-sphinx-theme>=0.13.3",
"sphinx-click>=4.4.0",
"sphinx-toolbox>=3.4.0",
"autodoc-pydantic>=1.9.0",
"shibuya>=2023.7.16",
"pydata-sphinx-theme>=0.14.4",
"sphinx-click>=5.1.0",
"sphinx-toolbox>=3.5.0",
"autodoc-pydantic>=2.0.1",
"shibuya>=2023.10.26",
"sphinxcontrib-mermaid>=0.9.2",
"sphinx-design>=0.5.0",
"sphinx-sqlalchemy>=0.2.0",
]
lint = [
"ruff>=0.0.280",
"codespell>=2.2.5",
"mypy>=1.4.1",
"pre-commit>=3.3.3",
"ruff>=0.1.7",
"codespell>=2.2.6",
"mypy>=1.7.1",
"pre-commit>=3.6.0",
"pytailwindcss>=0.2.0",
"sourcery>=1.6.0",
"sourcery>=1.14.0",
"pre-commit>=3.3.3",
"pyright>=1.1.334",
"pyright>=1.1.339",
]

[project.scripts]
Expand Down
30 changes: 29 additions & 1 deletion src/byte/plugins/custom/litestar.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Custom plugins for the Litestar Discord."""
from __future__ import annotations

from discord import Embed
from discord import Embed, Interaction, Message, app_commands
from discord.ext.commands import Bot, Cog, Context, command, group, is_owner

from src.byte.lib.utils import is_byte_dev, mention_role, mention_user
from src.server.domain.github.helpers import github_client

__all__ = ("LitestarCommands", "setup")

Expand All @@ -16,6 +17,8 @@ def __init__(self, bot: Bot) -> None:
"""Initialize cog."""
self.bot = bot
self.__cog_name__ = "Litestar Commands" # type: ignore[misc]
self.context_menu = app_commands.ContextMenu(name="Create GitHub Issue", callback=self.create_github_issue)
bot.tree.add_command(self.context_menu)

@group(name="litestar")
@is_byte_dev()
Expand Down Expand Up @@ -84,6 +87,31 @@ async def apply_role_embed(self, ctx: Context) -> None:

await ctx.send(embed=embed)

async def create_github_issue(self, interaction: Interaction, message: Message) -> None:
"""Context menu command to create a GitHub issue from a Discord message.
Args:
interaction: Interaction object.
message: Message object.
"""
issue_title = "Issue from Discord"
issue_body = message.content

try:
response_wrapper = await github_client.rest.issues.async_create(
owner="JacobCoffee", repo="byte", data={"title": issue_title, "body": issue_body}
)

if response_wrapper._response.is_success:
issue_data = response_wrapper._data_model.parse_obj(response_wrapper._response.json())
issue_url = issue_data.html_url
await interaction.response.send_message(f"GitHub Issue created: {issue_url}", ephemeral=True)
else:
await interaction.response.send_message("Issue creation failed.", ephemeral=True)

except Exception as e: # noqa: BLE001
await interaction.response.send_message(f"An error occurred: {e!s}", ephemeral=False)


async def setup(bot: Bot) -> None:
"""Add cog to bot.
Expand Down
1 change: 1 addition & 0 deletions src/server/domain/github/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""GitHub domain."""
18 changes: 18 additions & 0 deletions src/server/domain/github/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Helper functions for use within the GitHub domain."""
from __future__ import annotations

from githubkit import AppInstallationAuthStrategy, GitHub # type: ignore[reportMissingImports]

from src.server.lib import settings

__all__ = ("github_client",)

github_client = GitHub(
AppInstallationAuthStrategy(
app_id=settings.github.APP_ID,
private_key=settings.github.APP_PRIVATE_KEY,
installation_id=44969171,
client_id=settings.github.APP_CLIENT_ID,
client_secret=settings.github.APP_CLIENT_SECRET,
)
)
74 changes: 73 additions & 1 deletion src/server/lib/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,22 @@ class APISettings(BaseSettings):

HEALTH_PATH: str = "/health"
"""Route that the health check is served under."""
OPENCOLLECTIVE_KEY: str | None = None
"""OpenCollective API key."""
OPENCOLLECTIVE_URL: str = "https://api.opencollective.com/graphql/v2"
"""OpenCollective API URL.
.. note:: This is the GraphQL endpoint, the REST endpoint is no longer maintained.
See also: `OpenCollective API Docs <https://graphql-docs-v2.opencollective.com/>`_
"""
POLAR_KEY: str | None = None
"""Polar API key."""
POLAR_URL: str = "https://api.polar.sh"
"""Polar API URL.
.. seealso:: `Polar API Docs <https://api.polar.sh/docs>`_ and
the `Public API #834 Issue <https://github.com/polarsource/polar/issues/834>`_.
"""


class LogSettings(BaseSettings):
Expand Down Expand Up @@ -341,10 +357,63 @@ class DatabaseSettings(BaseSettings):
"""Name of the table used to track DDL version."""


class GitHubSettings(BaseSettings):
"""Configures GitHub app for the project."""

model_config = SettingsConfigDict(case_sensitive=True, env_file=".env", env_prefix="GITHUB_")

NAME: str = "byte-bot-app"
"""GitHub App name."""
APP_ID: int = 480575
"""GitHub App ID."""
APP_PRIVATE_KEY: str = ""
"""GitHub App private key."""
APP_CLIENT_ID: str = "Iv1.c3a5214c6642dedd"
"""GitHub App client ID."""
APP_CLIENT_SECRET: str = ""
"""GitHub App client secret."""
REDIRECT_URL: str = "http://127.0.0.1:3000/github/session"
"""GitHub App redirect URL."""
PERSONAL_ACCESS_TOKEN: str | None = None
"""GitHub personal access token."""

@field_validator("APP_PRIVATE_KEY", mode="before")
def validate_and_load_private_key(cls, value: str) -> str:
"""Validates and loads the GitHub App private key.
Args:
value: The value of the APP_PRIVATE_KEY setting.
Returns:
The validated and loaded GitHub App private key.
"""
environment = os.getenv("ENVIRONMENT", "dev")
if environment == "dev":
key_path = Path(BASE_DIR).parent / value

if key_path.is_file():
return key_path.read_text()
msg = f"Private key file not found at {key_path}"
raise ValueError(msg)

if not value.startswith("-----BEGIN RSA PRIVATE KEY") and not value.endswith("END RSA PRIVATE KEY-----"):
msg = "The GitHub private key must be a valid RSA key"
raise ValueError(msg)

return value


# noinspection PyShadowingNames
def load_settings() -> (
tuple[
ProjectSettings, APISettings, OpenAPISettings, TemplateSettings, ServerSettings, LogSettings, DatabaseSettings
ProjectSettings,
APISettings,
OpenAPISettings,
TemplateSettings,
ServerSettings,
LogSettings,
DatabaseSettings,
GitHubSettings,
]
):
"""Load Settings file.
Expand All @@ -364,6 +433,7 @@ def load_settings() -> (
template: TemplateSettings = TemplateSettings.model_validate({})
log: LogSettings = LogSettings.model_validate({})
database: DatabaseSettings = DatabaseSettings.model_validate({})
github: GitHubSettings = GitHubSettings.model_validate({})

except ValidationError as error:
print(f"Could not load settings. Error: {error!r}") # noqa: T201
Expand All @@ -376,6 +446,7 @@ def load_settings() -> (
server,
log,
database,
github,
)


Expand All @@ -387,4 +458,5 @@ def load_settings() -> (
server,
log,
db,
github,
) = load_settings()

0 comments on commit 18ee022

Please sign in to comment.