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.
Files changed (65) hide show
  1. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/PKG-INFO +58 -6
  2. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/README.md +57 -5
  3. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/pyproject.toml +1 -1
  4. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/__init__.py +17 -1
  5. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/__init__.py +2 -0
  6. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/base.py +65 -1
  7. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/doc.py +37 -2
  8. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/excel.py +43 -2
  9. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/form.py +5 -1
  10. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/friend.py +32 -1
  11. perk_pushplus_sdk-1.2.2/src/perk_pushplus/api/qqbot.py +89 -0
  12. perk_pushplus_sdk-1.2.2/src/perk_pushplus/api/topic_user.py +69 -0
  13. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/client.py +2 -0
  14. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/config.py +1 -1
  15. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/enums.py +2 -0
  16. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/__init__.py +18 -1
  17. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_doc.py +12 -13
  18. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_form.py +11 -8
  19. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_friend.py +12 -1
  20. perk_pushplus_sdk-1.2.2/src/perk_pushplus/models/open_qqbot.py +107 -0
  21. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_topic.py +13 -0
  22. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_user.py +1 -0
  23. perk_pushplus_sdk-1.2.2/src/perk_pushplus/multipart.py +43 -0
  24. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/PKG-INFO +58 -6
  25. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/SOURCES.txt +3 -0
  26. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_open_api.py +130 -0
  27. perk_pushplus_sdk-1.2.0/src/perk_pushplus/api/topic_user.py +0 -35
  28. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/setup.cfg +0 -0
  29. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/access_key.py +0 -0
  30. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/access_key.py +0 -0
  31. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/channel.py +0 -0
  32. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/clawbot.py +0 -0
  33. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/image.py +0 -0
  34. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/message.py +0 -0
  35. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/message_token.py +0 -0
  36. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/open_message.py +0 -0
  37. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/pre.py +0 -0
  38. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/setting.py +0 -0
  39. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/topic.py +0 -0
  40. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/user.py +0 -0
  41. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/api/webhook.py +0 -0
  42. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/callback_parser.py +0 -0
  43. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/exceptions.py +0 -0
  44. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/http.py +0 -0
  45. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/base.py +0 -0
  46. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/callback.py +0 -0
  47. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_access.py +0 -0
  48. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_channel.py +0 -0
  49. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_clawbot.py +0 -0
  50. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_image.py +0 -0
  51. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_message.py +0 -0
  52. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_pre.py +0 -0
  53. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_setting.py +0 -0
  54. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_token.py +0 -0
  55. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/open_webhook.py +0 -0
  56. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/models/send.py +0 -0
  57. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/py.typed +0 -0
  58. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus/rate_limit.py +0 -0
  59. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/dependency_links.txt +0 -0
  60. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/requires.txt +0 -0
  61. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/src/perk_pushplus_sdk.egg-info/top_level.txt +0 -0
  62. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_enums_and_exceptions.py +0 -0
  63. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_image_api.py +0 -0
  64. {perk_pushplus_sdk-1.2.0 → perk_pushplus_sdk-1.2.2}/tests/test_message_api.py +0 -0
  65. {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.0
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、功能设置、预处理信息、图片服务、push 表单、push 文档、push 表格。
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 表单:template=form 时需传 pushId(表单编码)
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.create("本周工作同步")
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.create("销售日报")
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、功能设置、预处理信息、图片服务、push 表单、push 文档、push 表格。
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 表单:template=form 时需传 pushId(表单编码)
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.create("本周工作同步")
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.create("销售日报")
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.0"
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.0"
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 typing import Optional
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 typing import Any, Optional, Sequence
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"]