feat: v2(Incomplete,very early version)
This commit is contained in:
@@ -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})
|
||||
}
|
||||
@@ -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": "规则创建成功"})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
+7
-45
@@ -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
|
||||
|
||||
+2
-2
@@ -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:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Bilibili
|
||||
|
||||
## Dynamic Config
|
||||
|
||||
```json
|
||||
{
|
||||
"msg": "something"
|
||||
}
|
||||
```
|
||||
|
||||
## Account Config
|
||||
|
||||
```json
|
||||
{
|
||||
"cookies_string": "xxxx"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
# Email Config
|
||||
|
||||
## Email Account Config
|
||||
@@ -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("登录成功")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package bilibili
|
||||
|
||||
type BiliAccount struct {
|
||||
CookiesString string `json:"cookies_string"`
|
||||
}
|
||||
|
||||
type BiliDynamicRule struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dingtalk
|
||||
|
||||
import "github.com/ssdomei232/goodBaby/model"
|
||||
|
||||
func SendDingTalkMsg(user *model.User, title string, msg string) {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package dingtalk
|
||||
|
||||
type DingTalkConfig struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 发送邮件,避免指数退避阻塞其他地址
|
||||
}
|
||||
}
|
||||
@@ -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"` // 邮件地址列表
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package github
|
||||
|
||||
type GithubReposConfig struct {
|
||||
Repos []string `yaml:"repos"`
|
||||
}
|
||||
|
||||
type GithubAccount struct {
|
||||
Token string
|
||||
Owner string
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
})
|
||||
}
|
||||
@@ -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登录")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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等
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user