Your Name 1 year ago
parent
commit
3cb25e5ae5

+ 150 - 0
cmd/tgadtool/tgadtool.go

@@ -0,0 +1,150 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"runtime"
+	"runtime/debug"
+	"strings"
+	"syscall"
+	"time"
+
+	rotatelogs "github.com/lestrrat/go-file-rotatelogs"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/rdb"
+	"nn.daxia.dev/model"
+	"nn.daxia.dev/tgadtool"
+)
+
+func main() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
+		}
+	}()
+
+	viper.Set("gormlog", true)
+	viper.AddConfigPath("conf")
+	viper.AddConfigPath("configs/tgadtool")
+
+	viper.SetConfigName("config")
+	viper.SetConfigType("yaml")
+
+	viper.AutomaticEnv()
+	viper.SetEnvPrefix("web")
+	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+
+	// 从环境变量总读取
+	viper.ReadInConfig()
+	initCoreDump()
+	initLog()
+
+	logrus.Println("==================")
+	logrus.Println(viper.AllSettings())
+	logrus.Println("==================")
+
+	flag.Parse()
+	log.SetFlags(0)
+
+	isDebug := viper.GetBool("is_debug")
+	logrus.Println("debug: ", isDebug)
+
+	if isDebug {
+		db.InitTest()
+	} else {
+		db.Init()
+	}
+
+	err := rdb.InitClient()
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	createDBTable()
+
+	tgadtool.Start()
+}
+
+func initLog() {
+	if viper.GetBool("log.logrus_json") {
+		logrus.SetFormatter(&logrus.JSONFormatter{})
+	}
+
+	// log.logrus_level
+	switch viper.GetString("log.logrus_level") {
+	case "trace":
+		logrus.SetLevel(logrus.TraceLevel)
+	case "debug":
+		logrus.SetLevel(logrus.DebugLevel)
+	case "info":
+		logrus.SetLevel(logrus.InfoLevel)
+	case "warn":
+		logrus.SetLevel(logrus.WarnLevel)
+	case "error":
+		logrus.SetLevel(logrus.ErrorLevel)
+	}
+
+	// log.logrus_file
+	logrusFile := viper.GetString("log.logrus_file")
+	os.MkdirAll(filepath.Dir(logrusFile), os.ModePerm)
+
+	logWriter, _ := rotatelogs.New(
+		// 分割后的文件名称
+		logrusFile+".%Y%m%d.log",
+
+		// 生成软链,指向最新日志文件
+		rotatelogs.WithLinkName(logrusFile),
+
+		// 设置最大保存时间(7天)
+		rotatelogs.WithMaxAge(7*24*time.Hour),
+
+		// 设置日志切割时间间隔(1天)
+		rotatelogs.WithRotationTime(24*time.Hour),
+	)
+
+	if viper.GetBool("log.logrus_console") {
+		logrus.SetOutput(io.MultiWriter(logWriter, os.Stdout))
+	} else {
+		logrus.SetOutput(logWriter)
+	}
+
+	logrus.SetFormatter(&logrus.JSONFormatter{
+		DisableHTMLEscape: true,
+	})
+
+	// default
+	logrus.SetReportCaller(true)
+}
+
+const panicFile = "./logs/panic.log"
+
+func initCoreDump() error {
+	if runtime.GOOS == "windows" {
+		return nil
+	}
+
+	log.Println("init panic file in unix mode")
+	file, err := os.OpenFile(panicFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	if err = syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
+		return err
+	}
+
+	return nil
+}
+func createDBTable() error {
+	(model.Admsg{}).Init()
+	(model.Timer{}).Init()
+
+	return nil
+}

+ 151 - 0
cmd/tgkk/tgkk.go

@@ -0,0 +1,151 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"runtime"
+	"runtime/debug"
+	"strings"
+	"syscall"
+	"time"
+
+	rotatelogs "github.com/lestrrat/go-file-rotatelogs"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/rdb"
+	"nn.daxia.dev/model"
+	"nn.daxia.dev/tgkk"
+)
+
+func main() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
+		}
+	}()
+
+	viper.Set("gormlog", true)
+	viper.AddConfigPath("conf")
+	viper.AddConfigPath("configs/tgkk")
+
+	viper.SetConfigName("config")
+	viper.SetConfigType("yaml")
+
+	viper.AutomaticEnv()
+	viper.SetEnvPrefix("web")
+	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+
+	// 从环境变量总读取
+	viper.ReadInConfig()
+	initCoreDump()
+	initLog()
+
+	logrus.Println("==================")
+	logrus.Println(viper.AllSettings())
+	logrus.Println("==================")
+
+	flag.Parse()
+	log.SetFlags(0)
+
+	isDebug := viper.GetBool("is_debug")
+	logrus.Println("debug: ", isDebug)
+
+	if isDebug {
+		db.InitTest()
+	} else {
+		db.Init()
+	}
+
+	err := rdb.InitClient()
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	createDBTable()
+
+	tgkk.Start()
+}
+
+func initLog() {
+	if viper.GetBool("log.logrus_json") {
+		logrus.SetFormatter(&logrus.JSONFormatter{})
+	}
+
+	// log.logrus_level
+	switch viper.GetString("log.logrus_level") {
+	case "trace":
+		logrus.SetLevel(logrus.TraceLevel)
+	case "debug":
+		logrus.SetLevel(logrus.DebugLevel)
+	case "info":
+		logrus.SetLevel(logrus.InfoLevel)
+	case "warn":
+		logrus.SetLevel(logrus.WarnLevel)
+	case "error":
+		logrus.SetLevel(logrus.ErrorLevel)
+	}
+
+	// log.logrus_file
+	logrusFile := viper.GetString("log.logrus_file")
+	os.MkdirAll(filepath.Dir(logrusFile), os.ModePerm)
+
+	logWriter, _ := rotatelogs.New(
+		// 分割后的文件名称
+		logrusFile+".%Y%m%d.log",
+
+		// 生成软链,指向最新日志文件
+		rotatelogs.WithLinkName(logrusFile),
+
+		// 设置最大保存时间(7天)
+		rotatelogs.WithMaxAge(7*24*time.Hour),
+
+		// 设置日志切割时间间隔(1天)
+		rotatelogs.WithRotationTime(24*time.Hour),
+	)
+
+	if viper.GetBool("log.logrus_console") {
+		logrus.SetOutput(io.MultiWriter(logWriter, os.Stdout))
+	} else {
+		logrus.SetOutput(logWriter)
+	}
+
+	logrus.SetFormatter(&logrus.JSONFormatter{
+		DisableHTMLEscape: true,
+	})
+
+	// default
+	logrus.SetReportCaller(true)
+}
+
+const panicFile = "./logs/panic.log"
+
+func initCoreDump() error {
+	if runtime.GOOS == "windows" {
+		return nil
+	}
+
+	log.Println("init panic file in unix mode")
+	file, err := os.OpenFile(panicFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	if err = syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
+		return err
+	}
+
+	return nil
+}
+func createDBTable() error {
+	(model.Admsg{}).Init()
+	(model.Timer{}).Init()
+	(model.DayBill{}).Init()
+
+	return nil
+}

+ 3 - 0
cmd/web/web.go

@@ -20,6 +20,7 @@ import (
 	"github.com/spf13/viper"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/rdb"
+	"nn.daxia.dev/handler/admin"
 	"nn.daxia.dev/router"
 )
 
@@ -81,6 +82,8 @@ func main() {
 		logrus.Fatal(err)
 	}
 
+	go admin.StartSendCreditGif()
+
 	// 服务器域名的地址和端口
 	addr := viper.GetString("addr")
 	logrus.Infof("启动服务器在 http address: %s", addr)

+ 35 - 0
configs/tgadtool/config.yaml

@@ -0,0 +1,35 @@
+runmode: debug
+addr: 0.0.0.0:18878
+is_debug: true
+gormlog: true
+db:
+  addr: 10.42.50.148:3306
+  name: qznn
+  username: root
+  password: "123456~"
+rdb:
+  addr: 10.42.129.197:6379
+  password: ""
+  db: 0
+  pool_size: 100
+test:
+  db:
+    addr: 10.42.50.148:3306
+    name: qznn
+    username: root
+    password: "123456~"
+log:
+  logrus_json: true
+  logrus_level: debug
+  logrus_file: logs/server.log
+  logrus_console: true
+bs:
+  url: "http://localhost:18878"
+game:
+  base_url: "http://localhost:18878"
+short_name: qznn
+bot_token: 5917242899:AAGVoKQPA-VAHDxqQBks8r6E9c8cEV2R5sE
+chat_id: -1001635227584
+img_url: "http://www.baidu.com"
+title: hello
+wait_second: 1800

+ 35 - 0
configs/tgkk/config.yaml

@@ -0,0 +1,35 @@
+runmode: debug
+addr: 0.0.0.0:18878
+is_debug: true
+gormlog: true
+db:
+  addr: 10.42.50.148:3306
+  name: qznn
+  username: root
+  password: "123456~"
+rdb:
+  addr: 10.42.129.197:6379
+  password: ""
+  db: 0
+  pool_size: 100
+test:
+  db:
+    addr: 10.42.50.148:3306
+    name: qznn
+    username: root
+    password: "123456~"
+log:
+  logrus_json: true
+  logrus_level: debug
+  logrus_file: logs/server.log
+  logrus_console: true
+bs:
+  url: "http://localhost:18878"
+game:
+  base_url: "http://localhost:18878"
+short_name: qznn
+bot_token: 5420926668:AAEyiTNhtZh7Q2o_PyPT3TLDSQFbs_6bN-E
+chat_id: -1001635227584
+img_url: "http://www.baidu.com"
+title: hello
+wait_second: 1800

BIN
dist/qznn/dist/qznn


+ 1 - 1
dist/qznn/version.txt

@@ -1 +1 @@
-1.0.2211.baf1f
+1.0.2211.f0787

BIN
dist/tg/dist/tg


+ 1 - 1
dist/tg/version.txt

@@ -1 +1 @@
-1.0.2211.bb55f
+1.0.2211.f0787

BIN
dist/web/dist/web


+ 1 - 1
dist/web/version.txt

@@ -1 +1 @@
-1.0.2211.96f8f
+1.0.2211.f0787

+ 0 - 0
game/qznn/onlin.go → game/qznn/online.go


+ 8 - 0
game/qznn/payout.go

@@ -139,6 +139,10 @@ func (p *Game) payout(tx *gorm.DB, roomBaseAmount decimal.Decimal, roomID int32)
 				masterTotalWinAmount = masterUserModel.CreditsQZNN.Mul(decimal.NewFromInt(-1))
 				needFix = true
 			}
+		} else {
+			if masterTotalWinAmount.GreaterThan(masterUserModel.CreditsQZNN) {
+				masterTotalWinAmount = masterUserModel.CreditsQZNN //不需要修正,系统吃了
+			}
 		}
 	} else {
 		if masterTotalWinAmount.LessThan(decimal.Zero) {
@@ -146,6 +150,10 @@ func (p *Game) payout(tx *gorm.DB, roomBaseAmount decimal.Decimal, roomID int32)
 				masterTotalWinAmount = masterUserModel.BalanceQZNN.Mul(decimal.NewFromInt(-1))
 				needFix = true
 			}
+		} else {
+			if masterTotalWinAmount.GreaterThan(masterUserModel.BalanceQZNN) {
+				masterTotalWinAmount = masterUserModel.BalanceQZNN //不需要修正,系统吃了
+			}
 		}
 	}
 

+ 25 - 22
game/qznn/qznn.go

@@ -5,6 +5,7 @@ import (
 	"encoding/hex"
 	"errors"
 	"fmt"
+	"math/rand"
 	"runtime/debug"
 	"sort"
 	"strconv"
@@ -523,24 +524,27 @@ func (p *Game) ChooseRndChair(playerID int32, roomType int32) error {
 			//新进入的玩家,不匹配在一起
 			timeNow := time_utils.TimeNowInCN()
 			player := p.PlayerMap[playerID]
-			if player.LastPayoutTime == nil || player.LastPayoutTime.Before(timeNow.Add(-6*time.Second)) {
-				found := false
-				for _, chairItem := range roomItem.ChairList {
-					if chairItem.PlayerID == 0 {
-						continue
-					}
+			if rand.Intn(4) != 1 {
+				if player.LastPayoutTime == nil || player.LastPayoutTime.Before(timeNow.Add(-6*time.Second)) {
+					found := false
+					for _, chairItem := range roomItem.ChairList {
+						if chairItem.PlayerID == 0 {
+							continue
+						}
 
-					chairPlayer := p.PlayerMap[chairItem.PlayerID]
-					if chairPlayer.LastPayoutTime == nil || chairPlayer.LastPayoutTime.Before(timeNow.Add(-6*time.Second)) {
-						found = true
-						break
+						chairPlayer := p.PlayerMap[chairItem.PlayerID]
+						if chairPlayer.LastPayoutTime == nil || chairPlayer.LastPayoutTime.Before(timeNow.Add(-6*time.Second)) {
+							found = true
+							break
+						}
 					}
-				}
 
-				if found {
-					continue
+					if found {
+						continue
+					}
 				}
 			}
+
 		}
 
 		userCount := 0
@@ -865,14 +869,9 @@ func (p *Game) ChooseChairNoLock(playerID int32, roomID int32, chairID int32, pa
 	p.PlayerMap[playerItem.ID] = playerItem
 
 	if roomItem.RoomType == int32(model.RoomTypeFree) {
-		if userModel.Isrealname == 0 {
-			p.EventInfo(playerID, "选桌子-inner", "未实名,不能进入房间")
-			return errors.New("未实名,不能进入积分房间")
-		}
-
 		if playerItem.Credits.LessThan(decimal.NewFromInt(10)) {
 			p.EventInfo(playerID, "选桌子-inner", "积分不足,无法进入房间")
-			return errors.New("积分不足,无法进入房间")
+			return errors.New("积分不足,请兑换积分或领取积分")
 		}
 	} else {
 		if playerItem.Balance.LessThan(decimal.NewFromInt(10)) {
@@ -930,6 +929,10 @@ func (p *Game) ChooseChairNoLock(playerID int32, roomID int32, chairID int32, pa
 
 	//够底注才能进
 	if roomItem.RoomType == int32(model.RoomTypeFree) {
+		if playerItem.Credits.LessThan(decimal.NewFromInt(10)) {
+			return errors.New("积分低于10,请兑换或绑定银行卡")
+		}
+
 		if playerItem.Credits.LessThan(roomItem.BaseAmount) {
 			p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("积分不足,无法进入房间,余额:%v,限额:%v", playerItem.Credits, roomItem.BaseAmount))
 
@@ -1285,14 +1288,14 @@ func (p *Game) ReadyInner(playerID int32, useLocker bool) error {
 
 			p.EventInfo(playerID, "准备", "等待房间完毕")
 
-			//开始前,冻结金额,看看要不要踢走
-			frozenAmount := roomItem.BaseAmount.Mul(decimal.NewFromInt(4 * 15 * 3 * 4))
-
 			for _, chairItem := range roomItem.ChairList {
 				if chairItem.PlayerID == 0 {
 					continue
 				}
 
+				//开始前,冻结金额,看看要不要踢走
+				frozenAmount := roomItem.BaseAmount.Mul(decimal.NewFromInt(4 * 15 * 3 * 4))
+
 				userModel := model.User{}
 				err = db.GetDB().Model(model.User{}).Where("id = ?", chairItem.PlayerID).First(&userModel).Error
 				if err != nil {

+ 363 - 0
handler/admin/exchange.go

@@ -0,0 +1,363 @@
+package admin
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/go-redis/redis/v8"
+	"github.com/shopspring/decimal"
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/guid"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/rdb"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/time_utils"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/webutils"
+	"gorm.io/gorm"
+	"nn.daxia.dev/model"
+)
+
+type ExchangeReq struct {
+	Token  string          `json:"token" validate:"required"`
+	Type   int             `json:"type" validate:"required, oneof=1,2"` //兑换类型,1:换余额,2:换积分
+	Amount decimal.Decimal `json:"amount" validate:"required"`
+}
+
+const (
+	ExchangeTypeAmount  int = 1
+	ExchangeTypeCredits int = 2
+)
+
+var CreditGifAmount = decimal.NewFromInt(2000)
+var CreditGifAmountDefault = decimal.NewFromInt(50)
+
+type CreditInfo struct {
+	Credits     decimal.Decimal `json:"credits"`
+	Balance     decimal.Decimal `json:"balance"`
+	CreditsType int             `json:"credits_type"`
+}
+
+func QueryCreditsInfo(c *gin.Context) {
+	var err error
+
+	token := strings.TrimSpace(c.Query("token"))
+
+	if token == "" {
+		webutils.FailedResponse(c, "参数错误")
+		return
+	}
+
+	userModel := &model.User{}
+	err = userModel.GetUserByToken(token)
+	if err != nil {
+		logrus.Error(err)
+		webutils.FailedResponse(c, "参数错误")
+		return
+	}
+
+	webutils.SuccessResponse(c, CreditInfo{
+		Credits:     userModel.Credits.Add(userModel.CreditsQZNN),
+		Balance:     userModel.Balance.Add(userModel.BalanceQZNN),
+		CreditsType: userModel.CreditsType,
+	})
+}
+
+func Exchange(c *gin.Context) {
+	var err error
+
+	reqData := ExchangeReq{}
+	if err := c.ShouldBindJSON(&reqData); err != nil {
+		logrus.Error(err)
+		webutils.FailedResponse(c, "参数错误")
+		return
+	}
+
+	if !reqData.Amount.IsInteger() {
+		webutils.FailedResponse(c, "金额只能是整数")
+		return
+	}
+
+	if reqData.Amount.LessThanOrEqual(decimal.Zero) {
+		webutils.FailedResponse(c, "金额不能小于0")
+		return
+	}
+
+	userModel := &model.User{}
+	err = userModel.GetUserByToken(reqData.Token)
+	if err != nil {
+		logrus.Error(err)
+		webutils.FailedResponse(c, "参数错误")
+		return
+	}
+
+	// allowName := []string{"58657315", "30086510", "yy111111", "97483903"}
+	// if !utils.Contains(allowName, userModel.Name) {
+	// 	webutils.FailedResponse(c, "暂未开放,敬请期待")
+	// 	return
+	// }
+	if reqData.Type == ExchangeTypeAmount {
+		minGifCredit := CreditGifAmount.Add(reqData.Amount)
+		if reqData.Amount.LessThan(decimal.NewFromInt(1000)) {
+			webutils.FailedResponse(c, "最少兑换积分1000")
+			return
+		}
+
+		if reqData.Amount.IntPart()%1000 != 0 {
+			webutils.FailedResponse(c, "积分只能是1000的倍数")
+			return
+		}
+
+		balanceAdd := reqData.Amount.IntPart() / 1000
+
+		err = db.GetDB().Transaction(func(tx *gorm.DB) error {
+			err = tx.Model(model.User{}).Where("id = ? ", userModel.ID).Update("credits", gorm.Expr("credits + 0")).Error
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
+
+			userModel, err = userModel.GetUserByIDTx(tx, uint32(userModel.ID))
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
+
+			if userModel.CreditsType == model.CreditsTypeGif {
+				if userModel.Credits.LessThan(minGifCredit) {
+					return errors.New("赠送积分不足,无法兑换")
+				}
+
+				// if userModel.Isrealname != 1 {
+				// 	return errors.New("未实名,无法兑换")
+				// }
+			}
+
+			if userModel.Credits.LessThan(decimal.NewFromInt(1000)) {
+				return errors.New("积分小于1000,无法兑换")
+			}
+
+			if userModel.Credits.LessThan(reqData.Amount) {
+				return errors.New("积分不足,无法兑换")
+			}
+
+			err = tx.Model(model.User{}).Where("id = ?", userModel.ID).Updates(map[string]interface{}{
+				"credits":         gorm.Expr("credits - ?", reqData.Amount),
+				"balance":         gorm.Expr("balance + ?", balanceAdd),
+				"commission_chek": 1,
+			}).Error
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
+
+			userModel, err = userModel.GetUserByIDTx(tx, uint32(userModel.ID))
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
+
+			if userModel.Credits.LessThan(decimal.Zero) {
+				return errors.New("积分不足")
+			}
+
+			if !userModel.CreditsQZNN.Equal(decimal.Zero) {
+				return errors.New("游戏中,无法兑换")
+			}
+
+			//增加操作日志
+			err = tx.Model(model.UserMoneyLog{}).Create(&model.UserMoneyLog{
+				TradeSN:    guid.GenNoSplit(),
+				UserID:     int32(userModel.ID),
+				RelationID: 0,
+				Type:       model.ExchangeCreditToAmount,
+				Money:      decimal.NewFromInt(balanceAdd),
+				Balance:    userModel.Balance.Add(userModel.BalanceQZNN),
+				Increment:  1,
+				Status:     1,
+				Remark:     "积分兑换余额",
+			}).Error
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
+
+			return nil
+		})
+
+		if err != nil {
+			webutils.FailedResponse(c, err.Error())
+			return
+		}
+
+		webutils.SuccessResponse(c, nil)
+		return
+	}
+
+	//兑换为积分
+	if userModel.Balance.LessThan(reqData.Amount) {
+		webutils.FailedResponse(c, "金额不足,无法兑换")
+		return
+	}
+
+	if userModel.CreditsType == model.CreditsTypeGif && userModel.Credits.Add(userModel.CreditsQZNN).GreaterThan(decimal.NewFromInt(10)) {
+		webutils.FailedResponse(c, "赠送积分大于10,无法兑换")
+		return
+	}
+
+	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
+		creditsAdd := reqData.Amount.IntPart() * 1000
+		err = tx.Model(model.User{}).Where("id = ? ", userModel.ID).Update("credits", gorm.Expr("credits + 0")).Error
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+
+		userModel, err = userModel.GetUserByIDTx(tx, uint32(userModel.ID))
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+
+		if userModel.CreditsType == model.CreditsTypeGif && userModel.Credits.Add(userModel.CreditsQZNN).GreaterThan(decimal.NewFromInt(10)) {
+			return errors.New("赠送积分大于10,无法兑换")
+		}
+
+		err = tx.Model(model.User{}).Where("id = ?", userModel.ID).Updates(map[string]interface{}{
+			"credits":      gorm.Expr("credits + ?", creditsAdd),
+			"balance":      gorm.Expr("balance - ?", reqData.Amount),
+			"credits_type": model.CreditsTypeExchange,
+		}).Error
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+
+		userModel, err = userModel.GetUserByIDTx(tx, uint32(userModel.ID))
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+
+		if userModel.Balance.LessThan(decimal.Zero) {
+			return errors.New("余额不足,兑换失败")
+		}
+
+		if !userModel.CreditsQZNN.Equal(decimal.Zero) {
+			return errors.New("游戏中,无法兑换")
+		}
+
+		//增加操作日志
+		err = tx.Model(model.UserMoneyLog{}).Create(&model.UserMoneyLog{
+			TradeSN:    guid.GenNoSplit(),
+			UserID:     int32(userModel.ID),
+			RelationID: 0,
+			Type:       model.ExchangeAmountToCredit,
+			Money:      reqData.Amount,
+			Balance:    userModel.Balance.Add(userModel.BalanceQZNN),
+			Increment:  0,
+			Status:     1,
+			Remark:     "余额兑换积分",
+		}).Error
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+		return nil
+	})
+
+	if err != nil {
+		webutils.FailedResponse(c, "操作失败")
+		return
+	}
+
+	webutils.SuccessResponse(c, nil)
+}
+
+//赠送积分
+func StartSendCreditGif() {
+	for {
+		<-time.After(20 * time.Second)
+		err := SendCreditGif()
+		if err != nil {
+			logrus.Error(err)
+		}
+	}
+}
+
+func SendCreditGif() error {
+	var err error
+
+	todayTiemstamp := time_utils.GetZeroTime(time_utils.TimeNowInCN()).Unix()
+	if todayTiemstamp < 1668700800 {
+		return nil
+	}
+
+	err = db.GetDB().Model(model.User{}).
+		Where("(credits + credits_qznn) < 10 and credits_type = ? and last_credit_gif_time <> ?",
+			model.CreditsTypeExchange, todayTiemstamp).Update("credits_type", model.CreditsTypeGif).Error
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	count := int64(0)
+	err = db.GetDB().Model(model.User{}).Where("isrealname = 1 and credits_type = ? and last_credit_gif_time <> ?",
+		model.CreditsTypeGif, todayTiemstamp).Count(&count).Error
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	if count == 0 {
+		return nil
+	}
+
+	err = db.GetDB().Model(model.User{}).
+		Where("isrealname = 1 and credits_type = ? and last_credit_gif_time <> ? and credits_qznn = 0",
+			model.CreditsTypeGif, todayTiemstamp).Updates(map[string]interface{}{
+		"last_credit_gif_time": todayTiemstamp,
+		"credits":              CreditGifAmount,
+	}).Error
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	err = db.GetDB().Model(model.User{}).
+		Where("isrealname = 0 and credits_type = ? and last_credit_gif_time <> ? and credits_qznn = 0",
+			model.CreditsTypeGif, todayTiemstamp).Updates(map[string]interface{}{
+		"last_credit_gif_time": todayTiemstamp,
+		"credits":              CreditGifAmountDefault,
+	}).Error
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	return nil
+}
+
+func IsSetToday(timestamp int64) (bool, error) {
+	key := fmt.Sprintf("reset_credist_user3_%d", timestamp)
+	_, err := rdb.GetRDB().Get(context.Background(), key).Result()
+	if err == redis.Nil {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+func SetIsRenew(timestamp int64) error {
+	todayDayStr := time_utils.GetFormatTime(time.Now())
+	key := fmt.Sprintf("reset_credist_user3_%d", timestamp)
+	_, err := rdb.GetRDB().Set(context.Background(), key, todayDayStr, time.Hour*48).Result()
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	return nil
+}

+ 10 - 66
handler/common/common.go

@@ -1,23 +1,17 @@
 package common
 
 import (
-	"context"
 	"errors"
 	"fmt"
-	"time"
+	"strings"
 
 	"github.com/gin-gonic/gin"
-	"github.com/go-redis/redis/v8"
 	"github.com/sirupsen/logrus"
-	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
-	"gogs.daxia.dev/huanan/pkg.daxia.dev/rdb"
-	"gogs.daxia.dev/huanan/pkg.daxia.dev/time_utils"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/webutils"
 	gameQZ "nn.daxia.dev/game/qznn"
 	"nn.daxia.dev/handler/api"
 	"nn.daxia.dev/handler/qznn"
 	"nn.daxia.dev/handler/room"
-	"nn.daxia.dev/model"
 	"nn.daxia.dev/nxd"
 )
 
@@ -39,6 +33,12 @@ func Dispatch(c *gin.Context) {
 			webutils.FailedResponse(c, "用户不在线")
 			return
 		}
+
+		if userModel.ParentTreeStr == "shiwan" {
+			logrus.Error("试玩账号不允许进入")
+			webutils.FailedResponse(c, "试玩账号不可进入此游戏")
+			return
+		}
 	}
 
 	logrus.Info("run function:", reqData.Name)
@@ -103,8 +103,9 @@ func Test(req api.PerformReq) error {
 
 func Connect(req api.PerformReq) error {
 	userModel := req.UserModel
-
-	UpdateCredits(int64(userModel.ID))
+	if !strings.HasPrefix(userModel.ParentTreeStr, "taiyang") {
+		return errors.New("暂不支持")
+	}
 
 	gameInstance := gameQZ.GetInstance()
 
@@ -135,60 +136,3 @@ func Ping(req api.PerformReq) error {
 
 	return nil
 }
-
-func UpdateCredits(userID int64) error {
-	isSet, err := IsSetToday(userID)
-	if err != nil {
-		logrus.Error(err)
-		return nil
-	}
-
-	if !isSet {
-		err = SetIsRenew(userID)
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
-
-		err = db.GetDB().Model(model.User{}).
-			Where("id  = ?", userID).Update("credits", "10000").Error
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
-	}
-
-	return nil
-}
-
-func IsSetToday(userID int64) (bool, error) {
-	key := fmt.Sprintf("reset_credist_user2_%d", userID)
-	result, err := rdb.GetRDB().Get(context.Background(), key).Result()
-	if err == redis.Nil {
-		return false, nil
-	}
-
-	if err != nil {
-		logrus.Error(err)
-		return false, err
-	}
-
-	todayDayStr := time_utils.GetFormatTime(time.Now())
-	if todayDayStr != result {
-		return false, nil
-	}
-
-	return true, nil
-}
-
-func SetIsRenew(userID int64) error {
-	todayDayStr := time_utils.GetFormatTime(time.Now())
-	key := fmt.Sprintf("reset_credist_user2_%d", userID)
-	_, err := rdb.GetRDB().Set(context.Background(), key, todayDayStr, time.Hour*48).Result()
-	if err != nil {
-		logrus.Error(err)
-		return err
-	}
-
-	return nil
-}

BIN
logs/panic.log


+ 1 - 1
logs/server.log

@@ -1 +1 @@
-logs/server.log.20221115.log
+logs/server.log.20221222.log

+ 42 - 0
model/admsg.go

@@ -0,0 +1,42 @@
+package model
+
+import (
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+)
+
+type Admsg struct {
+	ID        uint `gorm:"primarykey"`
+	CreatedAt time.Time
+	UpdatedAt time.Time
+
+	Name string `gorm:"text; comment: 名称"`
+	Json string `gorm:"text; comment: json数据"`
+}
+
+func (Admsg) TableName() string {
+	return "ad_msg"
+}
+
+func (p Admsg) Init() error {
+	var err error
+
+	if db.GetDB().Migrator().HasTable(p) {
+		return nil
+	}
+
+	err = db.GetDB().Migrator().CreateTable(p)
+	if err != nil {
+		logrus.Fatal(err)
+		return err
+	}
+
+	err = db.GetDB().Exec("create unique index idx_name on ad_msg (name)").Error
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 38 - 0
model/day_bill.go

@@ -0,0 +1,38 @@
+package model
+
+import (
+	"time"
+
+	"github.com/shopspring/decimal"
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+)
+
+type DayBill struct {
+	ID           uint `gorm:"primarykey"`
+	CreatedAt    time.Time
+	UpdatedAt    time.Time
+	DayTimestamp int64
+	DayStr       string
+	YesterAmount decimal.Decimal
+	NewCredits   decimal.Decimal
+	IsSet        int
+}
+
+func (DayBill) TableName() string {
+	return "day_bill"
+}
+
+func (p DayBill) Init() error {
+	var err error
+
+	if !db.GetDB().Migrator().HasTable(p) {
+		err = db.GetDB().Migrator().CreateTable(p)
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+	}
+
+	return nil
+}

+ 40 - 0
model/timer.go

@@ -0,0 +1,40 @@
+package model
+
+import (
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+)
+
+type Timer struct {
+	ID        uint `gorm:"primarykey"`
+	CreatedAt time.Time
+	UpdatedAt time.Time
+
+	ChatID        int64
+	Name          string `gorm:"text; comment: 名称"`
+	LastTimestamp int64
+	Interval      int64
+	AdMsgName     string
+}
+
+func (Timer) TableName() string {
+	return "timer"
+}
+
+func (p Timer) Init() error {
+	var err error
+
+	if db.GetDB().Migrator().HasTable(p) {
+		return nil
+	}
+
+	err = db.GetDB().Migrator().CreateTable(p)
+	if err != nil {
+		logrus.Fatal(err)
+		return err
+	}
+
+	return nil
+}

+ 42 - 21
model/user.go

@@ -9,24 +9,31 @@ import (
 	"gorm.io/gorm"
 )
 
+const (
+	CreditsTypeGif      int = 1
+	CreditsTypeExchange int = 2
+)
+
 type User struct {
-	ID            uint            `gorm:"primarykey"`
-	Pid           uint            `gorm:"varchar(40); comment:'父ID'"`
-	Name          string          `gorm:"varchar(40); comment:'名称'"`
-	Nickname      string          `gorm:"varchar(40); comment:'nickname'"`
-	Mobile        string          `gorm:"varchar(40); comment:'手机号'"`
-	AccountType   int             `gorm:"int; comment:'账号类型'"` //0:普通用户, 1:代理
-	Status        int             `gorm:"int; comment:'状态0:禁用,1:启用'"`
-	Balance       decimal.Decimal `sql:"type:decimal(16.2)"`
-	Credits       decimal.Decimal `sql:"type:decimal(16.2)"`
-	BalanceQZNN   decimal.Decimal `sql:"type:decimal(16.2)"`
-	CreditsQZNN   decimal.Decimal `sql:"type:decimal(16.2)"`
-	HeadImg       string          `gorm:"varchar(40); comment:'头像'"`
-	Token         string          `gorm:"varchar(40); comment:'token'"`
-	City          string          `gorm:"varchar(40); comment:'城市'"`
-	ParentTree    string          `gorm:"varchar(40); comment:'目录树'"`
-	ParentTreeStr string          `gorm:"varchar(40); comment:'父节点'"`
-	Isrealname    int             `gorm:"int; comment:是否实名"`
+	ID                uint            `gorm:"primarykey"`
+	Pid               uint            `gorm:"varchar(40); comment:'父ID'"`
+	Name              string          `gorm:"varchar(40); comment:'名称'"`
+	Nickname          string          `gorm:"varchar(40); comment:'nickname'"`
+	Mobile            string          `gorm:"varchar(40); comment:'手机号'"`
+	AccountType       int             `gorm:"int; comment:'账号类型'"` //0:普通用户, 1:代理
+	Status            int             `gorm:"int; comment:'状态0:禁用,1:启用'"`
+	Balance           decimal.Decimal `sql:"type:decimal(16.2)"`
+	Credits           decimal.Decimal `sql:"type:decimal(16.2)"`
+	BalanceQZNN       decimal.Decimal `sql:"type:decimal(16.2)"`
+	CreditsQZNN       decimal.Decimal `sql:"type:decimal(16.2)"`
+	HeadImg           string          `gorm:"varchar(40); comment:'头像'"`
+	Token             string          `gorm:"varchar(40); comment:'token'"`
+	City              string          `gorm:"varchar(40); comment:'城市'"`
+	ParentTree        string          `gorm:"varchar(40); comment:'目录树'"`
+	ParentTreeStr     string          `gorm:"varchar(40); comment:'父节点'"`
+	Isrealname        int             `gorm:"int; comment:是否实名"`
+	CreditsType       int             `gorm:"int; comment:积分类型"`
+	LastCreditGifTime int64           `gorm:"int; comment:赠送时间"`
 }
 
 func (User) TableName() string {
@@ -76,7 +83,22 @@ func (p *User) GetUserByID(userID uint32) error {
 
 	err = db.GetDB().
 		Model(p).
-		Where("status = 1 and id = ?", userID).First(p).Error
+		Where("id = ?", userID).First(p).Error
+
+	if err != nil {
+		logrus.Error(err)
+		return err
+	}
+
+	return nil
+}
+
+func (p *User) GetUserByToken(token string) error {
+	var err error
+
+	err = db.GetDB().
+		Model(p).
+		Where("token = ?", token).First(p).Error
 
 	if err != nil {
 		logrus.Error(err)
@@ -92,7 +114,6 @@ func (p User) GetUserByIDTx(tx *gorm.DB, userID uint32) (*User, error) {
 	userModel := User{}
 	err = tx.
 		Model(p).
-		Where("status = 1").
 		Where("id = ?", userID).First(&userModel).Error
 
 	if err != nil {
@@ -134,7 +155,7 @@ func (p *User) TokenIsValid(userToken string) (bool, error) {
 
 func (p User) IncBotUserAmount(userID uint32, amount *decimal.Decimal) error {
 	updateItem := db.GetDB().Model(p).
-		Where("status = 1 and platform_flag = 1 and parent_tree_str = 'robot'").
+		Where("platform_flag = 1 and parent_tree_str = 'robot'").
 		Where("id = ?", userID).
 		Update("balance", gorm.Expr("balance + ?", amount))
 
@@ -147,7 +168,7 @@ func (p User) IncBotUserAmount(userID uint32, amount *decimal.Decimal) error {
 
 func (p User) DescBotUserAmount(userID uint32, amount *decimal.Decimal) error {
 	updateItem := db.GetDB().Model(p).
-		Where("status = 1 and platform_flag = 1  and parent_tree_str = 'robot'").
+		Where("platform_flag = 1  and parent_tree_str = 'robot'").
 		Where("id = ? and balance - ? >= 0", userID, amount).
 		Update("balance", gorm.Expr("balance - ?", amount))
 

+ 32 - 0
model/user_money_log.go

@@ -0,0 +1,32 @@
+package model
+
+import (
+	"time"
+
+	"github.com/shopspring/decimal"
+)
+
+const (
+	ExchangeCreditToAmount int = 38 //积分换余额
+	ExchangeAmountToCredit int = 39 //余额换积分
+)
+
+type UserMoneyLog struct {
+	ID        uint `gorm:"primarykey"`
+	CreatedAt time.Time
+	UpdatedAt time.Time
+
+	TradeSN    string
+	UserID     int32
+	RelationID int32
+	Type       int
+	Money      decimal.Decimal
+	Balance    decimal.Decimal
+	Increment  int
+	Status     int
+	Remark     string
+}
+
+func (UserMoneyLog) TableName() string {
+	return "user_money_log"
+}

+ 9 - 1
router/router.go

@@ -65,8 +65,10 @@ func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {
 	{
 		adminApiRoute.POST("/set_maintain", admin.SetMaintain)
 		adminApiRoute.POST("/create_robot", admin.CreateRobot)
+
 		adminApiRoute.GET("/query_open_record", admin.QueryOpenRecord)
 		adminApiRoute.GET("/query_game_record", admin.QueryGameRecord)
+
 	}
 
 	isDebug := viper.GetBool("is_debug")
@@ -108,7 +110,7 @@ func LoadWeb(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {
 		})
 	}
 
-	adminApiRoute := g.Group("/api/admin")
+	adminApiRoute := g.Group("/api/admin/sec8877")
 	{
 		adminApiRoute.POST("/set_maintain", admin.SetMaintain)
 
@@ -119,5 +121,11 @@ func LoadWeb(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {
 		adminApiRoute.GET("/query_game_record", admin.QueryGameRecord)
 	}
 
+	adminGameApiRoute := g.Group("/api/admin/game")
+	{
+		adminGameApiRoute.POST("exchange", admin.Exchange)
+		adminGameApiRoute.GET("query_credits_info", admin.QueryCreditsInfo)
+	}
+
 	return g
 }

+ 62 - 0
tg/cmd.go

@@ -0,0 +1,62 @@
+package tg
+
+import (
+	"strings"
+
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"github.com/spf13/viper"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
+	"nn.daxia.dev/tg/cmd"
+)
+
+func StartCmd() {
+	event2.On[tgbotapi.Update](EventUpdateMsg, event2.ListenerFunc[tgbotapi.Update](onCmdUpdate))
+}
+
+func GetCmdList(cmdSimple string, chnStr string) []string {
+	return []string{
+		chnStr,
+		cmdSimple,
+		cmdSimple + "@" + viper.GetString("bot_username"),
+		"/" + cmdSimple,
+	}
+}
+
+func IsCmd(cmdSimple string, chnStr string, text string) (bool, string) {
+	cmdSimple = strings.ToLower(cmdSimple)
+	chnStr = strings.ToLower(chnStr)
+	text = strings.ToLower(text)
+
+	cmdList := GetCmdList(cmdSimple, chnStr)
+	for _, item := range cmdList {
+		if strings.HasPrefix(text, item) {
+			newText := strings.Replace(cmdSimple, item, cmdSimple, 1)
+			return true, newText
+		}
+	}
+
+	return false, ""
+}
+
+func onCmdUpdate(e event2.Event[tgbotapi.Update]) error {
+	update := e.Data()
+
+	if update.Message == nil {
+		return nil
+	}
+
+	textMsg := tgbotapi.MessageConfig{
+		BaseChat: tgbotapi.BaseChat{
+			ChatID:           update.Message.Chat.ID,
+			ReplyToMessageID: update.Message.MessageID,
+			ReplyMarkup:      cmd.GetServiceKeyboardBtn(),
+		},
+		Text:                  "欢迎使用福利来订单机器人",
+		ParseMode:             "HTML",
+		DisableWebPagePreview: false,
+	}
+
+	//SendMsg(textMsg, update, botApi)
+	event2.MustFire(EventSendMsgFix, tgbotapi.Chattable(textMsg))
+	return nil
+}

+ 20 - 0
tg/cmd/btn.go

@@ -0,0 +1,20 @@
+package cmd
+
+import tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+
+func GetServiceKeyboardBtn() tgbotapi.ReplyKeyboardMarkup {
+	return tgbotapi.NewReplyKeyboard(
+		tgbotapi.NewKeyboardButtonRow(
+			tgbotapi.NewKeyboardButton("上分"),
+			tgbotapi.NewKeyboardButton("推广查询"),
+		),
+		tgbotapi.NewKeyboardButtonRow(
+			tgbotapi.NewKeyboardButton("下载APP"),
+			tgbotapi.NewKeyboardButton("东方汇游戏"),
+		),
+		tgbotapi.NewKeyboardButtonRow(
+			tgbotapi.NewKeyboardButton("🏆排行榜"),
+			tgbotapi.NewKeyboardButton("🏅我的排行"),
+		),
+	)
+}

+ 17 - 0
tg/cmd/cmd.go

@@ -0,0 +1,17 @@
+package cmd
+
+type SettingInfo struct {
+	ChatID   int64
+	BotToken string
+}
+
+type BottomBtn struct {
+	Name   string
+	CmdStr string
+}
+
+//设置底部按钮
+func SetBottomBtnList(ChatID int64, botID int, btn []BottomBtn) error {
+	//
+	return nil
+}

+ 139 - 113
tg/game.go

@@ -11,7 +11,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/go-redis/redis/v8"
 	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
 	"github.com/shopspring/decimal"
 	"github.com/sirupsen/logrus"
@@ -20,8 +19,11 @@ import (
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/encrypt"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/guid"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/parse"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/rdb"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/time_utils"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/webutils"
+	"gorm.io/gorm/utils"
 	"nn.daxia.dev/gameproto"
 	"nn.daxia.dev/handler/qznn"
 	"nn.daxia.dev/model"
@@ -51,21 +53,30 @@ func onUpdate(e event2.Event[tgbotapi.Update]) error {
 
 	update := e.Data()
 
+	if update.InlineQuery != nil || update.ChosenInlineResult != nil {
+		handleInlineQuery(update)
+	}
+
 	if update.Message != nil {
 		handlerMsg(update)
 		return nil
 	}
 
-	if update.InlineQuery != nil || update.ChosenInlineResult != nil {
-		handleInlineQuery(update)
-	}
-
 	if update.CallbackQuery == nil {
 		logrus.Info("CallbackQuery is null")
 		return nil
 	}
 
 	fmt.Println("InlineMessageID", update.CallbackQuery.InlineMessageID)
+	pid := ""
+	if update.CallbackQuery.InlineMessageID != "" {
+		result, err := rdb.GetRDB().Get(context.Background(), "qznn_inline_msg_"+update.CallbackQuery.InlineMessageID).Result()
+		if err == nil {
+			pid = result
+		}
+
+		logrus.Infof("msgID:%s, pid:%s", update.CallbackQuery.InlineMessageID, pid)
+	}
 
 	tgID := update.CallbackQuery.From.ID
 	gameUserMapModel := model.GameUserMap{}
@@ -78,6 +89,62 @@ func onUpdate(e event2.Event[tgbotapi.Update]) error {
 		return nil
 	}
 
+	if pid != "" {
+		func() {
+			var err error
+
+			parentUserModel := model.User{}
+			err = parentUserModel.GetUserByID(uint32(parse.StringToInt(pid)))
+			if err != nil {
+				logrus.Error(err)
+				return
+			}
+
+			subUserInfo := model.User{}
+			err = parentUserModel.GetUserByID(uint32(gameUserMapModel.UserID))
+			if err != nil {
+				logrus.Error(err)
+				return
+			}
+
+			if subUserInfo.Pid != 20 {
+				return
+			}
+
+			fmt.Println(subUserInfo.Mobile)
+
+			//防止自己移动到自己
+			if subUserInfo.ID == parentUserModel.ID {
+				return
+			}
+
+			pIDtreeList := strings.Split(parentUserModel.ParentTreeStr, "/")
+			if !utils.Contains(pIDtreeList, "taiyang") {
+				return
+			}
+
+			if utils.Contains(pIDtreeList, subUserInfo.Name) {
+				return
+			}
+
+			fmt.Println(subUserInfo.Mobile, subUserInfo.Pid)
+
+			if gameUserMapModel.CreatedAt.After(time.Now().Add(-1*time.Hour)) && subUserInfo.Token != "" {
+				reqUrl := fmt.Sprintf("https://47.75.110.209:88/api/user/set_info?pid=%d&resetrate=1", parentUserModel.ID)
+				headers := map[string]string{
+					"authorization": "bearer " + subUserInfo.Token,
+				}
+
+				respData, err := webutils.PostWithHeader(reqUrl, "", headers, time.Second*5)
+				if err != nil {
+					logrus.Error(err, " token:", subUserInfo.Token)
+				}
+
+				fmt.Println(string(respData), subUserInfo.Mobile, "==>", parentUserModel.ID, " toke:", subUserInfo.Token)
+			}
+		}()
+	}
+
 	token, err := RefreshTokenByUserID(gameUserMapModel.UserID)
 	if err != nil {
 		logrus.Error(err)
@@ -105,11 +172,23 @@ func onUpdate(e event2.Event[tgbotapi.Update]) error {
 
 func handleInlineQuery(update tgbotapi.Update) error {
 	if update.ChosenInlineResult != nil {
-		fmt.Println("InlineMessageID:", update.ChosenInlineResult.InlineMessageID)
+		logrus.Infof("handleInlineQuery:%s", update.ChosenInlineResult.InlineMessageID)
+
+		if update.ChosenInlineResult.From != nil {
+			fromIDStr := fmt.Sprintf("%d", update.ChosenInlineResult.From.ID)
+			logrus.Infof("handleInlineQuery, msgID:%s, userID:%s", update.ChosenInlineResult.InlineMessageID, fromIDStr)
+
+			_, err := rdb.GetRDB().Set(context.Background(), "qznn_inline_msg_"+update.ChosenInlineResult.InlineMessageID, fromIDStr, time.Hour*24*30).Result()
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
+		}
+
 		return nil
 	}
 
-	btnList := GetGameReplyMarkupSimple()
+	btnList := GetGameReplyMarkupShare("抢庄牛牛游戏")
 
 	queryStr := update.InlineQuery.Query
 
@@ -161,7 +240,7 @@ func handlerMsg(update tgbotapi.Update) error {
 		}
 	}
 
-	if cmdMsg == "mylevel" || cmdMsg == "我的排行" || cmdMsg == "/wdph@fllqznnbot" || cmdMsg == "ye" || cmdMsg == "/ye@fllqznnbot" {
+	if cmdMsg == "mylevel" || cmdMsg == "我的排行" || cmdMsg == "/wdph@fllqznnbot" || cmdMsg == "🏅我的排行" || cmdMsg == "ye" || cmdMsg == "/ye@fllqznnbot" {
 
 		gameUserMapModel := model.GameUserMap{}
 		err = db.GetDB().Table("game_user_map").
@@ -224,10 +303,17 @@ func handlerMsg(update tgbotapi.Update) error {
 			msg = myIndex
 		}
 
+		tdayExchangeMap, err := GetTodayExchangeMap()
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+
 		textMsg := `ID: ` + userModel.Mobile + `
 用户名: ` + userModel.Nickname + `
 ` + fieldName + `: ` + msg + "(" + data.MyCredistLeaderBoard.TotalAmount + ")" + `
 积分: ` + userModel.Credits.StringFixed(2) + `
+今天已兑换: ` + tdayExchangeMap[int32(userModel.ID)].StringFixed(2) + "u" + `
 余额: ` + fmt.Sprintf("%.2f", respData.Data.Balance) + `
 今日流水: ` + fmt.Sprintf("%.2f", respData.Data.Amount) + `
 今日输赢: ` + fmt.Sprintf("%.2f", respData.Data.LoseWin) + `
@@ -262,7 +348,7 @@ func handlerMsg(update tgbotapi.Update) error {
 		return nil
 	}
 
-	if cmdMsg == "phb" || cmdMsg == "排行榜" || cmdMsg == "/phb@fllqznnbot" {
+	if cmdMsg == "phb" || cmdMsg == "排行榜" || cmdMsg == "🏆排行榜" || cmdMsg == "/phb@fllqznnbot" {
 		gameUserMapModel := model.GameUserMap{}
 		err = db.GetDB().Table("game_user_map").
 			Where("bot_user_id = ? and plat_id = 2", tgID).
@@ -283,6 +369,8 @@ func handlerMsg(update tgbotapi.Update) error {
 		timeNow := time.Now()
 		timeTodayZero := time_utils.GetZeroTime(timeNow).Unix()
 
+		var exchangeMap map[int32]decimal.Decimal
+
 		var data *gameproto.CredistLeaderBoardList
 		msg := "昨日游戏排行榜收入前50,请再接再厉\n\n"
 
@@ -294,6 +382,12 @@ func handlerMsg(update tgbotapi.Update) error {
 				logrus.Error(err)
 				return err
 			}
+
+			exchangeMap, err = GetYestedayExchangeMap()
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
 		} else {
 			msg = "今日游戏排行榜收入前50,请再接再厉\n\n"
 
@@ -304,6 +398,12 @@ func handlerMsg(update tgbotapi.Update) error {
 				logrus.Error(err)
 				return err
 			}
+
+			exchangeMap, err = GetTodayExchangeMap()
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
 		}
 
 		myIndex := "-"
@@ -311,9 +411,10 @@ func handlerMsg(update tgbotapi.Update) error {
 			userModel := model.User{}
 			err = userModel.GetUserByID(item.UserID)
 			if err == nil && strings.HasPrefix(userModel.Mobile, "FLL") {
-				msg += fmt.Sprintf("%d: %s(%s) %vu\n", index+1, item.Name, userModel.Mobile, item.TotalAmount)
+				msg += fmt.Sprintf("%d: %s(%s) %vu(已兑换%su)\n",
+					index+1, item.Name, userModel.Mobile, item.TotalAmount, exchangeMap[int32(item.UserID)].StringFixed(2))
 			} else {
-				msg += fmt.Sprintf("%d: %s %vu\n", index+1, item.Name, item.TotalAmount)
+				msg += fmt.Sprintf("%d: %s %vu(已兑换%su)\n", index+1, item.Name, item.TotalAmount, exchangeMap[int32(item.UserID)].StringFixed(2))
 			}
 
 			if data.MyCredistLeaderBoard.UserID == item.UserID {
@@ -328,8 +429,8 @@ func handlerMsg(update tgbotapi.Update) error {
 			nameExt = fmt.Sprintf("(%s, %d)", tgUsername, tgID)
 		}
 
-		msg += fmt.Sprintf("----------------------------\n%s: %s%s %vu\n", myIndex, data.MyCredistLeaderBoard.Name,
-			nameExt, data.MyCredistLeaderBoard.TotalAmount)
+		msg += fmt.Sprintf("----------------------------\n%s: %s%s %vu(已兑换%su)\n", myIndex, data.MyCredistLeaderBoard.Name,
+			nameExt, data.MyCredistLeaderBoard.TotalAmount, exchangeMap[int32(data.MyCredistLeaderBoard.UserID)].StringFixed(2))
 		msg += `
 (一将功成万骨枯,你们的牌技战胜了90%的敌人。)
 
@@ -352,124 +453,49 @@ func handlerMsg(update tgbotapi.Update) error {
 	return nil
 }
 
-func UpdateAppCredits() error {
-	isSet, err := IsSetToday(567)
-	if err != nil {
-		logrus.Error(err)
-		return nil
-	}
-
-	if !isSet {
-		err = SetIsRenew(567)
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
-
-		err = db.GetDB().Model(model.User{}).
-			Where("id in (?)", db.GetDB().Model(model.GameUserMap{}).Where("plat_id = 2").Select("user_id")).Update("credits", "10000").Error
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
+func GetTodayExchangeMap() (map[int32]decimal.Decimal, error) {
+	type InfoData struct {
+		UserID      int32
+		TotalAmount decimal.Decimal
 	}
 
-	return nil
-}
-
-func UpdateCredits(tgID int64, userID int64) error {
-	isSet, err := IsSetToday(tgID)
+	infoList := make([]InfoData, 0)
+	err := db.GetDB().Model(model.UserMoneyLog{}).Select("sum(money) total_amount, user_id").Where("created_at > CURDATE() and type = ?",
+		model.ExchangeCreditToAmount).Group("user_id").Scan(&infoList).Error
 	if err != nil {
 		logrus.Error(err)
-		return nil
+		return nil, err
 	}
 
-	if !isSet {
-		err = SetIsRenew(tgID)
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
-
-		err = db.GetDB().Model(model.User{}).
-			Where("id  = ?", userID).Update("credits", "10000").Error
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
+	resultMap := make(map[int32]decimal.Decimal)
+	for _, item := range infoList {
+		resultMap[item.UserID] = item.TotalAmount
 	}
 
-	return nil
+	return resultMap, nil
 }
 
-func UpdateBotCredits() error {
-	isSet, err := IsSetToday(8888)
-	if err != nil {
-		logrus.Error(err)
-		return nil
-	}
-
-	if !isSet {
-		err = SetIsRenew(8888)
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
-
-		tokenList := []string{
-			"6348307e0ce0c7c10043d05dad0b24d2",
-			"9f1eb722df23eef57368550487f33dff",
-			"8f04dc970bbcc98ede99ef129a342228",
-			"9ced6e7d4d336b46113235673465be74",
-			"99a649ba48b00cb84adbb5e889b35e8b",
-			"fa1a5e9b32a3bb3fd2995edc8efcaa8a",
-			"92084b7b81fed253d816fc719db825db",
-			"65fcc17eb6d00f12a95c8b31152b9ffa",
-			"6b2c0b92ddaa8f7d5f0d8439ca6dd1ce",
-			"7b22f1d4b590fda354cd62cb973c6216",
-		}
-		err = db.GetDB().Table("users").
-			Where("token in (?)", tokenList).Updates(map[string]interface{}{
-			"credits": "10000",
-		}).Error
-		if err != nil {
-			logrus.Error(err)
-			return nil
-		}
-	}
-
-	return nil
-}
-func IsSetToday(tgID int64) (bool, error) {
-	key := fmt.Sprintf("reset_credist_user_%d", tgID)
-	result, err := rdb.GetRDB().Get(context.Background(), key).Result()
-	if err == redis.Nil {
-		return false, nil
+func GetYestedayExchangeMap() (map[int32]decimal.Decimal, error) {
+	type InfoData struct {
+		UserID      int32
+		TotalAmount decimal.Decimal
 	}
 
+	infoList := make([]InfoData, 0)
+	err := db.GetDB().Model(model.UserMoneyLog{}).Select("sum(money) total_amount, user_id").
+		Where("created_at > DATE_SUB(curdate(),INTERVAL 1 DAY) and created_at < curdate() and type = ?",
+			model.ExchangeCreditToAmount).Group("user_id").Scan(&infoList).Error
 	if err != nil {
 		logrus.Error(err)
-		return false, err
-	}
-
-	todayDayStr := time_utils.GetFormatTime(time.Now())
-	if todayDayStr != result {
-		return false, nil
+		return nil, err
 	}
 
-	return true, nil
-}
-
-func SetIsRenew(tgID int64) error {
-	todayDayStr := time_utils.GetFormatTime(time.Now())
-	key := fmt.Sprintf("reset_credist_user_%d", tgID)
-	_, err := rdb.GetRDB().Set(context.Background(), key, todayDayStr, time.Hour*48).Result()
-	if err != nil {
-		logrus.Error(err)
-		return err
+	resultMap := make(map[int32]decimal.Decimal)
+	for _, item := range infoList {
+		resultMap[item.UserID] = item.TotalAmount
 	}
 
-	return nil
+	return resultMap, nil
 }
 
 func RefreshTokenByUserID(userID int) (string, error) {

+ 69 - 24
tg/game_timer.go

@@ -7,36 +7,47 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/viper"
 	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
+	"nn.daxia.dev/tg/cmd"
 )
 
-var deleteMsgList = make([]tgbotapi.DeleteMessageConfig, 0)
-
 func StartGameTimer() {
-	waitSecond := viper.GetInt("wait_second")
 	go func() {
+		var deleteMsgList = make([]tgbotapi.DeleteMessageConfig, 0)
+
 		for {
+			chatID := viper.GetInt64("chat_id")
 			logrus.Info("run...")
-			sendGameInfo()
-			<-time.After(time.Duration(waitSecond) * time.Second)
+			sendGameInfoWithDel(chatID, &deleteMsgList, true)
+			<-time.After(60 * time.Second)
 		}
 	}()
 
 	go func() {
+		var deleteMsgList = make([]tgbotapi.DeleteMessageConfig, 0)
+
 		for {
+
 			logrus.Info("run...")
-			sendGameInfo2()
-			<-time.After(60 * time.Second)
+			sendGameInfoWithDel(int64(-1001632614756), &deleteMsgList, false)
+			<-time.After(180 * time.Second)
 		}
 	}()
 }
 
 func sendGameInfo() {
+	text := `
+东方汇抢庄牛牛积分房,每天 #排名前50 免费最高瓜分2000U,积分盈利可参与 #USDT兑换 (详情请看置顶规则)。下载APP,大于100流水赠送5U。分享下级,下级注册赠送1U,上级推广奖励0.5U
+
+<a href='https://t.me/FLLDFH'>每天500流水免费领取60U</a>
+
+#每晚22点万千红包雨
+	`
 	chatID := viper.GetInt64("chat_id")
 	textMsg := tgbotapi.MessageConfig{
 		BaseChat: tgbotapi.BaseChat{
 			ChatID: chatID,
 		},
-		Text:                  "东方汇看牌抢庄牛牛积分房,每天最高免费赠送2000U,详情请看置顶规则。下载APP,大于100流水赠送5U,每日500流水赠送60U。<a href='https://t.me/FLLDFH'>(东方汇百亿资产福利来旗下平台,公平公正、大额无忧。)</a>",
+		Text:                  text,
 		ParseMode:             "HTML",
 		DisableWebPagePreview: true,
 	}
@@ -56,29 +67,41 @@ func sendGameInfo() {
 	event2.MustFire(EventSendMsg, tgbotapi.Chattable(gifMsg))
 }
 
-func sendGameInfo2() {
-	chatID2 := int64(-1001632614756)
+func sendGameInfoWithDel(chatID int64, deleteMsgList *[]tgbotapi.DeleteMessageConfig, showBtn bool) {
+	caption := `
+东方汇抢庄牛牛积分房,每天 #排名前50 免费最高瓜分2000U,积分盈利可参与 #USDT兑换 (详情请看置顶规则)。下载APP,大于100流水赠送5U。分享下级,下级注册赠送1U,上级推广奖励0.5U
 
-	if len(deleteMsgList) == 2 {
-		MainBot.botApi.Send(deleteMsgList[0])
+<a href='https://t.me/FLLDFH'>每天500流水免费领取60U</a>
 
-		<-time.After(1 * time.Second)
+#每晚22点万千红包雨
+	`
+	//chatID2 := int64(-1001632614756)
 
-		MainBot.botApi.Send(deleteMsgList[1])
-	}
+	if len(*deleteMsgList) == 2 {
+		deleteList := make([]tgbotapi.DeleteMessageConfig, len(*deleteMsgList))
+		copy(deleteList, *deleteMsgList)
 
-	deleteMsgList = make([]tgbotapi.DeleteMessageConfig, 0)
+		defer func() {
+			MainBot.botApi.Send((deleteList)[0])
 
-	caption := `东方汇看牌抢庄牛牛积分房,每天最高免费赠送2000U,详情请看牛牛群置顶规则。下载APP,大于100流水赠送5U。分享下级,下级注册赠送1U,上级推广奖励0.5U
- 
+			<-time.After(1 * time.Second)
 
-<a href='https://t.me/FLLDFH'>每天500流水免费领取60U</a>
-`
+			MainBot.botApi.Send((deleteList)[1])
+		}()
+	}
+
+	*deleteMsgList = make([]tgbotapi.DeleteMessageConfig, 0)
+	var btn tgbotapi.ReplyKeyboardMarkup
+
+	if showBtn {
+		btn = cmd.GetServiceKeyboardBtn()
+	}
 
 	{
 		textMsg := tgbotapi.MessageConfig{
 			BaseChat: tgbotapi.BaseChat{
-				ChatID: chatID2,
+				ChatID:      chatID,
+				ReplyMarkup: btn,
 			},
 			Text:                  caption,
 			ParseMode:             "HTML",
@@ -91,7 +114,7 @@ func sendGameInfo2() {
 			return
 		}
 
-		deleteMsgList = append(deleteMsgList, tgbotapi.DeleteMessageConfig{
+		*deleteMsgList = append(*deleteMsgList, tgbotapi.DeleteMessageConfig{
 			ChatID:    resp.Chat.ID,
 			MessageID: resp.MessageID,
 		})
@@ -102,7 +125,7 @@ func sendGameInfo2() {
 	{
 		gifMsg := &tgbotapi.GameConfig{
 			BaseChat: tgbotapi.BaseChat{
-				ChatID:      chatID2,
+				ChatID:      chatID,
 				ReplyMarkup: GetGameReplyMarkup2(),
 			},
 			GameShortName: "qznn",
@@ -113,7 +136,7 @@ func sendGameInfo2() {
 			logrus.Error(err)
 			return
 		}
-		deleteMsgList = append(deleteMsgList, tgbotapi.DeleteMessageConfig{
+		*deleteMsgList = append(*deleteMsgList, tgbotapi.DeleteMessageConfig{
 			ChatID:    resp.Chat.ID,
 			MessageID: resp.MessageID,
 		})
@@ -132,6 +155,28 @@ func GetGameReplyMarkupSimple() tgbotapi.InlineKeyboardMarkup {
 	)
 }
 
+func GetGameReplyMarkupShare(shareStr string) tgbotapi.InlineKeyboardMarkup {
+	gameBtn := tgbotapi.NewInlineKeyboardButtonData("🎮进入游戏", "")
+	gameBtn.CallbackGame = &tgbotapi.CallbackGame{}
+	gameBtn.CallbackData = nil
+
+	shareBtn := tgbotapi.InlineKeyboardButton{
+		Text:              "👉点击分享",
+		SwitchInlineQuery: &shareStr,
+	}
+
+	// shareBtn := tgbotapi.NewInlineKeyboardButtonData("🥂分享游戏", "")
+	// shareBtnData := shareStr
+	// shareBtn.CallbackData = &shareBtnData
+
+	return tgbotapi.NewInlineKeyboardMarkup(
+		tgbotapi.NewInlineKeyboardRow(
+			gameBtn,
+			shareBtn,
+		),
+	)
+}
+
 func GetGameReplyMarkup() tgbotapi.InlineKeyboardMarkup {
 	gameBtn := tgbotapi.NewInlineKeyboardButtonData("🎮进入游戏", "")
 	gameBtn.CallbackGame = &tgbotapi.CallbackGame{}

+ 12 - 15
tg/tg.go

@@ -59,19 +59,16 @@ func (p *Bot) onSendMsgInner(e event2.Event[tgbotapi.Chattable], delete bool) er
 	logrus.Info("send finish:", resp)
 
 	if delete {
-		chatID2 := int64(-1001632614756)
-		if resp.Chat.ID == chatID2 {
-			go func() {
-				<-time.After(time.Duration(30) * time.Second)
-
-				deleteMsg := tgbotapi.DeleteMessageConfig{
-					ChatID:    resp.Chat.ID,
-					MessageID: resp.MessageID,
-				}
-
-				p.botApi.Send(deleteMsg)
-			}()
-		}
+		go func() {
+			<-time.After(time.Duration(30) * time.Second)
+
+			deleteMsg := tgbotapi.DeleteMessageConfig{
+				ChatID:    resp.Chat.ID,
+				MessageID: resp.MessageID,
+			}
+
+			p.botApi.Send(deleteMsg)
+		}()
 	}
 
 	return nil
@@ -96,7 +93,7 @@ func (p *Bot) InitDebug(token string) error {
 	hookCfg := tgbotapi.NewUpdate(0)
 
 	hookCfg.AllowedUpdates = []string{tgbotapi.UpdateTypeChatMember, tgbotapi.UpdateTypeMessage,
-		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery}
+		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery, tgbotapi.UpdateTypeChosenInlineResult}
 
 	bot.Debug = true
 
@@ -130,7 +127,7 @@ func (p *Bot) Init(token string) error {
 	}
 
 	hookCfg.AllowedUpdates = []string{tgbotapi.UpdateTypeChatMember, tgbotapi.UpdateTypeMessage,
-		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery}
+		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery, tgbotapi.UpdateTypeChosenInlineResult}
 
 	_, err = bot.Request(hookCfg)
 	if err != nil {

+ 7 - 0
tgadtool/const_data.go

@@ -0,0 +1,7 @@
+package tgadtool
+
+const (
+	EventUpdateMsg  string = "event_update_msg"
+	EventSendMsg    string = "event_send_msg"
+	EventSendMsgFix string = "event_send_msg_fix"
+)

+ 501 - 0
tgadtool/gen_msg.go

@@ -0,0 +1,501 @@
+package tgadtool
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+	"time"
+
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/parse"
+	"nn.daxia.dev/model"
+)
+
+type MsgFormat interface {
+	GetType() string
+}
+
+type MsgFormatBase struct {
+	Name string   `json:"名称"`
+	Type string   `json:"类型"`
+	Btn1 []string `json:"按钮1"`
+	Btn2 []string `json:"按钮2"`
+	Btn3 []string `json:"按钮3"`
+	Btn4 []string `json:"按钮4"`
+}
+
+type MsgFormatText struct {
+	MsgFormatBase
+	Text string `json:"文字"`
+}
+
+func (p *MsgFormatText) GetType() string {
+	return p.Type
+}
+
+type MsgFormatImg struct {
+	MsgFormatBase
+	ImgUrl string `json:"图片地址"`
+	Text   string `json:"文字"`
+}
+
+func (p *MsgFormatImg) GetType() string {
+	return p.Type
+}
+
+type MsgFormatGif struct {
+	MsgFormatBase
+	ImgUrl string `json:"图片地址"`
+	Text   string `json:"文字"`
+}
+
+func (p *MsgFormatGif) GetType() string {
+	return p.Type
+}
+
+func StartGen() {
+	event2.On[tgbotapi.Update](EventUpdateMsg, event2.ListenerFunc[tgbotapi.Update](onUpdate))
+	go StartTimer()
+}
+
+func StartTimer() {
+	for {
+		<-time.After(3 * time.Second)
+
+		timerModelList := make([]model.Timer, 0)
+		err := db.GetDB().Model(model.Timer{}).Where("last_timestamp + `interval` < ?", time.Now().Unix()).Scan(&timerModelList).Error
+		if err != nil {
+			logrus.Error(err)
+			continue
+		}
+
+		for _, timerModel := range timerModelList {
+			adMsgModel := model.Admsg{}
+			err = db.GetDB().Model(model.Admsg{}).Where("name = ?", timerModel.AdMsgName).First(&adMsgModel).Error
+			if err != nil {
+				logrus.Error(err)
+
+				continue
+			}
+
+			err = db.GetDB().Model(model.Timer{}).Where("id = ?", timerModel.ID).Update("last_timestamp", time.Now().Unix()).Error
+			if err != nil {
+				logrus.Error(err)
+
+				continue
+			}
+
+			jsonStr := adMsgModel.Json
+			chattable, err := GenMsg(0, timerModel.ChatID, jsonStr, false)
+			if err != nil {
+				logrus.Error(err)
+				continue
+			}
+
+			MainBot.botApi.Send(chattable)
+			<-time.After(time.Second)
+		}
+	}
+}
+
+//按json,生成消息,输出编号
+func GenMsg(tgID, chatID int64, jsonStr string, save bool) (tgbotapi.Chattable, error) {
+	var err error
+
+	msgFormat := MsgFormatBase{}
+	logrus.Info("jsonStr:", jsonStr)
+	err = json.Unmarshal([]byte(jsonStr), &msgFormat)
+	if err != nil {
+		logrus.Error(err)
+		return nil, err
+	}
+
+	msgFormat.Name = strings.TrimSpace(msgFormat.Name)
+	if msgFormat.Name == "" {
+		return nil, errors.New("名称不能为空")
+	}
+
+	chattable, err := JsonToChattable(chatID, jsonStr)
+	if err != nil {
+		logrus.Error(err)
+		return nil, err
+	}
+
+	if save {
+		count := int64(0)
+		err = db.GetDB().Model(model.Admsg{}).Where("name = ?", msgFormat.Name).Count(&count).Error
+		if err != nil {
+			logrus.Error(err)
+
+			return nil, err
+		}
+
+		if count != 0 {
+			return nil, errors.New("名称已经存在")
+		}
+
+		err = db.GetDB().Model(model.Admsg{}).Create(&model.Admsg{
+			Name: msgFormat.Name,
+			Json: jsonStr,
+		}).Error
+		if err != nil {
+			logrus.Error(err)
+			return nil, err
+		}
+	}
+
+	return chattable, nil
+}
+
+func JsonToChattable(chatID int64, jsonStr string) (tgbotapi.Chattable, error) {
+	var err error
+
+	msgFormat := MsgFormatBase{}
+	logrus.Info("jsonStr:", jsonStr)
+	err = json.Unmarshal([]byte(jsonStr), &msgFormat)
+	if err != nil {
+		logrus.Error(err)
+		return nil, err
+	}
+
+	obj := tgbotapi.NewInlineKeyboardMarkup()
+	btnListWrap := &obj
+	btnList, err := parseBtnList(msgFormat.Btn1)
+	if err == nil && len(btnList) != 0 {
+		btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+	}
+
+	btnList, err = parseBtnList(msgFormat.Btn2)
+	if err == nil && len(btnList) != 0 {
+		btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+	}
+
+	btnList, err = parseBtnList(msgFormat.Btn3)
+	if err == nil && len(btnList) != 0 {
+		btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+	}
+
+	btnList, err = parseBtnList(msgFormat.Btn4)
+	if err == nil && len(btnList) != 0 {
+		btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+	}
+
+	if len(btnListWrap.InlineKeyboard) == 0 {
+		btnListWrap = nil
+	}
+
+	logrus.Info("msgFormat:", msgFormat)
+	if msgFormat.Type == "文字" {
+		msgFormatText := MsgFormatText{}
+		err = json.Unmarshal([]byte(jsonStr), &msgFormatText)
+		if err != nil {
+			logrus.Error(err)
+			return nil, err
+		}
+
+		msgFormatText.Text = strings.TrimSpace(msgFormatText.Text)
+		if msgFormatText.Text == "" {
+			return nil, errors.New("消息内容不能为空")
+		}
+
+		msgConfig := tgbotapi.MessageConfig{
+			BaseChat: tgbotapi.BaseChat{
+				ChatID:      chatID,
+				ReplyMarkup: btnListWrap,
+			},
+			Text:                  msgFormatText.Text,
+			ParseMode:             "MARKDOWN",
+			DisableWebPagePreview: true,
+		}
+
+		return msgConfig, nil
+	}
+
+	if msgFormat.Type == "图片" {
+		msgFormatImg := MsgFormatImg{}
+		err = json.Unmarshal([]byte(jsonStr), &msgFormatImg)
+		if err != nil {
+			logrus.Error(err)
+			return nil, err
+		}
+
+		msgFormatImg.Text = strings.TrimSpace(msgFormatImg.Text)
+		if msgFormatImg.Text == "" {
+			return nil, errors.New("消息内容不能为空")
+		}
+
+		msgConfig := tgbotapi.PhotoConfig{
+			BaseFile: tgbotapi.BaseFile{
+				BaseChat: tgbotapi.BaseChat{
+					ChatID:      chatID,
+					ReplyMarkup: btnListWrap,
+				},
+				File: tgbotapi.FileURL(msgFormatImg.ImgUrl),
+			},
+			Thumb:     tgbotapi.FileURL(msgFormatImg.ImgUrl),
+			Caption:   msgFormatImg.Text,
+			ParseMode: "MARKDOWN",
+		}
+
+		return msgConfig, nil
+	}
+
+	if msgFormat.Type == "动图" {
+		msgFormatGif := MsgFormatGif{}
+		err = json.Unmarshal([]byte(jsonStr), &msgFormatGif)
+		if err != nil {
+			logrus.Error(err)
+			return nil, err
+		}
+
+		msgFormatGif.Text = strings.TrimSpace(msgFormatGif.Text)
+		if msgFormatGif.Text == "" {
+			return nil, errors.New("消息内容不能为空")
+		}
+
+		msgConfig := tgbotapi.AnimationConfig{
+			BaseFile: tgbotapi.BaseFile{
+				BaseChat: tgbotapi.BaseChat{
+					ChatID:      chatID,
+					ReplyMarkup: btnListWrap,
+				},
+				File: tgbotapi.FileURL(msgFormatGif.ImgUrl),
+			},
+			Thumb:     tgbotapi.FileURL(msgFormatGif.ImgUrl),
+			Caption:   msgFormatGif.Text,
+			ParseMode: "MARKDOWN",
+		}
+
+		return msgConfig, nil
+
+	}
+	return nil, errors.New("找不到类型")
+}
+
+func onUpdate(e event2.Event[tgbotapi.Update]) error {
+	var err error
+
+	update := e.Data()
+	logrus.Info(update)
+
+	if update.Message == nil {
+		return nil
+	}
+
+	chatID := update.Message.Chat.ID
+
+	msgText := update.Message.Text
+	if msgText == "列表" {
+		adMsgListModel := make([]model.Admsg, 0)
+		err = db.GetDB().Model(model.Admsg{}).Scan(&adMsgListModel).Error
+		if err != nil {
+			logrus.Error(err)
+			SendSimpleMsg(update.Message.Chat.ID, err.Error())
+
+			return err
+		}
+
+		msg := "列表\n========================\n"
+		for index, item := range adMsgListModel {
+			msg += fmt.Sprintf("%d. %s\n", index+1, item.Name)
+		}
+
+		msgConfig := tgbotapi.MessageConfig{
+			BaseChat: tgbotapi.BaseChat{
+				ChatID: chatID,
+			},
+			Text:                  msg,
+			ParseMode:             "MARKDOWN",
+			DisableWebPagePreview: true,
+		}
+
+		MainBot.botApi.Send(msgConfig)
+		return nil
+	}
+
+	if strings.HasPrefix(msgText, "删除 ") {
+		deleteListStr := strings.Split(msgText, " ")
+		if len(deleteListStr) != 2 {
+			SendSimpleMsg(chatID, "格式错误: 删除 名字")
+			return nil
+		}
+
+		err = db.GetDB().Model(model.Admsg{}).Where("name = ?", deleteListStr[1]).Delete(model.Admsg{}).Error
+		if err != nil {
+			logrus.Error(err)
+			SendSimpleMsg(update.Message.Chat.ID, err.Error())
+
+			return err
+		}
+
+		SendSimpleMsg(chatID, "删除成功")
+		return nil
+	}
+
+	if strings.HasPrefix(msgText, "定时显示 ") {
+		//定时显示 定时1,180,测试5
+		timeerListStr := strings.Split(msgText, " ")
+		if len(timeerListStr) != 2 {
+			SendSimpleMsg(chatID, "格式错误: 定时显示 名字,时间,消息名称")
+			return nil
+		}
+
+		parameterListStr := strings.Split(timeerListStr[1], ",")
+		if len(parameterListStr) != 3 {
+			SendSimpleMsg(chatID, "格式错误: 定时显示 名字,时间,消息名称")
+			return nil
+		}
+
+		adMsgName := parameterListStr[2]
+
+		count := int64(0)
+		err = db.GetDB().Model(model.Admsg{}).Where("name = ?", adMsgName).Count(&count).Error
+		if err != nil {
+			logrus.Error(err)
+
+			return err
+		}
+
+		if count == 0 {
+			return errors.New("名称不存在")
+		}
+
+		err = db.GetDB().Model(model.Timer{}).Create(&model.Timer{
+			Name:          parameterListStr[0],
+			ChatID:        chatID,
+			LastTimestamp: 0,
+			Interval:      parse.StringToInt64(parameterListStr[1]),
+			AdMsgName:     adMsgName,
+		}).Error
+		if err != nil {
+			logrus.Error(err)
+			SendSimpleMsg(update.Message.Chat.ID, err.Error())
+
+			return err
+		}
+
+		SendSimpleMsg(chatID, "设置成功")
+		return nil
+	}
+
+	if strings.HasPrefix(msgText, "查看 ") {
+		deleteListStr := strings.Split(msgText, " ")
+		if len(deleteListStr) != 2 {
+			SendSimpleMsg(chatID, "格式错误: 查看 名字")
+			return nil
+		}
+
+		adMsgModel := model.Admsg{}
+		err = db.GetDB().Model(model.Admsg{}).Where("name = ?", deleteListStr[1]).First(&adMsgModel).Error
+		if err != nil {
+			logrus.Error(err)
+			SendSimpleMsg(update.Message.Chat.ID, err.Error())
+
+			return err
+		}
+
+		jsonStr := adMsgModel.Json
+		chattable, err := GenMsg(update.Message.From.ID, update.Message.Chat.ID, jsonStr, false)
+		if err != nil {
+			logrus.Error(err)
+			SendSimpleMsg(update.Message.Chat.ID, err.Error())
+
+			return err
+		}
+
+		logrus.Info("view msg:", chattable)
+
+		_, err = MainBot.botApi.Send(chattable)
+		if err != nil {
+			logrus.Error(err)
+			SendSimpleMsg(update.Message.Chat.ID, err.Error())
+
+			return err
+		}
+		return nil
+	}
+
+	if strings.HasPrefix(msgText, "生成") {
+		msgText = strings.ReplaceAll(msgText, "”", `"`)
+		msgText = strings.ReplaceAll(msgText, "“", `"`)
+		msgText = strings.ReplaceAll(msgText, ":", `:`)
+		msgText = strings.ReplaceAll(msgText, ",", ",")
+
+		jsonStr := strings.Replace(msgText, "生成", "", -1)
+		chattable, err := GenMsg(update.Message.From.ID, update.Message.Chat.ID, jsonStr, true)
+		if err != nil {
+			logrus.Error(err)
+			SendSimpleMsg(update.Message.Chat.ID, err.Error())
+
+			return err
+		}
+
+		logrus.Info("gen msg:", chattable)
+
+		MainBot.botApi.Send(chattable)
+	}
+
+	return nil
+}
+
+func SendSimpleMsg(chatID int64, msg string) {
+	logrus.Info("SendErrorMsg:", msg)
+
+	msgConfig := tgbotapi.MessageConfig{
+		BaseChat: tgbotapi.BaseChat{
+			ChatID: chatID,
+		},
+		Text:                  msg,
+		ParseMode:             "MARKDOWN",
+		DisableWebPagePreview: true,
+	}
+
+	MainBot.botApi.Send(msgConfig)
+}
+
+func parseBtnList(btnListStr []string) ([]tgbotapi.InlineKeyboardButton, error) {
+	if btnListStr == nil {
+		return nil, errors.New("不存在按钮")
+	}
+
+	resultBtn := make([]tgbotapi.InlineKeyboardButton, 0)
+	for _, item := range btnListStr {
+		btnListStr := strings.Split(item, ",")
+		if len(btnListStr) == 0 {
+			return nil, errors.New("参数错误")
+		}
+
+		name := btnListStr[0]
+		if name == "地址按钮" {
+			if len(btnListStr) != 3 {
+				return nil, errors.New("地址按钮参数错误")
+			}
+
+			displayName := btnListStr[1]
+			jumpUrl := btnListStr[2]
+			resultBtn = append(resultBtn, tgbotapi.NewInlineKeyboardButtonURL(displayName, jumpUrl))
+		}
+
+		if name == "分享按钮" {
+			if len(btnListStr) != 3 {
+				return nil, errors.New("分享按钮参数错误")
+			}
+
+			displayName := btnListStr[1]
+			shareStr := btnListStr[2]
+
+			shareBtn := tgbotapi.InlineKeyboardButton{
+				Text:              displayName,
+				SwitchInlineQuery: &shareStr,
+			}
+
+			resultBtn = append(resultBtn, shareBtn)
+		}
+	}
+
+	return resultBtn, nil
+}

+ 40 - 0
tgadtool/gen_msg_test.go

@@ -0,0 +1,40 @@
+package tgadtool
+
+import (
+	"testing"
+
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+func TestJsonToChattable(t *testing.T) {
+	type args struct {
+		jsonStr string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    tgbotapi.Chattable
+		wantErr bool
+	}{
+		{
+			name: "",
+			args: args{
+				jsonStr: `
+{
+	"text": "生成"
+}
+				`,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_, err := JsonToChattable(tt.args.jsonStr)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("JsonToChattable() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+		})
+	}
+}

+ 112 - 0
tgadtool/share.go

@@ -0,0 +1,112 @@
+package tgadtool
+
+import (
+	"encoding/json"
+	"errors"
+	"strings"
+
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/guid"
+	"nn.daxia.dev/model"
+)
+
+func StartShare() {
+	event2.On[tgbotapi.Update](EventUpdateMsg, event2.ListenerFunc[tgbotapi.Update](onUpdateShare))
+}
+
+func onUpdateShare(e event2.Event[tgbotapi.Update]) error {
+	var err error
+
+	update := e.Data()
+
+	if update.InlineQuery == nil && update.ChosenInlineResult == nil {
+		return nil
+	}
+
+	logrus.Info(update)
+
+	if update.InlineQuery != nil {
+		queryStr := strings.TrimSpace(update.InlineQuery.Query)
+
+		adMsgModel := model.Admsg{}
+		err = db.GetDB().Model(model.Admsg{}).Where("name = ?", queryStr).First(&adMsgModel).Error
+		if err != nil {
+			logrus.Error(err)
+
+			return err
+		}
+
+		jsonStr := adMsgModel.Json
+		msgFormat := MsgFormatBase{}
+		logrus.Info("jsonStr:", jsonStr)
+		err = json.Unmarshal([]byte(jsonStr), &msgFormat)
+		if err != nil {
+			logrus.Error(err)
+			return err
+		}
+
+		btnListWrap := tgbotapi.NewInlineKeyboardMarkup()
+		btnList, err := parseBtnList(msgFormat.Btn1)
+		if err == nil && len(btnList) != 0 {
+			btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+		}
+
+		btnList, err = parseBtnList(msgFormat.Btn2)
+		if err == nil && len(btnList) != 0 {
+			btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+		}
+
+		btnList, err = parseBtnList(msgFormat.Btn3)
+		if err == nil && len(btnList) != 0 {
+			btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+		}
+
+		btnList, err = parseBtnList(msgFormat.Btn4)
+		if err == nil && len(btnList) != 0 {
+			btnListWrap.InlineKeyboard = append(btnListWrap.InlineKeyboard, btnList)
+		}
+
+		if msgFormat.Type == "动图" {
+			msgFormatGif := MsgFormatGif{}
+			err = json.Unmarshal([]byte(jsonStr), &msgFormatGif)
+			if err != nil {
+				logrus.Error(err)
+				return err
+			}
+
+			msgFormatGif.Text = strings.TrimSpace(msgFormatGif.Text)
+			if msgFormatGif.Text == "" {
+				return errors.New("消息内容不能为空")
+			}
+
+			textMsg := &tgbotapi.InlineConfig{
+				InlineQueryID: update.InlineQuery.ID,
+				Results: []interface{}{
+					tgbotapi.InlineQueryResultMPEG4GIF{
+						Type:        "mpeg4_gif",
+						ID:          "id_" + guid.GenNoSplit() + "_" + queryStr,
+						Title:       adMsgModel.Name,
+						Caption:     msgFormatGif.Text,
+						URL:         msgFormatGif.ImgUrl,
+						ThumbURL:    msgFormatGif.ImgUrl,
+						ReplyMarkup: &btnListWrap,
+						ParseMode:   "MARKDOWN",
+					},
+				},
+			}
+
+			_, err = MainBot.botApi.Send(textMsg)
+			if err != nil {
+				logrus.Error(err)
+
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}

+ 27 - 0
tgadtool/test.data

@@ -0,0 +1,27 @@
+生成
+{
+    "类型": "图片",
+    "文字": "白纸🐬.(招号商),东方汇抢庄牛牛积分房,每天 #排名前50 免费最高瓜分2000U,积分盈利可参与 #USDT兑换 (详情请看置顶规则)。下载APP,大于100流水赠送5U。分享下级,下级注册赠送1U,上级推广奖励0.5U \n\n[每天500流水免费领取60U](https://t.me/FLLDFH)\n\n#每晚22点万千红包雨",
+    "图片地址": "https://i.ibb.co/C5Z9ygj/img-qznn-mfc.png"
+}
+
+生成
+{
+    "名称": "测试",
+    "类型": "动图",
+    "文字": "白纸🐬.(招号商),东方汇抢庄牛牛积分房,每天 #排名前50 免费最高瓜分2000U,积分盈利可参与 #USDT兑换 (详情请看置顶规则)。下载APP,大于100流水赠送5U。分享下级,下级注册赠送1U,上级推广奖励0.5U \n\n[每天500流水免费领取60U](https://t.me/FLLDFH)\n\n#每晚22点万千红包雨",
+    "图片地址": "https://ccc-oss.oss-accelerate.aliyuncs.com/196G/img/img_stop.mp4",
+    "按钮1":["地址按钮,上下分群,https://t.me/FLLDFH", "分享按钮,分享领取积分"],
+    "按钮2":["地址按钮,上下分群2,https://t.me/FLLDFH", "分享按钮,分享领取积分2"]
+}
+
+
+生成
+{
+    "名称": "测试红包6",
+    "类型": "动图",
+    "文字": "兄弟快来帮我砍一刀,积分抢庄牛牛,注册领1U,排行榜奖励前50名瓜分2000U,22点红包雨。\n\n[* 每天500流水免费领取60U](https://t.me/FLLDFH)\n\n[* 牛牛排行榜积分瓜分2000U](https://t.me/FLLDFH)\n\n",
+    "图片地址": "https://ccc-oss.oss-accelerate.aliyuncs.com/196G/img/img_stop.mp4",
+    "按钮1":["地址按钮,砍一刀,https://t.me/FLLDFH", "地址按钮,牛牛红包群,https://t.me/FLLDFH"]
+}
+

+ 166 - 0
tgadtool/tgadtool.go

@@ -0,0 +1,166 @@
+package tgadtool
+
+import (
+	"encoding/json"
+	"time"
+
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
+)
+
+var MainBot *Bot
+
+func Start() {
+	MainBot = NewBot()
+
+	event2.On[tgbotapi.Chattable](EventSendMsg, event2.ListenerFunc[tgbotapi.Chattable](MainBot.onSendMsg))
+
+	go func() {
+		StartGen()
+		StartShare()
+	}()
+
+	botToken := viper.GetString("bot_token")
+	logrus.Fatal(MainBot.InitDebug(botToken))
+}
+
+type Bot struct {
+	botApi        *tgbotapi.BotAPI
+	updateChannel tgbotapi.UpdatesChannel
+}
+
+func NewBot() *Bot {
+	return &Bot{}
+}
+
+func (p *Bot) onSendMsg(e event2.Event[tgbotapi.Chattable]) error {
+	return p.onSendMsgInner(e, true)
+}
+
+func (p *Bot) onSendMsgFix(e event2.Event[tgbotapi.Chattable]) error {
+	return p.onSendMsgInner(e, false)
+}
+
+func (p *Bot) onSendMsgInner(e event2.Event[tgbotapi.Chattable], delete bool) error {
+	logrus.Info("onSendMsg:", e)
+	c := e.Data()
+
+	resp, err := p.botApi.Send(c)
+	if err != nil {
+		logrus.Error(err)
+		return nil
+	}
+
+	logrus.Info("send finish:", resp)
+
+	if delete {
+		go func() {
+			<-time.After(time.Duration(30) * time.Second)
+
+			deleteMsg := tgbotapi.DeleteMessageConfig{
+				ChatID:    resp.Chat.ID,
+				MessageID: resp.MessageID,
+			}
+
+			p.botApi.Send(deleteMsg)
+		}()
+	}
+
+	return nil
+}
+
+func (p *Bot) InitDebug(token string) error {
+	bot, err := tgbotapi.NewBotAPI(token)
+	if err != nil {
+		logrus.Fatal(err)
+		return err
+	}
+
+	bot.Debug = true
+
+	logrus.Infof("Authorized on account %s", bot.Self.UserName)
+	delWebhookConfig := tgbotapi.DeleteWebhookConfig{
+		DropPendingUpdates: true,
+	}
+
+	bot.Send(delWebhookConfig)
+
+	hookCfg := tgbotapi.NewUpdate(0)
+
+	hookCfg.AllowedUpdates = []string{tgbotapi.UpdateTypeChatMember, tgbotapi.UpdateTypeMessage,
+		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery, tgbotapi.UpdateTypeChosenInlineResult}
+
+	bot.Debug = true
+
+	p.botApi = bot
+	p.updateChannel = bot.GetUpdatesChan(hookCfg)
+
+	p.ProcNewMessage()
+	return nil
+}
+
+func (p *Bot) Init(token string) error {
+	bot, err := tgbotapi.NewBotAPI(token)
+	if err != nil {
+		logrus.Fatal(err)
+		return err
+	}
+
+	bot.Debug = false
+
+	logrus.Infof("Authorized on account %s", bot.Self.UserName)
+
+	basePath := viper.GetString("base_path") // "/api/botzhenren"
+	fullPath := "http://13.214.75.66:1180" + basePath
+	if !viper.GetBool("is_debug") {
+		fullPath = "https://prodcpbot.daxia.dev" + basePath
+	}
+
+	hookCfg, err := tgbotapi.NewWebhook(fullPath)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	hookCfg.AllowedUpdates = []string{tgbotapi.UpdateTypeChatMember, tgbotapi.UpdateTypeMessage,
+		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery, tgbotapi.UpdateTypeChosenInlineResult}
+
+	_, err = bot.Request(hookCfg)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	info, err := bot.GetWebhookInfo()
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	if info.LastErrorDate != 0 {
+		logrus.Printf("Telegram callback failed: %s", info.LastErrorMessage)
+	}
+
+	p.updateChannel = bot.ListenForWebhook(basePath)
+
+	p.botApi = bot
+	p.ProcNewMessage()
+
+	return nil
+}
+
+func (p *Bot) GetBotApi() *tgbotapi.BotAPI {
+	return p.botApi
+}
+
+func (p *Bot) ProcNewMessage() {
+	if p.updateChannel == nil {
+		logrus.Error("updata channel is nil")
+		return
+	}
+
+	for update := range p.updateChannel {
+		updateJson, _ := json.Marshal(update)
+		logrus.Info("recv update msg:", string(updateJson))
+		go event2.MustFire(EventUpdateMsg, update)
+	}
+}

+ 7 - 0
tgkk/const_data.go

@@ -0,0 +1,7 @@
+package tgkk
+
+const (
+	EventUpdateMsg  string = "event_update_msg"
+	EventSendMsg    string = "event_send_msg"
+	EventSendMsgFix string = "event_send_msg_fix"
+)

+ 289 - 0
tgkk/msg.go

@@ -0,0 +1,289 @@
+package tgkk
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"github.com/shopspring/decimal"
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/rdb"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/time_utils"
+	"gorm.io/gorm"
+	"nn.daxia.dev/model"
+)
+
+type UpdateMsgInfo struct {
+	Locker sync.Mutex
+	ChatID int64
+	MsgID  int64
+}
+
+var updateMsgInfo UpdateMsgInfo
+
+func StartHandleMsg() {
+	updateMsgInfo = UpdateMsgInfo{
+		Locker: sync.Mutex{},
+		ChatID: 0,
+		MsgID:  0,
+	}
+
+	key := "xiaojin_update"
+	data, err := rdb.GetRDB().Get(context.Background(), key).Result()
+	if err == nil {
+		err = json.Unmarshal([]byte(data), &updateMsgInfo)
+		if err != nil {
+			logrus.Fatal(err)
+		}
+
+		updateMsgInfo.Locker = sync.Mutex{}
+	}
+
+	event2.On[tgbotapi.Update](EventUpdateMsg, event2.ListenerFunc[tgbotapi.Update](onUpdate))
+	go updateStatics()
+	go updateCredits()
+}
+
+func updateCredits() {
+	//
+}
+
+func updateStatics() {
+	for {
+		<-time.After(4 * time.Second)
+		updateMsgInfoTmp := UpdateMsgInfo{}
+		updateMsgInfo.Locker.Lock()
+		{
+			updateMsgInfoTmp.ChatID = updateMsgInfo.ChatID
+			updateMsgInfoTmp.MsgID = updateMsgInfo.MsgID
+		}
+		updateMsgInfo.Locker.Unlock()
+
+		rate, err := GetTodaySuccessRate()
+		if err != nil {
+			logrus.Error(err)
+			continue
+		}
+
+		dayBillModel := model.DayBill{}
+		err = db.GetDB().Model(model.DayBill{}).Where("day_timestamp = ?", rate.Data.TodayTimeSecond).First(&dayBillModel).Error
+		if err == gorm.ErrRecordNotFound {
+			timeNow := time.Unix(rate.Data.TodayTimeSecond, 0).In(time_utils.GetCNZone())
+			yesterdayAmount, _ := decimal.NewFromString(rate.Data.YesterAmount)
+
+			err = db.GetDB().Model(model.DayBill{}).Create(&model.DayBill{
+				DayTimestamp: int64(rate.Data.TodayTimeSecond),
+				DayStr:       time_utils.GetFormatTime(timeNow),
+				YesterAmount: yesterdayAmount,
+				NewCredits:   decimal.NewFromInt(0),
+				IsSet:        0,
+			}).Error
+			if err != nil {
+				logrus.Error(err)
+				continue
+			}
+
+			continue
+		}
+
+		if err != nil {
+			logrus.Error(err)
+			continue
+		}
+
+		if updateMsgInfoTmp.ChatID == 0 {
+			continue
+		}
+
+		timeNow := time_utils.TimeNowInCN()
+
+		ratePercent := decimal.NewFromInt(0)
+		if rate.Data.TodayCount15 != 0 {
+			ratePercent = decimal.NewFromInt(int64(rate.Data.TodaySuccCount15 * 100)).Div(decimal.NewFromInt32(int32(rate.Data.TodayCount15)))
+		}
+
+		msg := fmt.Sprintf("[%d:%d]今天:%s 昨天:%s 订单:%d/%d 15:%d/%d=%s%%", timeNow.Hour(), timeNow.Minute(), rate.Data.TotalAmount, rate.Data.YesterAmount,
+			rate.Data.TodaySuccessCount, rate.Data.TodayCount, rate.Data.TodaySuccCount15, rate.Data.TodayCount15, ratePercent.StringFixed(2))
+
+		logrus.Info("update edit msg", msg)
+
+		msgConfig := tgbotapi.EditMessageTextConfig{
+			BaseEdit: tgbotapi.BaseEdit{
+				ChatID:    updateMsgInfoTmp.ChatID,
+				MessageID: int(updateMsgInfoTmp.MsgID),
+			},
+			Text:                  msg,
+			ParseMode:             "HTML",
+			Entities:              []tgbotapi.MessageEntity{},
+			DisableWebPagePreview: true,
+		}
+
+		_, err = MainBot.botApi.Send(msgConfig)
+		if err != nil {
+			logrus.Error(err)
+			continue
+		}
+
+	}
+}
+
+func onUpdate(e event2.Event[tgbotapi.Update]) error {
+	update := e.Data()
+	logrus.Info(update)
+
+	if update.Message == nil {
+		return nil
+	}
+
+	chatID := update.Message.Chat.ID
+
+	msgText := update.Message.Text
+
+	if msgText == "event" {
+		rate, err := GetTodaySuccessRate()
+
+		if err != nil {
+			SendSimpleMsg(chatID, err.Error())
+			return nil
+		}
+
+		timeNow := time_utils.TimeNowInCN()
+		ratePercent := decimal.NewFromInt(int64(rate.Data.TodaySuccCount15 * 100)).Div(decimal.NewFromInt32(int32(rate.Data.TodayCount15)))
+		msg := fmt.Sprintf("[%d:%d]今天:%s 昨天:%s 订单:%d/%d 15:%d/%d=%s%%", timeNow.Hour(), timeNow.Minute(), rate.Data.TotalAmount, rate.Data.YesterAmount,
+			rate.Data.TodaySuccessCount, rate.Data.TodayCount, rate.Data.TodaySuccCount15, rate.Data.TodayCount15, ratePercent.StringFixed(2))
+		resp, err := SendSimpleMsg(chatID, msg)
+		if err == nil {
+			var updateStr []byte
+			updateMsgInfo.Locker.Lock()
+			{
+				updateMsgInfo.ChatID = chatID
+				updateMsgInfo.MsgID = int64(resp.MessageID)
+				updateStr, _ = json.Marshal(updateMsgInfo)
+			}
+
+			updateMsgInfo.Locker.Unlock()
+
+			key := "xiaojin_update"
+			_, err := rdb.GetRDB().Set(context.Background(), key, updateStr, time.Hour*48).Result()
+			if err != nil {
+				logrus.Error(err)
+				return nil
+			}
+		}
+		return nil
+	}
+
+	if strings.HasPrefix(msgText, "设置分数 ") {
+		logrus.Info("设置分数")
+		msgList := strings.Split(msgText, " ")
+		if len(msgList) != 2 {
+			SendSimpleMsg(chatID, "格式错误,应该为:设置分数 1234")
+			return nil
+		}
+
+		credits, err := strconv.Atoi(msgList[1])
+		if err != nil {
+			SendSimpleMsg(chatID, err.Error())
+			return nil
+		}
+
+		err = db.GetDB().Model(model.DayBill{}).Where("day_timestamp = ?", time_utils.GetZeroTime(time_utils.TimeNowInCN()).Unix()).
+			Updates(map[string]interface{}{
+				"new_credits": credits,
+				"is_set":      1,
+			}).Error
+		if err != nil {
+			SendSimpleMsg(chatID, "设置失败")
+			logrus.Error(err)
+			return nil
+		}
+
+		msgConfig := tgbotapi.MessageConfig{
+			BaseChat: tgbotapi.BaseChat{
+				ChatID:           chatID,
+				ReplyToMessageID: update.Message.MessageID,
+			},
+			Text:                  "设置成功",
+			ParseMode:             "HTML",
+			DisableWebPagePreview: true,
+		}
+
+		MainBot.botApi.Send(msgConfig)
+		return nil
+	}
+
+	if msgText == "查看分数" {
+		msg, err := GetTotalCreditsMsg()
+		if err != nil {
+			SendSimpleMsg(chatID, err.Error())
+			logrus.Error(err)
+			return nil
+		}
+
+		SendSimpleMsg(chatID, msg)
+	}
+
+	return nil
+}
+
+func SendSimpleMsg(chatID int64, msg string) (tgbotapi.Message, error) {
+	logrus.Info("SendErrorMsg:", msg)
+
+	msgConfig := tgbotapi.MessageConfig{
+		BaseChat: tgbotapi.BaseChat{
+			ChatID: chatID,
+		},
+		Text:                  msg,
+		ParseMode:             "HTML",
+		DisableWebPagePreview: true,
+	}
+
+	return MainBot.botApi.Send(msgConfig)
+}
+
+func GetTotalCreditsMsg() (string, error) {
+	baseAmount := decimal.NewFromInt(0)
+	hideAmount := decimal.NewFromInt(10000)
+	dayBillModel := model.DayBill{}
+	resetTime := "-"
+	err := db.GetDB().Model(model.DayBill{}).Where("is_set = 1").Order("id desc").Limit(1).First(&dayBillModel).Error
+	if err != nil && err != gorm.ErrRecordNotFound {
+		//SendSimpleMsg(chatID, "内部错误")
+		logrus.Error(err)
+		return "", err
+	}
+
+	if err != gorm.ErrRecordNotFound {
+		baseAmount = dayBillModel.NewCredits
+		resetTime = dayBillModel.DayStr
+	}
+
+	type SumAmount struct {
+		SumAmount decimal.Decimal
+	}
+	sumAmount := SumAmount{}
+
+	err = db.GetDB().Model(model.DayBill{}).Where("id > ?", dayBillModel.ID).
+		Select("sum(yester_amount) sum_amount").First(&sumAmount).Error
+
+	if err != nil {
+		//SendSimpleMsg(chatID, "内部错误(201)")
+		logrus.Error(err)
+		return "", err
+	}
+
+	msg := fmt.Sprintf(`
+分数: %s
+上次重置时间: %s
+上次重置积分: %s
+		`, baseAmount.Add(sumAmount.SumAmount).Div(hideAmount).StringFixed(2), resetTime, baseAmount.Div(hideAmount).StringFixed(2))
+
+	return msg, nil
+}

+ 39 - 0
tgkk/rate.go

@@ -0,0 +1,39 @@
+package tgkk
+
+import (
+	"encoding/json"
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/webutils"
+)
+
+type TodayRate struct {
+	Status bool `json:"status"`
+	Data   struct {
+		TodayTimeSecond   int64  `json:"today_time_second"`
+		TotalAmount       string `json:"total_amount"`
+		YesterAmount      string `json:"yester_amount"`
+		TodaySuccessCount int    `json:"today_success_count"`
+		TodayCount        int    `json:"today_count"`
+		TodaySuccCount15  int    `json:"today_succ_count15"`
+		TodayCount15      int    `json:"today_count15"`
+	} `json:"data"`
+}
+
+func GetTodaySuccessRate() (*TodayRate, error) {
+	respData, err := webutils.Get("http://nt.jgfh5.cyou/pay/getRateInfo?key=3bbkk8899923", time.Second*15)
+	if err != nil {
+		logrus.Error(err)
+		return nil, err
+	}
+
+	todayRate := TodayRate{}
+	err = json.Unmarshal(respData, &todayRate)
+	if err != nil {
+		logrus.Error(err)
+		return nil, err
+	}
+
+	return &todayRate, nil
+}

+ 165 - 0
tgkk/tgkk.go

@@ -0,0 +1,165 @@
+package tgkk
+
+import (
+	"encoding/json"
+	"time"
+
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+	"gogs.daxia.dev/huanan/pkg.daxia.dev/event2"
+)
+
+var MainBot *Bot
+
+func Start() {
+	MainBot = NewBot()
+
+	event2.On[tgbotapi.Chattable](EventSendMsg, event2.ListenerFunc[tgbotapi.Chattable](MainBot.onSendMsg))
+
+	go func() {
+		StartHandleMsg()
+	}()
+
+	botToken := viper.GetString("bot_token")
+	logrus.Fatal(MainBot.InitDebug(botToken))
+}
+
+type Bot struct {
+	botApi        *tgbotapi.BotAPI
+	updateChannel tgbotapi.UpdatesChannel
+}
+
+func NewBot() *Bot {
+	return &Bot{}
+}
+
+func (p *Bot) onSendMsg(e event2.Event[tgbotapi.Chattable]) error {
+	return p.onSendMsgInner(e, true)
+}
+
+func (p *Bot) onSendMsgFix(e event2.Event[tgbotapi.Chattable]) error {
+	return p.onSendMsgInner(e, false)
+}
+
+func (p *Bot) onSendMsgInner(e event2.Event[tgbotapi.Chattable], delete bool) error {
+	logrus.Info("onSendMsg:", e)
+	c := e.Data()
+
+	resp, err := p.botApi.Send(c)
+	if err != nil {
+		logrus.Error(err)
+		return nil
+	}
+
+	logrus.Info("send finish:", resp)
+
+	if delete {
+		go func() {
+			<-time.After(time.Duration(30) * time.Second)
+
+			deleteMsg := tgbotapi.DeleteMessageConfig{
+				ChatID:    resp.Chat.ID,
+				MessageID: resp.MessageID,
+			}
+
+			p.botApi.Send(deleteMsg)
+		}()
+	}
+
+	return nil
+}
+
+func (p *Bot) InitDebug(token string) error {
+	bot, err := tgbotapi.NewBotAPI(token)
+	if err != nil {
+		logrus.Fatal(err)
+		return err
+	}
+
+	bot.Debug = true
+
+	logrus.Infof("Authorized on account %s", bot.Self.UserName)
+	delWebhookConfig := tgbotapi.DeleteWebhookConfig{
+		DropPendingUpdates: true,
+	}
+
+	bot.Send(delWebhookConfig)
+
+	hookCfg := tgbotapi.NewUpdate(0)
+
+	hookCfg.AllowedUpdates = []string{tgbotapi.UpdateTypeChatMember, tgbotapi.UpdateTypeMessage,
+		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery, tgbotapi.UpdateTypeChosenInlineResult}
+
+	bot.Debug = true
+
+	p.botApi = bot
+	p.updateChannel = bot.GetUpdatesChan(hookCfg)
+
+	p.ProcNewMessage()
+	return nil
+}
+
+func (p *Bot) Init(token string) error {
+	bot, err := tgbotapi.NewBotAPI(token)
+	if err != nil {
+		logrus.Fatal(err)
+		return err
+	}
+
+	bot.Debug = false
+
+	logrus.Infof("Authorized on account %s", bot.Self.UserName)
+
+	basePath := viper.GetString("base_path") // "/api/botzhenren"
+	fullPath := "http://13.214.75.66:1180" + basePath
+	if !viper.GetBool("is_debug") {
+		fullPath = "https://prodcpbot.daxia.dev" + basePath
+	}
+
+	hookCfg, err := tgbotapi.NewWebhook(fullPath)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	hookCfg.AllowedUpdates = []string{tgbotapi.UpdateTypeChatMember, tgbotapi.UpdateTypeMessage,
+		tgbotapi.UpdateTypeInlineQuery, tgbotapi.UpdateTypeCallbackQuery, tgbotapi.UpdateTypeChosenInlineResult}
+
+	_, err = bot.Request(hookCfg)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	info, err := bot.GetWebhookInfo()
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	if info.LastErrorDate != 0 {
+		logrus.Printf("Telegram callback failed: %s", info.LastErrorMessage)
+	}
+
+	p.updateChannel = bot.ListenForWebhook(basePath)
+
+	p.botApi = bot
+	p.ProcNewMessage()
+
+	return nil
+}
+
+func (p *Bot) GetBotApi() *tgbotapi.BotAPI {
+	return p.botApi
+}
+
+func (p *Bot) ProcNewMessage() {
+	if p.updateChannel == nil {
+		logrus.Error("updata channel is nil")
+		return
+	}
+
+	for update := range p.updateChannel {
+		updateJson, _ := json.Marshal(update)
+		logrus.Info("recv update msg:", string(updateJson))
+		go event2.MustFire(EventUpdateMsg, update)
+	}
+}