perk-pushplus-sdk 1.2.0__tar.gz → 1.2.2__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.
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/PKG-INFO +58 -6
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/README.md +57 -5
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/pyproject.toml +1 -1
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/__init__.py +17 -1
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/__init__.py +2 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/base.py +65 -1
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/doc.py +37 -2
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/excel.py +43 -2
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/form.py +5 -1
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/friend.py +32 -1
- perk_pushplus_sdk-1.2.2/src/perk_pushplus/api/qqbot.py +89 -0
- perk_pushplus_sdk-1.2.2/src/perk_pushplus/api/topic_user.py +69 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/client.py +2 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/config.py +1 -1
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/enums.py +2 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/__init__.py +18 -1
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_doc.py +12 -13
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_form.py +11 -8
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_friend.py +12 -1
- perk_pushplus_sdk-1.2.2/src/perk_pushplus/models/open_qqbot.py +107 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_topic.py +13 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_user.py +1 -0
- perk_pushplus_sdk-1.2.2/src/perk_pushplus/multipart.py +43 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/PKG-INFO +58 -6
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/SOURCES.txt +3 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_open_api.py +130 -0
- perk_pushplus_sdk-1.2.0/src/perk_pushplus/api/topic_user.py +0 -35
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/setup.cfg +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/access_key.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/access_key.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/channel.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/clawbot.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/image.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/message.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/message_token.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/open_message.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/pre.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/setting.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/topic.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/user.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/webhook.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/callback_parser.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/exceptions.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/http.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/base.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/callback.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_access.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_channel.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_clawbot.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_image.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_message.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_pre.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_setting.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_token.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_webhook.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/send.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/py.typed +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/rate_limit.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/dependency_links.txt +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/requires.txt +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/top_level.txt +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_enums_and_exceptions.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_image_api.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_message_api.py +0 -0
- {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: perk-pushplus-sdk
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: pushplus(推送加) 官方接口的 Python SDK,覆盖消息发送接口与全部开放接口。
|
|
5
5
|
Author: perk-net
|
|
6
6
|
License: Apache-2.0
|
|
@@ -35,7 +35,7 @@ Requires-Dist: responses>=0.23; extra == "dev"
|
|
|
35
35
|
[pushplus(推送加)](https://www.pushplus.plus) 官方接口的 Python SDK,覆盖 **消息接口** 与 **全部开放接口**。
|
|
36
36
|
|
|
37
37
|
- **消息接口**(`/send`、`/batchSend`):单渠道 + 多渠道发送,含 Builder API。
|
|
38
|
-
- **开放接口**(`/api/open/...` 与 `/push/api/open/...`):用户、消息、消息 token、群组、群组用户、好友、webhook、渠道、ClawBot
|
|
38
|
+
- **开放接口**(`/api/open/...` 与 `/push/api/open/...`):用户、消息、消息 token、群组、群组用户、好友、webhook、渠道、ClawBot、QQ 机器人、功能设置、预处理信息、图片服务、push 表单、push 文档、push 表格。
|
|
39
39
|
- **AccessKey 自动管理**:缓存 + 过期前自动刷新;`code=401` 自动刷新并重试一次。
|
|
40
40
|
- **本地限流守卫**:命中 `code=900` 后按 token 短路同 token 后续发送,避免被服务端长期封禁。
|
|
41
41
|
- **回调解析**:`message_complate`、`add_topic_user`、`add_friend` 三类回调统一解析。
|
|
@@ -91,7 +91,7 @@ short_code = client.send(
|
|
|
91
91
|
.build()
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
# push
|
|
94
|
+
# push 表单 / 文档 / 表格:template 为 form/doc/excel 时需传 pushId(对应编码)
|
|
95
95
|
short_code = client.send(
|
|
96
96
|
SendRequest.builder()
|
|
97
97
|
.title("表单通知")
|
|
@@ -100,6 +100,22 @@ short_code = client.send(
|
|
|
100
100
|
.push_id("表单编码")
|
|
101
101
|
.build()
|
|
102
102
|
)
|
|
103
|
+
short_code = client.send(
|
|
104
|
+
SendRequest.builder()
|
|
105
|
+
.title("本周工作同步")
|
|
106
|
+
.content("请查收")
|
|
107
|
+
.template(Template.DOC)
|
|
108
|
+
.push_id("文档编码")
|
|
109
|
+
.build()
|
|
110
|
+
)
|
|
111
|
+
short_code = client.send(
|
|
112
|
+
SendRequest.builder()
|
|
113
|
+
.title("销售日报")
|
|
114
|
+
.content("请查收")
|
|
115
|
+
.template(Template.EXCEL)
|
|
116
|
+
.push_id("表格编码")
|
|
117
|
+
.build()
|
|
118
|
+
)
|
|
103
119
|
```
|
|
104
120
|
|
|
105
121
|
### 3. 多渠道发送
|
|
@@ -141,12 +157,31 @@ topics = client.topic.list(TopicListQuery.of(1, 20, topic_type=0))
|
|
|
141
157
|
detail = client.topic.detail(topic_id=123)
|
|
142
158
|
|
|
143
159
|
# 好友
|
|
160
|
+
from perk_pushplus import PageQuery, TopicUserListQuery
|
|
144
161
|
qr = client.friend.get_qr_code(content="welcome")
|
|
145
162
|
friends = client.friend.list()
|
|
163
|
+
client.friend.add_blacklist(friends.list[0].friendId)
|
|
164
|
+
friend_blacklist = client.friend.blacklist_list(PageQuery.of(1, 20))
|
|
165
|
+
client.topic_user.add_blacklist(topic_relation_id=10)
|
|
166
|
+
topic_blacklist = client.topic_user.blacklist_list(TopicUserListQuery.of(1, 20, topic_id=1))
|
|
146
167
|
|
|
147
168
|
# webhook 渠道
|
|
148
169
|
webhooks = client.webhook.list()
|
|
149
170
|
|
|
171
|
+
# QQ 机器人:绑定 -> 认领群 -> 建配置 -> 发到群
|
|
172
|
+
from perk_pushplus import QqBotSaveRequest
|
|
173
|
+
link = client.qq_bot.get_bind_link() # link.url 生成二维码,或私聊发送 link.bindCode
|
|
174
|
+
bind = client.qq_bot.bot_info() # bind.isBind == 1 表示已绑定
|
|
175
|
+
qq_groups = client.qq_bot.group_list()
|
|
176
|
+
client.qq_bot.add(QqBotSaveRequest(qqName="运维告警群", qqCode="ops-group", qqGroupId=qq_groups[0].id))
|
|
177
|
+
client.send(SendRequest(
|
|
178
|
+
title="服务告警",
|
|
179
|
+
content="订单服务响应超时",
|
|
180
|
+
channel=Channel.QQ,
|
|
181
|
+
option="ops-group", # 不传 option 则发给自己
|
|
182
|
+
template=Template.TXT,
|
|
183
|
+
))
|
|
184
|
+
|
|
150
185
|
# 设置
|
|
151
186
|
client.setting.change_is_send(1) # 启用发送
|
|
152
187
|
|
|
@@ -169,17 +204,34 @@ client.form.save(FormSaveRequest(
|
|
|
169
204
|
))
|
|
170
205
|
published = client.form.publish(form.id)
|
|
171
206
|
print(published.fillUrl)
|
|
207
|
+
client.send(SendRequest(
|
|
208
|
+
title=published.title,
|
|
209
|
+
content="请花1分钟完成填写",
|
|
210
|
+
template=Template.FORM,
|
|
211
|
+
pushId=published.formCode,
|
|
212
|
+
))
|
|
172
213
|
|
|
173
214
|
# push 文档
|
|
174
|
-
doc = client.doc.
|
|
175
|
-
client.doc.save_content(doc.docCode, "<h1>本周工作同步</h1><p>需求评审。</p>")
|
|
215
|
+
doc = client.doc.import_word("本周工作同步.docx")
|
|
176
216
|
client.doc.update_share(doc.docCode, 1, 0)
|
|
177
217
|
client.doc.publish(doc.docCode)
|
|
218
|
+
client.send(SendRequest(
|
|
219
|
+
title=doc.title,
|
|
220
|
+
content="请查收",
|
|
221
|
+
template=Template.DOC,
|
|
222
|
+
pushId=doc.docCode,
|
|
223
|
+
))
|
|
178
224
|
|
|
179
225
|
# push 表格
|
|
180
|
-
sheet = client.excel.
|
|
226
|
+
sheet = client.excel.import_excel("销售日报.xlsx")
|
|
181
227
|
client.excel.write_cells(sheet.docCode, "A1", [["日期", "销售额"], ["2026-08-13", 12800]], "Sheet1")
|
|
182
228
|
client.excel.publish(sheet.docCode)
|
|
229
|
+
client.send(SendRequest(
|
|
230
|
+
title=sheet.title,
|
|
231
|
+
content="请查收",
|
|
232
|
+
template=Template.EXCEL,
|
|
233
|
+
pushId=sheet.docCode,
|
|
234
|
+
))
|
|
183
235
|
```
|
|
184
236
|
|
|
185
237
|
### 图片服务
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[pushplus(推送加)](https://www.pushplus.plus) 官方接口的 Python SDK,覆盖 **消息接口** 与 **全部开放接口**。
|
|
4
4
|
|
|
5
5
|
- **消息接口**(`/send`、`/batchSend`):单渠道 + 多渠道发送,含 Builder API。
|
|
6
|
-
- **开放接口**(`/api/open/...` 与 `/push/api/open/...`):用户、消息、消息 token、群组、群组用户、好友、webhook、渠道、ClawBot
|
|
6
|
+
- **开放接口**(`/api/open/...` 与 `/push/api/open/...`):用户、消息、消息 token、群组、群组用户、好友、webhook、渠道、ClawBot、QQ 机器人、功能设置、预处理信息、图片服务、push 表单、push 文档、push 表格。
|
|
7
7
|
- **AccessKey 自动管理**:缓存 + 过期前自动刷新;`code=401` 自动刷新并重试一次。
|
|
8
8
|
- **本地限流守卫**:命中 `code=900` 后按 token 短路同 token 后续发送,避免被服务端长期封禁。
|
|
9
9
|
- **回调解析**:`message_complate`、`add_topic_user`、`add_friend` 三类回调统一解析。
|
|
@@ -59,7 +59,7 @@ short_code = client.send(
|
|
|
59
59
|
.build()
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
-
# push
|
|
62
|
+
# push 表单 / 文档 / 表格:template 为 form/doc/excel 时需传 pushId(对应编码)
|
|
63
63
|
short_code = client.send(
|
|
64
64
|
SendRequest.builder()
|
|
65
65
|
.title("表单通知")
|
|
@@ -68,6 +68,22 @@ short_code = client.send(
|
|
|
68
68
|
.push_id("表单编码")
|
|
69
69
|
.build()
|
|
70
70
|
)
|
|
71
|
+
short_code = client.send(
|
|
72
|
+
SendRequest.builder()
|
|
73
|
+
.title("本周工作同步")
|
|
74
|
+
.content("请查收")
|
|
75
|
+
.template(Template.DOC)
|
|
76
|
+
.push_id("文档编码")
|
|
77
|
+
.build()
|
|
78
|
+
)
|
|
79
|
+
short_code = client.send(
|
|
80
|
+
SendRequest.builder()
|
|
81
|
+
.title("销售日报")
|
|
82
|
+
.content("请查收")
|
|
83
|
+
.template(Template.EXCEL)
|
|
84
|
+
.push_id("表格编码")
|
|
85
|
+
.build()
|
|
86
|
+
)
|
|
71
87
|
```
|
|
72
88
|
|
|
73
89
|
### 3. 多渠道发送
|
|
@@ -109,12 +125,31 @@ topics = client.topic.list(TopicListQuery.of(1, 20, topic_type=0))
|
|
|
109
125
|
detail = client.topic.detail(topic_id=123)
|
|
110
126
|
|
|
111
127
|
# 好友
|
|
128
|
+
from perk_pushplus import PageQuery, TopicUserListQuery
|
|
112
129
|
qr = client.friend.get_qr_code(content="welcome")
|
|
113
130
|
friends = client.friend.list()
|
|
131
|
+
client.friend.add_blacklist(friends.list[0].friendId)
|
|
132
|
+
friend_blacklist = client.friend.blacklist_list(PageQuery.of(1, 20))
|
|
133
|
+
client.topic_user.add_blacklist(topic_relation_id=10)
|
|
134
|
+
topic_blacklist = client.topic_user.blacklist_list(TopicUserListQuery.of(1, 20, topic_id=1))
|
|
114
135
|
|
|
115
136
|
# webhook 渠道
|
|
116
137
|
webhooks = client.webhook.list()
|
|
117
138
|
|
|
139
|
+
# QQ 机器人:绑定 -> 认领群 -> 建配置 -> 发到群
|
|
140
|
+
from perk_pushplus import QqBotSaveRequest
|
|
141
|
+
link = client.qq_bot.get_bind_link() # link.url 生成二维码,或私聊发送 link.bindCode
|
|
142
|
+
bind = client.qq_bot.bot_info() # bind.isBind == 1 表示已绑定
|
|
143
|
+
qq_groups = client.qq_bot.group_list()
|
|
144
|
+
client.qq_bot.add(QqBotSaveRequest(qqName="运维告警群", qqCode="ops-group", qqGroupId=qq_groups[0].id))
|
|
145
|
+
client.send(SendRequest(
|
|
146
|
+
title="服务告警",
|
|
147
|
+
content="订单服务响应超时",
|
|
148
|
+
channel=Channel.QQ,
|
|
149
|
+
option="ops-group", # 不传 option 则发给自己
|
|
150
|
+
template=Template.TXT,
|
|
151
|
+
))
|
|
152
|
+
|
|
118
153
|
# 设置
|
|
119
154
|
client.setting.change_is_send(1) # 启用发送
|
|
120
155
|
|
|
@@ -137,17 +172,34 @@ client.form.save(FormSaveRequest(
|
|
|
137
172
|
))
|
|
138
173
|
published = client.form.publish(form.id)
|
|
139
174
|
print(published.fillUrl)
|
|
175
|
+
client.send(SendRequest(
|
|
176
|
+
title=published.title,
|
|
177
|
+
content="请花1分钟完成填写",
|
|
178
|
+
template=Template.FORM,
|
|
179
|
+
pushId=published.formCode,
|
|
180
|
+
))
|
|
140
181
|
|
|
141
182
|
# push 文档
|
|
142
|
-
doc = client.doc.
|
|
143
|
-
client.doc.save_content(doc.docCode, "<h1>本周工作同步</h1><p>需求评审。</p>")
|
|
183
|
+
doc = client.doc.import_word("本周工作同步.docx")
|
|
144
184
|
client.doc.update_share(doc.docCode, 1, 0)
|
|
145
185
|
client.doc.publish(doc.docCode)
|
|
186
|
+
client.send(SendRequest(
|
|
187
|
+
title=doc.title,
|
|
188
|
+
content="请查收",
|
|
189
|
+
template=Template.DOC,
|
|
190
|
+
pushId=doc.docCode,
|
|
191
|
+
))
|
|
146
192
|
|
|
147
193
|
# push 表格
|
|
148
|
-
sheet = client.excel.
|
|
194
|
+
sheet = client.excel.import_excel("销售日报.xlsx")
|
|
149
195
|
client.excel.write_cells(sheet.docCode, "A1", [["日期", "销售额"], ["2026-08-13", 12800]], "Sheet1")
|
|
150
196
|
client.excel.publish(sheet.docCode)
|
|
197
|
+
client.send(SendRequest(
|
|
198
|
+
title=sheet.title,
|
|
199
|
+
content="请查收",
|
|
200
|
+
template=Template.EXCEL,
|
|
201
|
+
pushId=sheet.docCode,
|
|
202
|
+
))
|
|
151
203
|
```
|
|
152
204
|
|
|
153
205
|
### 图片服务
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "perk-pushplus-sdk"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.2"
|
|
8
8
|
description = "pushplus(推送加) 官方接口的 Python SDK,覆盖消息发送接口与全部开放接口。"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -65,6 +65,7 @@ from .models import (
|
|
|
65
65
|
FormTheme,
|
|
66
66
|
FriendItem,
|
|
67
67
|
FriendQrCode,
|
|
68
|
+
FriendBlacklistItem,
|
|
68
69
|
ImageItem,
|
|
69
70
|
ImageUploadResult,
|
|
70
71
|
ImageUploadToken,
|
|
@@ -83,6 +84,12 @@ from .models import (
|
|
|
83
84
|
PreItem,
|
|
84
85
|
PreSaveRequest,
|
|
85
86
|
PreTestRequest,
|
|
87
|
+
QqBotBindInfo,
|
|
88
|
+
QqBotBindLink,
|
|
89
|
+
QqBotInfo,
|
|
90
|
+
QqBotItem,
|
|
91
|
+
QqBotSaveRequest,
|
|
92
|
+
QqGroupItem,
|
|
86
93
|
SendCount,
|
|
87
94
|
SendMessageResult,
|
|
88
95
|
SendRequest,
|
|
@@ -95,6 +102,7 @@ from .models import (
|
|
|
95
102
|
TopicUserInfo,
|
|
96
103
|
TopicUserItem,
|
|
97
104
|
TopicUserListQuery,
|
|
105
|
+
TopicUserBlacklistItem,
|
|
98
106
|
UserDefaultDetail,
|
|
99
107
|
UserDefaultItem,
|
|
100
108
|
UserDefaultSaveRequest,
|
|
@@ -106,7 +114,7 @@ from .models import (
|
|
|
106
114
|
)
|
|
107
115
|
from .rate_limit import RateLimitGuard
|
|
108
116
|
|
|
109
|
-
__version__ = "1.2.
|
|
117
|
+
__version__ = "1.2.2"
|
|
110
118
|
|
|
111
119
|
__all__ = [
|
|
112
120
|
"__version__",
|
|
@@ -158,10 +166,12 @@ __all__ = [
|
|
|
158
166
|
"TopicListQuery",
|
|
159
167
|
"TopicUserListQuery",
|
|
160
168
|
"TopicUserItem",
|
|
169
|
+
"TopicUserBlacklistItem",
|
|
161
170
|
"TopicAddRequest",
|
|
162
171
|
"TopicEditRequest",
|
|
163
172
|
"FriendItem",
|
|
164
173
|
"FriendQrCode",
|
|
174
|
+
"FriendBlacklistItem",
|
|
165
175
|
"WebhookItem",
|
|
166
176
|
"WebhookSaveRequest",
|
|
167
177
|
"MpItem",
|
|
@@ -171,6 +181,12 @@ __all__ = [
|
|
|
171
181
|
"ClawBotQrCode",
|
|
172
182
|
"ClawBotInfo",
|
|
173
183
|
"ClawBotMessage",
|
|
184
|
+
"QqBotBindLink",
|
|
185
|
+
"QqBotInfo",
|
|
186
|
+
"QqBotBindInfo",
|
|
187
|
+
"QqGroupItem",
|
|
188
|
+
"QqBotItem",
|
|
189
|
+
"QqBotSaveRequest",
|
|
174
190
|
"UserDefaultItem",
|
|
175
191
|
"UserDefaultDetail",
|
|
176
192
|
"UserDefaultSaveRequest",
|
|
@@ -12,6 +12,7 @@ from .message import MessageApi
|
|
|
12
12
|
from .message_token import MessageTokenApi
|
|
13
13
|
from .open_message import OpenMessageApi
|
|
14
14
|
from .pre import PreApi
|
|
15
|
+
from .qqbot import QqBotApi
|
|
15
16
|
from .setting import SettingApi
|
|
16
17
|
from .topic import TopicApi
|
|
17
18
|
from .topic_user import TopicUserApi
|
|
@@ -32,6 +33,7 @@ __all__ = [
|
|
|
32
33
|
"WebhookApi",
|
|
33
34
|
"ChannelApi",
|
|
34
35
|
"ClawBotApi",
|
|
36
|
+
"QqBotApi",
|
|
35
37
|
"SettingApi",
|
|
36
38
|
"PreApi",
|
|
37
39
|
"ImageApi",
|
|
@@ -7,7 +7,7 @@ from urllib.parse import quote
|
|
|
7
7
|
|
|
8
8
|
from ..config import PushPlusConfig
|
|
9
9
|
from ..exceptions import PushPlusError
|
|
10
|
-
from ..http import HttpRequester, to_json
|
|
10
|
+
from ..http import HttpRequester, call_execute_raw, to_json
|
|
11
11
|
from ..models.base import ApiResponse, model_from_dict, model_to_dict
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
@@ -83,6 +83,25 @@ class AbstractApi:
|
|
|
83
83
|
)
|
|
84
84
|
return self._parse_api_response(resp.body, data_type)
|
|
85
85
|
|
|
86
|
+
def execute_raw(
|
|
87
|
+
self,
|
|
88
|
+
method: str,
|
|
89
|
+
path: str,
|
|
90
|
+
headers: Optional[Dict[str, str]],
|
|
91
|
+
body: Optional[bytes],
|
|
92
|
+
data_type: Optional[Type[T]] = None,
|
|
93
|
+
) -> ApiResponse[T]:
|
|
94
|
+
"""执行带二进制请求体的请求并返回原始 :class:`ApiResponse`。"""
|
|
95
|
+
|
|
96
|
+
url = self.resolve_url(path)
|
|
97
|
+
resp = call_execute_raw(self.http, method, url, headers, body)
|
|
98
|
+
if not resp.is_successful:
|
|
99
|
+
raise PushPlusError(
|
|
100
|
+
f"PushPlus 接口 HTTP 调用失败: status={resp.status_code}, body={resp.body}",
|
|
101
|
+
code=resp.status_code,
|
|
102
|
+
)
|
|
103
|
+
return self._parse_api_response(resp.body, data_type)
|
|
104
|
+
|
|
86
105
|
def execute_for_data(
|
|
87
106
|
self,
|
|
88
107
|
method: str,
|
|
@@ -206,5 +225,50 @@ class OpenAbstractApi(AbstractApi):
|
|
|
206
225
|
code=resp.code if resp.code is not None else -1,
|
|
207
226
|
)
|
|
208
227
|
|
|
228
|
+
def execute_open_multipart(
|
|
229
|
+
self,
|
|
230
|
+
path: str,
|
|
231
|
+
content_type: str,
|
|
232
|
+
body: bytes,
|
|
233
|
+
data_type: Optional[Type[T]] = None,
|
|
234
|
+
) -> Optional[T]:
|
|
235
|
+
"""以 multipart 上传文件(自动携带 access-key;code=401 时刷新后重试一次)。"""
|
|
236
|
+
|
|
237
|
+
extra = {"Content-Type": content_type}
|
|
238
|
+
return self.execute_open_raw("POST", path, extra, body, data_type)
|
|
239
|
+
|
|
240
|
+
def execute_open_raw(
|
|
241
|
+
self,
|
|
242
|
+
method: str,
|
|
243
|
+
path: str,
|
|
244
|
+
extra_headers: Optional[Dict[str, str]],
|
|
245
|
+
body: Optional[bytes],
|
|
246
|
+
data_type: Optional[Type[T]] = None,
|
|
247
|
+
) -> Optional[T]:
|
|
248
|
+
"""执行带二进制 body 的开放接口请求;当返回 ``code=401`` 时自动刷新 key 并重试一次。"""
|
|
249
|
+
|
|
250
|
+
headers = self._headers_with_access_key()
|
|
251
|
+
if extra_headers:
|
|
252
|
+
headers.update(extra_headers)
|
|
253
|
+
resp = self.execute_raw(method, path, headers, body, data_type)
|
|
254
|
+
if resp.is_success():
|
|
255
|
+
return resp.data
|
|
256
|
+
if resp.code is not None and int(resp.code) == _CODE_ACCESS_KEY_INVALID:
|
|
257
|
+
self.access_key_manager.invalidate()
|
|
258
|
+
retry_headers = self._headers_with_access_key()
|
|
259
|
+
if extra_headers:
|
|
260
|
+
retry_headers.update(extra_headers)
|
|
261
|
+
retry = self.execute_raw(method, path, retry_headers, body, data_type)
|
|
262
|
+
if retry.is_success():
|
|
263
|
+
return retry.data
|
|
264
|
+
raise PushPlusError(
|
|
265
|
+
f"PushPlus 开放接口业务失败(重试后): code={retry.code}, msg={retry.msg}",
|
|
266
|
+
code=retry.code if retry.code is not None else -1,
|
|
267
|
+
)
|
|
268
|
+
raise PushPlusError(
|
|
269
|
+
f"PushPlus 开放接口业务失败: code={resp.code}, msg={resp.msg}",
|
|
270
|
+
code=resp.code if resp.code is not None else -1,
|
|
271
|
+
)
|
|
272
|
+
|
|
209
273
|
|
|
210
274
|
__all__ = ["AbstractApi", "OpenAbstractApi", "HEADER_ACCESS_KEY"]
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
"""开放接口 - push 文档(文档:https://www.pushplus.plus/doc/ecosystem/doc/)。
|
|
1
|
+
"""开放接口 - push 文档(文档:https://www.pushplus.plus/doc/ecosystem/doc/)。
|
|
2
|
+
|
|
3
|
+
文档开放接口不单独提供推送接口。发布后请通过 :class:`~perk_pushplus.api.message.MessageApi`
|
|
4
|
+
推送分享页:``template=doc``,``pushId=docCode``。
|
|
5
|
+
"""
|
|
2
6
|
from __future__ import annotations
|
|
3
7
|
|
|
4
|
-
from
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Union
|
|
5
10
|
|
|
6
11
|
from ..models import DocContent, DocListItem, DocListQuery, DocVo, PageResult
|
|
12
|
+
from ..multipart import build_file_multipart
|
|
7
13
|
from .base import OpenAbstractApi
|
|
8
14
|
|
|
9
15
|
|
|
@@ -22,6 +28,17 @@ class DocApi(OpenAbstractApi):
|
|
|
22
28
|
|
|
23
29
|
return self.execute_open("POST", "/push/api/open/doc/create", {"title": title}, DocVo)
|
|
24
30
|
|
|
31
|
+
def import_word(self, file: Union[str, Path, bytes], file_name: Optional[str] = None) -> DocVo:
|
|
32
|
+
"""导入 Word(``.docx``)创建文档。
|
|
33
|
+
|
|
34
|
+
标题默认取文件名;创建后默认关闭分享,需再调用 :meth:`publish` 才会同步到分享页。
|
|
35
|
+
``file`` 可为路径或文件字节。
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
data, name = _read_upload_file(file, file_name, "document.docx")
|
|
39
|
+
content_type, body = build_file_multipart(name, _guess_docx_content_type(name), data)
|
|
40
|
+
return self.execute_open_multipart("/push/api/open/doc/import", content_type, body, DocVo)
|
|
41
|
+
|
|
25
42
|
def content(self, doc_code: str) -> DocContent:
|
|
26
43
|
"""获取文档元信息与 HTML 草稿正文。"""
|
|
27
44
|
|
|
@@ -67,4 +84,22 @@ class DocApi(OpenAbstractApi):
|
|
|
67
84
|
return self.execute_open("POST", "/push/api/open/doc/updateShare", body, DocVo)
|
|
68
85
|
|
|
69
86
|
|
|
87
|
+
def _read_upload_file(
|
|
88
|
+
file: Union[str, Path, bytes], file_name: Optional[str], default_name: str
|
|
89
|
+
) -> tuple:
|
|
90
|
+
if isinstance(file, bytes):
|
|
91
|
+
name = file_name or default_name
|
|
92
|
+
return file, name
|
|
93
|
+
path = Path(file)
|
|
94
|
+
data = path.read_bytes()
|
|
95
|
+
name = file_name or path.name or default_name
|
|
96
|
+
return data, name
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _guess_docx_content_type(name: str) -> str:
|
|
100
|
+
if name.lower().endswith(".docx"):
|
|
101
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
102
|
+
return "application/octet-stream"
|
|
103
|
+
|
|
104
|
+
|
|
70
105
|
__all__ = ["DocApi"]
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
"""开放接口 - push 表格(文档:https://www.pushplus.plus/doc/ecosystem/sheet/)。
|
|
1
|
+
"""开放接口 - push 表格(文档:https://www.pushplus.plus/doc/ecosystem/sheet/)。
|
|
2
|
+
|
|
3
|
+
表格开放接口不单独提供推送接口。发布后请通过 :class:`~perk_pushplus.api.message.MessageApi`
|
|
4
|
+
推送分享页:``template=excel``,``pushId=docCode``。
|
|
5
|
+
"""
|
|
2
6
|
from __future__ import annotations
|
|
3
7
|
|
|
4
8
|
import json
|
|
5
|
-
from
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Optional, Sequence, Union
|
|
6
11
|
|
|
7
12
|
from ..models import DocListItem, DocListQuery, ExcelContent, ExcelVo, PageResult
|
|
13
|
+
from ..multipart import build_file_multipart
|
|
8
14
|
from .base import OpenAbstractApi
|
|
9
15
|
|
|
10
16
|
|
|
@@ -25,6 +31,20 @@ class ExcelApi(OpenAbstractApi):
|
|
|
25
31
|
"POST", "/push/api/open/excel/create", {"title": title}, ExcelVo
|
|
26
32
|
)
|
|
27
33
|
|
|
34
|
+
def import_excel(
|
|
35
|
+
self, file: Union[str, Path, bytes], file_name: Optional[str] = None
|
|
36
|
+
) -> ExcelVo:
|
|
37
|
+
"""导入 Excel(``.xlsx`` / ``.xls``)创建表格。
|
|
38
|
+
|
|
39
|
+
标题默认取文件名;创建后默认关闭分享,需再调用 :meth:`publish` 才会同步到分享页。
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
data, name = _read_upload_file(file, file_name, "workbook.xlsx")
|
|
43
|
+
content_type, body = build_file_multipart(name, _guess_excel_content_type(name), data)
|
|
44
|
+
return self.execute_open_multipart(
|
|
45
|
+
"/push/api/open/excel/import", content_type, body, ExcelVo
|
|
46
|
+
)
|
|
47
|
+
|
|
28
48
|
def content(self, doc_code: str) -> ExcelContent:
|
|
29
49
|
"""获取表格元信息与整表 JSON 草稿。"""
|
|
30
50
|
|
|
@@ -93,4 +113,25 @@ def _as_json_string(content: Any) -> str:
|
|
|
93
113
|
return json.dumps(content, ensure_ascii=False)
|
|
94
114
|
|
|
95
115
|
|
|
116
|
+
def _read_upload_file(
|
|
117
|
+
file: Union[str, Path, bytes], file_name: Optional[str], default_name: str
|
|
118
|
+
) -> tuple:
|
|
119
|
+
if isinstance(file, bytes):
|
|
120
|
+
name = file_name or default_name
|
|
121
|
+
return file, name
|
|
122
|
+
path = Path(file)
|
|
123
|
+
data = path.read_bytes()
|
|
124
|
+
name = file_name or path.name or default_name
|
|
125
|
+
return data, name
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _guess_excel_content_type(name: str) -> str:
|
|
129
|
+
lower = name.lower()
|
|
130
|
+
if lower.endswith(".xlsx"):
|
|
131
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
132
|
+
if lower.endswith(".xls"):
|
|
133
|
+
return "application/vnd.ms-excel"
|
|
134
|
+
return "application/octet-stream"
|
|
135
|
+
|
|
136
|
+
|
|
96
137
|
__all__ = ["ExcelApi"]
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""开放接口 - push 表单(文档:https://www.pushplus.plus/doc/ecosystem/form/)。
|
|
1
|
+
"""开放接口 - push 表单(文档:https://www.pushplus.plus/doc/ecosystem/form/)。
|
|
2
|
+
|
|
3
|
+
表单开放接口不单独提供推送接口。发布后请通过 :class:`~perk_pushplus.api.message.MessageApi`
|
|
4
|
+
推送填写页:``template=form``,``pushId=formCode``。
|
|
5
|
+
"""
|
|
2
6
|
from __future__ import annotations
|
|
3
7
|
|
|
4
8
|
from typing import Optional
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
from ..models import FriendItem, FriendQrCode, PageQuery, PageResult
|
|
6
|
+
from ..models import FriendBlacklistItem, FriendItem, FriendQrCode, PageQuery, PageResult
|
|
7
7
|
from .base import OpenAbstractApi
|
|
8
8
|
|
|
9
9
|
|
|
@@ -54,5 +54,36 @@ class FriendApi(OpenAbstractApi):
|
|
|
54
54
|
body = {"id": int(id_), "remark": remark}
|
|
55
55
|
self.execute_open("POST", "/api/open/friend/editRemark", body, None)
|
|
56
56
|
|
|
57
|
+
def add_blacklist(self, friend_id: int) -> None:
|
|
58
|
+
"""将好友加入黑名单。
|
|
59
|
+
|
|
60
|
+
加入后将解除双方好友关系,对方无法再添加你。不能将自己加入黑名单,仅可将已有好友加入黑名单。
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
path = self.append_query(
|
|
64
|
+
"/api/open/friend/addBlacklist", {"friendId": int(friend_id)}
|
|
65
|
+
)
|
|
66
|
+
self.execute_open("POST", path, None, None)
|
|
67
|
+
|
|
68
|
+
def blacklist_list(
|
|
69
|
+
self, query: Optional[PageQuery] = None
|
|
70
|
+
) -> PageResult[FriendBlacklistItem]:
|
|
71
|
+
"""好友黑名单列表。"""
|
|
72
|
+
|
|
73
|
+
body = query if query is not None else PageQuery()
|
|
74
|
+
result = self.execute_open(
|
|
75
|
+
"POST", "/api/open/friend/blacklistList", body, PageResult[FriendBlacklistItem]
|
|
76
|
+
)
|
|
77
|
+
return result or PageResult(list=[])
|
|
78
|
+
|
|
79
|
+
def remove_blacklist(self, id_: int) -> None:
|
|
80
|
+
"""解除好友黑名单。
|
|
81
|
+
|
|
82
|
+
解除后不会自动恢复好友关系,需重新扫码添加。
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
path = self.append_query("/api/open/friend/removeBlacklist", {"id": int(id_)})
|
|
86
|
+
self.execute_open("POST", path, None, None)
|
|
87
|
+
|
|
57
88
|
|
|
58
89
|
__all__ = ["FriendApi"]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""开放接口 - QQ 机器人(文档「九. QQ机器人接口」)。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import replace
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from ..models import (
|
|
8
|
+
PageQuery,
|
|
9
|
+
PageResult,
|
|
10
|
+
QqBotBindInfo,
|
|
11
|
+
QqBotBindLink,
|
|
12
|
+
QqBotItem,
|
|
13
|
+
QqBotSaveRequest,
|
|
14
|
+
QqGroupItem,
|
|
15
|
+
)
|
|
16
|
+
from .base import OpenAbstractApi
|
|
17
|
+
|
|
18
|
+
SEND_TYPE_QQ_GROUP = 2
|
|
19
|
+
"""发送到 QQ 群,目前渠道配置仅支持该类型。"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class QqBotApi(OpenAbstractApi):
|
|
23
|
+
"""QQ 机器人绑定与渠道配置相关接口。"""
|
|
24
|
+
|
|
25
|
+
def get_bind_link(self, refresh: bool = False) -> QqBotBindLink:
|
|
26
|
+
"""获取绑定链接与绑定码;``refresh=True`` 时旧绑定码失效并重新生成。"""
|
|
27
|
+
|
|
28
|
+
path = "/api/open/qqBot/getBindLink"
|
|
29
|
+
if refresh:
|
|
30
|
+
path = self.append_query(path, {"refresh": "true"})
|
|
31
|
+
return self.execute_open("GET", path, None, QqBotBindLink)
|
|
32
|
+
|
|
33
|
+
def bot_info(self) -> QqBotBindInfo:
|
|
34
|
+
"""查询绑定状态。"""
|
|
35
|
+
|
|
36
|
+
return self.execute_open(
|
|
37
|
+
"GET", "/api/open/qqBot/botInfo", None, QqBotBindInfo
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def unbind(self) -> None:
|
|
41
|
+
"""解绑 QQ 机器人。"""
|
|
42
|
+
|
|
43
|
+
self.execute_open("GET", "/api/open/qqBot/unbind", None, None)
|
|
44
|
+
|
|
45
|
+
def group_list(self) -> List[QqGroupItem]:
|
|
46
|
+
"""获取机器人已加入的 QQ 群列表。"""
|
|
47
|
+
|
|
48
|
+
result = self.execute_open(
|
|
49
|
+
"GET", "/api/open/qqBot/groupList", None, List[QqGroupItem]
|
|
50
|
+
)
|
|
51
|
+
return result or []
|
|
52
|
+
|
|
53
|
+
def list(self, query: Optional[PageQuery] = None) -> PageResult[QqBotItem]:
|
|
54
|
+
"""获取 QQ 机器人渠道配置列表。"""
|
|
55
|
+
|
|
56
|
+
body = query if query is not None else PageQuery()
|
|
57
|
+
result = self.execute_open(
|
|
58
|
+
"POST", "/api/open/qqBot/list", body, PageResult[QqBotItem]
|
|
59
|
+
)
|
|
60
|
+
return result or PageResult(list=[])
|
|
61
|
+
|
|
62
|
+
def add(self, request: QqBotSaveRequest) -> None:
|
|
63
|
+
"""新增渠道配置,用于把消息发送到指定 QQ 群;发给自己无需创建配置。"""
|
|
64
|
+
|
|
65
|
+
self.execute_open(
|
|
66
|
+
"POST", "/api/open/qqBot/add", _with_default_send_type(request), None
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def edit(self, request: QqBotSaveRequest) -> None:
|
|
70
|
+
"""修改渠道配置;配置编码不可修改。"""
|
|
71
|
+
|
|
72
|
+
self.execute_open(
|
|
73
|
+
"POST", "/api/open/qqBot/edit", _with_default_send_type(request), None
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def delete(self, config_id: int) -> None:
|
|
77
|
+
"""删除渠道配置。"""
|
|
78
|
+
|
|
79
|
+
path = self.append_query("/api/open/qqBot/delete", {"id": int(config_id)})
|
|
80
|
+
self.execute_open("DELETE", path, None, None)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _with_default_send_type(request: QqBotSaveRequest) -> QqBotSaveRequest:
|
|
84
|
+
if request is None or request.sendType is not None:
|
|
85
|
+
return request
|
|
86
|
+
return replace(request, sendType=SEND_TYPE_QQ_GROUP)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = ["QqBotApi", "SEND_TYPE_QQ_GROUP"]
|