feat: RSA login encryption + confirmed wechat type codes

Replace the Phase-0 placeholder password encryption with real RSA
(PKCS#1 v1.5). encrypt_password now builds an RSA public key from the
getSecureKey modulus/exponent (RSAPublicNumbers, e=int(hex,16),
n=int(hex,16)), encrypts the UTF-8 password, and returns hex (documented
assumption -- base64 is the one-line fallback noted in the docstring).
get_secure_key now returns the inner data dict to match the new contract;
pc_login keeps {account, password} with a comment on the username/password
and account/pwd fallbacks if pcLogin rejects.

Extend normalize_wechat_type with the Phase-0 confirmed codes:
2 -> image, 3 -> image_legacy, 4 -> video, 10 -> system (1 -> text and
-2 -> divider unchanged; 6/9/13/15/21/22 stay unknown until confirmed).

Tests:
- encrypt_password round-trip: generate an RSA keypair, build secure_key
  from its public numbers, encrypt, then decrypt with the private key and
  assert the original password comes back. Pins 512-hex output for a
  2048-bit key. No network.
- Parser parametrize table extended for the new codes (and a downstream
  message-fixture assertion updated from unknown -> image_legacy).

Suite: 87 -> 97 passing. ruff check clean.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-01 13:37:42 +08:00
parent 7f19b12e74
commit eebff9f786
7 changed files with 214 additions and 102 deletions
+15 -5
View File
@@ -18,17 +18,27 @@ from app.scraper.parsers import normalize_wechat_type
@pytest.mark.parametrize(
"raw,expected",
[
# Documented codes.
# Phase-0 confirmed codes (see docs/superpowers/specs/phase0-findings.md §2).
(1, "text"),
(-2, "divider"),
# Phase-0 unknown codes — pass through as "unknown", never dropped.
(3, "unknown"), # image
(34, "unknown"), # voice
(-2, "divider"), # date separator — NOT a real message (scraper drops it)
(2, "image"), # image / sticker (webp) — file/fileTh are direct URLs
(3, "image_legacy"), # old image format — file is an opaque ID (needs another endpoint)
(4, "video"), # mp4 — file is a direct URL
(10, "system"), # system / link / card / invite — non-media, skip download
# Still-unconfirmed codes — pass through as "unknown", never dropped.
# (6, 9, 13, 15, 21, 22 — voice/file/transfer/redpacket/quote/etc; raw_json retains the code.)
(6, "unknown"),
(9, "unknown"),
(34, "unknown"), # voice (legacy WeChat code, not yet seen in yunke)
(49, "unknown"), # file / link / transfer
(10000, "unknown"),
# Stringified ints (the upstream API may send either form).
("1", "text"),
("-2", "divider"),
("2", "image"),
("3", "image_legacy"),
("4", "video"),
("10", "system"),
("34", "unknown"),
],
)