Metadata-Version: 2.5
Name: py_wecom_toolkit
Version: 4.0.2
Summary: Enterprise WeChat (WeCom) API toolkit for Python / 企业微信（WeCom）API 工具包
Project-URL: homepage, https://gitee.com/guolei19850528/py_wecom_toolkit
Project-URL: repository, https://gitee.com/guolei19850528/py_wecom_toolkit
Author-email: 郭磊 <174000902@qq.com>
License: MIT
License-File: LICENSE
Keywords: api,enterprise-wechat,toolkit,wecom,weixin-work,企业微信,企业微信API,企业微信工具
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: diskcache>=5.6.0
Requires-Dist: httpx<1.0,>=0.24.0
Requires-Dist: pydantic<3.0,>=2.0.0
Requires-Dist: redis>=5.0.0
Provides-Extra: all
Requires-Dist: mkdocs-material>=9.0.0; extra == 'all'
Requires-Dist: mkdocs>=1.5.0; extra == 'all'
Requires-Dist: mypy>=1.10.0; extra == 'all'
Requires-Dist: pre-commit>=3.7.0; extra == 'all'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'all'
Requires-Dist: pytest-cov>=5.0.0; extra == 'all'
Requires-Dist: pytest>=8.0.0; extra == 'all'
Requires-Dist: python-dotenv>=1.0.0; extra == 'all'
Requires-Dist: respx>=0.21.0; extra == 'all'
Requires-Dist: ruff>=0.8.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: mypy>=1.10.0; extra == 'dev'
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0.0; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.0.0; extra == 'docs'
Requires-Dist: mkdocs>=1.5.0; extra == 'docs'
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
Requires-Dist: pytest>=8.0.0; extra == 'test'
Requires-Dist: python-dotenv>=1.0.0; extra == 'test'
Requires-Dist: respx>=0.21.0; extra == 'test'
Description-Content-Type: text/markdown

# py_wecom_toolkit

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

[中文文档](readme.md) | [English Documentation](readme.en.md)

## 📄 文档

- [企业微信开发者文档](https://developer.work.weixin.qq.com/document/path/90556)
- [httpx](https://pypi.org/project/httpx/)
- [pydantic](https://pypi.org/project/pydantic/)
- [diskcache](https://pypi.org/project/diskcache/)
- [redis](https://pypi.org/project/redis/)

## 📖 项目介绍

`py_wecom_toolkit` 是一个企业微信（WeCom）API 工具包，旨在为 Python 开发者提供简洁、易用的企业微信 API 调用接口。本库支持企业微信的消息推送（原群机器人）、素材管理等核心功能，采用现代 Python 工具链构建，支持 Python 3.9 及以上版本。

本工具包旨在简化与企业微信 API 的交互，提供统一、规范的接口调用方式，帮助开发者快速集成企业微信相关功能到自己的应用中。

## 🏗️ 软件架构

```
py_wecom_toolkit/
├── src/py_wecom_toolkit/
│   ├── __init__.py              # 包入口，导出版本、作者等元数据
│   ├── py.typed                 # PEP 561 类型标记文件
│   ├── server/
│   │   ├── __init__.py
│   │   ├── client.py            # access_token 获取客户端（基础客户端）
│   │   │   ├── Client           # 同步客户端
27: │   │   │   └── AsyncClient      # 异步客户端
29: │   │   ├── exception.py         # 异常类定义（Error, ConfigError, NetworkError）
30: │   │   ├── response.py          # 响应模型（BaseResponse, AccessTokenResponse）
31: │   │   ├── cache.py             # 缓存管理（TokenCache, MemoryCache, DiskCache, RedisCache, build_cache_key）
32: │   │   ├── message/             # 消息发送子模块
33: │   │   │   ├── __init__.py
34: │   │   │   ├── client.py        # WeCom 消息客户端（同步/异步）
35: │   │   │   └── msgtype.py       # 消息类型模型（TextMessage, MarkdownMessage, 等）
36: │   │   └── material/            # 素材管理子模块
37: │   │       ├── __init__.py
38: │   │       ├── client.py        # 素材客户端（同步/异步）
39: │   │       ├── msgtype.py       # 素材类型模型（MaterialType, UploadResponse, UploadImgResponse）
40: │   │       └── response.py      # 素材响应模型（UploadResponse, UploadImgResponse）
41: └── webhook/                 # 消息推送（原群机器人）子模块
42: │       ├── __init__.py
43: │       ├── client.py            # Webhook 客户端（同步/异步）
44: │       ├── exception.py         # 异常类定义
45: │       ├── msgtype.py           # 消息类型模型（TextMsgType, MarkdownMsgType, 等）
46: │       └── response.py          # 响应模型（BaseResponse, UploadMediaResponse）
├── tests/
49: │   ├── conftest.py              # pytest 测试配置和 fixture
50: │   ├── test_access_token.py     # access_token 获取测试
51: │   ├── test_message.py          # 应用消息发送测试
52: │   └── test_material.py         # 素材管理测试
53: ├── examples/                    # 使用示例
54:     ├── server/
55:     │   ├── access_token.py      # access_token 获取示例
56:     │   ├── message.py           # 应用消息发送示例
57:     │   └── material.py          # 素材管理示例
58:     └── webhook/
59:         ├── text.py              # 文本消息发送示例
60:         ├── markdown.py          # markdown 消息发送示例
61:         ├── markdown_v2.py       # markdown_v2 消息发送示例
62:         ├── image.py             # 图片消息发送示例
63:         ├── news.py              # 图文消息发送示例
64:         ├── file.py              # 文件消息发送示例
65:         ├── voice.py             # 语音消息发送示例
66:         └── template_card.py     # 模版卡片消息发送示例
```

### 核心模块说明

#### server 模块 - 企业微信服务端 API

| 模块 | 说明 |
|------|------|
| `server/client.py` | 企业微信 access_token 获取客户端，支持同步和异步调用，自动缓存 access_token |
| `server/exception.py` | 定义基础异常类 `Error`、`ConfigError`、`NetworkError` |
| `server/response.py` | 定义响应模型 `BaseResponse`、`AccessTokenResponse`，支持 `ok` 属性判断 |
| `server/cache.py` | 缓存抽象层，支持内存缓存（`MemoryCache`）、磁盘缓存（`DiskCache`）、Redis 缓存（`RedisCache`） |
| `server/message/client.py` | 企业微信应用消息客户端，支持文本、markdown、图片、图文、文件、语音、模版卡片等消息类型 |
| `server/message/msgtype.py` | 消息类型定义，支持 text、markdown、markdown_v2、image、news、file、voice、template_card |
| `server/material/client.py` | 素材管理客户端，支持上传临时素材、上传图片、下载素材 |
| `server/material/msgtype.py` | 素材类型定义，支持 image、voice、video、file 类型 |
| `server/material/response.py` | 素材响应模型 `UploadResponse`、`UploadImgResponse` |

#### webhook 模块 - 消息推送（原群机器人）

| 模块 | 说明 |
|------|------|
| `webhook/client.py` | Webhook 客户端，支持文本、markdown、图片、图文、文件、语音、模版卡片等消息类型 |
| `webhook/exception.py` | 定义基础异常类 `Error`、`ConfigError`、`NetworkError`、`UploadError` |
| `webhook/msgtype.py` | 消息类型定义，支持 text、markdown、markdown_v2、image、news、file、voice、template_card |
| `webhook/response.py` | 定义响应模型 `BaseResponse`、`Response`、`UploadMediaResponse`，支持 `ok` 属性判断 |

## 🛠️ 安装教程

### 方式一：使用 uv 安装（推荐）

[uv](https://github.com/astral-sh/uv) 是一个极快的 Python 包安装和管理工具，推荐使用：

```bash
uv add py_wecom_toolkit
```

### 方式二：使用 pip 安装

```bash
pip install py_wecom_toolkit
```

### 方式三：从源码安装

```bash
git clone https://gitee.com/guolei19850528/py_wecom_toolkit.git
cd py_wecom_toolkit
uv sync --all-groups
```

## 📖 使用说明

> ⚠️ 注意：以下示例中的 `CORP_ID`、`CORP_SECRET`、`AGENT_ID`、`TOUSER`、`WEBHOOK_KEY` 均为占位符，请替换为您的实际值后再使用。

### 1. server 模块 - 企业微信服务端 API

server 模块提供企业微信服务端 API 的访问功能，包括 access_token 获取、应用消息发送、素材管理等。

#### 1.1 获取 access_token

access_token 是企业微信 API 接口的调用凭证，有效期为 2 小时（7200 秒），请务必缓存。

**同步获取：**

```python
from py_wecom_toolkit.server.client import Client
from py_wecom_toolkit.server.response import AccessTokenResponse

# 初始化客户端
with Client(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
    resp: AccessTokenResponse = client.get_token()
    if resp.ok:
        print(f"access_token: {resp.access_token}")
        print(f"expires_in: {resp.expires_in} 秒")
    else:
        print(f"获取失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

**异步获取：**

```python
import asyncio
from py_wecom_toolkit.server.client import AsyncClient
from py_wecom_toolkit.server.response import AccessTokenResponse

async def main():
    async with AsyncClient(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
        resp: AccessTokenResponse = await client.get_token()
        if resp.ok:
            print(f"access_token: {resp.access_token}")
            print(f"expires_in: {resp.expires_in} 秒")
        else:
            print(f"获取失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

#### 1.2 发送应用消息

应用消息发送需要企业应用的 `corpid`、`corpsecret` 和 `agentid`（应用 ID）。

**发送文本消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import TextMessage

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = TextMessage(
        touser=TOUSER,
        agentid=AGENT_ID,
        text={"content": "Hello, WeCom!"}
    )
    resp = client.send(msg)
    if resp.ok:
        print("消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import TextMessage

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = TextMessage(
            touser=TOUSER,
            agentid=AGENT_ID,
            text={"content": "Hello, WeCom!"}
        )
        resp = await client.send(msg)
        if resp.ok:
            print("消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送 Markdown 消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import MarkdownMessage

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = MarkdownMessage(
        touser=TOUSER,
        agentid=AGENT_ID,
        markdown={"content": "**加粗**\n*斜体*\n[链接](https://example.com)\n> 引用内容"}
    )
    resp = client.send(msg)
    if resp.ok:
        print("Markdown 消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import MarkdownMessage

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = MarkdownMessage(
            touser=TOUSER,
            agentid=AGENT_ID,
            markdown={"content": "**加粗**\n*斜体*\n[链接](https://example.com)\n> 引用内容"}
        )
        resp = await client.send(msg)
        if resp.ok:
            print("Markdown 消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送 Markdown v2 消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import MarkdownV2Message

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = MarkdownV2Message(
        touser=TOUSER,
        agentid=AGENT_ID,
        markdown_v2={"content": "# 标题\n- 列表项1\n- 列表项2\n| 姓名 | 年龄 |\n| --- | --- |\n| 张三 | 25 |"}
    )
    resp = client.send(msg)
    if resp.ok:
        print("Markdown v2 消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import MarkdownV2Message

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = MarkdownV2Message(
            touser=TOUSER,
            agentid=AGENT_ID,
            markdown_v2={"content": "# 标题\n- 列表项1\n- 列表项2\n| 姓名 | 年龄 |\n| --- | --- |\n| 张三 | 25 |"}
        )
        resp = await client.send(msg)
        if resp.ok:
            print("Markdown v2 消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送图片消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import ImageMessage

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = ImageMessage(
        touser=TOUSER,
        agentid=AGENT_ID,
        image={"base64": "BASE64_ENCODED_IMAGE_DATA", "md5": "MD5_OF_BASE64_DATA"}
    )
    resp = client.send(msg)
    if resp.ok:
        print("图片消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import ImageMessage

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = ImageMessage(
            touser=TOUSER,
            agentid=AGENT_ID,
            image={"base64": "BASE64_ENCODED_IMAGE_DATA", "md5": "MD5_OF_BASE64_DATA"}
        )
        resp = await client.send(msg)
        if resp.ok:
            print("图片消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送图文消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import NewsMessage

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = NewsMessage(
        touser=TOUSER,
        agentid=AGENT_ID,
        news={
            "articles": [
                {
                    "title": "中秋节礼品领取",
                    "description": "今年中秋节公司有豪礼相送",
                    "url": "https://www.qq.com",
                    "picurl": "https://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"
                }
            ]
        }
    )
    resp = client.send(msg)
    if resp.ok:
        print("图文消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import NewsMessage

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = NewsMessage(
            touser=TOUSER,
            agentid=AGENT_ID,
            news={
                "articles": [
                    {
                        "title": "中秋节礼品领取",
                        "description": "今年中秋节公司有豪礼相送",
                        "url": "https://www.qq.com",
                        "picurl": "https://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"
                    }
                ]
            }
        )
        resp = await client.send(msg)
        if resp.ok:
            print("图文消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送文件消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import FileMessage

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = FileMessage(
        touser=TOUSER,
        agentid=AGENT_ID,
        file={"media_id": "MEDIA_ID_123"}  # 需要先上传文件获取 media_id
    )
    resp = client.send(msg)
    if resp.ok:
        print("文件消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import FileMessage

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = FileMessage(
            touser=TOUSER,
            agentid=AGENT_ID,
            file={"media_id": "MEDIA_ID_123"}  # 需要先上传文件获取 media_id
        )
        resp = await client.send(msg)
        if resp.ok:
            print("文件消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送语音消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import VoiceMessage

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = VoiceMessage(
        touser=TOUSER,
        agentid=AGENT_ID,
        voice={"media_id": "MEDIA_ID_VOICE"}  # 需要先上传语音文件获取 media_id
    )
    resp = client.send(msg)
    if resp.ok:
        print("语音消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import VoiceMessage

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = VoiceMessage(
            touser=TOUSER,
            agentid=AGENT_ID,
            voice={"media_id": "MEDIA_ID_VOICE"}  # 需要先上传语音文件获取 media_id
        )
        resp = await client.send(msg)
        if resp.ok:
            print("语音消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送模版卡片消息：**

同步：

```python
from py_wecom_toolkit.server.message.client import Client as MessageClient
from py_wecom_toolkit.server.message.msgtype import TemplateCardMessage

with MessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
    msg = TemplateCardMessage(
        touser=TOUSER,
        agentid=AGENT_ID,
        template_card={
            "card_type": "text_notice",
            "main_title": {
                "title": "欢迎使用企业微信",
                "desc": "您的好友正在邀请您加入企业微信"
            },
            "emphasis_content": {
                "title": "100",
                "desc": "数据含义"
            },
            "card_action": {
                "type": 1,
                "url": "https://work.weixin.qq.com"
            }
        }
    )
    resp = client.send(msg)
    if resp.ok:
        print("模版卡片消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.message.client import AsyncClient as MessageClient
from py_wecom_toolkit.server.message.msgtype import TemplateCardMessage

async def main():
    async with AsyncMessageClient(corpid=CORP_ID, corpsecret=CORP_SECRET, agentid=AGENT_ID) as client:
        msg = TemplateCardMessage(
            touser=TOUSER,
            agentid=AGENT_ID,
            template_card={
                "card_type": "text_notice",
                "main_title": {
                    "title": "欢迎使用企业微信",
                    "desc": "您的好友正在邀请您加入企业微信"
                },
                "emphasis_content": {
                    "title": "100",
                    "desc": "数据含义"
                },
                "card_action": {
                    "type": 1,
                    "url": "https://work.weixin.qq.com"
                }
            }
        )
        resp = await client.send(msg)
        if resp.ok:
            print("模版卡片消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

#### 1.4 素材管理

素材上传适用于需要上传文件、图片、语音等素材到企业微信服务器，获取 `media_id` 后可用于发送相应类型的消息。

**上传文件：**

同步：

```python
from py_wecom_toolkit.server.material.client import Client as MaterialClient
from py_wecom_toolkit.server.material.msgtype import MaterialType

with MaterialClient(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
    resp = client.upload(
        media_type=MaterialType.FILE,      # 文件类型：file
        file="path/to/your/file.txt",      # 本地文件路径
        filename="file.txt"                 # 上传展示的文件名
    )
    if resp.ok:
        print(f"上传成功，media_id: {resp.media_id}")
    else:
        print(f"上传失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.material.client import AsyncClient as MaterialClient
from py_wecom_toolkit.server.material.msgtype import MaterialType

async def main():
    async with AsyncMaterialClient(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
        resp = await client.upload(
            media_type=MaterialType.FILE,      # 文件类型：file
            file="path/to/your/file.txt",      # 本地文件路径
            filename="file.txt"                 # 上传展示的文件名
        )
        if resp.ok:
            print(f"上传成功，media_id: {resp.media_id}")
        else:
            print(f"上传失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**上传图片：**

同步：

```python
from py_wecom_toolkit.server.material.client import Client as MaterialClient

with MaterialClient(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
    resp = client.upload_img(
        file="path/to/your/image.jpg",
        filename="image.jpg"
    )
    if resp.ok:
        print(f"图片上传成功，URL: {resp.url}")
    else:
        print(f"上传失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.material.client import AsyncClient as MaterialClient

async def main():
    async with AsyncMaterialClient(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
        resp = await client.upload_img(
            file="path/to/your/image.jpg",
            filename="image.jpg"
        )
        if resp.ok:
            print(f"图片上传成功，URL: {resp.url}")
        else:
            print(f"上传失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**下载素材：**

同步：

```python
from py_wecom_toolkit.server.material.client import Client as MaterialClient

with MaterialClient(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
    data = client.download(media_id="MEDIA_ID_123")
    
    if isinstance(data, bytes):
        with open("downloaded_file", "wb") as f:
            f.write(data)
        print(f"下载成功，文件大小: {len(data)} 字节")
    else:
        print(f"下载失败")
```

异步：

```python
import asyncio
from py_wecom_toolkit.server.material.client import AsyncClient as MaterialClient

async def main():
    async with AsyncMaterialClient(corpid=CORP_ID, corpsecret=CORP_SECRET) as client:
        data = await client.download(media_id="MEDIA_ID_123")
        
        if isinstance(data, bytes):
            with open("downloaded_file", "wb") as f:
                f.write(data)
            print(f"下载成功，文件大小: {len(data)} 字节")
        else:
            print(f"下载失败")

asyncio.run(main())
```

### 2. webhook 模块 - 消息推送（原群机器人）

Webhook 消息推送（原群机器人）不需要 `corpid` 和 `corpsecret`，只需要 Webhook Key 即可发送消息到指定群组。

**发送文本消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_text("Hello, WeCom Webhook!")
    if resp.ok:
        print("文本消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_text("Hello, WeCom Webhook!")
        if resp.ok:
            print("文本消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送 Markdown 消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_markdown("**加粗文本**\n[链接](https://example.com)\n> 引用内容")
    if resp.ok:
        print("Markdown 消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_markdown("**加粗文本**\n[链接](https://example.com)\n> 引用内容")
        if resp.ok:
            print("Markdown 消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送 Markdown V2 消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_markdown_v2("# 标题\n- 列表项1\n- 列表项2")
    if resp.ok:
        print("Markdown V2 消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_markdown_v2("# 标题\n- 列表项1\n- 列表项2")
        if resp.ok:
            print("Markdown V2 消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送图片消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_image(base64="BASE64_DATA", md5="MD5_HASH")
    if resp.ok:
        print("图片消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_image(base64="BASE64_DATA", md5="MD5_HASH")
        if resp.ok:
            print("图片消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送图文消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_news(
        articles=[
            {
                "title": "图文标题",
                "description": "图文描述",
                "url": "https://example.com",
                "picurl": "https://example.com/pic.png"
            }
        ]
    )
    if resp.ok:
        print("图文消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_news(
            articles=[
                {
                    "title": "图文标题",
                    "description": "图文描述",
                    "url": "https://example.com",
                    "picurl": "https://example.com/pic.png"
                }
            ]
        )
        if resp.ok:
            print("图文消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送文件消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_file(media_id="MEDIA_ID_123")
    if resp.ok:
        print("文件消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_file(media_id="MEDIA_ID_123")
        if resp.ok:
            print("文件消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送语音消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_voice(media_id="MEDIA_ID_VOICE")
    if resp.ok:
        print("语音消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_voice(media_id="MEDIA_ID_VOICE")
        if resp.ok:
            print("语音消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

**发送模版卡片消息：**

同步：

```python
from py_wecom_toolkit.webhook.client import Client as WebhookClient

with WebhookClient(key=WEBHOOK_KEY) as client:
    resp = client.send_template_card(
        card_type="text_notice",
        main_title_title="欢迎使用企业微信",
        main_title_desc="您的好友正在邀请您加入企业微信",
        card_action_type=1,
        card_action_url="https://work.weixin.qq.com"
    )
    if resp.ok:
        print("模版卡片消息发送成功")
    else:
        print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")
```

异步：

```python
import asyncio
from py_wecom_toolkit.webhook.client import AsyncClient as WebhookClient

async def main():
    async with WebhookClient(key=WEBHOOK_KEY) as client:
        resp = await client.send_template_card(
            card_type="text_notice",
            main_title_title="欢迎使用企业微信",
            main_title_desc="您的好友正在邀请您加入企业微信",
            card_action_type=1,
            card_action_url="https://work.weixin.qq.com"
        )
        if resp.ok:
            print("模版卡片消息发送成功")
        else:
            print(f"发送失败: errcode={resp.errcode}, errmsg={resp.errmsg}")

asyncio.run(main())
```

## 🤝 贡献指南

欢迎为 py_wecom_toolkit 贡献代码！如果您想贡献，请按照以下步骤：

1. Fork 本仓库
2. 创建您的特性分支：`git checkout -b feature/your-feature`
3. 提交您的更改：`git commit -m 'Add some feature'`
4. 推送到分支：`git push origin feature/your-feature`
5. 提交一个拉取请求

请确保提交的代码符合代码风格规范，并通过所有测试用例。

## 👨‍💻 作者信息

- **姓名**: 郭磊
- **邮箱**: 174000902@qq.com

## 📄 许可证

本项目基于 [MIT 许可证](LICENSE) 开源。