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:
@@ -4,12 +4,12 @@ The yunke CRM uses opaque integer type codes (``type`` / ``messageType``) on
|
||||
WeChat message records. The storage layer prefers stable enum strings so that
|
||||
media dispatch (image / voice / video / file) and presentation (text vs.
|
||||
date divider) can be keyed off a single normalized value rather than the
|
||||
raw code, whose exact mapping for non-text codes is a Phase-0 unknown.
|
||||
raw code.
|
||||
|
||||
This module is intentionally tiny: one total function that maps the
|
||||
documented codes and falls through to ``"unknown"`` for everything else.
|
||||
It MUST NOT raise — every record pulled during a scrape passes through here,
|
||||
and one unexpected code must not abort the whole pull. The raw code is
|
||||
Phase-0 confirmed codes and falls through to ``"unknown"`` for everything
|
||||
else. It MUST NOT raise — every record pulled during a scrape passes through
|
||||
here, and one unexpected code must not abort the whole pull. The raw code is
|
||||
preserved on the record's ``raw_json`` so the persistence layer can recover
|
||||
it when needed.
|
||||
"""
|
||||
@@ -20,26 +20,40 @@ from typing import Any
|
||||
|
||||
__all__ = ["normalize_wechat_type"]
|
||||
|
||||
# Documented WeChat message type codes (see Task 1.6 spec).
|
||||
# * ``1`` — plain text (and sticker text). The only non-media type we map
|
||||
# today; everything else falls through to ``"unknown"``.
|
||||
# Phase-0 confirmed WeChat message type codes (see
|
||||
# docs/superpowers/specs/phase0-findings.md §2). Captured by live inspection
|
||||
# of the yunke CRM.
|
||||
# * ``1`` — plain text (and sticker text).
|
||||
# * ``-2`` — date divider. NOT a real message; the scraper uses this label as
|
||||
# its drop signal (filtered out, never yielded, never persisted).
|
||||
# Image / voice / video / file / transfer / redpacket / system codes are
|
||||
# observed in the wild but their exact integer values are a Phase-0 unknown.
|
||||
# When confirmed, extend this table — for now they pass through as
|
||||
# ``"unknown"`` so the message is still ingested rather than dropped.
|
||||
# * ``2`` — image / sticker (webp). ``file`` / ``fileTh`` are direct URLs;
|
||||
# download + dedupe by ``md5``.
|
||||
# * ``3`` — image, legacy format. ``file`` is an opaque ID (not a URL) and
|
||||
# needs a separate download endpoint; flagged distinctly so the media layer
|
||||
# can route it.
|
||||
# * ``4`` — video (mp4). ``file`` is a direct URL.
|
||||
# * ``10`` — system / link / card / invite. ``file`` is a support URL (non-media);
|
||||
# the media layer skips downloading.
|
||||
# Other codes (``6``, ``9``, ``13``, ``15``, ``21``, ``22`` — voice / file /
|
||||
# transfer / redpacket / quote / etc.) are observed but unconfirmed; they pass
|
||||
# through as ``"unknown"`` so the message is still ingested (raw code kept on
|
||||
# ``raw_json``) rather than dropped. Extend this table as each is confirmed.
|
||||
_WECHAT_TYPE_NAMES: dict[int, str] = {
|
||||
1: "text",
|
||||
-2: "divider",
|
||||
2: "image",
|
||||
3: "image_legacy",
|
||||
4: "video",
|
||||
10: "system",
|
||||
}
|
||||
|
||||
|
||||
def normalize_wechat_type(raw: Any) -> str:
|
||||
"""Map an upstream WeChat ``type`` / ``messageType`` code to a stable string.
|
||||
|
||||
Returns ``"text"`` for code ``1``, ``"divider"`` for code ``-2``, and
|
||||
``"unknown"`` for any other / missing / unparsable value.
|
||||
Confirmed mappings: ``1→text``, ``-2→divider``, ``2→image``,
|
||||
``3→image_legacy``, ``4→video``, ``10→system``. Any other / missing /
|
||||
unparsable value returns ``"unknown"``.
|
||||
|
||||
Never raises: an unexpected type must not break the scrape. The raw code
|
||||
is preserved on ``raw_json`` so the persistence layer can recover it.
|
||||
|
||||
Reference in New Issue
Block a user