How can I sync my WeChat avatar to my mobile address book?

Posted by ericw on Fri, 21 Jun 2019 18:47:49 +0200

Smartisan OS likes Nut Pro on May 10th, but I don't like the picture of my mobile phone contact. It's not distinctive or recognizable.
WeChat is much more. You can think of specific people at the first glance of the avatar. If only the contact avatar could be the same as the avatar of WeChat friends.

Brutally, modifying a contact's avatar is certainly possible, but such a manual programmer should not.
First searched on the Internet, there are similar requirements, such as "how to synchronize QQ avatars to mobile contacts", but I don't see a solution, it's up to me.

Below is my idea. First of all, I am accustomed to using real names for mobile contact names and WeChat friend notes, which means they can be related with each other.

  1. Get the name and avatar of WeChat friends and generate name-photo-map
  2. Traverse through your mobile phone contacts, find them in name-photo-map based on their name, and add a picture to them.

Let's look at the implementation.

1. Tools

  • Chrome Browser
  • NodeJS

2. Get the names and avatars of all your friends on WeChat

Chrome F12 opens the developer tools and logs in WeChat Web Page Edition To view the Network.

This request returns JSON, and all the friends'information is in the MemberList

  • Notes correspond to RemarkName
  • Nickname corresponds to NickName
  • HeadImgUrl for Avatar

Note that RemarkName is set in Chinese, and the garbled code is shown here.This is due to Chrome not using UTF8 encoding.
Right-click Open in new tab on the request, ctrl+s saves the result of the request return to the file wx-contacts.json on the new tab page, I open it with vscode, and the information is displayed properly.

The NodeJS program below parses the JSON, downloads the avatar picture, and uses the note name as the file name of the picture.

var https = require('https');
var fs = require('fs');

var contacts = JSON.parse(fs.readFileSync('./data/wx-contacts.json')).MemberList; // Read Friend Data

var cookie = 'Log in to get a picture of your avatar from the WeChat server. wx.qq.com Of cookie Put it here';

contacts.forEach((contact) => {
    makeRequest(contact);
});

function makeRequest(contact) {
    var contactName = contact.RemarkName || contact.NickName; // Notes for use, no nicknames for use
    if (contactName.indexOf('<') >= 0) return; // With emoticons, mobile contacts can't exist, skip directly
    var options = {
        host: 'wx.qq.com',
        port: 443,
        path: contact.HeadImgUrl,
        method: 'GET',
        headers: {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Encoding': 'gzip, deflate, sdch, br',
            'Accept-Language': 'zh-CN,zh;q=0.8',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Cookie': cookie
        }
    };
    var req = https.get(options, res => {
        res.setEncoding("binary");
        var imgData = '';
        res.on('data', chunk => {
            imgData += chunk;
        });
        res.on('end', () => {
            if (imgData.length > 0) {
                // The content-type in the response header was found to be image/jpeg. To simplify the code, the file suffix name was written to death
                fs.writeFile(`images/${contactName}.jpg`, imgData, "binary");
                if (contact.retry) {
                    console.log(`Download ${contactName} succeeded!`);
                }
            } else {
                // Network instability download failed, download again
                console.log(`Download ${contactName} failed! Retry...`);
                contact.retry = true;
                makeRequest(contact);
            }
        });
    });
    req.end();
}

Settings for cookie variables in the program:
Sign in WeChat Web Page Edition After success, view any request to wx.qq.com through Chrome Network and assign the cookie content in the request header to variables in the program.
This allows NodeJS requests to get avatars to pass server validation and download pictures.

Now I have downloaded all of my Weixin friends'avatar pictures and used the friend's comment name as the file name of the picture.

3. Set up avatars for mobile contacts

Steps:

  1. Export mobile contacts to xxx.vcf file [Always back up to avoid troubles]
  2. Parse the xxx.vcf file, add photo to each contact, and generate the xxx.new.vcf file
  3. Empty your mobile contact and import xxx.new.vcf to your mobile phone

The code for step 2 is as follows:

// The program uses third-party libraries to parse VCF files https://github.com/jhermsmeier/node-vcf  

// Two files were modified by me, so package.json was deleted  
// node_modules/foldline/foldline.js
// The original logic was to truncate a line of more than 75 characters to break the line, but found that mobile phone import would fail after truncating the line, so instead of truncating the line

// node_modules/vcf/lib/property.js
// Comparing the differences between the files before and after conversion, it is found that the field is omitted when the file field exported by the mobile phone is type, and the value is in uppercase.
// So modify the logic here to ensure that the program output is the same as the file content exported by your mobile phone

var fs = require('fs');

const OUTPUT_VCARD_PATH = 'output/contacts.vcf'; // vcf file output after program execution
const INPUT_VCARD_PATH = 'data/contacts.vcf'; // vcf file exported from mobile phone
const INPUT_IMG_PATH = 'images/'; // Directory for storing avatars of WeChat friends

var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');

// Parse vcf file to generate contact object array
var vCard = require('vcf');
var vcfData = fs.readFileSync(INPUT_VCARD_PATH, 'utf-8');
var cards = vCard.parseMultiple(vcfData); // Array of Contact Objects

var output = '';
var totalCount = 0;
var hasImgCount = 0;
cards.forEach((card) => {
    totalCount++;
    // Contact name fullName, encoded in UTF8 QUOTED-PRINTABLE in Chinese, needs to be decoded
    var fullName = card.get('fn')._data;
    if (card.get('fn').encoding == 'QUOTED-PRINTABLE') {
        fullName = decodeQuotedPrintable(card.get('fn')._data);
    }

    // Check to see if you downloaded the corresponding pictures of WeChat avatars based on the contact's name
    var imgPath = INPUT_IMG_PATH + fullName + '.jpg';
    if (fs.existsSync(imgPath)) {
        // photo set to contact with pictures of WeChat avatars
        var imgData = fs.readFileSync(imgPath);
        var dataStr = imgData.toString('base64');
        card.set('photo', dataStr, {
            encoding: 'BASE64',
            type: 'JPEG'
        });
        hasImgCount++;
        console.log(`${fullName} has a head image.`);
    } else {
        console.log(`${fullName} does not have a head image.`);
    }
    output += card.toString('2.1') + '\r\n'; // 2.1 Standard for representing vcf files
});

fs.writeFileSync(OUTPUT_VCARD_PATH, output, 'utf-8'); // Output a new vcf file
console.log(`\n\nHas Image Count / Total: ${hasImgCount} / ${totalCount}\nSave to "${OUTPUT_VCARD_PATH}" over!!!\n`);

// Decode UTF8 QUOTED-PRINTABLE string
function decodeQuotedPrintable(str) {
    var arr = str.split('=').slice(1);
    var byteArr = [];
    arr.forEach(str => {
        byteArr.push(parseInt('0x' + str));
    });
    var cent = Buffer.from(byteArr);
    return decoder.write(cent);
}

Import the program output vcf file into the mobile phone address book as follows:

4. End

The function is implemented, but it is still a bit cumbersome to use. I hope some experts can make an easy-to-use tool.Complete code in Here.

Topics: Mobile JSON encoding network