Initial commit
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
package tgbot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"stat/common/log"
|
||||
"stat/common/utils"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
type TgChats struct {
|
||||
BroadcastChats []int64 // 广播群
|
||||
CommandChats []int64 // 命令群
|
||||
MonitorChats []int64 // 监控群
|
||||
DownloadChats []int64 // 下载监控群
|
||||
DomainChats []int64 // 域名监控群
|
||||
DevelopChats []int64 // 开发调试群
|
||||
PrivateUsers []int64 // 私聊用户列表
|
||||
BlocStats []int64 // 集团统计群
|
||||
Advertise []int64 // 广告监控群
|
||||
ChannelApk []int64 // 渠道包生成
|
||||
Project []int64 // 项目的消息
|
||||
AdvsSend []int64 //广告群
|
||||
Rchssend []int64 //充值群
|
||||
}
|
||||
|
||||
type FileType int32
|
||||
|
||||
const (
|
||||
FileTypeNone FileType = 0
|
||||
FileTypePhoto FileType = 1
|
||||
FileTypeDocument FileType = 2
|
||||
FileTypeMusic FileType = 3
|
||||
FileTypeVideo FileType = 4
|
||||
)
|
||||
|
||||
type TgBot struct {
|
||||
bot *tgbotapi.BotAPI
|
||||
commands *TgCommands
|
||||
chats *TgChats
|
||||
messages *TgMessageList
|
||||
shutdown chan interface{}
|
||||
allow map[int64][]string
|
||||
executing sync.Map
|
||||
}
|
||||
|
||||
func NewTgBot(token string, chats *TgChats, allow map[int64][]string) (*TgBot, error) {
|
||||
ret := &TgBot{
|
||||
chats: chats,
|
||||
commands: &TgCommands{
|
||||
commands: make(map[string]*Command),
|
||||
},
|
||||
messages: &TgMessageList{
|
||||
list: make([]tgbotapi.Chattable, 0),
|
||||
},
|
||||
shutdown: make(chan interface{}),
|
||||
allow: allow,
|
||||
}
|
||||
if allow == nil {
|
||||
ret.allow = make(map[int64][]string)
|
||||
}
|
||||
if chats == nil {
|
||||
return nil, errors.New("chats can not be nil")
|
||||
}
|
||||
bot, err := tgbotapi.NewBotAPI(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.bot = bot
|
||||
ret.processMessages()
|
||||
go ret.start()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *TgBot) RegisterCommand(cmds ...*Command) {
|
||||
for _, cmd := range cmds {
|
||||
s.commands.SetCommand(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TgBot) start() {
|
||||
defer utils.RecoverError(func(err error) {
|
||||
fmt.Printf("TgBot Start panic:%v", err)
|
||||
})
|
||||
ctx := context.Background()
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 1
|
||||
updates := s.bot.GetUpdatesChan(u)
|
||||
for update := range updates {
|
||||
if update.Message != nil {
|
||||
if update.Message.IsCommand() {
|
||||
go func() {
|
||||
defer utils.RecoverError(func(err error) {
|
||||
fmt.Printf("TgBot processCommand panic:%v", err)
|
||||
})
|
||||
if err := s.processCommand(ctx, update.Message); err != nil {
|
||||
var uErr *UserError
|
||||
if errors.As(err, &uErr) {
|
||||
uErr.SendBack()
|
||||
} else {
|
||||
s.SendDevelopMessage(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
} else if update.CallbackQuery != nil {
|
||||
go func() {
|
||||
defer utils.RecoverError(func(err error) {
|
||||
fmt.Printf("TgBot processCallback panic:%v", err)
|
||||
})
|
||||
if err := s.processCallback(ctx, update.CallbackQuery); err != nil {
|
||||
var uErr *UserError
|
||||
if errors.As(err, &uErr) {
|
||||
uErr.SendBack()
|
||||
} else {
|
||||
s.SendDevelopMessage(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
close(s.shutdown)
|
||||
}
|
||||
|
||||
func (s *TgBot) Stop() {
|
||||
s.bot.StopReceivingUpdates()
|
||||
close(s.shutdown)
|
||||
}
|
||||
|
||||
func (s *TgBot) processMessages() {
|
||||
go func() {
|
||||
defer utils.RecoverError(func(err error) {
|
||||
log.Error("TgBot processMessages panic", zap.Error(err))
|
||||
})
|
||||
for {
|
||||
select {
|
||||
case <-s.shutdown:
|
||||
return
|
||||
default:
|
||||
}
|
||||
if msg := s.messages.Pop(); msg != nil {
|
||||
if _, err1 := s.bot.Send(msg); err1 != nil {
|
||||
log.Error("TgBot Send error", zap.Error(err1))
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *TgBot) SendBroadcastMessage(text string, filePath string, fileType FileType) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.BroadcastChats, filePath, fileType)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendBroadcastEntity(ent *EntityList) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
if ent == nil {
|
||||
return errors.New("entity can not be nil")
|
||||
}
|
||||
|
||||
for _, chat := range s.chats.BroadcastChats {
|
||||
entities, text := ent.ToEntities()
|
||||
msg := tgbotapi.NewMessage(chat, text)
|
||||
msg.Entities = entities
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendBroadcastMessageToChats(text string, filePath string, fileType FileType, chats []int64) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, chats, filePath, fileType)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendMonitorMessage(text string) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.MonitorChats, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendChannelApkMessage(text string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.ChannelApk, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TgBot) SendDevelopMessage(text string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.DevelopChats, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TgBot) SendBlocStatMessage(text string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.BlocStats, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TgBot) SendDownloadMessage(text string) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.DownloadChats, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendDomainMessage(text string) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.DomainChats, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendAdvertiseMessage(text string) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.Advertise, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendProjectMessage(text string) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.Project, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) isCmdAuthenticated(cmd *Command) bool {
|
||||
chatAllowCmds := s.allow[cmd.message.Chat.ID]
|
||||
if slices.Contains(s.chats.CommandChats, cmd.message.Chat.ID) || // 在命令群 1
|
||||
slices.Contains(s.chats.Rchssend, cmd.message.Chat.ID) || // 在命令群 2
|
||||
(cmd.message.Chat.IsPrivate() && slices.Contains(s.chats.PrivateUsers, cmd.message.From.ID)) || // 允许的私聊用户ID
|
||||
slices.Contains(chatAllowCmds, cmd.Cmd) || // 群已指定允许的命令
|
||||
cmd.Cmd == "what" { // 公共命令,显示群信息
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TgBot) processCommand(ctx context.Context, message *tgbotapi.Message) error {
|
||||
cmd, err := s.parseCommand(message, CommandTypeCommand)
|
||||
if err != nil {
|
||||
return UnknownCommandError(message)
|
||||
}
|
||||
if cmd.skip { // not to this bot
|
||||
return nil
|
||||
}
|
||||
if s.isCmdAuthenticated(cmd) { // 群已指定允许的命令
|
||||
return s.handleCommand(ctx, cmd)
|
||||
} else {
|
||||
return AuthError(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TgBot) processCallback(ctx context.Context, callback *tgbotapi.CallbackQuery) error {
|
||||
message := callback.Message
|
||||
message.From = callback.From
|
||||
message.Text = callback.Data
|
||||
cmd, err := s.parseCommand(message, CommandTypeCallback)
|
||||
if err != nil {
|
||||
return UnknownCommandError(message)
|
||||
}
|
||||
if cmd.skip { // not to this bot
|
||||
return nil
|
||||
}
|
||||
if s.isCmdAuthenticated(cmd) {
|
||||
return s.handleCommand(ctx, cmd)
|
||||
} else {
|
||||
return AuthError(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TgBot) handleCommand(ctx context.Context, cmd *Command) (ret error) {
|
||||
defer utils.RecoverError(func(err error) {
|
||||
ret = CommandPanic(cmd, err)
|
||||
})
|
||||
userID := cmd.message.From.ID
|
||||
if _, loaded := s.executing.LoadOrStore(userID, cmd.Cmd); loaded {
|
||||
return NewUserError(cmd, "你上次的命令尚未执行完成,请稍后再重试。。。")
|
||||
}
|
||||
defer s.executing.Delete(userID)
|
||||
|
||||
if cmd.Process != nil {
|
||||
if err := cmd.Process(ctx, cmd); err != nil {
|
||||
var uErr *UserError
|
||||
if errors.As(err, &uErr) {
|
||||
return uErr.SendBack()
|
||||
} else {
|
||||
return CommandError(cmd, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = UnknownCommandError(cmd.message)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TgBot) parseCommand(msg *tgbotapi.Message, cmdType CommandType) (*Command, error) {
|
||||
ret := Command{owner: s, message: msg}
|
||||
quoted := false
|
||||
fields := strings.FieldsFunc(msg.Text, func(r rune) bool {
|
||||
if r == '"' {
|
||||
quoted = !quoted
|
||||
}
|
||||
return !quoted && r == ' '
|
||||
})
|
||||
|
||||
args := make([]string, 0)
|
||||
for _, field := range fields {
|
||||
_field := strings.TrimSpace(field)
|
||||
if strings.HasPrefix(field, "@") {
|
||||
ret.botNames = append(ret.botNames, strings.Replace(_field, "@", "", 1))
|
||||
} else {
|
||||
args = append(args, _field)
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
if strings.HasPrefix(args[0], "/") {
|
||||
ret.Cmd = strings.Replace(args[0], "/", "", 1)
|
||||
args = DeleteSliceElem(args, args[0])
|
||||
}
|
||||
}
|
||||
|
||||
ret.skip = !(slices.Contains(ret.botNames, s.bot.Self.UserName) || len(ret.botNames) == 0)
|
||||
ret.Params = args
|
||||
item := s.commands.GetCommand(ret.Cmd)
|
||||
if item != nil {
|
||||
ret.Desc = item.Desc
|
||||
ret.Help = item.Help
|
||||
ret.Process = item.Process
|
||||
ret.CmdType = cmdType
|
||||
return &ret, nil
|
||||
} else {
|
||||
return nil, errors.New("unknown tg command")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TgBot) Sendrchstesttochat(text string, filePath string, fileType FileType) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.AdvsSend, filePath, fileType)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) Sendrchstochat(text string, filePath string, fileType FileType) error {
|
||||
if s == nil {
|
||||
return errors.New("bot can not be nil")
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.Rchssend, filePath, fileType)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TgBot) SendAdvsSendMessage(text string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.AdvsSend, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TgBot) SendrchssendMessage(text string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
msgs := getMessageList(text, s.chats.Rchssend, "", FileTypeNone)
|
||||
for _, msg := range msgs {
|
||||
s.messages.Push(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package tgbot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
type CommandType int32
|
||||
|
||||
const (
|
||||
CommandTypeUnknown CommandType = 0
|
||||
CommandTypeCommand CommandType = 1
|
||||
CommandTypeCallback CommandType = 2
|
||||
)
|
||||
|
||||
type TgMessageList struct {
|
||||
lock sync.Mutex
|
||||
list []tgbotapi.Chattable
|
||||
}
|
||||
|
||||
func (s *TgMessageList) Push(msg tgbotapi.Chattable) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if s.list == nil {
|
||||
s.list = make([]tgbotapi.Chattable, 0)
|
||||
}
|
||||
s.list = append(s.list, msg)
|
||||
}
|
||||
|
||||
func (s *TgMessageList) Pop() tgbotapi.Chattable {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
var ret tgbotapi.Chattable
|
||||
if len(s.list) > 0 {
|
||||
ret = s.list[0]
|
||||
s.list = s.list[1:]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type TgCommands struct {
|
||||
commands map[string]*Command
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (s *TgCommands) GetCommand(cmd string) *Command {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
return s.commands[cmd]
|
||||
}
|
||||
|
||||
func (s *TgCommands) SetCommand(cmd *Command) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.commands[cmd.Cmd] = cmd
|
||||
}
|
||||
|
||||
type CommandFunc func(ctx context.Context, cmd *Command) error
|
||||
|
||||
type Command struct {
|
||||
Cmd string // 命令
|
||||
Help string // 命令帮助
|
||||
Desc string // 命令描述
|
||||
CmdType CommandType // 命令类型
|
||||
Process CommandFunc // 处理函数
|
||||
Params []string // 参数
|
||||
botNames []string // @
|
||||
skip bool // 是否跳过处理
|
||||
owner *TgBot
|
||||
ParseMode string
|
||||
PhotoPath string
|
||||
FilePath string
|
||||
message *tgbotapi.Message
|
||||
}
|
||||
|
||||
func (c *Command) Message() *tgbotapi.Message {
|
||||
return c.message
|
||||
}
|
||||
|
||||
func (c *Command) HasFlag(flag string) bool {
|
||||
for _, arg := range c.Params {
|
||||
if strings.Contains(arg, "=") {
|
||||
ss := strings.Split(arg, "=")
|
||||
if len(ss) > 0 {
|
||||
if ss[0] == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if arg == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Command) GetFlagStringValue(flag string) string {
|
||||
for index, arg := range c.Params {
|
||||
pos := strings.Index(arg, "=")
|
||||
if pos > 0 {
|
||||
cmd := arg[:pos]
|
||||
if cmd == flag {
|
||||
content := arg[pos+1:]
|
||||
if strings.HasPrefix(content, `"`) && strings.HasSuffix(content, `"`) {
|
||||
if text, err := strconv.Unquote(content); err != nil {
|
||||
return content
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
} else {
|
||||
return content
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if arg == flag {
|
||||
if index+1 < len(c.Params) {
|
||||
return c.Params[index+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Command) GetFlagInt64Value(flag string) (int64, error) {
|
||||
s := c.GetFlagStringValue(flag)
|
||||
if s != "" {
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
return 0, errors.New("flag value is empty")
|
||||
}
|
||||
|
||||
func (c *Command) GetFlagInt32Value(flag string) (int32, error) {
|
||||
s := c.GetFlagStringValue(flag)
|
||||
if s != "" {
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
return int32(i), err
|
||||
}
|
||||
return 0, errors.New("flag value is empty")
|
||||
}
|
||||
|
||||
func (c *Command) GetFlagStringSlice(flag string) []string {
|
||||
s := c.GetFlagStringValue(flag)
|
||||
return strings.Split(s, ",")
|
||||
}
|
||||
|
||||
func (c *Command) GetFlagInt64Slice(flag string) []int64 {
|
||||
ret := make([]int64, 0)
|
||||
s := c.GetFlagStringValue(flag)
|
||||
ss := strings.Split(s, ",")
|
||||
for _, v := range ss {
|
||||
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||
ret = append(ret, i)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Command) GetFlagInt32Slice(flag string) []int32 {
|
||||
ret := make([]int32, 0)
|
||||
s := c.GetFlagStringValue(flag)
|
||||
ss := strings.Split(s, ",")
|
||||
for _, v := range ss {
|
||||
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||
ret = append(ret, int32(i))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Command) SendBack(text string) error {
|
||||
if c.message != nil {
|
||||
if c.CmdType == CommandTypeCallback {
|
||||
if c.PhotoPath != "" || c.FilePath != "" {
|
||||
return c.SendReply(text, nil)
|
||||
} else {
|
||||
return c.SendEdit(text, nil)
|
||||
}
|
||||
} else if c.CmdType == CommandTypeCommand {
|
||||
return c.SendReply(text, nil)
|
||||
} else {
|
||||
return c.SendText(text)
|
||||
}
|
||||
} else {
|
||||
return errors.New("tg message is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) SendEntity(ent *EntityList) error {
|
||||
if c == nil {
|
||||
return errors.New("bot cmd can not be nil")
|
||||
}
|
||||
if ent == nil {
|
||||
return errors.New("entity can not be nil")
|
||||
}
|
||||
if c.message == nil {
|
||||
return errors.New("tg message is nil")
|
||||
}
|
||||
chat := c.message.Chat.ID
|
||||
entities, text := ent.ToEntities()
|
||||
msg := tgbotapi.NewMessage(chat, text)
|
||||
msg.Entities = entities
|
||||
c.owner.messages.Push(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Command) SendReply(text string, replyMarkup interface{}) error {
|
||||
if c.message != nil {
|
||||
return replyMessage(c.owner, c.message, text, replyMarkup, c.ParseMode, c.PhotoPath, c.FilePath)
|
||||
} else {
|
||||
return errors.New("tg message is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) SendEdit(text string, replyMarkup interface{}) error {
|
||||
if c.message != nil {
|
||||
return editMessage(c.owner, c.message, text, replyMarkup, c.ParseMode)
|
||||
} else {
|
||||
return errors.New("tg message is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) SendText(text string) error {
|
||||
if c.message != nil {
|
||||
return sendMessageToChat(c.owner, text, c.message.Chat.ID, c.ParseMode, c.PhotoPath, c.FilePath)
|
||||
} else {
|
||||
return errors.New("tg message is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) SendFile(text string, fileName string) error {
|
||||
if c.message != nil {
|
||||
c.FilePath = fileName
|
||||
return sendMessageToChat(c.owner, text, c.message.Chat.ID, c.ParseMode, c.PhotoPath, c.FilePath)
|
||||
} else {
|
||||
return errors.New("tg message is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) SendDelete() error {
|
||||
if c.message != nil {
|
||||
msg := tgbotapi.NewDeleteMessage(c.message.Chat.ID, c.message.MessageID)
|
||||
_, err := c.owner.bot.Request(msg)
|
||||
return err
|
||||
} else {
|
||||
return errors.New("tg message is nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package tgbot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
type Entity struct {
|
||||
Title string
|
||||
Content string
|
||||
}
|
||||
|
||||
func (p *Entity) GetTitle() string {
|
||||
return fmt.Sprintf("%s", p.Title)
|
||||
}
|
||||
|
||||
func (p *Entity) Entity(startOffset int) ([]tgbotapi.MessageEntity, string) {
|
||||
ret := make([]tgbotapi.MessageEntity, 0)
|
||||
ret = append(ret, tgbotapi.MessageEntity{
|
||||
Type: "expandable_blockquote",
|
||||
Offset: startOffset,
|
||||
Length: p.TextU16Len(),
|
||||
})
|
||||
if p.TitleU16Len() > 0 {
|
||||
ret = append(ret, tgbotapi.MessageEntity{
|
||||
Type: "bold",
|
||||
Offset: startOffset,
|
||||
Length: p.TitleU16Len(),
|
||||
})
|
||||
}
|
||||
if p.ContentU16Len() > 0 {
|
||||
ret = append(ret, tgbotapi.MessageEntity{
|
||||
Type: "bold",
|
||||
Offset: startOffset + p.TitleU16Len() + 1,
|
||||
Length: p.ContentU16Len(),
|
||||
})
|
||||
}
|
||||
if p.TitleU16Len() > 0 {
|
||||
return ret, p.GetTitle() + "\n" + p.Content
|
||||
}
|
||||
return ret, p.Content
|
||||
}
|
||||
|
||||
func (p *Entity) TextU16Len() int {
|
||||
text := p.GetTitle()
|
||||
if text != "" {
|
||||
text += "\n"
|
||||
}
|
||||
text += p.Content
|
||||
return len(utf16.Encode([]rune(text)))
|
||||
}
|
||||
|
||||
func (p *Entity) TitleU16Len() int {
|
||||
return len(utf16.Encode([]rune(p.GetTitle())))
|
||||
}
|
||||
|
||||
func (p *Entity) ContentU16Len() int {
|
||||
return len(utf16.Encode([]rune(p.Content)))
|
||||
}
|
||||
|
||||
type EntityList struct {
|
||||
Title string
|
||||
Desc string
|
||||
Entities []*Entity
|
||||
}
|
||||
|
||||
func NewEntityList(title, desc string, entities []*Entity) *EntityList {
|
||||
return &EntityList{
|
||||
Title: strings.TrimSpace(title),
|
||||
Desc: strings.TrimSpace(desc),
|
||||
Entities: entities,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *EntityList) ToEntities() ([]tgbotapi.MessageEntity, string) {
|
||||
ret := make([]tgbotapi.MessageEntity, 0)
|
||||
var text string
|
||||
var startOffset int
|
||||
// 添加总标题
|
||||
titleU16Len := len(utf16.Encode([]rune(p.Title)))
|
||||
ret = append(ret, tgbotapi.MessageEntity{
|
||||
Type: "bold",
|
||||
Offset: 0,
|
||||
Length: titleU16Len,
|
||||
})
|
||||
text += p.Title + "\n"
|
||||
startOffset += titleU16Len + 1
|
||||
// 添加总描述
|
||||
if p.Desc != "" {
|
||||
descU16Len := len(utf16.Encode([]rune(p.Desc)))
|
||||
ret = append(ret, tgbotapi.MessageEntity{
|
||||
Type: "bold",
|
||||
Offset: startOffset,
|
||||
Length: descU16Len,
|
||||
})
|
||||
text += p.Desc + "\n"
|
||||
startOffset += descU16Len + 1
|
||||
}
|
||||
// 添加每个Prompt
|
||||
for _, ent := range p.Entities {
|
||||
promptEntities, promptText := ent.Entity(startOffset)
|
||||
ret = append(ret, promptEntities...)
|
||||
text += promptText + "\n"
|
||||
startOffset += ent.TextU16Len() + 1
|
||||
}
|
||||
return ret, text
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package tgbot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
type UserError struct {
|
||||
msg string
|
||||
cmd *Command
|
||||
}
|
||||
|
||||
func NewUserError(cmd *Command, text string) error {
|
||||
ret := &UserError{
|
||||
msg: text,
|
||||
cmd: cmd,
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (e *UserError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (e *UserError) SendBack() error {
|
||||
if e.cmd.CmdType == CommandTypeCallback {
|
||||
return e.cmd.SendEdit(e.msg, nil)
|
||||
} else if e.cmd.CmdType == CommandTypeCommand {
|
||||
return e.cmd.SendReply(e.msg, nil)
|
||||
} else {
|
||||
return e.cmd.SendText(e.msg)
|
||||
}
|
||||
}
|
||||
|
||||
func CommandError(cmd *Command, err error) error {
|
||||
return fmt.Errorf("命令失败,来自【%s:%d】%s,命令【%s】错误:\n%s", cmd.message.Chat.Title, cmd.message.Chat.ID, UserDisplay(cmd.message), cmd.Cmd, err.Error())
|
||||
}
|
||||
|
||||
func CommandPanic(cmd *Command, err error) error {
|
||||
return fmt.Errorf("命令异常,来自【%s:%d】%s,命令【%s】错误:\n%s", cmd.message.Chat.Title, cmd.message.Chat.ID, UserDisplay(cmd.message), cmd.Cmd, err.Error())
|
||||
}
|
||||
|
||||
func AuthError(message *tgbotapi.Message) error {
|
||||
return fmt.Errorf("非官方群,来自【%s:%d】%s, 内容:\n%s", message.Chat.Title, message.Chat.ID, UserDisplay(message), message.Text)
|
||||
}
|
||||
|
||||
func UnknownCommandError(message *tgbotapi.Message) error {
|
||||
return fmt.Errorf("未知命令,来自【%s:%d】%s, 内容:\n%s", message.Chat.Title, message.Chat.ID, UserDisplay(message), message.Text)
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package tgbot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
func sendMessageToChat(bot *TgBot, text string, chat int64, parseMode string, photoPath string, filePath string) error {
|
||||
blocks := getTextBlocks(text)
|
||||
if len(blocks) == 0 {
|
||||
return errors.New("发送的文本为空")
|
||||
}
|
||||
var msg tgbotapi.Chattable
|
||||
for _, block := range blocks {
|
||||
if photoPath != "" {
|
||||
tmp := tgbotapi.NewPhoto(chat, tgbotapi.FilePath(photoPath))
|
||||
tmp.Caption = text
|
||||
tmp.ParseMode = parseMode
|
||||
msg = tmp
|
||||
} else if filePath != "" {
|
||||
tmp := tgbotapi.NewDocument(chat, tgbotapi.FilePath(filePath))
|
||||
tmp.Caption = text
|
||||
tmp.ParseMode = parseMode
|
||||
msg = tmp
|
||||
} else {
|
||||
tmp := tgbotapi.NewMessage(chat, block)
|
||||
tmp.ParseMode = parseMode
|
||||
msg = tmp
|
||||
}
|
||||
bot.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func replyMessage(bot *TgBot, message *tgbotapi.Message, text string, replyMarkup interface{}, parseMode string, photoPath string, filePath string) error {
|
||||
blocks := getTextBlocks(text)
|
||||
if len(blocks) == 0 {
|
||||
return errors.New("发送的文本为空")
|
||||
}
|
||||
var msg tgbotapi.Chattable
|
||||
for _, block := range blocks {
|
||||
if photoPath != "" {
|
||||
tmp := tgbotapi.NewPhoto(message.Chat.ID, tgbotapi.FilePath(photoPath))
|
||||
tmp.Caption = text
|
||||
tmp.ParseMode = parseMode
|
||||
tmp.ReplyMarkup = replyMarkup
|
||||
tmp.ReplyToMessageID = message.MessageID
|
||||
msg = tmp
|
||||
} else if filePath != "" {
|
||||
tmp := tgbotapi.NewDocument(message.Chat.ID, tgbotapi.FilePath(filePath))
|
||||
tmp.Caption = text
|
||||
tmp.ParseMode = parseMode
|
||||
tmp.ReplyMarkup = replyMarkup
|
||||
tmp.ReplyToMessageID = message.MessageID
|
||||
msg = tmp
|
||||
} else {
|
||||
tmp := tgbotapi.NewMessage(message.Chat.ID, block)
|
||||
tmp.ParseMode = parseMode
|
||||
tmp.ReplyMarkup = replyMarkup
|
||||
tmp.ReplyToMessageID = message.MessageID
|
||||
msg = tmp
|
||||
}
|
||||
bot.messages.Push(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func editMessage(bot *TgBot, message *tgbotapi.Message, text string, replyMarkup interface{}, parseMode string) error {
|
||||
blocks := getTextBlocks(text)
|
||||
if len(blocks) == 0 {
|
||||
return errors.New("发送的文本为空")
|
||||
}
|
||||
var msg tgbotapi.Chattable
|
||||
for index, block := range blocks {
|
||||
if index == 0 {
|
||||
tmp := tgbotapi.NewEditMessageText(message.Chat.ID, message.MessageID, block)
|
||||
tmp.ParseMode = parseMode
|
||||
msg = tmp
|
||||
if markup, ok := replyMarkup.(tgbotapi.InlineKeyboardMarkup); ok {
|
||||
tmp = tgbotapi.NewEditMessageTextAndMarkup(message.Chat.ID, message.MessageID, block, markup)
|
||||
tmp.ParseMode = parseMode
|
||||
msg = tmp
|
||||
}
|
||||
} else {
|
||||
tmp := tgbotapi.NewMessage(message.Chat.ID, block)
|
||||
tmp.ParseMode = parseMode
|
||||
msg = tmp
|
||||
}
|
||||
bot.messages.Push(msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMessageList(text string, chats []int64, filePath string, fileType FileType) []tgbotapi.Chattable {
|
||||
msgList := make([]tgbotapi.Chattable, 0)
|
||||
blocks := getTextBlocks(text)
|
||||
|
||||
chatfile := filePath
|
||||
for _, chat := range chats {
|
||||
filePath = chatfile
|
||||
for _, block := range blocks {
|
||||
if filePath != "" {
|
||||
if fileType == FileTypePhoto {
|
||||
msg := tgbotapi.NewPhoto(chat, tgbotapi.FilePath(filePath))
|
||||
msg.Caption = block
|
||||
|
||||
msgList = append(msgList, msg)
|
||||
} else if fileType == FileTypeDocument {
|
||||
if chat == -1002009498163 {
|
||||
if text != "天宇公司综合日报" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
msg := tgbotapi.NewDocument(chat, tgbotapi.FilePath(filePath))
|
||||
msg.Caption = block
|
||||
msgList = append(msgList, msg)
|
||||
} else {
|
||||
msgList = append(msgList, tgbotapi.NewMessage(chat, block))
|
||||
}
|
||||
filePath = ""
|
||||
} else {
|
||||
if fileType == FileTypeDocument {
|
||||
continue
|
||||
}
|
||||
msgList = append(msgList, tgbotapi.NewMessage(chat, block))
|
||||
}
|
||||
}
|
||||
}
|
||||
return msgList
|
||||
}
|
||||
|
||||
func getTextBlocks(content string) []string {
|
||||
blocks := make([]string, 0)
|
||||
lines := strings.Split(content, "\n")
|
||||
text := ""
|
||||
for _, line := range lines {
|
||||
if len(text)+len(line) < MaxMessageSize {
|
||||
text += line + "\n"
|
||||
} else {
|
||||
blocks = append(blocks, text)
|
||||
if len(line) > MaxMessageSize {
|
||||
text = "[此行太长被忽略...]\n"
|
||||
} else {
|
||||
text = line + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(text) > 0 {
|
||||
blocks = append(blocks, text)
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tgbot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
func UserDisplay(message *tgbotapi.Message) string {
|
||||
if message.From != nil {
|
||||
return fmt.Sprintf("用户【%s:%d】", message.From.FirstName+message.From.LastName, message.From.ID)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package tgbot
|
||||
|
||||
const MaxMessageSize = 4090
|
||||
|
||||
func DeleteSliceElem(slice []string, elem string) []string {
|
||||
tmp := make([]string, 0, len(slice))
|
||||
for _, v := range slice {
|
||||
if v != elem {
|
||||
tmp = append(tmp, v)
|
||||
}
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
Reference in New Issue
Block a user