Greasy Fork

Greasy Fork is available in English.

微信公众号粉丝查看工具

微信公众号粉丝查看工具,可查看用户关注的精确时间(精确到时分秒),可导出用户数据,增加用户所在城市。

当前为 2021-06-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         微信公众号粉丝查看工具
// @namespace    https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_all_data
// @version      0.1.8
// @description  微信公众号粉丝查看工具,可查看用户关注的精确时间(精确到时分秒),可导出用户数据,增加用户所在城市。
// @author       Wilson
// @match        https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_all_data*
// @icon         https://img.icons8.com/fluent/48/000000/user-male-circle.png
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// ==/UserScript==

(function() {
    'use strict';

    //用户数据,记录每次变更的数据
    unsafeWindow.userData = [];
    unsafeWindow.allUserData = [];
    //请求响应时间
    var lastRequestTime = 0,
        lastResponseTime = 0;

    //添加关注信息
    function addFllowInfo(userList) {
        for (let i in userList) {
            //添加关注时间
            let user = userList[i];
            user.user_openid = user.user_openid || user.id;
            user.user_create_time = user.user_create_time || user.create_time;
            let localDate = new Date(user.user_create_time * 1000).toLocaleString();
            //console.log(user.user_create_time, user.user_openid);
            let userName = document.querySelector(".user_info a[data-fakeid=" + user.user_openid + "]");
            //console.log(userName, user.user_openid);
            if (userName) userName.innerHTML += '&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#9a9a9a;">(' + localDate + ')</span>';
        }

         //导出按钮
         $(".tool_area .pagination").append('<span><a href="javascript:;" onclick="wx_user_export()" style="margin-top:4px;margin-left:15px;display:inline-block;">导出本页</a></span>');
         let btnstyle = 'background-color: #fff;background-image: linear-gradient(to bottom,#fff 0,#fff 100%);border-color: #e7e7eb;color: #353535;height: 30px; line-height: 30px;padding-left: 10px; padding-right: 10px;width: auto;';
         $(".tool_area .pagination").append('<span style="margin-left:20px;">导出前<input id="wx_export_num" type="text" value="'+(unsafeWindow.exportNum||'')+'" style="width:40px;border:1px solid #e7e7eb;height:20px;text-align:center;">页<a id="wx_export_all" href="javascript:;" onclick="wx_user_export_all()" class="btn" style="margin-left:6px;display:inline-block;'+btnstyle+'">导出</a></span>');
    }

    //错误提示
    function errorTips(str, delay) {
        $(".page_tips .inner").html(str);
        $(".page_tips").show();
        setTimeout(function(){
            $(".page_tips").hide();
            $(".page_tips .inner").html("将浏览器更新为QQ浏览器或Chrome浏览器,以获得更好的体验");
        }, delay||3000);
    }

    //监听ajax请求
    //var lastUserUrl = "";
    (function(open) {
        XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {

            this.addEventListener("readystatechange", function() {

                //console.log(this.readyState, this.status, this);

                if (this.readyState==4 && this.status==200) {
                    //获取用户列表
                    if (url.indexOf("action=get_user_list") !== -1) {
                        let json = JSON.parse(this.response);
                        //console.log(this.readyState, this.status, json);
                        let userList = json.user_list.user_info_list || [];
                        //等待列表渲染完成添加信息
                        setTimeout(function(){
                            addFllowInfo(userList);
                        }, 400);
                        unsafeWindow.userData = userList;
                        //unsafeWindow.wx_user_export_page++;
                        if(unsafeWindow.isExporting) {
                            for(let j in userList) {
                                if(userList[j]) {
                                    unsafeWindow.allUserData.push(userList[j]);
                                }
                            }
                        }
                        //记录响应时间(毫秒)
                        lastResponseTime = new Date().getTime();
                    }

                    //获取用户信息
                    if (url.indexOf("action=get_fans_info") !== -1) {
                        let json = JSON.parse(this.response);
                        //console.log(this.readyState, this.status, json);
                        let userList = json.user_list.user_info_list || [];
                        if(userList.length > 0) {
                            let userInfo = userList[0];
                            setTimeout(function(){
                                $(".rich_buddy.popover").css("border", 0);
                                $(".popover_inner").css("border", "1px solid #C2C2C2").css("width", "355px");
                                let area = `<div class="frm_control_group location">
                                                <label class="frm_label">地区</label>
                                                <div class="frm_controls">`+userInfo.user_country+` `+userInfo.user_province+` `+userInfo.user_city+`</div>
                                            </div>`;
                                $(".rich_user_info_inner").append(area);
                                let localDate = new Date(userInfo.user_create_time * 1000).toLocaleString();
                               $(".tips_global").html(localDate + "关注");
                            }, 200);
                        }
                    }
                }

                /*
                let userUrl = document.location.origin + url;
                if (lastUserUrl != userUrl && url.indexOf("ction=get_user_list") !== -1) {
                    console.log(userUrl);

                    GM_xmlhttpRequest({
                        method: "get",
                        url: userUrl,
                        onload: function(res){
                            if(res.status === 200){
                                console.log('sucess')
                                let json = JSON.parse(res.response);
                                let userList = json.user_list.user_info_list || [];
                                addFllowInfo(userList);
                                unsafeWindow.userData = userList;
                                //unsafeWindow.wx_user_export_page++;
                            }else{
                                console.log('error')
                                console.log(res)
                            }
                        },
                        onerror : function(err){
                            console.log('error')
                            console.log(err)
                        }
                    });

                    //$.getJSON(userUrl, function(data){
                    //console.log(data);
                    //$.each(data.items, function(i,item){
                    // });
                    //});
                    lastUserUrl = userUrl;
                }
                */

            }, false);

            open.call(this, method, url, async, user, pass);
        };
    })(XMLHttpRequest.prototype.open);

    //全部导出
    unsafeWindow.isExporting = false;
    unsafeWindow.exportNum = '';
    unsafeWindow.isStop = 0;
    unsafeWindow.wx_user_export_all = function() {
        if(unsafeWindow.isExporting) return false;
        unsafeWindow.isExporting = true;
        let total = parseInt($(".page_num label:last").text()-0);
        let currpage = parseInt($(".page_num label:first").text()-0);
        if(currpage > 1){
            errorTips("请先跳转到第1页");
            unsafeWindow.isExporting = false;
            return false;
        }
        unsafeWindow.allUserData = wx.cgiData.user_list || [];
        let count = unsafeWindow.allUserData.length;
        let begin_openid = unsafeWindow.allUserData[count-1].id;
        let begin_create_time = unsafeWindow.allUserData[count-1].create_time;
        unsafeWindow.exportNum = parseInt($("#wx_export_num").val()-0);
        if(unsafeWindow.exportNum <= 0){
            errorTips("请填写页数");
            unsafeWindow.isExporting = false;
            return false;
        }

        //正在导出中...
        $(".tool_area").append('<div id="wx_export_tips" style="margin-top:10px;"></div>');

        let sleep = function (ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        let doGetUsers = async function() {
            let exportedPage = 1;
            for(let i = 2; i<=total; i++){
                //停止
                if(unsafeWindow.isStop == 1) {
                    unsafeWindow.isStop = 0;
                    break;
                }
                //超过页数
                if(i > unsafeWindow.exportNum || i > total) break;

                //记录请求时间(毫秒)
                lastRequestTime = new Date().getTime();

                //console.log('page ' + i, 1111, unsafeWindow.isExporting);
                $("#wx_export_tips").html("正在导出第"+i+"页,请勿关闭页面...&nbsp;&nbsp;&nbsp;&nbsp;<a href='javascript:;' onclick='isStop = 1;'>停止</a>");
                document.querySelector("a.page_next").click();

                /*
                let userUrl = "https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_user_list&groupid=-2&begin_openid="+begin_openid+"&begin_create_time="+begin_create_time+"&limit=20&offset=20&backfoward=1&token=" + wx.data.t + "&lang=zh_CN&f=json&ajax=1&random="+Math.random();
                console.log(2222, userUrl);
                GM_xmlhttpRequest({
                    method: "get",
                    url: userUrl,
                    onload: function(res){
                        if(res.status === 200){
                            console.log('sucess')
                            console.log(333, res);
                            let json = JSON.parse(res.response);
                            let userList = json.user_list.user_info_list || [];
                            console.log(4444, userList);
                            for(let j in userList) {
                                if(userList[j]) {
                                    unsafeWindow.allUserData.push(userList[j]);
                                    begin_openid = userList[j].user_openid;
                                    begin_create_time = userList[j].user_create_time;
                                }
                            }
                        }else{
                            console.log('error')
                            console.log(res)
                        }
                    },
                    onerror : function(err){
                        console.log('error')
                        console.log(err)
                    }
                });
                */
                exportedPage = i;
                await sleep(2000);
                //console.log('request-time', lastResponseTime, lastRequestTime, lastResponseTime - lastRequestTime);
                //如果接口未加载成功延迟2秒重试,最多延迟8秒
                if(lastResponseTime - lastRequestTime > 0) ; else await sleep(2000);
                if(lastResponseTime - lastRequestTime > 0) ; else await sleep(2000);
                if(lastResponseTime - lastRequestTime > 0) ; else await sleep(2000);
            } //for end
            wx_user_export(unsafeWindow.allUserData, "微信公众号粉丝前" + exportedPage + "页");
            $("#wx_export_tips").html("");
            unsafeWindow.isExporting = false;
        } //doGetUsers end
        doGetUsers();
    }

    //导出用户数据
    //unsafeWindow.wx_user_export_page = 1;
    unsafeWindow.wx_user_export = function(data, name) {
        //console.log(data,6666);
        let jsonData = data || unsafeWindow.userData;
        //列标题,逗号隔开,每一个逗号就是隔开一个单元格
        let str = `昵称,关注时间,openid,头像图片\n`;
        for (let i in jsonData) {
            let user = jsonData[i];
            user.user_openid = user.user_openid || user.id;
            if (!user.user_openid) continue;
            user.user_create_time = user.user_create_time || user.create_time;
            let localDate = new Date(user.user_create_time * 1000).toLocaleString();
            user.user_name = user.user_name || user.nick_name;
            user.user_head_img = user.user_head_img || "";
            str += `${user.user_name},${localDate},${user.user_openid},${user.user_head_img}\n`;
        }
        //encodeURIComponent解决中文乱码
        let uri = 'data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(str);
        //通过创建a标签实现
        var link = document.createElement("a");
        link.href = uri;
        //对下载的文件命名
        let currpage = parseInt($(".page_num label:first").text()-0);
        let today = new Date().toLocaleDateString().replace(/\//g,'-');
        link.download = (name || "微信公众号粉丝第" + currpage + "页") + "-" + today + ".csv";
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    //http请求
    function httpRequest(url, fn, method, data) {
        method = method || "get";
        data = data || "";
        GM_xmlhttpRequest({
            method: method,
            url: url,
            data: data,
            headers:  {
                "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                "referer": "https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_all_data&lang=zh_CN&token="+wx.data.t
            },
            onload: function(res){
                if(res.status === 200){
                    //console.log('sucess',method,url);
                    if(fn) fn(res);
                }else{
                    console.log('error',method,url);
                    console.log(res);
                    if(fn) fn(null);
                }
            },
            onerror : function(err){
                console.log('error',method,url);
                console.log(err);
                if(fn) fn(null);
            }
        });
    }

    //第一页用户数据(从js变量获取)
    window.addEventListener("load", function() {
        if (wx && wx.cgiData && wx.cgiData.user_list) {
            let userList = wx.cgiData.user_list || [];
            addFllowInfo(userList);
            unsafeWindow.userData = userList;

            ////首页获取图片地址

            //获取第一个用户的信息
            let userInfoUrl = "https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_fans_info";
            httpRequest(userInfoUrl, function(res){
                if(res && res.response){
                    let data = JSON.parse(res.response);
                    //console.log(11111,userInfoUrl,data);
                    let userList = data.user_list.user_info_list || [];
                    if(userList.length > 0) {
                        let userInfo = userList[0];
                        setTimeout(function(){
                            wx.cgiData.user_list[0].user_head_img = userInfo.user_head_img;
                        }, 500);
                    }
                }
            }, "post", "token="+wx.data.t+"&lang=zh_CN&f=json&ajax=1&random="+Math.random()+"&user_openid="+wx.cgiData.user_list[0].id);


            //获取用户列表
            let userListUrl = "https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_user_list&groupid=-2&begin_openid="+wx.cgiData.user_list[0].id+"&begin_create_time="+wx.cgiData.user_list[0].create_time+"&limit=20&offset=0&backfoward=1&token=" + wx.data.t + "&lang=zh_CN&f=json&ajax=1&random="+Math.random();
            httpRequest(userListUrl, function(res){
                if(res && res.response){
                    let data = JSON.parse(res.response);
                    //console.log(2222,userListUrl,data);
                    let userList = data.user_list.user_info_list || [];
                    let userMap = {};
                    if(userList.length > 0) {
                         for (let n in userList) {
                             if(userList[n] && userList[n].user_openid) userMap[userList[n].user_openid] = userList[n].user_head_img;
                         }
                    }
                    setTimeout(function(){
                        for (let i in wx.cgiData.user_list) {
                            let item = wx.cgiData.user_list[i];
                            if(userMap[item.id]) item.user_head_img = userMap[item.id];
                            wx.cgiData.user_list[i] = item;
                        }
                    }, 500);
                }
            }, "get");
        }
    });
})();