(原) 飞书消息提醒工具

原创文章,请后转载,并注明出处。

最近在使用飞书。微信占空间3.59G,内存1.2G。飞书存储1.02G,内存0.86G。比较一下…. 如果可能,我希望卸载微信。很多人其实已经被微信绑架。

之前做了一个企业微信通知(转到了个人微信上),达到了接收消息的目的。我用来发送一些服务器信息,一些自动化信息。也用于网站上,让网友给我发即时消息。我懒,没有做回复功能。

今天花了大半天时间,简单的,不求甚解的做了一个飞书机器人。也可以稍作改造完成微信通知同样的功能。

代码还不够干净,先留印吧。

//主函数的部分如下:
package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"reflect"
	"strings"
	"time"
)

type ReqBody struct {
	UUID  string `json:"uuid"`
	Event struct {
		AppID            string `json:"app_id"`
		ChatType         string `json:"chat_type"`
		IsMention        bool   `json:"is_mention"`
		LarkVersion      string `json:"lark_version"`
		MessageID        string `json:"message_id"`
		MsgType          string `json:"msg_type"`
		OpenChatID       string `json:"open_chat_id"`
		OpenID           string `json:"open_id"`
		OpenMessageID    string `json:"open_message_id"`
		ParentID         string `json:"parent_id"`
		RootID           string `json:"root_id"`
		TenantKey        string `json:"tenant_key"`
		Text             string `json:"text"`
		TextWithoutAtBot string `json:"text_without_at_bot"`
		Type             string `json:"type"`
		UserAgent        string `json:"user_agent"`
		UserOpenID       string `json:"user_open_id"`
	} `json:"event"`
	Token     string `json:"token"`
	Ts        string `json:"ts"`
	Type      string `json:"type"`
	Challenge string `json:"challenge"`
	Encrypt   string `json:"encrypt"`
}

// 事件信息
type EventBody struct {
	Schema string `json:"schema"`
	Header struct {
		EventID    string `json:"event_id"`
		EventType  string `json:"event_type"`
		CreateTime string `json:"create_time"`
		Token      string `json:"token"`
		AppID      string `json:"app_id"`
		TenantKey  string `json:"tenant_key"`
	} `json:"header"`
	Event struct {
		Sender struct {
			SenderID struct {
				UnionID string `json:"union_id"`
				UserID  string `json:"user_id"`
				OpenID  string `json:"open_id"`
			} `json:"sender_id"`
			SenderType string `json:"sender_type"`
			TenantKey  string `json:"tenant_key"`
		} `json:"sender"`
		Message struct {
			MessageID   string `json:"message_id"`
			RootID      string `json:"root_id"`
			ParentID    string `json:"parent_id"`
			CreateTime  string `json:"create_time"`
			ChatID      string `json:"chat_id"`
			ChatType    string `json:"chat_type"`
			MessageType string `json:"message_type"`
			Content     string `json:"content"`
		} `json:"message"`
	} `json:"event"`
}

type Challenge struct {
	Challenge string `json:"challenge"`
	Token     string `json:"token"`
	Type      string `json:"type"`
}
type Challenge1 struct {
	Challenge string `json:"challenge"`
}

type Tenant_access struct {
	Code              int    `json:"code"`
	Msg               string `json:"msg"`
	TenantAccessToken string `json:"tenant_access_token"`
	Expire            int    `json:"expire"`
	AccessTime        time.Time
}

type Send_meg struct {
	// OpenID  string `json:"open_id"`
	// MsgType string `json:"msg_type"`
	// Content struct {
	// 	Text string `json:"text"`
	// } `json:"content"`
	ReceiveID string `json:"receive_id"`
	Content   string `json:"content"`
	MsgType   string `json:"msg_type"`
}

type Access_token struct {
	App_id     string `json:"app_id"`
	App_secret string `json:"app_secret"`
}

type Send_callback struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
	Data struct {
		MessageID string `json:"message_id"`
	} `json:"data"`
}

// 信息内容
type MessageContent struct {
	Text       string `json:"text"`
	ImageKey   string `json:"image_key"`
	FileKey    string `json:"file_key"`
	FileName   string `json:"file_name"`
	Duration   int    `json:"duration"`
	UserID     string `json:"user_id"`
	Template   string `json:"template"`
	FromUser   string `json:"from_user"`
	ToChatters string `json:"to_chatters"`
	Lname      string `json:"name"`
	Longitude  string `json:"longitude"`
	Latitude   string `json:"latitude"`
	Topic      string `json:"topic"`
	StartTime  string `json:"start_time"`
}

func handle_request_url_verify(post_obj_byte []byte) []byte {
	var post_obj Challenge
	var challenge Challenge1
	err = json.Unmarshal(post_obj_byte, &post_obj)
	challenge.Challenge = post_obj.Challenge
	rsp, _ := json.Marshal(challenge)
	return rsp
}

func handle_request_url_encrypt(post_obj_byte string) []byte {
	var challenge Challenge1
	challenge.Challenge = post_obj_byte
	rsp, _ := json.Marshal(challenge)
	return rsp
}

// 获取Token
func get_tenant_access_token() Tenant_access {
	var jsonData Access_token
	var bodyContent Tenant_access
	url := "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
	jsonData.App_id = APP_ID
	jsonData.App_secret = APP_SECRET
	jsonStr, err := json.Marshal(jsonData)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		// handle error
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &bodyContent)
	bodyContent.AccessTime = time.Now() // 读取时间
	return bodyContent
}

func send_message(token string, open_id string, text string) {
	// url := "http://open.feishu.cn/open-apis/message/v4/send/"
	url := "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id"
	var send_meg Send_meg
	var bodyContent Send_callback
	// send_meg.OpenID = open_id
	send_meg.MsgType = "text"
	send_meg.Content = fmt.Sprintf(`{"text":"%s"}`, text)
	send_meg.ReceiveID = open_id
	jsonStr, err := json.Marshal(send_meg)
	fmt.Println(string(jsonStr))
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		// handle error
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &bodyContent)
	if bodyContent.Code != 0 {
		fmt.Printf("code =%v, msg =%v", bodyContent.Code, bodyContent.Msg)
	}
}

// 文本信息处理
func handle_message(Content interface{}) {
	if access_token.TenantAccessToken == "" || access_token.Code != 0 || access_token.Expire < 30 || int((time.Now()).Sub(access_token.AccessTime).Seconds()) >= access_token.Expire-10 { // 提前10秒重新获取
		access_token = get_tenant_access_token()
		fmt.Println("获取Token:", access_token.TenantAccessToken)
	}
	structType := reflect.TypeOf(Content).Name()
	if structType == "ReqBody" {
		c := Content.(ReqBody)
		if c.Event.Type != "text" {
			fmt.Printf("unknown msg_type =%v\n", c.Event.Type)
			//return
		}
		send_message(access_token.TenantAccessToken, c.Event.OpenID, c.Event.Text)
		//response(w,[]byte(""))
		return
	} else if structType == "EventBody" {
		var eventFormat MessageContent

		c := Content.(EventBody)
		json.Unmarshal([]byte(c.Event.Message.Content), &eventFormat) // 解释信息JSON
		// c.Header.EventType 信息类型 (im.message.receive_v1)
		// c.Event.Message.Content  信息内容 JSON格式
		if eventFormat.Text != "" {
			send_message(access_token.TenantAccessToken, c.Event.Sender.SenderID.OpenID, eventFormat.Text)
		}
	}
}

func calculateSignature(timestamp, nonce, encryptKey, bodystring string) string {
	var b strings.Builder
	b.WriteString(timestamp)
	b.WriteString(nonce)
	b.WriteString(encryptKey)
	b.WriteString(bodystring) //bodystring指整个请求体,不要在反序列化后再计算
	bs := []byte(b.String())
	h := sha256.New()
	h.Write(bs)
	bs = h.Sum(nil)
	sig := fmt.Sprintf("%x", bs)
	return sig
}

// 信息解密
func Decrypt(encrypt string, key string) (string, error) {
	buf, err := base64.StdEncoding.DecodeString(encrypt)
	if err != nil {
		return "", fmt.Errorf("base64StdEncode Error[%v]", err)
	}
	if len(buf) < aes.BlockSize {
		return "", errors.New("cipher  too short")
	}
	keyBs := sha256.Sum256([]byte(key))
	block, err := aes.NewCipher(keyBs[:sha256.Size])
	if err != nil {
		return "", fmt.Errorf("AESNewCipher Error[%v]", err)
	}
	iv := buf[:aes.BlockSize]
	buf = buf[aes.BlockSize:]
	// CBC mode always works in whole blocks.
	if len(buf)%aes.BlockSize != 0 {
		return "", errors.New("ciphertext is not a multiple of the block size")
	}
	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(buf, buf)
	n := strings.Index(string(buf), "{")
	if n == -1 {
		n = 0
	}
	m := strings.LastIndex(string(buf), "}")
	if m == -1 {
		m = len(buf) - 1
	}
	return string(buf[n : m+1]), nil
}

// -------------------------------------------------------

var err error
var Port = ":9988"
var APP_ID = "cli_11111111111111"
var APP_SECRET = "111111111111111"
var APP_VERIFICATION_TOKEN = ""
var EVENT_ENCRYPT = "111111111"
var EVENT_VERIFICATION_TOKEN = "111111111111111"
var ISME = "1111111111111111111111"  // 我的OpenID,将其固化到程序中,就可以直接给自己发信息了。
var access_token Tenant_access

func index(w http.ResponseWriter, r *http.Request) {
	var bodyContent ReqBody
	bodyData, err := ioutil.ReadAll(r.Body)
	// fmt.Println(r.Header)
	if err != nil {
		return
	} else {
		err = json.Unmarshal(bodyData, &bodyContent)
		if APP_VERIFICATION_TOKEN != "" && bodyContent.Token != APP_VERIFICATION_TOKEN {
			fmt.Printf("verification token not match, token =%v", bodyContent.Token)
			return
		}
		if bodyContent.Type == "url_verification" {
			rsp := handle_request_url_verify(bodyData)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(200)
			_, _ = w.Write(rsp)
			// } else if bodyContent.Type == "event_callback" {
			// 	if bodyContent.Event.Type == "message" {
			// 		handle_message(bodyContent)
			// 	}
		} else if bodyContent.Encrypt != "" {
			decrypt, _ := Decrypt(bodyContent.Encrypt, EVENT_ENCRYPT) // 解密出JSON字符串
			rsp := handle_request_url_verify([]byte(decrypt))         // 返回其中的challenge部份
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(200)
			_, _ = w.Write(rsp)
		} else {
			w.WriteHeader(200)
			_, _ = w.Write(bodyData)
			fmt.Println(string(bodyData))
		}
	}
}

func robot(w http.ResponseWriter, r *http.Request) {
	bodyData, _ := ioutil.ReadAll(r.Body)
	fmt.Println("信息:", string(bodyData))
}

func event(w http.ResponseWriter, r *http.Request) {
	var bodyContent ReqBody
	var eventContent EventBody
	bodyData, _ := ioutil.ReadAll(r.Body)
	err = json.Unmarshal(bodyData, &bodyContent)
	if bodyContent.Encrypt != "" {
		decrypt, _ := Decrypt(bodyContent.Encrypt, EVENT_ENCRYPT) // 解密出JSON字符串
		// fmt.Println("---- ", decrypt)
		json.Unmarshal([]byte(decrypt), &eventContent) // 解析JSON
		handle_message(eventContent)
	}

	// fmt.Println("事件:", string(bodyData))
}

func main() {
        // 给自己发一个通知
	go func() {
		access_token = get_tenant_access_token()
		send_message(access_token.TenantAccessToken, ISME, "服务器启动了...")
	}()
	http.HandleFunc("/", index) // 设置访问路由
	http.HandleFunc("/fs/robot", robot)
	http.HandleFunc("/fs/event", event)
	fmt.Printf("start at port%v\n", Port)
	log.Fatal(http.ListenAndServe(Port, nil))

}

给自己的企业添加一个私人机器人,指定好URL完成。

相关文章