Skip to content

Commit 59290e4

Browse files
authored
✨ add call api
1 parent 608fbec commit 59290e4

File tree

3 files changed

+196
-5
lines changed

3 files changed

+196
-5
lines changed

nonebot/adapters/github/adapter.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ async def _handle_webhook(
9494

9595
return Response(200, content="OK")
9696

97+
async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any:
98+
parts = api.split(".")
99+
func: Any = bot.github
100+
for part in parts:
101+
func = getattr(func, part)
102+
if not inspect.isroutine(func) or not inspect.iscoroutinefunction(func):
103+
raise TypeError(f"{api} is invalid.")
104+
return await func(**data)
105+
97106
@classmethod
98107
def payload_to_event(
99108
cls, event_id: str, event_name: str, payload: Union[str, bytes]

nonebot/adapters/github/bot.py

Lines changed: 169 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,98 @@
1-
from typing import TYPE_CHECKING, Union, Optional
1+
import re
2+
from typing_extensions import Self
3+
from contextlib import contextmanager
4+
from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, Generator
25

36
from nonebot.typing import overrides
7+
from githubkit.utils import UNSET, Unset
48
from nonebot.message import handle_event
5-
from githubkit import GitHub, AppAuthStrategy, OAuthAppAuthStrategy
9+
from githubkit import GitHub, AppAuthStrategy, TokenAuthStrategy, OAuthAppAuthStrategy
610

711
from nonebot.adapters import Bot as BaseBot
812

913
from .event import Event
14+
from .utils import APIContext
15+
from .message import MessageSegment
1016
from .config import OAuthApp, GitHubApp
1117

1218
if TYPE_CHECKING:
19+
from githubkit.rest import RestNamespace
20+
from githubkit.rest.types import AppPermissionsType
21+
1322
from .adapter import Adapter
1423

1524

25+
def _check_at_me(bot: "GitHubBot", event: Event) -> None:
26+
try:
27+
message = event.get_message()
28+
except Exception:
29+
return
30+
31+
# ensure message not empty
32+
if not message:
33+
message.append(MessageSegment.markdown(""))
34+
return
35+
36+
if message[0].type != "markdown":
37+
return
38+
if not bot._app_slug:
39+
return
40+
41+
seg = message[0]
42+
text = str(seg).lstrip()
43+
if text.startswith(f"@{bot._app_slug}"):
44+
message[0] = MessageSegment.markdown(text[len(bot._app_slug) + 1 :].lstrip())
45+
event.to_me = True
46+
47+
48+
def _check_nickname(bot: "Bot", event: Event) -> None:
49+
try:
50+
message = event.get_message()
51+
except Exception:
52+
return
53+
54+
# ensure message not empty
55+
if not message:
56+
message.append(MessageSegment.markdown(""))
57+
return
58+
59+
if message[0].type != "markdown":
60+
return
61+
62+
seg = message[0]
63+
text = str(seg).lstrip()
64+
65+
if nicknames := {nickname for nickname in bot.config.nickname if nickname}:
66+
# check if the user is calling me with my nickname
67+
nickname_regex = "|".join(nicknames)
68+
if m := re.match(rf"^({nickname_regex})([\s,,]*|$)", text, re.IGNORECASE):
69+
event.to_me = True
70+
message[0] = MessageSegment.markdown(text[m.end() :])
71+
72+
1673
class Bot(BaseBot):
17-
github: GitHub
74+
75+
if TYPE_CHECKING:
76+
rest: RestNamespace
77+
78+
async def async_graphql(
79+
self, query: str, variables: Optional[Dict[str, Any]] = None
80+
) -> Dict[str, Any]:
81+
...
1882

1983
@overrides(BaseBot)
2084
def __init__(self, adapter: "Adapter", app: Union[GitHubApp, OAuthApp]):
2185
super().__init__(adapter, app.id)
2286
self.app = app
87+
self._github: GitHub
88+
self._ctx_github: Optional[GitHub] = None
89+
90+
def __getattr__(self, name: str) -> APIContext:
91+
return APIContext(self, (name,))
92+
93+
@property
94+
def github(self) -> GitHub:
95+
return self._ctx_github or self._github
2396

2497
async def handle_event(self, event: Event) -> None:
2598
await handle_event(self, event)
@@ -33,14 +106,45 @@ class OAuthBot(Bot):
33106
@overrides(Bot)
34107
def __init__(self, adapter: "Adapter", app: OAuthApp):
35108
super().__init__(adapter, app)
36-
self._github = GitHub(OAuthAppAuthStrategy(app.client_id, app.client_secret))
109+
self._github: GitHub[OAuthAppAuthStrategy] = GitHub(
110+
OAuthAppAuthStrategy(app.client_id, app.client_secret)
111+
)
112+
113+
@contextmanager
114+
def as_web_user(
115+
self, code: str, redirect_uri: Optional[str] = None
116+
) -> Generator[Self, None, None]:
117+
if self._ctx_github is not None:
118+
raise RuntimeError("Can not enter context twice.")
119+
self._ctx_github = self._github.with_auth(
120+
self._github.auth.as_web_user(code, redirect_uri)
121+
)
122+
try:
123+
yield self
124+
finally:
125+
self._ctx_github = None
126+
127+
@contextmanager
128+
def as_user(self, token: str) -> Generator[Self, None, None]:
129+
if self._ctx_github is not None:
130+
raise RuntimeError("Can not enter context twice.")
131+
self._ctx_github = GitHub(TokenAuthStrategy(token))
132+
try:
133+
yield self
134+
finally:
135+
self._ctx_github = None
136+
137+
@overrides(Bot)
138+
async def handle_event(self, event: Event) -> None:
139+
_check_nickname(self, event)
140+
await super().handle_event(event)
37141

38142

39143
class GitHubBot(Bot):
40144
@overrides(Bot)
41145
def __init__(self, adapter: "Adapter", app: GitHubApp):
42146
super().__init__(adapter, app)
43-
self._github = GitHub(
147+
self._github: GitHub[AppAuthStrategy] = GitHub(
44148
AppAuthStrategy(
45149
app.app_id, app.private_key, app.client_id, app.client_secret
46150
)
@@ -52,3 +156,63 @@ async def _get_self_info(self):
52156
self._app_slug = (
53157
slug if isinstance((slug := res.parsed_data.slug), str) else None
54158
)
159+
160+
@contextmanager
161+
def as_oauth_app(self) -> Generator[Self, None, None]:
162+
if self._ctx_github is not None:
163+
raise RuntimeError("Can not enter context twice.")
164+
self._ctx_github = self._github.with_auth(self._github.auth.as_oauth_app())
165+
try:
166+
yield self
167+
finally:
168+
self._ctx_github = None
169+
170+
@contextmanager
171+
def as_installation(
172+
self,
173+
installation_id: int,
174+
repositories: Union[Unset, List[str]] = UNSET,
175+
repository_ids: Union[Unset, List[int]] = UNSET,
176+
permissions: Union[Unset, "AppPermissionsType"] = UNSET,
177+
) -> Generator[Self, None, None]:
178+
if self._ctx_github is not None:
179+
raise RuntimeError("Can not enter context twice.")
180+
self._ctx_github = self._github.with_auth(
181+
self._github.auth.as_installation(
182+
installation_id, repositories, repository_ids, permissions
183+
)
184+
)
185+
try:
186+
yield self
187+
finally:
188+
self._ctx_github = None
189+
190+
@contextmanager
191+
def as_web_user(
192+
self, code: str, redirect_uri: Optional[str] = None
193+
) -> Generator[Self, None, None]:
194+
if self._ctx_github is not None:
195+
raise RuntimeError("Can not enter context twice.")
196+
self._ctx_github = self._github.with_auth(
197+
self._github.auth.as_oauth_app().as_web_user(code, redirect_uri)
198+
)
199+
try:
200+
yield self
201+
finally:
202+
self._ctx_github = None
203+
204+
@contextmanager
205+
def as_user(self, token: str) -> Generator[Self, None, None]:
206+
if self._ctx_github is not None:
207+
raise RuntimeError("Can not enter context twice.")
208+
self._ctx_github = GitHub(TokenAuthStrategy(token))
209+
try:
210+
yield self
211+
finally:
212+
self._ctx_github = None
213+
214+
@overrides(Bot)
215+
async def handle_event(self, event: Event) -> None:
216+
_check_at_me(self, event)
217+
_check_nickname(self, event)
218+
await super().handle_event(event)

nonebot/adapters/github/utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
import re
2+
from typing import TYPE_CHECKING, Any, Tuple
23

34
from nonebot.utils import logger_wrapper
45

6+
if TYPE_CHECKING:
7+
from .bot import Bot
8+
59
log = logger_wrapper("GitHub")
610

711

812
def escape(content: str) -> str:
913
return re.sub(r"\\|`|\*|_|{|}|\[|\]|\(|\)|#|\+|-|\.|!", r"\\\1", content)
14+
15+
16+
class APIContext:
17+
__slots__ = ("bot", "parts")
18+
19+
def __init__(self, bot: "Bot", parts: Tuple[str, ...]):
20+
self.bot = bot
21+
self.parts = parts
22+
23+
def __getattr__(self, name: str) -> "APIContext":
24+
return APIContext(self.bot, self.parts + (name,))
25+
26+
async def __call__(self, **kwargs: Any) -> Any:
27+
return await self.bot.call_api(".".join(self.parts), **kwargs)

0 commit comments

Comments
 (0)