feat: v2(Incomplete,very early version)

This commit is contained in:
mei
2026-04-05 10:36:22 +08:00
parent 2ab9e060eb
commit 6190a8853a
41 changed files with 1131 additions and 766 deletions
-29
View File
@@ -1,29 +0,0 @@
package base
import (
"fmt"
"log"
"time"
"github.com/ssdomei232/goodBaby/configs"
)
func GetBasicInfo() string {
config, err := configs.GetConfig()
if err != nil {
log.Printf("获取配置文件失败: %v", err)
return "获取配置文件失败"
}
now := time.Now()
lastSignalTime := now.Add(-time.Duration(config.DisconnectDuration) * time.Hour).Format("2006-01-02 15:04:05")
if config.Debug {
return fmt.Sprintf("此消息为测试消息\n\n%s(QQ号:%s)已于%s至%s(UTC+8)之间去世,享年%d岁,死因可能为%s(此死因为推测最可能的死因,请勿完全相信,请等待后续消息)",
config.Basic.Name, config.Basic.QQNumber, lastSignalTime, time.Now().Format("2006-01-02 15:04:05"), config.Basic.Age, config.Basic.CauseStop)
} else {
return fmt.Sprintf("%s(QQ号:%s,网名:%s)已于%s至%s(UTC+8)之间去世,享年%d岁,死因可能为%s(此死因为推测最可能的死因,请勿完全相信,请等待后续消息)",
config.Basic.Name, config.Basic.NickName, config.Basic.QQNumber, lastSignalTime, time.Now().Format("2006-01-02 15:04:05"), config.Basic.Age, config.Basic.CauseStop)
}
}
-19
View File
@@ -1,19 +0,0 @@
package frontend
import (
"html/template"
"io/fs"
"net/http"
"github.com/gin-gonic/gin"
)
// loadTemplates loads templates from the embedded file system
func LoadTemplates(filesystem fs.FS) *template.Template {
templ := template.Must(template.New("").ParseFS(filesystem, "*.html"))
return templ
}
func IndexPage(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
-23
View File
@@ -1,23 +0,0 @@
package reminder
import (
"fmt"
"log"
"time"
"github.com/ssdomei232/goodBaby/configs"
"github.com/ssdomei232/goodBaby/internal/sender"
)
func Reminder() {
config, err := configs.GetConfig()
if err != nil {
log.Printf("获取配置文件失败: %v", err)
}
remainingTime := GlobalTimerManager.GetRemainingTime()
if remainingTime <= 48*time.Hour {
sendMsg := fmt.Sprintf("距离摇篮系统触发还有%d小时,请尽快发送Signal", int(remainingTime.Hours()))
sender.SendDingTalkMsg("摇篮系统触发提醒", sendMsg, config.DingtalkBot.AccessToken, config.DingtalkBot.Secret)
}
}
-52
View File
@@ -1,52 +0,0 @@
package reminder
import (
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
)
type TimerManager struct {
duration time.Duration
resetTime time.Time
mutex sync.RWMutex
}
var GlobalTimerManager *TimerManager
func InitTimerManager(duration time.Duration) {
GlobalTimerManager = &TimerManager{
duration: duration,
resetTime: time.Now(),
}
}
func (tm *TimerManager) Reset() {
tm.mutex.Lock()
defer tm.mutex.Unlock()
tm.resetTime = time.Now()
}
func (tm *TimerManager) GetRemainingTime() time.Duration {
tm.mutex.RLock()
defer tm.mutex.RUnlock()
elapsed := time.Since(tm.resetTime)
remaining := tm.duration - elapsed
if remaining < 0 {
return 0
}
return remaining
}
func HandleTimerStatus(c *gin.Context) {
remainingTime := GlobalTimerManager.GetRemainingTime()
c.JSON(http.StatusOK, gin.H{
"code": 200,
"remaining_time": remainingTime.Seconds(), // 返回剩余秒数
"message": "ok",
})
}
-283
View File
@@ -1,283 +0,0 @@
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登录")
}
}
-99
View File
@@ -1,99 +0,0 @@
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
@@ -1,36 +0,0 @@
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
@@ -1,43 +0,0 @@
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.Token, config.OneBotConfig.URL)
err = client.SendGroupMsg(strconv.Itoa(groupID), msg)
if err != nil {
log.Printf("发送消息到OneBot失败: %v", err)
} else {
log.Printf("成功发送消息到OneBot群 %d", groupID)
}
}