最近在使用飞书。微信占空间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完成。