init
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
// 开播、下播、开始录制、合并完成等事件的推送
|
||||
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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user