diff --git a/api/account/account.go b/api/account/account.go new file mode 100644 index 0000000..471b5f6 --- /dev/null +++ b/api/account/account.go @@ -0,0 +1,62 @@ +package account + +import ( + "github.com/gin-gonic/gin" + "github.com/ssdomei232/goodBaby/api/user" + "github.com/ssdomei232/goodBaby/handler/db" + "github.com/ssdomei232/goodBaby/model" +) + +// 添加账号 +func HandleAddAccount(c *gin.Context) { + gormDB, err := db.GetGormDB() + if err != nil { + c.JSON(500, gin.H{"code": 500, "data": "服务器内部错误"}) + return + } + + userInfo, err := user.GetUserInfoByGinCtx(c) + if err != nil { + c.JSON(401, gin.H{"code": 401, "data": "获取用户信息失败"}) + return + } + + var newAccount model.Account + if err := c.BindJSON(&newAccount); err != nil { + c.JSON(400, gin.H{"code": 400, "data": "输入参数错误"}) + return + } + + newAccount.UID = userInfo.ID + result := gormDB.Create(&newAccount) + if result.Error != nil { + c.JSON(500, gin.H{"code": 500, "data": "添加账号失败"}) + return + } + + c.JSON(200, gin.H{"code": 200, "data": "添加账号成功"}) +} + +// 获取用户的所有账号 +func HandleGetAllAccounts(c *gin.Context) { + gormDB, err := db.GetGormDB() + if err != nil { + c.JSON(500, gin.H{"code": 500, "data": "服务器内部错误"}) + return + } + + userInfo, err := user.GetUserInfoByGinCtx(c) + if err != nil { + c.JSON(401, gin.H{"code": 401, "data": "获取用户信息失败"}) + return + } + + var accounts []model.Account + result := gormDB.Where("uid = ?", userInfo.ID).Find(&accounts) + if result.Error != nil { + c.JSON(500, gin.H{"code": 500, "data": "获取账号失败"}) + return + } + + c.JSON(200, gin.H{"code": 200, "data": accounts}) +} diff --git a/api/rule/rule.go b/api/rule/rule.go new file mode 100644 index 0000000..0ec682d --- /dev/null +++ b/api/rule/rule.go @@ -0,0 +1,62 @@ +package rule + +import ( + "github.com/gin-gonic/gin" + "github.com/ssdomei232/goodBaby/api/user" + "github.com/ssdomei232/goodBaby/handler/db" + "github.com/ssdomei232/goodBaby/model" +) + +// 获取用户的所有规则 +func HandleGetAllRules(c *gin.Context) { + gormDB, err := db.GetGormDB() + if err != nil { + c.JSON(500, gin.H{"code": 500, "data": "获取规则失败"}) + return + } + + userInfo, err := user.GetUserInfoByGinCtx(c) + if err != nil { + c.JSON(401, gin.H{"code": 401, "data": "获取用户信息失败"}) + return + } + + var rules []model.Rule + result := gormDB.Where("uid = ?", userInfo.ID).Find(&rules) + if result.Error != nil { + c.JSON(500, gin.H{"code": 500, "data": "获取规则失败"}) + return + } + + c.JSON(200, gin.H{"code": 200, "data": rules}) +} + +// 创建新规则 +func HandleCreateRule(c *gin.Context) { + gormDB, err := db.GetGormDB() + if err != nil { + c.JSON(500, gin.H{"code": 500, "data": "服务器内部错误"}) + return + } + + userInfo, err := user.GetUserInfoByGinCtx(c) + if err != nil { + c.JSON(401, gin.H{"code": 401, "data": "获取用户信息失败"}) + return + } + + var newRule model.Rule + if err := c.BindJSON(&newRule); err != nil { + c.JSON(400, gin.H{"code": 400, "data": "输入参数错误"}) + return + } + + newRule.UID = userInfo.ID + result := gormDB.Create(&newRule) + if result.Error != nil { + c.JSON(500, gin.H{"code": 500, "data": "创建规则失败"}) + return + } + + c.JSON(200, gin.H{"code": 200, "data": "规则创建成功"}) +} diff --git a/api/user/tool.go b/api/user/tool.go new file mode 100644 index 0000000..d9d10a7 --- /dev/null +++ b/api/user/tool.go @@ -0,0 +1,81 @@ +package user + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "github.com/ssdomei232/goodBaby/handler/db" + "github.com/ssdomei232/goodBaby/model" + "golang.org/x/crypto/bcrypt" +) + +// 加密密码 +func encryptPassword(password string) (string, error) { + hashedID, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashedID), nil +} + +// 验证密码 +func verifyPassword(hashedPassword, inputPassword string) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(inputPassword)) +} + +func createUser(u *model.User) error { + gormDB, err := db.GetGormDB() + if err != nil { + return err + } + + hashedPassword, err := encryptPassword(u.Password) + if err != nil { + return err + } + u.Password = hashedPassword + + result := gormDB.Create(u) + return result.Error +} + +// 校验密码 +func verifyUser(u *model.User) error { + gormDB, err := db.GetGormDB() + if err != nil { + return err + } + + var hashedPassword string + result := gormDB.Where("username = ?", u.Username).First(&model.User{}).Select("password").Scan(&hashedPassword) + if result.Error != nil { + return result.Error + } + + return verifyPassword(hashedPassword, u.Password) +} + +// 通过用户名获取用户信息 +func GetUserInfo(username string) (*model.User, error) { + gormDB, err := db.GetGormDB() + if err != nil { + return nil, err + } + + var user model.User + result := gormDB.Where("username = ?", username).First(&user) + if result.Error != nil { + return nil, result.Error + } + return &user, nil +} + +// 通过 gin.Context 获取用户信息 +func GetUserInfoByGinCtx(c *gin.Context) (userInfo *model.User, err error) { + session := sessions.Default(c) + username := session.Get("username") + userInfo, err = GetUserInfo(username.(string)) + if err != nil { + return nil, err + } + return userInfo, nil +} diff --git a/api/user/user.go b/api/user/user.go new file mode 100644 index 0000000..146f6af --- /dev/null +++ b/api/user/user.go @@ -0,0 +1,103 @@ +package user + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "github.com/ssdomei232/goodBaby/configs" + "github.com/ssdomei232/goodBaby/model" +) + +// 处理注册请求,配置文件中可设置禁用注册 +func HandleRegistry(c *gin.Context) { + var user model.User + var err error + + config, err := configs.GetConfig() + if err != nil { + c.JSON(500, gin.H{"code": 500, "data": "获取配置文件失败"}) + return + } + + if !config.EnableRegistry { + c.JSON(403, gin.H{"code": 403, "data": "注册功能已关闭"}) + return + } + + err = c.BindJSON(&user) + if err != nil { + c.JSON(400, gin.H{"code": 400, "data": "输入错误"}) + return + } + + if err = user.IsValid(); err != nil { + c.JSON(400, gin.H{"code": 400, "data": err.Error()}) + return + } + + if isExist := user.IsExist(); isExist { + c.JSON(400, gin.H{"code": 400, "data": "用户名已存在"}) + return + } + + if err = createUser(&user); err != nil { + c.JSON(500, gin.H{"code": 500, "data": "注册失败"}) + return + } + + session := sessions.Default(c) + session.Set("username", user.Username) + session.Save() + + c.JSON(200, gin.H{"code": 200, "data": "注册成功"}) +} + +// 处理登录请求 +func HandleLogin(c *gin.Context) { + var user model.User + var err error + + err = c.BindJSON(&user) + if err != nil { + c.JSON(400, gin.H{"code": 400, "data": "输入错误"}) + return + } + + if err = user.IsValid(); err != nil { + c.JSON(400, gin.H{"code": 400, "data": err.Error()}) + } + + if err = verifyUser(&user); err != nil { + c.JSON(400, gin.H{"code": 400, "data": "用户名或密码错误"}) + return + } + + session := sessions.Default(c) + session.Set("username", user.Username) + session.Save() + + c.JSON(200, gin.H{"code": 200, "data": "登录成功"}) +} + +// 处理获取用户信息请求 +func HandleGetUserInfo(c *gin.Context) { + userInfo, err := GetUserInfoByGinCtx(c) + if err != nil { + c.JSON(500, gin.H{"code": 500, "data": "获取用户信息失败"}) + return + } + c.JSON(200, gin.H{"code": 200, "data": userInfo}) +} + +// 认证中间件 +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + session := sessions.Default(c) + username := session.Get("username") + if username == nil { + c.JSON(401, gin.H{"code": 401, "data": "未登录"}) + c.Abort() + return + } + c.Next() + } +} diff --git a/configs/config.go b/configs/config.go index ae0e815..42550fc 100644 --- a/configs/config.go +++ b/configs/config.go @@ -5,52 +5,14 @@ import ( "os" ) +// type DingtalkBot struct { +// AccessToken string `json:"access_token"` +// Secret string `json:"secret"` +// } + type Config struct { - SignalSecret string `json:"signal_secret"` - Debug bool `json:"debug"` - DisconnectDuration int `json:"disconnect_duration"` // Hours - BiliMsg string `json:"bili_msg"` - Basic Basic `json:"basic"` - MailConfig MailConfig `json:"mail_config"` - GithubConfig GithubConfig `json:"github_config"` - DingtalkBot DingtalkBot `json:"dingtalk_bot"` - OneBotConfig OneBotConfig `json:"onebot_config"` -} - -type MailConfig struct { - Host string `json:"host"` - Port int `json:"port"` - User string `json:"user"` - Password string `json:"pass"` - MailList []string `json:"mail_list"` - MailTitle string `json:"mail_title"` - MailContent string `json:"mail_content"` -} - -type GithubConfig struct { - Owner string `json:"owner"` - Repos []string `json:"repos"` - Token string `json:"token"` -} - -type Basic struct { - Name string `json:"name"` - Age int `json:"age"` - NickName string `json:"nickname"` - QQNumber string `json:"qq_number"` - CauseStop string `json:"cause_stop"` -} - -type DingtalkBot struct { - AccessToken string `json:"access_token"` - Secret string `json:"secret"` -} - -type OneBotConfig struct { - URL string `json:"url"` // 例如 "http://localhost:5700" - Token string `json:"token"` - SendGroups []int `json:"send_groups"` - Msg string `json:"msg"` + EnableRegistry bool `json:"enable_registry"` + TimeoutDurationHours int `json:"timeout_duration_hours"` } // Get config from json file diff --git a/data.db b/data.db new file mode 100644 index 0000000..7848f5c Binary files /dev/null and b/data.db differ diff --git a/docker-compose.yml b/docker-compose.yml index f7fa533..f0e4244 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: goodbaby: - image: mei232/goodbaby:v0.2.0 + image: mei232/goodbaby:v2.0.1 ports: - "8088:8088" environment: @@ -11,7 +11,7 @@ services: - app-network volumes: - ./config.json:/app/config.json:ro - - ./tmp:/app/tmp + - ./data.db:/app/data.db:ro networks: app-network: diff --git a/docs/bilibili-config.md b/docs/bilibili-config.md new file mode 100644 index 0000000..f55dbb6 --- /dev/null +++ b/docs/bilibili-config.md @@ -0,0 +1,17 @@ +# Bilibili + +## Dynamic Config + +```json +{ + "msg": "something" +} +``` + +## Account Config + +```json +{ + "cookies_string": "xxxx" +} +``` diff --git a/docs/email-config.md b/docs/email-config.md new file mode 100644 index 0000000..b03405b --- /dev/null +++ b/docs/email-config.md @@ -0,0 +1,3 @@ +# Email Config + +## Email Account Config diff --git a/drivers/bilibili/api.go b/drivers/bilibili/api.go new file mode 100644 index 0000000..e722c6e --- /dev/null +++ b/drivers/bilibili/api.go @@ -0,0 +1,30 @@ +package bilibili + +import ( + "log" + "os" + + "github.com/CuteReimu/bilibili/v2" +) + +// Qrcode login +func HandleLoginWithQRCode(biliClient *bilibili.Client) { + qrCode, err := biliClient.GetQRCode() + if err != nil { + log.Printf("获取二维码失败: %v", err) + return + } + 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("登录成功") +} diff --git a/drivers/bilibili/bilibili.go b/drivers/bilibili/bilibili.go new file mode 100644 index 0000000..39384c2 --- /dev/null +++ b/drivers/bilibili/bilibili.go @@ -0,0 +1,44 @@ +package bilibili + +import ( + "context" + "log" + "time" + + "github.com/CuteReimu/bilibili/v2" + "github.com/cenkalti/backoff/v5" + "github.com/ssdomei232/goodBaby/configs" +) + +// 在Bilibili动态发送消息 +// +// 暂时没有处理 429 和 403 的区别对待 +func SendBiliDynamicMsg(biliClient *bilibili.Client, msg string) { + config, err := configs.GetConfig() + if err != nil { + log.Printf("获取配置失败: %v", err) + return + } + var timeout time.Duration = time.Duration(config.TimeoutDurationHours) * time.Hour + + var dynamicParams bilibili.CreateDynamicParam + dynamicParams = bilibili.CreateDynamicParam{ + DynamicId: 0, + Type: 4, + Rid: 0, + Content: msg, + } + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + operation := func() (string, error) { + _, err := biliClient.CreateDynamic(dynamicParams) + return "", err + } + + _, err = backoff.Retry(ctx, operation, backoff.WithBackOff(backoff.NewExponentialBackOff())) + if err != nil { + log.Printf("发送B站动态失败: %v", err) + } +} diff --git a/drivers/bilibili/model.go b/drivers/bilibili/model.go new file mode 100644 index 0000000..243dd0b --- /dev/null +++ b/drivers/bilibili/model.go @@ -0,0 +1,9 @@ +package bilibili + +type BiliAccount struct { + CookiesString string `json:"cookies_string"` +} + +type BiliDynamicRule struct { + Msg string `json:"msg"` +} diff --git a/drivers/bilibili/tool.go b/drivers/bilibili/tool.go new file mode 100644 index 0000000..008225d --- /dev/null +++ b/drivers/bilibili/tool.go @@ -0,0 +1,134 @@ +package bilibili + +import ( + "encoding/json" + "log" + "net/http" + "strings" + "time" + + "github.com/CuteReimu/bilibili/v2" + "github.com/ssdomei232/goodBaby/handler/db" + "github.com/ssdomei232/goodBaby/model" +) + +// 检查cookie是否过期 +func isCookiesExpired(cookiesString string) bool { + // 从cookie字符串中提取过期时间 + 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 // 如果解析失败,cookie已过期 + } + if time.Now().After(expireTime) { + return true + } + } + } + } + + return false +} + +// 检查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) { + + // 生成新的二维码 + 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) + // } + +} + +// 获取 Bilibili Client +func GetBiliClient(rule *model.Rule) (*bilibili.Client, error) { + client := bilibili.New() + gormDB, err := db.GetGormDB() + if err != nil { + return nil, err + } + + // 1. 通过 account_id 获取到对应的 account 配置 + var biliAccount model.Account + result := gormDB.Where("id = ?", rule.AccountID).First(&biliAccount) + if result.Error != nil { + return nil, result.Error + } + + // 2. 通过 account 配置中的 config 字段获取到 CookiesString + var biliAccountConfig BiliAccount + err = json.Unmarshal([]byte(biliAccount.Config), &biliAccountConfig) + if err != nil { + return nil, err + } + + client.SetCookiesString(biliAccountConfig.CookiesString) + + return client, nil +} diff --git a/drivers/dingtalk/dingtalk.go b/drivers/dingtalk/dingtalk.go new file mode 100644 index 0000000..a5f901c --- /dev/null +++ b/drivers/dingtalk/dingtalk.go @@ -0,0 +1,5 @@ +package dingtalk + +import "github.com/ssdomei232/goodBaby/model" + +func SendDingTalkMsg(user *model.User, title string, msg string) {} diff --git a/drivers/dingtalk/model.go b/drivers/dingtalk/model.go new file mode 100644 index 0000000..eedc7ab --- /dev/null +++ b/drivers/dingtalk/model.go @@ -0,0 +1,6 @@ +package dingtalk + +type DingTalkConfig struct { + AccessToken string `json:"access_token"` + Secret string `json:"secret"` +} diff --git a/drivers/dingtalk/tool.go b/drivers/dingtalk/tool.go new file mode 100644 index 0000000..a9ccf6f --- /dev/null +++ b/drivers/dingtalk/tool.go @@ -0,0 +1,20 @@ +package dingtalk + +import ( + "encoding/json" + + "github.com/ssdomei232/goodBaby/model" +) + +func getDingTalkConfigFromUser(user *model.User) *DingTalkConfig { + if user.DingTalkConfig == nil { + return nil + } + + var config DingTalkConfig + err := json.Unmarshal([]byte(*user.DingTalkConfig), &config) + if err != nil { + return nil + } + return &config +} diff --git a/drivers/email/email.go b/drivers/email/email.go new file mode 100644 index 0000000..674aaa2 --- /dev/null +++ b/drivers/email/email.go @@ -0,0 +1,20 @@ +package email + +import ( + "log" + + "github.com/ssdomei232/goodBaby/model" +) + +// 向邮件列表发送消息 +func SendMail(rule *model.Rule) { + emailRule, err := GetEmailRuleFromRule(rule) + if err != nil { + log.Printf("获取邮件规则配置失败: %v", err) + return + } + + for _, destinations := range emailRule.Destinations { + go sendMailMsgWithRetry(rule, destinations) // 使用 goroutine 发送邮件,避免指数退避阻塞其他地址 + } +} diff --git a/drivers/email/model.go b/drivers/email/model.go new file mode 100644 index 0000000..ca4bafa --- /dev/null +++ b/drivers/email/model.go @@ -0,0 +1,15 @@ +package email + +type EmailAccountConfig struct { + SMTPServer string `json:"smtp_server"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + TestDestination string `json:"test_destination"` // 用于测试邮件发送的地址 +} + +type EmailRule struct { + Msg string `json:"msg"` + Title string `json:"title"` + Destinations []string `json:"destinations"` // 邮件地址列表 +} diff --git a/drivers/email/tool.go b/drivers/email/tool.go new file mode 100644 index 0000000..db929f9 --- /dev/null +++ b/drivers/email/tool.go @@ -0,0 +1,116 @@ +package email + +import ( + "context" + "encoding/json" + "fmt" + "log" + "time" + + "github.com/cenkalti/backoff/v5" + "github.com/ssdomei232/goodBaby/configs" + "github.com/ssdomei232/goodBaby/handler/db" + "github.com/ssdomei232/goodBaby/model" + "github.com/wneessen/go-mail" +) + +// 从 Rule 中获取 EmailAccount 配置 +func GetEmailAccountFromRule(rule *model.Rule) (*EmailAccountConfig, error) { + gormDB, err := db.GetGormDB() + if err != nil { + return nil, err + } + + // 1. 通过 account_id 获取到对应的 account 配置 + var emailAccount model.Account + result := gormDB.Where("id = ?", rule.AccountID).First(&emailAccount) + if result.Error != nil { + return nil, result.Error + } + + // 2. 通过 account 配置中的 config 字段获取到 EmailAccount + var emailAccountConfig EmailAccountConfig + err = json.Unmarshal([]byte(emailAccount.Config), &emailAccountConfig) + if err != nil { + return nil, err + } + + return &emailAccountConfig, nil +} + +// 从 Rule 中获取 EmailRule 配置 +func GetEmailRuleFromRule(rule *model.Rule) (*EmailRule, error) { + var emailRule EmailRule + err := json.Unmarshal([]byte(rule.ConfigJson), &emailRule) + if err != nil { + return nil, err + } + return &emailRule, nil +} +func sendMailMsgWithRetry(rule *model.Rule, address string) { + config, err := configs.GetConfig() + if err != nil { + log.Printf("获取配置失败: %v", err) + return + } + var timeout time.Duration = time.Duration(config.TimeoutDurationHours) * time.Hour + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + operation := func() (string, error) { + err := sendMailMsg(address, rule) + return "", err + } + + _, err = backoff.Retry(ctx, operation, backoff.WithBackOff(backoff.NewExponentialBackOff())) + if err != nil { + log.Printf("发送邮件失败: %v", err) + } +} + +func sendMailMsg(address string, rule *model.Rule) error { + emailAccountConfig, err := GetEmailAccountFromRule(rule) + if err != nil { + log.Printf("获取邮件账户配置失败: %v", err) + return fmt.Errorf("获取邮件账户配置失败: %v", err) + } + + emailRule, err := GetEmailRuleFromRule(rule) + if err != nil { + log.Printf("获取邮件规则配置失败: %v", err) + return fmt.Errorf("获取邮件规则配置失败: %v", err) + } + + client, err := mail.NewClient( + emailAccountConfig.SMTPServer, + mail.WithPort(emailAccountConfig.Port), + mail.WithSSL(), + mail.WithUsername(emailAccountConfig.Username), + mail.WithPassword(emailAccountConfig.Password), + mail.WithSMTPAuth(mail.SMTPAuthPlain), + ) + if err != nil { + return fmt.Errorf("创建邮件客户端失败: %v", err) + } + + // 创建邮件 + message := mail.NewMsg() + if err := message.From(emailAccountConfig.Username); err != nil { + return fmt.Errorf("设置发件人失败: %v", err) + } + + if err := message.To(address); err != nil { + return fmt.Errorf("设置收件人失败: %v", err) + } + + message.Subject(emailRule.Title) + message.SetBodyString(mail.TypeTextPlain, emailRule.Msg) + + // 发送邮件 + if err := client.DialAndSend(message); err != nil { + return fmt.Errorf("发送邮件失败: %v", err) + } + + return nil +} diff --git a/drivers/github/github.go b/drivers/github/github.go new file mode 100644 index 0000000..aea03b0 --- /dev/null +++ b/drivers/github/github.go @@ -0,0 +1,42 @@ +package github + +import ( + "context" + "log" + "time" + + "github.com/cenkalti/backoff/v5" + "github.com/ssdomei232/goodBaby/configs" + "github.com/ssdomei232/goodBaby/model" +) + +// 将repos设置为public +func MakeRepositoryPublic(rule *model.Rule) { + reposConfig, account := GetGithubReposAndAccountFromRule(rule) + if reposConfig == nil || account == nil { + log.Printf("无法获取 GitHub 配置或账户信息") + return + } + + for _, repo := range reposConfig.Repos { + config, err := configs.GetConfig() + if err != nil { + log.Printf("获取配置失败: %v", err) + return + } + var timeout time.Duration = time.Duration(config.TimeoutDurationHours) * time.Hour + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + operation := func() (string, error) { + err := SetRepositoryPublic(account.Token, account.Owner, repo) + return "", err + } + + _, err = backoff.Retry(ctx, operation, backoff.WithBackOff(backoff.NewExponentialBackOff())) + if err != nil { + log.Printf("将仓库 %s 设置为 public 失败: %v", repo, err) + } + } +} diff --git a/drivers/github/model.go b/drivers/github/model.go new file mode 100644 index 0000000..1a205d3 --- /dev/null +++ b/drivers/github/model.go @@ -0,0 +1,10 @@ +package github + +type GithubReposConfig struct { + Repos []string `yaml:"repos"` +} + +type GithubAccount struct { + Token string + Owner string +} diff --git a/drivers/github/tool.go b/drivers/github/tool.go new file mode 100644 index 0000000..dcf95ae --- /dev/null +++ b/drivers/github/tool.go @@ -0,0 +1,57 @@ +package github + +import ( + "context" + "encoding/json" + "log" + + "github.com/google/go-github/v84/github" + "github.com/ssdomei232/goodBaby/handler/db" + "github.com/ssdomei232/goodBaby/model" +) + +// 从 rule 中获取 GithubReposConfig 和 GithubAccount +func GetGithubReposAndAccountFromRule(rule *model.Rule) (*GithubReposConfig, *GithubAccount) { + var githubReposConfig GithubReposConfig + var githubAccount GithubAccount + + // 获取 GithubReposConfig + err := json.Unmarshal([]byte(rule.ConfigJson), &githubReposConfig) + if err != nil { + log.Printf("获取 GitHub 配置失败: %v", err) + return nil, nil + } + + // 获取 GithubAccount + gormDB, err := db.GetGormDB() + if err != nil { + log.Printf("获取 Gorm DB 失败: %v", err) + return nil, nil + } + + err = gormDB.Where("id = ?", rule.AccountID).First(&githubAccount).Error + if err != nil { + log.Printf("获取 GitHub 账户失败: %v", err) + return nil, nil + } + + return &githubReposConfig, &githubAccount +} + +// 将仓库设置为 public +func SetRepositoryPublic(token, owner, repo string) error { + ctx := context.Background() + + // 1. 初始化客户端 + client := github.NewClient(nil).WithAuthToken(token) + + // 2. 准备修改的参数 + opts := &github.Repository{ + Visibility: github.Ptr("public"), + } + + // 3. 执行更新操作 + _, _, err := client.Repositories.Edit(ctx, owner, repo, opts) + + return err +} diff --git a/drivers/onebot/model.go b/drivers/onebot/model.go new file mode 100644 index 0000000..eda95c0 --- /dev/null +++ b/drivers/onebot/model.go @@ -0,0 +1,11 @@ +package onebot + +type OneBotConfig struct { + SendGroups []int `json:"send_groups"` + Msg string `json:"msg"` +} + +type OneBotAccount struct { + URL string `json:"url"` // 例如 "http://localhost:5700" + Token string `json:"token"` // 例如 "your_token_here" +} diff --git a/drivers/onebot/onebot.go b/drivers/onebot/onebot.go new file mode 100644 index 0000000..40180a9 --- /dev/null +++ b/drivers/onebot/onebot.go @@ -0,0 +1,42 @@ +package onebot + +import ( + "context" + "log" + "time" + + "github.com/cenkalti/backoff/v5" + "github.com/ssdomei232/goodBaby/configs" + "github.com/ssdomei232/goodBaby/model" +) + +func SendOneBotMsg(rule *model.Rule) { + oneBotConfig := getOneBotConfigFromRule(rule) + oneBotAccount, err := getOneBotAccountFromRule(rule) + if err != nil { + log.Printf("获取OneBot账户失败: %v", err) + return + } + + for _, groupID := range oneBotConfig.SendGroups { + config, err := configs.GetConfig() + if err != nil { + log.Printf("获取配置失败: %v", err) + return + } + var timeout time.Duration = time.Duration(config.TimeoutDurationHours) * time.Hour + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + operation := func() (string, error) { + err := sendOneBotMsg(oneBotAccount, groupID, oneBotConfig.Msg) + return "", err + } + + _, err = backoff.Retry(ctx, operation, backoff.WithBackOff(backoff.NewExponentialBackOff())) + if err != nil { + log.Printf("发送OneBot消息失败: %v", err) + } + } +} diff --git a/drivers/onebot/tool.go b/drivers/onebot/tool.go new file mode 100644 index 0000000..a025ae7 --- /dev/null +++ b/drivers/onebot/tool.go @@ -0,0 +1,47 @@ +package onebot + +import ( + "encoding/json" + "strconv" + + "github.com/ssdomei232/goodBaby/handler/db" + "github.com/ssdomei232/goodBaby/model" + napcat "github.com/ssdomei232/napcat-http-go-sdk" +) + +// 从 Rule 中获取 OneBotConfig +func getOneBotConfigFromRule(rule *model.Rule) *OneBotConfig { + var oneBotConfig OneBotConfig + err := json.Unmarshal([]byte(rule.ConfigJson), &oneBotConfig) + if err != nil { + return nil + } + return &oneBotConfig +} + +// 从 Rule 中获取 OneBotAccount +func getOneBotAccountFromRule(rule *model.Rule) (*OneBotAccount, error) { + gormDB, err := db.GetGormDB() + if err != nil { + return nil, err + } + + var oneBotAccountEntry model.Account + gormDB.Where("id = ?", rule.AccountID).First(&oneBotAccountEntry) + + var oneBotAccount OneBotAccount + err = json.Unmarshal([]byte(oneBotAccountEntry.Config), &oneBotAccount) + if err != nil { + return nil, err + } + + return &oneBotAccount, nil +} + +// 发送一条消息到OneBot +func sendOneBotMsg(account *OneBotAccount, groupID int, msg string) error { + client := napcat.NewClient(account.Token, account.URL) + + err := client.SendGroupMsg(strconv.Itoa(groupID), msg) + return err +} diff --git a/go.mod b/go.mod index f70edb6..9686a9f 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,19 @@ module github.com/ssdomei232/goodBaby -go 1.24.10 +go 1.25.0 require ( github.com/CatchZeng/dingtalk v1.5.0 github.com/CuteReimu/bilibili/v2 v2.3.5 + github.com/cenkalti/backoff/v5 v5.0.3 + github.com/gin-contrib/sessions v1.0.4 github.com/gin-gonic/gin v1.11.0 - github.com/robfig/cron/v3 v3.0.1 + github.com/google/go-github/v84 v84.0.0 github.com/ssdomei232/napcat-http-go-sdk v1.0.0 github.com/wneessen/go-mail v0.7.2 + golang.org/x/crypto v0.48.0 + gorm.io/driver/sqlite v1.5.7 + gorm.io/gorm v1.25.12 ) require ( @@ -22,14 +27,21 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-resty/resty/v2 v2.17.2 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/google/go-querystring v1.2.0 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.4.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -41,12 +53,11 @@ require ( github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/protobuf v1.36.9 // indirect ) diff --git a/go.sum b/go.sum index 7e654bf..f2031b6 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQ github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -19,6 +21,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U= +github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -33,15 +37,32 @@ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHO github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github/v84 v84.0.0 h1:I/0Xn5IuChMe8TdmI2bbim5nyhaRFJ7DEdzmD2w+yVA= +github.com/google/go-github/v84 v84.0.0/go.mod h1:WwYL1z1ajRdlaPszjVu/47x1L0PXukJBn73xsiYrRRQ= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -56,8 +77,11 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -70,8 +94,6 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= @@ -99,26 +121,30 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/handler/db/db.go b/handler/db/db.go new file mode 100644 index 0000000..d816254 --- /dev/null +++ b/handler/db/db.go @@ -0,0 +1,16 @@ +package db + +import ( + "log" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func GetGormDB() (*gorm.DB, error) { + db, err := gorm.Open(sqlite.Open("file:data.db"), &gorm.Config{}) + if err != nil { + log.Fatal("打开数据库失败:", err) + } + return db, nil +} diff --git a/internal/base/base.go b/internal/base/base.go deleted file mode 100644 index d2cc6b8..0000000 --- a/internal/base/base.go +++ /dev/null @@ -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) - } - -} diff --git a/internal/frontend/frontend.go b/internal/frontend/frontend.go deleted file mode 100644 index 4169666..0000000 --- a/internal/frontend/frontend.go +++ /dev/null @@ -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) -} diff --git a/internal/reminder/reminder.go b/internal/reminder/reminder.go deleted file mode 100644 index a9a5ec6..0000000 --- a/internal/reminder/reminder.go +++ /dev/null @@ -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) - } -} diff --git a/internal/reminder/timer.go b/internal/reminder/timer.go deleted file mode 100644 index f613cc0..0000000 --- a/internal/reminder/timer.go +++ /dev/null @@ -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", - }) -} diff --git a/internal/sender/bilibili.go b/internal/sender/bilibili.go deleted file mode 100644 index 8c7529d..0000000 --- a/internal/sender/bilibili.go +++ /dev/null @@ -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登录") - } -} diff --git a/internal/sender/email.go b/internal/sender/email.go deleted file mode 100644 index 9c64353..0000000 --- a/internal/sender/email.go +++ /dev/null @@ -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 -} diff --git a/internal/sender/gitub.go b/internal/sender/gitub.go deleted file mode 100644 index 8528107..0000000 --- a/internal/sender/gitub.go +++ /dev/null @@ -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) - } - } - } -} diff --git a/internal/sender/onebot.go b/internal/sender/onebot.go deleted file mode 100644 index afe5df6..0000000 --- a/internal/sender/onebot.go +++ /dev/null @@ -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) - } -} diff --git a/main.go b/main.go index 4d54bea..ae91925 100644 --- a/main.go +++ b/main.go @@ -1,121 +1,47 @@ package main import ( - "embed" - "io/fs" - "log" - "net/http" - "time" + "crypto/rand" + "fmt" - "github.com/CuteReimu/bilibili/v2" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" - "github.com/robfig/cron/v3" - "github.com/ssdomei232/goodBaby/configs" - "github.com/ssdomei232/goodBaby/internal/frontend" - "github.com/ssdomei232/goodBaby/internal/reminder" - "github.com/ssdomei232/goodBaby/internal/sender" + "github.com/ssdomei232/goodBaby/api/account" + "github.com/ssdomei232/goodBaby/api/rule" + "github.com/ssdomei232/goodBaby/api/user" ) -//go:embed static/* -var staticFiles embed.FS - -//go:embed templates/* -var templateFiles embed.FS - -var timer *time.Timer -var duration time.Duration -var biliClient *bilibili.Client -var cookieCheckTimer *time.Ticker - -func init() { - // 1. 加载配置文件 - config, err := configs.GetConfig() - if err != nil { - log.Printf("获取配置文件失败: %v", err) - } - - if config.Debug { - duration = time.Duration(config.DisconnectDuration) * time.Second - } else { - duration = time.Duration(config.DisconnectDuration) * time.Hour - } - - // 2. 检查并创建tmp目录 - if err := sender.EnsureTmpDirectory(); err != nil { - log.Printf("创建tmp目录失败: %v", err) - } - - // 3. 初始化哔哩哔哩和定时器 - biliClient = bilibili.New() - sender.InitBilibili(biliClient) - sender.StartCookieChecker(cookieCheckTimer, biliClient) // Check Bilibili cookie - - reminder.InitTimerManager(duration) - timer = time.NewTimer(duration) - go func() { - <-timer.C - trigger(config) - }() -} - -// 触发器 -func trigger(config configs.Config) { - go sender.SendOneBot() - go sender.SendMail() - go sender.Github() - if !config.Debug { - go sender.SendBili(biliClient) - } -} - func main() { - config, err := configs.GetConfig() - if err != nil { - log.Printf("获取配置文件失败: %v", err) - } - // 定时任务 - reminder.Reminder() - c := cron.New() - if config.Debug { - c.AddFunc("@every 1s", reminder.Reminder) - } else { - c.AddFunc("@every 1h", reminder.Reminder) - } - c.Start() - - // 前端/后端 r := gin.Default() - templFS, _ := fs.Sub(templateFiles, "templates") - r.SetHTMLTemplate(frontend.LoadTemplates(templFS)) - staticFS, _ := fs.Sub(staticFiles, "static") - r.StaticFS("/static", http.FS(staticFS)) + store := cookie.NewStore(generateRandomKey(32)) + r.Use(sessions.Sessions("goodbaby-session", store)) + + v1 := r.Group("/api/v1") + { + v1.POST("/user/registry", user.HandleRegistry) + v1.POST("/user/login", user.HandleLogin) + } + + // 需要认证的路由组 + authorized := v1.Group("/") + authorized.Use(user.AuthMiddleware()) + { + authorized.GET("/user/info", user.HandleGetUserInfo) + authorized.GET("/rules", rule.HandleGetAllRules) + authorized.POST("/rules", rule.HandleCreateRule) + authorized.GET("/accounts", account.HandleGetAllAccounts) + authorized.POST("/accounts", account.HandleAddAccount) + } - r.GET("/", frontend.IndexPage) - r.GET("/signal", handleSignal) - r.GET("/timer/status", reminder.HandleTimerStatus) r.Run(":8088") } -// Handle Signal -func handleSignal(c *gin.Context) { - config, err := configs.GetConfig() +func generateRandomKey(length int) []byte { + key := make([]byte, length) + _, err := rand.Read(key) if err != nil { - log.Printf("获取配置文件失败: %v", err) + panic(fmt.Sprintf("无法生成随机密钥: %v", err)) } - - secret := c.Query("secret") - if secret != config.SignalSecret { - c.JSON(403, gin.H{ - "code": 403, - "message": "secret error", - }) - return - } - timer.Reset(duration) - reminder.GlobalTimerManager.Reset() - c.JSON(200, gin.H{ - "code": 200, - "message": "ok", - }) - log.Println("触发信号") + return key } diff --git a/model/account.go b/model/account.go new file mode 100644 index 0000000..3ae8dfa --- /dev/null +++ b/model/account.go @@ -0,0 +1,9 @@ +package model + +type Account struct { + ID uint `gorm:"primaryKey" json:"id"` + UID uint `json:"uid"` + Name string `json:"name"` // 账号名称 + Type string `json:"type"` // 账号类型,如 "bilibili" + Config string `json:"config"` // 存储账号相关配置,如cookie等 +} diff --git a/model/rule.go b/model/rule.go new file mode 100644 index 0000000..f4fc049 --- /dev/null +++ b/model/rule.go @@ -0,0 +1,11 @@ +package model + +type Rule struct { + ID uint `json:"id" gorm:"primaryKey"` + UID uint `json:"uid"` + TimerID uint `json:"timer_id"` + Name string `json:"name"` + AccountID uint `json:"account_id"` + Type string `json:"type"` + ConfigJson string `json:"config_json"` +} diff --git a/model/timer.go b/model/timer.go new file mode 100644 index 0000000..cd2d338 --- /dev/null +++ b/model/timer.go @@ -0,0 +1,9 @@ +package model + +type Timer struct { + ID uint `gorm:"primaryKey" json:"id"` + UID uint `json:"uid"` + Name string `json:"name"` + SignDerationSeconds int64 `json:"sign_deration_seconds"` + LastSign int64 `json:"last_sign"` +} diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000..e54ed32 --- /dev/null +++ b/model/user.go @@ -0,0 +1,43 @@ +package model + +import ( + "fmt" + + "github.com/ssdomei232/goodBaby/handler/db" +) + +type User struct { + ID uint `json:"id" gorm:"primaryKey"` + CreateAt int64 `json:"create_at"` + Username string `json:"username"` + Password string `json:"-"` + DingTalkConfig *string `json:"dingtalk_config"` +} + +func (u *User) IsValid() error { + if u.Username == "" || u.Password == "" { + return fmt.Errorf("用户名或密码不能为空") + } + if len(u.Username) > 32 || len(u.Username) < 2 { + return fmt.Errorf("用户名过长或过短") + } + if len(u.Password) > 64 || len(u.Password) < 6 { + return fmt.Errorf("密码过长或过短") + } + return nil +} + +func (u *User) IsExist() bool { + db, err := db.GetGormDB() + if err != nil { + return false + } + + var count int64 + result := db.Where("username = ?", u.Username).Count(&count) + if result.Error != nil { + return false + } + + return count > 0 +}