dootask-tools 1.0.0__py3-none-any.whl

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.
client.py ADDED
@@ -0,0 +1,443 @@
1
+ """
2
+ DooTask Tools 客户端
3
+ """
4
+
5
+ import json
6
+ import time
7
+ from typing import Optional, List, Dict, Any, Union, TypeVar, Type
8
+ from datetime import datetime, timedelta
9
+ from dataclasses import asdict, is_dataclass
10
+ from urllib.parse import urlencode, quote
11
+
12
+ import requests
13
+
14
+ from models import *
15
+ from exceptions import *
16
+
17
+ T = TypeVar('T')
18
+
19
+ class DooTaskClient:
20
+ """DooTask 客户端"""
21
+
22
+ def __init__(self, token: str, server: str = "http://nginx", timeout: int = 10):
23
+ """
24
+ 初始化客户端
25
+
26
+ Args:
27
+ token: 认证令牌
28
+ server: 服务器地址
29
+ timeout: 请求超时时间(秒)
30
+ """
31
+ self.token = token
32
+ self.server = server.rstrip('/')
33
+ self.timeout = timeout
34
+ self._cache: Dict[str, Dict[str, Any]] = {}
35
+ self._cache_time = 600 # 10分钟缓存
36
+
37
+ # 创建会话
38
+ self.session = requests.Session()
39
+ self.session.headers.update({
40
+ 'Token': token,
41
+ 'User-Agent': 'DooTask-Tools/1.0',
42
+ 'Content-Type': 'application/json'
43
+ })
44
+
45
+ def _build_url(self, base_url: str, params: Optional[Dict[str, Any]] = None) -> str:
46
+ """构建带查询参数的URL"""
47
+ if not params:
48
+ return base_url
49
+
50
+ query_params = []
51
+ for key, value in params.items():
52
+ if value is None:
53
+ continue
54
+
55
+ if isinstance(value, bool):
56
+ query_params.append(f"{key}={1 if value else 0}")
57
+ elif isinstance(value, str):
58
+ if value:
59
+ query_params.append(f"{key}={quote(value)}")
60
+ elif isinstance(value, (int, float)):
61
+ query_params.append(f"{key}={value}")
62
+ elif isinstance(value, list):
63
+ for item in value:
64
+ if isinstance(item, str):
65
+ query_params.append(f"{key}[]={quote(item)}")
66
+ else:
67
+ query_params.append(f"{key}[]={item}")
68
+ else:
69
+ query_params.append(f"{key}={quote(str(value))}")
70
+
71
+ if not query_params:
72
+ return base_url
73
+
74
+ separator = "&" if "?" in base_url else "?"
75
+ return base_url + separator + "&".join(query_params)
76
+
77
+ def _dataclass_to_dict(self, obj: Any) -> Dict[str, Any]:
78
+ """将数据类转换为字典"""
79
+ if obj is None:
80
+ return {}
81
+
82
+ if is_dataclass(obj):
83
+ return asdict(obj)
84
+
85
+ if isinstance(obj, dict):
86
+ return obj
87
+
88
+ # 如果是普通对象,尝试转换为字典
89
+ if hasattr(obj, '__dict__'):
90
+ return obj.__dict__
91
+
92
+ return {}
93
+
94
+ def _make_request(self, method: str, api: str, request_data: Any = None,
95
+ response_type: Optional[Type[T]] = None,
96
+ headers: Optional[Dict[str, Any]] = None) -> Optional[T]:
97
+ """发送请求"""
98
+ url = self.server + api
99
+
100
+ # 设置请求头
101
+ request_headers = self.session.headers.copy()
102
+ if headers:
103
+ request_headers.update(headers)
104
+
105
+ # 处理请求数据
106
+ if method.upper() == 'GET':
107
+ # GET 请求:将数据作为查询参数
108
+ if request_data:
109
+ params = self._dataclass_to_dict(request_data)
110
+ url = self._build_url(url, params)
111
+
112
+ response = self.session.get(url, timeout=self.timeout)
113
+
114
+ elif method.upper() in ['POST', 'PUT', 'PATCH']:
115
+ # POST/PUT/PATCH 请求:将数据作为 JSON body
116
+ json_data = None
117
+ if request_data:
118
+ json_data = self._dataclass_to_dict(request_data)
119
+
120
+ response = self.session.request(
121
+ method, url, json=json_data,
122
+ headers=request_headers, timeout=self.timeout
123
+ )
124
+
125
+ elif method.upper() == 'DELETE':
126
+ # DELETE 请求:支持查询参数
127
+ if request_data:
128
+ params = self._dataclass_to_dict(request_data)
129
+ url = self._build_url(url, params)
130
+
131
+ response = self.session.delete(url, timeout=self.timeout)
132
+
133
+ else:
134
+ raise DooTaskException(f"不支持的 HTTP 方法: {method}")
135
+
136
+ # 检查 HTTP 状态码
137
+ if response.status_code != 200:
138
+ raise DooTaskHTTPException(
139
+ f"HTTP {response.status_code}: {response.reason}, body: {response.text}",
140
+ response.status_code
141
+ )
142
+
143
+ # 解析响应
144
+ try:
145
+ api_response = response.json()
146
+ except (json.JSONDecodeError, ValueError):
147
+ raise DooTaskException("响应不是有效的 JSON 格式")
148
+
149
+ # 检查业务状态
150
+ if api_response.get('ret') != 1:
151
+ error_msg = api_response.get('msg', f"API 错误: {api_response.get('ret', 'unknown')}")
152
+ raise DooTaskAPIException(error_msg, api_response.get('ret', 0))
153
+
154
+ # 如果不需要响应数据,直接返回 None
155
+ if response_type is None:
156
+ return None
157
+
158
+ # 解析数据到目标类型
159
+ data = api_response.get('data')
160
+ if data is None:
161
+ return None
162
+
163
+ # 如果是原始类型,直接返回
164
+ if response_type in [str, int, float, bool, dict, list]:
165
+ return data
166
+
167
+ # 如果是数据类,进行转换
168
+ if is_dataclass(response_type):
169
+ if isinstance(data, dict):
170
+ return response_type(**data)
171
+ elif isinstance(data, list):
172
+ return [response_type(**item) if isinstance(item, dict) else item for item in data]
173
+
174
+ return data
175
+
176
+ def _get_request(self, api: str, request_data: Any = None,
177
+ response_type: Optional[Type[T]] = None,
178
+ headers: Optional[Dict[str, Any]] = None) -> Optional[T]:
179
+ """发送 GET 请求"""
180
+ return self._make_request('GET', api, request_data, response_type, headers)
181
+
182
+ def _post_request(self, api: str, request_data: Any = None,
183
+ response_type: Optional[Type[T]] = None) -> Optional[T]:
184
+ """发送 POST 请求"""
185
+ return self._make_request('POST', api, request_data, response_type)
186
+
187
+ # ------------------------------------------------------------------------------------------
188
+ # 用户相关接口
189
+ # ------------------------------------------------------------------------------------------
190
+
191
+ def get_user_info(self, no_cache: bool = False) -> UserInfo:
192
+ """获取用户信息"""
193
+ cache_key = f"user_info_{self.token}"
194
+
195
+ # 检查缓存
196
+ if not no_cache and cache_key in self._cache:
197
+ cache_data = self._cache[cache_key]
198
+ if time.time() < cache_data['expires_at']:
199
+ return cache_data['data']
200
+
201
+ # 获取用户信息
202
+ user_info = self._get_request('/api/users/info', response_type=UserInfo)
203
+
204
+ # 更新缓存
205
+ self._cache[cache_key] = {
206
+ 'data': user_info,
207
+ 'expires_at': time.time() + self._cache_time
208
+ }
209
+
210
+ return user_info
211
+
212
+ def check_user_identity(self, identity: str) -> UserInfo:
213
+ """检查用户是否具有指定身份"""
214
+ user = self.get_user_info()
215
+
216
+ if identity not in user.identity:
217
+ raise DooTaskPermissionException("权限不足")
218
+
219
+ return user
220
+
221
+ def get_user_departments(self) -> List[Department]:
222
+ """获取用户部门信息"""
223
+ return self._get_request('/api/users/info/departments', response_type=List[Department])
224
+
225
+ def get_users_basic(self, userids: List[int]) -> List[UserBasic]:
226
+ """获取指定用户基础信息(支持多个用户)"""
227
+ params = {'userid': userids}
228
+ return self._get_request('/api/users/basic', params, response_type=List[UserBasic])
229
+
230
+ def get_user_basic(self, userid: int) -> UserBasic:
231
+ """获取指定用户基础信息(单个用户)"""
232
+ users = self.get_users_basic([userid])
233
+ if not users:
234
+ raise DooTaskException("用户不存在")
235
+ return users[0]
236
+
237
+ # ------------------------------------------------------------------------------------------
238
+ # 消息相关接口
239
+ # ------------------------------------------------------------------------------------------
240
+
241
+ def send_message(self, message: SendMessageRequest) -> None:
242
+ """发送消息"""
243
+ if not message.text_type:
244
+ message.text_type = "md"
245
+
246
+ self._post_request('/api/dialog/msg/sendtext', message)
247
+
248
+ def send_message_to_user(self, message: SendMessageToUserRequest) -> None:
249
+ """发送消息到用户"""
250
+ # 获取用户对话ID
251
+ query_params = {'userid': message.userid}
252
+ response = self._get_request('/api/dialog/open/user', query_params,
253
+ response_type=DialogOpenUserResponse)
254
+
255
+ # 发送消息
256
+ self.send_message(SendMessageRequest(
257
+ dialog_id=response.dialog_user.dialog_id,
258
+ text=message.text,
259
+ text_type=message.text_type,
260
+ silence=message.silence
261
+ ))
262
+
263
+ def send_bot_message(self, message: SendBotMessageRequest) -> None:
264
+ """发送机器人消息"""
265
+ if not message.bot_type:
266
+ message.bot_type = "system-msg"
267
+
268
+ self._post_request('/api/dialog/msg/sendbot', message)
269
+
270
+ def send_anonymous_message(self, message: SendAnonymousMessageRequest) -> None:
271
+ """发送匿名消息"""
272
+ self._post_request('/api/dialog/msg/sendanon', message)
273
+
274
+ # ------------------------------------------------------------------------------------------
275
+ # 对话相关接口
276
+ # ------------------------------------------------------------------------------------------
277
+
278
+ def get_dialog_list(self, params: Optional[TimeRangeRequest] = None) -> ResponsePaginate[DialogInfo]:
279
+ """获取对话列表"""
280
+ if params is None:
281
+ params = TimeRangeRequest()
282
+ return self._get_request('/api/dialog/lists', params, response_type=ResponsePaginate[DialogInfo])
283
+
284
+ def search_dialog(self, params: SearchDialogRequest) -> List[DialogInfo]:
285
+ """搜索会话"""
286
+ return self._get_request('/api/dialog/search', params, response_type=List[DialogInfo])
287
+
288
+ def get_dialog_one(self, params: GetDialogRequest) -> DialogInfo:
289
+ """获取单个会话信息"""
290
+ return self._get_request('/api/dialog/one', params, response_type=DialogInfo)
291
+
292
+ def get_dialog_user(self, params: GetDialogUserRequest) -> List[DialogMember]:
293
+ """获取会话成员"""
294
+ return self._get_request('/api/dialog/user', params, response_type=List[DialogMember])
295
+
296
+ # ------------------------------------------------------------------------------------------
297
+ # 群组相关接口
298
+ # ------------------------------------------------------------------------------------------
299
+
300
+ def create_group(self, params: CreateGroupRequest) -> DialogInfo:
301
+ """新增群组"""
302
+ return self._get_request('/api/dialog/group/add', params, response_type=DialogInfo)
303
+
304
+ def edit_group(self, params: EditGroupRequest) -> None:
305
+ """修改群组"""
306
+ self._get_request('/api/dialog/group/edit', params)
307
+
308
+ def add_group_user(self, params: AddGroupUserRequest) -> None:
309
+ """添加群成员"""
310
+ self._get_request('/api/dialog/group/adduser', params)
311
+
312
+ def remove_group_user(self, params: RemoveGroupUserRequest) -> None:
313
+ """移除群成员"""
314
+ self._get_request('/api/dialog/group/deluser', params)
315
+
316
+ def exit_group(self, dialog_id: int) -> None:
317
+ """退出群组"""
318
+ self.remove_group_user(RemoveGroupUserRequest(dialog_id=dialog_id))
319
+
320
+ def transfer_group(self, params: TransferGroupRequest) -> None:
321
+ """转让群组"""
322
+ self._get_request('/api/dialog/group/transfer', params)
323
+
324
+ def disband_group(self, params: DisbandGroupRequest) -> None:
325
+ """解散群组"""
326
+ self._get_request('/api/dialog/group/disband', params)
327
+
328
+ # ------------------------------------------------------------------------------------------
329
+ # 项目管理相关接口
330
+ # ------------------------------------------------------------------------------------------
331
+
332
+ def get_project_list(self, params: Optional[GetProjectListRequest] = None) -> ResponsePaginate[Project]:
333
+ """获取项目列表"""
334
+ if params is None:
335
+ params = GetProjectListRequest()
336
+ return self._get_request('/api/project/lists', params, response_type=ResponsePaginate[Project])
337
+
338
+ def get_project(self, params: GetProjectRequest) -> Project:
339
+ """获取项目信息"""
340
+ return self._get_request('/api/project/one', params, response_type=Project)
341
+
342
+ def create_project(self, params: CreateProjectRequest) -> Project:
343
+ """创建项目"""
344
+ return self._get_request('/api/project/add', params, response_type=Project)
345
+
346
+ def update_project(self, params: UpdateProjectRequest) -> Project:
347
+ """更新项目"""
348
+ return self._get_request('/api/project/update', params, response_type=Project)
349
+
350
+ def exit_project(self, project_id: int) -> None:
351
+ """退出项目"""
352
+ params = ProjectActionRequest(project_id=project_id)
353
+ self._get_request('/api/project/exit', params)
354
+
355
+ def delete_project(self, project_id: int) -> None:
356
+ """删除项目"""
357
+ params = ProjectActionRequest(project_id=project_id)
358
+ self._get_request('/api/project/remove', params)
359
+
360
+ # ------------------------------------------------------------------------------------------
361
+ # 任务列表相关接口
362
+ # ------------------------------------------------------------------------------------------
363
+
364
+ def get_column_list(self, params: GetColumnListRequest) -> ResponsePaginate[ProjectColumn]:
365
+ """获取任务列表"""
366
+ return self._get_request('/api/project/column/lists', params,
367
+ response_type=ResponsePaginate[ProjectColumn])
368
+
369
+ def create_column(self, params: CreateColumnRequest) -> ProjectColumn:
370
+ """创建任务列表"""
371
+ return self._get_request('/api/project/column/add', params, response_type=ProjectColumn)
372
+
373
+ def update_column(self, params: UpdateColumnRequest) -> ProjectColumn:
374
+ """更新任务列表"""
375
+ return self._get_request('/api/project/column/update', params, response_type=ProjectColumn)
376
+
377
+ def delete_column(self, column_id: int) -> None:
378
+ """删除任务列表"""
379
+ params = ColumnActionRequest(column_id=column_id)
380
+ self._get_request('/api/project/column/remove', params)
381
+
382
+ # ------------------------------------------------------------------------------------------
383
+ # 任务相关接口
384
+ # ------------------------------------------------------------------------------------------
385
+
386
+ def get_task_list(self, params: Optional[GetTaskListRequest] = None) -> ResponsePaginate[ProjectTask]:
387
+ """获取任务列表"""
388
+ if params is None:
389
+ params = GetTaskListRequest()
390
+ return self._get_request('/api/project/task/lists', params,
391
+ response_type=ResponsePaginate[ProjectTask])
392
+
393
+ def get_task(self, params: GetTaskRequest) -> ProjectTask:
394
+ """获取任务信息"""
395
+ return self._get_request('/api/project/task/one', params, response_type=ProjectTask)
396
+
397
+ def get_task_content(self, params: GetTaskContentRequest) -> TaskContent:
398
+ """获取任务内容"""
399
+ return self._get_request('/api/project/task/content', params, response_type=TaskContent)
400
+
401
+ def get_task_files(self, params: GetTaskFilesRequest) -> List[TaskFile]:
402
+ """获取任务文件列表"""
403
+ return self._get_request('/api/project/task/files', params, response_type=List[TaskFile])
404
+
405
+ def create_task(self, params: CreateTaskRequest) -> ProjectTask:
406
+ """创建任务"""
407
+ return self._post_request('/api/project/task/add', params, response_type=ProjectTask)
408
+
409
+ def create_sub_task(self, params: CreateSubTaskRequest) -> ProjectTask:
410
+ """创建子任务"""
411
+ return self._get_request('/api/project/task/addsub', params, response_type=ProjectTask)
412
+
413
+ def update_task(self, params: UpdateTaskRequest) -> ProjectTask:
414
+ """更新任务"""
415
+ return self._post_request('/api/project/task/update', params, response_type=ProjectTask)
416
+
417
+ def create_task_dialog(self, params: CreateTaskDialogRequest) -> CreateTaskDialogResponse:
418
+ """创建任务对话"""
419
+ return self._get_request('/api/project/task/dialog', params,
420
+ response_type=CreateTaskDialogResponse)
421
+
422
+ def archive_task(self, task_id: int, archive_type: str = "add") -> None:
423
+ """归档任务"""
424
+ params = TaskActionRequest(task_id=task_id, type=archive_type)
425
+ self._get_request('/api/project/task/archived', params)
426
+
427
+ def delete_task(self, task_id: int, delete_type: str = "delete") -> None:
428
+ """删除任务"""
429
+ params = TaskActionRequest(task_id=task_id, type=delete_type)
430
+ self._get_request('/api/project/task/remove', params)
431
+
432
+ # ------------------------------------------------------------------------------------------
433
+ # 系统设置相关方法
434
+ # ------------------------------------------------------------------------------------------
435
+
436
+ def get_system_settings(self) -> SystemSettings:
437
+ """获取系统设置"""
438
+ return self._get_request('/api/system/setting', response_type=SystemSettings)
439
+
440
+ def get_version(self) -> VersionInfo:
441
+ """获取版本信息"""
442
+ headers = {'version': 'true'}
443
+ return self._get_request('/api/system/version', headers=headers, response_type=VersionInfo)
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: dootask-tools
3
+ Version: 1.0.0
4
+ Summary: DooTask Tools 客户端库
5
+ Home-page: https://github.com/dootask/dootask-tools
6
+ Author: DooTask Team
7
+ Author-email: support@dootask.com
8
+ Project-URL: Bug Reports, https://github.com/dootask/dootask-tools/issues
9
+ Project-URL: Source, https://github.com/dootask/dootask-tools
10
+ Project-URL: Documentation, https://github.com/dootask/dootask-tools#readme
11
+ Keywords: dootask api client chat project management collaboration
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: Communications :: Chat
25
+ Classifier: Topic :: Office/Business :: Groupware
26
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
27
+ Requires-Python: >=3.7
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: requests>=2.28.0
31
+ Requires-Dist: dataclasses>=0.8; python_version < "3.7"
32
+ Dynamic: author
33
+ Dynamic: author-email
34
+ Dynamic: classifier
35
+ Dynamic: description
36
+ Dynamic: description-content-type
37
+ Dynamic: home-page
38
+ Dynamic: keywords
39
+ Dynamic: license-file
40
+ Dynamic: project-url
41
+ Dynamic: requires-dist
42
+ Dynamic: requires-python
43
+ Dynamic: summary
44
+
45
+ # DooTask Tools
46
+
47
+ 一个用于与 DooTask 系统交互的 Python 客户端库,提供了完整的 API 封装和类型支持。
48
+
49
+ ## 安装
50
+
51
+ ```bash
52
+ pip install dootask-tools
53
+ ```
54
+
55
+ ## 快速开始
56
+
57
+ ### 初始化客户端
58
+
59
+ ```python
60
+ from dootask import DooTaskClient
61
+
62
+ # 创建客户端
63
+ client = DooTaskClient(
64
+ token="your_token_here",
65
+ server="https://your-dootask-server.com"
66
+ )
67
+
68
+ # 获取用户信息
69
+ user = client.get_user_info()
70
+ print(f"用户: {user.nickname}")
71
+ ```
72
+
73
+ ### 发送消息示例
74
+
75
+ ```python
76
+ from dootask import SendMessageRequest, SendMessageToUserRequest
77
+
78
+ # 发送消息到指定对话
79
+ client.send_message(SendMessageRequest(
80
+ dialog_id=123,
81
+ text="Hello, World!",
82
+ text_type="md"
83
+ ))
84
+
85
+ # 发送消息到用户
86
+ client.send_message_to_user(SendMessageToUserRequest(
87
+ userid=456,
88
+ text="私信内容",
89
+ text_type="md"
90
+ ))
91
+ ```
92
+
93
+ ### 项目管理示例
94
+
95
+ ```python
96
+ from dootask import GetProjectListRequest, CreateProjectRequest, CreateTaskRequest
97
+
98
+ # 获取项目列表
99
+ projects = client.get_project_list(GetProjectListRequest(
100
+ page=1,
101
+ pagesize=20
102
+ ))
103
+
104
+ # 创建项目
105
+ project = client.create_project(CreateProjectRequest(
106
+ name="新项目",
107
+ desc="项目描述"
108
+ ))
109
+
110
+ # 创建任务
111
+ task = client.create_task(CreateTaskRequest(
112
+ project_id=project.id,
113
+ name="新任务",
114
+ content="任务内容"
115
+ ))
116
+ ```
117
+
118
+ ## API 方法列表
119
+
120
+ ### 客户端配置
121
+
122
+ | 方法 | 描述 | 参数 | 返回值 |
123
+ |------|------|------|--------|
124
+ | `DooTaskClient` | 创建客户端实例 | `token, server, timeout` | `DooTaskClient` |
125
+
126
+ ### 用户相关接口
127
+
128
+ | 方法 | 描述 | 参数 | 返回值 |
129
+ |------|------|------|--------|
130
+ | `get_user_info` | 获取用户信息 | `no_cache=False` | `UserInfo` |
131
+ | `check_user_identity` | 检查用户身份 | `identity` | `UserInfo` |
132
+ | `get_user_departments` | 获取用户部门信息 | - | `List[Department]` |
133
+ | `get_users_basic` | 获取多个用户基础信息 | `userids: List[int]` | `List[UserBasic]` |
134
+ | `get_user_basic` | 获取单个用户基础信息 | `userid: int` | `UserBasic` |
135
+
136
+ ### 消息相关接口
137
+
138
+ | 方法 | 描述 | 参数 | 返回值 |
139
+ |------|------|------|--------|
140
+ | `send_message` | 发送消息 | `SendMessageRequest` | `None` |
141
+ | `send_message_to_user` | 发送消息到用户 | `SendMessageToUserRequest` | `None` |
142
+ | `send_bot_message` | 发送机器人消息 | `SendBotMessageRequest` | `None` |
143
+ | `send_anonymous_message` | 发送匿名消息 | `SendAnonymousMessageRequest` | `None` |
144
+
145
+ ### 对话相关接口
146
+
147
+ | 方法 | 描述 | 参数 | 返回值 |
148
+ |------|------|------|--------|
149
+ | `get_dialog_list` | 获取对话列表 | `TimeRangeRequest` | `ResponsePaginate[DialogInfo]` |
150
+ | `search_dialog` | 搜索会话 | `SearchDialogRequest` | `List[DialogInfo]` |
151
+ | `get_dialog_one` | 获取单个会话信息 | `GetDialogRequest` | `DialogInfo` |
152
+ | `get_dialog_user` | 获取会话成员 | `GetDialogUserRequest` | `List[DialogMember]` |
153
+
154
+ ### 群组相关接口
155
+
156
+ | 方法 | 描述 | 参数 | 返回值 |
157
+ |------|------|------|--------|
158
+ | `create_group` | 创建群组 | `CreateGroupRequest` | `DialogInfo` |
159
+ | `edit_group` | 修改群组 | `EditGroupRequest` | `None` |
160
+ | `add_group_user` | 添加群成员 | `AddGroupUserRequest` | `None` |
161
+ | `remove_group_user` | 移除群成员 | `RemoveGroupUserRequest` | `None` |
162
+ | `exit_group` | 退出群组 | `dialog_id: int` | `None` |
163
+ | `transfer_group` | 转让群组 | `TransferGroupRequest` | `None` |
164
+ | `disband_group` | 解散群组 | `DisbandGroupRequest` | `None` |
165
+
166
+ ### 项目管理相关接口
167
+
168
+ | 方法 | 描述 | 参数 | 返回值 |
169
+ |------|------|------|--------|
170
+ | `get_project_list` | 获取项目列表 | `GetProjectListRequest` | `ResponsePaginate[Project]` |
171
+ | `get_project` | 获取项目信息 | `GetProjectRequest` | `Project` |
172
+ | `create_project` | 创建项目 | `CreateProjectRequest` | `Project` |
173
+ | `update_project` | 更新项目 | `UpdateProjectRequest` | `Project` |
174
+ | `exit_project` | 退出项目 | `project_id: int` | `None` |
175
+ | `delete_project` | 删除项目 | `project_id: int` | `None` |
176
+
177
+ ### 任务列表相关接口
178
+
179
+ | 方法 | 描述 | 参数 | 返回值 |
180
+ |------|------|------|--------|
181
+ | `get_column_list` | 获取任务列表 | `GetColumnListRequest` | `ResponsePaginate[ProjectColumn]` |
182
+ | `create_column` | 创建任务列表 | `CreateColumnRequest` | `ProjectColumn` |
183
+ | `update_column` | 更新任务列表 | `UpdateColumnRequest` | `ProjectColumn` |
184
+ | `delete_column` | 删除任务列表 | `column_id: int` | `None` |
185
+
186
+ ### 任务相关接口
187
+
188
+ | 方法 | 描述 | 参数 | 返回值 |
189
+ |------|------|------|--------|
190
+ | `get_task_list` | 获取任务列表 | `GetTaskListRequest` | `ResponsePaginate[ProjectTask]` |
191
+ | `get_task` | 获取任务信息 | `GetTaskRequest` | `ProjectTask` |
192
+ | `get_task_content` | 获取任务内容 | `GetTaskContentRequest` | `TaskContent` |
193
+ | `get_task_files` | 获取任务文件列表 | `GetTaskFilesRequest` | `List[TaskFile]` |
194
+ | `create_task` | 创建任务 | `CreateTaskRequest` | `ProjectTask` |
195
+ | `create_sub_task` | 创建子任务 | `CreateSubTaskRequest` | `ProjectTask` |
196
+ | `update_task` | 更新任务 | `UpdateTaskRequest` | `ProjectTask` |
197
+ | `create_task_dialog` | 创建任务对话 | `CreateTaskDialogRequest` | `CreateTaskDialogResponse` |
198
+ | `archive_task` | 归档任务 | `task_id: int, archive_type: str` | `None` |
199
+ | `delete_task` | 删除任务 | `task_id: int, delete_type: str` | `None` |
200
+
201
+ ### 系统设置相关接口
202
+
203
+ | 方法 | 描述 | 参数 | 返回值 |
204
+ |------|------|------|--------|
205
+ | `get_system_settings` | 获取系统设置 | - | `SystemSettings` |
206
+ | `get_version` | 获取版本信息 | - | `VersionInfo` |
207
+
208
+ ## 主要数据类型
209
+
210
+ - `UserInfo` - 用户信息
211
+ - `UserBasic` - 用户基础信息
212
+ - `Department` - 部门信息
213
+ - `DialogInfo` - 对话信息
214
+ - `DialogMember` - 对话成员
215
+ - `Project` - 项目信息
216
+ - `ProjectColumn` - 项目列表
217
+ - `ProjectTask` - 项目任务
218
+ - `TaskFile` - 任务文件
219
+ - `TaskContent` - 任务内容
220
+ - `SystemSettings` - 系统设置
221
+ - `VersionInfo` - 版本信息
222
+
223
+ ## 详细示例
224
+
225
+ ```python
226
+ from dootask import (
227
+ DooTaskClient,
228
+ CreateProjectRequest,
229
+ CreateTaskRequest,
230
+ DooTaskException
231
+ )
232
+
233
+ client = DooTaskClient(
234
+ token="your_token",
235
+ server="https://your-server.com"
236
+ )
237
+
238
+ try:
239
+ # 创建项目
240
+ project = client.create_project(CreateProjectRequest(
241
+ name="我的项目",
242
+ desc="项目描述"
243
+ ))
244
+
245
+ # 创建任务
246
+ task = client.create_task(CreateTaskRequest(
247
+ project_id=project.id,
248
+ name="任务名称",
249
+ content="任务内容",
250
+ owner=[user.userid]
251
+ ))
252
+
253
+ print(f"项目创建成功: {project.name}")
254
+ print(f"任务创建成功: {task.name}")
255
+
256
+ except DooTaskException as e:
257
+ print(f"错误: {e}")
258
+ ```
259
+
260
+ ## 异常处理
261
+
262
+ 所有方法都可能抛出异常,包含详细的错误信息:
263
+
264
+ ```python
265
+ from dootask import (
266
+ DooTaskException,
267
+ DooTaskAPIException,
268
+ DooTaskHTTPException,
269
+ DooTaskAuthException,
270
+ DooTaskPermissionException
271
+ )
272
+
273
+ try:
274
+ user = client.get_user_info()
275
+ except DooTaskAuthException:
276
+ print("认证失败,请检查 token")
277
+ except DooTaskPermissionException:
278
+ print("权限不足")
279
+ except DooTaskAPIException as e:
280
+ print(f"API 错误: {e}")
281
+ except DooTaskHTTPException as e:
282
+ print(f"网络错误: {e}")
283
+ except DooTaskException as e:
284
+ print(f"其他错误: {e}")
285
+ ```
286
+
287
+ ## 许可证
288
+
289
+ MIT License
@@ -0,0 +1,8 @@
1
+ client.py,sha256=5gN0cJDuy1MN42iL4OYfyAyWKVN6M_DUreFVgeliDYY,18393
2
+ exceptions.py,sha256=xsyQ_x13Tz-NNV9g3vHRbR4PHO_Fxgb-a7Ci522e4uA,703
3
+ models.py,sha256=V37I0Qt0RKugydiax7a0zmmGGF1nMpdgwlvkdvHK84M,11057
4
+ dootask_tools-1.0.0.dist-info/licenses/LICENSE,sha256=XZTR4JqVYnbWD7E7Z8ID68qgYx_reZd9dpU5MameQOM,1069
5
+ dootask_tools-1.0.0.dist-info/METADATA,sha256=KvUAuLqE7FPHqjCeYage97am-ABRKLVyY9rcLpdZLLo,9137
6
+ dootask_tools-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ dootask_tools-1.0.0.dist-info/top_level.txt,sha256=6Tc1kkb0pPCjirIzy28Nxe70crS6gyxjtP1pvmjaQYc,25
8
+ dootask_tools-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 DooTask Team
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,3 @@
1
+ client
2
+ exceptions
3
+ models
exceptions.py ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ DooTask Tools 异常定义
3
+ """
4
+
5
+ class DooTaskException(Exception):
6
+ """DooTask 基础异常类"""
7
+ pass
8
+
9
+ class DooTaskAPIException(DooTaskException):
10
+ """DooTask API 异常"""
11
+ def __init__(self, message: str, ret_code: int = 0):
12
+ super().__init__(message)
13
+ self.ret_code = ret_code
14
+
15
+ class DooTaskHTTPException(DooTaskException):
16
+ """DooTask HTTP 异常"""
17
+ def __init__(self, message: str, status_code: int = 0):
18
+ super().__init__(message)
19
+ self.status_code = status_code
20
+
21
+ class DooTaskAuthException(DooTaskException):
22
+ """DooTask 认证异常"""
23
+ pass
24
+
25
+ class DooTaskPermissionException(DooTaskException):
26
+ """DooTask 权限异常"""
27
+ pass
models.py ADDED
@@ -0,0 +1,487 @@
1
+ """
2
+ DooTask Tools 数据类型定义
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional, List, Dict, Any, Union, Generic, TypeVar
7
+ from datetime import datetime
8
+
9
+ T = TypeVar('T')
10
+
11
+ # ------------------------------------------------------------------------------------------
12
+ # 基础响应结构
13
+ # ------------------------------------------------------------------------------------------
14
+
15
+ @dataclass
16
+ class Response(Generic[T]):
17
+ """基础响应结构"""
18
+ ret: int
19
+ msg: str
20
+ data: Optional[T] = None
21
+
22
+ @dataclass
23
+ class ResponsePaginate(Generic[T]):
24
+ """分页数据"""
25
+ current_page: int
26
+ data: List[T]
27
+ next_page_url: Optional[str] = None
28
+ path: str = ""
29
+ per_page: Union[int, str] = 0
30
+ prev_page_url: Optional[str] = None
31
+ to: Union[int, str] = 0
32
+ total: int = 0
33
+
34
+ # ------------------------------------------------------------------------------------------
35
+ # 用户相关结构
36
+ # ------------------------------------------------------------------------------------------
37
+
38
+ @dataclass
39
+ class UserInfo:
40
+ """用户信息"""
41
+ userid: int
42
+ identity: List[str] = field(default_factory=list)
43
+ email: str = ""
44
+ nickname: str = ""
45
+ profession: str = ""
46
+ userimg: str = ""
47
+ bot: int = 0
48
+ department: List[int] = field(default_factory=list)
49
+ department_name: str = ""
50
+
51
+ @dataclass
52
+ class UserBasic:
53
+ """用户基础信息"""
54
+ userid: int
55
+ email: str = ""
56
+ nickname: str = ""
57
+ profession: str = ""
58
+ userimg: str = ""
59
+ bot: int = 0
60
+ online: bool = False
61
+ department: List[int] = field(default_factory=list)
62
+ department_name: str = ""
63
+
64
+ @dataclass
65
+ class Department:
66
+ """部门信息"""
67
+ id: int
68
+ name: str
69
+ parent_id: int = 0
70
+ owner_userid: int = 0
71
+
72
+ # ------------------------------------------------------------------------------------------
73
+ # 对话相关结构
74
+ # ------------------------------------------------------------------------------------------
75
+
76
+ @dataclass
77
+ class DialogUserResponse:
78
+ """对话用户信息"""
79
+ dialog_id: int
80
+ userid: int
81
+ bot: int = 0
82
+
83
+ @dataclass
84
+ class DialogOpenUserResponse:
85
+ """打开用户对话响应数据"""
86
+ dialog_user: DialogUserResponse
87
+
88
+ @dataclass
89
+ class DialogInfo:
90
+ """对话信息"""
91
+ id: int
92
+ type: str = ""
93
+ group_type: str = ""
94
+ name: str = ""
95
+ avatar: str = ""
96
+ owner_id: int = 0
97
+ created_at: str = ""
98
+ updated_at: str = ""
99
+ last_at: str = ""
100
+ mark_unread: int = 0
101
+ silence: int = 0
102
+ hide: int = 0
103
+ color: str = ""
104
+ unread: int = 0
105
+ unread_one: int = 0
106
+ mention: int = 0
107
+ mention_ids: List[int] = field(default_factory=list)
108
+ people: int = 0
109
+ people_user: int = 0
110
+ people_bot: int = 0
111
+ todo_num: int = 0
112
+ last_msg: Any = None
113
+ pinyin: str = ""
114
+ bot: int = 0
115
+ top_at: str = ""
116
+
117
+ @dataclass
118
+ class DialogMember:
119
+ """会话成员信息"""
120
+ id: int
121
+ dialog_id: int
122
+ userid: int
123
+ nickname: str = ""
124
+ email: str = ""
125
+ userimg: str = ""
126
+ bot: int = 0
127
+ online: bool = False
128
+
129
+ # ------------------------------------------------------------------------------------------
130
+ # 消息相关结构
131
+ # ------------------------------------------------------------------------------------------
132
+
133
+ @dataclass
134
+ class SendMessageRequest:
135
+ """发送消息请求"""
136
+ dialog_id: int
137
+ text: str
138
+ text_type: str = "md"
139
+ silence: bool = False
140
+
141
+ @dataclass
142
+ class SendMessageToUserRequest:
143
+ """发送消息到用户请求"""
144
+ userid: int
145
+ text: str
146
+ text_type: str = "md"
147
+ silence: bool = False
148
+
149
+ @dataclass
150
+ class SendBotMessageRequest:
151
+ """发送机器人消息请求"""
152
+ userid: int
153
+ text: str
154
+ bot_type: str = "system-msg"
155
+ bot_name: str = ""
156
+ silence: bool = False
157
+
158
+ @dataclass
159
+ class SendAnonymousMessageRequest:
160
+ """发送匿名消息请求"""
161
+ userid: int
162
+ text: str
163
+
164
+ # ------------------------------------------------------------------------------------------
165
+ # 群组相关结构
166
+ # ------------------------------------------------------------------------------------------
167
+
168
+ @dataclass
169
+ class CreateGroupRequest:
170
+ """创建群组请求"""
171
+ userids: List[int]
172
+ avatar: str = ""
173
+ chat_name: str = ""
174
+
175
+ @dataclass
176
+ class EditGroupRequest:
177
+ """修改群组请求"""
178
+ dialog_id: int
179
+ avatar: str = ""
180
+ chat_name: str = ""
181
+ admin: int = 0
182
+
183
+ @dataclass
184
+ class AddGroupUserRequest:
185
+ """添加群成员请求"""
186
+ dialog_id: int
187
+ userids: List[int]
188
+
189
+ @dataclass
190
+ class RemoveGroupUserRequest:
191
+ """移除群成员请求"""
192
+ dialog_id: int
193
+ userids: List[int] = field(default_factory=list)
194
+
195
+ @dataclass
196
+ class TransferGroupRequest:
197
+ """转让群组请求"""
198
+ dialog_id: int
199
+ userid: int
200
+ check_owner: str = ""
201
+ key: str = ""
202
+
203
+ @dataclass
204
+ class DisbandGroupRequest:
205
+ """解散群组请求"""
206
+ dialog_id: int
207
+
208
+ # ------------------------------------------------------------------------------------------
209
+ # 通用请求参数
210
+ # ------------------------------------------------------------------------------------------
211
+
212
+ @dataclass
213
+ class TimeRangeRequest:
214
+ """时间范围请求参数"""
215
+ timerange: str = ""
216
+ page: int = 1
217
+ pagesize: int = 50
218
+
219
+ @dataclass
220
+ class SearchDialogRequest:
221
+ """搜索会话请求"""
222
+ key: str
223
+
224
+ @dataclass
225
+ class GetDialogRequest:
226
+ """获取单个会话请求"""
227
+ dialog_id: int
228
+
229
+ @dataclass
230
+ class GetDialogUserRequest:
231
+ """获取会话成员请求"""
232
+ dialog_id: int
233
+ getuser: int = 0
234
+
235
+ # ------------------------------------------------------------------------------------------
236
+ # 项目管理相关结构
237
+ # ------------------------------------------------------------------------------------------
238
+
239
+ @dataclass
240
+ class Project:
241
+ """项目信息"""
242
+ id: int
243
+ name: str = ""
244
+ desc: str = ""
245
+ userid: int = 0
246
+ dialog_id: int = 0
247
+ archived_at: str = ""
248
+ created_at: str = ""
249
+ updated_at: str = ""
250
+ owner: int = 0
251
+ owner_userid: int = 0
252
+ personal: int = 0
253
+ # 任务统计
254
+ task_num: int = 0
255
+ task_complete: int = 0
256
+ task_percent: int = 0
257
+ task_my_num: int = 0
258
+ task_my_complete: int = 0
259
+ task_my_percent: int = 0
260
+
261
+ @dataclass
262
+ class ProjectColumn:
263
+ """项目列表"""
264
+ id: int
265
+ project_id: int
266
+ name: str = ""
267
+ color: str = ""
268
+ sort: int = 0
269
+ created_at: str = ""
270
+ updated_at: str = ""
271
+
272
+ @dataclass
273
+ class ProjectTask:
274
+ """项目任务"""
275
+ id: int
276
+ project_id: int = 0
277
+ column_id: int = 0
278
+ parent_id: int = 0
279
+ name: str = ""
280
+ desc: str = ""
281
+ start_at: str = ""
282
+ end_at: str = ""
283
+ complete_at: str = ""
284
+ archived_at: str = ""
285
+ created_at: str = ""
286
+ updated_at: str = ""
287
+ userid: int = 0
288
+ dialog_id: int = 0
289
+ flow_item_id: int = 0
290
+ flow_item_name: str = ""
291
+ visibility: int = 0
292
+ color: str = ""
293
+ # 统计信息
294
+ file_num: int = 0
295
+ msg_num: int = 0
296
+ sub_num: int = 0
297
+ sub_complete: int = 0
298
+ percent: int = 0
299
+ # 关联数据
300
+ project_name: str = ""
301
+ column_name: str = ""
302
+
303
+ @dataclass
304
+ class TaskFile:
305
+ """任务文件"""
306
+ id: int
307
+ task_id: int
308
+ name: str = ""
309
+ ext: str = ""
310
+ size: int = 0
311
+ path: str = ""
312
+ thumb: str = ""
313
+ userid: int = 0
314
+ created_at: str = ""
315
+ updated_at: str = ""
316
+
317
+ @dataclass
318
+ class TaskContent:
319
+ """任务内容"""
320
+ content: str = ""
321
+ type: str = ""
322
+
323
+ # ------------------------------------------------------------------------------------------
324
+ # 项目管理请求参数
325
+ # ------------------------------------------------------------------------------------------
326
+
327
+ @dataclass
328
+ class GetProjectListRequest:
329
+ """获取项目列表请求"""
330
+ type: str = "all"
331
+ archived: str = "no"
332
+ getcolumn: str = "no"
333
+ getuserid: str = "no"
334
+ getstatistics: str = "no"
335
+ timerange: str = ""
336
+ page: int = 1
337
+ pagesize: int = 50
338
+
339
+ @dataclass
340
+ class GetProjectRequest:
341
+ """获取项目信息请求"""
342
+ project_id: int
343
+
344
+ @dataclass
345
+ class CreateProjectRequest:
346
+ """创建项目请求"""
347
+ name: str
348
+ desc: str = ""
349
+ columns: str = ""
350
+ flow: str = ""
351
+ personal: int = 0
352
+
353
+ @dataclass
354
+ class UpdateProjectRequest:
355
+ """更新项目请求"""
356
+ project_id: int
357
+ name: str
358
+ desc: str = ""
359
+ archive_method: str = ""
360
+ archive_days: int = 0
361
+
362
+ @dataclass
363
+ class ProjectActionRequest:
364
+ """项目操作请求"""
365
+ project_id: int
366
+ type: str = ""
367
+
368
+ @dataclass
369
+ class GetColumnListRequest:
370
+ """获取列表请求"""
371
+ project_id: int
372
+ page: int = 1
373
+ pagesize: int = 100
374
+
375
+ @dataclass
376
+ class CreateColumnRequest:
377
+ """创建列表请求"""
378
+ project_id: int
379
+ name: str
380
+
381
+ @dataclass
382
+ class UpdateColumnRequest:
383
+ """更新列表请求"""
384
+ column_id: int
385
+ name: str = ""
386
+ color: str = ""
387
+
388
+ @dataclass
389
+ class ColumnActionRequest:
390
+ """列表操作请求"""
391
+ column_id: int
392
+
393
+ @dataclass
394
+ class GetTaskListRequest:
395
+ """获取任务列表请求"""
396
+ project_id: int = 0
397
+ parent_id: int = 0
398
+ archived: str = "no"
399
+ deleted: str = "no"
400
+ timerange: str = ""
401
+ page: int = 1
402
+ pagesize: int = 100
403
+
404
+ @dataclass
405
+ class GetTaskRequest:
406
+ """获取任务信息请求"""
407
+ task_id: int
408
+ archived: str = "no"
409
+
410
+ @dataclass
411
+ class GetTaskContentRequest:
412
+ """获取任务内容请求"""
413
+ task_id: int
414
+ history_id: int = 0
415
+
416
+ @dataclass
417
+ class GetTaskFilesRequest:
418
+ """获取任务文件请求"""
419
+ task_id: int
420
+
421
+ @dataclass
422
+ class CreateTaskRequest:
423
+ """创建任务请求"""
424
+ project_id: int
425
+ name: str
426
+ column_id: Union[int, str] = 0
427
+ content: str = ""
428
+ times: List[str] = field(default_factory=list)
429
+ owner: List[int] = field(default_factory=list)
430
+ top: int = 0
431
+
432
+ @dataclass
433
+ class CreateSubTaskRequest:
434
+ """创建子任务请求"""
435
+ task_id: int
436
+ name: str
437
+
438
+ @dataclass
439
+ class UpdateTaskRequest:
440
+ """更新任务请求"""
441
+ task_id: int
442
+ name: str = ""
443
+ content: str = ""
444
+ times: List[str] = field(default_factory=list)
445
+ owner: List[int] = field(default_factory=list)
446
+ assist: List[int] = field(default_factory=list)
447
+ color: str = ""
448
+ visibility: int = 0
449
+ complete_at: Any = None
450
+
451
+ @dataclass
452
+ class TaskActionRequest:
453
+ """任务操作请求"""
454
+ task_id: int
455
+ type: str = ""
456
+
457
+ @dataclass
458
+ class CreateTaskDialogRequest:
459
+ """创建任务对话请求"""
460
+ task_id: int
461
+
462
+ @dataclass
463
+ class CreateTaskDialogResponse:
464
+ """创建任务对话响应"""
465
+ id: int
466
+ dialog_id: int
467
+ dialog_data: Any = None
468
+
469
+ # ------------------------------------------------------------------------------------------
470
+ # 系统设置相关
471
+ # ------------------------------------------------------------------------------------------
472
+
473
+ @dataclass
474
+ class SystemSettings:
475
+ """系统设置"""
476
+ reg: Optional[str] = None
477
+ task_default_time: Optional[List[str]] = None
478
+ system_alias: Optional[str] = None
479
+ system_welcome: str = ""
480
+ server_timezone: Optional[str] = None
481
+ server_version: Optional[str] = None
482
+
483
+ @dataclass
484
+ class VersionInfo:
485
+ """版本信息"""
486
+ device_count: int = 0
487
+ version: str = ""