微信公众号粉丝查看增强工具

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

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         微信公众号粉丝查看增强工具
// @namespace    https://greasyfork.org/zh-CN/scripts/428181
// @version      0.1.12
// @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/jquery@3.2.1/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>';
        }

        //绑定页面跳转按钮点击事件
        $(".page_go")[0].addEventListener("click", function(e) {
            //如果手动点击则自动跳转目标页
            if (e.isTrusted) {
                autoGoPage();
            }
        });

         //导出按钮
         $(".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");
                                if($(".frm_control_group.location").length === 0){
                                    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);

    //导出x页
    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 = unsafeWindow.userData || 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 = currpage;
            let fromPage = currpage + 1;
            for(let i = fromPage; i<=total; i++){
                //停止
                if(unsafeWindow.isStop == 1) {
                    unsafeWindow.isStop = 0;
                    break;
                }
                //超过页数
                let maxPage = currpage + (unsafeWindow.exportNum - 1);
                if(i > maxPage || i > total) break;

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

                //console.log('page ' + i, 1111, unsafeWindow.isExporting);
                $("#wx_export_tips").html("正在导出第"+i+"页,剩余"+(maxPage-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
            let exportedPageStr = currpage === exportedPage ? "" : "-" + exportedPage;
            wx_user_export(unsafeWindow.allUserData, "微信公众号粉丝第" + currpage + exportedPageStr + "页");
            $("#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);
            }
        });
    }

    //睡眠x毫秒
    let sleep = function (ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    //自动跳转页面
    let autoGoPage = async function() {
        let currpage = parseInt($(".page_num label:first").text()-0);
        let go = parseInt($(".goto_area :text").val()-0);
        if(!go) return;
        setTimeout(function(){$(".JS_TIPS.page_tips").hide();}, 0);

        //前进
        if ((go - currpage) > 25) {
            let max = Math.ceil((go-currpage)/25);
            let begin = currpage+25;
            for(let i=1;i<=max;i++) {
                if(begin >= go) begin = go;
                //console.log(i, begin, go);
                $(".goto_area :text").val(begin);
                await sleep(200);
                $(".page_go")[0].click();
                await sleep(2000);
                begin += 25;
            }
        }
        //后退
        if ((currpage - go) > 25) {
            let max = Math.ceil((currpage-go)/25);
            let begin = currpage-25;
            for(let i=1;i<=max;i++) {
                if(begin <= go) begin = go;
                //console.log(i, begin, go);
                $(".goto_area :text").val(begin);
                await sleep(200);
                $(".page_go")[0].click();
                await sleep(2000);
                begin -= 25;
            }
        }
    }

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

            //绑定页面跳转按钮点击事件
            $(".page_go")[0].addEventListener("click", function(e) {
                //如果手动点击则自动跳转目标页
                if (e.isTrusted) {
                    autoGoPage();
                }
            });

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

            //获取第一个用户的信息
            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");
        }
    });
})();