diff --git a/.gitignore b/.gitignore index c0ca475b..e63e229d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ test.py itchat.pkl QR.jpg .DS_Store +QR.png diff --git a/README.md b/README.md index 75767d9b..3cceb963 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,27 @@ itchat.logout() 若不设置loginCallback的值,则将会自动删除二维码图片并清空命令行显示。 +### RPC调用 + +如果你想启动RPC服务器,请使用下面的命令,监听地址和端口可根据自己需求修改 + +```python +import itchat + +itchat.start_rpc_server('localhost', 9000) +``` + +几乎所有的API都已经导出,细节请查看rpc.py文件 + +客户端代码示例 + +```python +import xmlrpc.client + +rpc = xmlrpc.client.ServerProxy('http://localhost:9000/') +rpc.get_friends() +``` + ## 常见问题与解答 Q: 如何通过这个包将自己的微信号变为控制器? diff --git a/README.rst b/README.rst index 47b6df45..a1180ac6 100644 --- a/README.rst +++ b/README.rst @@ -235,6 +235,26 @@ If loginCallback is not set, qr picture will be deleted and cmd will be cleared. If you exit through phone, exitCallback will also be called. +*RPC Call* + +If you want to start RPC server, please use the command below. You can specify the listening address and port. + +.. code:: python + import itchat + + itchat.start_rpc_server('localhost', 9000) + +Almost all the APIs are exported for RPC use, please check rpc.py for detail + +Client side code: + +.. code:: python + import xmlrpc.client + + rpc = xmlrpc.client.ServerProxy('http://localhost:9000/') + rpc.get_friends() + + **FAQ** Q: Why I can't send files whose name is encoded in utf8? diff --git a/README_EN.md b/README_EN.md index ab6df03d..a38da6e8 100644 --- a/README_EN.md +++ b/README_EN.md @@ -237,6 +237,27 @@ If loginCallback is not set, qr picture will be deleted and cmd will be cleared. If you exit through phone, exitCallback will also be called. +### RPC Call + +If you want to start RPC server, please use the command below. You can specify the listening address and port. + +```python +import itchat + +itchat.start_rpc_server('localhost', 9000) +``` + +Almost all the APIs are exported for RPC use, please check rpc.py for detail + +Client side code: + +```python +import xmlrpc.client + +rpc = xmlrpc.client.ServerProxy('http://localhost:9000/') +rpc.get_friends() +``` + ## FAQ Q: How to use this package to use my wechat as an monitor? diff --git a/itchat/__init__.py b/itchat/__init__.py index 256fc721..adb930cc 100644 --- a/itchat/__init__.py +++ b/itchat/__init__.py @@ -18,6 +18,10 @@ def new_instance(): # but it makes auto-fill a real mess, so forgive me for my following ** # actually it toke me less than 30 seconds, god bless Uganda +# rpc +start_rpc_server = originInstance.start_rpc_server +recv = originInstance.recv + # components.login login = originInstance.login get_QRuuid = originInstance.get_QRuuid diff --git a/itchat/components/__init__.py b/itchat/components/__init__.py index f088c173..c4122f22 100644 --- a/itchat/components/__init__.py +++ b/itchat/components/__init__.py @@ -3,6 +3,7 @@ from .login import load_login from .messages import load_messages from .register import load_register +from .rpc import load_rpc def load_components(core): load_contact(core) @@ -10,3 +11,4 @@ def load_components(core): load_login(core) load_messages(core) load_register(core) + load_rpc(core) diff --git a/itchat/components/rpc.py b/itchat/components/rpc.py new file mode 100644 index 00000000..d389db67 --- /dev/null +++ b/itchat/components/rpc.py @@ -0,0 +1,119 @@ +import base64 +import io +import json +import logging +import threading +import types + +import itchat.returnvalues as rv +import itchat.storage.templates as tpl +import six.moves.xmlrpc_client as cli +import six.moves.xmlrpc_server as rpc + +logger = logging.getLogger('itchat') + +exported_functions = [ + # components.login + 'login', + 'get_QRuuid', + 'get_QR', + 'check_login', + 'web_init', + 'show_mobile_login', + 'start_receiving', + 'get_msg', + 'logout', + + # components.contact + 'update_chatroom', + 'update_friend', + 'get_contacts', + 'get_friends', + 'get_chatrooms', + 'get_mps', + 'set_alias', + 'set_pinned', + 'add_friend', + 'get_head_img', + 'create_chatroom', + 'set_chatroom_name', + 'delete_member_from_chatroom', + 'add_member_into_chatroom', + + # components.messages + 'send_raw_msg', + 'send_msg', + 'upload_file', + 'send_file', + 'send_image', + 'send_video', + 'send', + 'revoke', + + # components.hotreload + 'dump_login_status', + 'load_login_status', + + # components.register + 'auto_login', + 'configured_reply', + 'msg_register', + 'run', + + # other functions + 'search_friends', + 'search_chatrooms', + 'search_mps', + 'set_logging', + 'recv', +] + + +def dump_obj(marshaller, value, write, escape=cli.escape): + if isinstance(value, io.BytesIO): + write('') + encoded = base64.encodebytes(value.getvalue()) + write(encoded.decode('ascii')) + write('') + elif isinstance(value, int): + write('') + write('%d' % value) + write('') + else: + write('') + write(escape(json.dumps(value))) + write('') + + +def register_types(): + cli.Marshaller.dispatch[tpl.Chatroom] = dump_obj + cli.Marshaller.dispatch[tpl.ContactList] = dump_obj + cli.Marshaller.dispatch[tpl.User] = dump_obj + cli.Marshaller.dispatch[tpl.ChatroomMember] = dump_obj + cli.Marshaller.dispatch[tpl.MassivePlatform] = dump_obj + cli.Marshaller.dispatch[rv.ReturnValue] = dump_obj + cli.Marshaller.dispatch[io.BytesIO] = dump_obj + cli.Marshaller.dispatch[int] = dump_obj + + +def load_rpc(core): + core.start_rpc_server = start_rpc_server + + +def start_rpc_server(self, host, port, block=False): + server = rpc.SimpleXMLRPCServer((host, port), logRequests=False, allow_none=True) + server.register_introspection_functions() + for i in exported_functions: + if hasattr(self, i): + server.register_function(getattr(self, i)) + logger.info('Starting RPC server') + self.rpc = server + if not block: + rpc_thread = threading.Thread(target=server.serve_forever, args=()) + rpc_thread.daemon = True + register_types() + rpc_thread.start() + else: + register_types() + server.serve_forever() + diff --git a/itchat/core.py b/itchat/core.py index 52d6ae4f..950a1a13 100644 --- a/itchat/core.py +++ b/itchat/core.py @@ -27,6 +27,8 @@ def __init__(self): self.functionDict = {'FriendChat': {}, 'GroupChat': {}, 'MpChat': {}} self.useHotReload, self.hotReloadDir = False, 'itchat.pkl' self.receivingRetryCount = 5 + self.rpc = None + def login(self, enableCmdQR=False, picDir=None, qrCallback=None, loginCallback=None, exitCallback=None): ''' log in like web wechat does @@ -455,5 +457,13 @@ def search_chatrooms(self, name=None, userName=None): return self.storageClass.search_chatrooms(name, userName) def search_mps(self, name=None, userName=None): return self.storageClass.search_mps(name, userName) - + def recv(self): + ''' receive cached message on msgList + ''' + return self.storageClass.recv() + def start_rpc_server(self, host, port): + ''' start rpc server + it is defined in components/rpc.py + ''' + raise NotImplementedError() load_components(Core) diff --git a/itchat/storage/__init__.py b/itchat/storage/__init__.py index ec6c8ba7..24b1d034 100644 --- a/itchat/storage/__init__.py +++ b/itchat/storage/__init__.py @@ -115,3 +115,12 @@ def search_mps(self, name=None, userName=None): if name in m['NickName']: matchList.append(copy.deepcopy(m)) return matchList + def recv(self): + rtn = [] + count = 1024 + while count > 0 and not self.msgList.empty(): + elem = self.msgList.get() + rtn.append(dict(elem)) + count -= 1 + return rtn + \ No newline at end of file diff --git a/scripts/itchat_server b/scripts/itchat_server new file mode 100755 index 00000000..38305e70 --- /dev/null +++ b/scripts/itchat_server @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +HOST=localhost +PORT=9000 +QRCODE=True + +usage() { echo "Usage: $0 [-h ] [-p ] [-2]" 1>&2; exit 1; } + +while getopts ":h:p:2" o; do + case "${o}" in + h) + HOST=${OPTARG} + ;; + p) + PORT=${OPTARG} + ;; + 2) + QRCODE=2 + ;; + *) + usage + ;; + esac +done +shift $((OPTIND-1)) + +cat << EOF | python3 +import sys +import itchat + +def quit(): + print('error, exiting...') + sys.exit(-1) + +if __name__ == '__main__': + try: + itchat.auto_login(enableCmdQR=${QRCODE}, hotReload=True, exitCallback=quit) + itchat.start_rpc_server('${HOST}', ${PORT}, block=True) + except KeyboardInterrupt: + print('exiting...') + sys.exit(0) +EOF \ No newline at end of file diff --git a/setup.py b/setup.py index 51e04f1d..1173033e 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,8 @@ install_requires=['requests', 'pyqrcode', 'pypng'], + scripts = ['scripts/itchat_server'], + # List additional groups of dependencies here extras_require={}, )