python script to export wechat stickers (macos)

cheems

Here's a Python script that extracts and downloads your WeChat stickers on macOS. It converts WeChat's proprietary fav.archive file into a readable format, extracts sticker URLs, and downloads them automatically. Inspired by wibus-wee's node implementation.

# wsd.py by mgx.me

# inspired by @wibus-wee's DownloadWeChatStickers_macOS.js

import os import re import shutil import time import uuid import subprocess from pathlib import Path import requests

def sleep(seconds): time.sleep(seconds)

def copy_fav_archive(): wechat_data_path = Path.home() / 'Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9' user_dirs = [d for d in wechat_data_path.iterdir() if d.is_dir() and len(d.name) > 30] user_dirs = [d for d in user_dirs if (d / 'Stickers/fav.archive').exists()]

<span class=k>if</span> <span class=ow>not</span> <span class=n>user_dirs</span><span class=p>:</span>
    <span class=nb>print</span><span class=p>(</span><span class=s1>'No user folder found'</span><span class=p>)</span>
    <span class=k>return</span> <span class=kc>None</span>

<span class=k>if</span> <span class=nb>len</span><span class=p>(</span><span class=n>user_dirs</span><span class=p>)</span> <span class=o>></span> <span class=mi>1</span><span class=p>:</span>
    <span class=k>for</span> <span class=n>index</span><span class=p>,</span> <span class=n>dir_path</span> <span class=ow>in</span> <span class=nb>enumerate</span><span class=p>(</span><span class=n>user_dirs</span><span class=p>,</span> <span class=n>start</span><span class=o>=</span><span class=mi>1</span><span class=p>):</span>
        <span class=nb>print</span><span class=p>(</span><span class=sa>f</span><span class=s2>"</span><span class=si>{</span><span class=n>index</span><span class=si>}</span><span class=s2>. </span><span class=si>{</span><span class=n>dir_path</span><span class=si>}</span><span class=s2>"</span><span class=p>)</span>
    <span class=n>choice</span> <span class=o>=</span> <span class=nb>input</span><span class=p>(</span><span class=s2>"Please enter the user folder number: "</span><span class=p>)</span>
    <span class=k>try</span><span class=p>:</span>
        <span class=n>user_dir</span> <span class=o>=</span> <span class=n>user_dirs</span><span class=p>[</span><span class=nb>int</span><span class=p>(</span><span class=n>choice</span><span class=p>)</span> <span class=o>-</span> <span class=mi>1</span><span class=p>]</span>
    <span class=k>except</span> <span class=p>(</span><span class=ne>IndexError</span><span class=p>,</span> <span class=ne>ValueError</span><span class=p>):</span>
        <span class=nb>print</span><span class=p>(</span><span class=s2>"Invalid choice"</span><span class=p>)</span>
        <span class=k>return</span> <span class=kc>None</span>
<span class=k>else</span><span class=p>:</span>
    <span class=n>user_dir</span> <span class=o>=</span> <span class=n>user_dirs</span><span class=p>[</span><span class=mi>0</span><span class=p>]</span>

<span class=n>fav_archive_path</span> <span class=o>=</span> <span class=n>user_dir</span> <span class=o>/</span> <span class=s1>'Stickers/fav.archive'</span>
<span class=n>dest_path</span> <span class=o>=</span> <span class=n>Path</span><span class=o>.</span><span class=n>home</span><span class=p>()</span> <span class=o>/</span> <span class=s1>'Desktop/fav.archive.plist'</span>
<span class=n>shutil</span><span class=o>.</span><span class=n>copy</span><span class=p>(</span><span class=n>fav_archive_path</span><span class=p>,</span> <span class=n>dest_path</span><span class=p>)</span>
<span class=nb>print</span><span class=p>(</span><span class=sa>f</span><span class=s2>"Copied fav.archive file to desktop: </span><span class=si>{</span><span class=n>dest_path</span><span class=si>}</span><span class=s2>"</span><span class=p>)</span>
<span class=k>return</span> <span class=n>dest_path</span>

def convert_to_plist(plist_path): subprocess.run(['plutil', '-convert', 'xml1', str(plist_path)], check=True)

def extract_urls(plist_path): with open(plist_path, 'r', encoding='utf-8') as file: content = file.read()

<span class=n>link_regex</span> <span class=o>=</span> <span class=n>re</span><span class=o>.</span><span class=n>compile</span><span class=p>(</span><span class=sa>r</span><span class=s1>'&LTstring>(https?:\/\/.*?)<\/string>'</span><span class=p>)</span>
<span class=n>links</span> <span class=o>=</span> <span class=p>[</span><span class=n>match</span><span class=o>.</span><span class=n>replace</span><span class=p>(</span><span class=s1>'&ampamp;'</span><span class=p>,</span> <span class=s1>'&'</span><span class=p>)</span> <span class=k>for</span> <span class=n>match</span> <span class=ow>in</span> <span class=n>link_regex</span><span class=o>.</span><span class=n>findall</span><span class=p>(</span><span class=n>content</span><span class=p>)]</span>
<span class=nb>print</span><span class=p>(</span><span class=sa>f</span><span class=s2>"Found </span><span class=si>{</span><span class=nb>len</span><span class=p>(</span><span class=n>links</span><span class=p>)</span><span class=si>}</span><span class=s2> sticker URLs"</span><span class=p>)</span>
<span class=k>return</span> <span class=n>links</span>

def download_stickers(links, output_dir): output_dir.mkdir(parents=True, exist_ok=True) user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36'

<span class=k>for</span> <span class=n>index</span><span class=p>,</span> <span class=n>url</span> <span class=ow>in</span> <span class=nb>enumerate</span><span class=p>(</span><span class=n>links</span><span class=p>,</span> <span class=n>start</span><span class=o>=</span><span class=mi>1</span><span class=p>):</span>
    <span class=n>filename</span> <span class=o>=</span> <span class=sa>f</span><span class=s2>"</span><span class=si>{</span><span class=n>uuid</span><span class=o>.</span><span class=n>uuid4</span><span class=p>()</span><span class=si>}</span><span class=s2>.gif"</span>
    <span class=n>filepath</span> <span class=o>=</span> <span class=n>output_dir</span> <span class=o>/</span> <span class=n>filename</span>
    <span class=nb>print</span><span class=p>(</span><span class=sa>f</span><span class=s2>"Downloading sticker </span><span class=si>{</span><span class=n>index</span><span class=si>}</span><span class=s2>: </span><span class=si>{</span><span class=n>url</span><span class=si>}</span><span class=s2>"</span><span class=p>)</span>

    <span class=n>headers</span> <span class=o>=</span> <span class=p>{</span><span class=s1>'User-Agent'</span><span class=p>:</span> <span class=n>user_agent</span><span class=p>}</span>
    <span class=k>try</span><span class=p>:</span>
        <span class=n>response</span> <span class=o>=</span> <span class=n>requests</span><span class=o>.</span><span class=n>get</span><span class=p>(</span><span class=n>url</span><span class=p>,</span> <span class=n>headers</span><span class=o>=</span><span class=n>headers</span><span class=p>)</span>
        <span class=n>response</span><span class=o>.</span><span class=n>raise_for_status</span><span class=p>()</span>
        <span class=k>with</span> <span class=nb>open</span><span class=p>(</span><span class=n>filepath</span><span class=p>,</span> <span class=s1>'wb'</span><span class=p>)</span> <span class=k>as</span> <span class=n>f</span><span class=p>:</span>
            <span class=n>f</span><span class=o>.</span><span class=n>write</span><span class=p>(</span><span class=n>response</span><span class=o>.</span><span class=n>content</span><span class=p>)</span>
        <span class=nb>print</span><span class=p>(</span><span class=sa>f</span><span class=s2>"Download completed: </span><span class=si>{</span><span class=n>filename</span><span class=si>}</span><span class=s2>"</span><span class=p>)</span>
    <span class=k>except</span> <span class=n>requests</span><span class=o>.</span><span class=n>RequestException</span> <span class=k>as</span> <span class=n>e</span><span class=p>:</span>
        <span class=nb>print</span><span class=p>(</span><span class=sa>f</span><span class=s2>"Failed to download: </span><span class=si>{</span><span class=n>url</span><span class=si>}</span><span class=s2> Error: </span><span class=si>{</span><span class=n>e</span><span class=si>}</span><span class=s2>"</span><span class=p>)</span>

def main(): plist_path = copy_fav_archive() if not plist_path: return

<span class=n>convert_to_plist</span><span class=p>(</span><span class=n>plist_path</span><span class=p>)</span>
<span class=n>links</span> <span class=o>=</span> <span class=n>extract_urls</span><span class=p>(</span><span class=n>plist_path</span><span class=p>)</span>

<span class=k>if</span> <span class=n>links</span><span class=p>:</span>
    <span class=n>output_dir</span> <span class=o>=</span> <span class=n>Path</span><span class=o>.</span><span class=n>home</span><span class=p>()</span> <span class=o>/</span> <span class=s1>'Desktop'</span> <span class=o>/</span> <span class=s1>'WX-Stickers'</span>
    <span class=nb>print</span><span class=p>(</span><span class=s1>'Starting sticker download in 5 seconds...'</span><span class=p>)</span>
    <span class=n>sleep</span><span class=p>(</span><span class=mi>5</span><span class=p>)</span>
    <span class=n>download_stickers</span><span class=p>(</span><span class=n>links</span><span class=p>,</span> <span class=n>output_dir</span><span class=p>)</span>

if name == "main": main()