Token导航 LogoToken导航TokenDH.com
研究检索操作浏览器github未标认证来源可访问许可证需确认审计异常

wx-favorites-reportwx 最爱报告

Agent Skill

wx-favorites-report 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

3,740

周安装

159

GitHub Stars

39

下载量

1,310
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:wx-favorites-report(wx 最爱报告)
来源仓库:https://github.com/aradotso/trending-skills
仓库路径:skills/wx-favorites-report
安装命令:
npx skills add https://github.com/aradotso/trending-skills --skill wx-favorites-report
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/aradotso/trending-skills --skill wx-favorites-report

简介

用于查找、检索和筛选相关信息,支持关键词匹配与来源线索定位。

  • 适合在知识探索、资料调研或任务驱动型搜索中快速获取候选结果。
  • 通过 GitHub 安装,使用 npx 命令添加技能并传入查询条件与过滤规则。
  • 建议确认数据源范围、更新频率及是否涉及敏感或受限内容访问。
  • wx-favorites-report 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

wx-favorites-report

Skill by ara.so — Daily 2026 Skills collection.

End-to-end pipeline that hooks into the WeChat Mac client via Frida, extracts PBKDF2-derived encryption keys, decrypts the favorite.db SQLCipher database, parses XML-encoded favorites, and renders a single-file interactive HTML report with charts, word cloud, and filterable card browser.


Prerequisites

  • macOS (Apple Silicon or Intel)
  • WeChat Mac 4.x installed and logged in
  • Python 3.9+
  • Frida 17.x
pip3 install frida frida-tools pycryptodome

Project Layout

~/.claude/skills/wechat-favorites-viz/
├── SKILL.md
└── scripts/
    ├── parse_favorites.py      # SQLite/CSV/JSON → unified JSON
    ├── generate_report.py      # JSON → single-file HTML
    └── demo_data.py            # synthetic data for testing

Full Pipeline (Step-by-Step)

Step 1 — Strip Hardened Runtime from WeChat

The App Store build blocks Frida injection. Copy and re-sign without entitlements:

killall WeChat 2>/dev/null; sleep 2
cp -R /Applications/WeChat.app ~/Desktop/WeChat.app
codesign --force --deep --sign - ~/Desktop/WeChat.app
Never run with sudo — doing so changes the data directory to /var/root/… and breaks DB path resolution.

Step 2 — Hook PBKDF2 with Frida

Save as hook_wechat.js:

// hook_wechat.js — capture all CCKeyDerivationPBKDF calls
var CCKeyDerivationPBKDF = Module.findExportByName(
  "libcommonCrypto.dylib",
  "CCKeyDerivationPBKDF"
);

Interceptor.attach(CCKeyDerivationPBKDF, {
  onEnter: function (args) {
    // args[3] = password ptr, args[4] = password len
    // args[5] = salt ptr,     args[6] = salt len
    // args[9] = iterations
    // args[10]= derived key ptr, args[11]= derived key len
    this.saltPtr = args[5];
    this.saltLen = args[6].toInt32();
    this.dkPtr   = args[10];
    this.dkLen   = args[11].toInt32();
  },
  onLeave: function (_retval) {
    try {
      var salt = Memory.readByteArray(this.saltPtr, this.saltLen);
      var dk   = Memory.readByteArray(this.dkPtr,   this.dkLen);
      var entry = {
        salt: Array.from(new Uint8Array(salt))
              .map(b => b.toString(16).padStart(2, "0")).join(""),
        key:  Array.from(new Uint8Array(dk))
              .map(b => b.toString(16).padStart(2, "0")).join(""),
        ts:   Date.now()
      };
      var line = JSON.stringify(entry) + "\n";
      // Write to log via send()
      send(line);
    } catch (e) {}
  }
});

Run the hook:

frida ~/Desktop/WeChat.app/Contents/MacOS/WeChat \
  -l hook_wechat.js \
  --runtime=v8 \
  2>/dev/null | tee /tmp/wechat_frida_keys.log &

# WeChat will launch — log in, then open the 收藏 (Favorites) tab.
# Wait ~60 seconds for all DB keys to be derived, then Ctrl+C.
Key insight: favorite.db is only opened when the user navigates to the Favorites tab. If you hook before opening Favorites, the key won't appear.

Step 3 — Match Key to favorite.db

# match_key.py
import json, sqlite3, pathlib

LOG = pathlib.Path("/tmp/wechat_frida_keys.log")
DB  = pathlib.Path.home() / (
    "Library/Containers/com.tencent.xinWeChat/Data/Documents/"
    "xwechat_files"
)

def find_db(wxid=None):
    """Locate favorite.db under the first (or named) wxid folder."""
    root = DB
    candidates = sorted(root.glob("*/db_storage/favorite/favorite.db"))
    if not candidates:
        raise FileNotFoundError("favorite.db not found")
    if wxid:
        return next(p for p in candidates if wxid in str(p))
    return candidates[0]

def read_salt(db_path: pathlib.Path) -> bytes:
    """First 16 bytes after the 16-byte SQLCipher header = salt."""
    with open(db_path, "rb") as f:
        f.read(16)               # skip "SQLite format 3\x00"
        return f.read(16)        # salt

def match(db_path: pathlib.Path) -> str | None:
    salt_hex = read_salt(db_path).hex()
    for line in LOG.read_text().splitlines():
        try:
            entry = json.loads(line)
            if entry["salt"] == salt_hex:
                return entry["key"]
        except Exception:
            continue
    return None

if __name__ == "__main__":
    db = find_db()
    key = match(db)
    if key:
        print(f"enc_key (hex): {key}")
        print(f"db path      : {db}")
    else:
        print("Key not found — did you open the Favorites tab while Frida was running?")

Step 4 — Decrypt the Database

# decrypt_db.py
"""
SQLCipher 4 parameters:
  cipher       : AES-256-CBC
  hmac         : HMAC-SHA512
  kdf_iter     : 256000
  page_size    : 4096
  reserve      : 80  (64 HMAC + 16 IV)
"""
import hashlib, hmac, struct, pathlib
from Crypto.Cipher import AES

PAGE_SIZE = 4096
RESERVE   = 80
IV_SIZE   = 16
HMAC_SIZE = 64
KDF_ITER  = 256000

def decrypt_db(enc_path: pathlib.Path, key_hex: str, out_path: pathlib.Path):
    raw_key = bytes.fromhex(key_hex)
    data    = enc_path.read_bytes()

    # SQLCipher stores salt in first 16 bytes of file
    salt = data[:16]

    # Derive page key and HMAC key
    page_key  = hashlib.pbkdf2_hmac("sha512", raw_key, salt, KDF_ITER, dklen=32)
    hmac_key  = hashlib.pbkdf2_hmac("sha512", page_key, salt, 1, dklen=32)

    out_pages = bytearray()

    # Page 1: skip 16-byte salt header
    pages = [data[16:PAGE_SIZE]] + [
        data[i:i+PAGE_SIZE] for i in range(PAGE_SIZE, len(data), PAGE_SIZE)
    ]

    for page_num, page in enumerate(pages, start=1):
        content  = page[:PAGE_SIZE - RESERVE]
        reserved = page[PAGE_SIZE - RESERVE:]
        iv       = reserved[HMAC_SIZE:HMAC_SIZE + IV_SIZE]

        cipher    = AES.new(page_key, AES.MODE_CBC, iv)
        plaintext = cipher.decrypt(content)

        if page_num == 1:
            # Restore SQLite header
            out_pages += b"SQLite format 3\x00" + plaintext[16:]
        else:
            out_pages += plaintext

        # Zero-pad to full page size
        out_pages += bytes(RESERVE)

    out_path.write_bytes(bytes(out_pages))
    print(f"Decrypted → {out_path}")

if __name__ == "__main__":
    import sys
    enc_path = pathlib.Path(sys.argv[1])
    key_hex  = sys.argv[2]
    out_path = pathlib.Path(sys.argv[3])
    decrypt_db(enc_path, key_hex, out_path)
python3 decrypt_db.py \
    ~/Library/Containers/.../favorite.db \
    <32-byte-key-hex> \
    /tmp/favorite_decrypted.db

Step 5 — Parse Favorites

WeChat 4.x uses a single table fav_db_item with XML content (not the 3.x FavItems/FavDataItem split):

# parse_favorites.py (core logic excerpt)
import sqlite3, json, re, pathlib
from datetime import datetime
from xml.etree import ElementTree as ET

TYPE_MAP = {
    1: "text", 2: "image", 3: "voice", 4: "video",
    5: "playlist", 6: "location", 7: "attachment",
    8: "article", 43: "video_channel", 49: "link",
}

def parse_xml_content(xml_str: str, fav_type: int) -> dict:
    """Extract title, desc, source, url from XML blob."""
    result = {"title": "", "desc": "", "source": "", "url": ""}
    if not xml_str:
        return result
    try:
        root = ET.fromstring(xml_str)
    except ET.ParseError:
        return result

    def txt(tag):
        el = root.find(f".//{tag}")
        return el.text.strip() if el is not None and el.text else ""

    if fav_type == 8:           # article — WeChat 4.x uses <pagetitle>
        result["title"]  = txt("pagetitle") or txt("title")
        result["url"]    = txt("url")
        result["source"] = txt("sourcename") or txt("fromnickname")
        result["desc"]   = txt("desc")
    elif fav_type == 49:        # link
        result["title"]  = txt("title")
        result["url"]    = txt("url")
        result["source"] = txt("sourcename")
        result["desc"]   = txt("desc")
    elif fav_type in (3, 4, 43):  # voice/video
        for item in root.findall(".//dataitem"):
            t = item.findtext("datatitle", "").strip()
            if t:
                result["title"] = t
                break
        result["source"] = txt("fromnickname")
    else:                       # text, image, etc.
        result["title"]  = txt("title") or txt("pagetitle")
        result["desc"]   = txt("desc") or txt("content")
        result["source"] = txt("fromnickname")

    return result

def parse(db_path: pathlib.Path) -> list[dict]:
    con = sqlite3.connect(db_path)
    con.row_factory = sqlite3.Row
    rows = con.execute(
        "SELECT localId, favLocalId, type, createTime, updateTime, "
        "       xmlBuf, tagNames "
        "FROM   fav_db_item "
        "ORDER  BY createTime"
    ).fetchall()

    items = []
    for row in rows:
        parsed = parse_xml_content(row["xmlBuf"] or "", row["type"])
        items.append({
            "id":         row["localId"],
            "type":       TYPE_MAP.get(row["type"], f"unknown_{row['type']}"),
            "created_at": datetime.utcfromtimestamp(row["createTime"]).isoformat(),
            "updated_at": datetime.utcfromtimestamp(row["updateTime"]).isoformat(),
            "title":      parsed["title"],
            "desc":       parsed["desc"],
            "source":     parsed["source"],
            "url":        parsed["url"],
            "tags":       [t.strip() for t in (row["tagNames"] or "").split(",") if t.strip()],
        })
    con.close()
    return items

if __name__ == "__main__":
    import sys
    db   = pathlib.Path(sys.argv[1])
    out  = pathlib.Path(sys.argv[2])
    data = parse(db)
    out.write_text(json.dumps(data, ensure_ascii=False, indent=2))
    print(f"Parsed {len(data)} items → {out}")
python3 parse_favorites.py /tmp/favorite_decrypted.db /tmp/data.json

Step 6 — Generate HTML Report

python3 generate_report.py --input /tmp/data.json --output /tmp/report.html

Serve locally (required — file:// breaks ECharts event delegation):

cd /tmp && python3 -m http.server 8765
open http://localhost:8765/report.html

Key Configuration Reference

ParameterValueNotes
SQLCipher version4WeChat 4.x
CipherAES-256-CBC
HMACHMAC-SHA512
KDF iterations256 000PBKDF2
Page size4 096 bytes
Reserve per page80 bytes64 HMAC + 16 IV
Salt locationbytes 0–15 of file
Table name (4.x)fav_db_item3.x used FavItems
Article title field<pagetitle>Not <title>

Common Issues & Fixes

"Key not found in log"

  1. Confirm you opened the 收藏 tab while Frida was attached.
  2. Check log for any entries: wc -l /tmp/wechat_frida_keys.log
  3. Salt mismatch — re-read salt: xxd ~/…/favorite.db | head -2 (bytes 16–31 after the ASCII header).

"database disk image is malformed"

Decryption parameters are wrong. Double-check KDF_ITER=256000 and PAGE_SIZE=4096. If WeChat updated, parameters may have changed — try kdf_iter=64000 (SQLCipher 3 default) as a fallback.

"codesign: No identity found"

Use - (ad-hoc signing), not a certificate name:

codesign --force --deep --sign - ~/Desktop/WeChat.app

Report images broken

Thumbnail URLs are WeChat CDN links — they require an active network session. Add onerror handler using " to avoid quote conflicts in inline HTML:

img_tag = f'<img src="{url}" onerror="this.style.display="none"">'

onclick not firing on file://

Use event delegation on a parent element instead of inline onclick:

document.getElementById("card-list").addEventListener("click", function(e) {
  var card = e.target.closest(".fav-card");
  if (card) showDetail(card.dataset.id);
});

WeChat updated — hook stopped working

Re-copy and re-sign the app bundle, then re-run the full pipeline. The PBKDF2 hook targets a system library (libcommonCrypto.dylib) so it is resilient to WeChat binary changes, but the re-signing step must be repeated.


Report Features

SectionChart type
Summary cardsStatic KPI tiles
Monthly trendECharts line + area
Type distributionECharts doughnut
Top 15 sourcesECharts horizontal bar
Activity heatmapECharts heatmap (weekday × hour)
Word cloudecharts-wordcloud
Tag cloudCSS flex tags
Favorites browserCard grid with type/tag filter + full-text search + pagination
Detail modalFull content, URL, source, tags

Known Limitations

  • Image/video/file binary blobs are stored in WeChat's encrypted CDN — not previewable offline.
  • Key extraction requires macOS + Frida; no Windows/Linux support.
  • After each WeChat update, the Desktop copy must be re-signed.
  • The tagNames column stores comma-separated tag strings; empty tags are filtered client-side.

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

33.37%
按下载量换算437

Claude

30.59%
按下载量换算401

Cursor

20.14%
按下载量换算264

Gemini CLI

9.15%
按下载量换算120

安全审计

Gen Agent Trust Hub

未通过

Socket

可疑

Snyk

未通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills