Skip to content

Commit

Permalink
feat: add /github command to add/view issues (#92)
Browse files Browse the repository at this point in the history
* feat(github): add `/github` command to add/view issues

* build: migrate from black to ruff formatter
  • Loading branch information
natelandau authored Nov 11, 2023
1 parent 675b8cf commit 7f654d5
Show file tree
Hide file tree
Showing 54 changed files with 577 additions and 110 deletions.
1 change: 1 addition & 0 deletions .github/workflows/automated-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ jobs:
poetry run coverage run
poetry run coverage report
poetry run coverage xml
# ----------------------------------------------
# upload coverage stats
# ----------------------------------------------
Expand Down
12 changes: 3 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,19 @@ repos:
types: [python]

- repo: "https://github.com/adrienverge/yamllint.git"
rev: v1.32.0
rev: v1.33.0
hooks:
- id: yamllint
files: ^.*\.(yaml|yml)$
entry: yamllint --strict --config-file .yamllint.yml

- repo: "https://github.com/charliermarsh/ruff-pre-commit"
rev: "v0.1.4"
rev: "v0.1.5"
hooks:
- id: ruff
args: ["--extend-ignore", "I001,D301,D401"]
exclude: tests/
- id: ruff-format

- repo: "https://github.com/jendrikseipp/vulture"
rev: "v2.10"
Expand All @@ -87,13 +88,6 @@ repos:
pass_filenames: true
types: [text]

- id: black
name: black
entry: black
require_serial: true
language: system
types: [python]

- id: shellcheck
name: shellcheck
entry: shellcheck --check-sourced --severity=warning
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Before running Valentina, the following must be configured or installed.
| VALENTINA_OWNER_IDS | | Sets the Discord user IDs that are allowed to run bot admin commands. This is a comma separated string of Discord user IDs. |
| VALENTINA_MONGO_URI | `mongodb://localhost:27017` | Production MongoDB URI |
| VALENTINA_MONGO_DATABASE_NAME | `valentina` | Production Database name |
| VALENTINA_GITHUB_REPO | | Optional: Sets the Github repo to use for Github integration `username/repo` |
| VALENTINA_GITHUB_TOKEN | | Optional: Sets the Github API Access token to use for Github integration |

---

Expand Down
405 changes: 334 additions & 71 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
numpy = "^1.26.1"
py-cord = "^2.4.1"
pydantic = "^2.4.2"
pygithub = "^2.1.1"
python = ">=3.11,<3.13"
python-dotenv = "^1.0.0"
rich = "^13.6.0"
Expand All @@ -45,22 +46,18 @@
pytest-xdist = "^3.3.1"

[tool.poetry.group.dev.dependencies]
black = "^23.11.0"
commitizen = "^3.12.0"
coverage = "^7.3.2"
mypy = "^1.6.1"
pdoc = "^14.0.0"
poethepoet = "^0.22.1"
pre-commit = "^3.5.0"
ruff = "^0.1.4"
ruff = "^0.1.5"
shellcheck-py = "^0.9.0.5"
types-aiofiles = "^23.2.0.0"
typos = "^1.16.23"
vulture = "^2.10"

[tool.black]
line-length = 100

[tool.commitizen]
bump_message = "bump(release): v$current_version → v$new_version"
changelog_merge_prerelease = true
Expand Down Expand Up @@ -191,6 +188,7 @@
"D413",
"E266",
"E501",
"ISC001",
"N805",
"PGH001",
"PGH003",
Expand Down Expand Up @@ -328,7 +326,7 @@
shell = "ruff --preview --no-fix src/"

[[tool.poe.tasks.lint.sequence]]
shell = "black --check src/ tests/"
shell = "ruff format --check src/ tests/"

[[tool.poe.tasks.lint.sequence]]
shell = "poetry check"
Expand Down
1 change: 1 addition & 0 deletions src/valentina/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
"""Valentina version."""

__version__ = "2.0.0"
1 change: 1 addition & 0 deletions src/valentina/characters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Models for working with characters."""

from .add_from_sheet import AddFromSheetWizard
from .chargen import CharGenWizard, RNGCharGen
from .reallocate_dots import DotsReallocationWizard
Expand Down
1 change: 1 addition & 0 deletions src/valentina/characters/add_from_sheet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""A wizard that walks the user through the character creation process."""

import asyncio
import uuid
from typing import Any
Expand Down
1 change: 1 addition & 0 deletions src/valentina/characters/buttons.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Buttons for character creation."""

from typing import cast

import discord
Expand Down
17 changes: 13 additions & 4 deletions src/valentina/characters/chargen.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""A RNG character generator for Valentina."""

import random
from typing import Literal, cast

Expand Down Expand Up @@ -94,7 +95,9 @@ def _disable_all(self) -> None:
row=2,
)
async def reroll_callback(
self, button: Button, interaction: discord.Interaction # noqa: ARG002
self,
button: Button, # noqa: ARG002
interaction: discord.Interaction,
) -> None:
"""Disable all buttons and stop the view."""
await interaction.response.defer()
Expand All @@ -109,7 +112,9 @@ async def reroll_callback(
row=2,
)
async def cancel_callback(
self, button: Button, interaction: discord.Interaction # noqa: ARG002
self,
button: Button, # noqa: ARG002
interaction: discord.Interaction,
) -> None:
"""Disable all buttons and stop the view."""
await interaction.response.defer()
Expand Down Expand Up @@ -220,7 +225,9 @@ def _disable_all(self) -> None:
row=2,
)
async def rename_callback(
self, button: Button, interaction: discord.Interaction # noqa: ARG002
self,
button: Button, # noqa: ARG002
interaction: discord.Interaction,
) -> None:
"""Callback for the rename button."""
self._disable_all()
Expand All @@ -240,7 +247,9 @@ async def rename_callback(
row=2,
)
async def reallocate_callback(
self, button: Button, interaction: discord.Interaction # noqa: ARG002
self,
button: Button, # noqa: ARG002
interaction: discord.Interaction,
) -> None:
"""Callback for the reallocate button."""
await interaction.response.defer()
Expand Down
1 change: 1 addition & 0 deletions src/valentina/characters/reallocate_dots.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""A wizard that walks the user through the character creation process."""

from typing import cast

import discord
Expand Down
4 changes: 3 additions & 1 deletion src/valentina/cogs/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mypy: disable-error-code="valid-type"
"""Administration commands for Valentina."""

from pathlib import Path

import discord
Expand Down Expand Up @@ -216,7 +217,8 @@ async def massban(
await assert_permissions(ctx, ban_members=True)
converter = MemberConverter()
converted_members = [
await converter.convert(ctx, member) for member in members.split() # type: ignore # mismatching context type
await converter.convert(ctx, member) # type: ignore # mismatching context type
for member in members.split()
]
if (count := len(converted_members)) > 10: # noqa: PLR2004
await present_embed(
Expand Down
4 changes: 3 additions & 1 deletion src/valentina/cogs/characters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mypy: disable-error-code="valid-type"
"""Character cog for Valentina."""

from pathlib import Path

import discord
Expand Down Expand Up @@ -234,7 +235,8 @@ async def list_characters(
) -> None:
"""List all player characters in this guild."""
all_characters = await Character.find_many(
Character.guild == ctx.guild.id, Character.type_player == True # noqa: E712
Character.guild == ctx.guild.id,
Character.type_player == True, # noqa: E712
).to_list()

if scope == "mine":
Expand Down
4 changes: 3 additions & 1 deletion src/valentina/cogs/developer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mypy: disable-error-code="valid-type"
"""Commands for bot development."""

from datetime import datetime
from pathlib import Path

Expand Down Expand Up @@ -183,7 +184,8 @@ async def delete_developer_characters(
) -> None:
"""Delete all developer characters from the database."""
dev_characters = await Character.find(
Character.type_developer == True, fetch_links=True # noqa: E712
Character.type_developer == True, # noqa: E712
fetch_links=True,
).to_list()

title = f"Delete `{len(dev_characters)}` developer {p.plural_noun('character', len(dev_characters))} characters from `{ctx.guild.name}`"
Expand Down
1 change: 1 addition & 0 deletions src/valentina/cogs/experience.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mypy: disable-error-code="valid-type"
"""Experience commands."""

import discord
import inflect
from discord.commands import Option
Expand Down
118 changes: 118 additions & 0 deletions src/valentina/cogs/github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# mypy: disable-error-code="valid-type"
"""Github cog for Valentina."""

import discord
from discord.commands import Option
from discord.ext import commands
from github import Auth, Github
from github.Repository import Repository
from loguru import logger

from valentina.constants import CONFIG, GithubIssueLabels
from valentina.models.bot import Valentina, ValentinaContext
from valentina.utils import errors
from valentina.views import present_embed


class GithubCog(commands.Cog):
"""Github Cog commands."""

def __init__(self, bot: Valentina) -> None:
self.bot: Valentina = bot
self.repo: Repository | None = None

async def fetch_github_repo(self) -> Repository:
"""Fetch the github repo."""
if self.repo:
return self.repo

msg = "Github"
if "VALENTINA_GITHUB_TOKEN" not in CONFIG or CONFIG["VALENTINA_GITHUB_TOKEN"] is None:
raise errors.ServiceDisabledError(msg)
token = CONFIG["VALENTINA_GITHUB_TOKEN"]

if "VALENTINA_GITHUB_REPO" not in CONFIG or CONFIG["VALENTINA_GITHUB_REPO"] is None:
raise errors.ServiceDisabledError(msg)
repo_name = CONFIG["VALENTINA_GITHUB_REPO"]

try:
auth = Auth.Token(token)
g = Github(auth=auth)
except Exception as e: # noqa: BLE001
logger.error(f"Error fetching github repo: {e}")
raise errors.ServiceDisabledError(msg) from e

self.repo = g.get_repo(repo_name)
return self.repo

github = discord.SlashCommandGroup("github", "Interact with the Valentina Noir Github repo")
issues = github.create_subgroup("issues", "Interact with Github issues")

@issues.command(name="list", description="List open issues")
async def issue_list(self, ctx: ValentinaContext) -> None:
"""List open Github issues."""
repo = await self.fetch_github_repo()
open_issues = repo.get_issues(state="open")
for issue in open_issues:
logger.info(issue)

if open_issues.totalCount == 0:
await ctx.send("No open issues")
return

issue_list = "\n- ".join(
[
f"**[{issue.title}]({issue.html_url})** `(#{issue.number})`"
for issue in sorted(open_issues, key=lambda x: x.number)
],
)
await present_embed(
ctx,
f"Listing {open_issues.totalCount} Open Github Issues",
description=f" - {issue_list}\n\n> - Use `/github issues get <issue number>` for details on a specific issue\n> - Use `/github issues add` to add a new issue\n> - View [all issues on Github]({repo.html_url}/issues)",
level="info",
footer="",
)

@issues.command(name="get", description="Get details for a specific issue")
async def issue_get(self, ctx: ValentinaContext, issue_number: int) -> None:
"""Get details for a specific Github issue."""
repo = await self.fetch_github_repo()
issue = repo.get_issue(number=issue_number)
await present_embed(
ctx,
f"Github Issue #{issue.number}",
description=f"### [{issue.title}]({issue.html_url})\n{issue.body}",
level="info",
)

@issues.command(name="add", description="Add a new issue")
async def issue_add(
self,
ctx: ValentinaContext,
title: Option(str, name="title", description="Title of the issue", required=True),
description: Option(
str, name="description", description="Description of the issue", required=True
),
type_of_issue: Option(
str,
name="type",
description="Type of issue",
required=True,
choices=[x.value for x in GithubIssueLabels],
),
) -> None:
"""Add a new Github issue."""
repo = await self.fetch_github_repo()
issue = repo.create_issue(title=title, body=description, labels=[type_of_issue])
await present_embed(
ctx,
"Issue Created",
description=f"### [{issue.title}]({issue.html_url})\n{description}",
level="info",
)


def setup(bot: Valentina) -> None:
"""Add the cog to the bot."""
bot.add_cog(GithubCog(bot))
5 changes: 4 additions & 1 deletion src/valentina/cogs/help.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mypy: disable-error-code="valid-type"
"""Help Command for Valentina."""

from pathlib import Path

import discord
Expand Down Expand Up @@ -73,7 +74,9 @@ def __build_help_text(self, cmd: commands.Command) -> str:

@help.command(name="commands", description="Help information for Valentina's commands")
async def command_help(
self, ctx: ValentinaContext, command: Option(str, required=False) # type: ignore
self,
ctx: ValentinaContext,
command: Option(str, required=False), # type: ignore
) -> None:
"""Provide help information."""
commands = self.__build_command_list(ctx)
Expand Down
5 changes: 4 additions & 1 deletion src/valentina/cogs/misc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mypy: disable-error-code="valid-type"
"""Miscellaneous commands."""

import random

import arrow
Expand Down Expand Up @@ -102,7 +103,9 @@ async def server_info(
embed.add_field(
name="Roll Statistics",
value=await roll_stats.guild_statistics(
as_embed=False, with_title=False, with_help=True # type: ignore [arg-type]
as_embed=False,
with_title=False,
with_help=True, # type: ignore [arg-type]
),
inline=False,
)
Expand Down
Loading

0 comments on commit 7f654d5

Please sign in to comment.