bbgo/pkg/notifier/larknotifier/lark.go

197 lines
3.9 KiB
Go
Raw Normal View History

2024-08-30 15:03:08 +00:00
package larknotifier
import (
"bytes"
"context"
"encoding/json"
"fmt"
"git.qtrade.icu/lychiyu/bbgo/pkg/types"
"github.com/sirupsen/logrus"
"golang.org/x/time/rate"
"net/http"
"reflect"
"time"
)
var apiLimiter = rate.NewLimiter(rate.Every(time.Second), 5)
var log = logrus.WithField("service", "lark")
type notifyTask struct {
message string
}
type Notifier struct {
token string
taskC chan notifyTask
}
type Option func(notifier *Notifier)
type TextMessage struct {
MsgType string `json:"msg_type"`
Content struct {
Text string `json:"text"`
} `json:"content"`
}
func getLocDateTimeString() string {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println("Error loading location:", err)
return ""
}
// 获取当前东八区时间
currentTime := time.Now().In(loc)
// 格式化时间
formattedTime := currentTime.Format("2006-01-02 15:04:05")
return formattedTime
}
func sendLarkMessage(token, text string) error {
// 创建消息结构体
message := TextMessage{
MsgType: "text",
Content: struct {
Text string `json:"text"`
}{
Text: fmt.Sprintf("通知时间:%s\n内容\n%s", getLocDateTimeString(), text),
},
}
// 将消息结构体序列化为 JSON
jsonData, err := json.Marshal(message)
if err != nil {
return fmt.Errorf("failed to marshal JSON: %v", err)
}
webhookURL := fmt.Sprintf("https://open.larksuite.com/open-apis/bot/v2/hook/%s", token)
// 创建 HTTP POST 请求
req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
// 发送 HTTP 请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("request failed with status code: %d", resp.StatusCode)
}
return nil
}
func New(token string, options ...Option) *Notifier {
notifier := &Notifier{
token: token,
taskC: make(chan notifyTask, 100),
}
for _, o := range options {
o(notifier)
}
go notifier.worker()
return notifier
}
func (n *Notifier) worker() {
ctx := context.Background()
for {
select {
case <-ctx.Done():
return
case task := <-n.taskC:
apiLimiter.Wait(ctx)
n.consume(task)
}
}
}
func (n *Notifier) consume(task notifyTask) {
if task.message != "" {
if err := sendLarkMessage(n.token, task.message); err != nil {
log.WithError(err).Error("lark send error")
}
}
}
func (n *Notifier) Notify(obj interface{}, args ...interface{}) {
n.NotifyTo("", obj, args...)
}
func filterPlaintextMessages(args []interface{}) (texts []string, pureArgs []interface{}) {
var firstObjectOffset = -1
for idx, arg := range args {
rt := reflect.TypeOf(arg)
if rt.Kind() == reflect.Ptr {
switch a := arg.(type) {
case nil:
texts = append(texts, "nil")
if firstObjectOffset == -1 {
firstObjectOffset = idx
}
case types.PlainText:
texts = append(texts, a.PlainText())
if firstObjectOffset == -1 {
firstObjectOffset = idx
}
case types.Stringer:
texts = append(texts, a.String())
if firstObjectOffset == -1 {
firstObjectOffset = idx
}
}
}
}
pureArgs = args
if firstObjectOffset > -1 {
pureArgs = args[:firstObjectOffset]
}
return texts, pureArgs
}
func (n *Notifier) NotifyTo(channel string, obj interface{}, args ...interface{}) {
var message string
switch a := obj.(type) {
case string:
message = fmt.Sprintf(a, args...)
default:
//log.Errorf("unsupported notification format: %T %+v", a, a)
return
}
select {
case n.taskC <- notifyTask{
message: message,
}:
default:
log.Error("[lark] cannot send task to notify")
}
}
func (n *Notifier) SendPhoto(buffer *bytes.Buffer) {
n.SendPhotoTo("", buffer)
}
func (n *Notifier) SendPhotoTo(channel string, buffer *bytes.Buffer) {
// TODO
}