py-lmobile-toolkit 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.
- py_lmobile_toolkit/__init__.py +24 -0
- py_lmobile_toolkit/sms/__init__.py +238 -0
- py_lmobile_toolkit/sms/responses.py +68 -0
- py_lmobile_toolkit/sms/utils.py +273 -0
- py_lmobile_toolkit-1.0.0.dist-info/METADATA +326 -0
- py_lmobile_toolkit-1.0.0.dist-info/RECORD +9 -0
- py_lmobile_toolkit-1.0.0.dist-info/WHEEL +5 -0
- py_lmobile_toolkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- py_lmobile_toolkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
py_lmobile - 微网通联短信服务 Python SDK
|
|
5
|
+
|
|
6
|
+
本模块提供接入微网通联(51welink)短信服务的完整功能,包括:
|
|
7
|
+
- 同步和异步短信发送能力
|
|
8
|
+
- 请求签名认证机制
|
|
9
|
+
- 响应数据模型
|
|
10
|
+
- 工具函数支持
|
|
11
|
+
|
|
12
|
+
主要组件:
|
|
13
|
+
- Sms: 短信客户端核心类,提供同步和异步发送接口
|
|
14
|
+
- utils: 工具函数模块,包含签名生成、时间戳等功能
|
|
15
|
+
- responses: 响应数据模型,用于标准化API响应
|
|
16
|
+
|
|
17
|
+
官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
|
|
18
|
+
|
|
19
|
+
使用示例:
|
|
20
|
+
from py_lmobile.sms import Sms
|
|
21
|
+
|
|
22
|
+
sms = Sms(account_id="your_account", password="your_password", product_id="your_product")
|
|
23
|
+
response = sms.send(phone_nos="13800138000", content="您的验证码是:123456")
|
|
24
|
+
"""
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
短信客户端模块
|
|
5
|
+
|
|
6
|
+
本模块提供微网通联短信服务的客户端实现,支持同步和异步两种发送方式。
|
|
7
|
+
|
|
8
|
+
核心功能:
|
|
9
|
+
- 客户端配置与初始化
|
|
10
|
+
- 同步/异步HTTP请求封装
|
|
11
|
+
- 短信发送接口(支持批量发送)
|
|
12
|
+
- 请求签名生成与认证
|
|
13
|
+
|
|
14
|
+
官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
|
|
15
|
+
|
|
16
|
+
使用示例:
|
|
17
|
+
# 同步发送
|
|
18
|
+
sms = Sms(account_id="xxx", password="xxx", product_id="xxx")
|
|
19
|
+
response = sms.send(phone_nos="13800138000", content="测试短信")
|
|
20
|
+
|
|
21
|
+
# 异步发送
|
|
22
|
+
response = await sms.async_send(phone_nos=["13800138000", "13900139000"], content="测试短信")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Optional, Union
|
|
26
|
+
|
|
27
|
+
import httpx
|
|
28
|
+
from py_httpx_toolkit import Httpx
|
|
29
|
+
|
|
30
|
+
from .utils import timestamp, random_digits, sha256_signature
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Sms(Httpx):
|
|
34
|
+
"""
|
|
35
|
+
微网通联短信客户端
|
|
36
|
+
|
|
37
|
+
提供同步和异步两种方式发送短信,支持请求签名认证机制。
|
|
38
|
+
|
|
39
|
+
设计架构:
|
|
40
|
+
- 配置层:初始化时配置账号信息、服务器地址等参数
|
|
41
|
+
- 客户端层:提供同步/异步HTTP客户端的创建与管理
|
|
42
|
+
- 请求层:封装底层HTTP请求,支持外部传入客户端实例
|
|
43
|
+
- 业务层:短信发送接口,处理签名生成和参数组装
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
base_url: Optional[str] = "https://api.51welink.com/",
|
|
49
|
+
account_id: Optional[str] = None,
|
|
50
|
+
password: Optional[str] = None,
|
|
51
|
+
product_id: Optional[Union[str, int]] = None,
|
|
52
|
+
smms_encrypt_key: Optional[str] = "SMmsEncryptKey",
|
|
53
|
+
client_kwargs: Optional[dict] = None
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
初始化短信客户端
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
base_url: API服务器地址,默认为微网通联官方地址
|
|
60
|
+
account_id: 账号ID,从微网通联控制台获取
|
|
61
|
+
password: 账号密码,用于签名生成
|
|
62
|
+
product_id: 产品ID,标识具体的短信产品
|
|
63
|
+
smms_encrypt_key: 加密密钥,用于密码的MD5加密盐值
|
|
64
|
+
client_kwargs: 传递给httpx.Client的额外配置参数
|
|
65
|
+
|
|
66
|
+
配置处理逻辑:
|
|
67
|
+
1. 处理base_url末尾斜杠,确保统一格式
|
|
68
|
+
2. 将None值转换为空字符串,避免后续处理异常
|
|
69
|
+
3. 合并默认客户端配置与用户传入配置
|
|
70
|
+
4. 默认禁用SSL验证(针对部分内部环境),超时60秒
|
|
71
|
+
"""
|
|
72
|
+
# 处理base_url,确保末尾无斜杠
|
|
73
|
+
self.base_url = base_url or ""
|
|
74
|
+
self.base_url = self.base_url[:-1] if self.base_url.endswith("/") else self.base_url
|
|
75
|
+
|
|
76
|
+
# 存储账号信息,None值转换为空字符串
|
|
77
|
+
self.account_id = account_id or ""
|
|
78
|
+
self.password = password or ""
|
|
79
|
+
self.product_id = product_id or ""
|
|
80
|
+
self.smms_encrypt_key = smms_encrypt_key or ""
|
|
81
|
+
|
|
82
|
+
# 合并客户端配置,用户配置可覆盖默认配置
|
|
83
|
+
self.client_kwargs = client_kwargs or {}
|
|
84
|
+
self.client_kwargs = {
|
|
85
|
+
**{
|
|
86
|
+
"base_url": self.base_url,
|
|
87
|
+
"verify": False, # 默认禁用SSL验证
|
|
88
|
+
"timeout": 60, # 默认超时60秒
|
|
89
|
+
},
|
|
90
|
+
**self.client_kwargs,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def send(
|
|
94
|
+
self,
|
|
95
|
+
phone_nos: Optional[Union[str, list, tuple]] = None,
|
|
96
|
+
content: Optional[str] = None,
|
|
97
|
+
client: Optional[httpx.Client] = None,
|
|
98
|
+
client_kwargs: Optional[dict] = None,
|
|
99
|
+
**kwargs
|
|
100
|
+
) -> httpx.Response:
|
|
101
|
+
"""
|
|
102
|
+
同步发送短信
|
|
103
|
+
|
|
104
|
+
组装短信发送请求参数,生成签名,调用底层HTTP请求发送短信。
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
client: 可选的HTTP客户端实例,若不传则自动创建
|
|
108
|
+
phone_nos: 接收短信的手机号码,支持单个字符串或列表/元组
|
|
109
|
+
content: 短信内容
|
|
110
|
+
**kwargs: 传递给httpx.Client.request的额外参数
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
httpx.Response: HTTP响应对象
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
Exception: 发送过程中可能抛出的异常(网络错误、超时等)
|
|
117
|
+
|
|
118
|
+
请求组装逻辑:
|
|
119
|
+
1. 处理手机号格式:列表/元组转换为逗号分隔的字符串
|
|
120
|
+
2. 生成时间戳(毫秒级)用于请求时效性验证
|
|
121
|
+
3. 生成随机数用于签名唯一性
|
|
122
|
+
4. 使用SHA256算法生成请求签名
|
|
123
|
+
5. 组装完整的请求参数并发送
|
|
124
|
+
|
|
125
|
+
签名生成规则:
|
|
126
|
+
1. 密码先进行MD5加密(带盐值smms_encrypt_key)
|
|
127
|
+
2. 按顺序拼接:AccountId、PhoneNos(第一个)、Password(MD5后)、Random、Timestamp
|
|
128
|
+
3. 使用&连接所有参数
|
|
129
|
+
4. 对拼接结果进行SHA256加密
|
|
130
|
+
"""
|
|
131
|
+
# 处理手机号格式:列表/元组转换为逗号分隔的字符串
|
|
132
|
+
if isinstance(phone_nos, (list, tuple)):
|
|
133
|
+
phone_nos = ",".join(phone_nos)
|
|
134
|
+
|
|
135
|
+
# 生成时间戳和随机数
|
|
136
|
+
ts = timestamp()
|
|
137
|
+
digits = random_digits()
|
|
138
|
+
client = client or None
|
|
139
|
+
client_kwargs = client_kwargs or {}
|
|
140
|
+
# 处理None值,确保kwargs是字典类型
|
|
141
|
+
kwargs = kwargs or {}
|
|
142
|
+
|
|
143
|
+
# 组装请求参数
|
|
144
|
+
kwargs = {
|
|
145
|
+
**{
|
|
146
|
+
"method": "POST",
|
|
147
|
+
"url": "/EncryptionSubmit/SendSms.ashx",
|
|
148
|
+
"data": {
|
|
149
|
+
"AccountID": self.account_id,
|
|
150
|
+
"ProductID": self.product_id,
|
|
151
|
+
"PhoneNos": phone_nos,
|
|
152
|
+
"Timestamp": ts,
|
|
153
|
+
"Random": digits,
|
|
154
|
+
"Content": content,
|
|
155
|
+
"AccessKey": sha256_signature(
|
|
156
|
+
account_id=self.account_id,
|
|
157
|
+
password=self.password,
|
|
158
|
+
phone_nos=phone_nos,
|
|
159
|
+
random_digits=digits,
|
|
160
|
+
timestamp=ts,
|
|
161
|
+
smms_encrypt_key=self.smms_encrypt_key,
|
|
162
|
+
)
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
**kwargs
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# 调用同步请求方法发送短信
|
|
169
|
+
return self.request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
170
|
+
|
|
171
|
+
async def async_send(
|
|
172
|
+
self,
|
|
173
|
+
phone_nos: Optional[Union[str, list, tuple]] = None,
|
|
174
|
+
content: Optional[str] = None,
|
|
175
|
+
client: Optional[httpx.AsyncClient] = None,
|
|
176
|
+
client_kwargs: Optional[dict] = None,
|
|
177
|
+
**kwargs
|
|
178
|
+
) -> httpx.Response:
|
|
179
|
+
"""
|
|
180
|
+
异步发送短信
|
|
181
|
+
|
|
182
|
+
组装短信发送请求参数,生成签名,调用底层异步HTTP请求发送短信。
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
client: 可选的异步HTTP客户端实例,若不传则自动创建
|
|
186
|
+
phone_nos: 接收短信的手机号码,支持单个字符串或列表/元组
|
|
187
|
+
content: 短信内容
|
|
188
|
+
**kwargs: 传递给httpx.AsyncClient.request的额外参数
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
httpx.Response: HTTP响应对象
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
Exception: 发送过程中可能抛出的异常(网络错误、超时等)
|
|
195
|
+
|
|
196
|
+
请求组装逻辑:
|
|
197
|
+
与同步版本相同,详见send()方法的注释
|
|
198
|
+
"""
|
|
199
|
+
# 处理手机号格式:列表/元组转换为逗号分隔的字符串
|
|
200
|
+
if isinstance(phone_nos, (list, tuple)):
|
|
201
|
+
phone_nos = ",".join(phone_nos)
|
|
202
|
+
|
|
203
|
+
# 生成时间戳和随机数
|
|
204
|
+
ts = timestamp()
|
|
205
|
+
digits = random_digits()
|
|
206
|
+
client = client or None
|
|
207
|
+
client_kwargs = client_kwargs or {}
|
|
208
|
+
|
|
209
|
+
# 处理None值,确保kwargs是字典类型
|
|
210
|
+
kwargs = kwargs or {}
|
|
211
|
+
|
|
212
|
+
# 组装请求参数
|
|
213
|
+
kwargs = {
|
|
214
|
+
**{
|
|
215
|
+
"method": "POST",
|
|
216
|
+
"url": "/EncryptionSubmit/SendSms.ashx",
|
|
217
|
+
"data": {
|
|
218
|
+
"AccountID": self.account_id,
|
|
219
|
+
"ProductID": self.product_id,
|
|
220
|
+
"PhoneNos": phone_nos,
|
|
221
|
+
"Timestamp": ts,
|
|
222
|
+
"Random": digits,
|
|
223
|
+
"Content": content,
|
|
224
|
+
"AccessKey": sha256_signature(
|
|
225
|
+
account_id=self.account_id,
|
|
226
|
+
password=self.password,
|
|
227
|
+
phone_nos=phone_nos,
|
|
228
|
+
random_digits=digits,
|
|
229
|
+
timestamp=ts,
|
|
230
|
+
smms_encrypt_key=self.smms_encrypt_key,
|
|
231
|
+
)
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
**kwargs
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# 调用异步请求方法发送短信
|
|
238
|
+
return await self.async_request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
响应模型模块
|
|
5
|
+
|
|
6
|
+
本模块定义了微网通联API响应的数据模型,使用Pydantic进行数据验证和序列化。
|
|
7
|
+
|
|
8
|
+
核心模型:
|
|
9
|
+
- Base: 所有响应的基类,包含Result字段
|
|
10
|
+
- Success: 成功响应模型,Result字段值固定为"succ"
|
|
11
|
+
|
|
12
|
+
设计说明:
|
|
13
|
+
- 使用Pydantic BaseModel作为基类,提供自动数据验证
|
|
14
|
+
- 支持额外字段(extra="allow"),兼容API返回的未知字段
|
|
15
|
+
- 使用Field描述字段的标题和说明,便于生成文档
|
|
16
|
+
|
|
17
|
+
官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field
|
|
21
|
+
from typing import Literal, Union, Any, Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Base(BaseModel):
|
|
25
|
+
"""
|
|
26
|
+
微网通联API响应基类
|
|
27
|
+
|
|
28
|
+
所有API响应都继承自此基类,包含统一的错误码字段。
|
|
29
|
+
|
|
30
|
+
字段说明:
|
|
31
|
+
Result: 错误码字段,用于标识请求处理结果
|
|
32
|
+
- "succ" 表示成功
|
|
33
|
+
- 其他值表示失败,具体含义参考API文档
|
|
34
|
+
|
|
35
|
+
配置说明:
|
|
36
|
+
extra: "allow" - 允许响应中包含模型未定义的额外字段
|
|
37
|
+
这是因为API可能返回一些未在文档中明确说明的字段
|
|
38
|
+
"""
|
|
39
|
+
Result: str = Field(..., title="错误码", description="错误码,succ表示成功,其他表示失败")
|
|
40
|
+
|
|
41
|
+
model_config = {
|
|
42
|
+
"extra": "allow" # 允许额外动态字段,兼容API返回的扩展字段
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Success(Base):
|
|
47
|
+
"""
|
|
48
|
+
成功响应模型
|
|
49
|
+
|
|
50
|
+
继承自Base,专门用于表示API请求成功的响应。
|
|
51
|
+
|
|
52
|
+
字段约束:
|
|
53
|
+
Result: 固定为"succ",使用Literal类型进行严格类型约束
|
|
54
|
+
|
|
55
|
+
使用场景:
|
|
56
|
+
- 验证响应是否为成功状态
|
|
57
|
+
- 作为响应数据的标准化容器
|
|
58
|
+
- 与utils中的build_success_instance函数配合使用
|
|
59
|
+
|
|
60
|
+
代码演示:
|
|
61
|
+
>>> from py_lmobile_toolkit.sms.responses import Success
|
|
62
|
+
>>> result = Success(Result="succ")
|
|
63
|
+
>>> print(result.Result)
|
|
64
|
+
'succ'
|
|
65
|
+
>>> result.model_dump()
|
|
66
|
+
{'Result': 'succ'}
|
|
67
|
+
"""
|
|
68
|
+
Result: Literal["succ"] = Field(..., title="错误码", description="成功,值为succ")
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
工具函数模块
|
|
5
|
+
|
|
6
|
+
本模块提供短信服务所需的辅助工具函数,包括:
|
|
7
|
+
- JSON数据处理工具
|
|
8
|
+
- 时间戳生成
|
|
9
|
+
- 随机数生成
|
|
10
|
+
- SHA256签名生成
|
|
11
|
+
- 响应模型转换
|
|
12
|
+
- JSON Schema校验
|
|
13
|
+
|
|
14
|
+
这些工具函数被Sms客户端内部使用,也可单独导出供外部使用。
|
|
15
|
+
|
|
16
|
+
官方 API 文档:https://www.lmobile.cn/ApiPages/index.html
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import random
|
|
21
|
+
import string
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from typing import Any, Optional, Union
|
|
24
|
+
|
|
25
|
+
import httpx
|
|
26
|
+
from jsonpath_ng import parse
|
|
27
|
+
from jsonschema.validators import Draft202012Validator
|
|
28
|
+
|
|
29
|
+
from .responses import Success
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def json_find_first(expression: str, data: Any) -> Any:
|
|
33
|
+
"""
|
|
34
|
+
使用 JSONPath 表达式从数据中查找第一个匹配项
|
|
35
|
+
|
|
36
|
+
JSONPath是一种用于在JSON数据中定位和提取信息的查询语言,
|
|
37
|
+
类似于XPath用于XML文档的查询。
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
expression: JSONPath表达式,用于指定要查找的数据路径
|
|
41
|
+
data: 待查询的JSON数据(字典或列表)
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Any: 第一个匹配的值,如果没有匹配项则返回None
|
|
45
|
+
|
|
46
|
+
支持的JSONPath语法示例:
|
|
47
|
+
- $.key: 获取根节点下的key字段
|
|
48
|
+
- $..key: 递归查找所有名为key的字段
|
|
49
|
+
- $.list[0]: 获取列表的第一个元素
|
|
50
|
+
- $.list[*].name: 获取列表所有元素的name字段
|
|
51
|
+
|
|
52
|
+
代码演示:
|
|
53
|
+
>>> data = {"store": {"books": [{"title": "Python"}, {"title": "Java"}]}}
|
|
54
|
+
>>> json_find_first("$.store.books[0].title", data)
|
|
55
|
+
'Python'
|
|
56
|
+
"""
|
|
57
|
+
# 使用jsonpath_ng解析表达式并查找匹配项
|
|
58
|
+
results = [i.value for i in parse(expression).find(data)]
|
|
59
|
+
|
|
60
|
+
# 返回第一个匹配结果,若无匹配则返回None
|
|
61
|
+
if isinstance(results, list) and len(results) > 0:
|
|
62
|
+
return results[0]
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def json_is_valid(schema: Optional[dict], data: Any) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
校验 JSON 数据是否符合指定的 JSON Schema
|
|
69
|
+
|
|
70
|
+
JSON Schema是一种用于定义JSON数据结构的规范,可用于数据验证、
|
|
71
|
+
文档生成和代码生成等场景。本函数使用Draft2020-12版本的规范。
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
schema: JSON Schema字典,定义了数据的结构约束
|
|
75
|
+
data: 待校验的JSON数据
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
bool: 校验是否成功,True表示符合Schema,False表示不符合
|
|
79
|
+
|
|
80
|
+
代码演示:
|
|
81
|
+
>>> schema = {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}
|
|
82
|
+
>>> json_is_valid(schema, {"name": "test"})
|
|
83
|
+
True
|
|
84
|
+
>>> json_is_valid(schema, {"age": 18})
|
|
85
|
+
False
|
|
86
|
+
"""
|
|
87
|
+
# 使用Draft2020-12版本的Validator进行校验
|
|
88
|
+
return Draft202012Validator(schema).is_valid(data)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def timestamp() -> int:
|
|
92
|
+
"""
|
|
93
|
+
生成当前时间戳(毫秒)
|
|
94
|
+
|
|
95
|
+
返回当前时间距离1970年1月1日00:00:00 UTC的毫秒数,
|
|
96
|
+
用于请求的时效性验证和签名生成。
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
int: 当前时间戳(毫秒)
|
|
100
|
+
|
|
101
|
+
设计说明:
|
|
102
|
+
- 使用datetime.now().timestamp()获取秒级时间戳
|
|
103
|
+
- 乘以1000转换为毫秒
|
|
104
|
+
- 使用int()取整,确保返回整数类型
|
|
105
|
+
|
|
106
|
+
代码演示:
|
|
107
|
+
>>> ts = timestamp()
|
|
108
|
+
>>> print(ts) # 输出示例: 1630000000000
|
|
109
|
+
>>> isinstance(ts, int)
|
|
110
|
+
True
|
|
111
|
+
"""
|
|
112
|
+
return int((datetime.now().timestamp() * 1000))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def random_digits(length: int = 10) -> int:
|
|
116
|
+
"""
|
|
117
|
+
生成指定长度的随机数字
|
|
118
|
+
|
|
119
|
+
从数字字符集'0123456789'中随机选取指定长度的字符,
|
|
120
|
+
拼接后转换为整数返回。
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
length: 随机数的长度,默认为10位
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
int: 指定长度的随机数字
|
|
127
|
+
|
|
128
|
+
实现细节:
|
|
129
|
+
- 使用random.sample()确保不会出现重复字符
|
|
130
|
+
- 使用string.digits获取数字字符集
|
|
131
|
+
- 转换为整数而非字符串,便于后续计算和比较
|
|
132
|
+
|
|
133
|
+
代码演示:
|
|
134
|
+
>>> digits = random_digits(6)
|
|
135
|
+
>>> print(digits) # 输出示例: 123456
|
|
136
|
+
>>> len(str(digits))
|
|
137
|
+
6
|
|
138
|
+
"""
|
|
139
|
+
# 从数字字符集中随机选取length个字符,拼接后转换为整数
|
|
140
|
+
return int("".join(random.sample(string.digits, length)))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def sha256_signature(
|
|
144
|
+
account_id: str = "",
|
|
145
|
+
password: str = "",
|
|
146
|
+
phone_nos: str = "",
|
|
147
|
+
random_digits: Optional[Union[int, str]] = "",
|
|
148
|
+
timestamp: Optional[Union[int, str]] = "",
|
|
149
|
+
smms_encrypt_key: str = ""
|
|
150
|
+
) -> str:
|
|
151
|
+
"""
|
|
152
|
+
生成SHA256签名
|
|
153
|
+
|
|
154
|
+
按照微网通联API要求的格式生成签名,用于短信发送请求的认证。
|
|
155
|
+
|
|
156
|
+
签名生成算法(严格按照API要求的顺序):
|
|
157
|
+
1. 密码MD5加密:password + smms_encrypt_key -> MD5 -> 转大写
|
|
158
|
+
2. 参数拼接:AccountId={account_id}&PhoneNos={first_phone}&Password={md5_password}&Random={random}&Timestamp={timestamp}
|
|
159
|
+
3. SHA256加密:对拼接后的字符串进行SHA256哈希
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
account_id: 账号ID
|
|
163
|
+
password: 原始密码
|
|
164
|
+
phone_nos: 手机号,多个手机号用逗号分隔(签名时只取第一个)
|
|
165
|
+
random_digits: 随机数字(由random_digits()生成)
|
|
166
|
+
timestamp: 时间戳(由timestamp()生成)
|
|
167
|
+
smms_encrypt_key: 加密密钥,作为MD5加密的盐值
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
str: SHA256签名结果(64位十六进制字符串)
|
|
171
|
+
|
|
172
|
+
代码演示:
|
|
173
|
+
>>> signature = sha256_signature(
|
|
174
|
+
... account_id="dljtwy00",
|
|
175
|
+
... password="g07KjuLN1",
|
|
176
|
+
... phone_nos="13800138000",
|
|
177
|
+
... random_digits="1234567890",
|
|
178
|
+
... timestamp="1630000000000",
|
|
179
|
+
... smms_encrypt_key="SMmsEncryptKey"
|
|
180
|
+
... )
|
|
181
|
+
>>> len(signature)
|
|
182
|
+
64
|
|
183
|
+
"""
|
|
184
|
+
# 步骤1:对密码进行MD5加密,使用加密密钥作为盐值
|
|
185
|
+
# 格式:MD5(password + smms_encrypt_key),结果转大写
|
|
186
|
+
password_md5 = hashlib.md5(f"{password}{smms_encrypt_key}".encode('utf-8')).hexdigest()
|
|
187
|
+
|
|
188
|
+
# 步骤2:按照API要求的顺序拼接参数
|
|
189
|
+
# 注意:PhoneNos只取第一个手机号用于签名
|
|
190
|
+
temp_string = "&".join([
|
|
191
|
+
f"AccountId={account_id}",
|
|
192
|
+
f"PhoneNos={phone_nos.split(',')[0]}", # 只取第一个手机号
|
|
193
|
+
f"Password={password_md5.upper()}", # MD5结果转大写
|
|
194
|
+
f"Random={random_digits}",
|
|
195
|
+
f"Timestamp={timestamp}",
|
|
196
|
+
])
|
|
197
|
+
|
|
198
|
+
# 步骤3:对拼接后的字符串进行SHA256加密
|
|
199
|
+
return hashlib.sha256(temp_string.encode("utf-8")).hexdigest()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def build_success_instance(response: Union[httpx.Response, dict]) -> Success:
|
|
203
|
+
"""
|
|
204
|
+
将 HTTP 响应或字典转换为 Success 模型对象
|
|
205
|
+
|
|
206
|
+
统一处理httpx.Response对象和字典,将其转换为标准化的Success响应模型,
|
|
207
|
+
便于后续的响应数据处理和校验。
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
response: HTTP响应对象或字典数据
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Success: 标准化的响应模型对象,包含Result字段
|
|
214
|
+
|
|
215
|
+
处理逻辑:
|
|
216
|
+
- 如果输入是httpx.Response对象,先调用json()方法解析响应体
|
|
217
|
+
- 如果输入是字典,直接传入Success构造函数
|
|
218
|
+
|
|
219
|
+
代码演示:
|
|
220
|
+
>>> response = httpx.Response(200, json={"Result": "succ"})
|
|
221
|
+
>>> result = build_success_instance(response)
|
|
222
|
+
>>> print(result.Result)
|
|
223
|
+
'succ'
|
|
224
|
+
>>> isinstance(result, Success)
|
|
225
|
+
True
|
|
226
|
+
"""
|
|
227
|
+
if isinstance(response, httpx.Response):
|
|
228
|
+
return Success(**response.json())
|
|
229
|
+
return Success(**response)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def success__is_valid(
|
|
233
|
+
response: Union[httpx.Response, dict] = None,
|
|
234
|
+
schema: dict = {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"properties": {
|
|
237
|
+
"Result": {
|
|
238
|
+
"oneOf": [
|
|
239
|
+
{"type": "string", "const": "succ"}
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
"required": ["Result"]
|
|
244
|
+
}
|
|
245
|
+
):
|
|
246
|
+
"""
|
|
247
|
+
校验 HTTP 响应或字典是否符合成功响应的 JSON Schema
|
|
248
|
+
|
|
249
|
+
用于验证API响应是否为预期的成功格式,确保响应数据的正确性。
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
response: HTTP响应对象或字典数据
|
|
253
|
+
schema: JSON Schema字典,默认为成功响应的Schema
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
bool: 校验是否成功,True表示符合Schema,False表示不符合
|
|
257
|
+
|
|
258
|
+
默认Schema说明:
|
|
259
|
+
- 必须是对象类型
|
|
260
|
+
- 必须包含Result字段
|
|
261
|
+
- Result字段的值必须是字符串"succ"
|
|
262
|
+
|
|
263
|
+
代码演示:
|
|
264
|
+
>>> response = httpx.Response(200, json={"Result": "succ"})
|
|
265
|
+
>>> success__is_valid(response)
|
|
266
|
+
True
|
|
267
|
+
>>> success__is_valid({"Result": "fail"})
|
|
268
|
+
False
|
|
269
|
+
"""
|
|
270
|
+
# 根据输入类型选择不同的处理方式
|
|
271
|
+
if isinstance(response, httpx.Response):
|
|
272
|
+
return json_is_valid(schema, response.json())
|
|
273
|
+
return json_is_valid(schema, response)
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-lmobile-toolkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 微网通联(51welink)短信服务 Python SDK,提供同步和异步两种发送方式,支持请求签名认证
|
|
5
|
+
Author-email: Guolei <174000902@qq.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 郭磊
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://gitee.com/guolei19850528/py_lmobile_toolkit
|
|
29
|
+
Project-URL: Repository, https://gitee.com/guolei19850528/py_lmobile_toolkit.git
|
|
30
|
+
Project-URL: Documentation, https://www.lmobile.cn/ApiPages/index.html
|
|
31
|
+
Keywords: lmobile,python,client,api,微网通联,短信,sms,51welink
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Operating System :: OS Independent
|
|
41
|
+
Classifier: Topic :: Communications
|
|
42
|
+
Classifier: Topic :: Communications :: Telephony
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Requires-Dist: httpx>=0.27.0
|
|
48
|
+
Requires-Dist: pydantic>=2.0
|
|
49
|
+
Requires-Dist: jsonpath-ng>=1.5.3
|
|
50
|
+
Requires-Dist: jsonschema>=4.21.0
|
|
51
|
+
Requires-Dist: py-httpx-toolkit>=1.0.1
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
# py-lmobile-toolkit
|
|
55
|
+
|
|
56
|
+
微网通联(51welink)短信服务 Python SDK,提供同步和异步两种发送方式,支持请求签名认证机制。
|
|
57
|
+
|
|
58
|
+
## 作者
|
|
59
|
+
|
|
60
|
+
**Guolei**
|
|
61
|
+
|
|
62
|
+
- 邮箱:174000902@qq.com
|
|
63
|
+
|
|
64
|
+
## 项目主页
|
|
65
|
+
|
|
66
|
+
- **Gitee**: https://gitee.com/guolei19850528/py_lmobile_toolkit
|
|
67
|
+
- **官方 API 文档**: https://www.lmobile.cn/ApiPages/index.html
|
|
68
|
+
|
|
69
|
+
## 功能特性
|
|
70
|
+
|
|
71
|
+
- ✅ 同步短信发送接口
|
|
72
|
+
- ✅ 异步短信发送接口
|
|
73
|
+
- ✅ 请求签名认证机制(SHA256)
|
|
74
|
+
- ✅ 响应数据模型验证(Pydantic)
|
|
75
|
+
- ✅ 完整的工具函数支持
|
|
76
|
+
|
|
77
|
+
## 安装
|
|
78
|
+
|
|
79
|
+
使用 `uv` 安装:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv add py-lmobile-toolkit
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
或从pip安装:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install py-lmobile-toolkit
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 依赖包
|
|
92
|
+
|
|
93
|
+
| 依赖 | 版本要求 | 说明 |
|
|
94
|
+
|------|----------|------|
|
|
95
|
+
| httpx | >=0.27.0 | HTTP 客户端 |
|
|
96
|
+
| pydantic | >=2.0 | 数据验证 |
|
|
97
|
+
| jsonpath-ng | >=1.5.3 | JSONPath 查询 |
|
|
98
|
+
| jsonschema | >=4.21.0 | JSON Schema 校验 |
|
|
99
|
+
| py-httpx-toolkit | >=1.0.1 | HTTP 工具封装 |
|
|
100
|
+
|
|
101
|
+
## API 接口
|
|
102
|
+
|
|
103
|
+
### Sms 类
|
|
104
|
+
|
|
105
|
+
短信客户端核心类,提供同步和异步发送接口。
|
|
106
|
+
|
|
107
|
+
#### 初始化
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from py_lmobile_toolkit.sms import Sms
|
|
111
|
+
|
|
112
|
+
sms = Sms(
|
|
113
|
+
account_id="your_account",
|
|
114
|
+
password="your_password",
|
|
115
|
+
product_id="your_product"
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**参数说明**:
|
|
120
|
+
|
|
121
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
122
|
+
|------|------|--------|------|
|
|
123
|
+
| base_url | str | https://api.51welink.com/ | API 服务器地址 |
|
|
124
|
+
| account_id | str | None | 账号 ID |
|
|
125
|
+
| password | str | None | 账号密码 |
|
|
126
|
+
| product_id | str/int | None | 产品 ID |
|
|
127
|
+
| smms_encrypt_key | str | SMmsEncryptKey | 加密密钥 |
|
|
128
|
+
| client_kwargs | dict | None | httpx.Client 配置 |
|
|
129
|
+
|
|
130
|
+
#### 同步发送
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
response = sms.send(
|
|
134
|
+
phone_nos="13800138000",
|
|
135
|
+
content="您的验证码是:123456"
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### 异步发送
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
response = await sms.async_send(
|
|
143
|
+
phone_nos=["13800138000", "13900139000"],
|
|
144
|
+
content="测试短信"
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**参数说明**:
|
|
149
|
+
|
|
150
|
+
| 参数 | 类型 | 说明 |
|
|
151
|
+
|------|------|------|
|
|
152
|
+
| phone_nos | str/list/tuple | 手机号码,支持单个或多个 |
|
|
153
|
+
| content | str | 短信内容 |
|
|
154
|
+
| client | httpx.Client/AsyncClient | 可选的客户端实例 |
|
|
155
|
+
| client_kwargs | dict | 客户端配置 |
|
|
156
|
+
|
|
157
|
+
## Utils 工具函数
|
|
158
|
+
|
|
159
|
+
### 时间戳生成
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from py_lmobile_toolkit.sms.utils import timestamp
|
|
163
|
+
|
|
164
|
+
ts = timestamp() # 返回毫秒级时间戳
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 随机数生成
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from py_lmobile_toolkit.sms.utils import random_digits
|
|
171
|
+
|
|
172
|
+
digits = random_digits(length=10) # 生成10位随机数
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### SHA256 签名生成
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from py_lmobile_toolkit.sms.utils import sha256_signature
|
|
179
|
+
|
|
180
|
+
signature = sha256_signature(
|
|
181
|
+
account_id="dljtwy00",
|
|
182
|
+
password="g07KjuLN1",
|
|
183
|
+
phone_nos="13800138000",
|
|
184
|
+
random_digits="1234567890",
|
|
185
|
+
timestamp="1630000000000",
|
|
186
|
+
smms_encrypt_key="SMmsEncryptKey"
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### JSONPath 查询
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from py_lmobile_toolkit.sms.utils import json_find_first
|
|
194
|
+
|
|
195
|
+
data = {"store": {"books": [{"title": "Python"}, {"title": "Java"}]}}
|
|
196
|
+
title = json_find_first("$.store.books[0].title", data) # 返回 "Python"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### JSON Schema 校验
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from py_lmobile_toolkit.sms.utils import json_is_valid
|
|
203
|
+
|
|
204
|
+
schema = {"type": "object", "properties": {"name": {"type": "string"}}}
|
|
205
|
+
valid = json_is_valid(schema, {"name": "test"}) # 返回 True
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 响应模型转换
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from py_lmobile_toolkit.sms.utils import build_success_instance
|
|
212
|
+
|
|
213
|
+
result = build_success_instance(response) # 转换为 Success 模型
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## 响应模型
|
|
217
|
+
|
|
218
|
+
### Base
|
|
219
|
+
|
|
220
|
+
所有响应的基类:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from py_lmobile_toolkit.sms.responses import Base
|
|
224
|
+
|
|
225
|
+
class Base(BaseModel):
|
|
226
|
+
Result: str # 错误码,succ 表示成功
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Success
|
|
230
|
+
|
|
231
|
+
成功响应模型:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from py_lmobile_toolkit.sms.responses import Success
|
|
235
|
+
|
|
236
|
+
result = Success(Result="succ")
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## 使用示例
|
|
240
|
+
|
|
241
|
+
### 同步发送示例
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from py_lmobile_toolkit.sms import Sms
|
|
245
|
+
|
|
246
|
+
# 初始化客户端
|
|
247
|
+
sms = Sms(
|
|
248
|
+
account_id="your_account",
|
|
249
|
+
password="your_password",
|
|
250
|
+
product_id="your_product"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# 发送单条短信
|
|
254
|
+
response = sms.send(
|
|
255
|
+
phone_nos="13800138000",
|
|
256
|
+
content="您的验证码是:123456"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# 解析响应
|
|
260
|
+
data = response.json()
|
|
261
|
+
print(f"发送结果: {data.get('Result')}")
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 异步发送示例
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
import asyncio
|
|
268
|
+
from py_lmobile_toolkit.sms import Sms
|
|
269
|
+
|
|
270
|
+
async def main():
|
|
271
|
+
# 初始化客户端
|
|
272
|
+
sms = Sms(
|
|
273
|
+
account_id="your_account",
|
|
274
|
+
password="your_password",
|
|
275
|
+
product_id="your_product"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# 批量发送短信
|
|
279
|
+
response = await sms.async_send(
|
|
280
|
+
phone_nos=["13800138000", "13900139000", "13700137000"],
|
|
281
|
+
content="【通知】您的订单已发货"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# 解析响应
|
|
285
|
+
data = response.json()
|
|
286
|
+
print(f"发送结果: {data.get('Result')}")
|
|
287
|
+
|
|
288
|
+
asyncio.run(main())
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 签名验证示例
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from py_lmobile_toolkit.sms.utils import sha256_signature, timestamp, random_digits
|
|
295
|
+
|
|
296
|
+
# 生成签名所需参数
|
|
297
|
+
ts = timestamp()
|
|
298
|
+
digits = random_digits()
|
|
299
|
+
|
|
300
|
+
# 生成签名
|
|
301
|
+
signature = sha256_signature(
|
|
302
|
+
account_id="your_account",
|
|
303
|
+
password="your_password",
|
|
304
|
+
phone_nos="13800138000",
|
|
305
|
+
random_digits=digits,
|
|
306
|
+
timestamp=ts,
|
|
307
|
+
smms_encrypt_key="SMmsEncryptKey"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
print(f"生成的签名: {signature}")
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 签名生成算法
|
|
314
|
+
|
|
315
|
+
签名生成遵循以下步骤:
|
|
316
|
+
|
|
317
|
+
1. **密码 MD5 加密**:`MD5(password + smms_encrypt_key)`,结果转大写
|
|
318
|
+
2. **参数拼接**(按顺序):
|
|
319
|
+
```
|
|
320
|
+
AccountId={account_id}&PhoneNos={first_phone}&Password={md5_password}&Random={random}&Timestamp={timestamp}
|
|
321
|
+
```
|
|
322
|
+
3. **SHA256 加密**:对拼接后的字符串进行 SHA256 哈希
|
|
323
|
+
|
|
324
|
+
## 许可证
|
|
325
|
+
|
|
326
|
+
MIT License
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
py_lmobile_toolkit/__init__.py,sha256=BMXppKx35yJaI1CLgvFtEotzOEheeZmTAwSNTTIpOfE,813
|
|
2
|
+
py_lmobile_toolkit/sms/__init__.py,sha256=ObWMMFmjMcXjuhlxR-jB5SZ9fTf4FCWWc2U4WucZS18,8845
|
|
3
|
+
py_lmobile_toolkit/sms/responses.py,sha256=3FR5JuJcq7KGZ3074P3BgZYO14TfPXYfkjsXHgJqask,2203
|
|
4
|
+
py_lmobile_toolkit/sms/utils.py,sha256=-tYocYe_AUH-hdg2cuDHGDnC-kaXCZboPx4lsgrdHFI,8975
|
|
5
|
+
py_lmobile_toolkit-1.0.0.dist-info/licenses/LICENSE,sha256=HazFJNaQP3rbY1dvg3hAmO-FZ6v7DwM0YHwXvWsGG3A,1063
|
|
6
|
+
py_lmobile_toolkit-1.0.0.dist-info/METADATA,sha256=QyPZFtNTBH2QnAmoC83CNN3gf4KBl1oEDFqySG7UAp0,8268
|
|
7
|
+
py_lmobile_toolkit-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
8
|
+
py_lmobile_toolkit-1.0.0.dist-info/top_level.txt,sha256=cOZV6kg2o6nx4OrH7Qs3zP_Svy1zijgBt0xN9RuBb3A,19
|
|
9
|
+
py_lmobile_toolkit-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 郭磊
|
|
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 @@
|
|
|
1
|
+
py_lmobile_toolkit
|