nonebot-adapter-afdian 0.0.1__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.
- nonebot_adapter_afdian-0.0.1/LICENSE +21 -0
- nonebot_adapter_afdian-0.0.1/PKG-INFO +92 -0
- nonebot_adapter_afdian-0.0.1/README.md +74 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/__init__.py +5 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/adapter.py +126 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/bot.py +64 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/config.py +13 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/event.py +69 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/exception.py +43 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/message.py +33 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/payload.py +253 -0
- nonebot_adapter_afdian-0.0.1/nonebot/adapters/afdian/utils.py +3 -0
- nonebot_adapter_afdian-0.0.1/pyproject.toml +51 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 MineGraphCommunity
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nonebot-adapter-afdian
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: 爱发电适配器
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: 17TheWord
|
|
7
|
+
Author-email: 17theword@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: nonebot2 (>=2.2.0,<3.0.0)
|
|
15
|
+
Requires-Dist: pydantic (>=1.10.0,<3.0.0,!=2.5.0,!=2.5.1)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# nonebot-adapter-afdian
|
|
19
|
+
|
|
20
|
+
基于 NoneBot2 的爱发电适配器
|
|
21
|
+
|
|
22
|
+
## 使用
|
|
23
|
+
|
|
24
|
+
1. 安装
|
|
25
|
+
```shell
|
|
26
|
+
pip install nonebot-adapter-afdian
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. 启用
|
|
30
|
+
```toml
|
|
31
|
+
adapters = [
|
|
32
|
+
{ name = "Afdian", module_name = "nonebot.adapter.afdian" }
|
|
33
|
+
]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. 配置
|
|
37
|
+
```dotenv
|
|
38
|
+
AFDIAN_BOTS='[
|
|
39
|
+
{
|
|
40
|
+
"user_id": "Your User id",
|
|
41
|
+
"api_token": "Your Api Token"
|
|
42
|
+
}
|
|
43
|
+
]'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from nonebot import on_notice
|
|
50
|
+
from nonebot.adapters.afdian import OrderNotifyEvent, Bot
|
|
51
|
+
|
|
52
|
+
test_afd = on_notice()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@test_afd.handle()
|
|
56
|
+
async def handle_afd(bot: Bot, event: OrderNotifyEvent):
|
|
57
|
+
result1 = await bot.query_order_by_page(page=1)
|
|
58
|
+
print(result1)
|
|
59
|
+
|
|
60
|
+
result2 = await bot.query_order_by_out_trade_no(
|
|
61
|
+
out_trade_no="202408070218485652995332024"
|
|
62
|
+
)
|
|
63
|
+
print(result2)
|
|
64
|
+
|
|
65
|
+
result3 = await bot.query_order_by_order_list(
|
|
66
|
+
order_list=["202408070218485652995332024", "202308281533159897535713113"]
|
|
67
|
+
)
|
|
68
|
+
print(result3)
|
|
69
|
+
|
|
70
|
+
result4 = await bot.query_sponsor(page=1)
|
|
71
|
+
print(result4)
|
|
72
|
+
|
|
73
|
+
result5 = await bot.query_sponsor(page=1, per_page=20) # 查询第一页,每页20个
|
|
74
|
+
print(result5)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 特别感谢
|
|
78
|
+
|
|
79
|
+
- [NoneBot2](https://github.com/nonebot/nonebot2):开发框架。
|
|
80
|
+
|
|
81
|
+
## 贡献与支持
|
|
82
|
+
|
|
83
|
+
觉得好用可以给这个项目点个 `Star` 或者去 [爱发电](https://afdian.net/a/17TheWord) 投喂我。
|
|
84
|
+
|
|
85
|
+
有意见或者建议也欢迎提交 [Issues](https://github.com/MineGraphCN/nonebot-adapter-afdian/issues)
|
|
86
|
+
和 [Pull requests](https://github.com/MineGraphCN/nonebot-adapter-afdian/pulls) 。
|
|
87
|
+
|
|
88
|
+
## 开源许可
|
|
89
|
+
|
|
90
|
+
本项目使用 [MIT](./LICENSE) 作为开源许可证。
|
|
91
|
+
|
|
92
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# nonebot-adapter-afdian
|
|
2
|
+
|
|
3
|
+
基于 NoneBot2 的爱发电适配器
|
|
4
|
+
|
|
5
|
+
## 使用
|
|
6
|
+
|
|
7
|
+
1. 安装
|
|
8
|
+
```shell
|
|
9
|
+
pip install nonebot-adapter-afdian
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. 启用
|
|
13
|
+
```toml
|
|
14
|
+
adapters = [
|
|
15
|
+
{ name = "Afdian", module_name = "nonebot.adapter.afdian" }
|
|
16
|
+
]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. 配置
|
|
20
|
+
```dotenv
|
|
21
|
+
AFDIAN_BOTS='[
|
|
22
|
+
{
|
|
23
|
+
"user_id": "Your User id",
|
|
24
|
+
"api_token": "Your Api Token"
|
|
25
|
+
}
|
|
26
|
+
]'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from nonebot import on_notice
|
|
33
|
+
from nonebot.adapters.afdian import OrderNotifyEvent, Bot
|
|
34
|
+
|
|
35
|
+
test_afd = on_notice()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@test_afd.handle()
|
|
39
|
+
async def handle_afd(bot: Bot, event: OrderNotifyEvent):
|
|
40
|
+
result1 = await bot.query_order_by_page(page=1)
|
|
41
|
+
print(result1)
|
|
42
|
+
|
|
43
|
+
result2 = await bot.query_order_by_out_trade_no(
|
|
44
|
+
out_trade_no="202408070218485652995332024"
|
|
45
|
+
)
|
|
46
|
+
print(result2)
|
|
47
|
+
|
|
48
|
+
result3 = await bot.query_order_by_order_list(
|
|
49
|
+
order_list=["202408070218485652995332024", "202308281533159897535713113"]
|
|
50
|
+
)
|
|
51
|
+
print(result3)
|
|
52
|
+
|
|
53
|
+
result4 = await bot.query_sponsor(page=1)
|
|
54
|
+
print(result4)
|
|
55
|
+
|
|
56
|
+
result5 = await bot.query_sponsor(page=1, per_page=20) # 查询第一页,每页20个
|
|
57
|
+
print(result5)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 特别感谢
|
|
61
|
+
|
|
62
|
+
- [NoneBot2](https://github.com/nonebot/nonebot2):开发框架。
|
|
63
|
+
|
|
64
|
+
## 贡献与支持
|
|
65
|
+
|
|
66
|
+
觉得好用可以给这个项目点个 `Star` 或者去 [爱发电](https://afdian.net/a/17TheWord) 投喂我。
|
|
67
|
+
|
|
68
|
+
有意见或者建议也欢迎提交 [Issues](https://github.com/MineGraphCN/nonebot-adapter-afdian/issues)
|
|
69
|
+
和 [Pull requests](https://github.com/MineGraphCN/nonebot-adapter-afdian/pulls) 。
|
|
70
|
+
|
|
71
|
+
## 开源许可
|
|
72
|
+
|
|
73
|
+
本项目使用 [MIT](./LICENSE) 作为开源许可证。
|
|
74
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from functools import partial
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
from nonebot import get_plugin_config
|
|
9
|
+
from nonebot.adapters import Adapter as BaseAdapter
|
|
10
|
+
from nonebot.compat import type_validate_python
|
|
11
|
+
from nonebot.drivers import (
|
|
12
|
+
URL,
|
|
13
|
+
Driver,
|
|
14
|
+
Request,
|
|
15
|
+
Response,
|
|
16
|
+
ReverseDriver,
|
|
17
|
+
HTTPServerSetup,
|
|
18
|
+
)
|
|
19
|
+
from nonebot.utils import escape_tag
|
|
20
|
+
from typing_extensions import override
|
|
21
|
+
|
|
22
|
+
from .bot import Bot
|
|
23
|
+
from .config import Config, BotInfo
|
|
24
|
+
from .event import OrderNotifyEvent
|
|
25
|
+
from .exception import ActionFailed, ApiNotAvailable
|
|
26
|
+
from .payload import (
|
|
27
|
+
PingResponse,
|
|
28
|
+
WrongResponse,
|
|
29
|
+
OrderResponse,
|
|
30
|
+
SponsorResponse,
|
|
31
|
+
TSExpiredResponse
|
|
32
|
+
)
|
|
33
|
+
from .utils import log
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Adapter(BaseAdapter):
|
|
37
|
+
|
|
38
|
+
@override
|
|
39
|
+
def __init__(self, driver: Driver, **kwargs: Any):
|
|
40
|
+
super().__init__(driver, **kwargs)
|
|
41
|
+
self.afdian_config: Config = get_plugin_config(Config)
|
|
42
|
+
self._setup()
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
@override
|
|
46
|
+
def get_name(cls) -> str:
|
|
47
|
+
return "Afdian"
|
|
48
|
+
|
|
49
|
+
def _setup(self):
|
|
50
|
+
if not isinstance(self.driver, ReverseDriver):
|
|
51
|
+
log(
|
|
52
|
+
"WARNING",
|
|
53
|
+
f"Current driver {self.config.driver} is not a ReverseDriver. {self.get_name()}"
|
|
54
|
+
" Webhook disabled.",
|
|
55
|
+
)
|
|
56
|
+
for bot_info in self.afdian_config.afdian_bots:
|
|
57
|
+
webhook_route = HTTPServerSetup(
|
|
58
|
+
URL("/afdian/webhooks/" + bot_info.user_id),
|
|
59
|
+
"POST",
|
|
60
|
+
self.get_name(),
|
|
61
|
+
partial(self._handle_webhook, bot_info=bot_info),
|
|
62
|
+
)
|
|
63
|
+
self.setup_http_server(webhook_route)
|
|
64
|
+
|
|
65
|
+
self.driver.on_startup(self._startup)
|
|
66
|
+
|
|
67
|
+
async def _startup(self):
|
|
68
|
+
await asyncio.gather(
|
|
69
|
+
*(self._startup_bot(bot_info) for bot_info in self.afdian_config.afdian_bots)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def _startup_bot(self, bot_info: BotInfo):
|
|
73
|
+
bot = Bot(self, self_id=bot_info.user_id, bot_info=bot_info)
|
|
74
|
+
result: PingResponse = await bot.send_ping()
|
|
75
|
+
if result.ec != 200:
|
|
76
|
+
log("ERROR", f"<y>Bot {bot.self_id}</y> connect <r>failed</r>")
|
|
77
|
+
return
|
|
78
|
+
self.bot_connect(bot)
|
|
79
|
+
log("INFO", f"<y>Bot {escape_tag(bot_info.user_id)}</y> connected")
|
|
80
|
+
|
|
81
|
+
async def _handle_webhook(self, request: Request, bot_info: BotInfo) -> Response:
|
|
82
|
+
json_data = json.loads(request.content)
|
|
83
|
+
try:
|
|
84
|
+
event = type_validate_python(OrderNotifyEvent, json_data)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
log("ERROR", f"Webhook data parse failed: {e}")
|
|
87
|
+
return Response(400, content='{"ec": 400, "em": "parse event failed"}')
|
|
88
|
+
else:
|
|
89
|
+
if event.ec != 200:
|
|
90
|
+
log("ERROR", f"Webhook data parse failed: {event.em}")
|
|
91
|
+
return Response(400, content='{"ec": 400, "em": "parse event failed"}')
|
|
92
|
+
bot = cast(Bot, self.bots[bot_info.user_id])
|
|
93
|
+
asyncio.create_task(bot.handle_event(event))
|
|
94
|
+
return Response(200, content='{"ec": 200, "em": "success"}')
|
|
95
|
+
|
|
96
|
+
@override
|
|
97
|
+
async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any:
|
|
98
|
+
if api not in ("/api/open/ping", "/api/open/query-order", "/api/open/query-sponsor"):
|
|
99
|
+
log("ERROR", f"Unsupported api: {api}")
|
|
100
|
+
raise ApiNotAvailable(api)
|
|
101
|
+
|
|
102
|
+
ts = int(time.time())
|
|
103
|
+
param_json_data = json.dumps(data)
|
|
104
|
+
sign_str = f"{bot.api_token}params{param_json_data}ts{ts}user_id{bot.self_id}"
|
|
105
|
+
sign = hashlib.md5(sign_str.encode("utf-8")).hexdigest()
|
|
106
|
+
request = Request(
|
|
107
|
+
"GET",
|
|
108
|
+
URL(self.afdian_config.afdian_api_base + api),
|
|
109
|
+
params={
|
|
110
|
+
"user_id": bot.bot_info.user_id,
|
|
111
|
+
"params": param_json_data,
|
|
112
|
+
"ts": ts,
|
|
113
|
+
"sign": sign
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
response = await bot.adapter.request(request)
|
|
117
|
+
response_json = json.loads(response.content)
|
|
118
|
+
for model in (PingResponse, WrongResponse, OrderResponse, SponsorResponse, TSExpiredResponse):
|
|
119
|
+
try:
|
|
120
|
+
result = type_validate_python(model, response_json)
|
|
121
|
+
return result
|
|
122
|
+
except Exception:
|
|
123
|
+
continue
|
|
124
|
+
else:
|
|
125
|
+
log("ERROR", f"Parse result failed: {response_json}")
|
|
126
|
+
raise ActionFailed(response)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Union, Any, TYPE_CHECKING, Dict, List
|
|
2
|
+
|
|
3
|
+
from nonebot.message import handle_event
|
|
4
|
+
from typing_extensions import override
|
|
5
|
+
|
|
6
|
+
from nonebot.adapters import Bot as BaseBot
|
|
7
|
+
|
|
8
|
+
from .config import BotInfo
|
|
9
|
+
from .event import Event
|
|
10
|
+
from .message import Message, MessageSegment
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .adapter import Adapter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Bot(BaseBot):
|
|
17
|
+
adapter: "Adapter"
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def __init__(self, adapter: "Adapter", self_id: str, bot_info: BotInfo):
|
|
21
|
+
super().__init__(adapter, self_id)
|
|
22
|
+
self.api_token = bot_info.api_token
|
|
23
|
+
self.bot_info = bot_info
|
|
24
|
+
|
|
25
|
+
async def send_ping(self):
|
|
26
|
+
return await self.call_api("/api/open/ping", data={})
|
|
27
|
+
|
|
28
|
+
async def __query_order(self, query: Dict[str, Any]):
|
|
29
|
+
return await self.call_api("/api/open/query-order", data=query)
|
|
30
|
+
|
|
31
|
+
async def query_order_by_page(self, page: int):
|
|
32
|
+
"""根据页码查询订单"""
|
|
33
|
+
if page <= 0:
|
|
34
|
+
raise ValueError("page must be greater than 0")
|
|
35
|
+
return await self.__query_order(query={"page": page})
|
|
36
|
+
|
|
37
|
+
async def query_order_by_out_trade_no(self, out_trade_no: str):
|
|
38
|
+
"""根据订单号查询订单"""
|
|
39
|
+
return await self.__query_order(query={"out_trade_no": out_trade_no})
|
|
40
|
+
|
|
41
|
+
async def query_order_by_order_list(self, order_list: List[str]):
|
|
42
|
+
"""根据订单号列表查询多个订单"""
|
|
43
|
+
order_list_str = ",".join([out_trade_no for out_trade_no in order_list])
|
|
44
|
+
return await self.__query_order(query={"out_trade_no": order_list_str})
|
|
45
|
+
|
|
46
|
+
async def query_sponsor(self, page: int, per_page: int = 20):
|
|
47
|
+
"""查询赞助者,可选传参每页数量 1-100"""
|
|
48
|
+
if page <= 0:
|
|
49
|
+
raise ValueError("page must be greater than 0")
|
|
50
|
+
if per_page > 100 or per_page < 1:
|
|
51
|
+
raise ValueError("per_page must be between 1 and 100")
|
|
52
|
+
return await self.call_api("/api/open/query-sponsor", data={"page": page, "per_page": per_page})
|
|
53
|
+
|
|
54
|
+
async def handle_event(self, event: Event) -> None:
|
|
55
|
+
await handle_event(self, event)
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
async def send(
|
|
59
|
+
self,
|
|
60
|
+
event: Event,
|
|
61
|
+
message: Union[str, Message, MessageSegment],
|
|
62
|
+
**kwargs,
|
|
63
|
+
) -> Any:
|
|
64
|
+
...
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, BaseModel, HttpUrl
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BotInfo(BaseModel):
|
|
7
|
+
user_id: str = ""
|
|
8
|
+
api_token: str = ""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Config(BaseModel):
|
|
12
|
+
afdian_bots: List[BotInfo] = Field(default=[])
|
|
13
|
+
afdian_api_base: HttpUrl = Field("https://afdian.com")
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from nonebot import escape_tag, model_dump
|
|
2
|
+
from nonebot.adapters import Event as BaseEvent
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
5
|
+
from .message import Message
|
|
6
|
+
from .payload import WebhookData
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Event(BaseEvent):
|
|
10
|
+
"""Event"""
|
|
11
|
+
ec: int
|
|
12
|
+
em: str
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
def get_type(self) -> str:
|
|
16
|
+
raise ValueError("Event has no type")
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
def get_event_name(self) -> str:
|
|
20
|
+
raise ValueError("Event has no name")
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
def get_event_description(self) -> str:
|
|
24
|
+
return escape_tag(str(model_dump(self)))
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
def get_message(self) -> Message:
|
|
28
|
+
raise ValueError("Event has no context")
|
|
29
|
+
|
|
30
|
+
@override
|
|
31
|
+
def get_plaintext(self) -> str:
|
|
32
|
+
raise ValueError("Event has no plaintext")
|
|
33
|
+
|
|
34
|
+
@override
|
|
35
|
+
def get_user_id(self) -> str:
|
|
36
|
+
raise ValueError("Event has no sender")
|
|
37
|
+
|
|
38
|
+
@override
|
|
39
|
+
def get_session_id(self) -> str:
|
|
40
|
+
raise ValueError("Event has no sender")
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
def is_tome(self) -> bool:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OrderNotifyEvent(Event):
|
|
48
|
+
"""Order Notify Event"""
|
|
49
|
+
data: WebhookData
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
def get_type(self) -> str:
|
|
53
|
+
return "notice"
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
def get_event_name(self) -> str:
|
|
57
|
+
return "order_notify"
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
def get_user_id(self) -> str:
|
|
61
|
+
return self.data.order.user_private_id
|
|
62
|
+
|
|
63
|
+
@override
|
|
64
|
+
def get_session_id(self) -> str:
|
|
65
|
+
return self.data.order.user_private_id
|
|
66
|
+
|
|
67
|
+
@override
|
|
68
|
+
def get_event_description(self) -> str:
|
|
69
|
+
return f"Order {self.data.order.out_trade_no} from user @{self.data.order.user_private_id}"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from nonebot.drivers import Response
|
|
4
|
+
from nonebot.exception import AdapterException
|
|
5
|
+
from nonebot.exception import ActionFailed as BaseActionFailed
|
|
6
|
+
from nonebot.exception import NetworkError as BaseNetworkError
|
|
7
|
+
from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AfdianAdapterException(AdapterException):
|
|
11
|
+
def __init__(self):
|
|
12
|
+
super().__init__("Afdian")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NetworkError(BaseNetworkError, AfdianAdapterException):
|
|
16
|
+
def __init__(self, msg: Optional[str] = None):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self.msg: Optional[str] = msg
|
|
19
|
+
"""错误原因"""
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return f"<NetWorkError message={self.msg}>"
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
return self.__repr__()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ActionFailed(
|
|
29
|
+
BaseActionFailed,
|
|
30
|
+
AfdianAdapterException,
|
|
31
|
+
):
|
|
32
|
+
def __init__(self, response: Response):
|
|
33
|
+
self.status_code: int = response.status_code
|
|
34
|
+
self.code: Optional[int] = None
|
|
35
|
+
self.message: Optional[str] = None
|
|
36
|
+
self.data: Optional[dict] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ApiNotAvailable(BaseApiNotAvailable, AfdianAdapterException):
|
|
40
|
+
def __init__(self, msg: Optional[str] = None):
|
|
41
|
+
super().__init__()
|
|
42
|
+
self.msg: Optional[str] = msg
|
|
43
|
+
"""错误原因"""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Type, Iterable
|
|
2
|
+
from typing_extensions import override
|
|
3
|
+
|
|
4
|
+
from nonebot.adapters import Message as BaseMessage, MessageSegment as BaseMessageSegment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MessageSegment(BaseMessageSegment["Message"]):
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
@override
|
|
11
|
+
def get_message_class(cls) -> Type["Message"]:
|
|
12
|
+
return Message
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
def is_text(self) -> bool:
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Message(BaseMessage[MessageSegment]):
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
@override
|
|
27
|
+
def get_segment_class(cls) -> Type[MessageSegment]:
|
|
28
|
+
return MessageSegment
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
@override
|
|
32
|
+
def _construct(msg: str) -> Iterable[MessageSegment]:
|
|
33
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from typing import Optional, List, Any, Dict
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Request(BaseModel):
|
|
7
|
+
"""Request Data"""
|
|
8
|
+
|
|
9
|
+
user_id: str
|
|
10
|
+
params: str
|
|
11
|
+
ts: int
|
|
12
|
+
sign: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class KVString(BaseModel):
|
|
16
|
+
"""KV String Data"""
|
|
17
|
+
|
|
18
|
+
kv_string: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WrongResponseData(BaseModel):
|
|
22
|
+
"""错误响应"""
|
|
23
|
+
|
|
24
|
+
explain: str
|
|
25
|
+
debug: KVString
|
|
26
|
+
request: Request
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TSExpiredResponseData(BaseModel):
|
|
30
|
+
"""TS 过期"""
|
|
31
|
+
|
|
32
|
+
explain: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PingResponseData(BaseModel):
|
|
36
|
+
"""Ping Response Data"""
|
|
37
|
+
|
|
38
|
+
uid: str
|
|
39
|
+
request: Request
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ResponseData(BaseModel):
|
|
43
|
+
"""Response Data"""
|
|
44
|
+
|
|
45
|
+
total_count: Optional[int] = None
|
|
46
|
+
total_page: Optional[int] = None
|
|
47
|
+
request: Request
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BaseAfdianResponse(BaseModel):
|
|
51
|
+
"""Base Afdian Response"""
|
|
52
|
+
|
|
53
|
+
ec: int
|
|
54
|
+
em: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class WrongResponse(BaseAfdianResponse):
|
|
58
|
+
"""请求错误"""
|
|
59
|
+
|
|
60
|
+
data: WrongResponseData
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PingResponse(BaseAfdianResponse):
|
|
64
|
+
"""Ping 回应"""
|
|
65
|
+
|
|
66
|
+
data: PingResponseData
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TSExpiredResponse(BaseAfdianResponse):
|
|
70
|
+
"""TS 过期"""
|
|
71
|
+
|
|
72
|
+
data: TSExpiredResponseData
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SKUDetail(BaseModel):
|
|
76
|
+
"""售卖类型"""
|
|
77
|
+
|
|
78
|
+
sku_id: str
|
|
79
|
+
count: int
|
|
80
|
+
name: str
|
|
81
|
+
album_id: Optional[str] = ""
|
|
82
|
+
pic: Optional[str] = ""
|
|
83
|
+
stock: Optional[str] = ""
|
|
84
|
+
post_id: Optional[str] = ""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Order(BaseModel):
|
|
88
|
+
"""Order Model"""
|
|
89
|
+
|
|
90
|
+
out_trade_no: str
|
|
91
|
+
"""订单号"""
|
|
92
|
+
custom_order_id: Optional[str] = None
|
|
93
|
+
"""自定义信息"""
|
|
94
|
+
plan_title: str
|
|
95
|
+
user_private_id: str
|
|
96
|
+
user_id: str
|
|
97
|
+
"""下单用户ID"""
|
|
98
|
+
plan_id: str
|
|
99
|
+
"""方案ID,如自选,则为空"""
|
|
100
|
+
title: Optional[str] = ""
|
|
101
|
+
"""方案描述"""
|
|
102
|
+
month: int
|
|
103
|
+
"""赞助月份"""
|
|
104
|
+
total_amount: str
|
|
105
|
+
"""真实付款金额,如有兑换码,则为 0.00"""
|
|
106
|
+
show_amount: str
|
|
107
|
+
"""显示金额,如有折扣则为折扣前金额"""
|
|
108
|
+
status: int
|
|
109
|
+
"""2 为交易成功。目前仅会推送此类型"""
|
|
110
|
+
remark: Optional[str] = ""
|
|
111
|
+
"""订单留言"""
|
|
112
|
+
redeem_id: Optional[str] = ""
|
|
113
|
+
"""兑换码ID"""
|
|
114
|
+
product_type: int
|
|
115
|
+
"""0 表示常规方案,1 表示售卖方案"""
|
|
116
|
+
discount: Optional[str] = ""
|
|
117
|
+
"""折扣"""
|
|
118
|
+
sku_detail: Optional[List[SKUDetail]] = []
|
|
119
|
+
"""如果为售卖类型,以数组形式表示具体型号"""
|
|
120
|
+
address_person: Optional[str] = ""
|
|
121
|
+
"""收件人"""
|
|
122
|
+
address_phone: Optional[str] = ""
|
|
123
|
+
"""收件人电话"""
|
|
124
|
+
address_address: Optional[str] = ""
|
|
125
|
+
"""收件人地址"""
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class WebhookData(BaseModel):
|
|
129
|
+
"""Webhook Data"""
|
|
130
|
+
|
|
131
|
+
type: Optional[str] = None
|
|
132
|
+
order: Order
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class OrderResponseData(ResponseData):
|
|
136
|
+
"""订单响应"""
|
|
137
|
+
|
|
138
|
+
list: List[Order]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class OrderResponse(BaseAfdianResponse):
|
|
142
|
+
"""订单 Response"""
|
|
143
|
+
|
|
144
|
+
data: OrderResponseData
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class SponsorPlan(BaseModel):
|
|
148
|
+
"""赞助方案"""
|
|
149
|
+
|
|
150
|
+
plan_id: str
|
|
151
|
+
rank: int
|
|
152
|
+
user_id: str
|
|
153
|
+
status: int
|
|
154
|
+
name: str
|
|
155
|
+
pic: str
|
|
156
|
+
desc: str
|
|
157
|
+
price: str
|
|
158
|
+
update_time: int
|
|
159
|
+
pay_month: int
|
|
160
|
+
show_price: str
|
|
161
|
+
independent: int
|
|
162
|
+
permanent: int
|
|
163
|
+
can_buy_hide: int
|
|
164
|
+
need_address: int
|
|
165
|
+
product_type: int
|
|
166
|
+
sale_limit_count: int
|
|
167
|
+
need_invite_code: bool
|
|
168
|
+
expire_time: int
|
|
169
|
+
sku_processed: List
|
|
170
|
+
rankType: int
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class Timing(BaseModel):
|
|
174
|
+
timing_on: int
|
|
175
|
+
timing_off: int
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class CurrentPlan(BaseModel):
|
|
179
|
+
"""赞助方案"""
|
|
180
|
+
|
|
181
|
+
can_ali_agreement: Optional[int] = None
|
|
182
|
+
plan_id: Optional[str] = None
|
|
183
|
+
rank: Optional[int] = None
|
|
184
|
+
user_id: Optional[str] = None
|
|
185
|
+
status: Optional[int] = None
|
|
186
|
+
name: str
|
|
187
|
+
pic: Optional[str] = None
|
|
188
|
+
desc: Optional[str] = None
|
|
189
|
+
price: Optional[str] = None
|
|
190
|
+
update_time: Optional[int] = None
|
|
191
|
+
timing: Optional[Timing] = None
|
|
192
|
+
pay_month: Optional[int] = None
|
|
193
|
+
show_price: Optional[str] = None
|
|
194
|
+
show_price_after_adjust: Optional[str] = None
|
|
195
|
+
has_coupon: Optional[int] = None
|
|
196
|
+
coupon: Optional[List] = None
|
|
197
|
+
favorable_price: Optional[int] = None
|
|
198
|
+
independent: Optional[int] = None
|
|
199
|
+
permanent: Optional[int] = None
|
|
200
|
+
can_buy_hide: Optional[int] = None
|
|
201
|
+
need_address: Optional[int] = None
|
|
202
|
+
product_type: Optional[int] = None
|
|
203
|
+
sale_limit_count: Optional[int] = None
|
|
204
|
+
need_invite_code: Optional[bool] = None
|
|
205
|
+
bundle_stock: Optional[int] = None
|
|
206
|
+
bundle_sku_select_count: Optional[int] = None
|
|
207
|
+
config: Optional[Dict[str, Any]] = None
|
|
208
|
+
has_plan_config: Optional[int] = None
|
|
209
|
+
shipping_fee_info: Optional[List] = None
|
|
210
|
+
expire_time: Optional[int] = None
|
|
211
|
+
sku_processed: Optional[List] = None
|
|
212
|
+
rank_type: Optional[int] = Field(None, alias='rankType')
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class User(BaseModel):
|
|
216
|
+
"""用户属性"""
|
|
217
|
+
|
|
218
|
+
user_id: str
|
|
219
|
+
"""用户唯一ID"""
|
|
220
|
+
name: str
|
|
221
|
+
"""昵称,非唯一,可重复"""
|
|
222
|
+
avatar: str
|
|
223
|
+
"""头像"""
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class SponsorList(BaseModel):
|
|
227
|
+
"""赞助者列表"""
|
|
228
|
+
|
|
229
|
+
sponsor_plans: List[SponsorPlan]
|
|
230
|
+
"""赞助方案"""
|
|
231
|
+
current_plan: CurrentPlan
|
|
232
|
+
"""当前赞助方案,如果节点仅有 name: "",不包含其它内容时,表示无方案"""
|
|
233
|
+
all_sum_amount: str
|
|
234
|
+
"""累计赞助金额,此处为折扣前金额。如有兑换码,则此处为虚拟金额,回比实际提现的多"""
|
|
235
|
+
create_time: Optional[int] = None
|
|
236
|
+
"""int 秒级时间戳,表示成为赞助者的时间,即首次赞助时间"""
|
|
237
|
+
first_pay_time: Optional[int] = None
|
|
238
|
+
last_pay_time: int
|
|
239
|
+
"""int 秒级时间戳,最近一次赞助时间"""
|
|
240
|
+
user: User
|
|
241
|
+
"""用户属性"""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class SponsorResponseData(ResponseData):
|
|
245
|
+
"""赞助者响应数据"""
|
|
246
|
+
|
|
247
|
+
list: List[SponsorList]
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class SponsorResponse(BaseAfdianResponse):
|
|
251
|
+
"""赞助者 Response"""
|
|
252
|
+
|
|
253
|
+
data: SponsorResponseData
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "nonebot-adapter-afdian"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "爱发电适配器"
|
|
5
|
+
authors = ["17TheWord <17theword@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{ include = "nonebot" }]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "^3.10"
|
|
12
|
+
nonebot2 = "^2.2.0"
|
|
13
|
+
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1"
|
|
14
|
+
|
|
15
|
+
[tool.poetry.group.test.dependencies]
|
|
16
|
+
ruff = "^0.5.0"
|
|
17
|
+
isort = "^5.10.1"
|
|
18
|
+
nonebug = "^0.3.5"
|
|
19
|
+
pytest-cov = "^4.0.0"
|
|
20
|
+
pre-commit = "^3.7.0"
|
|
21
|
+
pytest-asyncio = "^0.23.0"
|
|
22
|
+
nonebot2 = { version = "^2.2.0", extras = ["fastapi"] }
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
addopts = "--cov=nonebot/adapters/afdian --cov-report term-missing"
|
|
26
|
+
|
|
27
|
+
[tool.isort]
|
|
28
|
+
profile = "black"
|
|
29
|
+
line_length = 88
|
|
30
|
+
length_sort = true
|
|
31
|
+
skip_gitignore = true
|
|
32
|
+
force_sort_within_sections = true
|
|
33
|
+
extra_standard_library = ["typing_extensions"]
|
|
34
|
+
|
|
35
|
+
[tool.ruff]
|
|
36
|
+
line-length = 88
|
|
37
|
+
target-version = "py310"
|
|
38
|
+
|
|
39
|
+
[tool.ruff.lint]
|
|
40
|
+
ignore = []
|
|
41
|
+
|
|
42
|
+
[tool.ruff.lint.flake8-pytest-style]
|
|
43
|
+
fixture-parentheses = false
|
|
44
|
+
mark-parentheses = false
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["poetry-core"]
|
|
48
|
+
build-backend = "poetry.core.masonry.api"
|
|
49
|
+
|
|
50
|
+
[tool.data.packages]
|
|
51
|
+
my_package = { path = "nonebot/adapters/afdian" }
|