黑科技,Python 脚本帮你找出微信上删除你好友的人
本文来自稀土掘金江昪编译自 github:0x5e/wechat-deleted-friends
“ 清理下[微笑],不用回。你的朋友圈没事也该清清了,打开设置,通用,功能,群助手,全选,把我的信息粘贴一下,就可以了,发送就知道谁把你删了,方便你清人,不清不知道 ,一清吓一跳。”
相信大家在微信上一定被上面的这段话刷过屏,群发消息应该算是微信上流传最广的找到删除好友的方法了。但群发消息不仅仅会把通讯录里面所有的好友骚扰一遍,而且你还得挨个删除好几百个聊天记录,回复大家的疑问和鄙视。作为一个互联网从业者,除了群发消息就不能有更高效、不打扰好友的方式么?
答案是当然有,本人的微:1613161916可以一起讨论python,还有许多python资料可以发送。
微信在拉好友进群聊的时候,如果这个人删除了你好友的话,会提示你一下「请先发送朋友验证申请给某某,对方将你加为微信朋友后,你才能邀请其加入群聊。」有办法了,那我把微信好友拉一个大群里面,然后默默的删掉微信群不就好了么。
于是 github 上就有一位叫 0x5e 的开发者写了这么一个 python 脚本来代替你手动拉群和踢人。经过笔者测试,目前只能支持 mac 的操作系统。在此之前,笔者在稀土掘金上先放出了github的方法,这里再详细提下。
在 0x5e 的 github 代码仓库描述里面,他具体的实现步骤是用 python 脚本处理网页版微信的拉群、踢人操作。具体的步骤如下:
1、下载代码文件 wdf.py
2、打开 terminal 输入:python 然后拖动刚才下载的 wdf.py 到 terminal 窗后回车。命令格式类似: python wdf.py
3、接下来按步骤扫码操作即可;
查询结果可能会引起一些心理上的不适,请小心使用。
笔者测试了两次,这个 python 脚本还是有一些小问题,欢迎开发者们给原作者发 pull request 完善:
1、两次结果稍有出入,可能是微信网页版返回数据的问题
2、最终在微信会遗留一个只有自己的群组,需要手动删除
3、此脚本暂时不支持查找被拉黑的情况
源码
#!/usr/bin/env python # coding=utf-8
#本人威信:1613161916
from __future__ import print_function import os import requests import re import time import xml.dom.minidom import json import sys import math import subprocess import ssl import threading debug = false max_group_num = 2 # 每组人数 interface_calling_interval = 5 # 接口调用时间间隔, 间隔太短容易出现"操作太频繁", 会被限制操作半小时左右 max_progress_len = 50 qrimagepath = os.path.join(os.getcwd(), 'qrcode.jpg') tip = 0 uuid = '' base_uri = '' redirect_uri = '' push_uri = '' skey = '' wxsid = '' wxuin = '' pass_ticket = '' deviceid = 'e000000000000000' baserequest = {} contactlist = [] my = [] synckey = [] try: xrange range = xrange except: # python 3 pass def responsestate(func, baseresponse): errmsg = baseresponse['errmsg'] ret = baseresponse['ret'] if debug or ret != 0: print('func: %s, ret: %d, errmsg: %s' % (func, ret, errmsg)) if ret != 0: return false return true def getuuid(): global uuid url = 'https://login.weixin.qq.com/jslogin' params = { 'appid': 'wx782c26e4c19acffb', 'fun': 'new', 'lang': 'zh_cn', '_': int(time.time()), } r= myrequests.get(url=url, params=params) r.encoding = 'utf-8' data = r.text # print(data) # window.qrlogin.code = 200; window.qrlogin.uuid = "ozwt_bffrg=="; regx = r'window.qrlogin.code = (\d+); window.qrlogin.uuid = "(\s+?)"' pm = re.search(regx, data) code = pm.group(1) uuid = pm.group(2) if code == '200': return true return false def showqrimage(): global tip url = 'https://login.weixin.qq.com/qrcode/' + uuid params = { 't': 'webwx', '_': int(time.time()), } r = myrequests.get(url=url, params=params) tip = 1 f = open(qrimagepath, 'wb') f.write(r.content) f.close() time.sleep(1) if sys.platform.find('darwin') >= 0: subprocess.call(['open', qrimagepath]) elif sys.platform.find('linux') >= 0: subprocess.call(['xdg-open', qrimagepath]) else: os.startfile(qrimagepath) print('请使用微信扫描二维码以登录') def waitforlogin(): global tip, base_uri, redirect_uri, push_uri url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % ( tip, uuid, int(time.time())) r = myrequests.get(url=url) r.encoding = 'utf-8' data = r.text # print(data) # window.code=500; regx = r'window.code=(\d+);' pm = re.search(regx, data) code = pm.group(1) if code == '201': # 已扫描 print('成功扫描,请在手机上点击确认以登录') tip = 0 elif code == '200': # 已登录 print('正在登录...') regx = r'window.redirect_uri="(\s+?)";' pm = re.search(regx, data) redirect_uri = pm.group(1) + '&fun=new' base_uri = redirect_uri[:redirect_uri.rfind('/')] # push_uri与base_uri对应关系(排名分先后)(就是这么奇葩..) services = [ ('wx2.qq.com', 'webpush2.weixin.qq.com'), ('qq.com', 'webpush.weixin.qq.com'), ('web1.wechat.com', 'webpush1.wechat.com'), ('web2.wechat.com', 'webpush2.wechat.com'), ('wechat.com', 'webpush.wechat.com'), ('web1.wechatapp.com', 'webpush1.wechatapp.com'), ] push_uri = base_uri for (searchurl, pushurl) in services: if base_uri.find(searchurl) >= 0: push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushurl break # closeqrimage if sys.platform.find('darwin') >= 0: # for osx with preview os.system("osascript -e 'quit app \"preview\"'") elif code == '408': # 超时 pass # elif code == '400' or code == '500': return code def login(): global skey, wxsid, wxuin, pass_ticket, baserequest r = myrequests.get(url=redirect_uri) r.encoding = 'utf-8' data = r.text # print(data) doc = xml.dom.minidom.parsestring(data) root = doc.documentelement for node in root.childnodes: if node.nodename == 'skey': skey = node.childnodes[0].data elif node.nodename == 'wxsid': wxsid = node.childnodes[0].data elif node.nodename == 'wxuin': wxuin = node.childnodes[0].data elif node.nodename == 'pass_ticket': pass_ticket = node.childnodes[0].data # print('skey: %s, wxsid: %s, wxuin: %s, pass_ticket: %s' % (skey, wxsid, # wxuin, pass_ticket)) if not all((skey, wxsid, wxuin, pass_ticket)): return false baserequest = { 'uin': int(wxuin), 'sid': wxsid, 'skey': skey, 'deviceid': deviceid, } return true def webwxinit(): url = (base_uri + '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % ( pass_ticket, skey, int(time.time())) ) params = {'baserequest': baserequest } headers = {'content-type': 'application/json; charset=utf-8'} r = myrequests.post(url=url, data=json.dumps(params),headers=headers) r.encoding = 'utf-8' data = r.json() if debug: f = open(os.path.join(os.getcwd(), 'webwxinit.json'), 'wb') f.write(r.content) f.close() # print(data) global contactlist, my, synckey dic = data contactlist = dic['contactlist'] my = dic['user'] synckey = dic['synckey'] state = responsestate('webwxinit', dic['baseresponse']) return state def webwxgetcontact(): url = (base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % ( pass_ticket, skey, int(time.time())) ) headers = {'content-type': 'application/json; charset=utf-8'} r = myrequests.post(url=url,headers=headers) r.encoding = 'utf-8' data = r.json() if debug: f = open(os.path.join(os.getcwd(), 'webwxgetcontact.json'), 'wb') f.write(r.content) f.close() # print(data) dic = data memberlist = dic['memberlist'] # 倒序遍历,不然删除的时候出问题.. specialusers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", "notification_messages", "wxitil", "userexperience_alarm"] for i in range(len(memberlist) - 1, -1, -1): member = memberlist[i] if member['verifyflag'] & 8 != 0: # 公众号/服务号 memberlist.remove(member) elif member['username'] in specialusers: # 特殊账号 memberlist.remove(member) elif member['username'].find('@@') != -1: # 群聊 memberlist.remove(member) elif member['username'] == my['username']: # 自己 memberlist.remove(member) return memberlist def createchatroom(usernames): memberlist = [{'username': username} for username in usernames] url = (base_uri + '/webwxcreatechatroom?pass_ticket=%s&r=%s' % ( pass_ticket, int(time.time())) ) params = { 'baserequest': baserequest, 'membercount': len(memberlist), 'memberlist': memberlist, 'topic': '', } headers = {'content-type': 'application/json; charset=utf-8'} r = myrequests.post(url=url, data=json.dumps(params),headers=headers) r.encoding = 'utf-8' data = r.json() # print(data) dic = data chatroomname = dic['chatroomname'] memberlist = dic['memberlist'] deletedlist = [] blockedlist = [] for member in memberlist: if member['memberstatus'] == 4: # 被对方删除了 deletedlist.append(member['username']) elif member['memberstatus'] == 3: # 被加入黑名单 blockedlist.append(member['username']) state = responsestate('createchatroom', dic['baseresponse']) return chatroomname, deletedlist, blockedlist def deletemember(chatroomname, usernames): url = (base_uri + '/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % (pass_ticket) ) params = { 'baserequest': baserequest, 'chatroomname': chatroomname, 'delmemberlist': ','.join(usernames), } headers = {'content-type': 'application/json; charset=utf-8'} r = myrequests.post(url=url, data=json.dumps(params),headers=headers) r.encoding = 'utf-8' data = r.json() # print(data) dic = data state = responsestate('deletemember', dic['baseresponse']) return state def addmember(chatroomname, usernames): url = (base_uri + '/webwxupdatechatroom?fun=addmember&pass_ticket=%s' % (pass_ticket) ) params = { 'baserequest': baserequest, 'chatroomname': chatroomname, 'addmemberlist': ','.join(usernames), } headers = {'content-type': 'application/json; charset=utf-8'} r = myrequests.post(url=url, data=json.dumps(params),headers=headers) r.encoding = 'utf-8' data = r.json() # print(data) dic = data memberlist = dic['memberlist'] deletedlist = [] blockedlist = [] for member in memberlist: if member['memberstatus'] == 4: # 被对方删除了 deletedlist.append(member['username']) elif member['memberstatus'] == 3: # 被加入黑名单 blockedlist.append(member['username']) state = responsestate('addmember', dic['baseresponse']) return deletedlist, blockedlist def synckey(): synckeyitems = ['%s_%s' % (item['key'], item['val']) for item in synckey['list']] synckeystr = '|'.join(synckeyitems) return synckeystr def synccheck(): url = push_uri + '/synccheck?' params = { 'skey': baserequest['skey'], 'sid': baserequest['sid'], 'uin': baserequest['uin'], 'deviceid': baserequest['deviceid'], 'synckey': synckey(), 'r': int(time.time()), } r = myrequests.get(url=url,params=params) r.encoding = 'utf-8' data = r.text # print(data) # window.synccheck={retcode:"0",selector:"2"} regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}' pm = re.search(regx, data) retcode = pm.group(1) selector = pm.group(2) return selector def webwxsync(): global synckey url = base_uri + '/webwxsync?lang=zh_cn&skey=%s&sid=%s&pass_ticket=%s' % ( baserequest['skey'], baserequest['sid'], quote_plus(pass_ticket)) params = { 'baserequest': baserequest, 'synckey': synckey, 'rr': ~int(time.time()), } headers = {'content-type': 'application/json; charset=utf-8'} r = myrequests.post(url=url, data=json.dumps(params)) r.encoding = 'utf-8' data = r.json() # print(data) dic = data synckey = dic['synckey'] state = responsestate('webwxsync', dic['baseresponse']) return state def heartbeatloop(): while true: selector = synccheck() if selector != '0': webwxsync() time.sleep(1) def main(): global myrequests if hasattr(ssl, '_create_unverified_context'): ssl._create_default_https_context = ssl._create_unverified_context headers = {'user-agent': 'mozilla/5.0 (macintosh; intel mac os x 10_11_2) applewebkit/537.36 (khtml, like gecko) chrome/44.0.2403.125 safari/537.36'} myrequests = requests.session() myrequests.headers.update(headers) if not getuuid(): print('获取uuid失败') return print('正在获取二维码图片...') showqrimage() while waitforlogin() != '200': pass os.remove(qrimagepath) if not login(): print('登录失败') return if not webwxinit(): print('初始化失败') return memberlist = webwxgetcontact() print('开启心跳线程') threading.thread(target=heartbeatloop) membercount = len(memberlist) print('通讯录共%s位好友' % membercount) chatroomname = '' result = [] d = {} for member in memberlist: d[member['username']] = (member['nickname'], member['remarkname']) print('开始查找...') group_num = int(math.ceil(membercount / float(max_group_num))) for i in range(0, group_num): usernames = [] for j in range(0, max_group_num): if i * max_group_num + j >= membercount: break member = memberlist[i * max_group_num + j] usernames.append(member['username']) # 新建群组/添加成员 if chatroomname == '': (chatroomname, deletedlist, blockedlist) = createchatroom( usernames) else: (deletedlist, blockedlist) = addmember(chatroomname, usernames) # todo blockedlist 被拉黑列表 deletedcount = len(deletedlist) if deletedcount > 0: result += deletedlist # 删除成员 deletemember(chatroomname, usernames) # 进度条 progress = max_progress_len * (i + 1) / group_num print('[', '#' * int(progress), '-' * int(max_progress_len - progress), ']', end=' ') print('新发现你被%d人删除' % deletedcount) for i in range(deletedcount): if d[deletedlist[i]][1] != '': print('%s(%s)' % (d[deletedlist[i]][0],d[deletedlist[i]][1])) else: print(d[deletedlist[i]][0]) if i != group_num - 1: print('正在继续查找,请耐心等待...') # 下一次进行接口调用需要等待的时间 time.sleep(interface_calling_interval) # todo 删除群组 print('\n结果汇总完毕,20s后可重试...') resultnames = [] for r in result: if d[r][1] != '': resultnames.append('%s(%s)' % (d[r][0],d[r][1])) else: resultnames.append(d[r][0]) print('---------- 被删除的好友列表(共%d人) ----------' % len(result)) # 过滤emoji resultnames = list(map(lambda x: re.sub(r'<span.+/span>', '', x), resultnames)) if len(resultnames): print('\n'.join(resultnames)) else: print("无") print('---------------------------------------------') # windows下编码问题修复 # http://blog.csdn.net/heyuxuanzee/article/details/8442718 class unicodestreamfilter: def __init__(self, target): self.target = target self.encoding = 'utf-8' self.errors = 'replace' self.encode_to = self.target.encoding def write(self, s): if type(s) == str: try: s = s.decode('utf-8') except: pass s = s.encode(self.encode_to, self.errors).decode(self.encode_to) self.target.write(s) if sys.stdout.encoding == 'cp936': sys.stdout = unicodestreamfilter(sys.stdout) if __name__ == '__main__': print('本程序的查询结果可能会引起一些心理上的不适,请小心使用...') print('1小时内只能使用一次,否则会因操作繁忙阻止建群') main() print('回车键退出...') input()