Skip to content

Commit 01e2bcf

Browse files
Add mention decorator for GitHub command handling (#90)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 91f1bd6 commit 01e2bcf

File tree

9 files changed

+1645
-13
lines changed

9 files changed

+1645
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
1818

1919
## [Unreleased]
2020

21+
### Added
22+
23+
- 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).
24+
2125
## [0.7.0]
2226

2327
### Added

README.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ cog.outl(f"- Django {', '.join([version for version in DJ_VERSIONS if version !=
211211
212212
## Getting Started
213213
214+
### Webhook Events
215+
214216
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.
215217
216218
To start handling GitHub webhooks, create your event handlers in a new file (e.g., `events.py`) within your Django app.
@@ -315,6 +317,78 @@ For more information about GitHub webhook events and payloads, see these pages i
315317
316318
For more details about how `gidgethub.sansio.Event` and webhook routing work, see the [gidgethub documentation](https://gidgethub.readthedocs.io).
317319
320+
### Mentions
321+
322+
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.
323+
324+
For ASGI projects:
325+
326+
```python
327+
# your_app/events.py
328+
import re
329+
from django_github_app.routing import GitHubRouter
330+
from django_github_app.mentions import MentionScope
331+
332+
gh = GitHubRouter()
333+
334+
# Respond to mentions of your bot
335+
@gh.mention(username="mybot")
336+
async def handle_bot_mention(event, gh, *args, context, **kwargs):
337+
"""Respond when someone mentions @mybot"""
338+
mention = context.mention
339+
issue_url = event.data["issue"]["comments_url"]
340+
341+
await gh.post(
342+
issue_url,
343+
data={"body": f"Hello! You mentioned me at position {mention.position}"},
344+
)
345+
346+
347+
# Use regex to match multiple bot names
348+
@gh.mention(username=re.compile(r".*-bot"))
349+
async def handle_any_bot(event, gh, *args, context, **kwargs):
350+
"""Respond to any mention ending with '-bot'"""
351+
mention = context.mention
352+
await gh.post(
353+
event.data["issue"]["comments_url"],
354+
data={"body": f"Bot {mention.username} at your service!"},
355+
)
356+
357+
358+
# Restrict to pull request mentions only
359+
@gh.mention(username="deploy-bot", scope=MentionScope.PR)
360+
async def handle_deploy_command(event, gh, *args, context, **kwargs):
361+
"""Only respond to @deploy-bot in pull requests"""
362+
await gh.post(
363+
event.data["issue"]["comments_url"], data={"body": "Starting deployment..."}
364+
)
365+
```
366+
367+
For WSGI projects:
368+
369+
```python
370+
# your_app/events.py
371+
import re
372+
from django_github_app.routing import GitHubRouter
373+
from django_github_app.mentions import MentionScope
374+
375+
gh = GitHubRouter()
376+
377+
# Respond to mentions of your bot
378+
@gh.mention(username="mybot")
379+
def handle_bot_mention(event, gh, *args, context, **kwargs):
380+
"""Respond when someone mentions @mybot"""
381+
mention = context.mention
382+
issue_url = event.data["issue"]["comments_url"]
383+
384+
gh.post(
385+
issue_url,
386+
data={"body": f"Hello! You mentioned me at position {mention.position}"},
387+
)
388+
```
389+
390+
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.
391+
318392
## Features
319393
320394
### GitHub API Client
@@ -485,6 +559,135 @@ The library includes event handlers for managing GitHub App installations and re
485559
486560
The library loads either async or sync versions of these handlers based on your `GITHUB_APP["WEBHOOK_TYPE"]` setting.
487561
562+
### Mentions
563+
564+
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.
565+
566+
The mention system:
567+
1. Monitors incoming webhook events for comments containing mentions
568+
2. Extracts all mentions while ignoring those in code blocks, inline code, or blockquotes
569+
3. Filters mentions based on your specified criteria (username pattern, scope)
570+
4. Calls your handler once for each matching mention, providing rich context
571+
572+
#### Context
573+
574+
Each handler receives a `context` parameter with detailed information about the mention:
575+
576+
```python
577+
@gh.mention(username="mybot")
578+
async def handle_mention(event, gh, *args, context, **kwargs):
579+
mention = context.mention
580+
581+
# Access mention details
582+
print(f"Username: {mention.username}") # "mybot"
583+
print(f"Position: {mention.position}") # Character position in comment
584+
print(f"Line: {mention.line_info.lineno}") # Line number (1-based)
585+
print(f"Line text: {mention.line_info.text}") # Full text of the line
586+
587+
# Navigate between mentions in the same comment
588+
if mention.previous_mention:
589+
print(f"Previous: @{mention.previous_mention.username}")
590+
if mention.next_mention:
591+
print(f"Next: @{mention.next_mention.username}")
592+
593+
# Check the scope (ISSUE, PR, or COMMIT)
594+
print(f"Scope: {context.scope}")
595+
```
596+
597+
#### Filtering Options
598+
599+
##### Username Patterns
600+
601+
Filter mentions by username using exact matches or regular expressions:
602+
603+
```python
604+
# Exact match (case-insensitive)
605+
@gh.mention(username="deploy-bot")
606+
def exact_match_mention():
607+
...
608+
609+
610+
# Regular expression pattern
611+
@gh.mention(username=re.compile(r".*-bot"))
612+
def regex_mention():
613+
...
614+
615+
616+
# Respond to all mentions (no filter)
617+
@gh.mention()
618+
def all_mentions():
619+
...
620+
```
621+
622+
##### Scopes
623+
624+
Limit mentions to specific GitHub contexts:
625+
626+
```python
627+
from django_github_app.mentions import MentionScope
628+
629+
# Only respond in issues (not PRs)
630+
@gh.mention(username="issue-bot", scope=MentionScope.ISSUE)
631+
def issue_mention():
632+
...
633+
634+
635+
# Only respond in pull requests
636+
@gh.mention(username="review-bot", scope=MentionScope.PR)
637+
def pull_request_mention():
638+
...
639+
640+
641+
# Only respond in commit comments
642+
@gh.mention(username="commit-bot", scope=MentionScope.COMMIT)
643+
def commit_mention():
644+
...
645+
```
646+
647+
Scope mappings:
648+
- `MentionScope.ISSUE`: Issue comments only
649+
- `MentionScope.PR`: PR comments, PR reviews, and PR review comments
650+
- `MentionScope.COMMIT`: Commit comments only
651+
652+
#### Parsing Rules
653+
654+
The mention parser follows GitHub's rules:
655+
656+
- **Valid mentions**: Must start with `@` followed by a GitHub username
657+
- **Username format**: 1-39 characters, alphanumeric or single hyphens, no consecutive hyphens
658+
- **Position**: Must be preceded by whitespace or start of line
659+
- **Exclusions**: Mentions in code blocks, inline code, or blockquotes are ignored
660+
661+
Examples:
662+
```
663+
@bot help ✓ Detected
664+
Hey @bot can you help? ✓ Detected
665+
@deploy-bot start ✓ Detected
666+
See @user's comment ✓ Detected
667+
668+
email@example.com ✗ Not a mention
669+
@@bot ✗ Invalid format
670+
`@bot help` ✗ Inside code
671+
```@bot in code``` ✗ Inside code block
672+
> @bot quoted ✗ Inside blockquote
673+
```
674+
675+
#### Multiple Mentions
676+
677+
When a comment contains multiple mentions, each matching mention triggers a separate handler call:
678+
679+
```python
680+
@gh.mention(username=re.compile(r".*-bot"))
681+
async def handle_bot_mention(event, gh, *args, context, **kwargs):
682+
mention = context.mention
683+
684+
# For comment: "@deploy-bot start @test-bot validate @user check"
685+
# This handler is called twice:
686+
# 1. For @deploy-bot (mention.username = "deploy-bot")
687+
# 2. For @test-bot (mention.username = "test-bot")
688+
# The @user mention is filtered out by the regex pattern
689+
```
690+
488691
### System Checks
489692
490693
The library includes Django system checks to validate your webhook configuration:

0 commit comments

Comments
 (0)