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.
@@ -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,5 @@
1
+ from .bot import Bot as Bot
2
+ from .event import * # noqa: F403
3
+ from .adapter import Adapter as Adapter
4
+ from .message import Message as Message
5
+ from .message import MessageSegment as MessageSegment
@@ -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,3 @@
1
+ from nonebot.utils import logger_wrapper
2
+
3
+ log = logger_wrapper("Afdian")
@@ -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" }