苍天有泪 发表于 2019-1-17 14:03:28

Zabbix微信个人账号告警

  前言:
  最近研究zabbix告警,网上看了帖子有各式各样姿势:电话语音告警,邮件告警,短信告警,微信公众号告警等等等..姿势五花八门,真是纠结。
  电话语音告警,短信告警首先pass 前者花钱,后者通过设置139邮箱,就可以实现伪短信告警效果。
  剩下邮件告警与微信公众号告警。邮件告警已经在部署的时候配置完毕,剩下这个微信公众号告警,查一下帖子,申请各种麻烦。那么有没有基于微信个人账号的告警呢?想到这个点,马上github一番。
  搜索到一些优秀的开源代码:https://github.com/0x5e/wechat-deleted-friends

  及封包:https://github.com/xiangzhai/qwx/blob/master/doc/protocol.md
  

  经过一番思考,大致思路如下:
  微信WEB保持活动状态,通过不断使用zabbix api抓取故障告警,入库后微信发送告警给相关人员。

  

  一 需求实现具体思路

  

  实现该需求是得有多个线程同时进行的

[*]  微信心跳,微信WEB版保持活动状态。

[*]  ZABBIX API ,不断请求zabbix告警,发现告警后,判断后入库。
[*]  使用数据库不断查询告警,如果发现符合条件告警则发送告警给相关人员。
  

  

  二 部分代码及注释
  第一部分:wechat

# coding=utf-8
#伪装请求头
headers = {'User-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36'}
myRequests = requests.Session()
myRequests.headers.update(headers)
DEBUG = False
#微信类
class WeChat(object):
    def __init__(self):
      self.uuid = ''
      self.base_uri = ''
      self.push_uri = ''
      self.redirect_uri = ''
      self.BaseRequest = {}
      self.skey = ''
      self.wxuin = ''
      self.wxsid = ''
      self.skey = ''
      self.deviceId = 'e' + repr(random())   #随机生成15位机器码
      self.pass_ticket = ''
      self.MemberList =[]
      self.ContactList = []
      self.AlarmFriends =[]
      self.Intervals = ''
      self.xintiao = ''
    #获取UUID
    def Get_UUID(self):
      url = 'https://login.weixin.qq.com/jslogin'
      params = {
            'appid': 'wx782c26e4c19acffb',
            'fun': 'new',
            'lang': 'zh_CN',
            '_': int(time.time()),
      }
      r = myRequests.get(url=url, params=params)
      r.encoding = 'utf-8'
      data = r.text
      #       data返回,code=200为状态.uuid="IZTW06WnSg=="为uuid
      #   window.QRLogin.code = 200; window.QRLogin.uuid = "IZtWO6WnSg==";
      # 正则匹配:匹配出状态码    以及UUID
      regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'
      PM = re.search(regx, data)
      code = PM.group(1)
      if code == '200':
            self.uuid = PM.group(2)
            return True
      return False
    #   windows下直接打开二维码图
    def _openWinQRCodeImg(self):
      url = 'https://login.weixin.qq.com/qrcode/' + self.uuid
      params = {
            't': 'webwx',
            '_': int(time.time())
      }
      r = myRequests.get(url=url, params=params)
      f = open(QRImagePath, 'wb')
      f.write(r.content)
      f.close()
      time.sleep(1)
      os.startfile(QRImagePath)
    #   Linux下的二维码处理
    def _printQR(self, mat):
      for i in mat:
            BLACK = '\033[40m\033[0m'
            WHITE = '\033[47m\033[0m'
            print (''.join())
    def _str2qr(self, str):
      qr = qrcode.QRCode()
      qr.border = 1
      qr.add_data(str)
      mat = qr.get_matrix()
      self._printQR(mat)# qr.print_tty() or qr.print_ascii()
    #   判断操作系统,选择打开二维码扫描方式
    def genQRCode(self):
      if sys.platform.startswith('win'):
            self._openWinQRCodeImg()
      else:
            self._str2qr('https://login.weixin.qq.com/l/' + self.uuid)
    #等待登陆
    def WaitForLogin(self, tip=1):
      time.sleep(tip)
      url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (
            tip, self.uuid, int(time.time()))
      r = myRequests.get(url=url)
      r.encoding = 'utf-8'
      data = r.text
      #   data返回:
      #   window.code = 201;
      #判断返回码
      regx = r'window.code=(\d+);'
      pm = re.search(regx, data)
      code = pm.group(1)
      if code == '201':# 已扫描
            print('
[*]成功扫描,请在手机上点击确认以登录')
      elif code == '200':# 已登录
            print('[.]正在登录...')
            regx = r'window.redirect_uri="(\S+?)";'
            pm = re.search(regx, data)
            self.redirect_uri = pm.group(1) + '&fun=new'
            base_uri = self.redirect_uri[:self.redirect_uri.rfind('/')]
            # push_uri与base_uri对应关系(排名分先后)
            services = [
                ('wx2.qq.com', 'webpush2.weixin.qq.com'),
                ('qq.com', 'webpush.weixin.qq.com'),
                ('web1.wechat.com', 'webpush1.wechat.com'),
                ('web2.wechat.com', 'webpush2.wechat.com'),
                ('wechat.com', 'webpush.wechat.com'),
                ('web1.wechatapp.com', 'webpush1.wechatapp.com'),
            ]
          #self.push_uri = self.base_uri
            self.push_uri = base_uri
            for (searchUrl, pushUrl) in services:
                if base_uri.find(searchUrl) >= 0:
                  self.push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl
                  self.base_uri = 'https://%s/cgi-bin/mmwebwx-bin' % searchUrl
                  break
      elif code == '408':# 超时
            pass
      # elif code == '400' or code == '500':
      return code
    #登陆
    def login(self):
      r = myRequests.get(url=self.redirect_uri)
      r.encoding = 'utf-8'
      data = r.text
    #    print (data)
      # data返回
      #   < ret > 0 < / ret > < message > OK < / message >
      #   < skey >XXXX < skey >
      #   < wxsid > XXXX < / wxsid >
      #   < wxuin > XXXX < / wxuin >
      #   < pass_ticket > XXXX < / pass_ticket >
      #   < isgrayscale > 1 < / isgrayscale >
      #解析XML文件
      doc = xml.dom.minidom.parseString(data)
      root = doc.documentElement
      for node in root.childNodes:
            if node.nodeName == 'skey':
                self.skey = node.childNodes.data
            elif node.nodeName == 'wxsid':
                self.wxsid = node.childNodes.data
            elif node.nodeName == 'wxuin':
                self. wxuin = node.childNodes.data
            elif node.nodeName == 'pass_ticket':
                self.pass_ticket = node.childNodes.data
    #    print('skey: %s, wxsid: %s, wxuin: %s, pass_ticket: %s' % (skey, wxsid,wxuin, pass_ticket))
      if not all((self.skey, self.wxsid, self.wxuin, self.pass_ticket)):
            return False
      self.BaseRequest = {
            'Uin': int(self.wxuin),
            'Sid': self.wxsid,
            'Skey': self.skey,
            'DeviceID': self.deviceId,
      }
   #   print (self.push_uri)
      return True
    def webwxinit(self):
      url = ( self.base_uri +'/webwxinit?pass_ticket=%s&skey=%s&r=%s' \
                % (self.pass_ticket, self.skey, int(time.time())))
      params = {'BaseRequest': self.BaseRequest}
      headers = {'content-type': 'application/json; charset=UTF-8'}
      r = myRequests.post(url=url, data=json.dumps(params), headers=headers)
      r.encoding = 'utf-8'
      data = r.json()
      self.SyncKey = data['SyncKey']
      self.User = data['User']
      if False:
            f = open(os.path.join(os.getcwd(), 'webwxinit.json'), 'wb')
            f.write(r.content)
            f.close()
      self.synckey = '|'.join() + '_' + str(keyVal['Val']) for keyVal in self.SyncKey['List']])
      state = self.responseState('webwxinit', data['BaseResponse'])
      return state
    def webwxstatusnotify(self):
      url = self.base_uri + \
            '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (self.pass_ticket)
      params = {
            'BaseRequest': self.BaseRequest,
            "Code": 3,
            "FromUserName": self.User['UserName'],
            "ToUserName": self.User['UserName'],
            "ClientMsgId": int(time.time())
      }
      r = myRequests.post(url=url, params=json.dumps(params))
      data = r.json()
      state = self.responseState('WexinStatusNoTify',data['BaseResponse'])
      #return data['BaseResponse']['Ret'] == 0
      return state
    #获取好友列表
    def webwxgetcontact(self):
      url = (self.base_uri +'/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (\
                self.pass_ticket, self.skey, int(time.time())))
      headers = {'content-type': 'application/json; charset=UTF-8'}
      r = myRequests.post(url=url, headers=headers)
      r.encoding = 'utf-8'
      data = r.json()
      if False:
            f = open(os.path.join(os.getcwd(), 'webwxgetcontact.json'), 'wb')
            f.write(r.content)
            f.close()
      self.MemberList = data['MemberList']
      SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage",\
                        "qqsync","floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp",\
                        "blogapp", "facebookapp", "masssendapp","meishiapp", "feedsapp", "voip",\
                        "blogappweixin", "weixin", "brandsessionholder", "weixinreminder",\
                        "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts",\
                        "notification_messages", "wxitil", "userexperience_alarm"]
      #将列表中特殊账号删除
      for i in range(len(self.MemberList) - 1, -1, -1):
            Member = self.MemberList
            if Member['VerifyFlag'] & 8 != 0:# 公众号/服务号
                self.MemberList.remove(Member)
            elif Member['UserName'] in SpecialUsers:# 特殊账号
                self.MemberList.remove(Member)
            elif Member['UserName'].find('@@') != -1:# 群聊
                self.MemberList.remove(Member)
            elif Member['UserName'] ==self.User:# 自己
                self.MemberList.remove(Member)
      self.ContactList = self.MemberList
      return True
    #发送信息
    def webwxsendmsg(self, word, to='filehelper'):
      url = self.base_uri + \
            '/webwxsendmsg?pass_ticket=%s' % (self.pass_ticket)
      clientMsgId = str(int(time.time() * 1000)) + \
                      str(random())[:5].replace('.', '')
      params = {
            'BaseRequest':{
                "Uin": int(self.wxuin),
                "Sid": self.wxsid,
                "Skey":self.skey,
                "DeviceID": self.deviceId,
            },
            'Scene': 0,
            'Msg':{
                "Type": 1,
                "Content": self._transcoding(word),
                "FromUserName": self.User['UserName'],
                "ToUserName": to,
                "LocalID": clientMsgId,
                "ClientMsgId": clientMsgId,
            }
      }
      headers = {'content-type': 'application/json; charset=UTF-8'}
      data = json.dumps(params, ensure_ascii=False).encode('utf8')
      r =myRequests.post(url,data=data,headers=headers)
      dic = r.json()
      state = self.responseState('SendMsg', dic['BaseResponse'])
      print (params)
      return state
      #print (params)
    def Wx_Views(self):
      print ('[.]正在获取好友列表..')
      list = self.ContactList
      Alarmlist=[]
      # list = json.dump(List,ensure_ascii=False)
      for i in range(0,len(list)):
            if list:
                list['id'] = i
                Name = self._untostr(list['NickName'])
                Rname = self._untostr(list['RemarkName'])
                Id = i
                #print (list)
                print ('\t %d\t姓名:%s \t 备注:%s' %(Id,Name,Rname))
            else:
                print ('[!]获取失败!')
                exit()
      while True:
            try:
                iNput = raw_input("[.]请设置告警对象ID,使用空格隔开\n")
                Alist = iNput.split(' ')
            except:
                print ("[!]输入错误!")
            try:
                for i in range(0,len(Alist)):
                  if Alist:
                        for j in range(0, len(list)):
                            if int(Alist) == list['id']:
                              print ('
[*]你设置的对象是:%s' % self._untostr(list['NickName']))
                              Alarmlist.append(list['UserName'])
                              self.AlarmFriends.append(list['UserName'])
                            else:
                              pass
            except:
                continue
            if self.AlarmFriends:
                Input = raw_input ("[!]确认设置(y/n)")
                if Input == 'y':
                  self.AlarmFriends = Alarmlist
                  break
                elif Input == 'n':
                  Alarmlist = []
                  self.AlarmFriends = []
                  pass
                else:
                  Alarmlist = []
                  self.AlarmFriends = []
                  print ("[!]输入错误")
            else:
                print ("[!]检测不到有效输入,请重试")
      print (self.AlarmFriends)
    def Wx_heartBeatLoop(self):
      while True:
            selector = self.syncCheck()
            if selector != '0':
                self.webwxsync()
            time.sleep(int(self.xintiao))
            print ("
[*]Wechat心跳正常..")
    def run(self):
      while True:
            time.sleep(5)
            SleepTime = int(self.Intervals)
            print("
[*]告警检测心跳..")
            Time = int(time.time())
            LastTime = Time - int(SleepTime)
            Select_sql = "SELECT * FROM wechat_sendmsg WHERE TIME BETWEEN %d and %d" % (LastTime,Time)
            data = db.select(Select_sql)
            # print (data)
            if data:
                for i in data:
                  print (data )
                  triggerTime = self._untostr(i)
                  Hostname =self._untostr(i)
                  HostIP = self._untostr(i)
                  Description = self._untostr(i)
                  level = self._untostr(i)
                  msg = """
[!]发现告警
告警服务器:%s
告警时间:%s
告警IP:%s
告警项:%s
告警级别:%s
""" %(Hostname,triggerTime,HostIP,Description,level)
                  print (msg)
                  for j in range(0,len(self.AlarmFriends)):
                        self.webwxsendmsg(msg,self.AlarmFriends)
                        # print (j)
                        # print (self.AlarmFriends)
            else:
                pass  第二部分:zabbix API
from ZabbixTriggerDb import SQLiteDB
myRequests = requests.Session()
db = SQLiteDB
class Zabbix(object):
    def __init__(self):
      self.Holist = []
      self.Zabbix_Address = ''
      self.Zabbix_Username=''
      self.Time = time.strftime('%Y-%m-%d %H:%M')
      self.Passwd = ''
      self.z_Intervals = ''
      self.w_Intervals = ''
      self.sleeptime = ''
      self.Trigger= []
      self.LastTrigger = []
      self.WxTriggerList = []
      #获取zabbix api token
    def get_auth(self):
      url = '%s/api_jsonrpc.php' % self.Zabbix_Address
      params = json.dumps({
            "jsonrpc": "2.0",
            "method": "user.login",
            "params": {
                "user": self.Zabbix_Username,
                "password": self.Passwd
                },
            "id": 0
      })
      headers = {'content-type': 'application/json; charset=UTF-8'}
      r = myRequests.post(url=url, data=params, headers=headers)
      r.encoding = 'utf-8'
      data = r.json()
      return data['result']
      #获取zabbix监控主机列表
    def get_host(self):
      url = '%s/api_jsonrpc.php' % self.Zabbix_Address
      params = json.dumps({
                "jsonrpc": "2.0",
                "method": "host.get",
                "params": {
                  "output":[
                        "hostid",
                        "name"
                  ],
                "selectInterfaces":[
                  "interfaceid",
                  "ip",
                ]
                },
                "id":2,
                "auth":self.get_auth()
      })
      headers = {'content-type': 'application/json; charset=UTF-8'}
      r = myRequests.post(url=url, data=params, headers=headers)
      r.encoding = 'utf-8'
      data = r.json()
      self.Holist = data['result']
      return self.Holist
      
      #获取告警
    def get_trig(self,hostid):
      url = '%s/api_jsonrpc.php' % self.Zabbix_Address
      params = json.dumps({
                "jsonrpc":"2.0",
                "method":"trigger.get",
                "params": {
                  "output": [
                            "triggerid",
                            "description",
                            "priority"
                            ],
                  "filter": {
                            "value": 1,
                            "hostid":hostid
                            },
                  "sortfield": "priority",
                  "sortorder": "DESC"
                         },
                "auth": self.get_auth(),
                "id":1
      })
      headers = {'content-type': 'application/json; charset=UTF-8'}
      r = myRequests.post(url=url, data=params, headers=headers)
      r.encoding = 'utf-8'
      data = r.json()
      if data['result']:
   #       text = json.dumps(data,ensure_ascii=False)
            return data['result']
      else:
            return None
#告警信息入库
    def get_triggerlist(self):
      list = self.Holist
      if list:
            for i in range(0,len(list)):
                # ip = self._untostr(list['interfaces']['ip'])
                trigger = self.get_trig(list['hostid'])
                Level = {'1':'DISASTER','2':'HIGH','3':'AVERAGE','4':'WARNING','5':'INFORMATION',\
                            '6':'NOT CLASSIFIED'}
                if trigger != None:
                  Trigger = self._untostr(trigger['description'])
                  level = self._untostr(trigger['priority'])
                  name = self._untostr(list['name'])
                  ip = self._untostr(list['interfaces']['ip'])
                  Datatime = time.strftime("%Y-%m-%d %H:%M", time.localtime())
                  Time = int(time.time())
                  z_LastTime = Time - int(self.z_Intervals)
                  w_LastTime = Time - int(self.w_Intervals)
                     #这个地方先查询在间隔时间段内有没有存在相同数据,如果没有就插入,有就跳过
                  zabbix_sql = "SELECT * FROM zabbix_trigger WHERE HOSTNAME='%s' and \
    DESCRIPTION='%s' and TIME BETWEEN %d and %d " % (name,Trigger,z_LastTime,Time)
                  z_data = db.select(zabbix_sql)
                  if z_data:
                        pass
                  else:
                        z_Inset_sql = "INSERT INTO zabbix_trigger(DATA,TIME,HOSTNAME,HOSTIP,DESCRIPTION,LEVEL)\
    VALUES('%s',%d,'%s','%s','%s','%s');" % (Datatime,Time,name,ip,Trigger,Level)
                        db.insert(z_Inset_sql)
                        #判断间隔时间内wechat_sendmsg表中是否存在相同输入,没有则插入
                  wechat_sql = "SELECT * FROM wechat_sendmsg WHERE HOSTNAME='%s' and \
    DESCRIPTION='%s' and TIME BETWEEN %d and %d limit 1" % (name, Trigger, w_LastTime, Time)
                  w_data = db.select(wechat_sql)
                  if w_data:
                        pass
                  else:
                        w_Inset_sql = "INSERT INTO wechat_sendmsg(DATA,TIME,HOSTNAME,HOSTIP,DESCRIPTION,LEVEL)\
    VALUES('%s',%d,'%s','%s','%s','%s');" % (Datatime, Time, name, ip, Trigger, Level)
                        db.insert(w_Inset_sql)
                  #这里思路是 设定时间内获取告警信息存入zabbix表,
                  #然后再另外设定一个时间写入weixin表 这样做是为了一个时间范围内不重复告警      
      else:
            print ("[!]获取主机列表失败,正在重新获取...")
            self.get_auth()
            self.get_host()
            self.get_triggerlist()
   #ZABBIX 心跳
    def run(self):
      while True:
            print ("
[*]Zabbix心跳正常..")
            time.sleep(self.sleeptime)
            self.get_triggerlist()  第三部分:SQLite
# coding=utf-8
import sqlite3,os
SQLiteDB = os.path.join(os.getcwd(), 'TriggerDB.db')
DBCON = sqlite3.connect(SQLiteDB,check_same_thread=False) #多线程操作要开启这个选项
DBCUR = DBCON.cursor()
class SQLiteDB(object):
    @staticmethod
    def insert(sql):
      try:
            DBCUR.execute(sql)
      except sqlite3.Error as e:
            print ("[!]Insert Error! %s" % e.args)
      DBCON.commit()
    @staticmethod
    def select(sql):
      data = []
      try:
            DBCUR.execute(sql)
            data = DBCUR.fetchall()
      except sqlite3.Error as e:
            print ("[!]Slect Error!%s"% e.args)
      return data
      #初始化,新建两个表
    @staticmethod
    def CreatTable():
      zabbix_sql ="create table if not exists Zabbix_Trigger (DATA text,TIME integer,HOSTNAME text, \
             HOSTIP text,DESCRIPTION text,LEVEL text);"
      weixin_sql = "create table if not exists Wechat_Sendmsg (DATA text,TIME integer,HOSTNAME text, \
             HOSTIP text,DESCRIPTION text,LEVEL text);"
      try:
            DBCUR.execute(zabbix_sql)
            DBCUR.execute(weixin_sql)
      except sqlite3.Error as e:
            print ("[!]Creat Error! %s" % e.args)  第四部分:合体
# coding=utf-8
from Zabbix import Zabbix
from ZabbixTriggerDb import SQLiteDB
from WeChat import WeChat
import os,sys,thread


if __name__ == '__main__':
    db = SQLiteDB
    db.CreatTable()
    z = Zabbix()    #zabbix类
    w = WeChat()    #wechat类

    z.Zabbix_Address = 'http://Zabbix服务器地址'
    z.Zabbix_Username = 'zabbix用户'
    z.Passwd = 'zabbix密码'
    z.z_Intervals = 600   #zabbix告警入库间隔
    z.w_Intervals = 3600    #wechat告警入库间隔
    z.sleeptime = 10      #Zabbix心跳间隔
    w.Intervals = 3         #告警检测心跳间隔
    w.xintiao = 2         #微信心跳间隔
    z.get_auth()            #zabbix token
    z.get_host()            #zabbix hostlist
    z.get_triggerlist()   #zabbix triggerlist

    if not w.Get_UUID():
      print('[!]获取uuid失败,请重新运行!')
    print('
[*]正在获取二维码图片...')
    w.genQRCode()       #获取二维码
    while w.WaitForLogin() != '200':
      pass
    w.login()         #登陆
    w.webwxinit()       #初始化
    w.webwxgetcontact() #获取好友列表
    w.Wx_Views()      #设置告警好友

   #定义一个线程方法,加入zabbix运行线程与微信发送告警线程
    def RUN():
      thread.start_new(z.run, ())
      thread.start_new(w.run, ())
    RUN()
    #启动微信心跳(让微信保持在线状态)
    w.Wx_heartBeatLoop()  

  最终效果:
http://s2.运维网.com/wyfs02/M01/84/6D/wKiom1eQeB_hmSQ1AAVSD5RfSEg653.png-wh_500x0-wm_3-wmp_4-s_1322861761.png
http://s3.运维网.com/wyfs02/M01/84/6D/wKioL1eQeCLCuC69AAVjp8lldPI740.png-wh_500x0-wm_3-wmp_4-s_1559864173.png
http://s1.运维网.com/wyfs02/M01/84/6D/wKiom1eQeCbw8cI8AAetjBfSqg4621.png-wh_500x0-wm_3-wmp_4-s_3308396360.png
http://s4.运维网.com/wyfs02/M02/84/6D/wKiom1eQeCuz2KbaAAi3sz-LEfk380.png-wh_500x0-wm_3-wmp_4-s_4142464422.png
http://s2.运维网.com/wyfs02/M02/84/6D/wKioL1eQeCvzVP4qAAEaS_emPzI880.png-wh_500x0-wm_3-wmp_4-s_2067830480.png
  




页: [1]
查看完整版本: Zabbix微信个人账号告警