Skip to content

Instantly share code, notes, and snippets.

@leeyisoft
Created August 31, 2017 08:16
Show Gist options
  • Select an option

  • Save leeyisoft/fd2a469cbdf17d3501a41cce642b8777 to your computer and use it in GitHub Desktop.

Select an option

Save leeyisoft/fd2a469cbdf17d3501a41cce642b8777 to your computer and use it in GitHub Desktop.

Revisions

  1. leeyisoft created this gist Aug 31, 2017.
    223 changes: 223 additions & 0 deletions wxpy_bot.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,223 @@
    #!/usr/bin/env python3
    # encoding: utf-8

    import requests
    import base64
    import hashlib
    import os

    from wxpy import *
    from wxpy.api.chats.friend import Friend

    def _md5(Str):
    m = hashlib.md5(Str.encode(encoding='utf8'))
    return m.hexdigest()

    class WxBot(object):
    bot = None
    qr_path = 'qrcode.png'
    port = ''

    """docstring for WxBot"""
    def __init__(self, port, api_url, security_key):
    self.port = str(port)
    self.api_url = str(api_url)
    self.security_key = str(security_key)

    self.qr_path = './qrcode/%s_qrcode.png' % self.port

    self.bot = Bot(cache_path='./pkl/%s.pkl' % self.port
    , qr_callback=self.qr_callback
    )
    self.bot.enable_puid(path='./pkl/%s_puid.pkl' % self.port)
    # print(dir(self.bot))

    def qr_callback(self, **kwargs):
    qrcode = kwargs.get('qrcode')
    if not qrcode:
    return False

    with open(self.qr_path, 'wb') as fp:
    fp.write(qrcode)

    b64img = base64.b64encode(qrcode) #读取文件内容,转换为base64编码
    # 下面参数顺序很不能够变
    payload = {
    'data' : 'data:image/png;base64,%s' % bytes.decode(b64img),
    # 'name': '',
    'port' : self.port,
    'qq' : '',
    'type': 'wechat',
    }
    # print("payload", payload)
    sign = self.security_key
    for key in payload:
    sign += '%s=%s' % (str(key), str(payload[key]))

    payload['sign'] = _md5(sign)

    response = requests.post(self.api_url, data=payload)
    # print('response %s' % response.text)

    def login_callback(self):
    # 登陆成功记录端口和服务的关系
    if os.path.isfile(self.qr_path):
    # 下面参数顺序很不能够变
    payload = {
    'data' : '',
    # 'name' : str(self.bot.self.name),
    'port' : self.port,
    'qq' : str(self.bot.self.puid),
    'type': 'wechat',
    }

    # print('payload', payload)
    sign = self.security_key
    for key in payload:
    sign += '%s=%s' % (str(key), str(payload[key]))

    payload['sign'] = _md5(sign)

    response = requests.post(self.api_url, data=payload)

    os.remove(self.qr_path)

    def _list_buddy(self, params):
    """
    查询好友
    参数 以是 sex(性别), province(省份), city(城市) 等。例如可指定 province='广东'
    """
    response = {}

    keywords = params.get('keywords', '')
    sex = params.get('sex')
    city = params.get('city')
    province = params.get('province')
    sex = sex if sex else False
    city = city if city else False
    province = province if province else False

    if sex and city and province:
    response = self.bot.friends(update=True).search(keywords
    , sex=sex
    , city=city
    , province=province
    )
    elif sex and city:
    response = self.bot.friends(update=True).search(keywords
    , sex=sex
    , city=city
    )
    elif sex and province:
    response = self.bot.friends(update=True).search(keywords
    , sex=sex
    , province=province
    )
    elif city and province:
    response = self.bot.friends(update=True).search(keywords
    , city=city
    , province=province
    )
    else:
    response = self.bot.friends(update=True).search(keywords)

    # print('keywords: %s,' % keywords
    # , 'sex: %s,' % sex
    # ,'city: %s,' % city
    # ,'province: %s,' % province
    # )

    def _filter_friend_raw(Friend):
    # print('raw', type(raw), raw)
    return {
    'puid': Friend.puid,
    'NickName': Friend.raw.get('NickName'),
    'UserName': Friend.raw.get('UserName'),
    'RemarkName': Friend.raw.get('RemarkName'),
    'Sex': Friend.raw.get('Sex'),
    'Province': Friend.raw.get('Province'),
    'City': Friend.raw.get('City'),
    'Signature': Friend.raw.get('Signature'),
    }
    data = [ _filter_friend_raw(Friend) for Friend in response]
    return {'result': data, 'count': len(data)}

    def _send_buddy(self, params):
    """
    给好友发送消息
    """
    buddy = params.get('buddy')
    content = params.get('content')
    # print('buddy', buddy)
    # print('content', content)
    try:
    f = Friend(buddy, self.bot)
    res = f.send(content)
    res = {'result': 'success'}
    except Exception as e:
    res = {'err': str(e)}

    # print('send/buddy', type(res), res)
    return res

    def _send_buddy_remarkname(self, params):
    RemarkName = params.get('RemarkName')
    content = params.get('content')
    try:
    my_friend = self.bot.friends().search(RemarkName)
    if not my_friend:
    my_friend = self.bot.friends(update=True).search(RemarkName)
    if not my_friend:
    raise Exception('Friend [%s] does not exist' % RemarkName)
    res = my_friend[0].send(content)
    res = {'result': 'success'}
    except Exception as e:
    res = {'err': str(e)}

    # print('send/buddy/remarkname', type(res), res)
    return res

    def call(self, cmd, params):
    response = {}
    try:
    if cmd=='list/buddy':
    response = self._list_buddy(params)
    elif cmd=='send/buddy':
    response = self._send_buddy(params)
    elif cmd=='send/buddy/remarkname':
    response = self._send_buddy_remarkname(params)
    except Exception as e:
    response = {'err': str(e), 'links': 'http://wxpy.readthedocs.io/zh/latest/'}
    raise e

    return response

    if __name__ == '__main__':
    try:
    PORT = 8110
    wechat_bot = WxBot(PORT)
    wechat_bot.login_callback()

    # print(wechat_bot.bot.self.puid)
    # print(dir(wechat_bot.bot.self))
    # exit(0)

    cmd = 'list/buddy'
    # params = {'keywords':'李', 'sex':2, 'city':'深圳', 'province':'广东'}
    params = {'keywords':'aaaleeyi茉莉茶'}
    response = wechat_bot.call(cmd, params)
    print(type(response), response)

    cmd = 'send/buddy'
    # [{'puid': 'f89084da', 'NickName': '茉莉茶', 'UserName': '@e63bbd48c33b4aa35b1eacfd72e0210f25f976f32fa8e5962931313b57fc09c0', 'RemarkName': '', 'Sex': 0, 'Province': '', 'City': '', 'Signature': 'molochakello'}]
    params = {'buddy':{'UserName': '@7639ff66321d9e8a474fdc9945c9b16ae0ac5a99f554dde77227c63b21861e82'}, 'content': 'eecccc usernme 变化33'}
    response = wechat_bot.call(cmd, params)
    print(type(response), response)

    cmd = 'send/buddy/remarkname'
    # [{'puid': 'f89084da', 'NickName': '茉莉茶', 'UserName': '@e63bbd48c33b4aa35b1eacfd72e0210f25f976f32fa8e5962931313b57fc09c0', 'RemarkName': '', 'Sex': 0, 'Province': '', 'City': '', 'Signature': 'molochakello'}]
    params = {'RemarkName':'aaaleeyi茉莉茶', 'content': 'dddd usernme 变化33'}
    response = wechat_bot.call(cmd, params)
    print(type(response), response)
    except KeyboardInterrupt:
    sys.exit(0)
    137 changes: 137 additions & 0 deletions wxpy_server.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,137 @@
    #!/usr/bin/env python3
    # encoding: utf-8

    import sys
    import os
    import logging
    import socketserver
    import json

    from multiprocessing import Pool

    # https://github.com/serverdensity/python-daemon/blob/master/daemon.py
    from amqp.daemon import Daemon

    from common.wxpy_bot import WxBot
    logger_name = 'wxpy_server'
    HOST = '127.0.0.1'


    def _logger_init(logger_name, logger_path, logger_level):
    """
    日志初始化
    """
    formatter = logging.Formatter('[%(asctime)s]-[%(name)s]-[%(levelname)s]: %(message)s')
    logger = logging.getLogger(logger_name)
    logger.setLevel(logger_level)
    fh = logging.FileHandler(logger_path)
    fh.setFormatter(formatter)
    logger.addHandler(fh)

    class SingleTCPHandler(socketserver.BaseRequestHandler):
    "One instance per connection. Override handle(self) to customize action."
    def handle(self):
    # self.request is the client connection
    data = self.request.recv(102400) # clip input at 100Kb
    text = data.decode('utf-8')
    try:
    # 把单引号替换成双引号
    text = text.replace("'",'"')
    req_dict = json.loads(text)
    cmd = req_dict.get('cmd')
    params = req_dict.get('params', {})
    if not cmd:
    raise Exception('cmd is necessary parameter')

    response = self.server.wechat_bot.call(cmd, params)
    response = json.dumps(response)
    except Exception as e:
    response = json.dumps({'err': str(e)})

    self.request.send(bytes(response, 'utf8'))
    self.request.close()

    class WXPYServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    # Ctrl-C will cleanly kill all spawned threads
    daemon_threads = True
    # much faster rebinding
    allow_reuse_address = True

    wechat_bot = None

    def __init__(self, server_address, RequestHandlerClass):

    from config import qrcode_upload_api
    from config import qrcode_upload_key
    # print('server_address', server_address)
    _, port = server_address
    self.wechat_bot = WxBot(port=port
    , api_url=qrcode_upload_api
    , security_key=qrcode_upload_key
    )
    self.wechat_bot.login_callback()

    socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass)


    class WXPYServerDaemon(Daemon):
    def start_server(self, port):
    self.log('port', port)
    try:
    server = WXPYServer((HOST, port), SingleTCPHandler)
    server.serve_forever()
    except Exception as e:
    self.log(e)
    raise e

    def run(self):
    """
    对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
    """
    # 8110 ~ 8179 之间的端口计划给wxpy用; 8180 ~ 8210 之间的端口计划给qqbot用
    from config import wxpy_server_ports

    pool_num = len(wxpy_server_ports)
    p = Pool(pool_num)
    # logging.getLogger(logger_name).info("pid[%s] run consumers: %s" % (os.getpid(), consumers))
    for port in wxpy_server_ports:
    p.apply_async(self.start_server, args=(port,))

    p.close()
    p.join()

    def restart(self):
    """
    Restart the daemon
    """
    self.stop()
    self.start()

    if __name__ == '__main__':

    if len(sys.argv) != 2:
    print( "ERROR: Wrong options for wxpy_server.py [start|stop|restart]")
    sys.exit(1)

    cmd = sys.argv[1]
    data_dir = './logs'

    if not os.path.exists(data_dir):
    print( "ERROR: pid_idr [%s] not exists!" % data_dir)
    sys.exit(1)

    pid_path = os.path.join(data_dir, 'wxpy_server.pid')
    log_path = os.path.join(data_dir, 'wxpy_server.log')

    _logger_init(logger_name, log_path, logging.INFO)

    wxpy_server = WXPYServerDaemon(pid_path, stdout=log_path)
    if cmd == "start":
    wxpy_server.start()
    elif cmd == 'stop':
    wxpy_server.stop()
    elif cmd == 'restart':
    wxpy_server.restart()
    else:
    print( "wxpy_server.py [start|stop|restart]")
    sys.exit(1)