釘釘機器人發送消息

應用場景:

用于提醒項目構建完成,提醒消息接收

核心代碼:

# coding=utf-8
import json
import logging
import time
import hmac
import hashlib
import base64
import urllib
import requests

from manage_system.start.test_settings import WEB_HOOK, DING_SECRET

quote_plus = urllib.quote_plus


# https://oapi.dingtalk.com/robot/send?access_token=ce67d932ba4d62e490c26700cd1a922c474fe72e39a7d88b231e1e52949a531d


def is_not_null_and_blank_str(content):
    """
    非空字符串
    :param content: 字符串
    :return: 非空 - True,空 - False
    """
    if content and content.strip():
        return True
    else:
        return False


class DingClient(object):
    """
    釘釘群自定義機器人(每個機器人每分鐘最多發送20條),支持文本(text)、連接(link)、markdown三種消息類型!
    """

    def __init__(self, webhook, secret=None, fail_notice=False):
        """
        機器人初始化
        :param webhook: 釘釘群自定義機器人webhook地址
        :param secret: 機器人安全設置頁面勾選“加簽”時需要傳入的密鑰
        :param pc_slide: 消息鏈接打開方式,默認False為瀏覽器打開,設置為True時為PC端側邊欄打開
        :param fail_notice: 消息發送失敗提醒,默認為False不提醒,開發者可以根據返回的消息發送結果自行判斷和處理
        """
        super(DingClient, self).__init__()
        self.headers = {'Content-Type': 'application/json; charset=utf-8'}
        self.ls = list()  # 釘釘官方限流每分鐘發送20條信息
        self.webhook = webhook
        self.secret = secret
        self.fail_notice = fail_notice
        self.start_time = time.time()  # 加簽時,請求時間戳與請求時間不能超過1小時,用于定時更新簽名
        if self.secret is not None and self.secret.startswith('SEC'):
            self.update_webhook()

    def update_webhook(self):
        """
        釘釘群自定義機器人安全設置加簽時,簽名中的時間戳與請求時不能超過一個小時,所以每個1小時需要更新簽名
        """
        timestamp = long(round(self.start_time * 1000))
        secret_enc = bytes(self.secret).encode('utf-8')
        string_to_sign = '{}\n{}'.format(timestamp, self.secret)
        string_to_sign_enc = bytes(string_to_sign).encode('utf-8')
        hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
        sign = quote_plus(base64.b64encode(hmac_code))
        self.webhook = '{}&timestamp={}&sign={}'.format(self.webhook, str(timestamp), sign)

    def send_text(self, msg, is_at_all=False, at_mobiles=None):
        """
        text類型
        :param msg: 消息內容
        :param is_at_all: @所有人時:true,否則為false(可選)
        :param at_mobiles: 被@人的手機號(注意:可以在msg內容里自定義@手機號的位置,也支持同時@多個手機號,可選)
        :return: 返回消息發送結果
        """
        if at_mobiles is None:
            at_mobiles = []
        data = {"msgtype": "text", "at": {}}
        if is_not_null_and_blank_str(msg):
            data["text"] = {"content": msg}
        else:
            logging.error("text類型,消息內容不能為空!")
            raise ValueError("text類型,消息內容不能為空!")
        if is_at_all:
            data["at"]["isAtAll"] = is_at_all
        if at_mobiles:
            at_mobiles = list(map(str, at_mobiles))
            data["at"]["atMobiles"] = at_mobiles
        logging.debug('text類型:%s' % data)
        return self.post(data)

    def post(self, data):

        """
        發送消息(內容UTF-8編碼)
        :param data: 消息數據(字典)
        :return: 返回消息發送結果
        """
        now = time.time()

        # 釘釘自定義機器人安全設置加簽時,簽名中的時間戳與請求時不能超過一個小時,所以每個1小時需要更新簽名
        if now - self.start_time >= 3600 and self.secret is not None and self.secret.startswith('SEC'):
            self.start_time = now
            self.update_webhook()

        # 釘釘自定義機器人現在每分鐘最多發送20條消息
        self.ls.append(now)
        if len(self.ls) > 20:
            # 拿出第一個元素進行比對時間
            elapse_time = now - self.ls.pop(0)
            if elapse_time < 60:
                sleep_time = int(60 - elapse_time) + 1
                logging.debug('釘釘官方限制機器人每分鐘最多發送20條,當前發送頻率已達限制條件,休眠 {}s'.format(str(sleep_time)))
                time.sleep(sleep_time)
        try:
            post_data = json.dumps(data)
            response = requests.post(self.webhook, headers=self.headers, data=post_data)
        except requests.exceptions.HTTPError as exc:
            logging.error("消息發送失敗, HTTP error: %d, reason: %s" % (exc.response.status_code, exc.response.reason))
            raise
        except requests.exceptions.ConnectionError:
            logging.error("消息發送失敗,HTTP connection error!")
            raise
        except requests.exceptions.Timeout:
            logging.error("消息發送失敗,Timeout error!")
            raise
        except requests.exceptions.RequestException:
            logging.error("消息發送失敗, Request Exception!")
            raise
        else:
            try:
                result = response.json()
            except json.JSONDecodeError:
                logging.error("服務器響應異常,狀態碼:%s,響應內容:%s" % (response.status_code, response.text))
                return {'errcode': 500, 'errmsg': '服務器響應異常'}
            else:
                logging.debug('發送結果:%s' % result)
                # 消息發送失敗提醒(errcode 不為 0,表示消息發送異常),默認不提醒,開發者可以根據返回的消息發送結果自行判斷和處理
                if self.fail_notice and result.get('errcode', True):
                    time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
                    error_data = {
                        "msgtype": "text",
                        "text": {
                            "content": "[注意-自動通知]釘釘機器人消息發送失敗,時間:%s,原因:%s,請及時跟進,謝謝!" % (
                                time_now, result['errmsg'] if result.get('errmsg', False) else '未知異常')
                        },
                        "at": {
                            "isAtAll": False
                        }
                    }
                    logging.error("消息發送失敗,自動通知:%s" % error_data)
                    requests.post(self.webhook, headers=self.headers, data=json.dumps(error_data))
                return result


def get_ding_instance():
    return DingClient(WEB_HOOK, DING_SECRET)


if __name__ == '__main__':
    # 環境配置完成后 放于dev_settings中
    # WEB_HOOK = "https://oapi.dingtalk.com/robot/send?access_token=************************"
    # DING_SECRET = "************************"
    # d = DingClient(WEB_HOOK, DING_SECRET)
    # d.send_text('測試', at_mobiles=['*****'])
    print '測試完成'

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容