174 lines
4.8 KiB
Go
174 lines
4.8 KiB
Go
// 开播、下播、开始录制、合并完成等事件的推送
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// 推送的事件名,外部(如 NoneBot 机器人)按这个字段区分事件
|
|
const (
|
|
notifyEventLiveOn = "liveOn"
|
|
notifyEventLiveOff = "liveOff"
|
|
notifyEventRecordStart = "recordStart"
|
|
notifyEventMergeDone = "mergeDone"
|
|
)
|
|
|
|
// 推送到 webhook 的请求体
|
|
type notifyPayload struct {
|
|
Event string `json:"event"` // 事件名:liveOn、liveOff、recordStart、mergeDone
|
|
UID int `json:"uid"` // 主播 uid
|
|
Name string `json:"name"` // 主播名字
|
|
Title string `json:"title"` // 直播标题(下播和合并完成时可能为空)
|
|
URL string `json:"url"` // 直播间地址
|
|
File string `json:"file"` // 相关文件路径(录制开始时的视频文件、合并完成时的成片)
|
|
Group string `json:"group"` // web UI 里配置的通知群号,机器人可以直接用它决定发到哪个群
|
|
Text string `json:"text"` // 可以直接发出去的文本,机器人拿到后原样发送即可
|
|
Time string `json:"time"` // 事件发生的时刻
|
|
}
|
|
|
|
// 推送用的 http 客户端,超时短一点,避免推送把任务拖住
|
|
var notifyClient = &http.Client{Timeout: 10 * time.Second}
|
|
|
|
// 已经推送过开播通知的 liveID,避免重连或改设置时重复推送
|
|
var notifiedLive struct {
|
|
sync.Mutex
|
|
ids map[string]struct{}
|
|
}
|
|
|
|
// 首次见到这个 liveID 时返回 true
|
|
func notifiedLiveOnce(liveID string) bool {
|
|
if liveID == "" {
|
|
return false
|
|
}
|
|
notifiedLive.Lock()
|
|
defer notifiedLive.Unlock()
|
|
if notifiedLive.ids == nil {
|
|
notifiedLive.ids = make(map[string]struct{})
|
|
}
|
|
if _, ok := notifiedLive.ids[liveID]; ok {
|
|
return false
|
|
}
|
|
notifiedLive.ids[liveID] = struct{}{}
|
|
// 长时间运行后简单地清一次,避免无限增长
|
|
if len(notifiedLive.ids) > 4096 {
|
|
notifiedLive.ids = map[string]struct{}{liveID: {}}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 推送设置的一份快照,避免在推送过程中读到正在被修改的配置
|
|
type notifySetting struct {
|
|
url string
|
|
token string
|
|
group string
|
|
liveOn bool
|
|
liveOff bool
|
|
recordStart bool
|
|
mergeDone bool
|
|
}
|
|
|
|
func currentNotifySetting() notifySetting {
|
|
streamers.RLock()
|
|
defer streamers.RUnlock()
|
|
return notifySetting{
|
|
url: strings.TrimSpace(config.NotifyURL),
|
|
token: strings.TrimSpace(config.NotifyToken),
|
|
group: strings.TrimSpace(config.NotifyGroup),
|
|
liveOn: config.NotifyLiveOn,
|
|
liveOff: config.NotifyLiveOff,
|
|
recordStart: config.NotifyRecordStart,
|
|
mergeDone: config.NotifyMergeDone,
|
|
}
|
|
}
|
|
|
|
// 该事件是否开启了推送
|
|
func (n notifySetting) enabled(event string) bool {
|
|
switch event {
|
|
case notifyEventLiveOn:
|
|
return n.liveOn
|
|
case notifyEventLiveOff:
|
|
return n.liveOff
|
|
case notifyEventRecordStart:
|
|
return n.recordStart
|
|
case notifyEventMergeDone:
|
|
return n.mergeDone
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 推送一个事件。没配置推送地址或没开启该事件时什么都不做,
|
|
// 推送在后台 goroutine 里进行,失败只打警告,不会影响录制
|
|
func sendNotify(event string, uid int, name, title, url, file, text string) {
|
|
n := currentNotifySetting()
|
|
if n.url == "" || !n.enabled(event) {
|
|
return
|
|
}
|
|
p := notifyPayload{
|
|
Event: event,
|
|
UID: uid,
|
|
Name: name,
|
|
Title: title,
|
|
URL: url,
|
|
File: file,
|
|
Group: n.group,
|
|
Text: text,
|
|
Time: getLogTime(),
|
|
}
|
|
// 推送地址可能在推送过程中被改掉,这里把地址和口令带进闭包,保证用的是同一次配置
|
|
addr, token := n.url, n.token
|
|
safeGo("推送"+event+"通知", func() {
|
|
if err := postNotify(addr, token, p); err != nil {
|
|
lPrintWarnf("推送%s通知失败:%v", event, err)
|
|
return
|
|
}
|
|
lPrintf("已推送%s通知:%s", event, p.Text)
|
|
})
|
|
}
|
|
|
|
// 把事件 POST 到 webhook
|
|
func postNotify(addr, token string, p notifyPayload) error {
|
|
data, err := json.Marshal(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req, err := http.NewRequest(http.MethodPost, addr, bytes.NewReader(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set("X-Auth-Token", token)
|
|
}
|
|
|
|
resp, err := notifyClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return fmt.Errorf("HTTP %d %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 检查设置里的推送地址,返回错误信息,空字符串表示通过
|
|
func checkNotifyURL(addr string) string {
|
|
addr = strings.TrimSpace(addr)
|
|
if addr == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(addr, "http://") && !strings.HasPrefix(addr, "https://") {
|
|
return "推送地址必须以 http:// 或 https:// 开头:" + addr
|
|
}
|
|
return ""
|
|
}
|