mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-07-08 22:14:52 +08:00
feat: parse media group, wip
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -16,39 +18,99 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
linkRegexString = `t.me/.*/\d+`
|
linkRegexString = `https?://t\.me/(?:c/\d+|[a-zA-Z0-9_]+)/\d+(?:\?[^\s]*)?`
|
||||||
linkRegex = regexp.MustCompile(linkRegexString)
|
linkRegex = regexp.MustCompile(linkRegexString)
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseLink(ctx *ext.Context, link string) (chatID int64, messageID int, err error) {
|
type parseResult struct {
|
||||||
strSlice := strings.Split(link, "/")
|
ChatID int64
|
||||||
if len(strSlice) < 3 {
|
MessageID int
|
||||||
return 0, 0, fmt.Errorf("链接格式错误: %s", link)
|
Files []*types.File
|
||||||
}
|
UserClient bool
|
||||||
messageID, err = strconv.Atoi(strSlice[len(strSlice)-1])
|
}
|
||||||
|
|
||||||
|
func parseLink(ctx *ext.Context, link string) (*parseResult, error) {
|
||||||
|
u, err := url.Parse(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, fmt.Errorf("无法解析消息 ID: %s", err)
|
return nil, fmt.Errorf("无法解析链接: %s", err)
|
||||||
}
|
}
|
||||||
|
strSlice := strings.Split(u.Path, "/")
|
||||||
|
if len(strSlice) < 3 {
|
||||||
|
return nil, fmt.Errorf("链接格式错误: %s", link)
|
||||||
|
}
|
||||||
|
messageID, err := strconv.Atoi(strSlice[len(strSlice)-1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("无法解析消息 ID: %s", err)
|
||||||
|
}
|
||||||
|
var chatID int64
|
||||||
if len(strSlice) == 3 {
|
if len(strSlice) == 3 {
|
||||||
chatUsername := strSlice[1]
|
chatUsername := strSlice[1]
|
||||||
linkChat, err := ctx.ResolveUsername(chatUsername)
|
peer := ctx.PeerStorage.GetPeerByUsername(chatUsername)
|
||||||
if err != nil {
|
if peer != nil {
|
||||||
return 0, 0, fmt.Errorf("解析用户名失败: %s", err)
|
chatID = peer.ID
|
||||||
|
} else {
|
||||||
|
linkChat, err := ctx.ResolveUsername(chatUsername)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解析用户名失败: %s", err)
|
||||||
|
}
|
||||||
|
if linkChat == nil {
|
||||||
|
return nil, fmt.Errorf("找不到该聊天: %s", chatUsername)
|
||||||
|
}
|
||||||
|
chatID = linkChat.GetID()
|
||||||
}
|
}
|
||||||
if linkChat == nil {
|
|
||||||
return 0, 0, fmt.Errorf("找不到该聊天: %s", chatUsername)
|
|
||||||
}
|
|
||||||
chatID = linkChat.GetID()
|
|
||||||
} else if len(strSlice) == 4 {
|
} else if len(strSlice) == 4 {
|
||||||
chatIDInt, err := strconv.Atoi(strSlice[2])
|
chatIDInt, err := strconv.Atoi(strSlice[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, fmt.Errorf("无法解析 Chat ID: %s", err)
|
return nil, fmt.Errorf("无法解析 Chat ID: %s", err)
|
||||||
}
|
}
|
||||||
chatID = int64(chatIDInt)
|
chatID = int64(chatIDInt)
|
||||||
} else {
|
} else {
|
||||||
return 0, 0, fmt.Errorf("无效的链接: %s", link)
|
return nil, errors.New("链接格式不正确,无法解析 Chat ID")
|
||||||
}
|
}
|
||||||
return chatID, messageID, nil
|
if chatID == 0 || messageID == 0 {
|
||||||
|
return nil, fmt.Errorf("链接中缺少 Chat ID 或 Message ID: %s", link)
|
||||||
|
}
|
||||||
|
msg, err := tryFetchMessage(ctx, chatID, messageID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取消息失败: %s", err)
|
||||||
|
}
|
||||||
|
mediaGroup, isGroup := msg.GetGroupedID()
|
||||||
|
if u.Query().Has("single") || !isGroup || (mediaGroup == 0) || userclient.UC == nil {
|
||||||
|
file, useUserClient, err := tryFetchFileFromMessage(ctx, chatID, messageID, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取文件失败: %s", err)
|
||||||
|
}
|
||||||
|
if file.FileName == "" {
|
||||||
|
file.FileName = GenFileNameFromMessage(*msg, file)
|
||||||
|
}
|
||||||
|
return &parseResult{
|
||||||
|
ChatID: chatID,
|
||||||
|
MessageID: messageID,
|
||||||
|
Files: []*types.File{file},
|
||||||
|
UserClient: useUserClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
groupMessages, isUserClient, err := tryGetMediaGroup(chatID, messageID, mediaGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取媒体组消息失败: %s", err)
|
||||||
|
}
|
||||||
|
var files []*types.File
|
||||||
|
for _, groupMsg := range groupMessages {
|
||||||
|
file, err := FileFromMedia(groupMsg.Media, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取媒体文件失败: %s", err)
|
||||||
|
}
|
||||||
|
if file.FileName == "" {
|
||||||
|
file.FileName = GenFileNameFromMessage(*groupMsg, file)
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
return &parseResult{
|
||||||
|
ChatID: chatID,
|
||||||
|
MessageID: messageID,
|
||||||
|
Files: files,
|
||||||
|
UserClient: isUserClient,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// use passed ctx client to fetch file from message,
|
// use passed ctx client to fetch file from message,
|
||||||
@@ -79,6 +141,18 @@ func tryFetchFileFromMessage(ctx *ext.Context, chatID int64, messageID int, file
|
|||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tryGetMediaGroup(chatID int64, messageID int, mediaGroupID int64) ([]*tg.Message, bool, error) {
|
||||||
|
if userclient.UC != nil {
|
||||||
|
uctx := userclient.GetCtx()
|
||||||
|
messages, err := GetMediaGroup(uctx, chatID, messageID, mediaGroupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, fmt.Errorf("failed to get media group from userbot: %w", err)
|
||||||
|
}
|
||||||
|
return messages, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, errors.New("userclient is not available, cannot fetch media group")
|
||||||
|
}
|
||||||
|
|
||||||
func tryFetchMessage(ctx *ext.Context, chatID int64, messageID int) (*tg.Message, error) {
|
func tryFetchMessage(ctx *ext.Context, chatID int64, messageID int) (*tg.Message, error) {
|
||||||
return GetTGMessage(ctx, chatID, messageID)
|
return GetTGMessage(ctx, chatID, messageID)
|
||||||
}
|
}
|
||||||
@@ -89,10 +163,10 @@ func handleLinkMessage(ctx *ext.Context, update *ext.Update) error {
|
|||||||
if link == "" {
|
if link == "" {
|
||||||
return dispatcher.ContinueGroups
|
return dispatcher.ContinueGroups
|
||||||
}
|
}
|
||||||
linkChatID, messageID, err := parseLink(ctx, link)
|
result, err := parseLink(ctx, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Log.Errorf("解析链接失败: %s", err)
|
common.Log.Errorf("解析链接失败: %s", err)
|
||||||
ctx.Reply(update, ext.ReplyTextString("解析链接失败: "+err.Error()), nil)
|
ctx.Reply(update, ext.ReplyTextString("解析链接失败"), nil)
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,29 +183,15 @@ func handleLinkMessage(ctx *ext.Context, update *ext.Update) error {
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
file, useUserClient, err := tryFetchFileFromMessage(ctx, linkChatID, messageID, "")
|
// TODO: handle group files
|
||||||
if err != nil {
|
|
||||||
common.Log.Errorf("获取文件失败: %s", err)
|
|
||||||
ctx.Reply(update, ext.ReplyTextString("获取文件失败: "+err.Error()), nil)
|
|
||||||
return dispatcher.EndGroups
|
|
||||||
}
|
|
||||||
if file.FileName == "" {
|
|
||||||
msg, err := tryFetchMessage(ctx, linkChatID, messageID)
|
|
||||||
if err != nil {
|
|
||||||
file.FileName = fmt.Sprintf("%d_%d", linkChatID, messageID)
|
|
||||||
} else {
|
|
||||||
file.FileName = GenFileNameFromMessage(*msg, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
receivedFile := &dao.ReceivedFile{
|
receivedFile := &dao.ReceivedFile{
|
||||||
Processing: false,
|
Processing: false,
|
||||||
FileName: file.FileName,
|
FileName: result.Files[0].FileName,
|
||||||
ChatID: linkChatID,
|
ChatID: result.ChatID,
|
||||||
MessageID: messageID,
|
MessageID: result.MessageID,
|
||||||
ReplyMessageID: replied.ID,
|
ReplyMessageID: replied.ID,
|
||||||
ReplyChatID: update.GetUserChat().GetID(),
|
ReplyChatID: update.GetUserChat().GetID(),
|
||||||
UseUserClient: useUserClient,
|
UseUserClient: result.UserClient,
|
||||||
}
|
}
|
||||||
record, err := dao.SaveReceivedFile(receivedFile)
|
record, err := dao.SaveReceivedFile(receivedFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -142,19 +202,20 @@ func handleLinkMessage(ctx *ext.Context, update *ext.Update) error {
|
|||||||
})
|
})
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
file := result.Files[0]
|
||||||
if !user.Silent || user.DefaultStorage == "" {
|
if !user.Silent || user.DefaultStorage == "" {
|
||||||
return ProvideSelectMessage(ctx, update, file.FileName, linkChatID, messageID, replied.ID)
|
return ProvideSelectMessage(ctx, update, file.FileName, result.ChatID, result.MessageID, replied.ID)
|
||||||
}
|
}
|
||||||
return HandleSilentAddTask(ctx, update, user, &types.Task{
|
return HandleSilentAddTask(ctx, update, user, &types.Task{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Status: types.Pending,
|
Status: types.Pending,
|
||||||
FileDBID: record.ID,
|
FileDBID: record.ID,
|
||||||
UseUserClient: useUserClient,
|
UseUserClient: result.UserClient,
|
||||||
File: file,
|
File: file,
|
||||||
StorageName: user.DefaultStorage,
|
StorageName: user.DefaultStorage,
|
||||||
UserID: user.ChatID,
|
UserID: user.ChatID,
|
||||||
FileChatID: linkChatID,
|
FileChatID: result.ChatID,
|
||||||
FileMessageID: messageID,
|
FileMessageID: result.MessageID,
|
||||||
ReplyMessageID: replied.ID,
|
ReplyMessageID: replied.ID,
|
||||||
ReplyChatID: update.GetUserChat().GetID(),
|
ReplyChatID: update.GetUserChat().GetID(),
|
||||||
})
|
})
|
||||||
|
|||||||
70
bot/utils.go
70
bot/utils.go
@@ -237,6 +237,8 @@ func GetTGMessage(ctx *ext.Context, chatId int64, messageID int) (*tg.Message, e
|
|||||||
return tgMessage, nil
|
return tgMessage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Userbot only
|
||||||
|
//
|
||||||
// https://github.com/iyear/tdl/blob/fbb396da774ba544e527c3ef41c44921ad74ee98/core/util/tutil/tutil.go#L174
|
// https://github.com/iyear/tdl/blob/fbb396da774ba544e527c3ef41c44921ad74ee98/core/util/tutil/tutil.go#L174
|
||||||
func GetSingleHistoryMessage(ctx context.Context, client *tg.Client, peer tg.InputPeerClass, msg int) (*tg.Message, error) {
|
func GetSingleHistoryMessage(ctx context.Context, client *tg.Client, peer tg.InputPeerClass, msg int) (*tg.Message, error) {
|
||||||
it := query.Messages(client).GetHistory(peer).OffsetID(msg + 1).BatchSize(1).Iter()
|
it := query.Messages(client).GetHistory(peer).OffsetID(msg + 1).BatchSize(1).Iter()
|
||||||
@@ -256,6 +258,74 @@ func GetSingleHistoryMessage(ctx context.Context, client *tg.Client, peer tg.Inp
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Userbot only
|
||||||
|
func GetHistoryMessages(ctx context.Context, client *tg.Client, peer tg.InputPeerClass, startID, limit int) ([]*tg.Message, error) {
|
||||||
|
endID := startID + limit - 1
|
||||||
|
msgs, err := client.MessagesGetHistory(ctx, &tg.MessagesGetHistoryRequest{
|
||||||
|
Peer: peer,
|
||||||
|
OffsetID: startID,
|
||||||
|
Limit: limit,
|
||||||
|
AddOffset: startID - endID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get history messages: %w", err)
|
||||||
|
}
|
||||||
|
var msgClass []tg.MessageClass
|
||||||
|
switch msgsv := msgs.(type) {
|
||||||
|
case *tg.MessagesMessages:
|
||||||
|
msgClass = msgsv.GetMessages()
|
||||||
|
case *tg.MessagesMessagesSlice:
|
||||||
|
msgClass = msgsv.GetMessages()
|
||||||
|
case *tg.MessagesChannelMessages:
|
||||||
|
msgClass = msgsv.GetMessages()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected messages type: %T", msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
messageBatch := make([]*tg.Message, 0, 100)
|
||||||
|
|
||||||
|
for _, msg := range msgClass {
|
||||||
|
msgNotEmpty, ok := msg.AsNotEmpty()
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch msgNotEmptyV := msgNotEmpty.(type) {
|
||||||
|
case *tg.Message:
|
||||||
|
messageBatch = append(messageBatch, msgNotEmptyV)
|
||||||
|
default:
|
||||||
|
common.Log.Warnf("Unexpected message type: %T, skipping", msgNotEmptyV)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(messageBatch) == 0 {
|
||||||
|
return nil, fmt.Errorf("no messages found for peer %s with startID %d and limit %d", peer, startID, limit)
|
||||||
|
}
|
||||||
|
return messageBatch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMediaGroup(ctx *ext.Context, chatID int64, messageID int, groupID int64) ([]*tg.Message, error) {
|
||||||
|
peer := ctx.PeerStorage.GetInputPeerById(chatID)
|
||||||
|
if peer == nil {
|
||||||
|
return nil, fmt.Errorf("无法获取聊天 %d 的输入 Peer", chatID)
|
||||||
|
}
|
||||||
|
messages, err := GetHistoryMessages(ctx, ctx.Raw, peer, messageID-9, 20)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取消息失败: %s", err)
|
||||||
|
}
|
||||||
|
var groupMessages []*tg.Message
|
||||||
|
for _, msg := range messages {
|
||||||
|
common.Log.Debugf("Checking message %d in group %d", msg.ID, groupID)
|
||||||
|
gID, isGroup := msg.GetGroupedID()
|
||||||
|
if isGroup && gID == groupID {
|
||||||
|
groupMessages = append(groupMessages, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(groupMessages) == 0 || (len(groupMessages) == 1 && groupMessages[0].ID == messageID) {
|
||||||
|
return nil, fmt.Errorf("未找到媒体组 %d 中的消息", groupID)
|
||||||
|
}
|
||||||
|
return groupMessages, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetInputPeerID(peer tg.InputPeerClass) int64 {
|
func GetInputPeerID(peer tg.InputPeerClass) int64 {
|
||||||
switch p := peer.(type) {
|
switch p := peer.(type) {
|
||||||
case *tg.InputPeerUser:
|
case *tg.InputPeerUser:
|
||||||
|
|||||||
13
dao/file.go
13
dao/file.go
@@ -9,6 +9,19 @@ func SaveReceivedFile(receivedFile *ReceivedFile) (*ReceivedFile, error) {
|
|||||||
return receivedFile, db.Error
|
return receivedFile, db.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BatchSaveReceivedFiles(receivedFiles []*ReceivedFile) error {
|
||||||
|
if len(receivedFiles) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, file := range receivedFiles {
|
||||||
|
record, err := GetReceivedFileByChatAndMessageID(file.ChatID, file.MessageID)
|
||||||
|
if err == nil {
|
||||||
|
file.ID = record.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return db.Save(receivedFiles).Error
|
||||||
|
}
|
||||||
|
|
||||||
func GetReceivedFileByChatAndMessageID(chatID int64, messageID int) (*ReceivedFile, error) {
|
func GetReceivedFileByChatAndMessageID(chatID int64, messageID int) (*ReceivedFile, error) {
|
||||||
var receivedFile ReceivedFile
|
var receivedFile ReceivedFile
|
||||||
err := db.Where("chat_id = ? AND message_id = ?", chatID, messageID).First(&receivedFile).Error
|
err := db.Where("chat_id = ? AND message_id = ?", chatID, messageID).First(&receivedFile).Error
|
||||||
|
|||||||
Reference in New Issue
Block a user