From 7b23c748d0e1b640b33c7c383aacf84403a4b957 Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Wed, 14 Mar 2018 15:15:46 -0700 Subject: [PATCH 01/12] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86web=20?= =?UTF-8?q?=E5=92=8C=20=E5=91=BD=E4=BB=A4=E8=A1=8C=E7=9A=84=20poll?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qqbot/qqbotcls.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ qqbot/termbot.py | 15 +++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/qqbot/qqbotcls.py b/qqbot/qqbotcls.py index 1882936..f6fff52 100644 --- a/qqbot/qqbotcls.py +++ b/qqbot/qqbotcls.py @@ -12,6 +12,7 @@ if p not in sys.path: sys.path.insert(0, p) +import queue import sys, subprocess, time from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger @@ -128,6 +129,37 @@ def Login(self, argv=None): # child thread 1 self.poll = session.Copy().Poll + def PollMessage(self): + maxIter = 1024 + result = [] + while maxIter > 0: + try: + if not self.msgQueue.empty(): + tmp = self.msgQueue.get(block=False) + if tmp[1] is None: + tmpdict = { + 'ctype' : tmp[5], + 'buddy' : str(tmp[0]), + 'content' : tmp[2], + 'fromUin' : tmp[3], + } + else: + tmpdict = { + 'ctype' : tmp[5], + 'group' : str(tmp[0]), + 'member' : str(tmp[1]), + 'content' : tmp[2], + 'fromUin' : tmp[3], + 'membUin' : tmp[4], + } + result.append(tmpdict) + maxIter -= 1 + else: + break + except Exception as e: + ERROR('处理消息队列出错 %s'%str(e), exc_info=True) + return result + def Run(self): if self.conf.startAfterFetch: self.firstFetch() @@ -198,6 +230,18 @@ def onPollComplete(self, ctype, fromUin, membUin, content): else: INFO('来自 %s[%s] 的消息: "%s"' % (contact, member, content)) + try: + self.msgQueue.put((contact, member, content, fromUin, membUin, ctype), block=False) + except queue.Full: + self.discardedMessage += 1 + self.msgQueue.get(block=False) + try: + self.msgQueue.put((contact, member, content, fromUin, membUin, ctype), block=False) + except Exception as e: + ERROR('消息队列出错 %s'%str(e), exc_info=True) + except Exception as e: + ERROR('消息队列出错 %s'%str(e), exc_info=True) + self.onQQMessage(contact, member, content) def detectAtMe(self, nameInGroup, content): @@ -240,6 +284,9 @@ def init(self, argv): for pluginName in self.conf.plugins: self.Plug(pluginName) + self.discardedMessage = 0 + self.msgQueue = queue.Queue(maxsize=1024) + self.onInit() def wrap(self, slots): diff --git a/qqbot/termbot.py b/qqbot/termbot.py index 13cedee..2558a70 100644 --- a/qqbot/termbot.py +++ b/qqbot/termbot.py @@ -138,6 +138,14 @@ def cmd_send(bot, args, http=False): else: return None, 'QQBot 命令格式错误' +def cmd_poll(bot, args, http=False): + '''1 poll''' + if len(args) == 0: + result = bot.PollMessage() + return result, None + else: + return None, 'QQBot 命令格式错误' + def group_operation(bot, ginfo, minfos, func, exArgs, http): gl = bot.List('group', ginfo) if gl is None: @@ -275,7 +283,10 @@ def cmd_plugins(bot, args, http=False): 4) 消息发送命令 qq send buddy|group|discuss qq|name|key=val message -5) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 +5) 消息接收命令 + qq poll + +6) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 qq group-set-admin ginfo minfo1,minfo2,... qq group-unset-admin ginfo minfo1,minfo2,... qq group-set-card ginfo minfo1,minfo2,... card @@ -283,7 +294,7 @@ def cmd_plugins(bot, args, http=False): qq group-shut ginfo minfo1,minfo2,... [t] qq group-kick ginfo minfo1,minfo2,... -6) 加载/卸载/显示插件 +7) 加载/卸载/显示插件 qq plug/unplug myplugin qq plugins\ ''' From 06ba12f5bbf1e8baa421deacf69ec4df55346644 Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Wed, 14 Mar 2018 15:18:12 -0700 Subject: [PATCH 02/12] Update README.MD update reademe with new added poll command support --- README.MD | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.MD b/README.MD index 0402142..b745d5d 100644 --- a/README.MD +++ b/README.MD @@ -66,9 +66,14 @@ QQBot 启动后,在另一个控制台窗口使用 qq 命令操作 QQBot ,目 4) 消息发送命令 qq send buddy|group|discuss $rinfo $message + + +    5) 消息接收命令 + +        qq poll - 5) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 + 6) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 qq group-set-admin $ginfo $minfo1,$minfo2,... @@ -83,7 +88,7 @@ QQBot 启动后,在另一个控制台窗口使用 qq 命令操作 QQBot ,目 qq group-kick $ginfo $minfo1,$minfo2,... - 6) 加载/卸载/显示插件 + 7) 加载/卸载/显示插件 qq plug/unplug myplugin From 40c85bd27d46088080095e0c09cade33ed5b5beb Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Wed, 14 Mar 2018 15:37:26 -0700 Subject: [PATCH 03/12] fix python import issue --- qqbot/qqbotcls.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qqbot/qqbotcls.py b/qqbot/qqbotcls.py index f6fff52..b557714 100644 --- a/qqbot/qqbotcls.py +++ b/qqbot/qqbotcls.py @@ -12,7 +12,6 @@ if p not in sys.path: sys.path.insert(0, p) -import queue import sys, subprocess, time from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger @@ -21,7 +20,7 @@ from qqbot.qconf import QConf from qqbot.utf8logger import INFO, CRITICAL, ERROR, WARN from qqbot.qsession import QLogin, RequestError -from qqbot.common import StartDaemonThread, Import +from qqbot.common import StartDaemonThread, Import, Queue from qqbot.qterm import QTermServer from qqbot.mainloop import MainLoop, Put from qqbot.groupmanager import GroupManager From 38ea8882c8f1a8d1a1e9c0e851bd49c8a2be2d9e Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Wed, 14 Mar 2018 15:38:38 -0700 Subject: [PATCH 04/12] fix python import issue part 2 --- qqbot/qqbotcls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qqbot/qqbotcls.py b/qqbot/qqbotcls.py index b557714..eb869ef 100644 --- a/qqbot/qqbotcls.py +++ b/qqbot/qqbotcls.py @@ -284,7 +284,7 @@ def init(self, argv): self.Plug(pluginName) self.discardedMessage = 0 - self.msgQueue = queue.Queue(maxsize=1024) + self.msgQueue = Queue(maxsize=1024) self.onInit() From 48565396a0e2ed20bcbb02e97c0e764684f86c5f Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Wed, 14 Mar 2018 15:39:42 -0700 Subject: [PATCH 05/12] fix queue issue --- qqbot/qqbotcls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qqbot/qqbotcls.py b/qqbot/qqbotcls.py index eb869ef..00a5bcf 100644 --- a/qqbot/qqbotcls.py +++ b/qqbot/qqbotcls.py @@ -284,7 +284,7 @@ def init(self, argv): self.Plug(pluginName) self.discardedMessage = 0 - self.msgQueue = Queue(maxsize=1024) + self.msgQueue = Queue.Queue(maxsize=1024) self.onInit() From 0375c672a2e7df62f4fa5e4ba1e5d702d499a7bd Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Wed, 14 Mar 2018 15:15:46 -0700 Subject: [PATCH 06/12] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86web=20?= =?UTF-8?q?=E5=92=8C=20=E5=91=BD=E4=BB=A4=E8=A1=8C=E7=9A=84=20poll?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update README.MD update reademe with new added poll command support fix python import issue fix python import issue part 2 fix queue issue Revert "fix python import issue" This reverts commit 40c85bd27d46088080095e0c09cade33ed5b5beb. update --- README.MD | 9 +++++++-- qqbot/qqbotcls.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++- qqbot/termbot.py | 15 +++++++++++++-- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/README.MD b/README.MD index 0402142..b745d5d 100644 --- a/README.MD +++ b/README.MD @@ -66,9 +66,14 @@ QQBot 启动后,在另一个控制台窗口使用 qq 命令操作 QQBot ,目 4) 消息发送命令 qq send buddy|group|discuss $rinfo $message + + +    5) 消息接收命令 + +        qq poll - 5) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 + 6) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 qq group-set-admin $ginfo $minfo1,$minfo2,... @@ -83,7 +88,7 @@ QQBot 启动后,在另一个控制台窗口使用 qq 命令操作 QQBot ,目 qq group-kick $ginfo $minfo1,$minfo2,... - 6) 加载/卸载/显示插件 + 7) 加载/卸载/显示插件 qq plug/unplug myplugin diff --git a/qqbot/qqbotcls.py b/qqbot/qqbotcls.py index 1882936..00a5bcf 100644 --- a/qqbot/qqbotcls.py +++ b/qqbot/qqbotcls.py @@ -20,7 +20,7 @@ from qqbot.qconf import QConf from qqbot.utf8logger import INFO, CRITICAL, ERROR, WARN from qqbot.qsession import QLogin, RequestError -from qqbot.common import StartDaemonThread, Import +from qqbot.common import StartDaemonThread, Import, Queue from qqbot.qterm import QTermServer from qqbot.mainloop import MainLoop, Put from qqbot.groupmanager import GroupManager @@ -128,6 +128,37 @@ def Login(self, argv=None): # child thread 1 self.poll = session.Copy().Poll + def PollMessage(self): + maxIter = 1024 + result = [] + while maxIter > 0: + try: + if not self.msgQueue.empty(): + tmp = self.msgQueue.get(block=False) + if tmp[1] is None: + tmpdict = { + 'ctype' : tmp[5], + 'buddy' : str(tmp[0]), + 'content' : tmp[2], + 'fromUin' : tmp[3], + } + else: + tmpdict = { + 'ctype' : tmp[5], + 'group' : str(tmp[0]), + 'member' : str(tmp[1]), + 'content' : tmp[2], + 'fromUin' : tmp[3], + 'membUin' : tmp[4], + } + result.append(tmpdict) + maxIter -= 1 + else: + break + except Exception as e: + ERROR('处理消息队列出错 %s'%str(e), exc_info=True) + return result + def Run(self): if self.conf.startAfterFetch: self.firstFetch() @@ -198,6 +229,18 @@ def onPollComplete(self, ctype, fromUin, membUin, content): else: INFO('来自 %s[%s] 的消息: "%s"' % (contact, member, content)) + try: + self.msgQueue.put((contact, member, content, fromUin, membUin, ctype), block=False) + except queue.Full: + self.discardedMessage += 1 + self.msgQueue.get(block=False) + try: + self.msgQueue.put((contact, member, content, fromUin, membUin, ctype), block=False) + except Exception as e: + ERROR('消息队列出错 %s'%str(e), exc_info=True) + except Exception as e: + ERROR('消息队列出错 %s'%str(e), exc_info=True) + self.onQQMessage(contact, member, content) def detectAtMe(self, nameInGroup, content): @@ -240,6 +283,9 @@ def init(self, argv): for pluginName in self.conf.plugins: self.Plug(pluginName) + self.discardedMessage = 0 + self.msgQueue = Queue.Queue(maxsize=1024) + self.onInit() def wrap(self, slots): diff --git a/qqbot/termbot.py b/qqbot/termbot.py index 13cedee..2558a70 100644 --- a/qqbot/termbot.py +++ b/qqbot/termbot.py @@ -138,6 +138,14 @@ def cmd_send(bot, args, http=False): else: return None, 'QQBot 命令格式错误' +def cmd_poll(bot, args, http=False): + '''1 poll''' + if len(args) == 0: + result = bot.PollMessage() + return result, None + else: + return None, 'QQBot 命令格式错误' + def group_operation(bot, ginfo, minfos, func, exArgs, http): gl = bot.List('group', ginfo) if gl is None: @@ -275,7 +283,10 @@ def cmd_plugins(bot, args, http=False): 4) 消息发送命令 qq send buddy|group|discuss qq|name|key=val message -5) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 +5) 消息接收命令 + qq poll + +6) 群管理命令: 设置/取消管理员 、 设置/删除群名片 、 群成员禁言 以及 踢除群成员 qq group-set-admin ginfo minfo1,minfo2,... qq group-unset-admin ginfo minfo1,minfo2,... qq group-set-card ginfo minfo1,minfo2,... card @@ -283,7 +294,7 @@ def cmd_plugins(bot, args, http=False): qq group-shut ginfo minfo1,minfo2,... [t] qq group-kick ginfo minfo1,minfo2,... -6) 加载/卸载/显示插件 +7) 加载/卸载/显示插件 qq plug/unplug myplugin qq plugins\ ''' From 1ba8ebb6ea8ac3f2baa86f3130b19d008b7bdb58 Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Thu, 15 Mar 2018 22:48:00 -0700 Subject: [PATCH 07/12] add tornado server code --- qqbot/qqbotcls.py | 4 +- qqbot/termbot.py | 148 +++++++++++++++++++++++++++++------- qqbot/webserver/__init__.py | 0 qqbot/webserver/server.py | 68 +++++++++++++++++ setup.py | 2 +- 5 files changed, 194 insertions(+), 28 deletions(-) create mode 100644 qqbot/webserver/__init__.py create mode 100644 qqbot/webserver/server.py diff --git a/qqbot/qqbotcls.py b/qqbot/qqbotcls.py index 00a5bcf..cf152d8 100644 --- a/qqbot/qqbotcls.py +++ b/qqbot/qqbotcls.py @@ -22,6 +22,7 @@ from qqbot.qsession import QLogin, RequestError from qqbot.common import StartDaemonThread, Import, Queue from qqbot.qterm import QTermServer +from qqbot.webserver.server import TornadoServer from qqbot.mainloop import MainLoop, Put from qqbot.groupmanager import GroupManager from qqbot.termbot import TermBot @@ -169,7 +170,8 @@ def Run(self): # child thread 1~4 StartDaemonThread(self.pollForever) StartDaemonThread(self.intervalForever) - StartDaemonThread(QTermServer(self.conf.termServerPort, self.onTermCommand).Run) + #StartDaemonThread(QTermServer(self.conf.termServerPort, self.onTermCommand).Run) + StartDaemonThread(TornadoServer(self.conf.termServerPort, self.onCommand).Run) self.scheduler.start() self.started = True diff --git a/qqbot/termbot.py b/qqbot/termbot.py index 2558a70..b7c4b89 100644 --- a/qqbot/termbot.py +++ b/qqbot/termbot.py @@ -4,10 +4,40 @@ from qqbot.mainloop import Put from qqbot.common import Unquote, STR2BYTES, JsonDumps, BYTES2STR +import pdb + cmdFuncs, usage = {}, {} class TermBot(object): + def onCommandDict(bot, argv, http): + if argv and 'cmd' in argv and argv['cmd'] in cmdFuncs: + try: + cmd = argv['cmd'] + argv.pop('cmd', None) + if argv['subcmd'] == None: + argv.pop('subcmd', None) + result, err = cmdFuncs[cmd](bot, argv, http) + except Exception as e: + result, err = None, '运行命令过程中出错:' + str(type(e)) + str(e) + ERROR(err, exc_info=True) + else: + result, err = None, 'QQBot 命令格式错误' + return result, err + + def onCommand(bot, argv, http=True): + if isinstance(argv, dict): + return TermBot.onCommandDict(bot, argv, http) + if argv and argv[0] in cmdFuncs: + try: + result, err = cmdFuncs[argv[0]](bot, argv[1:], http) + except Exception as e: + result, err = None, '运行命令过程中出错:' + str(type(e)) + str(e) + ERROR(err, exc_info=True) + else: + result, err = None, 'QQBot 命令格式错误' + return result, err + def onTermCommand(bot, command): command = BYTES2STR(command) if command.startswith('GET /'): @@ -24,14 +54,7 @@ def onTermCommand(bot, command): http = False argv = command.strip().split(None, 3) - if argv and argv[0] in cmdFuncs: - try: - result, err = cmdFuncs[argv[0]](bot, argv[1:], http) - except Exception as e: - result, err = None, '运行命令过程中出错:' + str(type(e)) + str(e) - ERROR(err, exc_info=True) - else: - result, err = None, 'QQBot 命令格式错误' + result, err = TermBot.onCommand(bot, argv, http) if http: rep = {'result':result, 'err': err} @@ -48,14 +71,16 @@ def onTermCommand(bot, command): def cmd_help(bot, args, http=False): '''1 help''' - if len(args) == 0: + if isinstance(args, dict) and dict == {}: + return usage['term'], None + elif len(args) == 0: return usage['term'], None else: return None, 'QQBot 命令格式错误' def cmd_stop(bot, args, http=False): '''1 stop''' - if len(args) == 0: + if (isinstance(args, dict) and dict == {}) or len(args) == 0: Put(bot.Stop) return 'QQBot已停止', None else: @@ -63,7 +88,7 @@ def cmd_stop(bot, args, http=False): def cmd_restart(bot, args, http=False): '''1 restart''' - if len(args) == 0: + if (isinstance(args, dict) and dict == {}) or len(args) == 0: Put(bot.Restart) return 'QQBot已重启(自动登录)', None else: @@ -71,7 +96,7 @@ def cmd_restart(bot, args, http=False): def cmd_fresh_restart(bot, args, http=False): '''1 fresh-restart''' - if len(args) == 0: + if (isinstance(args, dict) and dict == {}) or len(args) == 0: Put(bot.FreshRestart) return 'QQBot已重启(手工登录)', None else: @@ -80,23 +105,56 @@ def cmd_fresh_restart(bot, args, http=False): def cmd_list(bot, args, http=False): '''2 list buddy|group|discuss [qq|name|key=val] 2 list group-member|discuss-member oqq|oname|okey=oval [qq|name|key=val]''' - - if (len(args) in (1, 2)) and args[0] in ('buddy', 'group', 'discuss'): + if isinstance(args, dict) and 'subcmd' in args.keys(): + if args['subcmd'] in ('buddy', 'group', 'discuss'): + tmp = [args['subcmd']] + args.pop('subcmd', None) + if len(args.keys()) == 1: + key = list(args.keys())[0] + if key == 'name' or key == 'qq': + tmp.append(args[key]) + else: + tmp.append('%s=%s'%(key, args[key])) + return bot.ObjOfList(*tmp) + elif args['subcmd'] in ('group-member', 'discuss-member'): + tmp = [args['subcmd']] + args.pop('subcmd', None) + if len(args.keys()) == 1: + key = list(args.keys())[0] + if key == 'oname' or key == 'oqq': + tmp.append(args[key]) + else: + tmp.append('%s=%s'%(key[1:], args[key])) + elif len(args.keys()) == 2: + for key in args.keys(): + if key[0] == 'o': + if key == 'oname' or key == 'oqq': + tmp.append(args[key]) + else: + tmp.append('%s=%s'%(key[1:], args[key])) + else: + if key == 'name' or key == 'qq': + tmp.append(args[key]) + else: + tmp.append('%s=%s'%(key, args[key])) + else: + return None, 'QQBot 命令格式错误' + return bot.ObjOfList(*tmp) + return None, 'QQBot 命令格式错误' + elif isinstance(args, list) and (len(args) in (1, 2)) and args[0] in ('buddy', 'group', 'discuss'): # list buddy # list buddy jack if not http: return bot.StrOfList(*args), None else: - return bot.ObjOfList(*args) - - elif (len(args) in (2, 3)) and args[1] and (args[0] in ('group-member', 'discuss-member')): + return bot.ObjOfList(*args) + elif isinstance(args, list) and (len(args) in (2, 3)) and args[1] and (args[0] in ('group-member', 'discuss-member')): # list group-member xxx班 # list group-member xxx班 yyy if not http: return bot.StrOfList(*args), None else: return bot.ObjOfList(*args) - else: return None, 'QQBot 命令格式错误' @@ -104,10 +162,37 @@ def cmd_update(bot, args, http=False): '''2 update buddy|group|discuss 2 update group-member|discuss-member oqq|oname|okey=oval''' - if len(args) == 1 and args[0] in ('buddy', 'group', 'discuss'): + if isinstance(args, dict) and 'subcmd' in args.keys(): + if args['subcmd'] in ('buddy', 'group', 'discuss'): + tmp = [args['subcmd']] + args.pop('subcmd', None) + if args == {}: + return bot.Update(tmp[0]), None + return None, 'QQBot 命令格式错误' + elif args['subcmd'] in ('group-member', 'discuss-member'): + tmp = [args['subcmd']] + args.pop('subcmd', None) + if len(args.keys()) == 1: + key = list(args.keys())[0] + if key == 'oqq' or key == 'oname': + tmp.append(args[key]) + else: + tmp.append('%s=%s'%(key[1:], args[key])) + else: + return None, 'QQBot 命令格式错误' + cl = bot.List(tmp[0][:-7], tmp[1]) + if cl is None: + return None, 'QQBot 在向 QQ 服务器请求数据获取联系人资料的过程中发生错误' + elif not cl: + return None, '%s-%s 不存在' % (tmp[0], tmp[1]) + else: + return [bot.Update(c) for c in cl], None + else: + return None, 'QQBot 命令格式错误' + elif isinstance(args, list) and len(args) == 1 and args[0] in ('buddy', 'group', 'discuss'): # update buddy - return bot.Update(args[0]), None - elif len(args) == 2 and args[1] and (args[0] in ('group-member', 'discuss-member')): + return bot.Update(args[0]), None + elif isinstance(args, list) and len(args) == 2 and args[1] and (args[0] in ('group-member', 'discuss-member')): # update group-member xxx班 cl = bot.List(args[0][:-7], args[1]) if cl is None: @@ -121,7 +206,6 @@ def cmd_update(bot, args, http=False): def cmd_send(bot, args, http=False): '''3 send buddy|group|discuss qq|name|key=val message''' - if len(args) == 3 and args[0] in ('buddy', 'group', 'discuss'): # send buddy jack hello cl = bot.List(args[0], args[1]) @@ -140,7 +224,10 @@ def cmd_send(bot, args, http=False): def cmd_poll(bot, args, http=False): '''1 poll''' - if len(args) == 0: + if isinstance(args, dict) and dict == {}: + result = bot.PollMessage() + return result, None + elif isinstance(args, list) and len(args) == 0: result = bot.PollMessage() return result, None else: @@ -241,21 +328,30 @@ def cmd_group_unset_card(bot, args, http=False): def cmd_plug(bot, args, http=False): '''5 plug myplugin''' - if len(args) == 1: + if isinstance(args, dict) and 'name' in args and len(args.keys()) == 1: + return bot.Plug(args['name']), None + elif isinstance(args, list) and len(args) == 1: return bot.Plug(args[0]), None else: return None, 'QQBot 命令格式错误' def cmd_unplug(bot, args, http=False): '''5 unplug myplugin''' - if len(args) == 1: + if isinstance(args, dict) and 'name' in args and len(args.keys()) == 1: + return bot.Unplug(args['name']), None + elif isinstance(args, list) and len(args) == 1: return bot.Unplug(args[0]), None else: return None, 'QQBot 命令格式错误' def cmd_plugins(bot, args, http=False): '''5 plugins''' - if len(args) == 0: + if isinstance(args, dict) and args == {}: + if not http: + return '已加载插件:%s' % bot.Plugins(), None + else: + return bot.Plugins(), None + elif isinstance(args, list) and len(args) == 0: if not http: return '已加载插件:%s' % bot.Plugins(), None else: diff --git a/qqbot/webserver/__init__.py b/qqbot/webserver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qqbot/webserver/server.py b/qqbot/webserver/server.py new file mode 100644 index 0000000..531d913 --- /dev/null +++ b/qqbot/webserver/server.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +QQBot -- A conversation robot base on Tencent's SmartQQ +Website -- https://github.com/wilsonwang371/qqbot +Author -- wilsonw@vmware.com + +This is a new web server that based on Tornado web server +""" +import sys, os +p = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if p not in sys.path: + sys.path.insert(0, p) + +import tornado.ioloop +import tornado.web +from qqbot.common import STR2BYTES, JsonDumps +from qqbot.mainloop import Put +from qqbot.utf8logger import ERROR, INFO +from tornado.web import asynchronous + + + +HOST, DEFPORT = '127.0.0.1', 8189 + +class MainHandler(tornado.web.RequestHandler): + def initialize(self, server): + self.server = server + + @asynchronous + def get(self, cmd, subcmd=None): + data_dict = { + 'cmd': cmd, + 'subcmd': subcmd, + } + for i in self.request.arguments: + data_dict[i] = self.get_argument(i, None) + Put(self.onData, data_dict, self.server) + + def onData(self, data_dict, server): + try: + result, err = server.response(data_dict) + rep = {'result':result, 'err': err} + resp = STR2BYTES(JsonDumps(rep, ensure_ascii=False, indent=4)) + except Exception as e: + resp = '在处理 请求时发生错误,%s' % (e) + ERROR(resp, exc_info = True) + resp = STR2BYTES(resp) + self.write(resp) + self.finish() + + +class TornadoServer(object): + + def __init__(self, port, onCommand): + self.response = onCommand + self.host = HOST + self.port = int(port) + self.name = 'QQBot-Term 服务器' + + def Run(self): + self.app = tornado.web.Application([ + (r'/api/([^/]+)', MainHandler, dict(server=self)), + (r'/api/([^/]+)/([^/]+)', MainHandler, dict(server=self)), + ]) + self.app.listen(self.port) + tornado.ioloop.IOLoop.current().start() diff --git a/setup.py b/setup.py index 3b43e8f..5ea7562 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name = 'qqbot', version = version, - packages = ['qqbot', 'qqbot.qcontactdb', 'qqbot.plugins'], + packages = ['qqbot', 'qqbot.qcontactdb', 'qqbot.plugins', 'qqbot.webserver'], entry_points = { 'console_scripts': [ 'qqbot = qqbot:RunBot', From 66bd714ddf1c20b97f8580efa577b55ffae23e09 Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Thu, 15 Mar 2018 22:54:09 -0700 Subject: [PATCH 08/12] fix poll cmd --- qqbot/termbot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qqbot/termbot.py b/qqbot/termbot.py index b7c4b89..2edc946 100644 --- a/qqbot/termbot.py +++ b/qqbot/termbot.py @@ -224,7 +224,8 @@ def cmd_send(bot, args, http=False): def cmd_poll(bot, args, http=False): '''1 poll''' - if isinstance(args, dict) and dict == {}: + if isinstance(args, dict): + args.pop('subcmd', None) result = bot.PollMessage() return result, None elif isinstance(args, list) and len(args) == 0: From b2bb5ef28c05497e1ce6921ebc42064e14becdcb Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Thu, 15 Mar 2018 23:02:30 -0700 Subject: [PATCH 09/12] update send cmd --- qqbot/termbot.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/qqbot/termbot.py b/qqbot/termbot.py index 2edc946..bd5f6bb 100644 --- a/qqbot/termbot.py +++ b/qqbot/termbot.py @@ -206,7 +206,33 @@ def cmd_update(bot, args, http=False): def cmd_send(bot, args, http=False): '''3 send buddy|group|discuss qq|name|key=val message''' - if len(args) == 3 and args[0] in ('buddy', 'group', 'discuss'): + if isinstance(args, dict): + tmp = [args['subcmd']] + msg = None + args.pop('subcmd', None) + if len(args.keys()) == 2: + key = list(args.keys())[0] + if key == 'name' or key == 'qq': + tmp.append(args[key]) + elif key == 'message': + msg = args[key] + args.pop(key, None) + else: + tmp.append('%s=%s'%(key, args[key])) + if msg is not None: + cl = bot.List(tmp[0], tmp[1]) + if cl is None: + return None, 'QQBot 在向 QQ 服务器请求数据获取联系人资料的过程中发生错误' + elif not cl: + return None, '%s-%s 不存在' % (tmp[0], tmp[1]) + else: + msg = msg.replace('\\n','\n').replace('\\t','\t') + result = [bot.SendTo(c, msg) for c in cl] + if not http: + result = '\n'.join(result) + return result, None + return None, 'QQBot 命令格式错误' + elif isinstance(args, list) and len(args) == 3 and args[0] in ('buddy', 'group', 'discuss'): # send buddy jack hello cl = bot.List(args[0], args[1]) if cl is None: From 00bac298aaf8b4aec8b3714204894c938858461d Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Thu, 15 Mar 2018 23:15:22 -0700 Subject: [PATCH 10/12] update --- qqbot/termbot.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/qqbot/termbot.py b/qqbot/termbot.py index bd5f6bb..b92de61 100644 --- a/qqbot/termbot.py +++ b/qqbot/termbot.py @@ -211,14 +211,13 @@ def cmd_send(bot, args, http=False): msg = None args.pop('subcmd', None) if len(args.keys()) == 2: - key = list(args.keys())[0] - if key == 'name' or key == 'qq': - tmp.append(args[key]) - elif key == 'message': - msg = args[key] - args.pop(key, None) - else: - tmp.append('%s=%s'%(key, args[key])) + for key in args.keys(): + if key == 'name' or key == 'qq': + tmp.append(args[key]) + elif key == 'message': + msg = args[key] + else: + tmp.append('%s=%s'%(key, args[key])) if msg is not None: cl = bot.List(tmp[0], tmp[1]) if cl is None: From ae75c3b83fd93a5542b96151df0f84dc4b1a75d3 Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Fri, 16 Mar 2018 10:33:32 -0700 Subject: [PATCH 11/12] =?UTF-8?q?=E5=A2=9E=E5=8A=A0RESTAPI=EF=BC=8C?= =?UTF-8?q?=E7=9B=AE=E5=89=8D=E8=BF=98=E4=B8=8D=E6=94=AF=E6=8C=81GROUP?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.MD | 8 ++++++++ qqbot/qconf.py | 15 +++++++++++++-- qqbot/qqbotcls.py | 4 ++-- qqbot/webserver/server.py | 15 +++++++++------ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/README.MD b/README.MD index b745d5d..88d1c43 100644 --- a/README.MD +++ b/README.MD @@ -463,6 +463,9 @@ GUI 模式是默认的模式,只适用于个人电脑。邮箱模式可以适 # 二维码 http 服务器端口号 "httpServerPort" : 8189, + + # Tornado restapi 服务器端口号 + "restapiServerPort" : 8190, # 自动登录的 QQ 号 "qq" : "3497303033", @@ -517,6 +520,7 @@ GUI 模式是默认的模式,只适用于个人电脑。邮箱模式可以适 # "termServerPort" : 8188, # "httpServerIP" : "", # "httpServerPort" : 8189, + # "restapiServerPort" : 8190, # "qq" : "", # "mailAccount" : "", # "mailAuthCode" : "", @@ -585,6 +589,10 @@ QQBot 启动后,会开启一个 QQBot-term 服务器监听用户通过 qq 命 如果需要在同一台机器上登录多个 QQ 号码,可以直接在不同的终端中开启多个 qqbot 进程进行登录,但是,每个 qqbot 进程必须设置专有的 termServerPort 和 httpServerPort (或者全部设置为 0 或 空值 ),否则会造成端口号冲突。 +#### QQBot-restapi 服务器端口号( restapiServerPort ) + +逻辑和 QQBot-term 一样,但是是基于Tornado的一个restapi接口。便于使用其它程序和QQBot进行交互 + #### 调试模式( debug ) 若 debug 项设置为 True ,则运行过程中会打印调试信息。 diff --git a/qqbot/qconf.py b/qqbot/qconf.py index 83a14a6..8f11094 100644 --- a/qqbot/qconf.py +++ b/qqbot/qconf.py @@ -28,6 +28,9 @@ # 二维码 http 服务器端口号 "httpServerPort" : 8189, + + # restapi 服务器端口号 + "restapiServerPort" : 8190, # 自动登录的 QQ 号 "qq" : "3497303033", @@ -82,6 +85,7 @@ # "termServerPort" : 8188, # "httpServerIP" : "", # "httpServerPort" : 8189, + # "restapiServerPort" : 8190, # "qq" : "", # "mailAccount" : "", # "mailAuthCode" : "", @@ -102,6 +106,7 @@ "termServerPort" : 8188, "httpServerIP" : "", "httpServerPort" : 8189, + "restapiServerPort" : 8190, "qq" : "", "mailAccount" : "", "mailAuthCode" : "", @@ -124,7 +129,7 @@ QQBot 机器人 用法: {PROGNAME} [-h] [-d] [-nd] [-u USER] [-q QQ] - [-p TERMSERVERPORT] [-ip HTTPSERVERIP][-hp HTTPSERVERPORT] + [-p TERMSERVERPORT] [-rp RESTAPISERVERPORT] [-ip HTTPSERVERIP][-hp HTTPSERVERPORT] [-m MAILACCOUNT] [-mc MAILAUTHCODE] [-r] [-nr] [-fi FETCHINTERVAL] @@ -156,6 +161,9 @@ -p TERMSERVERPORT, --termServerPort TERMSERVERPORT 更改QTerm控制台的监听端口到 TERMSERVERPORT 。 默认的监听端口是 8188 (TCP)。 + -rp RESTAPISERVERPORT, --restapiServerPort RESTAPISERVERPORT + 更改RestAPI监听端口到 RESTAPISERVERPORT 。 + 默认的监听端口是 8190 (TCP)。 HTTP二维码查看服务器设置: (请阅读说明文件以了解此HTTP服务器的详细信息。) @@ -227,7 +235,9 @@ def readCmdLine(self, argv): parser.add_argument('-ip', '--httpServerIP') - parser.add_argument('-hp', '--httpServerPort', type=int) + parser.add_argument('-hp', '--httpServerPort', type=int) + + parser.add_argument('-rp', '--restapiServerPort', type=int) parser.add_argument('-m', '--mailAccount') @@ -427,6 +437,7 @@ def Display(self): INFO('二维码服务器 ip :%s', self.httpServerIP or '无') INFO('二维码服务器端口号:%s', self.httpServerIP and self.httpServerPort or '无') + INFO('RestAPI服务器端口号:%s', self.restapiServerPort or '无') INFO('用于接收二维码的邮箱账号:%s', self.mailAccount or '无') INFO('邮箱服务授权码:%s', self.mailAccount and '******' or '无') INFO('以文本模式显示二维码:%s', self.cmdQrcode and '是' or '否') diff --git a/qqbot/qqbotcls.py b/qqbot/qqbotcls.py index cf152d8..9eb9a32 100644 --- a/qqbot/qqbotcls.py +++ b/qqbot/qqbotcls.py @@ -170,8 +170,8 @@ def Run(self): # child thread 1~4 StartDaemonThread(self.pollForever) StartDaemonThread(self.intervalForever) - #StartDaemonThread(QTermServer(self.conf.termServerPort, self.onTermCommand).Run) - StartDaemonThread(TornadoServer(self.conf.termServerPort, self.onCommand).Run) + StartDaemonThread(QTermServer(self.conf.termServerPort, self.onTermCommand).Run) + StartDaemonThread(TornadoServer(self.conf.restapiServerPort, self.onCommand).Run) self.scheduler.start() self.started = True diff --git a/qqbot/webserver/server.py b/qqbot/webserver/server.py index 531d913..d8bff54 100644 --- a/qqbot/webserver/server.py +++ b/qqbot/webserver/server.py @@ -60,9 +60,12 @@ def __init__(self, port, onCommand): self.name = 'QQBot-Term 服务器' def Run(self): - self.app = tornado.web.Application([ - (r'/api/([^/]+)', MainHandler, dict(server=self)), - (r'/api/([^/]+)/([^/]+)', MainHandler, dict(server=self)), - ]) - self.app.listen(self.port) - tornado.ioloop.IOLoop.current().start() + if not self.port: + INFO('QQBot-RestAPI 服务器未开启') + else: + self.app = tornado.web.Application([ + (r'/api/([^/]+)', MainHandler, dict(server=self)), + (r'/api/([^/]+)/([^/]+)', MainHandler, dict(server=self)), + ]) + self.app.listen(self.port) + tornado.ioloop.IOLoop.current().start() From 0c54b0c72b81ed24be0118d033e49885b3cd90dd Mon Sep 17 00:00:00 2001 From: Wilson Wang Date: Sat, 17 Mar 2018 22:28:50 -0700 Subject: [PATCH 12/12] update tornado requirements --- faq.md | 4 +++- requirements.txt | 1 + setup.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/faq.md b/faq.md index 77a2a65..27b0927 100644 --- a/faq.md +++ b/faq.md @@ -54,7 +54,7 @@ contact.ctype 为 'buddy'/'group'/'discuss' 时,分别代表本消息时 好 #### 如何采用 virtualenv 将本项目安装至独立的运行环境? -本项目依赖于 reqests 、flask 、 certifi 和 apscheduler 库,用 pip 安装本项目时会自动安装以上四个库以及它们所依赖的库。一般来说安装本项目不会与系统其他项目冲突,因此可直接安装至系统的全局 site-packages 目录。 +本项目依赖于 reqests 、flask 、 certifi 、 tornado 和 apscheduler 库,用 pip 安装本项目时会自动安装以上四个库以及它们所依赖的库。一般来说安装本项目不会与系统其他项目冲突,因此可直接安装至系统的全局 site-packages 目录。 在某些系统中可能会出现 https 请求错误,这时需要安装 certifi 库的指定版本(2015.4.28 版),可能会将系统中已有的 certifi 库升级或降级并导致会使系统中的其他项目无法使用,这时可以使用 virtualenv 将本项目安装至独立的运行环境中。 @@ -76,6 +76,7 @@ virtualenv 基本原理和使用可参考 [廖雪峰的教程](http://www.liaoxu pip install certifi==2015.4.28 pip install flask==0.12 pip install apscheduler==3.3.1 + pip install tornado==4.5.2 pip install qqbot 注意:使用本方式安装本项目后,每次使用 qqbot 和 qq 命令之前,需要先运行下面这条命令激活 qqbot-venv 下的运行环境: @@ -97,6 +98,7 @@ Windows 下, 上述脚本改为: pip install certifi==2015.4.28 pip install flask==0.12 pip install apscheduler==3.3.1 + pip install tornado==4.5.2 pip install qqbot 其中 %UserProfile% 是用户主目录,Win7中为 C:\Users\xxx 目录。 diff --git a/requirements.txt b/requirements.txt index c5e74e0..a88e0d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ requests==2.7.0 certifi==2015.4.28 flask==0.12 apscheduler==3.3.1 +tornado==4.5.2 diff --git a/setup.py b/setup.py index 5ea7562..de1c4ce 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ 'qq = qqbot:QTerm' ] }, - install_requires = ['requests', 'certifi', 'apscheduler'], + install_requires = ['requests', 'certifi', 'apscheduler', 'tornado'], description = "QQBot: A conversation robot base on Tencent's SmartQQ", author = 'pandolia' , author_email = 'pandolia@yeah.net',