Skip to content

Add mention decorator for GitHub command handling #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9b14143
Add mention decorator for GitHub command handling
joshuadavidthomas Jun 17, 2025
d09b071
Add mention parsing and command extraction logic
joshuadavidthomas Jun 17, 2025
e4406a4
Add scope validation to mention decorator
joshuadavidthomas Jun 17, 2025
fdb03f3
Refactor check_event functions to accept sansio.Event
joshuadavidthomas Jun 17, 2025
82e07d0
Rename commands module to mentions and CommandScope to MentionScope
joshuadavidthomas Jun 17, 2025
25b2338
Add GitHub permission checking utilities
joshuadavidthomas Jun 17, 2025
0175403
Integrate permission checking into mention decorator
joshuadavidthomas Jun 18, 2025
0577ecd
Refactor mention decorator from gatekeeper to enrichment pattern
joshuadavidthomas Jun 18, 2025
27dba6c
Refactor mention system to use explicit re.Pattern API
joshuadavidthomas Jun 18, 2025
f139a7e
Simplify permission checking and remove optional return types
joshuadavidthomas Jun 18, 2025
b2b6a2d
Strip mention system down to core functionality
joshuadavidthomas Jun 18, 2025
f076165
Rename mention decorator kwarg from mention to context
joshuadavidthomas Jun 19, 2025
86801b8
Refactor get_event_scope to MentionScope.from_event classmethod
joshuadavidthomas Jun 19, 2025
7eef0c0
Refactor mention system for cleaner API and better encapsulation
joshuadavidthomas Jun 19, 2025
e33f5e2
Reorder mentions.py for better code organization
joshuadavidthomas Jun 19, 2025
f04dce4
Fix test fixtures and refactor decorator test to use stacked pattern
joshuadavidthomas Jun 19, 2025
e137cc0
Rename test fixtures for consistency between sync and async
joshuadavidthomas Jun 19, 2025
48bf085
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 19, 2025
d867379
Refactor mention parsing for clarity and maintainability
joshuadavidthomas Jun 19, 2025
d4c5d05
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 19, 2025
1785cfc
Use app settings SLUG as default mention pattern
joshuadavidthomas Jun 19, 2025
24465f8
Merge branch 'gh-command' of https://github.com/joshuadavidthomas/dja…
joshuadavidthomas Jul 25, 2025
1511264
Replace manual event creation with consolidated create_event fixture
joshuadavidthomas Jun 19, 2025
f2e1b24
Refactor tests to use faker and reduce manual field setting
joshuadavidthomas Jun 19, 2025
623e7e8
Remove unused mention handler attributes from routing decorator
joshuadavidthomas Jun 19, 2025
ecd9f52
Clean up comments and formatting
joshuadavidthomas Jun 19, 2025
3fe197d
adjust and refactor test suite for mentions
joshuadavidthomas Jun 20, 2025
3212976
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 20, 2025
f85a6f5
Simplify mention parsing and remove text extraction
joshuadavidthomas Jun 23, 2025
d396bd7
update sync version of mock_github_api client
joshuadavidthomas Jul 25, 2025
c250a4a
update README documentation for new feature
joshuadavidthomas Jul 25, 2025
71249f5
update changelog
joshuadavidthomas Jul 25, 2025
d3a8bab
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 25, 2025
28e6c66
blacken and lint readme
joshuadavidthomas Jul 25, 2025
1e10d7a
Merge branch 'gh-command' of https://github.com/joshuadavidthomas/dja…
joshuadavidthomas Jul 25, 2025
e2fe6a4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 25, 2025
3554ad4
tweak
joshuadavidthomas Jul 25, 2025
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/

## [Unreleased]

### Added

- Added `@gh.mention` decorator for handling GitHub mentions in comments. Supports filtering by username pattern (exact match or regex) and scope (issues, PRs, or commits).

## [0.7.0]

### Added
Expand Down
203 changes: 203 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ cog.outl(f"- Django {', '.join([version for version in DJ_VERSIONS if version !=

## Getting Started

### Webhook Events

django-github-app provides a router-based system for handling GitHub webhook events, built on top of [gidgethub](https://github.com/gidgethub/gidgethub). The router matches incoming webhooks to your handler functions based on the event type and optional action.

To start handling GitHub webhooks, create your event handlers in a new file (e.g., `events.py`) within your Django app.
Expand Down Expand Up @@ -315,6 +317,78 @@ For more information about GitHub webhook events and payloads, see these pages i

For more details about how `gidgethub.sansio.Event` and webhook routing work, see the [gidgethub documentation](https://gidgethub.readthedocs.io).

### Mentions

django-github-app provides a `@gh.mention` decorator to easily respond when your GitHub App is mentioned in comments. This is useful for building interactive bots that respond to user commands.

For ASGI projects:

```python
# your_app/events.py
import re
from django_github_app.routing import GitHubRouter
from django_github_app.mentions import MentionScope

gh = GitHubRouter()

# Respond to mentions of your bot
@gh.mention(username="mybot")
async def handle_bot_mention(event, gh, *args, context, **kwargs):
"""Respond when someone mentions @mybot"""
mention = context.mention
issue_url = event.data["issue"]["comments_url"]

await gh.post(
issue_url,
data={"body": f"Hello! You mentioned me at position {mention.position}"},
)


# Use regex to match multiple bot names
@gh.mention(username=re.compile(r".*-bot"))
async def handle_any_bot(event, gh, *args, context, **kwargs):
"""Respond to any mention ending with '-bot'"""
mention = context.mention
await gh.post(
event.data["issue"]["comments_url"],
data={"body": f"Bot {mention.username} at your service!"},
)


# Restrict to pull request mentions only
@gh.mention(username="deploy-bot", scope=MentionScope.PR)
async def handle_deploy_command(event, gh, *args, context, **kwargs):
"""Only respond to @deploy-bot in pull requests"""
await gh.post(
event.data["issue"]["comments_url"], data={"body": "Starting deployment..."}
)
```

For WSGI projects:

```python
# your_app/events.py
import re
from django_github_app.routing import GitHubRouter
from django_github_app.mentions import MentionScope

gh = GitHubRouter()

# Respond to mentions of your bot
@gh.mention(username="mybot")
def handle_bot_mention(event, gh, *args, context, **kwargs):
"""Respond when someone mentions @mybot"""
mention = context.mention
issue_url = event.data["issue"]["comments_url"]

gh.post(
issue_url,
data={"body": f"Hello! You mentioned me at position {mention.position}"},
)
```

The mention decorator automatically extracts mentions from comments and provides context about each mention. Handlers are called once for each matching mention in a comment.

## Features

### GitHub API Client
Expand Down Expand Up @@ -485,6 +559,135 @@ The library includes event handlers for managing GitHub App installations and re

The library loads either async or sync versions of these handlers based on your `GITHUB_APP["WEBHOOK_TYPE"]` setting.

### Mentions

The `@gh.mention` decorator provides a powerful way to build interactive GitHub Apps that respond to mentions in comments. When users mention your app (e.g., `@mybot help`), the decorator automatically detects these mentions and routes them to your handlers.

The mention system:
1. Monitors incoming webhook events for comments containing mentions
2. Extracts all mentions while ignoring those in code blocks, inline code, or blockquotes
3. Filters mentions based on your specified criteria (username pattern, scope)
4. Calls your handler once for each matching mention, providing rich context

#### Context

Each handler receives a `context` parameter with detailed information about the mention:

```python
@gh.mention(username="mybot")
async def handle_mention(event, gh, *args, context, **kwargs):
mention = context.mention

# Access mention details
print(f"Username: {mention.username}") # "mybot"
print(f"Position: {mention.position}") # Character position in comment
print(f"Line: {mention.line_info.lineno}") # Line number (1-based)
print(f"Line text: {mention.line_info.text}") # Full text of the line

# Navigate between mentions in the same comment
if mention.previous_mention:
print(f"Previous: @{mention.previous_mention.username}")
if mention.next_mention:
print(f"Next: @{mention.next_mention.username}")

# Check the scope (ISSUE, PR, or COMMIT)
print(f"Scope: {context.scope}")
```

#### Filtering Options

##### Username Patterns

Filter mentions by username using exact matches or regular expressions:

```python
# Exact match (case-insensitive)
@gh.mention(username="deploy-bot")
def exact_match_mention():
...


# Regular expression pattern
@gh.mention(username=re.compile(r".*-bot"))
def regex_mention():
...


# Respond to all mentions (no filter)
@gh.mention()
def all_mentions():
...
```

##### Scopes

Limit mentions to specific GitHub contexts:

```python
from django_github_app.mentions import MentionScope

# Only respond in issues (not PRs)
@gh.mention(username="issue-bot", scope=MentionScope.ISSUE)
def issue_mention():
...


# Only respond in pull requests
@gh.mention(username="review-bot", scope=MentionScope.PR)
def pull_request_mention():
...


# Only respond in commit comments
@gh.mention(username="commit-bot", scope=MentionScope.COMMIT)
def commit_mention():
...
```

Scope mappings:
- `MentionScope.ISSUE`: Issue comments only
- `MentionScope.PR`: PR comments, PR reviews, and PR review comments
- `MentionScope.COMMIT`: Commit comments only

#### Parsing Rules

The mention parser follows GitHub's rules:

- **Valid mentions**: Must start with `@` followed by a GitHub username
- **Username format**: 1-39 characters, alphanumeric or single hyphens, no consecutive hyphens
- **Position**: Must be preceded by whitespace or start of line
- **Exclusions**: Mentions in code blocks, inline code, or blockquotes are ignored

Examples:
```
@bot help ✓ Detected
Hey @bot can you help? ✓ Detected
@deploy-bot start ✓ Detected
See @user's comment ✓ Detected

[email protected] ✗ Not a mention
@@bot ✗ Invalid format
`@bot help` ✗ Inside code
```@bot in code``` ✗ Inside code block
> @bot quoted ✗ Inside blockquote
```

#### Multiple Mentions

When a comment contains multiple mentions, each matching mention triggers a separate handler call:

```python
@gh.mention(username=re.compile(r".*-bot"))
async def handle_bot_mention(event, gh, *args, context, **kwargs):
mention = context.mention

# For comment: "@deploy-bot start @test-bot validate @user check"
# This handler is called twice:
# 1. For @deploy-bot (mention.username = "deploy-bot")
# 2. For @test-bot (mention.username = "test-bot")
# The @user mention is filtered out by the regex pattern
```

### System Checks

The library includes Django system checks to validate your webhook configuration:
Expand Down
Loading