oibot 2026.2.25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oibot-2026.2.25/.github/workflows/pypi.yml +18 -0
- oibot-2026.2.25/PKG-INFO +12 -0
- oibot-2026.2.25/README.md +3 -0
- oibot-2026.2.25/pyproject.toml +14 -0
- oibot-2026.2.25/src/oibot/__init__.py +0 -0
- oibot-2026.2.25/src/oibot/bot.py +147 -0
- oibot-2026.2.25/src/oibot/event/__init__.py +117 -0
- oibot-2026.2.25/src/oibot/event/c2c_message_create.py +159 -0
- oibot-2026.2.25/src/oibot/event/friend_add.py +63 -0
- oibot-2026.2.25/src/oibot/event/friend_del.py +27 -0
- oibot-2026.2.25/src/oibot/event/group_add_robot.py +60 -0
- oibot-2026.2.25/src/oibot/event/group_at_message_create.py +164 -0
- oibot-2026.2.25/src/oibot/event/group_del_robot.py +24 -0
- oibot-2026.2.25/src/oibot/matcher.py +191 -0
- oibot-2026.2.25/src/oibot/mixin/__init__.py +95 -0
- oibot-2026.2.25/src/oibot/mixin/access_token.py +57 -0
- oibot-2026.2.25/src/oibot/mixin/recall_message.py +41 -0
- oibot-2026.2.25/src/oibot/mixin/send_message.py +210 -0
- oibot-2026.2.25/src/oibot/mixin/upload_file.py +140 -0
- oibot-2026.2.25/src/oibot/plugin.py +407 -0
- oibot-2026.2.25/src/oibot/py.typed +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: pypi
|
|
2
|
+
|
|
3
|
+
on: push
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
pypi:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@master
|
|
10
|
+
- uses: actions/setup-python@master
|
|
11
|
+
with:
|
|
12
|
+
python-version: 3.x
|
|
13
|
+
- run: |
|
|
14
|
+
python -m pip install build --user
|
|
15
|
+
python -m build --sdist --wheel --outdir dist
|
|
16
|
+
- uses: pypa/gh-action-pypi-publish@master
|
|
17
|
+
with:
|
|
18
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
oibot-2026.2.25/PKG-INFO
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oibot
|
|
3
|
+
Version: 2026.2.25
|
|
4
|
+
Summary: a lightweight bot framework
|
|
5
|
+
Requires-Python: >=3
|
|
6
|
+
Requires-Dist: aiohttp
|
|
7
|
+
Requires-Dist: cryptography
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# OiBot
|
|
11
|
+
|
|
12
|
+
A lightweight bot framework built on [aiohttp](https://github.com/aio-libs/aiohttp) and the official protocol.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "oibot"
|
|
3
|
+
version = "2026.2.25"
|
|
4
|
+
description = "a lightweight bot framework"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"aiohttp",
|
|
9
|
+
"cryptography",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["hatchling"]
|
|
14
|
+
build-backend = "hatchling.build"
|
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from enum import IntEnum
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
from os import environ
|
|
7
|
+
from typing import Any, Iterable, Literal, NamedTuple, Optional
|
|
8
|
+
|
|
9
|
+
from aiohttp import ClientSession, web
|
|
10
|
+
from aiohttp.web_request import Request
|
|
11
|
+
from aiohttp.web_response import Response
|
|
12
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
13
|
+
|
|
14
|
+
from oibot.mixin.access_token import AccessTokenMixin
|
|
15
|
+
from oibot.mixin.recall_message import RecallMessageMixin
|
|
16
|
+
from oibot.mixin.send_message import SendMessageMixin
|
|
17
|
+
from oibot.mixin.upload_file import UploadFileMixin
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OP(IntEnum):
|
|
21
|
+
MESSAGE = 0
|
|
22
|
+
VERIFICATION = 13
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Bot(NamedTuple):
|
|
26
|
+
app_id: str
|
|
27
|
+
app_token: str
|
|
28
|
+
app_secret: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Singleton(type):
|
|
32
|
+
instance: Optional["OiBot"] = None
|
|
33
|
+
|
|
34
|
+
def __call__(cls, *args, **kwargs) -> "OiBot":
|
|
35
|
+
if not (instance := cls.instance):
|
|
36
|
+
cls.instance = instance = super().__call__(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
return instance
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class OiBot(
|
|
42
|
+
AccessTokenMixin,
|
|
43
|
+
RecallMessageMixin,
|
|
44
|
+
SendMessageMixin,
|
|
45
|
+
UploadFileMixin,
|
|
46
|
+
metaclass=Singleton,
|
|
47
|
+
):
|
|
48
|
+
__slots__ = ("app", "plugin_manager", "bot")
|
|
49
|
+
|
|
50
|
+
def __init__(self, plugins: str | Iterable[str] | None = None, **kwargs) -> None:
|
|
51
|
+
from oibot.plugin import PluginManager
|
|
52
|
+
|
|
53
|
+
self.plugin_manager = plugin_manager = PluginManager(self)
|
|
54
|
+
|
|
55
|
+
if isinstance(plugins, str):
|
|
56
|
+
plugin_manager.import_from(plugins)
|
|
57
|
+
|
|
58
|
+
elif isinstance(plugins, Iterable):
|
|
59
|
+
for plugin in plugins:
|
|
60
|
+
plugin_manager.import_from(plugin)
|
|
61
|
+
|
|
62
|
+
self.app = app = web.Application(**kwargs)
|
|
63
|
+
|
|
64
|
+
app.router.add_post(path="/", handler=self.handler)
|
|
65
|
+
|
|
66
|
+
self.bot: ContextVar[Bot] = ContextVar("bot")
|
|
67
|
+
|
|
68
|
+
async def __call__(
|
|
69
|
+
self,
|
|
70
|
+
method: Literal[
|
|
71
|
+
"GET",
|
|
72
|
+
"POST",
|
|
73
|
+
"PUT",
|
|
74
|
+
"DELETE",
|
|
75
|
+
"PATCH",
|
|
76
|
+
"HEAD",
|
|
77
|
+
"OPTIONS",
|
|
78
|
+
"TRACE",
|
|
79
|
+
"CONNECT",
|
|
80
|
+
],
|
|
81
|
+
url: str,
|
|
82
|
+
**kwargs,
|
|
83
|
+
) -> Any:
|
|
84
|
+
logging.debug(f"{method=} {url=} {kwargs=}")
|
|
85
|
+
|
|
86
|
+
async with ClientSession(
|
|
87
|
+
base_url="https://api.sgroup.qq.com", # "https://sandbox.api.sgroup.qq.com"
|
|
88
|
+
) as session:
|
|
89
|
+
async with session.request(method, url, **kwargs) as resp:
|
|
90
|
+
resp.raise_for_status()
|
|
91
|
+
|
|
92
|
+
return await resp.json()
|
|
93
|
+
|
|
94
|
+
async def handler(
|
|
95
|
+
self, request: Request, *, background_tasks: set[asyncio.Task] = set()
|
|
96
|
+
) -> Response:
|
|
97
|
+
ctx = await request.json()
|
|
98
|
+
|
|
99
|
+
logging.debug(ctx)
|
|
100
|
+
|
|
101
|
+
self.bot.set(
|
|
102
|
+
bot := Bot(
|
|
103
|
+
app_id=request.query.get("id", environ["OIBOT_APP_ID"]),
|
|
104
|
+
app_token=request.query.get("token", environ["OIBOT_APP_TOKEN"]),
|
|
105
|
+
app_secret=request.query.get("secret", environ["OIBOT_APP_SECRET"]),
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
match ctx["op"]:
|
|
110
|
+
case OP.MESSAGE:
|
|
111
|
+
background_tasks.add(
|
|
112
|
+
task := asyncio.create_task(self.plugin_manager(ctx))
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
task.add_done_callback(background_tasks.discard)
|
|
116
|
+
|
|
117
|
+
case OP.VERIFICATION:
|
|
118
|
+
logging.info("webhook verification request received")
|
|
119
|
+
|
|
120
|
+
if not (secret := bot.app_secret):
|
|
121
|
+
raise ValueError("parameter `app_secret` must be specified")
|
|
122
|
+
|
|
123
|
+
secret = secret.encode("utf-8")
|
|
124
|
+
|
|
125
|
+
while len(secret) < 32:
|
|
126
|
+
secret *= 2
|
|
127
|
+
|
|
128
|
+
d = ctx["d"]
|
|
129
|
+
|
|
130
|
+
return web.json_response(
|
|
131
|
+
{
|
|
132
|
+
"plain_token": d["plain_token"],
|
|
133
|
+
"signature": (
|
|
134
|
+
Ed25519PrivateKey.from_private_bytes(secret[:32])
|
|
135
|
+
.sign(f"{d['event_ts']}{d['plain_token']}".encode("utf-8"))
|
|
136
|
+
.hex()
|
|
137
|
+
),
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
case _:
|
|
142
|
+
logging.warning(f"invalid type received {ctx=}")
|
|
143
|
+
|
|
144
|
+
return web.Response(body=None, status=HTTPStatus.OK)
|
|
145
|
+
|
|
146
|
+
def run(self, *args, **kwargs) -> None:
|
|
147
|
+
web.run_app(self.app, *args, **kwargs)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import ClassVar, TypedDict
|
|
4
|
+
|
|
5
|
+
from oibot.bot import OiBot
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EventType(StrEnum):
|
|
9
|
+
AT_MESSAGE_CREATE = "AT_MESSAGE_CREATE"
|
|
10
|
+
AUDIO_FINISH = "AUDIO_FINISH"
|
|
11
|
+
AUDIO_OR_LIVE_CHANNEL_MEMBER_ENTER = "AUDIO_OR_LIVE_CHANNEL_MEMBER_ENTER"
|
|
12
|
+
AUDIO_OR_LIVE_CHANNEL_MEMBER_EXIT = "AUDIO_OR_LIVE_CHANNEL_MEMBER_EXIT"
|
|
13
|
+
AUDIO_START = "AUDIO_START"
|
|
14
|
+
C2C_MESSAGE_CREATE = "C2C_MESSAGE_CREATE"
|
|
15
|
+
C2C_MSG_RECEIVE = "C2C_MSG_RECEIVE"
|
|
16
|
+
C2C_MSG_REJECT = "C2C_MSG_REJECT"
|
|
17
|
+
CHANNEL_CREATE = "CHANNEL_CREATE"
|
|
18
|
+
CHANNEL_DELETE = "CHANNEL_DELETE"
|
|
19
|
+
CHANNEL_UPDATE = "CHANNEL_UPDATE"
|
|
20
|
+
DIRECT_MESSAGE_CREATE = "DIRECT_MESSAGE_CREATE"
|
|
21
|
+
DIRECT_MESSAGE_DELETE = "DIRECT_MESSAGE_DELETE"
|
|
22
|
+
FORUM_POST_CREATE = "FORUM_POST_CREATE"
|
|
23
|
+
FORUM_POST_DELETE = "FORUM_POST_DELETE"
|
|
24
|
+
FORUM_PUBLISH_AUDIT_RESULT = "FORUM_PUBLISH_AUDIT_RESULT"
|
|
25
|
+
FORUM_REPLY_CREATE = "FORUM_REPLY_CREATE"
|
|
26
|
+
FORUM_REPLY_DELETE = "FORUM_REPLY_DELETE"
|
|
27
|
+
FORUM_THREAD_CREATE = "FORUM_THREAD_CREATE"
|
|
28
|
+
FORUM_THREAD_DELETE = "FORUM_THREAD_DELETE"
|
|
29
|
+
FORUM_THREAD_UPDATE = "FORUM_THREAD_UPDATE"
|
|
30
|
+
FRIEND_ADD = "FRIEND_ADD"
|
|
31
|
+
FRIEND_DEL = "FRIEND_DEL"
|
|
32
|
+
GROUP_ADD_ROBOT = "GROUP_ADD_ROBOT"
|
|
33
|
+
GROUP_AT_MESSAGE_CREATE = "GROUP_AT_MESSAGE_CREATE"
|
|
34
|
+
GROUP_DEL_ROBOT = "GROUP_DEL_ROBOT"
|
|
35
|
+
GROUP_MSG_RECEIVE = "GROUP_MSG_RECEIVE"
|
|
36
|
+
GROUP_MSG_REJECT = "GROUP_MSG_REJECT"
|
|
37
|
+
GUILD_CREATE = "GUILD_CREATE"
|
|
38
|
+
GUILD_DELETE = "GUILD_DELETE"
|
|
39
|
+
GUILD_MEMBER_ADD = "GUILD_MEMBER_ADD"
|
|
40
|
+
GUILD_MEMBER_REMOVE = "GUILD_MEMBER_REMOVE"
|
|
41
|
+
GUILD_MEMBER_UPDATE = "GUILD_MEMBER_UPDATE"
|
|
42
|
+
GUILD_UPDATE = "GUILD_UPDATE"
|
|
43
|
+
INTERACTION_CREATE = "INTERACTION_CREATE"
|
|
44
|
+
MESSAGE_AUDIT_PASS = "MESSAGE_AUDIT_PASS"
|
|
45
|
+
MESSAGE_AUDIT_REJECT = "MESSAGE_AUDIT_REJECT"
|
|
46
|
+
MESSAGE_CREATE = "MESSAGE_CREATE"
|
|
47
|
+
MESSAGE_DELETE = "MESSAGE_DELETE"
|
|
48
|
+
MESSAGE_REACTION_ADD = "MESSAGE_REACTION_ADD"
|
|
49
|
+
MESSAGE_REACTION_REMOVE = "MESSAGE_REACTION_REMOVE"
|
|
50
|
+
OFF_MIC = "OFF_MIC"
|
|
51
|
+
ON_MIC = "ON_MIC"
|
|
52
|
+
OPEN_FORUM_POST_CREATE = "OPEN_FORUM_POST_CREATE"
|
|
53
|
+
OPEN_FORUM_POST_DELETE = "OPEN_FORUM_POST_DELETE"
|
|
54
|
+
OPEN_FORUM_REPLY_CREATE = "OPEN_FORUM_REPLY_CREATE"
|
|
55
|
+
OPEN_FORUM_REPLY_DELETE = "OPEN_FORUM_REPLY_DELETE"
|
|
56
|
+
OPEN_FORUM_THREAD_CREATE = "OPEN_FORUM_THREAD_CREATE"
|
|
57
|
+
OPEN_FORUM_THREAD_DELETE = "OPEN_FORUM_THREAD_DELETE"
|
|
58
|
+
OPEN_FORUM_THREAD_UPDATE = "OPEN_FORUM_THREAD_UPDATE"
|
|
59
|
+
PUBLIC_MESSAGE_DELETE = "PUBLIC_MESSAGE_DELETE"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Context(TypedDict):
|
|
63
|
+
d: dict
|
|
64
|
+
id: str
|
|
65
|
+
op: int
|
|
66
|
+
s: int
|
|
67
|
+
t: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Event:
|
|
71
|
+
__slots__ = ("bot", "_d", "_id", "_op", "_s", "_t")
|
|
72
|
+
|
|
73
|
+
_d: dict
|
|
74
|
+
_id: str
|
|
75
|
+
_op: int
|
|
76
|
+
_s: int
|
|
77
|
+
_t: str
|
|
78
|
+
|
|
79
|
+
event_type: ClassVar[EventType]
|
|
80
|
+
|
|
81
|
+
event: ClassVar[dict[str, type["Event"]]] = {}
|
|
82
|
+
|
|
83
|
+
def __new__(cls, bot: OiBot, ctx: Context) -> "Event":
|
|
84
|
+
return (
|
|
85
|
+
event.__new__(event, bot, ctx)
|
|
86
|
+
if (event := cls.dispatch(ctx)) is not cls
|
|
87
|
+
else super().__new__(cls)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def __init__(self, bot: OiBot, ctx: Context) -> None:
|
|
91
|
+
self.bot = bot
|
|
92
|
+
|
|
93
|
+
self._d = ctx["d"]
|
|
94
|
+
self._id = ctx["id"]
|
|
95
|
+
self._op = ctx["op"]
|
|
96
|
+
# self._s = ctx["s"]
|
|
97
|
+
self._t = ctx["t"]
|
|
98
|
+
|
|
99
|
+
logging.info(self.__repr__())
|
|
100
|
+
|
|
101
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
102
|
+
logging.debug(f"registered {cls} as type {cls._t}")
|
|
103
|
+
|
|
104
|
+
Event.event[cls.event_type] = cls
|
|
105
|
+
|
|
106
|
+
def __repr__(self) -> str:
|
|
107
|
+
return f"""{self.__class__.__name__}({
|
|
108
|
+
", ".join(
|
|
109
|
+
f"{item}={value}"
|
|
110
|
+
for item in self.__slots__
|
|
111
|
+
if (not item.startswith("__")) and (value := getattr(self, item, None))
|
|
112
|
+
)
|
|
113
|
+
})"""
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def dispatch(cls, ctx: Context) -> type["Event"]:
|
|
117
|
+
return cls.event.get(ctx["t"], cls)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from itertools import count
|
|
4
|
+
from typing import ClassVar, Literal, NamedTuple
|
|
5
|
+
|
|
6
|
+
from oibot.bot import OiBot
|
|
7
|
+
from oibot.event import Context, Event
|
|
8
|
+
from oibot.mixin.send_message import (
|
|
9
|
+
Ark,
|
|
10
|
+
Embed,
|
|
11
|
+
Keyboard,
|
|
12
|
+
Markdown,
|
|
13
|
+
Media,
|
|
14
|
+
MessageReference,
|
|
15
|
+
SendMessageResponse,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class C2CMessageCreateEvent(Event):
|
|
20
|
+
__slots__ = (
|
|
21
|
+
"id",
|
|
22
|
+
"content",
|
|
23
|
+
"timestamp",
|
|
24
|
+
"author",
|
|
25
|
+
"attachments",
|
|
26
|
+
"message_scene",
|
|
27
|
+
"message_type",
|
|
28
|
+
"msg_seq",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
class Author(NamedTuple):
|
|
32
|
+
id: str
|
|
33
|
+
user_openid: str
|
|
34
|
+
union_openid: str
|
|
35
|
+
|
|
36
|
+
class Attachment(NamedTuple):
|
|
37
|
+
content_type: str
|
|
38
|
+
filename: str
|
|
39
|
+
height: int
|
|
40
|
+
size: int
|
|
41
|
+
url: str
|
|
42
|
+
width: int
|
|
43
|
+
|
|
44
|
+
class MessageScene(NamedTuple):
|
|
45
|
+
source: str
|
|
46
|
+
callback_data: str
|
|
47
|
+
|
|
48
|
+
event_type: ClassVar[Literal["C2C_MESSAGE_CREATE"]] = "C2C_MESSAGE_CREATE"
|
|
49
|
+
|
|
50
|
+
id: str
|
|
51
|
+
content: str
|
|
52
|
+
timestamp: datetime
|
|
53
|
+
author: Author
|
|
54
|
+
attachments: list[Attachment]
|
|
55
|
+
|
|
56
|
+
message_scene: MessageScene
|
|
57
|
+
message_type: int
|
|
58
|
+
|
|
59
|
+
def __init__(self, bot: OiBot, ctx: Context) -> None:
|
|
60
|
+
d = ctx["d"]
|
|
61
|
+
|
|
62
|
+
self.id = d["id"]
|
|
63
|
+
self.content = d["content"]
|
|
64
|
+
self.timestamp = d["timestamp"]
|
|
65
|
+
|
|
66
|
+
self.author = self.Author(
|
|
67
|
+
id=d["author"]["id"],
|
|
68
|
+
user_openid=d["author"]["user_openid"],
|
|
69
|
+
union_openid=d["author"]["union_openid"],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
self.attachments = [
|
|
73
|
+
self.Attachment(
|
|
74
|
+
content_type=attachment["content_type"],
|
|
75
|
+
filename=attachment["filename"],
|
|
76
|
+
height=attachment["height"],
|
|
77
|
+
size=attachment["size"],
|
|
78
|
+
url=attachment["url"],
|
|
79
|
+
width=attachment["width"],
|
|
80
|
+
)
|
|
81
|
+
for attachment in d.get("attachments", [])
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
self.message_scene = self.MessageScene(
|
|
85
|
+
source=d["message_scene"]["source"],
|
|
86
|
+
callback_data=d["message_scene"]["callback_data"],
|
|
87
|
+
)
|
|
88
|
+
self.message_type = d["message_type"]
|
|
89
|
+
|
|
90
|
+
super().__init__(bot, ctx)
|
|
91
|
+
|
|
92
|
+
self.msg_seq = count()
|
|
93
|
+
|
|
94
|
+
async def reply(
|
|
95
|
+
self,
|
|
96
|
+
content: str | None = None,
|
|
97
|
+
markdown: Markdown | None = None,
|
|
98
|
+
keyboard: Keyboard | None = None,
|
|
99
|
+
embed: Embed | None = None,
|
|
100
|
+
ark: Ark | None = None,
|
|
101
|
+
media: Media | None = None,
|
|
102
|
+
message_reference: MessageReference | None = None,
|
|
103
|
+
event_id: str | None = None,
|
|
104
|
+
msg_seq: int = 0,
|
|
105
|
+
) -> SendMessageResponse:
|
|
106
|
+
return await self.bot.send_private_message(
|
|
107
|
+
openid=self.author.union_openid,
|
|
108
|
+
content=content,
|
|
109
|
+
markdown=markdown,
|
|
110
|
+
keyboard=keyboard,
|
|
111
|
+
embed=embed,
|
|
112
|
+
ark=ark,
|
|
113
|
+
media=media,
|
|
114
|
+
message_reference=message_reference,
|
|
115
|
+
event_id=event_id,
|
|
116
|
+
msg_id=self.id,
|
|
117
|
+
msg_seq=msg_seq or next(self.msg_seq),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
async def defer(
|
|
121
|
+
self,
|
|
122
|
+
content: str | None = None,
|
|
123
|
+
markdown: Markdown | None = None,
|
|
124
|
+
keyboard: Keyboard | None = None,
|
|
125
|
+
embed: Embed | None = None,
|
|
126
|
+
ark: Ark | None = None,
|
|
127
|
+
media: Media | None = None,
|
|
128
|
+
message_reference: MessageReference | None = None,
|
|
129
|
+
event_id: str | None = None,
|
|
130
|
+
msg_seq: int = 0,
|
|
131
|
+
) -> "C2CMessageCreateEvent":
|
|
132
|
+
self.bot.plugin_manager.sessions[
|
|
133
|
+
key := (
|
|
134
|
+
self.bot.bot.get(),
|
|
135
|
+
getattr(self, "group_openid", None),
|
|
136
|
+
(
|
|
137
|
+
getattr(self, "author.member_openid", None)
|
|
138
|
+
or getattr(self, "author.user_openid", None)
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
] = future = asyncio.get_running_loop().create_future()
|
|
142
|
+
|
|
143
|
+
await self.reply(
|
|
144
|
+
content=content,
|
|
145
|
+
markdown=markdown,
|
|
146
|
+
keyboard=keyboard,
|
|
147
|
+
embed=embed,
|
|
148
|
+
ark=ark,
|
|
149
|
+
media=media,
|
|
150
|
+
message_reference=message_reference,
|
|
151
|
+
event_id=event_id,
|
|
152
|
+
msg_seq=msg_seq,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
return await future
|
|
157
|
+
|
|
158
|
+
finally:
|
|
159
|
+
self.bot.plugin_manager.sessions.pop(key, None)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import ClassVar, Literal, NamedTuple
|
|
3
|
+
|
|
4
|
+
from oibot.bot import OiBot
|
|
5
|
+
from oibot.event import Context, Event
|
|
6
|
+
from oibot.mixin.send_message import (
|
|
7
|
+
Ark,
|
|
8
|
+
Embed,
|
|
9
|
+
Keyboard,
|
|
10
|
+
Markdown,
|
|
11
|
+
Media,
|
|
12
|
+
MessageReference,
|
|
13
|
+
SendMessageResponse,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FriendAddEvent(Event):
|
|
18
|
+
__slots__ = ("timestamp", "openid", "author")
|
|
19
|
+
|
|
20
|
+
class Author(NamedTuple):
|
|
21
|
+
union_openid: str
|
|
22
|
+
|
|
23
|
+
event_type: ClassVar[Literal["FRIEND_ADD"]] = "FRIEND_ADD"
|
|
24
|
+
|
|
25
|
+
timestamp: datetime
|
|
26
|
+
openid: str
|
|
27
|
+
author: Author
|
|
28
|
+
|
|
29
|
+
def __init__(self, bot: OiBot, ctx: Context) -> None:
|
|
30
|
+
d = ctx["d"]
|
|
31
|
+
|
|
32
|
+
self.timestamp = d["timestamp"]
|
|
33
|
+
self.openid = d["openid"]
|
|
34
|
+
self.author = self.Author(union_openid=d["author"]["union_openid"])
|
|
35
|
+
|
|
36
|
+
super().__init__(bot, ctx)
|
|
37
|
+
|
|
38
|
+
async def reply(
|
|
39
|
+
self,
|
|
40
|
+
content: str | None = None,
|
|
41
|
+
markdown: Markdown | None = None,
|
|
42
|
+
keyboard: Keyboard | None = None,
|
|
43
|
+
embed: Embed | None = None,
|
|
44
|
+
ark: Ark | None = None,
|
|
45
|
+
media: Media | None = None,
|
|
46
|
+
message_reference: MessageReference | None = None,
|
|
47
|
+
event_id: str | None = None,
|
|
48
|
+
msg_id: str | None = None,
|
|
49
|
+
msg_seq: int = 0,
|
|
50
|
+
) -> SendMessageResponse:
|
|
51
|
+
return await self.bot.send_private_message(
|
|
52
|
+
openid=self.author.union_openid,
|
|
53
|
+
content=content,
|
|
54
|
+
markdown=markdown,
|
|
55
|
+
keyboard=keyboard,
|
|
56
|
+
embed=embed,
|
|
57
|
+
ark=ark,
|
|
58
|
+
media=media,
|
|
59
|
+
message_reference=message_reference,
|
|
60
|
+
event_id=event_id,
|
|
61
|
+
msg_id=msg_id,
|
|
62
|
+
msg_seq=msg_seq,
|
|
63
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import ClassVar, Literal, NamedTuple
|
|
3
|
+
|
|
4
|
+
from oibot.bot import OiBot
|
|
5
|
+
from oibot.event import Context, Event
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FriendDelEvent(Event):
|
|
9
|
+
__slots__ = ("timestamp", "openid", "author")
|
|
10
|
+
|
|
11
|
+
class Author(NamedTuple):
|
|
12
|
+
union_openid: str
|
|
13
|
+
|
|
14
|
+
event_type: ClassVar[Literal["FRIEND_DEL"]] = "FRIEND_DEL"
|
|
15
|
+
|
|
16
|
+
timestamp: datetime
|
|
17
|
+
openid: str
|
|
18
|
+
author: Author
|
|
19
|
+
|
|
20
|
+
def __init__(self, bot: OiBot, ctx: Context) -> None:
|
|
21
|
+
d = ctx["d"]
|
|
22
|
+
|
|
23
|
+
self.timestamp = d["timestamp"]
|
|
24
|
+
self.openid = d["openid"]
|
|
25
|
+
self.author = self.Author(union_openid=d["author"]["union_openid"])
|
|
26
|
+
|
|
27
|
+
super().__init__(bot, ctx)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import ClassVar, Literal
|
|
3
|
+
|
|
4
|
+
from oibot.bot import OiBot
|
|
5
|
+
from oibot.event import Context, Event
|
|
6
|
+
from oibot.mixin.send_message import (
|
|
7
|
+
Ark,
|
|
8
|
+
Embed,
|
|
9
|
+
Keyboard,
|
|
10
|
+
Markdown,
|
|
11
|
+
Media,
|
|
12
|
+
MessageReference,
|
|
13
|
+
SendMessageResponse,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GroupAddRobotEvent(Event):
|
|
18
|
+
__slots__ = ("timestamp", "group_openid", "op_member_openid")
|
|
19
|
+
|
|
20
|
+
event_type: ClassVar[Literal["GROUP_ADD_ROBOT"]] = "GROUP_ADD_ROBOT"
|
|
21
|
+
|
|
22
|
+
timestamp: datetime
|
|
23
|
+
group_openid: str
|
|
24
|
+
op_member_openid: str
|
|
25
|
+
|
|
26
|
+
def __init__(self, bot: OiBot, ctx: Context) -> None:
|
|
27
|
+
d = ctx["d"]
|
|
28
|
+
|
|
29
|
+
self.timestamp = d["timestamp"]
|
|
30
|
+
self.group_openid = d["group_openid"]
|
|
31
|
+
self.op_member_openid = d["op_member_openid"]
|
|
32
|
+
|
|
33
|
+
super().__init__(bot, ctx)
|
|
34
|
+
|
|
35
|
+
async def reply(
|
|
36
|
+
self,
|
|
37
|
+
content: str | None = None,
|
|
38
|
+
markdown: Markdown | None = None,
|
|
39
|
+
keyboard: Keyboard | None = None,
|
|
40
|
+
embed: Embed | None = None,
|
|
41
|
+
ark: Ark | None = None,
|
|
42
|
+
media: Media | None = None,
|
|
43
|
+
message_reference: MessageReference | None = None,
|
|
44
|
+
event_id: str | None = None,
|
|
45
|
+
msg_id: str | None = None,
|
|
46
|
+
msg_seq: int = 0,
|
|
47
|
+
) -> SendMessageResponse:
|
|
48
|
+
return await self.bot.send_group_message(
|
|
49
|
+
group_openid=self.group_openid,
|
|
50
|
+
content=content,
|
|
51
|
+
markdown=markdown,
|
|
52
|
+
keyboard=keyboard,
|
|
53
|
+
embed=embed,
|
|
54
|
+
ark=ark,
|
|
55
|
+
media=media,
|
|
56
|
+
message_reference=message_reference,
|
|
57
|
+
event_id=event_id,
|
|
58
|
+
msg_id=msg_id,
|
|
59
|
+
msg_seq=msg_seq,
|
|
60
|
+
)
|