mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-05-11 23:09:47 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06f326088a | ||
|
|
b7d3ec6230 | ||
|
|
f812990e1c | ||
|
|
492900bbef | ||
|
|
764be2a083 | ||
|
|
46c21b77e9 |
@@ -19,12 +19,13 @@ import (
|
|||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(ctx context.Context) {
|
func Init(ctx context.Context) (<-chan struct{}) {
|
||||||
log.FromContext(ctx).Info("初始化 Bot...")
|
log.FromContext(ctx).Info("初始化 Bot...")
|
||||||
resultChan := make(chan struct {
|
resultChan := make(chan struct {
|
||||||
client *gotgproto.Client
|
client *gotgproto.Client
|
||||||
err error
|
err error
|
||||||
})
|
})
|
||||||
|
shouldRestart := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
var resolver dcs.Resolver
|
var resolver dcs.Resolver
|
||||||
if config.C().Telegram.Proxy.Enable && config.C().Telegram.Proxy.URL != "" {
|
if config.C().Telegram.Proxy.Enable && config.C().Telegram.Proxy.URL != "" {
|
||||||
@@ -55,7 +56,11 @@ func Init(ctx context.Context) {
|
|||||||
MaxRetries: config.C().Telegram.RpcRetry,
|
MaxRetries: config.C().Telegram.RpcRetry,
|
||||||
AutoFetchReply: true,
|
AutoFetchReply: true,
|
||||||
ErrorHandler: func(ctx *ext.Context, u *ext.Update, s string) error {
|
ErrorHandler: func(ctx *ext.Context, u *ext.Update, s string) error {
|
||||||
log.FromContext(ctx).Errorf("Unhandled error: %s", s)
|
if s == "SAVEANTBOT-RESTART" {
|
||||||
|
shouldRestart <- struct{}{}
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
log.FromContext(ctx).Errorf("unhandled error: %s", s)
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -103,4 +108,5 @@ func Init(ctx context.Context) {
|
|||||||
handlers.Register(result.client.Dispatcher)
|
handlers.Register(result.client.Dispatcher)
|
||||||
log.FromContext(ctx).Info("Bot 初始化完成")
|
log.FromContext(ctx).Info("Bot 初始化完成")
|
||||||
}
|
}
|
||||||
|
return shouldRestart
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ Save Any Bot - 转存你的 Telegram 文件
|
|||||||
/save [自定义文件名] - 保存文件
|
/save [自定义文件名] - 保存文件
|
||||||
/dir - 管理存储目录
|
/dir - 管理存储目录
|
||||||
/rule - 管理规则
|
/rule - 管理规则
|
||||||
|
/update - 检查更新并升级
|
||||||
|
|
||||||
使用帮助: https://sabot.unv.app/usage/
|
使用帮助: https://sabot.unv.app/usage
|
||||||
|
反馈群组: https://t.me/ProjectSaveAny
|
||||||
`
|
`
|
||||||
shortHash := config.GitCommit
|
shortHash := config.GitCommit
|
||||||
if len(shortHash) > 7 {
|
if len(shortHash) > 7 {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ func Register(disp dispatcher.Dispatcher) {
|
|||||||
disp.AddHandler(handlers.NewCommand("unwatch", handleUnwatchCmd))
|
disp.AddHandler(handlers.NewCommand("unwatch", handleUnwatchCmd))
|
||||||
disp.AddHandler(handlers.NewCommand("save", handleSilentMode(handleSaveCmd, handleSilentSaveReplied)))
|
disp.AddHandler(handlers.NewCommand("save", handleSilentMode(handleSaveCmd, handleSilentSaveReplied)))
|
||||||
disp.AddHandler(handlers.NewCommand("config", handleConfigCmd))
|
disp.AddHandler(handlers.NewCommand("config", handleConfigCmd))
|
||||||
|
disp.AddHandler(handlers.NewCommand("update", handleUpdateCmd))
|
||||||
|
disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("update"), handleUpdateCallback))
|
||||||
disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix(tcbdata.TypeAdd), handleAddCallback))
|
disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix(tcbdata.TypeAdd), handleAddCallback))
|
||||||
disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix(tcbdata.TypeSetDefault), handleSetDefaultCallback))
|
disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix(tcbdata.TypeSetDefault), handleSetDefaultCallback))
|
||||||
disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix(tcbdata.TypeCancel), handleCancelCallback))
|
disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix(tcbdata.TypeCancel), handleCancelCallback))
|
||||||
|
|||||||
102
client/bot/handlers/update.go
Normal file
102
client/bot/handlers/update.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/celestix/gotgproto/dispatcher"
|
||||||
|
"github.com/celestix/gotgproto/ext"
|
||||||
|
"github.com/gotd/td/telegram/message/html"
|
||||||
|
"github.com/gotd/td/tg"
|
||||||
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
|
"github.com/rhysd/go-github-selfupdate/selfupdate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleUpdateCmd(ctx *ext.Context, u *ext.Update) error {
|
||||||
|
currentV, err := semver.Parse(config.Version)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Reply(u, ext.ReplyTextString(fmt.Sprintf("You are in dev or the version var failed to inject: %v", err)), nil)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
latest, ok, err := selfupdate.DetectLatest(config.GitRepo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Reply(u, ext.ReplyTextString(fmt.Sprintf("检测最新版本失败: %v", err)), nil)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ctx.Reply(u, ext.ReplyTextString("没有找到版本信息"), nil)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
if latest.Version.LT(currentV) || latest.Version.Equals(currentV) {
|
||||||
|
ctx.Reply(u, ext.ReplyTextString(fmt.Sprintf("当前已经是最新版本: %s", config.Version)), nil)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
ctx.Sender.To(u.GetUserChat().AsInputPeer()).StyledText(ctx, html.String(nil, func() string {
|
||||||
|
md := latest.ReleaseNotes
|
||||||
|
md = regexp.MustCompile(`(?m)^###\s+ (.+)$`).ReplaceAllString(md, "<b>$1</b>")
|
||||||
|
md = regexp.MustCompile(`(?m)^#####\s+ (.+)$`).ReplaceAllString(md, "<i>$1</i>")
|
||||||
|
|
||||||
|
md = regexp.MustCompile(`(?m)^- `).ReplaceAllString(md, "• ")
|
||||||
|
|
||||||
|
md = regexp.MustCompile(`\[\((\w{6,})\)\]\((https?://[^\s)]+)\)`).ReplaceAllString(md, `(<a href="$2">$1</a>)`)
|
||||||
|
|
||||||
|
md = regexp.MustCompile(`\[(.+?)\]\((https?://[^\s)]+)\)`).ReplaceAllString(md, `<a href="$2">$1</a>`)
|
||||||
|
|
||||||
|
md = strings.ReplaceAll(md, " ", " ")
|
||||||
|
|
||||||
|
return `<blockquote expandable>` + md + `</blockquote>`
|
||||||
|
}()))
|
||||||
|
text := fmt.Sprintf(`发现新版本: %s
|
||||||
|
当前版本: %s
|
||||||
|
|
||||||
|
文件大小: %.2f MB
|
||||||
|
下载链接: %s
|
||||||
|
发布时间: %s
|
||||||
|
|
||||||
|
升级将重启 Bot , 是否升级?`, latest.Version, config.Version,
|
||||||
|
float64(latest.AssetByteSize)/(1024*1024), latest.AssetURL,
|
||||||
|
latest.PublishedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
ctx.Reply(u, ext.ReplyTextString(text), &ext.ReplyOpts{
|
||||||
|
Markup: &tg.ReplyInlineMarkup{
|
||||||
|
Rows: []tg.KeyboardButtonRow{
|
||||||
|
{
|
||||||
|
Buttons: []tg.KeyboardButtonClass{
|
||||||
|
&tg.KeyboardButtonCallback{
|
||||||
|
Text: "升级",
|
||||||
|
Data: []byte("update"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUpdateCallback(ctx *ext.Context, u *ext.Update) error {
|
||||||
|
currentV, err := semver.Parse(config.Version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.EditMessage(u.GetUserChat().GetID(), &tg.MessagesEditMessageRequest{
|
||||||
|
ID: u.CallbackQuery.GetMsgID(),
|
||||||
|
Message: fmt.Sprintf("正在升级中, 当前版本: %s", config.Version),
|
||||||
|
})
|
||||||
|
latest, err := selfupdate.UpdateSelf(currentV, config.GitRepo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.EditMessage(u.GetUserChat().GetID(), &tg.MessagesEditMessageRequest{
|
||||||
|
ID: u.CallbackQuery.GetMsgID(),
|
||||||
|
Message: fmt.Sprintf("升级失败: %v", err),
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
ctx.EditMessage(u.GetUserChat().GetID(), &tg.MessagesEditMessageRequest{
|
||||||
|
ID: u.CallbackQuery.GetMsgID(),
|
||||||
|
Message: fmt.Sprintf("已升级至版本 %s\n若 Bot 未自动重启请手动启动", latest.Version),
|
||||||
|
})
|
||||||
|
return errors.New("SAVEANTBOT-RESTART")
|
||||||
|
}
|
||||||
18
cmd/run.go
18
cmd/run.go
@@ -25,7 +25,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Run(cmd *cobra.Command, _ []string) {
|
func Run(cmd *cobra.Command, _ []string) {
|
||||||
ctx := cmd.Context()
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
logger := log.NewWithOptions(os.Stdout, log.Options{
|
logger := log.NewWithOptions(os.Stdout, log.Options{
|
||||||
Level: log.DebugLevel,
|
Level: log.DebugLevel,
|
||||||
ReportTimestamp: true,
|
ReportTimestamp: true,
|
||||||
@@ -34,7 +34,15 @@ func Run(cmd *cobra.Command, _ []string) {
|
|||||||
})
|
})
|
||||||
ctx = log.WithContext(ctx, logger)
|
ctx = log.WithContext(ctx, logger)
|
||||||
|
|
||||||
initAll(ctx)
|
exitChan, err := initAll(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Failed to initialize", "error", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-exitChan
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
core.Run(ctx)
|
core.Run(ctx)
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
@@ -43,10 +51,10 @@ func Run(cmd *cobra.Command, _ []string) {
|
|||||||
cleanCache()
|
cleanCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
func initAll(ctx context.Context) {
|
func initAll(ctx context.Context) (<-chan struct{}, error) {
|
||||||
if err := config.Init(ctx); err != nil {
|
if err := config.Init(ctx); err != nil {
|
||||||
fmt.Println("Failed to load config:", err)
|
fmt.Println("Failed to load config:", err)
|
||||||
os.Exit(1)
|
return nil, err
|
||||||
}
|
}
|
||||||
cache.Init()
|
cache.Init()
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
@@ -69,7 +77,7 @@ func initAll(ctx context.Context) {
|
|||||||
logger.Fatalf("User client login failed: %s", err)
|
logger.Fatalf("User client login failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bot.Init(ctx)
|
return bot.Init(ctx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanCache() {
|
func cleanCache() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ var upgradeCmd = &cobra.Command{
|
|||||||
Short: "Upgrade saveany-bot to the latest version",
|
Short: "Upgrade saveany-bot to the latest version",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
v := semver.MustParse(config.Version)
|
v := semver.MustParse(config.Version)
|
||||||
latest, err := selfupdate.UpdateSelf(v, "krau/SaveAny-Bot")
|
latest, err := selfupdate.UpdateSelf(v, config.GitRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Binary update failed:", err)
|
fmt.Println("Binary update failed:", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,3 +7,7 @@ var (
|
|||||||
BuildTime string = "unknown"
|
BuildTime string = "unknown"
|
||||||
GitCommit string = "unknown"
|
GitCommit string = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GitRepo = "krau/SaveAny-Bot"
|
||||||
|
)
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/duke-git/lancet/v2/slice"
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
@@ -96,12 +95,12 @@ func Init(ctx context.Context) error {
|
|||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
fmt.Println("Error reading config file, ", err)
|
fmt.Println("Error reading config file, ", err)
|
||||||
os.Exit(1)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := viper.Unmarshal(cfg); err != nil {
|
if err := viper.Unmarshal(cfg); err != nil {
|
||||||
fmt.Println("Error unmarshalling config file, ", err)
|
fmt.Println("Error unmarshalling config file, ", err)
|
||||||
os.Exit(1)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
storagesConfig, err := storage.LoadStorageConfigs(viper.GetViper())
|
storagesConfig, err := storage.LoadStorageConfigs(viper.GetViper())
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ Stream 模式对于磁盘空间有限的部署环境十分有用, 但也有一
|
|||||||
{{< hint warning >}}
|
{{< hint warning >}}
|
||||||
启用 userbot 集成后, bot 可以下载私密频道和群组的文件, 但具有无法避免的账号被封禁的风险.
|
启用 userbot 集成后, bot 可以下载私密频道和群组的文件, 但具有无法避免的账号被封禁的风险.
|
||||||
<br />
|
<br />
|
||||||
开启 userbot 集成后第一次启动 bot 时需要通过终端交互输入手机号, 2FA 和验证码, 如果你使用 docker 部署, 请进入容器内执行相关操作.
|
开启 userbot 集成后第一次启动 bot 时需要通过终端交互输入手机号, 2FA 和验证码.
|
||||||
|
<br />
|
||||||
|
如果你使用 docker 部署, 请进入容器内执行相关操作.
|
||||||
{{< /hint >}}
|
{{< /hint >}}
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: "安装与更新"
|
|||||||
|
|
||||||
# 安装与更新
|
# 安装与更新
|
||||||
|
|
||||||
## 从预编译文件部署
|
## 从预编译文件部署(推荐)
|
||||||
|
|
||||||
在 [Release](https://github.com/krau/SaveAny-Bot/releases) 页面下载对应平台的二进制文件.
|
在 [Release](https://github.com/krau/SaveAny-Bot/releases) 页面下载对应平台的二进制文件.
|
||||||
|
|
||||||
@@ -131,13 +131,13 @@ docker run -d --name saveany-bot \
|
|||||||
|
|
||||||
## 更新
|
## 更新
|
||||||
|
|
||||||
使用 `upgrade` 或 `up` 升级到最新版
|
向 Bot 发送 `/update` 指令检查更新并升级, 或者使用 CLI 命令更新:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./saveany-bot upgrade
|
./saveany-bot up
|
||||||
```
|
```
|
||||||
|
|
||||||
如果是 Docker 部署, 使用以下命令更新:
|
如果是 Docker 部署, 还可以使用以下命令更新:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ghcr.io/krau/saveany-bot:latest
|
docker pull ghcr.io/krau/saveany-bot:latest
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ import (
|
|||||||
const (
|
const (
|
||||||
MaxPartSize = 1024 * 1024
|
MaxPartSize = 1024 * 1024
|
||||||
MaxUploadPartSize = uploader.MaximumPartSize
|
MaxUploadPartSize = uploader.MaximumPartSize
|
||||||
|
MaxPhotoSize = 10 * 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/duke-git/lancet/v2/slice"
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
|
"github.com/duke-git/lancet/v2/validator"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/gotd/td/constant"
|
"github.com/gotd/td/constant"
|
||||||
"github.com/gotd/td/telegram/message"
|
"github.com/gotd/td/telegram/message"
|
||||||
@@ -87,7 +88,7 @@ func (t *Telegram) Save(ctx context.Context, r io.Reader, storagePath string) er
|
|||||||
if len(parts) >= 1 {
|
if len(parts) >= 1 {
|
||||||
filename = parts[len(parts)-1]
|
filename = parts[len(parts)-1]
|
||||||
}
|
}
|
||||||
if len(parts) >= 2 {
|
if len(parts) >= 2 && validator.IsAlphaNumeric(parts[0]) {
|
||||||
cid, err := tgutil.ParseChatID(tctx, parts[0])
|
cid, err := tgutil.ParseChatID(tctx, parts[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// id不合法时使用配置文件中的 chat_id
|
// id不合法时使用配置文件中的 chat_id
|
||||||
@@ -141,9 +142,13 @@ func (t *Telegram) Save(ctx context.Context, r io.Reader, storagePath string) er
|
|||||||
return fmt.Errorf("failed to upload file to telegram: %w", err)
|
return fmt.Errorf("failed to upload file to telegram: %w", err)
|
||||||
}
|
}
|
||||||
caption := styling.Plain(filename)
|
caption := styling.Plain(filename)
|
||||||
|
forceFile := t.config.ForceFile
|
||||||
|
if strings.HasPrefix(mtype.String(), "image/") && size >= tglimit.MaxPhotoSize {
|
||||||
|
forceFile = true
|
||||||
|
}
|
||||||
docb := message.UploadedDocument(file, caption).
|
docb := message.UploadedDocument(file, caption).
|
||||||
Filename(filename).
|
Filename(filename).
|
||||||
ForceFile(t.config.ForceFile).
|
ForceFile(forceFile).
|
||||||
MIME(mtype.String())
|
MIME(mtype.String())
|
||||||
|
|
||||||
var media message.MediaOption = docb
|
var media message.MediaOption = docb
|
||||||
|
|||||||
Reference in New Issue
Block a user