mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-05-10 17:52:44 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71844deab1 | ||
|
|
55fed6389e | ||
|
|
8ce5c2e007 | ||
|
|
6ecfbd8385 | ||
|
|
6c2bfd72cd |
@@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/gotd/td/tg"
|
"github.com/gotd/td/tg"
|
||||||
"github.com/krau/SaveAny-Bot/common"
|
"github.com/krau/SaveAny-Bot/common"
|
||||||
"github.com/krau/SaveAny-Bot/dao"
|
"github.com/krau/SaveAny-Bot/dao"
|
||||||
"github.com/krau/SaveAny-Bot/storage"
|
|
||||||
"github.com/krau/SaveAny-Bot/types"
|
"github.com/krau/SaveAny-Bot/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,11 +27,11 @@ func handleFileMessage(ctx *ext.Context, update *ext.Update) error {
|
|||||||
ctx.Reply(update, ext.ReplyTextString("获取用户失败"), nil)
|
ctx.Reply(update, ext.ReplyTextString("获取用户失败"), nil)
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
storages := storage.GetUserStorages(user.ChatID)
|
// storages := storage.GetUserStorages(user.ChatID)
|
||||||
if len(storages) == 0 {
|
// if len(storages) == 0 {
|
||||||
ctx.Reply(update, ext.ReplyTextString("无可用的存储"), nil)
|
// ctx.Reply(update, ext.ReplyTextString("无可用的存储"), nil)
|
||||||
return dispatcher.EndGroups
|
// return dispatcher.EndGroups
|
||||||
}
|
// }
|
||||||
|
|
||||||
msg, err := ctx.Reply(update, ext.ReplyTextString("正在获取文件信息..."), nil)
|
msg, err := ctx.Reply(update, ext.ReplyTextString("正在获取文件信息..."), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -10,7 +11,6 @@ import (
|
|||||||
"github.com/gotd/td/tg"
|
"github.com/gotd/td/tg"
|
||||||
"github.com/krau/SaveAny-Bot/common"
|
"github.com/krau/SaveAny-Bot/common"
|
||||||
"github.com/krau/SaveAny-Bot/dao"
|
"github.com/krau/SaveAny-Bot/dao"
|
||||||
"github.com/krau/SaveAny-Bot/storage"
|
|
||||||
"github.com/krau/SaveAny-Bot/types"
|
"github.com/krau/SaveAny-Bot/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,47 +19,47 @@ var (
|
|||||||
linkRegex = regexp.MustCompile(linkRegexString)
|
linkRegex = regexp.MustCompile(linkRegexString)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func parseLink(ctx *ext.Context, link string) (chatID int64, messageID int, err error) {
|
||||||
|
strSlice := strings.Split(link, "/")
|
||||||
|
if len(strSlice) < 3 {
|
||||||
|
return 0, 0, fmt.Errorf("链接格式错误: %s", link)
|
||||||
|
}
|
||||||
|
messageID, err = strconv.Atoi(strSlice[len(strSlice)-1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("无法解析消息 ID: %s", err)
|
||||||
|
}
|
||||||
|
if len(strSlice) == 3 {
|
||||||
|
chatUsername := strSlice[1]
|
||||||
|
linkChat, err := ctx.ResolveUsername(chatUsername)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("解析用户名失败: %s", err)
|
||||||
|
}
|
||||||
|
if linkChat == nil {
|
||||||
|
return 0, 0, fmt.Errorf("找不到该聊天: %s", chatUsername)
|
||||||
|
}
|
||||||
|
chatID = linkChat.GetID()
|
||||||
|
} else if len(strSlice) == 4 {
|
||||||
|
chatIDInt, err := strconv.Atoi(strSlice[2])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("无法解析 Chat ID: %s", err)
|
||||||
|
}
|
||||||
|
chatID = int64(chatIDInt)
|
||||||
|
} else {
|
||||||
|
return 0, 0, fmt.Errorf("无效的链接: %s", link)
|
||||||
|
}
|
||||||
|
return chatID, messageID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleLinkMessage(ctx *ext.Context, update *ext.Update) error {
|
func handleLinkMessage(ctx *ext.Context, update *ext.Update) error {
|
||||||
common.Log.Trace("Got link message")
|
common.Log.Trace("Got link message")
|
||||||
link := linkRegex.FindString(update.EffectiveMessage.Text)
|
link := linkRegex.FindString(update.EffectiveMessage.Text)
|
||||||
if link == "" {
|
if link == "" {
|
||||||
return dispatcher.ContinueGroups
|
return dispatcher.ContinueGroups
|
||||||
}
|
}
|
||||||
strSlice := strings.Split(link, "/")
|
linkChatID, messageID, err := parseLink(ctx, link)
|
||||||
if len(strSlice) < 3 {
|
|
||||||
return dispatcher.ContinueGroups
|
|
||||||
}
|
|
||||||
messageID, err := strconv.Atoi(strSlice[len(strSlice)-1])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Log.Errorf("解析消息 ID 失败: %s", err)
|
common.Log.Errorf("解析链接失败: %s", err)
|
||||||
ctx.Reply(update, ext.ReplyTextString("无法解析消息 ID"), nil)
|
ctx.Reply(update, ext.ReplyTextString("解析链接失败: "+err.Error()), nil)
|
||||||
return dispatcher.EndGroups
|
|
||||||
}
|
|
||||||
var linkChatID int64
|
|
||||||
if len(strSlice) == 3 {
|
|
||||||
chatUsername := strSlice[1]
|
|
||||||
linkChat, err := ctx.ResolveUsername(chatUsername)
|
|
||||||
if err != nil {
|
|
||||||
common.Log.Errorf("解析用户名失败: %s", err)
|
|
||||||
ctx.Reply(update, ext.ReplyTextString("解析用户名失败"), nil)
|
|
||||||
return dispatcher.EndGroups
|
|
||||||
}
|
|
||||||
if linkChat == nil {
|
|
||||||
common.Log.Errorf("无法找到聊天: %s", chatUsername)
|
|
||||||
ctx.Reply(update, ext.ReplyTextString("无法找到聊天"), nil)
|
|
||||||
return dispatcher.EndGroups
|
|
||||||
}
|
|
||||||
linkChatID = linkChat.GetID()
|
|
||||||
} else if len(strSlice) == 4 {
|
|
||||||
chatID, err := strconv.Atoi(strSlice[2])
|
|
||||||
if err != nil {
|
|
||||||
common.Log.Errorf("解析 Chat ID 失败: %s", err)
|
|
||||||
ctx.Reply(update, ext.ReplyTextString("解析 Chat ID 失败"), nil)
|
|
||||||
return dispatcher.EndGroups
|
|
||||||
}
|
|
||||||
linkChatID = int64(chatID)
|
|
||||||
} else {
|
|
||||||
ctx.Reply(update, ext.ReplyTextString("无法解析链接"), nil)
|
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,12 +69,13 @@ func handleLinkMessage(ctx *ext.Context, update *ext.Update) error {
|
|||||||
ctx.Reply(update, ext.ReplyTextString("获取用户失败"), nil)
|
ctx.Reply(update, ext.ReplyTextString("获取用户失败"), nil)
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
storages := storage.GetUserStorages(user.ChatID)
|
|
||||||
|
|
||||||
if len(storages) == 0 {
|
// storages := storage.GetUserStorages(user.ChatID)
|
||||||
ctx.Reply(update, ext.ReplyTextString("无可用的存储"), nil)
|
// if len(storages) == 0 {
|
||||||
return dispatcher.EndGroups
|
// ctx.Reply(update, ext.ReplyTextString("无可用的存储"), nil)
|
||||||
}
|
// return dispatcher.EndGroups
|
||||||
|
// }
|
||||||
|
|
||||||
replied, err := ctx.Reply(update, ext.ReplyTextString("正在获取文件..."), nil)
|
replied, err := ctx.Reply(update, ext.ReplyTextString("正在获取文件..."), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Log.Errorf("回复失败: %s", err)
|
common.Log.Errorf("回复失败: %s", err)
|
||||||
|
|||||||
@@ -63,12 +63,11 @@ func saveCmd(ctx *ext.Context, update *ext.Update) error {
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
storages := storage.GetUserStorages(user.ChatID)
|
// storages := storage.GetUserStorages(user.ChatID)
|
||||||
|
// if len(storages) == 0 {
|
||||||
if len(storages) == 0 {
|
// ctx.Reply(update, ext.ReplyTextString("无可用的存储"), nil)
|
||||||
ctx.Reply(update, ext.ReplyTextString("无可用的存储"), nil)
|
// return dispatcher.EndGroups
|
||||||
return dispatcher.EndGroups
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := GetTGMessage(ctx, update.EffectiveChat().GetID(), replyToMsgID)
|
msg, err := GetTGMessage(ctx, update.EffectiveChat().GetID(), replyToMsgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/gotd/contrib/middleware/floodwait"
|
"github.com/gotd/contrib/middleware/floodwait"
|
||||||
"github.com/gotd/contrib/middleware/ratelimit"
|
"github.com/gotd/contrib/middleware/ratelimit"
|
||||||
"github.com/gotd/td/telegram"
|
"github.com/gotd/td/telegram"
|
||||||
|
"github.com/krau/SaveAny-Bot/common"
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
@@ -30,8 +31,40 @@ const noPermissionText string = `
|
|||||||
func checkPermission(ctx *ext.Context, update *ext.Update) error {
|
func checkPermission(ctx *ext.Context, update *ext.Update) error {
|
||||||
userID := update.GetUserChat().GetID()
|
userID := update.GetUserChat().GetID()
|
||||||
if !slice.Contain(config.Cfg.GetUsersID(), userID) {
|
if !slice.Contain(config.Cfg.GetUsersID(), userID) {
|
||||||
|
if config.Cfg.AsPublicCopyMediaBot {
|
||||||
|
tryCopyMedia(ctx, update)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
ctx.Reply(update, ext.ReplyTextString(noPermissionText), nil)
|
ctx.Reply(update, ext.ReplyTextString(noPermissionText), nil)
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
return dispatcher.ContinueGroups
|
return dispatcher.ContinueGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tryCopyMedia(ctx *ext.Context, update *ext.Update) {
|
||||||
|
if !config.Cfg.AsPublicCopyMediaBot {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if update.EffectiveMessage == nil || update.EffectiveMessage.Message == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.Log.Tracef("Got copy media request from %d", update.EffectiveChat().GetID())
|
||||||
|
msg := update.EffectiveMessage.Message
|
||||||
|
if link := linkRegex.FindString(update.EffectiveMessage.Text); link != "" {
|
||||||
|
linkChatID, messageID, err := parseLink(ctx, link)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileMessage, err := GetTGMessage(ctx, linkChatID, messageID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fileMessage == nil || fileMessage.Media == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = fileMessage
|
||||||
|
}
|
||||||
|
if _, err := copyMediaToChat(ctx, msg, update.EffectiveChat().GetID()); err != nil {
|
||||||
|
common.Log.Errorf("Failed to copy media: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ func getSelectStorageMarkup(userChatID int64, fileChatID, fileMessageID int) (*t
|
|||||||
return nil, fmt.Errorf("failed to get user by chat ID: %d, error: %w", userChatID, err)
|
return nil, fmt.Errorf("failed to get user by chat ID: %d, error: %w", userChatID, err)
|
||||||
}
|
}
|
||||||
storages := storage.GetUserStorages(user.ChatID)
|
storages := storage.GetUserStorages(user.ChatID)
|
||||||
if len(storages) == 0 {
|
// if len(storages) == 0 {
|
||||||
return nil, ErrNoStorages
|
// return nil, ErrNoStorages
|
||||||
}
|
// }
|
||||||
|
|
||||||
buttons := make([]tg.KeyboardButtonClass, 0)
|
buttons := make([]tg.KeyboardButtonClass, 0)
|
||||||
for _, storage := range storages {
|
for _, storage := range storages {
|
||||||
@@ -216,7 +216,7 @@ func GetTGMessage(ctx *ext.Context, chatId int64, messageID int) (*tg.Message, e
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return cacheMessage, nil
|
return cacheMessage, nil
|
||||||
}
|
}
|
||||||
common.Log.Debugf("Fetching message: %d", messageID)
|
common.Log.Debugf("Fetching message: %d:%d", chatId, messageID)
|
||||||
messages, err := ctx.GetMessages(chatId, []tg.InputMessageClass{&tg.InputMessageID{ID: messageID}})
|
messages, err := ctx.GetMessages(chatId, []tg.InputMessageClass{&tg.InputMessageID{ID: messageID}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ type Config struct {
|
|||||||
Threads int `toml:"threads" mapstructure:"threads" json:"threads"`
|
Threads int `toml:"threads" mapstructure:"threads" json:"threads"`
|
||||||
Stream bool `toml:"stream" mapstructure:"stream" json:"stream"`
|
Stream bool `toml:"stream" mapstructure:"stream" json:"stream"`
|
||||||
|
|
||||||
|
// Experimental: 将拷贝媒体文件的功能设为公开可用
|
||||||
|
AsPublicCopyMediaBot bool `toml:"as_public_copy_media_bot" mapstructure:"as_public_copy_media_bot" json:"as_public_copy_media_bot"`
|
||||||
|
|
||||||
Users []userConfig `toml:"users" mapstructure:"users" json:"users"`
|
Users []userConfig `toml:"users" mapstructure:"users" json:"users"`
|
||||||
|
|
||||||
Temp tempConfig `toml:"temp" mapstructure:"temp"`
|
Temp tempConfig `toml:"temp" mapstructure:"temp"`
|
||||||
|
|||||||
46
docs/docs/experimental.md
Normal file
46
docs/docs/experimental.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# 实验性功能
|
||||||
|
|
||||||
|
这里的功能不太稳定, 且未来可能会被删除或修改。
|
||||||
|
|
||||||
|
## 存储规则
|
||||||
|
|
||||||
|
允许你为 Bot 在上传文件到存储时设置一些重定向规则, 用于自动整理所保存的文件.
|
||||||
|
|
||||||
|
见: https://github.com/krau/SaveAny-Bot/issues/28
|
||||||
|
|
||||||
|
目前支持的规则类型:
|
||||||
|
|
||||||
|
1. FILENAME-REGEX
|
||||||
|
2. MESSAGE-REGEX
|
||||||
|
|
||||||
|
添加规则的基本语法:
|
||||||
|
|
||||||
|
"规则类型 规则内容 存储名 路径"
|
||||||
|
|
||||||
|
注意空格的使用, 语法正确 bot 才能解析, 以下是一条合法的添加规则命令:
|
||||||
|
|
||||||
|
```
|
||||||
|
/rule add FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /视频
|
||||||
|
```
|
||||||
|
|
||||||
|
此外, 规则中的存储名若使用 "CHOSEN" , 则表示存储到点击按钮选择的存储端的路径下
|
||||||
|
|
||||||
|
规则介绍:
|
||||||
|
|
||||||
|
### FILENAME-REGEX
|
||||||
|
|
||||||
|
根据文件名正则匹配, 规则内容要求为一个合法的正则表达式, 如
|
||||||
|
|
||||||
|
```
|
||||||
|
FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /视频
|
||||||
|
```
|
||||||
|
|
||||||
|
表示将文件名后缀为 mp4,mkv,ts,avi,flv 的文件放到名为 MyAlist 存储下的 /视频 目录内 (同时受配置文件中的 `base_path` 影响)
|
||||||
|
|
||||||
|
### MESSAGE-REGEX
|
||||||
|
|
||||||
|
同上, 根据消息文本内容正则匹配
|
||||||
|
|
||||||
|
## 复制并发送媒体消息
|
||||||
|
|
||||||
|
将接收到的文件(媒体)消息, 或链接对应的消息原样发送到当前聊天, 点击选择存储按钮中的 "发送到当前聊天" 即可.
|
||||||
@@ -35,4 +35,4 @@ Bot 接受两种消息: 文件和链接.
|
|||||||
|
|
||||||
**不支持** Stream 模式的存储端:
|
**不支持** Stream 模式的存储端:
|
||||||
|
|
||||||
- alist
|
- alist
|
||||||
@@ -30,5 +30,6 @@ nav:
|
|||||||
- index.md
|
- index.md
|
||||||
- deploy.md
|
- deploy.md
|
||||||
- help.md
|
- help.md
|
||||||
|
- experimental.md
|
||||||
- faq.md
|
- faq.md
|
||||||
- contribute.md
|
- contribute.md
|
||||||
|
|||||||
Reference in New Issue
Block a user