raytoolsbox 1.1.0__py3-none-any.whl → 1.1.2__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.
@@ -1,5 +1,10 @@
1
+ """
2
+ RayToolsbox 安全模块
3
+ 包含加密管理器、PIN管理器和身份验证器
4
+ """
5
+
1
6
  from .crypto_manager import CryptoManager
2
7
  from .pin_manager import PinManager
3
8
  from .authenticator import Authenticator
4
9
 
5
- __all__ = ["CryptoManager", "PinManager", "Authenticator"]
10
+ __all__ = ["CryptoManager", "PinManager", "Authenticator"]
@@ -1,7 +1,8 @@
1
1
  import time,json,os,stat,keyring,pyotp,qrcode
2
2
  class Authenticator:
3
3
  """
4
- 基于 RFC 6238 的 TOTP 验证器。
4
+ 基于 RFC 6238 的 TOTP 验证器
5
+
5
6
  功能:
6
7
  - 生成及验证 TOTP(基于时间的一次性验证码)
7
8
  - 使用系统安全存储(keyring)保存密钥,文件存储作为兜底
@@ -9,15 +10,17 @@ class Authenticator:
9
10
  - 支持终端展示绑定二维码(ASCII)
10
11
 
11
12
  说明:
12
- TOTP 本质上需要一个“secret 秘钥”来生成验证码。
13
+ TOTP 本质上需要一个"secret 秘钥"来生成验证码。
13
14
  此类负责安全地保存 secret,并提供验证与绑定机制。
14
15
  """
15
16
 
16
- def __init__(self, service_name: str="", account: str = ""):
17
+ def __init__(self, service_name: str = "", account: str = ""):
17
18
  """
18
- 初始化 Authenticator 对象。
19
- - service_name:你的应用名称,如 "testApp"
20
- - account:区分不同账户的标识(多个账号时很重要)
19
+ 初始化 Authenticator 对象
20
+
21
+ 参数:
22
+ service_name: 应用名称,如 "testApp"
23
+ account: 区分不同账户的标识(多个账号时很重要)
21
24
  """
22
25
  self.service_name = service_name
23
26
  self.account = account
@@ -31,34 +34,39 @@ class Authenticator:
31
34
  # 临时 secret(仅在 get_secret → verify_and_bind 期间使用)
32
35
  self._pending_secret = None
33
36
 
34
-
35
37
  try:
36
38
  self._load_secret()
37
39
  self._if_bind = True
38
40
  print("✅ 已存在绑定的 Authenticator。")
39
-
40
41
  except RuntimeError:
41
42
  self._if_bind = False
42
43
  print("🔐 未绑定,请使用 .get_secret()初始化...")
43
44
 
44
45
  def exist(self):
46
+ """
47
+ 检查是否已绑定 Authenticator
48
+
49
+ 返回:
50
+ bool: True 表示已绑定,False 表示未绑定
51
+ """
45
52
  return self._if_bind
53
+
46
54
  # ============================================================
47
55
  # 绑定 / 初始化(不立即保存)
48
56
  # ============================================================
49
57
  def get_secret(self):
50
58
  """
51
- 生成新的 TOTP secret,但不立即永久保存。
52
-
59
+ 生成新的 TOTP secret,但不立即永久保存
60
+
53
61
  功能:
54
- - 账户尚未绑定时,调用此方法会生成一个新的临时密钥(secret)。
55
- 需配合 verify() 输入首次验证码验证成功后永久保存。
56
-
62
+ - 账户尚未绑定时,调用此方法会生成一个新的临时密钥(secret
63
+ - 需配合 verify() 输入首次验证码验证成功后永久保存
64
+
57
65
  返回:
58
- - dict: 包含以下键值:
59
- - "uri"TOTP 绑定用的 otpauth URI,可用于生成二维码。
60
- - "secret":此次生成的临时密钥。
61
- 如果当前账户已绑定,则返回错误说明字符串。
66
+ dict: 包含以下键值:
67
+ - "uri": TOTP 绑定用的 otpauth URI,可用于生成二维码
68
+ - "secret": 此次生成的临时密钥
69
+ 如果当前账户已绑定,则返回错误说明字符串
62
70
  """
63
71
  if not self._if_bind:
64
72
  # 随机生成 TOTP 的 Base32 秘钥
@@ -90,21 +98,19 @@ class Authenticator:
90
98
 
91
99
  return {"uri": uri, "secret": self._pending_secret}
92
100
  else:
93
- msg="已存在对应账户和应用的Authenticator,请先删除"
101
+ msg = "已存在对应账户和应用的Authenticator,请先删除"
94
102
  print(msg)
95
103
  return msg
96
-
104
+
97
105
  # ============================================================
98
106
  # 删除现有绑定(解绑)
99
107
  # ============================================================
100
-
101
108
  def delete(self) -> bool:
102
109
  """
103
- 删除当前账户的 TOTP 绑定。
104
-
110
+ 删除当前账户的 TOTP 绑定
111
+
105
112
  返回:
106
- - True:至少有一个存储被删除
107
- - False:两个地方都没有数据(或删除失败)
113
+ bool: True 表示至少有一个存储被删除,False 表示两个地方都没有数据(或删除失败)
108
114
  """
109
115
  removed = False
110
116
  try:
@@ -123,26 +129,23 @@ class Authenticator:
123
129
  pass
124
130
  self._if_bind = False
125
131
  return removed
126
-
132
+
127
133
  # ============================================================
128
134
  # 验证验证码
129
135
  # ============================================================
130
-
131
136
  def verify(self, code: str, window: int = 1) -> bool:
132
137
  """
133
- 验证已绑定账户的 TOTP 验证码。
138
+ 验证已绑定账户的 TOTP 验证码
134
139
 
135
140
  参数:
136
- - code: 用户输入的 6 位数字字符串
137
- - window: 时间容差(默认 1)
138
- window=0:只接受当前30秒(最严格)
139
- window=1:接受前/当前/后 共 90 秒(推荐,防手机时间误差)
140
-
141
+ code: 用户输入的 6 位数字字符串
142
+ window: 时间容差(默认 1)
143
+ window=0:只接受当前30秒(最严格)
144
+ window=1:接受前/当前/后 共 90 秒(推荐,防手机时间误差)
145
+
141
146
  返回:
142
- - True 验证成功
143
- - False 验证失败
147
+ bool: True 表示验证成功,False 表示验证失败
144
148
  """
145
-
146
149
  if self._if_bind:
147
150
  totp = pyotp.TOTP(self._load_secret()) # 根据存储密钥,创建 TOTP 验证器
148
151
  return totp.verify(code, valid_window=window) # 验证输入的验证码
@@ -178,11 +181,15 @@ class Authenticator:
178
181
  # ============================================================
179
182
  # 存储层
180
183
  # ============================================================
181
-
182
184
  def _save_keyring(self, data: dict) -> bool:
183
185
  """
184
186
  尝试将 secret 保存到系统安全存储(keyring)
185
- 写入失败返回 False。
187
+
188
+ 参数:
189
+ data: 包含 secret 等信息的数据字典
190
+
191
+ 返回:
192
+ bool: True 表示保存成功,False 表示保存失败
186
193
  """
187
194
  try:
188
195
  keyring.set_password(self.service_name, self._key, json.dumps(data))
@@ -192,10 +199,16 @@ class Authenticator:
192
199
 
193
200
  def _load_secret(self) -> str:
194
201
  """
195
- 加载已绑定的 secret
196
- 优先从 keyring 获取。
197
- 如果 keyring 不可用,则回退读取文件。
198
- 如果都不存在 → 表示尚未绑定。
202
+ 加载已绑定的 secret
203
+
204
+ 优先从 keyring 获取,如果 keyring 不可用,则回退读取文件。
205
+ 如果都不存在则表示尚未绑定。
206
+
207
+ 返回:
208
+ str: Base32 编码的 secret
209
+
210
+ 异常:
211
+ RuntimeError: 如果 Authenticator 尚未绑定
199
212
  """
200
213
  # 尝试从 keyring 获取
201
214
  try:
@@ -214,8 +227,13 @@ class Authenticator:
214
227
 
215
228
  def _save_file(self, data: dict):
216
229
  """
217
- 将 secret 写入文件(兜底方案)。
218
- 写入后将文件权限设置为 600(仅当前用户可读写)。
230
+ 将 secret 写入文件(兜底方案)
231
+
232
+ 参数:
233
+ data: 包含 secret 等信息的数据字典
234
+
235
+ 说明:
236
+ 写入后将文件权限设置为 600(仅当前用户可读写)
219
237
  """
220
238
  with open(self._file_path, "w") as f:
221
239
  json.dump(data, f)
@@ -11,23 +11,32 @@ from pathlib import Path
11
11
 
12
12
  class CryptoManager:
13
13
  """
14
- 现代化 ECC 加密管理器(无签名版)
15
-
14
+ 现代化 ECC 加密管理器(无签名版)
15
+
16
+ 功能:
16
17
  - 使用 X25519 进行密钥交换
17
18
  - 使用 ChaCha20-Poly1305 进行对称加密
18
- - 原始数据支持 str / bytes / 文件,加密或签名后返回二进制数据包
19
+ - 支持文本、字节和文件的加密解密
20
+ - 支持数字签名和验签功能(Ed25519)
19
21
  """
22
+
23
+ # 常量定义
20
24
  _SIGN_LEN = 64 # Ed25519 固定长度
21
- # 数据类型信息
22
- _TYPE_TEXT = b"\x01"
23
- _TYPE_BYTES = b"\x02"
24
- # 二进制格式数据包指纹
25
- _MAGIC = b"ECC1"
26
- _VERSION = b"\x01"
27
- _SIGN_MAGIC = b"SIGN"
28
- _SIGN_VERSION = b"\x01"
25
+ _TYPE_TEXT = b"\x01" # 文本数据标识
26
+ _TYPE_BYTES = b"\x02" # 字节数据标识
27
+ _MAGIC = b"ECC1" # 二进制格式数据包指纹
28
+ _VERSION = b"\x01" # 版本标识
29
+ _SIGN_MAGIC = b"SIGN" # 签名数据包标识
30
+ _SIGN_VERSION = b"\x01" # 签名版本标识
29
31
 
30
32
  def __init__(self, key_dir=None, PIN="toolsbox"):
33
+ """
34
+ 初始化加密管理器
35
+
36
+ 参数:
37
+ key_dir: 密钥目录路径(可选)
38
+ PIN: 密钥保护密码,默认为"toolsbox"
39
+ """
31
40
  self._PIN = PIN
32
41
  self._key_dir = None
33
42
 
@@ -36,7 +45,10 @@ class CryptoManager:
36
45
 
37
46
  def set_key_dir(self, key_dir):
38
47
  """
39
- 切换 / 设置密钥目录
48
+ 切换/设置密钥目录
49
+
50
+ 参数:
51
+ key_dir: 密钥目录路径
40
52
  """
41
53
  self._key_dir = Path(key_dir)
42
54
  self._key_dir.mkdir(parents=True, exist_ok=True)
@@ -50,8 +62,16 @@ class CryptoManager:
50
62
  # 密钥生成 / 保存 / 加载
51
63
  # -----------------------------------------
52
64
  def generate_keys(self):
53
-
54
- """ X25519与Ed25519公私钥对生成 """
65
+ """
66
+ 生成 X25519 Ed25519 公私钥对
67
+
68
+ 返回:
69
+ tuple: (crypt_key, crypt_pub, sign_key, sign_pub)
70
+ - crypt_key: X25519 私钥
71
+ - crypt_pub: X25519 公钥
72
+ - sign_key: Ed25519 私钥
73
+ - sign_pub: Ed25519 公钥
74
+ """
55
75
  crypt_key = x25519.X25519PrivateKey.generate()
56
76
  crypt_pub = crypt_key.public_key()
57
77
  # Ed25519公私钥对生成
@@ -63,8 +83,15 @@ class CryptoManager:
63
83
 
64
84
  # -----------------------------------------
65
85
  def _save_keypair(self, crypt_key, crypt_pub, sign_key, sign_pub):
66
- """保存密钥对为 PEM 文件"""
67
-
86
+ """
87
+ 保存密钥对为 PEM 文件
88
+
89
+ 参数:
90
+ crypt_key: X25519 私钥
91
+ crypt_pub: X25519 公钥
92
+ sign_key: Ed25519 私钥
93
+ sign_pub: Ed25519 公钥
94
+ """
68
95
  pin = self._PIN
69
96
  enc = (
70
97
  serialization.BestAvailableEncryption(pin.encode())
@@ -95,7 +122,12 @@ class CryptoManager:
95
122
 
96
123
  # -----------------------------------------
97
124
  def _load_keypair(self):
98
- """加载私钥和公钥"""
125
+ """
126
+ 加载私钥和公钥
127
+
128
+ 返回:
129
+ tuple: (crypt_key, crypt_pub, sign_key, sign_pub) 或 (None, None, None, None)
130
+ """
99
131
  if not all(p.exists() for p in (
100
132
  self._crypt_key_path,
101
133
  self._crypt_pub_path,
@@ -125,16 +157,21 @@ class CryptoManager:
125
157
  # text → 加密 → dict
126
158
  # ============================================
127
159
 
128
- def encrypt(self, data=None, pubkey=None,data_path=None,save="off") -> bytes:
160
+ def encrypt(self, data=None, pubkey=None, data_path=None, save="off") -> bytes:
129
161
  """
162
+ 使用 X25519 公钥加密数据
163
+
130
164
  参数:
131
- data: str | bytes | data_path
132
- pubkey: 公钥(X25519
133
- data_path: 数据文件路径(可选)
134
- save: 为空时不保存。否则保存到data_path同名.enc文件或者指定文件路径
135
-
165
+ data: 要加密的数据(str bytes
166
+ pubkey: X25519 公钥对象
167
+ data_path: 数据文件路径(可选,如果提供则从文件读取数据)
168
+ save: 保存选项
169
+ - "off": 不保存(默认)
170
+ - "on": 保存到 data_path 同名.enc 文件
171
+ - 其他字符串:保存到指定路径
172
+
136
173
  返回:
137
- bytes: 二进制数据包
174
+ bytes: 加密后的二进制数据包
138
175
  """
139
176
 
140
177
  if pubkey is None and self._key_dir is not None:
@@ -218,13 +255,19 @@ class CryptoManager:
218
255
  # 解密流程:ECC → AEAD → text
219
256
  def decrypt(self, packet: bytes = None, privkey=None, enc_data_path=None, save="off"):
220
257
  """
258
+ 使用 X25519 私钥解密数据
259
+
221
260
  参数:
222
- packet (bytes): get_encrypt() 返回的数据
223
- privkey: 私钥(X25519
224
- enc_data_path: .enc 文件路径(可选)
225
- save: 保存文件名(不含后缀);若 enc_data_path 存在则自动去掉 .enc
261
+ packet: 加密后的数据包(bytes
262
+ privkey: X25519 私钥对象
263
+ enc_data_path: 加密文件路径(可选,如果提供则从文件读取数据包)
264
+ save: 保存选项
265
+ - "off": 不保存(默认)
266
+ - "on": 保存到 enc_data_path 同名文件(去掉.enc后缀)
267
+ - 其他字符串:保存到指定路径
268
+
226
269
  返回:
227
- str | bytes
270
+ str | bytes: 解密后的原始数据
228
271
  """
229
272
  if privkey is None and self._key_dir is not None:
230
273
  privkey, _, _, _ = self._load_keypair()
@@ -326,20 +369,19 @@ class CryptoManager:
326
369
  # ============================================
327
370
  def sign(self, data=None, sign_key=None, data_path=None, save="off"):
328
371
  """
329
- 使用 Ed25519 对数据进行数字签名,并生成自描述的签名数据包。
330
-
372
+ 使用 Ed25519 对数据进行数字签名
373
+
331
374
  参数:
332
- data (str | bytes | None): 待签名的数据内容。
333
-
334
- sign_key (ed25519.Ed25519PrivateKey | None): 可选密钥
335
-
336
- data_path (str | Path | None): 可选待签名文件的路径(覆盖data)
337
-
338
- save (str): 控制是否将签名数据包保存为文件。"off"(默认)
339
-
375
+ data: 要签名的数据(str bytes
376
+ sign_key: Ed25519 私钥对象
377
+ data_path: 数据文件路径(可选,如果提供则从文件读取数据)
378
+ save: 保存选项
379
+ - "off": 不保存(默认)
380
+ - "on": 保存到 data_path 同名.signed 文件
381
+ - 其他字符串:保存到指定路径
382
+
340
383
  返回:
341
- bytes: 签名数据包(二进制格式),结构如下:
342
-
384
+ bytes: 签名数据包(二进制格式)
343
385
  """
344
386
  if sign_key is None and self._key_dir is not None:
345
387
  _, _, sign_key, _ = self._load_keypair()
@@ -404,18 +446,18 @@ class CryptoManager:
404
446
  def verify(self, signed_data=None, verify_key=None, signed_data_path=None, save="off"):
405
447
  """
406
448
  验证 Ed25519 签名
407
-
408
- 参数:
409
- signed_data:
410
- - bytes : 签名数据包
411
- - str (base64) : 签名数据包
412
- signed_data_path:
413
- - None : 从签名包验签
414
- - 文件路径(str) : 对指定文件验签
415
-
449
+
450
+ 参数:
451
+ signed_data: 签名数据包(bytes 或 base64 编码的 str)
452
+ verify_key: Ed25519 公钥对象
453
+ signed_data_path: 签名文件路径(可选,如果提供则从文件读取签名数据包)
454
+ save: 保存选项
455
+ - "off": 不保存(默认)
456
+ - "on": 保存到 signed_data_path 同名.verify 文件
457
+ - 其他字符串:保存到指定路径
458
+
416
459
  返回:
417
- - str : 验签成功并还原文本数据
418
- - None : 验签失败
460
+ str | None: 验签成功返回原始数据,失败返回 None
419
461
  """
420
462
  if verify_key is None and self._key_dir is not None:
421
463
  _, _, _, verify_key = self._load_keypair()
@@ -3,19 +3,37 @@ import json
3
3
 
4
4
  class PinManager:
5
5
  """
6
- 只负责 PIN 的生成、验证、修改。
7
- 使用操作系统安全存储 (keyring) 保存哈希,提高安全性。
6
+ PIN 管理器
7
+
8
+ 功能:
9
+ - 生成和验证 PIN 码
10
+ - 使用操作系统安全存储 (keyring) 保存哈希
11
+ - 支持修改 PIN 码
8
12
  """
9
13
 
10
14
  def __init__(self, service_name):
11
- # service_name 用来区分你的程序名称
15
+ """
16
+ 初始化 PIN 管理器
17
+
18
+ 参数:
19
+ service_name: 服务名称,用于区分不同的应用程序
20
+ """
12
21
  self.service_name = service_name
13
22
 
14
23
  # ============================================================
15
24
  # PIN 生成 / 保存
16
25
  # ============================================================
17
26
  def set_pin(self, pin: str, iterations: int = 200_000):
18
- """生成新的 PIN 哈希并保存到系统安全存储"""
27
+ """
28
+ 生成新的 PIN 哈希并保存到系统安全存储
29
+
30
+ 参数:
31
+ pin: 要设置的 PIN 码
32
+ iterations: PBKDF2 迭代次数,默认 200,000
33
+
34
+ 返回:
35
+ dict: 包含 salt、hash 和 iterations 的数据字典
36
+ """
19
37
  salt = os.urandom(16)
20
38
  hash_bytes = hashlib.pbkdf2_hmac(
21
39
  "sha256", pin.encode("utf-8"), salt, iterations
@@ -34,8 +52,16 @@ class PinManager:
34
52
  # ============================================================
35
53
  # PIN 验证
36
54
  # ============================================================
37
- def verify_pin(self, pin: str)-> str:
38
- """从系统安全存储读取哈希验证输入的 PIN"""
55
+ def verify_pin(self, pin: str) -> str:
56
+ """
57
+ 从系统安全存储读取哈希验证输入的 PIN
58
+
59
+ 参数:
60
+ pin: 要验证的 PIN 码
61
+
62
+ 返回:
63
+ str: "OK" 表示验证成功,"FAIL" 表示验证失败,"not_set" 表示未设置 PIN
64
+ """
39
65
  stored_json = keyring.get_password(self.service_name, "pin_data")
40
66
  if not stored_json:
41
67
  print("没有检测到保存的 PIN,已经使用默认 PIN。")
@@ -56,7 +82,16 @@ class PinManager:
56
82
  # 修改 PIN
57
83
  # ============================================================
58
84
  def change_pin(self, old_pin: str, new_pin: str):
59
- """验证旧 PIN 并更新新的 PIN"""
85
+ """
86
+ 验证旧 PIN 并更新新的 PIN
87
+
88
+ 参数:
89
+ old_pin: 旧的 PIN 码
90
+ new_pin: 新的 PIN 码
91
+
92
+ 返回:
93
+ dict: 新 PIN 的数据字典,如果旧 PIN 验证失败则返回 None
94
+ """
60
95
  if self.verify_pin(old_pin) == "OK":
61
96
  return self.set_pin(new_pin)
62
97
 
@@ -1,2 +1,9 @@
1
- # from .useful_func import get_resource_path, center_window, make_timer, list_github_repos
2
- # __all__ = ["get_resource_path", "center_window", "make_timer", "list_github_repos"]
1
+ """
2
+ RayToolsbox 工具模块
3
+ 包含常用的辅助函数
4
+ """
5
+
6
+ from .useful_func import get_resource_path, center_window, make_timer, list_github_repos
7
+ from .mailToPhone import send_email
8
+
9
+ __all__ = ["get_resource_path", "center_window", "make_timer", "list_github_repos", "send_email"]
@@ -32,17 +32,23 @@ def _load_email_config(provider: str)-> bool:
32
32
 
33
33
 
34
34
  def send_email(
35
- subject: str='HelloWorld',
36
- content: str='这是一个测试邮件',
37
- to_addr: str='',
38
- serverName: str='',
39
- use_smtp:str='qq') -> None:
35
+ subject: str = 'HelloWorld',
36
+ content: str = '这是一个测试邮件',
37
+ to_addr: str = '',
38
+ serverName: str = '',
39
+ use_smtp: str = 'qq') -> bool:
40
40
  """
41
- :param subject: 邮件主题
42
- :param content: 邮件内容
43
- :param to_addr: 接收邮箱地址
44
- :param serverName: 服务器名称
45
- :param use_smtp: 使用的SMTP服务提供商
41
+ 发送一封电子邮件(支持 HTML 内容)
42
+
43
+ 参数:
44
+ subject: 邮件主题,默认 'HelloWorld'
45
+ content: 邮件正文内容(HTML 格式),默认 '这是一个测试邮件'
46
+ to_addr: 收件人邮箱地址,若为空则默认发送给发件人自己
47
+ serverName: 发件人昵称(显示在邮件"发件人"处)
48
+ use_smtp: SMTP 服务类型标识,用于加载对应配置,例如:'qq'、'gmail' 等
49
+
50
+ 返回:
51
+ bool: True 表示邮件发送成功,False 表示邮件发送失败(配置加载失败或发送异常)
46
52
  """
47
53
  try:
48
54
  config = _load_email_config(use_smtp)
@@ -1,14 +1,12 @@
1
1
  """
2
- ===============================================
3
- 📦 raytoolsbox.utils.common_tools
4
- 常用辅助函数模块
5
- -----------------------------------------------
2
+ RayToolsbox 工具模块
3
+ 包含常用的辅助函数
4
+
6
5
  功能包含:
7
6
  - 获取资源路径(兼容 PyInstaller)
8
7
  - Tk 窗口居中显示
9
8
  - 简易性能计时器 make_timer()
10
9
  - 获取指定 GitHub 用户的仓库列表
11
- ===============================================
12
10
  """
13
11
 
14
12
  import os,sys,time,requests
@@ -18,16 +16,15 @@ from pathlib import Path
18
16
  # ============================================================
19
17
  def make_timer():
20
18
  """
21
- 创建一个简单的性能计时函数。
22
-
19
+ 创建一个简单的性能计时函数
20
+
23
21
  功能:
24
- - 返回一个内部函数,可多次调用自动计算时间间隔。
25
- - 输出调用次数与耗时(毫秒)。
26
-
22
+ - 返回一个内部函数,可多次调用自动计算时间间隔
23
+ - 输出调用次数与耗时(毫秒)
24
+
27
25
  返回:
28
- - callable: 内部计时函数 tt(msg)
29
- 调用格式:
30
- tt("说明文字") → 打印上次调用到当前的耗时。
26
+ callable: 内部计时函数 tt(msg)
27
+ 调用格式:tt("说明文字") → 打印上次调用到当前的耗时
31
28
  """
32
29
  last_time = None
33
30
  count = -1
@@ -52,43 +49,51 @@ def make_timer():
52
49
 
53
50
  return tt
54
51
 
55
-
56
52
  # ============================================================
57
53
  # 路径工具函数
58
54
  # ============================================================
59
55
  def get_resource_path(relpath: str) -> str:
60
56
  """
61
- 修正资源路径(兼容 PyInstaller 打包运行环境)。
62
-
63
- 功能:
64
- - 判断程序是否由 PyInstaller 打包。
65
- - 自动切换资源访问路径:打包环境使用 sys._MEIPASS。
66
-
57
+ 获取应用资源的绝对路径(仅应用资源)
58
+
59
+ 规则:
60
+ - PyInstaller 打包环境:使用 sys._MEIPASS
61
+ - 普通运行环境:以应用入口脚本(__main__.__file__)所在目录为基准
62
+
67
63
  参数:
68
- - relpath (str): 相对路径字符串。
69
-
64
+ relpath: 相对路径
65
+
70
66
  返回:
71
- - str: 绝对资源路径。
67
+ str: 绝对路径
72
68
  """
69
+ # PyInstaller 打包环境
73
70
  if hasattr(sys, "_MEIPASS"):
74
71
  return os.path.join(sys._MEIPASS, relpath)
75
- return os.path.join(os.path.dirname(__file__), relpath)
76
72
 
73
+ # 普通 Python 运行环境:应用入口脚本
74
+ try:
75
+ import __main__
76
+ base_path = os.path.dirname(os.path.abspath(__main__.__file__))
77
+ except Exception:
78
+ # 兜底:交互式 / 特殊环境(REPL、某些 IDE)
79
+ base_path = os.getcwd()
80
+
81
+ return os.path.join(base_path, relpath)
77
82
 
78
83
  # ============================================================
79
84
  # Tk 窗口居中
80
85
  # ============================================================
81
86
  def center_window(win, width: int | None = None, height: int | None = None):
82
87
  """
83
- 将 Tkinter 窗口居中显示。
84
-
88
+ 将 Tkinter 窗口居中显示
89
+
85
90
  参数:
86
- - win: Tkinter 窗口或 Toplevel 对象。
87
- - width: 目标宽度(可选,默认当前窗口宽度)。
88
- - height: 目标高度(可选,默认当前窗口高度)。
89
-
91
+ win: Tkinter 窗口或 Toplevel 对象
92
+ width: 目标宽度(可选,默认当前窗口宽度)
93
+ height: 目标高度(可选,默认当前窗口高度)
94
+
90
95
  返回:
91
- - None(直接更新窗口位置)
96
+ None(直接更新窗口位置)
92
97
  """
93
98
  win.update_idletasks()
94
99
  w = width or win.winfo_width()
@@ -99,33 +104,31 @@ def center_window(win, width: int | None = None, height: int | None = None):
99
104
  y = (sh - h) // 3
100
105
  win.geometry(f"{w}x{h}+{x}+{y}")
101
106
 
102
-
103
-
104
107
  # ============================================================
105
108
  # GitHub API 工具
106
109
  # ============================================================
107
110
  def list_github_repos(username: str, token: str | None = None, print_result: bool = True):
108
111
  """
109
- 获取指定 GitHub 用户的仓库列表。
110
-
112
+ 获取指定 GitHub 用户的仓库列表
113
+
111
114
  功能:
112
- - 支持传入个人访问 Token(可访问私有仓库)。
113
- - 支持分页抓取所有仓库(最大 100 每页)。
114
-
115
+ - 支持传入个人访问 Token(可访问私有仓库)
116
+ - 支持分页抓取所有仓库(最大 100 每页)
117
+
115
118
  参数:
116
- - username: GitHub 用户名。
117
- - token: 个人访问令牌(str 或 None)。
118
- 如果 token 不为空且用户名为自己,则访问 /user/repos
119
- - print_result: 是否打印获取结果(默认 True)。
120
-
119
+ username: GitHub 用户名
120
+ token: 个人访问令牌(str 或 None
121
+ 如果 token 不为空且用户名为自己,则访问 /user/repos
122
+ print_result: 是否打印获取结果(默认 True
123
+
121
124
  返回:
122
- - list[dict]: 仓库列表,每个元素结构:
123
- {
124
- "name": 仓库名称,
125
- "size_mb": 仓库大小(MB),
126
- "private": 是否私有仓库 (bool)
127
- }
128
- 如果 Token 无效返回 None
125
+ list[dict]: 仓库列表,每个元素结构:
126
+ {
127
+ "name": 仓库名称,
128
+ "size_mb": 仓库大小(MB),
129
+ "private": 是否私有仓库 (bool)
130
+ }
131
+ 如果 Token 无效返回 None
129
132
  """
130
133
  # 选择 API URL
131
134
  if token and username:
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: raytoolsbox
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Add your description here
5
5
  Author: TIMLES@https://github.com/TIMLES
6
6
  License: MIT
7
- Requires-Python: >=3.13
7
+ Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  Requires-Dist: cryptography>=46.0.4
10
10
  Requires-Dist: keyring>=25.7.0
@@ -0,0 +1,12 @@
1
+ raytoolsbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ raytoolsbox/security/__init__.py,sha256=awenOLOReXurtyQCIJCzZirwE5Q_4pEfQkvrioNSGJQ,278
3
+ raytoolsbox/security/authenticator.py,sha256=tZp2q72taAjrojigFt6uc5C01U5_zYX2MCCjrCjUiZY,9569
4
+ raytoolsbox/security/crypto_manager.py,sha256=7mKS_mhehlk0WUMYYI6M6ci1GE2TnADzsFtT5W2DWi0,20260
5
+ raytoolsbox/security/pin_manager.py,sha256=UwcsiE-ZfFNIZ0NEGdHEyvz8ozIQtVek7uUFkWv_sHA,3750
6
+ raytoolsbox/utils/__init__.py,sha256=AYk4BqqGVL1ZaGJX2dJJuKTdtfFL4LQMGmoT3v91BSg,295
7
+ raytoolsbox/utils/mailToPhone.py,sha256=C_mN3qJiJM7epbYdp6nY5k6lk3p_ksLJA8o7rcWB3kY,3022
8
+ raytoolsbox/utils/useful_func.py,sha256=__RLYc9d2xx47oG-6IvzTc7LWz_k1O3NMDy1lWJa_wE,6124
9
+ raytoolsbox-1.1.2.dist-info/METADATA,sha256=rhw-ymgZL2f3ih45eeL7HRgtdk6Wo7szral_o6UY_p0,2956
10
+ raytoolsbox-1.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ raytoolsbox-1.1.2.dist-info/top_level.txt,sha256=W3bjrGqlV1zMr5MySZQhI4ZT8K-rs2zXQdSNdLI6JDE,12
12
+ raytoolsbox-1.1.2.dist-info/RECORD,,
raytoolsbox/to_github.py DELETED
@@ -1,73 +0,0 @@
1
- import requests
2
- # token: str | None = None, 意思是 token 可以是字符串类型,也可以是 None 类型 。默认值是 None 。
3
- def list_github_repos(username: str, token: str | None = None, print_result: bool = True):
4
- """
5
- 获取 GitHub 仓库列表。
6
-
7
- :param username: GitHub 用户名(可以是自己,也可以是别人)
8
- :param token: 不传则访问别人公开仓库;传入则可访问自己的公开+私有
9
- :param print_result: 是否打印
10
- :return: 仓库列表 [{name, size_mb, private}, ...]
11
- """
12
- # 选择 API URL
13
- if token and username:
14
- # token 一般只能访问“当前登录用户”
15
- # 所以如果 username == token中的用户 → /user/repos
16
- api_url = "https://api.github.com/user/repos"
17
- use_auth = True
18
- else:
19
- # 无 token → 访问公开仓库
20
- api_url = f"https://api.github.com/users/{username}/repos"
21
- use_auth = False
22
-
23
- headers = {"Accept": "application/vnd.github+json"}
24
- if token:
25
- headers["Authorization"] = f"token {token}"
26
-
27
- repos = []
28
- page = 1
29
-
30
- while True:
31
- resp = requests.get(api_url, headers=headers, params={"page": page, "per_page": 100})
32
-
33
- # ========== 处理错误 Token ==========
34
- if resp.status_code == 401:
35
- print("❌ GitHub Token 无效(Bad credentials), 请检查:")
36
- print(" 1. Token 是否拼写正确")
37
- print(" 2. Token 是否未过期")
38
- print(" 3. 如果访问别人公开仓库 → 不要传 token")
39
- return None # 或者 raise ValueError("无效 Token")
40
-
41
- # ========== 其它错误 ==========
42
- if resp.status_code != 200:
43
- raise RuntimeError(
44
- f"GitHub API 请求失败:{resp.status_code}\n{resp.text}"
45
- )
46
-
47
- batch = resp.json()
48
- if not batch:
49
- break
50
-
51
- for r in batch:
52
- repos.append({
53
- "name": r["name"],
54
- "size_mb": r["size"] / 1024,
55
- "private": r["private"],
56
- })
57
-
58
- page += 1
59
-
60
- # 如果是 /user/repos,但 username 不是 token 的用户 → 会只返回公开仓库(合理情况)
61
-
62
- # 打印结果
63
- if print_result:
64
- for r in repos:
65
- print(f"{r['name']:25} {r['size_mb']:7.2f} MB {'private' if r['private'] else 'public'}")
66
-
67
- return repos
68
-
69
- if __name__ == "__main__":
70
-
71
- user = "2dust"
72
- data = list_github_repos(user)
73
- # print(data)
@@ -1,13 +0,0 @@
1
- raytoolsbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- raytoolsbox/to_github.py,sha256=gvOg_ltE2dXQQ2hhpoa0A4Eth-h36NYl0qLRlizxnRM,2602
3
- raytoolsbox/security/__init__.py,sha256=2S4Eld0loDwKle0gW83byxu6KH8Zk9SfpwH9TiAsecs,182
4
- raytoolsbox/security/authenticator.py,sha256=POk11M4MR5nhs9ungVyGsIC1laP225IUF3sCQv14Tss,8944
5
- raytoolsbox/security/crypto_manager.py,sha256=6LMlfd9PrtvFMaW3x3hQ3ddGZU4v4myLvNbCJK7pqOE,18585
6
- raytoolsbox/security/pin_manager.py,sha256=B0Iuu8oY9a_9Cr5QhE9jr9h92f0VnMe8mciXPfQb5kU,2905
7
- raytoolsbox/utils/__init__.py,sha256=sncYTfqEkIk_cUDRyrle0rTOt_h7LOaJQJI38rmlaQ4,177
8
- raytoolsbox/utils/mailToPhone.py,sha256=JhVRMhPlzrQ-B3-Sf2YVNsfa3N1LZSlOSoYJnd4rbNo,2600
9
- raytoolsbox/utils/useful_func.py,sha256=-KzFo5_nXTzeh-KbCBGCzva8EeerK0nuyTAP-yflW4E,5958
10
- raytoolsbox-1.1.0.dist-info/METADATA,sha256=vg2AHFtN9GXDKHR0_hecTo9fqtKZvn696Og3Q6BRnZ4,2956
11
- raytoolsbox-1.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
- raytoolsbox-1.1.0.dist-info/top_level.txt,sha256=W3bjrGqlV1zMr5MySZQhI4ZT8K-rs2zXQdSNdLI6JDE,12
13
- raytoolsbox-1.1.0.dist-info/RECORD,,