Metadata-Version: 2.4
Name: wecomkit
Version: 0.2.0
Summary: Async WeCom (Enterprise WeChat) SDK built with httpx.
Author: KevinFan1
License-Expression: MIT
Project-URL: Homepage, https://github.com/KevinFan1/wecomkit
Project-URL: Repository, https://github.com/KevinFan1/wecomkit.git
Project-URL: Issues, https://github.com/KevinFan1/wecomkit/issues
Keywords: wecom,enterprise-wechat,async,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: redis
Requires-Dist: redis>=5.0.0; extra == "redis"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: black>=23.0.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Dynamic: license-file

# wecomkit 企业微信 SDK

[English](./README_en.md) | **中文**

`wecomkit` 是一个基于 `asyncio + httpx` 的企业微信异步 SDK。当前版本覆盖常用服务端 API 场景，包括通讯录、应用消息、素材、打卡、日程、审批、企业邮箱、人事助手、微盘和 WeDoc 的部分接口。

> 当前 SDK 不是企业微信全量 API 封装。接口状态以 `src/wecomkit/services/` 中实际实现为准。

## 安装

从 PyPI 安装：

```bash
uv pip install wecomkit
# 或
pip install wecomkit
```

如果当前项目由 `uv` 管理，也可以直接添加依赖：

```bash
uv add wecomkit
```

需要 Redis token 缓存时安装可选依赖：

```bash
uv pip install "wecomkit[redis]"
# 或
uv add "wecomkit[redis]"
```

从源码开发安装：

```bash
git clone https://github.com/KevinFan1/wecomkit.git
cd wecomkit
uv pip install -e ".[dev]"
```

构建本地 wheel：

```bash
uv build
```

构建产物会输出到 `dist/`，可用于本地验证安装：

```bash
uv pip install ./dist/wecomkit-*.whl
# 或
pip install ./dist/wecomkit-*.whl
```

## 快速开始

```python
import asyncio

from wecomkit import AsyncWeComClient, JSONFileTokenCache, WeComConfig


async def main() -> None:
    config = WeComConfig(
        corp_id="wwxxxxxx",
        corp_secret="your-secret",
    )

    async with AsyncWeComClient(
        config,
        token_cache=JSONFileTokenCache(".cache/wecom-token.json"),
    ) as client:
        token = await client.auth.get_access_token()
        departments = await client.departments.list()
        users = await client.users.simple_list_by_department(1, fetch_child=True)

        print(token)
        print(departments)
        print(users)


asyncio.run(main())
```

## Examples

示例代码位于 [examples](./examples)：

- [examples/quickstart.py](./examples/quickstart.py)：初始化客户端、获取 token、读取部门和成员
- [examples/contacts.py](./examples/contacts.py)：成员、部门、标签常用操作
- [examples/messages_and_media.py](./examples/messages_and_media.py)：上传素材并发送应用消息
- [examples/mail.py](./examples/mail.py)：企业邮箱发送、未读数和邮件列表
- [examples/hr.py](./examples/hr.py)：人事助手字段配置和员工花名册
- [examples/robot.py](./examples/robot.py)：群机器人 webhook 消息
- [examples/wedoc_smartsheet.py](./examples/wedoc_smartsheet.py)：WeDoc 文档和智能表格
- [examples/approval.py](./examples/approval.py)：审批查询与假期余额
- [examples/checkin.py](./examples/checkin.py)：打卡记录、排班、规则和日报
- [examples/wedrive.py](./examples/wedrive.py)：微盘空间、文件列表、下载链接
- [examples/calendar_schedule.py](./examples/calendar_schedule.py)：日历与日程查询

运行示例前设置环境变量：

```bash
export WECOM_CORP_ID="wwxxxxxx"
export WECOM_CORP_SECRET="your-secret"
export WECOM_AGENT_ID="1000001"
```

```bash
uv run python examples/quickstart.py
```

## 客户端结构

```python
client.auth          # 授权
client.users         # 成员
client.departments   # 部门
client.tags          # 标签
client.agent         # 应用
client.menu          # 应用菜单
client.messages      # 应用消息
client.media         # 素材
client.mail          # 企业邮箱
client.robot         # 群机器人 webhook
client.hr            # 人事助手
client.checkin       # 打卡
client.approval      # 审批
client.calendar      # 日历
client.schedule      # 日程
client.wedrive       # 微盘
client.wedoc         # WeDoc 聚合服务
```

WeDoc 子服务：

```python
client.wedoc.documents     # 文档
client.wedoc.smartsheets  # 智能表格
client.wedoc.permissions   # 权限
client.wedoc.forms         # 收集表通用调用
client.wedoc.materials     # 文档素材
```


## 接口覆盖清单

> 图例：✅ = 已实现；⬜ = 未实现。SDK 方法按 `client.<module>.<method>(...)` 调用，端点与[企业微信官方文档](https://developer.work.weixin.qq.com/document/path/90664)对应。

### 基础 / 授权

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/gettoken` | `auth.get_access_token()` | ✅ |
| `/cgi-bin/auth/getuserinfo` | `auth.get_user_info_by_code(code)` | ✅ |
| `/cgi-bin/auth/getuserdetail` | `auth.get_user_detail(user_ticket)` | ✅ |
| `/cgi-bin/auth/get_tfa_info` | `auth.get_tfa_info(code)` | ✅ |
| `/cgi-bin/get_jsapi_ticket` | — | ⬜ |
| `/cgi-bin/getcallbackip` | — | ⬜ |

### 通讯录 · 成员

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/user/create` | `users.create(...)` | ✅ |
| `/cgi-bin/user/get` | `users.get(userid)` | ✅ |
| `/cgi-bin/user/update` | `users.update(userid, ...)` | ✅ |
| `/cgi-bin/user/delete` | `users.delete(userid)` | ✅ |
| `/cgi-bin/user/batchdelete` | `users.batch_delete(userids)` | ✅ |
| `/cgi-bin/user/simplelist` | `users.simple_list_by_department(...)` | ✅ |
| `/cgi-bin/user/list` | `users.list_by_department(...)` | ✅ |
| `/cgi-bin/user/list_id` | `users.list_id(...)` | ✅ |
| `/cgi-bin/user/get_join_qrcode` | `users.get_join_qrcode()` | ✅ |
| `/cgi-bin/invite/send` | `users.invite(...)` | ✅ |
| `/cgi-bin/user/convert_to_openid` | — | ⬜ |
| `/cgi-bin/user/convert_to_userid` | — | ⬜ |
| `/cgi-bin/user/export`（批量导出成员） | — | ⬜ |

### 通讯录 · 部门

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/department/create` | `departments.create(...)` | ✅ |
| `/cgi-bin/department/update` | `departments.update(...)` | ✅ |
| `/cgi-bin/department/delete` | `departments.delete(department_id)` | ✅ |
| `/cgi-bin/department/list` | `departments.list()` | ✅ |
| `/cgi-bin/department/simplelist` | `departments.simple_list()` | ✅ |
| `/cgi-bin/department/get` | `departments.get(department_id)` | ✅ |
| `/cgi-bin/department/export`（批量导出部门） | — | ⬜ |

### 通讯录 · 标签

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/tag/create` | `tags.create(tagname)` | ✅ |
| `/cgi-bin/tag/update` | `tags.update(tagid, tagname)` | ✅ |
| `/cgi-bin/tag/delete` | `tags.delete(tagid)` | ✅ |
| `/cgi-bin/tag/get` | `tags.get(tagid)` | ✅ |
| `/cgi-bin/tag/addtagusers` | `tags.add_users(...)` | ✅ |
| `/cgi-bin/tag/deltagusers` | `tags.delete_users(...)` | ✅ |
| `/cgi-bin/tag/list` | `tags.list()` | ✅ |
| `/cgi-bin/tag/export`（批量导出标签成员） | — | ⬜ |

### 应用与菜单

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/agent/get` | `agent.get(agentid)` | ✅ |
| `/cgi-bin/agent/list` | `agent.list()` | ✅ |
| `/cgi-bin/agent/set` | `agent.set(agentid, ...)` | ✅ |
| `/cgi-bin/menu/create` | `menu.create(agentid, button)` | ✅ |
| `/cgi-bin/menu/get` | `menu.get(agentid)` | ✅ |
| `/cgi-bin/menu/delete` | `menu.delete(agentid)` | ✅ |
| `/cgi-bin/agent/set_workbench_template` | — | ⬜ |
| `/cgi-bin/agent/set_workbench_data` | — | ⬜ |

### 消息推送

| 消息类型 / 接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/message/send`（通用） | `messages.send(msg_type, agent_id, content, ...)` | ✅ |
| `text` | `messages.send_text(agent_id, content, ...)` | ✅ |
| `markdown` | `messages.send_markdown(...)` | ✅ |
| `image` | `messages.send_image(...)` | ✅ |
| `voice` | `messages.send_voice(...)` | ✅ |
| `video` | `messages.send_video(...)` | ✅ |
| `file` | `messages.send_file(...)` | ✅ |
| `textcard` | `messages.send_textcard(...)` | ✅ |
| `news` | `messages.send_news(...)` | ✅ |
| `mpnews` | `messages.send_mpnews(...)` | ✅ |
| `miniprogram_notice` | `messages.send_miniprogram_notice(...)` | ✅ |
| `template_card` | `messages.send_template_card(...)` | ✅ |
| `/cgi-bin/message/recall` | — | ⬜ |
| `/cgi-bin/message/update_template_card` | `messages.update_template_card(...)` | ✅ |
| 群机器人 webhook：text / markdown / image / news / file / textcard / template_card | `robot.send(webhook_url, msg_type, content)` 及便捷方法 | ✅ |
| 群机器人 webhook：interactive 消息 | — | ⬜ |

### 素材管理

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/media/upload` | `media.upload(file_path, media_type)`、`media.upload_bytes(...)` | ✅ |
| `/cgi-bin/media/get` | `media.get(media_id)`、`media.download(media_id, path)` | ✅ |
| `/cgi-bin/media/uploadimg` | `media.upload_image(file_path)` | ✅ |
| `/cgi-bin/media/upload_attachment` | `media.upload_attachment(...)`、`media.upload_jssdk_image(...)` | ✅ |
| `/cgi-bin/media/get/jssdk`（获取高清语音素材） | `media.get_jssdk_voice(media_id)` | ✅ |
| `/cgi-bin/media/upload_by_url`（异步上传临时素材） | `media.upload_by_url(...)` | ✅ |

### OA · 打卡

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/checkin/getcheckindata` | `checkin.get_checkin_data(...)` | ✅ |
| `/cgi-bin/checkin/getcheckinschedulist` | `checkin.get_checkin_schedules(...)` | ✅ |
| `/cgi-bin/checkin/setcheckinschedulist` | `checkin.set_checkin_schedules(...)` | ✅ |
| `/cgi-bin/checkin/getcheckinoption` | `checkin.get_checkin_option(...)` | ✅ |
| `/cgi-bin/checkin/addcheckinuserface` | `checkin.add_checkin_user_face(...)` | ✅ |
| `/cgi-bin/checkin/getcheckin_daydata` | `checkin.get_checkin_daydata(...)` | ✅ |
| `/cgi-bin/checkin/getcheckin_monthdata` | `checkin.get_checkin_monthdata(...)` | ✅ |
| `/cgi-bin/checkin/getcorpcheckinoption` | `checkin.get_checkin_all_options()` | ✅ |
| `/cgi-bin/checkin/getusercheckinintime_rang` | `checkin.get_checkin_user_options(...)` | ✅ |
| `/cgi-bin/checkin/punch_correction` | `checkin.punch_correction(...)` | ✅ |
| `/cgi-bin/checkin/add_checkin_option`（创建打卡规则） | `checkin.add_checkin_option(...)` | ✅ |
| `/cgi-bin/checkin/add_checkin_record`（添加打卡记录） | `checkin.add_checkin_record(...)` | ✅ |

### OA · 审批

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/oa/gettemplatedetail` | `approval.get_template(template_id)` | ✅ |
| `/cgi-bin/oa/applyevent` | `approval.apply(...)` | ✅ |
| `/cgi-bin/oa/getapprovaldetail` | `approval.get_approve(sp_no)` | ✅ |
| `/cgi-bin/oa/getapprovalinfo` | `approval.get_approve_list(...)` | ✅ |
| `/cgi-bin/oa/vacation/getcorpconf` | `approval.get_vacation_config()` | ✅ |
| `/cgi-bin/oa/vacation/getuservacationquota` | `approval.get_user_vacation(userid)` | ✅ |
| `/cgi-bin/oa/vacation/setoneuserquota`（修改成员假期余额） | `approval.set_user_vacation(...)` | ✅ |
| `/cgi-bin/oa/approval/create_template`（新版创建审批模板） | `approval.create_template(...)` | ✅ |
| `/cgi-bin/oa/approval/update_template`（新版更新审批模板） | `approval.update_template(...)` | ✅ |

### OA · 日历

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/oa/calendar/add` | `calendar.create(...)` | ✅ |
| `/cgi-bin/oa/calendar/get` | `calendar.get(cal_id_list)` | ✅ |
| `/cgi-bin/oa/calendar/update` | `calendar.update(cal_id, ...)` | ✅ |
| `/cgi-bin/oa/calendar/del` | `calendar.delete(cal_id)` | ✅ |

### OA · 日程

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/oa/schedule/add` | `schedule.create(...)` | ✅ |
| `/cgi-bin/oa/schedule/get` | `schedule.get(schedule_id_list)` | ✅ |
| `/cgi-bin/oa/schedule/get_by_calendar` | `schedule.list_by_calendar(...)` | ✅ |
| `/cgi-bin/oa/schedule/update` | `schedule.update(schedule_id, ...)` | ✅ |
| `/cgi-bin/oa/schedule/del` | `schedule.cancel(schedule_id)` | ✅ |
| `/cgi-bin/oa/schedule/add_attendees` | `schedule.add_attendees(...)` | ✅ |
| `/cgi-bin/oa/schedule/del_attendees` | `schedule.del_attendees(...)` | ✅ |

### OA · 文档（WeDoc）

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/wedoc/create_doc` | `wedoc.documents.create(...)` | ✅ |
| `/cgi-bin/wedoc/rename_doc` | `wedoc.documents.rename(...)` | ✅ |
| `/cgi-bin/wedoc/del_doc` | `wedoc.documents.delete(docid)` | ✅ |
| `/cgi-bin/wedoc/get_doc_base_info` | `wedoc.documents.get_base_info(docid)` | ✅ |
| `/cgi-bin/wedoc/doc_share` | `wedoc.documents.share(...)` | ✅ |
| `/cgi-bin/wedoc/document/get` | `wedoc.documents.get_content(docid)` | ✅ |
| `/cgi-bin/wedoc/document/batch_update` | `wedoc.documents.edit_content(...)` | ✅ |
| `/cgi-bin/wedoc/image_upload` | `wedoc.materials.upload_image(...)` | ✅ |

### OA · 智能表格

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/wedoc/smartsheet/add_records` | `smartsheets.add_records(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/delete_records` | `smartsheets.delete_records(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/update_records` | `smartsheets.update_records(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/get_records` | `smartsheets.get_records(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/add_sheet` | `smartsheets.add_sheet(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/delete_sheet` | `smartsheets.delete_sheet(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/update_sheet` | `smartsheets.update_sheet(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/get_sheet` | `smartsheets.get_sheet(...)`、`get_sheets(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/add_view` | `smartsheets.add_view(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/delete_views` | `smartsheets.delete_view(...)`、`delete_views(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/update_view` | `smartsheets.update_view(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/get_views` | `smartsheets.get_views(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/add_fields` | `smartsheets.add_fields(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/delete_fields` | `smartsheets.delete_fields(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/update_fields` | `smartsheets.update_fields(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/get_fields` | `smartsheets.get_fields(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/add_field_group` | `smartsheets.add_group(...)`、`add_field_group(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/delete_field_groups` | `smartsheets.delete_group(...)`、`delete_field_groups(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/update_field_group` | `smartsheets.update_group(...)`、`update_field_group(...)` | ✅ |
| `/cgi-bin/wedoc/smartsheet/get_field_groups` | `smartsheets.get_groups(...)`、`get_field_groups(...)` | ✅ |

### OA · 文档权限

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/wedoc/doc_get_auth` | `wedoc.permissions.get_auth(docid)` | ✅ |
| `/cgi-bin/wedoc/doc_set_auth` | `wedoc.permissions.set_auth(...)` | ✅ |
| `/cgi-bin/wedoc/mod_doc_member` | `wedoc.permissions.mod_member(...)` | ✅ |
| `/cgi-bin/wedoc/mod_doc_join_rule` | `wedoc.permissions.mod_join_rule(...)` | ✅ |
| `/cgi-bin/wedoc/mod_doc_safty_setting`（修改文档安全设置） | `wedoc.permissions.mod_safty_setting(...)` | ✅ |

### OA · 收集表

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/wedoc/create_form` | `forms.create_collect(...)` | ✅ |
| `/cgi-bin/wedoc/modify_form` | `forms.modify_collect(...)` | ✅ |
| `/cgi-bin/wedoc/get_form_info` | `forms.get_info(formid)` | ✅ |
| `/cgi-bin/wedoc/get_form_answer` | `forms.get_answer(...)` | ✅ |
| `/cgi-bin/wedoc/get_form_statistic` | `forms.get_statistic(...)` | ✅ |
| 其他收集表接口 | `forms.call(endpoint, payload)`（通用调用） | ✅ |

### 微盘（WeDrive）

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/wedrive/new_space_info` | `wedrive.get_space_info(spaceid)` | ✅ |
| `/cgi-bin/wedrive/space_create` | `wedrive.create_space(...)` | ✅ |
| `/cgi-bin/wedrive/space_rename` | `wedrive.rename_space(...)` | ✅ |
| `/cgi-bin/wedrive/space_dismiss` | `wedrive.dismiss_space(spaceid)` | ✅ |
| `/cgi-bin/wedrive/space_setting` | `wedrive.set_space(...)` | ✅ |
| `/cgi-bin/wedrive/space_share` | `wedrive.share_space(spaceid)` | ✅ |
| `/cgi-bin/wedrive/space_acl_add` | `wedrive.add_space_acl(...)` | ✅ |
| `/cgi-bin/wedrive/space_acl_del` | `wedrive.delete_space_acl(...)` | ✅ |
| `/cgi-bin/wedrive/file_create` | `wedrive.create_folder(...)` | ✅ |
| `/cgi-bin/wedrive/file_upload` | `wedrive.upload_file(...)` | ✅ |
| `/cgi-bin/wedrive/file_download` | `wedrive.download_file(...)` | ✅ |
| `/cgi-bin/wedrive/file_delete` | `wedrive.delete_file(...)` | ✅ |
| `/cgi-bin/wedrive/file_list` | `wedrive.list_file(...)` | ✅ |
| `/cgi-bin/wedrive/file_info` | `wedrive.get_file_meta(fileid)` | ✅ |
| `/cgi-bin/wedrive/file_rename` | `wedrive.rename_file(...)` | ✅ |
| `/cgi-bin/wedrive/file_move` | `wedrive.move_file(...)` | ✅ |
| `/cgi-bin/wedrive/file_setting` | `wedrive.set_file(...)` | ✅ |
| `/cgi-bin/wedrive/file_secure_setting` | `wedrive.set_file_security(...)` | ✅ |
| `/cgi-bin/wedrive/file_acl_add` | `wedrive.add_file_acl(...)` | ✅ |
| `/cgi-bin/wedrive/file_acl_del` | `wedrive.delete_file_acl(...)`、`cancel_share(...)` | ✅ |
| `/cgi-bin/wedrive/file_share` | `wedrive.get_share_link(fileid)` | ✅ |
| `/cgi-bin/wedrive/file_upload_init`（文件分块上传初始化） | `wedrive.file_upload_init(...)` | ✅ |
| `/cgi-bin/wedrive/file_upload_part`（文件分块上传） | `wedrive.file_upload_part(...)` | ✅ |
| `/cgi-bin/wedrive/file_upload_finish`（文件分块上传完成） | `wedrive.file_upload_finish(...)` | ✅ |
| `/cgi-bin/wedrive/get_file_permission`（获取文件权限信息） | `wedrive.get_file_permission(...)` | ✅ |
| `/cgi-bin/wedrive/mng_pro_info`（获取盘专业版信息） | `wedrive.mng_pro_info()` | ✅ |

### 企业邮箱

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/exmail/app/compose_send` | `mail.compose_send(payload)`、`send_mail(...)`、`send_schedule_mail(...)`、`send_meeting_mail(...)` | ✅ |
| `/cgi-bin/exmail/mail/getlist` | `mail.get_mail_list(...)` | ✅ |
| `/cgi-bin/exmail/mail/get` | `mail.get_mail_content(...)` | ✅ |
| `/cgi-bin/exmail/mail/unread` | `mail.get_mail_unread_count(...)` | ✅ |
| `/cgi-bin/exmail/app/update` | `mail.update_app_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/app/get` | `mail.get_app_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/group/create` | `mail.create_mail_group(...)` | ✅ |
| `/cgi-bin/exmail/group/update` | `mail.update_mail_group(...)` | ✅ |
| `/cgi-bin/exmail/group/delete` | `mail.delete_mail_group(...)` | ✅ |
| `/cgi-bin/exmail/group/get` | `mail.get_mail_group(...)` | ✅ |
| `/cgi-bin/exmail/group/search` | `mail.search_mail_group(...)` | ✅ |
| `/cgi-bin/exmail/publicmailbox/create` | `mail.create_public_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/publicmailbox/update` | `mail.update_public_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/publicmailbox/delete` | `mail.delete_public_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/publicmailbox/get` | `mail.get_public_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/publicmailbox/search` | `mail.search_public_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/useroption/list` | `mail.get_client_password_list(...)` | ✅ |
| `/cgi-bin/exmail/useroption/delete` | `mail.delete_client_password(...)` | ✅ |
| `/cgi-bin/exmail/vip/batchadd` | `mail.allocate_mail_advanced_account(...)` | ✅ |
| `/cgi-bin/exmail/vip/batchdel` | `mail.deallocate_mail_advanced_account(...)` | ✅ |
| `/cgi-bin/exmail/vip/list` | `mail.get_mail_advanced_account_list(...)` | ✅ |
| `/cgi-bin/exmail/user/option` | `mail.toggle_mailbox_status(...)`、`enable_mailbox(...)`、`disable_mailbox(...)` | ✅ |
| `/cgi-bin/exmail/user/get` | `mail.get_user_mail_attribute(...)` | ✅ |
| `/cgi-bin/exmail/user/update` | `mail.update_user_mail_attribute(...)` | ✅ |

### 人事助手

| 企业微信接口 | SDK 方法 | 状态 |
| --- | --- | --- |
| `/cgi-bin/hr/get_fields` | `hr.get_fields()` | ✅ |
| `/cgi-bin/hr/get_staff_info` | `hr.get_staff_info(userid)` | ✅ |
| `/cgi-bin/hr/update_staff_info` | `hr.update_staff_info(userid, update_attrs)`（配合 `hr.text_attr` / `hr.uint32_attr`） | ✅ |

### 未实现模块（代表性接口）

以下模块当前未封装，列出的官方接口均可作为后续扩展方向：

| 模块 | 代表性接口 | 状态 |
| --- | --- | --- |
| 客户联系 / 客户群 / 客户朋友圈 | `/cgi-bin/externalcontact/*`（联系我配置、客户详情、客户群、朋友圈等） | ⬜ |
| 微信客服 | `/cgi-bin/kf/*`（客服账号、接待人员、客服消息等） | ⬜ |
| 会话内容存档 | `/cgi-bin/msgaudit/*` | ⬜ |
| 会议 / 网络研讨会 / 会议室 | `/cgi-bin/meeting/*` | ⬜ |
| 直播 | `/cgi-bin/living/*` | ⬜ |
| 汇报 | `/cgi-bin/report/*` | ⬜ |
| 公费电话 | `/cgi-bin/pstncc/*` | ⬜ |
| 紧急通知 | `/cgi-bin/oa/emergency/send` | ⬜ |
| 企业支付 / 红包 | `/mmpaymkttransfers/*`（企业红包、向员工付款，走微信支付域名）、`/cgi-bin/externalpay/*`（对外收款）、`/cgi-bin/miniapppay/*`（小程序支付） | ⬜ |
| 家校沟通 | `/cgi-bin/school/*`（家长、学生、健康上报、上课打卡等） | ⬜ |
| 上下游 / 企业互联 | `/cgi-bin/corpgroup/*`、`/cgi-bin/corp/*` | ⬜ |
| 第三方应用 / 服务商 | `/cgi-bin/service/*`（套件 token、授权等） | ⬜ |
| 数据分析 | 成员增减、群聊数据等数据统计接口 | ⬜ |

## 返回值与类型

服务方法默认返回企业微信原始 JSON 响应，即 `dict[str, Any]`。`wecomkit.types` 提供 Pydantic v2 模型，调用方可以按需自行校验：

```python
from wecomkit.types import BaseResp, UserInfo

resp = await client.users.get("zhangsan")
user = UserInfo.model_validate(resp)

ok = BaseResp.model_validate({"errcode": 0, "errmsg": "ok"})
```

当前服务方法没有统一的 `response_model` 参数。

智能表格和 WeDoc 权限也提供了宽松模型，未知字段会保留，便于兼容接口扩展：

```python
from wecomkit.types import SmartSheetRecordListResponse

resp = await client.wedoc.smartsheets.get_records(docid, sheet_id)
records = SmartSheetRecordListResponse.model_validate(resp)
```

## 重试策略

默认不重试。需要对网络错误或企业微信限流错误做简单重试时，在 `WeComConfig` 中配置：

```python
config = WeComConfig(
    corp_id="wwxx",
    corp_secret="secret",
    max_retries=2,
    retry_backoff=0.5,
)
```

等待时间按 `retry_backoff * 2 ** attempt` 递增。

## Token 缓存

SDK 默认使用内存缓存 token，并在过期前 `token_refresh_buffer` 秒提前刷新。生产环境建议使用文件缓存或自定义缓存：

```python
from wecomkit import AsyncWeComClient, JSONFileTokenCache, WeComConfig

client = AsyncWeComClient(
    WeComConfig(corp_id="wwxx", corp_secret="secret"),
    token_cache=JSONFileTokenCache(".cache/wecom-token.json"),
)
```

多进程或多实例部署建议使用 Redis。Redis 是可选依赖：

```bash
uv pip install "wecomkit[redis]"
# 或
uv add "wecomkit[redis]"
```

```python
from redis.asyncio import Redis
from wecomkit import AsyncWeComClient, RedisTokenCache, WeComConfig

redis = Redis.from_url("redis://localhost:6379/0")

client = AsyncWeComClient(
    WeComConfig(corp_id="wwxx", corp_secret="secret"),
    token_cache=RedisTokenCache(redis, key="wecomkit:access_token"),
)
```

也可以直接通过 URL 创建：

```python
from wecomkit import RedisTokenCache

token_cache = RedisTokenCache.from_url(
    "redis://localhost:6379/0",
    key="wecomkit:access_token",
)
```

自定义缓存：

```python
from wecomkit.token_cache import TokenCache


class RedisTokenCache(TokenCache):
    async def get(self) -> str | None:
        ...

    async def set(self, token: str, expires_in: int) -> None:
        ...
```

## 异常处理

```python
from wecomkit.exceptions import WeComAPIError, WeComNetworkError

try:
    user = await client.users.get("invalid_userid")
except WeComAPIError as e:
    print(e.errcode, e.errmsg)
except WeComNetworkError as e:
    print("network error", e)
```

## 开发

```bash
uv run pytest
uv run ruff check .
uv build
```

## License

MIT
