feat(sender/onebot): 新增onebot协议支持

This commit is contained in:
mei
2026-03-01 15:27:45 +08:00
parent e68d48e816
commit 4f305cfeda
19 changed files with 214 additions and 245 deletions
+283
View File
@@ -0,0 +1,283 @@
package sender
import (
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/CuteReimu/bilibili/v2"
"github.com/ssdomei232/goodBaby/configs"
"github.com/ssdomei232/goodBaby/internal/base"
)
// check tmp dir exist
func EnsureTmpDirectory() error {
_, err := os.Stat("tmp")
if os.IsNotExist(err) {
err = os.Mkdir("tmp", 0755)
if err != nil {
return err
}
log.Println("已创建tmp目录")
} else if err != nil {
return err
} else {
log.Println("tmp目录已存在")
}
return nil
}
func isCookiesExpired(cookiesString string) bool {
// parase expires date from cookie string
lines := strings.SplitSeq(cookiesString, "\n")
for line := range lines {
if strings.Contains(line, "Expires=") {
// 提取过期时间字符串
expireStr := ""
parts := strings.SplitSeq(line, ";")
for part := range parts {
part = strings.TrimSpace(part)
if after, ok := strings.CutPrefix(part, "Expires="); ok {
expireStr = after
break
}
}
if expireStr != "" {
// phrase expirres date
expireTime, err := http.ParseTime(expireStr)
if err != nil {
log.Printf("解析过期时间失败: %v", err)
return true // if phrase failed, cookie expired
}
// compare expires date and now
if time.Now().After(expireTime) {
return true // expired
}
}
}
}
return false // not expired
}
// Qrcode login
func LoginWithQRCode(biliClient *bilibili.Client) {
qrCode, err := biliClient.GetQRCode()
if err != nil {
log.Printf("获取二维码失败: %v", err)
return
}
log.Println("请使用哔哩哔哩APP扫码登录或进入文件目录寻找qrcode.png查看二维码")
buf, _ := qrCode.Encode()
os.WriteFile("tmp/qrcode.png", buf, 0644)
qrCode.Print()
result, err := biliClient.LoginWithQRCode(bilibili.LoginWithQRCodeParam{
QrcodeKey: qrCode.QrcodeKey,
})
if err != nil || result.Code != 0 {
log.Printf("登录失败: %v", err)
return
}
log.Println("登录成功")
// save new cookie
saveCookies(biliClient)
}
// save cookie to file
func saveCookies(client *bilibili.Client) {
cookiesString := client.GetCookiesString()
err := os.WriteFile("tmp/cookies.txt", []byte(cookiesString), 0644)
if err != nil {
log.Printf("保存cookie失败: %v", err)
}
}
// 从文件加载cookie并验证有效性
func LoadCookies(client *bilibili.Client) bool {
// 检查cookie文件是否存在
if _, err := os.Stat("tmp/cookies.txt"); os.IsNotExist(err) {
return false
}
// 读取cookie
cookiesBytes, err := os.ReadFile("tmp/cookies.txt")
if err != nil {
log.Printf("读取cookie文件失败: %v", err)
return false
}
cookiesString := string(cookiesBytes)
client.SetCookiesString(cookiesString)
// 验证cookie是否过期
if isCookiesExpired(cookiesString) {
log.Println("cookie已过期")
return false
}
return true
}
// 在Bilibili动态发送死亡通告
func SendBili(biliClient *bilibili.Client) {
config, err := configs.GetConfig()
if err != nil {
log.Printf("获取配置文件失败: %v", err)
return
}
stopMsg := fmt.Sprintf("%s\n\n%s\n\n本消息由自动程序发送(摇篮系统)", base.GetBasicInfo(), config.BiliMsg)
var dynamicParams bilibili.CreateDynamicParam
dynamicParams = bilibili.CreateDynamicParam{
DynamicId: 0,
Type: 4,
Rid: 0,
Content: stopMsg,
}
// 重试机制
retryCount := 10 // 重试次数
delay := time.Second * 2 // 初始重试延迟
for i := 0; i <= retryCount; i++ {
_, err := biliClient.CreateDynamic(dynamicParams)
if err == nil {
log.Println("动态发送成功")
return
}
log.Printf("动态发送失败 (尝试 %d/%d): %v", i+1, retryCount+1, err)
// 如果不是最后一次尝试,则等待后重试
if i < retryCount {
log.Printf("等待 %v 后进行第 %d 次重试", delay, i+2)
time.Sleep(delay)
delay *= 2 // 指数退避
}
}
log.Println("动态发送最终失败,已达到最大重试次数")
}
// 检查cookie有效性
func CheckCookieValidity(biliClient *bilibili.Client) {
// 读取存储的cookie文件
if _, err := os.Stat("tmp/cookies.txt"); os.IsNotExist(err) {
log.Println("cookie文件不存在,需要重新登录")
triggerLoginRequest(biliClient)
return
}
cookiesBytes, err := os.ReadFile("tmp/cookies.txt")
if err != nil {
log.Printf("读取cookie文件失败: %v", err)
triggerLoginRequest(biliClient)
return
}
cookiesString := string(cookiesBytes)
// 检查cookie是否即将过期(提前1天提醒)
if isCookiesExpiringSoon(cookiesString) {
log.Println("cookie即将过期,需要重新登录")
triggerLoginRequest(biliClient)
}
}
// 检查cookie是否即将过期(提前1天)
func isCookiesExpiringSoon(cookiesString string) bool {
lines := strings.SplitSeq(cookiesString, "\n")
for line := range lines {
if strings.Contains(line, "Expires=") {
// 提取过期时间字符串
expireStr := ""
parts := strings.SplitSeq(line, ";")
for part := range parts {
part = strings.TrimSpace(part)
if after, ok := strings.CutPrefix(part, "Expires="); ok {
expireStr = after
break
}
}
if expireStr != "" {
// 解析过期时间
expireTime, err := http.ParseTime(expireStr)
if err != nil {
log.Printf("解析过期时间失败: %v", err)
return true // 如果解析失败,认为需要重新登录
}
// 检查是否即将过期(提前1天)
if time.Now().Add(24 * time.Hour).After(expireTime) {
return true // 即将过期
}
}
}
}
return false
}
// 触发登录请求
func triggerLoginRequest(biliClient *bilibili.Client) {
// 获取配置
config, err := configs.GetConfig()
if err != nil {
log.Printf("获取配置文件失败: %v", err)
return
}
// 生成新的二维码
qrCode, err := biliClient.GetQRCode()
if err != nil {
log.Printf("获取二维码失败: %v", err)
return
}
// 在命令行打印二维码
log.Println("需要重新登录哔哩哔哩")
qrCode.Print()
// 通过Dingtalk发送登录请求
title := "B站登录请求 - Cookie即将过期"
msg := "您的B站cookie即将过期,请尽快重启摇篮系统进行登陆,请尽快完成登录操作。"
// 发送Dingtalk通知
if config.DingtalkBot.AccessToken != "" && config.DingtalkBot.Secret != "" {
SendDingTalkMsg(title, msg, config.DingtalkBot.AccessToken, config.DingtalkBot.Secret)
}
}
// Enable periodic cookie checks
func StartCookieChecker(cookieCheckTimer *time.Ticker, biliClient *bilibili.Client) {
// check cookie per hour
cookieCheckTimer = time.NewTicker(1 * time.Hour)
go func() {
for {
<-cookieCheckTimer.C
CheckCookieValidity(biliClient)
}
}()
}
func InitBilibili(biliClient *bilibili.Client) {
// try cached cookie
if !LoadCookies(biliClient) {
// if cookie check failed, request qrcode login
LoginWithQRCode(biliClient)
} else {
log.Println("使用已存储的有效cookie登录")
}
}
+16
View File
@@ -0,0 +1,16 @@
package sender
import (
"log"
"github.com/CatchZeng/dingtalk/pkg/dingtalk"
)
func SendDingTalkMsg(title string, msg string, accessToken string, secret string) {
client := dingtalk.NewClient(accessToken, secret)
sendMsg := dingtalk.NewMarkdownMessage().SetMarkdown(title, msg)
_, _, err := client.Send(sendMsg)
if err != nil {
log.Printf("发送钉钉消息失败: %v", err)
}
}
+99
View File
@@ -0,0 +1,99 @@
package sender
import (
"fmt"
"log"
"time"
"github.com/ssdomei232/goodBaby/configs"
"github.com/ssdomei232/goodBaby/internal/base"
"github.com/wneessen/go-mail"
)
const (
maxRetries = 10
retryDelay = 10 * time.Second
)
// 向邮件列表发送死亡通告
func SendMail() {
config, err := configs.GetConfig()
if err != nil {
log.Printf("获取配置文件失败: %v", err)
return
}
stopMsg := fmt.Sprintf("%s\n\n%s\n\n本消息由自动程序发送", base.GetBasicInfo(), config.MailConfig.MailContent)
for _, address := range config.MailConfig.MailList {
sendMailMsgWithRetry(address, stopMsg, config.MailConfig.MailTitle)
}
}
func sendMailMsgWithRetry(address string, msg string, title string) {
var lastErr error
// 重试循环
for i := 0; i <= maxRetries; i++ {
if i > 0 {
log.Printf("第 %d 次重试发送邮件给 %s", i, address)
}
err := sendMailMsg(address, msg, title)
if err == nil {
// 发送成功
fmt.Printf("邮件成功发送给 %s\n", address)
return
}
lastErr = err
log.Printf("发送邮件给 %s 失败: %v", address, err)
// 如果不是最后一次重试,则等待后重试
if i < maxRetries {
time.Sleep(retryDelay * time.Duration(i+1)) // 递增延迟
}
}
// 所有重试都失败
log.Printf("发送邮件给 %s 经过 %d 次重试后仍然失败: %v", address, maxRetries, lastErr)
}
func sendMailMsg(address string, msg string, title string) error {
config, err := configs.GetConfig()
if err != nil {
return fmt.Errorf("获取配置文件失败: %v", err)
}
client, err := mail.NewClient(
config.MailConfig.Host,
mail.WithPort(config.MailConfig.Port),
mail.WithSSL(),
mail.WithUsername(config.MailConfig.User),
mail.WithPassword(config.MailConfig.Password),
mail.WithSMTPAuth(mail.SMTPAuthPlain),
)
if err != nil {
return fmt.Errorf("创建邮件客户端失败: %v", err)
}
// 创建邮件
message := mail.NewMsg()
if err := message.From(config.MailConfig.User); err != nil {
return fmt.Errorf("设置发件人失败: %v", err)
}
if err := message.To(address); err != nil {
return fmt.Errorf("设置收件人失败: %v", err)
}
message.Subject(title)
message.SetBodyString(mail.TypeTextPlain, msg)
// 发送邮件
if err := client.DialAndSend(message); err != nil {
return fmt.Errorf("发送邮件失败: %v", err)
}
return nil
}
+36
View File
@@ -0,0 +1,36 @@
package sender
import (
"log"
"time"
"github.com/ssdomei232/goodBaby/configs"
"github.com/ssdomei232/goodBaby/pkg/github"
)
// 将repos设置为public
func Github() {
config, err := configs.GetConfig()
if err != nil {
log.Fatalln(err)
}
for _, repo := range config.GithubConfig.Repos {
maxRetries := 10
for attempt := range maxRetries {
err := github.MakeRepositoryPublic(config.GithubConfig.Owner, repo, config.GithubConfig.Token)
if err == nil {
break
}
log.Printf("尝试设置仓库 %s 为公开失败 (尝试 %d/%d): %v", repo, attempt+1, maxRetries, err)
// 如果不是最后一次尝试,则等待一段时间后重试
if attempt < maxRetries-1 {
time.Sleep(time.Duration(attempt+1) * time.Second) // 逐步增加等待时间
} else {
log.Printf("设置仓库 %s 为公开最终失败", repo)
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
package sender
import (
"fmt"
"log"
"strconv"
"github.com/ssdomei232/goodBaby/configs"
"github.com/ssdomei232/goodBaby/internal/base"
napcat "github.com/ssdomei232/napcat-http-go-sdk"
)
func SendOneBot() {
config, err := configs.GetConfig()
if err != nil {
log.Printf("获取配置文件失败: %v", err)
return
}
stopMsg := fmt.Sprintf("%s\n\n%s\n\n本消息由自动程序发送(摇篮系统)", base.GetBasicInfo(), config.OneBotConfig.Msg)
for _, groupId := range config.OneBotConfig.SendGroups {
sendOneBotMsg(groupId, stopMsg)
}
}
// 发送一条消息到OneBot
func sendOneBotMsg(groupID int, msg string) {
config, err := configs.GetConfig()
if err != nil {
log.Printf("获取配置文件失败: %v", err)
return
}
client := napcat.NewClient(config.OneBotConfig.URL, config.OneBotConfig.Token)
err = client.SendGroupMsg(strconv.Itoa(groupID), msg)
if err != nil {
log.Printf("发送消息到OneBot失败: %v", err)
} else {
log.Printf("成功发送消息到OneBot群 %d", groupID)
}
}