# wx-favorites-report: WeChat favorites turned into a local HTML report

> This Claude Code skill is an end-to-end pipeline that reads the encrypted WeChat database on a Mac, extracts the SQLCipher key with a frida hook, decrypts the favorites table, and renders a single-file HTML report with a dashboard, trends, a word cloud and a searchable browser. It is macOS only, it needs the WeChat app re-signed after every update, and it says plainly that the media it reports on cannot be previewed.

**zhuyansen/wx-favorites-report** — 微信收藏可视化 Claude Code Skill — 从加密 DB 到交互式 HTML 报告的端到端管线

- Repository: https://github.com/zhuyansen/wx-favorites-report
- Stars: 642 · Forks: 133
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/zhuyansen-wx-favorites-report

## One pipeline from an encrypted database to a browsable archive

The project is packaged as a Claude Code skill, and the repository name gives the topic away: it extracts favorites data from the encrypted WeChat database on macOS and produces an interactive HTML report. The package name inside the skill folder is wechat-favorites-viz.

The interesting part is that this is not a data export utility. It is a four stage pipeline that starts at the level of key extraction, because the local WeChat database is encrypted with SQLCipher and the key is derived at runtime inside the running application. So the pipeline has to be: capture the derived key, decrypt the database, parse the rows into a normalised JSON structure, and render a report.

The requirements are short. macOS on either Apple Silicon or Intel, WeChat for Mac 4.x already logged in, Python 3.9 or later, and Claude Code, which the README recommends because it automates the whole flow including the key extraction. There is a manual path too, for when you want to see each step.

One line in the README tells you how the author thinks about the output. The preview screenshots are not published, and the note explains why: the data contains personal information and screenshots of it should not be shared publicly. That is a good default for a tool that renders your private saved content into a chart-friendly summary.

The licence is MIT, the language is Python, and the repository contains LICENSE, README.md, SKILL.md, a docs/ directory and a scripts/ directory. There are no GitHub releases, so the skill is used from the repository rather than from a versioned artefact.

## Eleven panels over your own saved archive

The report is where the value is, and the feature list is unusually specific.

There are statistic cards for the total count, the number of days the archive spans, a daily average, and the number of sources. Then a highlights block with the busiest day, the most used source and the dominant content type.

The charts cover a monthly trend as a line and area chart, a content type distribution as a donut chart, a top fifteen sources ranking as a horizontal bar chart, an activity heatmap over weekday and hour, and a distribution by hour and by weekday as bar charts. A word cloud pulls keywords out of titles and descriptions, and a separate tag cloud shows the tags WeChat itself assigned to your favorites.

The browser section is the part that makes it a tool rather than a poster. You can filter by type and by tag, run a full text search, sort, and page through the results, and clicking a card opens a detail modal with the full content, the original link, the source and the tags.

The rendering stack is ECharts 5.x from a CDN with the echarts-wordcloud 2.x plugin, and the output format is a single HTML file with a dark theme and all dependencies inlined. A single file matters more than it sounds: the report can be archived, emailed to yourself, opened on a machine with no network, and the data never has to be uploaded anywhere for the charts to work.

## Six failed approaches before the frida hook worked

The most valuable part of the repository is the section documenting what did not work, and the README is blunt about it: key extraction was the hardest part, and it took six rounds of iteration.

The table lists each attempt, the method and the outcome. Scanning the C memory for the key in hex form found zero keys, because WeChat 4.x does not store the key in that format. Searching for the raw salt bytes produced a false match, because it matched an ASCII string rather than a real key. Brute forcing the HMAC with 8 byte alignment did not find it, because the filter skipped the real position. Brute forcing without a filter at 4 byte alignment also failed, because the key is stored after being derived through PBKDF2 and not as the original enc_key.

The fifth attempt, a DYLD hook with lldb, was blocked outright by macOS SIP and the Hardened Runtime. The sixth attempt, frida with a hook on CCKeyDerivationPBKDF, succeeded, because it hooks the system PBKDF2 derivation function and captures the derivation as it happens.

The practical consequence for the key itself is in the decryption step. The enc_key for favorite.db is found by matching the salt, and the derived value is what the database is actually encrypted with.

The other two obstacles are operational. The App Store build of WeChat runs with the Hardened Runtime, so the signed application under /Applications cannot be modified, which is why the workflow copies it elsewhere and re-signs the copy. And a sudo launch of WeChat changes the data directory to /var/root, so the script has to run as your normal user.

## The database lives in a container, and the parameters are SQLCipher 4

The path to the favorites database is inside the macOS application container for WeChat:

~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<wxid>/db_storage/favorite/favorite.db

The <wxid> segment is the account identifier, so a machine with several accounts has several candidate paths.

The decryption parameters are listed in the README: SQLCipher 4, AES-256-CBC, HMAC-SHA512, PBKDF2 with 256000 iterations, page_size 4096 and reserve 80. Those are the settings the Python side has to reproduce, using PyCryptodome for the AES and HMAC work, and the iteration count is the expensive part of the derivation, which is why the key is captured at derivation time rather than brute forced.

A third pitfall in the list is worth separating out because it is not a cryptography problem at all. The favorite.db key is not loaded when the application starts. WeChat only loads favorite.db when you open the favorites page. So the frida hook has to be running while you log in and open that page, and the README tells you to wait 60 seconds after opening the favorites page before the key is in the log.

The log itself is written to /tmp/wechat_frida_keys.log, which is where the salt to enc_key mapping is read from.

It is worth being explicit about the trust boundary here. This pipeline decrypts data belonging to the WeChat account that is logged in on the machine it runs on, using a key extracted from that same local process, and it writes the result to a local file. It is a personal data analysis tool, not a remote attack tool, and the README is careful that the report stays on your machine.

## The 4.x schema change and the XML that does not name its fields consistently

Even with the database decrypted, the content is not a clean table, and the README spends two pitfalls on why.

WeChat 4.x changed the schema. Version 3.x used two tables, FavItems and FavDataItem, and 4.x replaced them with a single table called fav_db_item, in which the content column holds XML. So a parser written against 3.x does not work, and the table structure has to be taken from the version you actually have.

The second problem is that the XML does not use consistent element names for the same idea. The README lists three cases. An article title is in a pagetitle element, not a title element. Chat history is in a datalist containing dataitem containing datadesc. A video title is in a datatitle element nested inside a dataitem.

That is the kind of detail that turns a parsing script into a pile of special cases, and it is why the parsing layer is described as Python sqlite3 plus regex over XML rather than a schema-aware XML mapping. A regex over inconsistent element names is fragile, and the report will silently show empty titles for a category the author did not anticipate.

The generator layer has its own three pitfalls, all of them in the HTML and JavaScript that the report is made of. Nesting quotes in an onerror attribute breaks the Python f-string that also holds a JavaScript template string, and the fix is to use &quot; instead. A click handler does not work under the file:// protocol, so the code has to use event delegation with addEventListener on a parent element instead. And an ECharts container without an explicit height renders parts of the chart as blank space.

All three are the kind of problem that a single self-contained HTML file creates, and each cost an iteration.

## Running the pipeline by hand, one command at a time

The manual path is documented step by step, and it is the clearest way to see what the skill automates.

Step one is the dependencies, which are frida for the instrumentation, frida-tools for the command line, and pycryptodome for the decryption:

```bash
pip3 install frida frida-tools pycryptodome
```

Step two is the re-signed copy of the application. The copy has to go somewhere the SIP rules do not protect, and the copy has to be signed again after being modified:

```bash
cp -R /Applications/WeChat.app ~/Desktop/WeChat.app
codesign --force --deep --sign - ~/Desktop/WeChat.app
```

Step three is the key extraction. The current WeChat is closed, the desktop copy is started under frida with the hook script from SKILL.md, and then you log in and open the favorites page and wait. The frida session writes to /tmp/wechat_frida_keys.log:

```bash
killall WeChat 2>/dev/null; sleep 2
```

Step four is the decryption, done in Python with the salt matched to the enc_key and the SQLCipher 4 parameters applied.

Step five produces the output, first the parsed data and then the report:

```bash
python3 parse_favorites.py --input favorite_decrypted.db --output data.json
python3 generate_report.py --input data.json --output report.html
```

The README recommends serving the file over HTTP rather than opening it directly, and the reason is the click handler problem described earlier:

```bash
python3 -m http.server 8765
```

The three scripts behind this are parse_favorites.py, which takes SQLite, CSV or JSON into a unified JSON structure, generate_report.py, which turns that JSON into the single file HTML, and demo_data.py, which generates mock data for testing. That last one is the practical starting point, because it lets you work on the report generator without a decrypted database in hand.

## What the report cannot show, and the maintenance the approach demands

The known limitations section is short and specific, and one of them shapes what the report is useful for.

The raw content of images, videos and files lives on the WeChat CDN in an encrypted format, so it cannot be previewed in the report. Article thumbnails also come from the WeChat CDN and need a network connection, so a report opened offline shows the layout without the images. The report is therefore a report about your archive, not a copy of it.

Key extraction supports macOS only and requires frida. And after every WeChat update the desktop copy has to be re-signed, which is a recurring cost rather than a one-off setup step, and it is the reason the skill automates the flow when it can: re-signing and re-running the hook is exactly the kind of repeated, well-defined work an agent is good at.

The packaging reflects that. The skill directory holds SKILL.md, which defines the trigger word, the flow and the parameters, and the scripts/ directory with the three Python files. It installs into ~/.claude/skills/wechat-favorites-viz/, so it is invoked the way any Claude Code skill is invoked, with one trigger phrase, and Claude then performs the extraction, the decryption, the parsing and the report generation.

The licence is MIT, so nothing stops you from adapting the scripts. The last push to the repository was on 2026-10-19, and there are no GitHub releases, which is normal for a skill that people install from the repository rather than from a published version.

## Conclusion

wx-favorites-report suits a WeChat user on a Mac who wants to look at their own saved articles, links and notes as a dataset rather than a scrolling list, and it suits people who want an agent-driven skill rather than a script to run by hand. It does not suit anyone on Windows or Linux, since the key extraction is macOS only, and it does not suit a workflow that needs the images, videos and files, because those live on an encrypted WeChat CDN and cannot be previewed in the report. Check three things before you rely on it: that your WeChat is version 4.x, since the 4.x schema differs from 3.x, that you are prepared to re-sign a copy of the WeChat application after every WeChat update, and that the report leaves your machine, because the documentation points out that the data contains personal information and asks you not to publish screenshots of it. The last push to the repository was on 2026-10-19 and the project has no GitHub releases.

## FAQ

### What does wx-favorites-report actually do?

It is a Claude Code skill that extracts favorites data from the encrypted WeChat database on macOS and generates an interactive HTML report. The pipeline covers key extraction, decryption, parsing and report generation, and can be run automatically through Claude Code or step by step by hand.

### What are the requirements for running it?

macOS on Apple Silicon or Intel, WeChat for Mac 4.x already logged in, Python 3.9 or later, and Claude Code, which the README recommends because it automates the whole flow. The manual path needs frida, frida-tools and pycryptodome.

### How does the key extraction work?

The App Store build of WeChat runs with the Hardened Runtime, so a copy is made to the desktop and re-signed, and frida is used to hook CCKeyDerivationPBKDF and capture the PBKDF2 derivations as they happen, writing them to /tmp/wechat_frida_keys.log. The enc_key for favorite.db is then found by matching the salt.

### Why does the WeChat app have to be copied and re-signed?

The README states that the App Store version of WeChat has Hardened Runtime protection, and the pitfall list notes that macOS SIP blocks signing the application under /Applications. Copying it to the desktop and running codesign --force --deep --sign - on the copy works around that, and it has to be repeated after every WeChat update.

### What cannot the generated report show?

The raw content of images, videos and files lives on the WeChat CDN in an encrypted format and cannot be previewed, and article thumbnails come from the WeChat CDN so they need a network connection. The report shows metadata, text and links rather than the media itself.

## Sources

- [Issues](https://github.com/zhuyansen/wx-favorites-report/issues)
- [License: MIT](https://github.com/zhuyansen/wx-favorites-report/blob/main/LICENSE)
- [README](https://github.com/zhuyansen/wx-favorites-report/blob/main/README.md)
- [zhuyansen/wx-favorites-report on GitHub](https://github.com/zhuyansen/wx-favorites-report)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zhuyansen-wx-favorites-report
