mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-02-08 09:12:19 +08:00
489 lines
20 KiB
Python
489 lines
20 KiB
Python
import time, re, io
|
|
import json, copy
|
|
import logging
|
|
|
|
from .. import config, utils
|
|
from ..components.contact import accept_friend
|
|
from ..returnvalues import ReturnValue
|
|
from ..storage import contact_change
|
|
from ..utils import update_info_dict
|
|
|
|
logger = logging.getLogger('itchat')
|
|
|
|
def load_contact(core):
|
|
core.update_chatroom = update_chatroom
|
|
core.update_friend = update_friend
|
|
core.get_contact = get_contact
|
|
core.get_friends = get_friends
|
|
core.get_chatrooms = get_chatrooms
|
|
core.get_mps = get_mps
|
|
core.set_alias = set_alias
|
|
core.set_pinned = set_pinned
|
|
core.accept_friend = accept_friend
|
|
core.get_head_img = get_head_img
|
|
core.create_chatroom = create_chatroom
|
|
core.set_chatroom_name = set_chatroom_name
|
|
core.delete_member_from_chatroom = delete_member_from_chatroom
|
|
core.add_member_into_chatroom = add_member_into_chatroom
|
|
|
|
def update_chatroom(self, userName, detailedMember=False):
|
|
if not isinstance(userName, list):
|
|
userName = [userName]
|
|
url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
|
|
self.loginInfo['url'], int(time.time()))
|
|
headers = {
|
|
'ContentType': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT }
|
|
data = {
|
|
'BaseRequest': self.loginInfo['BaseRequest'],
|
|
'Count': len(userName),
|
|
'List': [{
|
|
'UserName': u,
|
|
'ChatRoomId': '', } for u in userName], }
|
|
chatroomList = json.loads(self.s.post(url, data=json.dumps(data), headers=headers
|
|
).content.decode('utf8', 'replace')).get('ContactList')
|
|
if not chatroomList:
|
|
return ReturnValue({'BaseResponse': {
|
|
'ErrMsg': 'No chatroom found',
|
|
'Ret': -1001, }})
|
|
|
|
if detailedMember:
|
|
def get_detailed_member_info(encryChatroomId, memberList):
|
|
url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
|
|
self.loginInfo['url'], int(time.time()))
|
|
headers = {
|
|
'ContentType': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT, }
|
|
data = {
|
|
'BaseRequest': self.loginInfo['BaseRequest'],
|
|
'Count': len(memberList),
|
|
'List': [{
|
|
'UserName': member['UserName'],
|
|
'EncryChatRoomId': encryChatroomId} \
|
|
for member in memberList], }
|
|
return json.loads(self.s.post(url, data=json.dumps(data), headers=headers
|
|
).content.decode('utf8', 'replace'))['ContactList']
|
|
MAX_GET_NUMBER = 50
|
|
for chatroom in chatroomList:
|
|
totalMemberList = []
|
|
for i in range(int(len(chatroom['MemberList']) / MAX_GET_NUMBER + 1)):
|
|
memberList = chatroom['MemberList'][i*MAX_GET_NUMBER: (i+1)*MAX_GET_NUMBER]
|
|
totalMemberList += get_detailed_member_info(chatroom['EncryChatRoomId'], memberList)
|
|
chatroom['MemberList'] = totalMemberList
|
|
|
|
update_local_chatrooms(self, chatroomList)
|
|
r = [self.storageClass.search_chatrooms(userName=c['UserName'])
|
|
for c in chatroomList]
|
|
return r if 1 < len(r) else r[0]
|
|
|
|
def update_friend(self, userName):
|
|
if not isinstance(userName, list):
|
|
userName = [userName]
|
|
url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
|
|
self.loginInfo['url'], int(time.time()))
|
|
headers = {
|
|
'ContentType': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT }
|
|
data = {
|
|
'BaseRequest': self.loginInfo['BaseRequest'],
|
|
'Count': len(userName),
|
|
'List': [{
|
|
'UserName': u,
|
|
'EncryChatRoomId': '', } for u in userName], }
|
|
friendList = json.loads(self.s.post(url, data=json.dumps(data), headers=headers
|
|
).content.decode('utf8', 'replace')).get('ContactList')
|
|
|
|
update_local_friends(self, friendList)
|
|
r = [self.storageClass.search_friends(userName=f['UserName'])
|
|
for f in friendList]
|
|
return r if len(r) != 1 else r[0]
|
|
|
|
@contact_change
|
|
def update_local_chatrooms(core, l):
|
|
'''
|
|
get a list of chatrooms for updating local chatrooms
|
|
return a list of given chatrooms with updated info
|
|
'''
|
|
for chatroom in l:
|
|
# format new chatrooms
|
|
utils.emoji_formatter(chatroom, 'NickName')
|
|
for member in chatroom['MemberList']:
|
|
if 'NickName' in member:
|
|
utils.emoji_formatter(member, 'NickName')
|
|
if 'DisplayName' in member:
|
|
utils.emoji_formatter(member, 'DisplayName')
|
|
if 'RemarkName' in member:
|
|
utils.emoji_formatter(member, 'RemarkName')
|
|
# update it to old chatrooms
|
|
oldChatroom = utils.search_dict_list(
|
|
core.chatroomList, 'UserName', chatroom['UserName'])
|
|
if oldChatroom:
|
|
update_info_dict(oldChatroom, chatroom)
|
|
# - update other values
|
|
memberList = chatroom.get('MemberList', [])
|
|
oldMemberList = oldChatroom['MemberList']
|
|
if memberList:
|
|
for member in memberList:
|
|
oldMember = utils.search_dict_list(
|
|
oldMemberList, 'UserName', member['UserName'])
|
|
if oldMember:
|
|
update_info_dict(oldMember, member)
|
|
else:
|
|
oldMemberList.append(member)
|
|
else:
|
|
core.chatroomList.append(chatroom)
|
|
oldChatroom = utils.search_dict_list(
|
|
core.chatroomList, 'UserName', chatroom['UserName'])
|
|
# delete useless members
|
|
if len(chatroom['MemberList']) != len(oldChatroom['MemberList']) and \
|
|
chatroom['MemberList']:
|
|
existsUserNames = [member['UserName'] for member in chatroom['MemberList']]
|
|
delList = []
|
|
for i, member in enumerate(oldChatroom['MemberList']):
|
|
if member['UserName'] not in existsUserNames:
|
|
delList.append(i)
|
|
delList.sort(reverse=True)
|
|
for i in delList:
|
|
del oldChatroom['MemberList'][i]
|
|
# - update OwnerUin
|
|
if oldChatroom.get('ChatRoomOwner') and oldChatroom.get('MemberList'):
|
|
owner = utils.search_dict_list(oldChatroom['MemberList'],
|
|
'UserName', oldChatroom['ChatRoomOwner'])
|
|
oldChatroom['OwnerUin'] = (owner or {}).get('Uin', 0)
|
|
# - update IsAdmin
|
|
if 'OwnerUin' in oldChatroom and oldChatroom['OwnerUin'] != 0:
|
|
oldChatroom['IsAdmin'] = \
|
|
oldChatroom['OwnerUin'] == int(core.loginInfo['wxuin'])
|
|
else:
|
|
oldChatroom['IsAdmin'] = None
|
|
# - update Self
|
|
newSelf = utils.search_dict_list(oldChatroom['MemberList'],
|
|
'UserName', core.storageClass.userName)
|
|
oldChatroom['Self'] = newSelf or copy.deepcopy(core.loginInfo['User'])
|
|
return {
|
|
'Type' : 'System',
|
|
'Text' : [chatroom['UserName'] for chatroom in l],
|
|
'SystemInfo' : 'chatrooms',
|
|
'FromUserName' : core.storageClass.userName,
|
|
'ToUserName' : core.storageClass.userName, }
|
|
|
|
@contact_change
|
|
def update_local_friends(core, l):
|
|
'''
|
|
get a list of friends or mps for updating local contact
|
|
'''
|
|
fullList = core.memberList + core.mpList
|
|
for friend in l:
|
|
if 'NickName' in friend:
|
|
utils.emoji_formatter(friend, 'NickName')
|
|
if 'DisplayName' in friend:
|
|
utils.emoji_formatter(friend, 'DisplayName')
|
|
if 'RemarkName' in friend:
|
|
utils.emoji_formatter(friend, 'RemarkName')
|
|
oldInfoDict = utils.search_dict_list(
|
|
fullList, 'UserName', friend['UserName'])
|
|
if oldInfoDict is None:
|
|
oldInfoDict = copy.deepcopy(friend)
|
|
if oldInfoDict['VerifyFlag'] & 8 == 0:
|
|
core.memberList.append(oldInfoDict)
|
|
else:
|
|
core.mpList.append(oldInfoDict)
|
|
else:
|
|
update_info_dict(oldInfoDict, friend)
|
|
|
|
@contact_change
|
|
def update_local_uin(core, msg):
|
|
'''
|
|
content contains uins and StatusNotifyUserName contains username
|
|
they are in same order, so what I do is to pair them together
|
|
|
|
I caught an exception in this method while not knowing why
|
|
but don't worry, it won't cause any problem
|
|
'''
|
|
uins = re.search('<username>([^<]*?)<', msg['Content'])
|
|
usernameChangedList = []
|
|
r = {
|
|
'Type': 'System',
|
|
'Text': usernameChangedList,
|
|
'SystemInfo': 'uins', }
|
|
if uins:
|
|
uins = uins.group(1).split(',')
|
|
usernames = msg['StatusNotifyUserName'].split(',')
|
|
if 0 < len(uins) == len(usernames):
|
|
for uin, username in zip(uins, usernames):
|
|
if not '@' in username: continue
|
|
fullContact = core.memberList + core.chatroomList + core.mpList
|
|
userDicts = utils.search_dict_list(fullContact,
|
|
'UserName', username)
|
|
if userDicts:
|
|
if userDicts.get('Uin', 0) == 0:
|
|
userDicts['Uin'] = uin
|
|
usernameChangedList.append(username)
|
|
logger.debug('Uin fetched: %s, %s' % (username, uin))
|
|
else:
|
|
if userDicts['Uin'] != uin:
|
|
logger.debug('Uin changed: %s, %s' % (
|
|
userDicts['Uin'], uin))
|
|
else:
|
|
if '@@' in username:
|
|
core.storageClass.updateLock.release()
|
|
update_chatroom(core, username)
|
|
core.storageClass.updateLock.acquire()
|
|
newChatroomDict = utils.search_dict_list(
|
|
core.chatroomList, 'UserName', username)
|
|
if newChatroomDict is None:
|
|
newChatroomDict = utils.struct_friend_info({
|
|
'UserName': username,
|
|
'Uin': uin,
|
|
'Self': copy.deepcopy(core.loginInfo['User'])})
|
|
core.chatroomList.append(newChatroomDict)
|
|
else:
|
|
newChatroomDict['Uin'] = uin
|
|
elif '@' in username:
|
|
core.storageClass.updateLock.release()
|
|
update_friend(core, username)
|
|
core.storageClass.updateLock.acquire()
|
|
newFriendDict = utils.search_dict_list(
|
|
core.memberList, 'UserName', username)
|
|
if newFriendDict is None:
|
|
newFriendDict = utils.struct_friend_info({
|
|
'UserName': username,
|
|
'Uin': uin, })
|
|
core.memberList.append(newFriendDict)
|
|
else:
|
|
newFriendDict['Uin'] = uin
|
|
usernameChangedList.append(username)
|
|
logger.debug('Uin fetched: %s, %s' % (username, uin))
|
|
else:
|
|
logger.debug('Wrong length of uins & usernames: %s, %s' % (
|
|
len(uins), len(usernames)))
|
|
else:
|
|
logger.debug('No uins in 51 message')
|
|
logger.debug(msg['Content'])
|
|
return r
|
|
|
|
def get_contact(self, update=False):
|
|
if not update:
|
|
return utils.contact_deep_copy(self, self.chatroomList)
|
|
def _get_contact(seq=0):
|
|
url = '%s/webwxgetcontact?r=%s&seq=%s&skey=%s' % (self.loginInfo['url'],
|
|
int(time.time()), seq, self.loginInfo['skey'])
|
|
headers = {
|
|
'ContentType': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT, }
|
|
try:
|
|
r = self.s.get(url, headers=headers)
|
|
except:
|
|
logger.info('Failed to fetch contact, that may because of the amount of your chatrooms')
|
|
for chatroom in self.get_chatrooms():
|
|
self.update_chatroom(chatroom['UserName'], detailedMember=True)
|
|
return 0, []
|
|
j = json.loads(r.content.decode('utf-8', 'replace'))
|
|
return j.get('Seq', 0), j.get('MemberList')
|
|
seq, memberList = 0, []
|
|
while 1:
|
|
seq, batchMemberList = _get_contact(seq)
|
|
memberList.extend(batchMemberList)
|
|
if seq == 0:
|
|
break
|
|
chatroomList, otherList = [], []
|
|
for m in memberList:
|
|
if m['Sex'] != 0:
|
|
otherList.append(m)
|
|
elif '@@' in m['UserName']:
|
|
chatroomList.append(m)
|
|
elif '@' in m['UserName']:
|
|
# mp will be dealt in update_local_friends as well
|
|
otherList.append(m)
|
|
if chatroomList:
|
|
update_local_chatrooms(self, chatroomList)
|
|
if otherList:
|
|
update_local_friends(self, otherList)
|
|
return utils.contact_deep_copy(self, chatroomList)
|
|
|
|
def get_friends(self, update=False):
|
|
if update:
|
|
self.get_contact(update=True)
|
|
return utils.contact_deep_copy(self, self.memberList)
|
|
|
|
def get_chatrooms(self, update=False, contactOnly=False):
|
|
if contactOnly:
|
|
return self.get_contact(update=True)
|
|
else:
|
|
if update:
|
|
self.get_contact(True)
|
|
return utils.contact_deep_copy(self, self.chatroomList)
|
|
|
|
def get_mps(self, update=False):
|
|
if update: self.get_contact(update=True)
|
|
return utils.contact_deep_copy(self, self.mpList)
|
|
|
|
def set_alias(self, userName, alias):
|
|
oldFriendInfo = utils.search_dict_list(
|
|
self.memberList, 'UserName', userName)
|
|
if oldFriendInfo is None:
|
|
return ReturnValue({'BaseResponse': {
|
|
'Ret': -1001, }})
|
|
url = '%s/webwxoplog?lang=%s&pass_ticket=%s' % (
|
|
self.loginInfo['url'], 'zh_CN', self.loginInfo['pass_ticket'])
|
|
data = {
|
|
'UserName' : userName,
|
|
'CmdId' : 2,
|
|
'RemarkName' : alias,
|
|
'BaseRequest' : self.loginInfo['BaseRequest'], }
|
|
headers = { 'User-Agent' : config.USER_AGENT}
|
|
r = self.s.post(url, json.dumps(data, ensure_ascii=False).encode('utf8'),
|
|
headers=headers)
|
|
r = ReturnValue(rawResponse=r)
|
|
if r:
|
|
oldFriendInfo['RemarkName'] = alias
|
|
return r
|
|
|
|
def set_pinned(self, userName, isPinned=True):
|
|
url = '%s/webwxoplog?pass_ticket=%s' % (
|
|
self.loginInfo['url'], self.loginInfo['pass_ticket'])
|
|
data = {
|
|
'UserName' : userName,
|
|
'CmdId' : 3,
|
|
'OP' : int(isPinned),
|
|
'BaseRequest' : self.loginInfo['BaseRequest'], }
|
|
headers = { 'User-Agent' : config.USER_AGENT}
|
|
r = self.s.post(url, json=data, headers=headers)
|
|
return ReturnValue(rawResponse=r)
|
|
|
|
def accept_friend(self, userName, v4= '', autoUpdate=True):
|
|
url = f"{self.loginInfo['url']}/webwxverifyuser?r={int(time.time())}&pass_ticket={self.loginInfo['pass_ticket']}"
|
|
data = {
|
|
'BaseRequest': self.loginInfo['BaseRequest'],
|
|
'Opcode': 3, # 3
|
|
'VerifyUserListSize': 1,
|
|
'VerifyUserList': [{
|
|
'Value': userName,
|
|
'VerifyUserTicket': v4, }],
|
|
'VerifyContent': '',
|
|
'SceneListCount': 1,
|
|
'SceneList': [33],
|
|
'skey': self.loginInfo['skey'], }
|
|
headers = {
|
|
'ContentType': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT }
|
|
r = self.s.post(url, headers=headers,
|
|
data=json.dumps(data, ensure_ascii=False).encode('utf8', 'replace'))
|
|
if autoUpdate:
|
|
self.update_friend(userName)
|
|
return ReturnValue(rawResponse=r)
|
|
|
|
def get_head_img(self, userName=None, chatroomUserName=None, picDir=None):
|
|
''' get head image
|
|
* if you want to get chatroom header: only set chatroomUserName
|
|
* if you want to get friend header: only set userName
|
|
* if you want to get chatroom member header: set both
|
|
'''
|
|
params = {
|
|
'userName': userName or chatroomUserName or self.storageClass.userName,
|
|
'skey': self.loginInfo['skey'],
|
|
'type': 'big', }
|
|
url = '%s/webwxgeticon' % self.loginInfo['url']
|
|
if chatroomUserName is None:
|
|
infoDict = self.storageClass.search_friends(userName=userName)
|
|
if infoDict is None:
|
|
return ReturnValue({'BaseResponse': {
|
|
'ErrMsg': 'No friend found',
|
|
'Ret': -1001, }})
|
|
else:
|
|
if userName is None:
|
|
url = '%s/webwxgetheadimg' % self.loginInfo['url']
|
|
else:
|
|
chatroom = self.storageClass.search_chatrooms(userName=chatroomUserName)
|
|
if chatroomUserName is None:
|
|
return ReturnValue({'BaseResponse': {
|
|
'ErrMsg': 'No chatroom found',
|
|
'Ret': -1001, }})
|
|
if 'EncryChatRoomId' in chatroom:
|
|
params['chatroomid'] = chatroom['EncryChatRoomId']
|
|
params['chatroomid'] = params.get('chatroomid') or chatroom['UserName']
|
|
headers = { 'User-Agent' : config.USER_AGENT}
|
|
r = self.s.get(url, params=params, stream=True, headers=headers)
|
|
tempStorage = io.BytesIO()
|
|
for block in r.iter_content(1024):
|
|
tempStorage.write(block)
|
|
if picDir is None:
|
|
return tempStorage.getvalue()
|
|
with open(picDir, 'wb') as f:
|
|
f.write(tempStorage.getvalue())
|
|
tempStorage.seek(0)
|
|
return ReturnValue({'BaseResponse': {
|
|
'ErrMsg': 'Successfully downloaded',
|
|
'Ret': 0, },
|
|
'PostFix': utils.get_image_postfix(tempStorage.read(20)), })
|
|
|
|
def create_chatroom(self, memberList, topic=''):
|
|
url = '%s/webwxcreatechatroom?pass_ticket=%s&r=%s' % (
|
|
self.loginInfo['url'], self.loginInfo['pass_ticket'], int(time.time()))
|
|
data = {
|
|
'BaseRequest': self.loginInfo['BaseRequest'],
|
|
'MemberCount': len(memberList.split(',')),
|
|
'MemberList': [{'UserName': member} for member in memberList.split(',')],
|
|
'Topic': topic, }
|
|
headers = {
|
|
'content-type': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT }
|
|
r = self.s.post(url, headers=headers,
|
|
data=json.dumps(data, ensure_ascii=False).encode('utf8', 'ignore'))
|
|
return ReturnValue(rawResponse=r)
|
|
|
|
def set_chatroom_name(self, chatroomUserName, name):
|
|
url = '%s/webwxupdatechatroom?fun=modtopic&pass_ticket=%s' % (
|
|
self.loginInfo['url'], self.loginInfo['pass_ticket'])
|
|
data = {
|
|
'BaseRequest': self.loginInfo['BaseRequest'],
|
|
'ChatRoomName': chatroomUserName,
|
|
'NewTopic': name, }
|
|
headers = {
|
|
'content-type': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT }
|
|
r = self.s.post(url, headers=headers,
|
|
data=json.dumps(data, ensure_ascii=False).encode('utf8', 'ignore'))
|
|
return ReturnValue(rawResponse=r)
|
|
|
|
def delete_member_from_chatroom(self, chatroomUserName, memberList):
|
|
url = '%s/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % (
|
|
self.loginInfo['url'], self.loginInfo['pass_ticket'])
|
|
data = {
|
|
'BaseRequest': self.loginInfo['BaseRequest'],
|
|
'ChatRoomName': chatroomUserName,
|
|
'DelMemberList': ','.join([member['UserName'] for member in memberList]), }
|
|
headers = {
|
|
'content-type': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT}
|
|
r = self.s.post(url, data=json.dumps(data),headers=headers)
|
|
return ReturnValue(rawResponse=r)
|
|
|
|
def add_member_into_chatroom(self, chatroomUserName, memberList,
|
|
useInvitation=False):
|
|
''' add or invite member into chatroom
|
|
* there are two ways to get members into chatroom: invite or directly add
|
|
* but for chatrooms with more than 40 users, you can only use invite
|
|
* but don't worry we will auto-force userInvitation for you when necessary
|
|
'''
|
|
if not useInvitation:
|
|
chatroom = self.storageClass.search_chatrooms(userName=chatroomUserName)
|
|
if not chatroom: chatroom = self.update_chatroom(chatroomUserName)
|
|
if len(chatroom['MemberList']) > self.loginInfo['InviteStartCount']:
|
|
useInvitation = True
|
|
if useInvitation:
|
|
fun, memberKeyName = 'invitemember', 'InviteMemberList'
|
|
else:
|
|
fun, memberKeyName = 'addmember', 'AddMemberList'
|
|
url = '%s/webwxupdatechatroom?fun=%s&pass_ticket=%s' % (
|
|
self.loginInfo['url'], fun, self.loginInfo['pass_ticket'])
|
|
params = {
|
|
'BaseRequest' : self.loginInfo['BaseRequest'],
|
|
'ChatRoomName' : chatroomUserName,
|
|
memberKeyName : memberList, }
|
|
headers = {
|
|
'content-type': 'application/json; charset=UTF-8',
|
|
'User-Agent' : config.USER_AGENT}
|
|
r = self.s.post(url, data=json.dumps(params),headers=headers)
|
|
return ReturnValue(rawResponse=r)
|