-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathplugin.py
More file actions
152 lines (129 loc) · 5.98 KB
/
Copy pathplugin.py
File metadata and controls
152 lines (129 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""GitHub Webhook Plugin core implementation."""
import json
from aiohttp import web
from astrbot.api import all as api
from astrbot.api.message_components import Plain
from astrbot.api.star import Context, Star
from astrbot.api import logger
from .config import PluginConfig
from .constants import DEFAULT_PORT
from ..handlers.issues_handler import handle_issues_event
from ..handlers.pull_request_handler import handle_pull_request_event
from ..handlers.push_handler import handle_push_event
from ..utils.rate_limiter import RateLimiter
from ..utils.verify_signature import verify_signature
class GitHubWebhookPlugin(Star):
"""GitHub Webhook receiver plugin."""
def __init__(self, context: Context, config):
super().__init__(context)
self.app = web.Application()
self.app.router.add_post("/webhook", self.handle_webhook)
self.runner = None
self.site = None
self.cfg = PluginConfig(config)
if self.cfg.rate_limit > 0:
self.rate_limiter = RateLimiter(max_requests=self.cfg.rate_limit)
else:
self.rate_limiter = None
async def start_server(self):
# Clean up any existing server instance
if self.site:
await self.site.stop()
logger.info("GitHub Webhook: Cleaned up existing server instance")
if self.runner:
await self.runner.cleanup()
logger.info("GitHub Webhook: Cleaned up existing runner")
self.runner = web.AppRunner(self.app)
await self.runner.setup()
self.site = web.TCPSite(self.runner, "0.0.0.0", self.cfg.port)
await self.site.start()
logger.info(f"GitHub Webhook: Server started on port {self.cfg.port}")
async def handle_webhook(self, request: web.Request):
event_type = request.headers.get("X-GitHub-Event", "unknown")
signature = request.headers.get("X-Hub-Signature-256", "")
# Rate limiting check
if self.rate_limiter:
is_allowed, retry_after = await self.rate_limiter.is_allowed()
if not is_allowed:
current, max_req = self.rate_limiter.get_usage()
logger.warning(
f"GitHub Webhook: Rate limit exceeded "
f"({current}/{max_req} requests/minute)"
)
return web.Response(
status=429,
text=f"Rate limit exceeded. Retry after {retry_after} seconds.",
headers={
"Retry-After": str(retry_after),
"X-RateLimit-Limit": str(max_req),
"X-RateLimit-Remaining": str(max_req - current),
"X-RateLimit-Reset": str(retry_after),
},
)
# Read payload
try:
payload_bytes = await request.read()
data = json.loads(payload_bytes.decode("utf-8"))
except json.JSONDecodeError as e:
logger.error(f"GitHub Webhook: Failed to parse JSON: {e}")
return web.Response(status=400, text="Invalid JSON")
except Exception as e:
logger.error(f"GitHub Webhook: Failed to read request body: {e}")
return web.Response(status=400, text="Failed to read request")
# Signature verification
if self.cfg.webhook_secret and signature:
if not verify_signature(payload_bytes, signature, self.cfg.webhook_secret):
logger.warning("GitHub Webhook: Invalid signature - request rejected")
return web.Response(status=401, text="Invalid signature")
logger.info(f"GitHub Webhook: Received event type: {event_type}")
if event_type == "ping":
return web.Response(text="Pong")
message = None
try:
if event_type == "push":
message = await handle_push_event(data, self.context)
elif event_type == "issues":
message = await handle_issues_event(data, self.context)
elif event_type == "pull_request":
message = await handle_pull_request_event(data, self.context)
else:
logger.info(f"GitHub Webhook: Event type '{event_type}' not handled")
except Exception as e:
logger.error(f"GitHub Webhook: Error processing event: {e}", exc_info=True)
return web.Response(status=500, text="Internal server error")
if message:
if self.cfg.enable_agent:
from ..services.llm_service import send_with_agent
await send_with_agent(self, message, data, event_type)
else:
await self.send_message(message)
return web.Response(status=200, text="OK")
async def send_message(self, message: str):
if not self.cfg.target_umo:
logger.error(
"GitHub Webhook: Cannot send message - target_umo not configured"
)
return
try:
message_chain = api.MessageChain([Plain(message)])
result = await self.context.send_message(self.cfg.target_umo, message_chain)
logger.info(
f"GitHub Webhook: Message sent to {self.cfg.target_umo}, result: {result}"
)
if not result:
logger.warning(
f"GitHub Webhook: Platform not found for {self.cfg.target_umo}"
)
except Exception as e:
# 记录完整错误信息但不传播异常
logger.error(f"GitHub Webhook: Failed to send message: {e}")
logger.error(f"GitHub Webhook: Error type: {type(e).__name__}")
logger.error(f"GitHub Webhook: Error details: {str(e)}")
async def terminate(self):
logger.info("GitHub Webhook: Shutting down server...")
if self.site:
await self.site.stop()
logger.info("GitHub Webhook: Server stopped")
if self.runner:
await self.runner.cleanup()
logger.info("GitHub Webhook: Runner cleaned up")