Skip to content

Commit d53914b

Browse files
authored
Merge pull request #23 from Cosr-Backup/origin
Origin
2 parents e786266 + 2518ad2 commit d53914b

17 files changed

Lines changed: 321 additions & 51 deletions

File tree

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
downloads/
77
data/
88
cache/
9-
docs
9+
docs/
1010
config.example.toml
1111
docker-compose.*

.github/FUNDING.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# These are supported funding model platforms
22

33
custom: [
4-
"https://afdian.com/a/acherkrau"
4+
"https://afdian.com/a/unvapp"
55
]

.github/workflows/build-docker.yml

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,7 @@ jobs:
2929
type=semver,pattern={{version}}
3030
type=semver,pattern={{major}}.{{minor}}
3131
type=sha
32-
type=raw,value=latest
33-
type=ref,event=branch
34-
type=ref,event=tag
35-
labels: |
36-
org.opencontainers.image.title=${{ env.IMAGE_NAME }}
37-
org.opencontainers.image.source=https://github.com/krau/SaveAny-Bot
38-
org.opencontainers.image.url=https://github.com/krau/SaveAny-Bot
32+
type=raw,value=latest,enable={{is_default_branch}}
3933
4034
- name: Set up QEMU
4135
uses: docker/setup-qemu-action@v3
@@ -50,23 +44,20 @@ jobs:
5044
username: ${{ github.actor }}
5145
password: ${{ secrets.GITHUB_TOKEN }}
5246

53-
- name: Extract version from Git Ref
54-
id: extract_version
55-
run: |
56-
VERSION=$(echo "${{ github.ref }}" | sed 's/refs\/tags\/v//')
57-
echo "VERSION=${VERSION}" >> $GITHUB_ENV
58-
5947
- name: Build and push Docker image
48+
id: build-and-push
6049
uses: docker/build-push-action@v6
6150
with:
6251
context: .
6352
platforms: linux/amd64,linux/arm64
64-
cache-from: type=gha
53+
push: ${{ github.event_name != 'pull_request' }}
54+
tags: ${{ steps.meta.outputs.tags }}
55+
labels: ${{ steps.meta.outputs.labels }}
56+
cache-from: |
57+
type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
58+
type=gha
6559
cache-to: type=gha,mode=max
6660
build-args: |
6761
VERSION=${{ steps.meta.outputs.version }}
6862
GitCommit=${{ github.sha }}
69-
BuildTime=${{ format(github.event.repository.updated_at, 'yyyy-MM-dd HH:mm:ss') }}
70-
push: true
71-
tags: ${{ steps.meta.outputs.tags }}
72-
labels: ${{ steps.meta.outputs.labels }}
63+
BuildTime=${{ fromJson(toJSON(github.event.repository.pushed_at)) }}

Dockerfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ ARG GitCommit="Unknown"
55
ARG BuildTime="Unknown"
66

77
WORKDIR /app
8-
COPY go.* ./
9-
RUN go mod download
8+
9+
COPY go.mod go.sum ./
10+
RUN --mount=type=cache,target=/go/pkg/mod \
11+
go mod download
12+
1013
COPY . .
1114
RUN --mount=type=cache,target=/root/.cache/go-build \
1215
--mount=type=cache,target=/go/pkg \

client/bot/handlers/media.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
package handlers
22

33
import (
4+
"fmt"
5+
"sync"
6+
"time"
7+
48
"github.com/celestix/gotgproto/dispatcher"
59
"github.com/celestix/gotgproto/ext"
610
"github.com/charmbracelet/log"
11+
"github.com/gotd/td/tg"
12+
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/mediautil"
713
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
814
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/shortcut"
15+
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
16+
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
17+
"github.com/krau/SaveAny-Bot/pkg/tfile"
918
"github.com/krau/SaveAny-Bot/storage"
1019
)
1120

1221
func handleMediaMessage(ctx *ext.Context, update *ext.Update) error {
1322
logger := log.FromContext(ctx)
1423
message := update.EffectiveMessage.Message
24+
groupID, isGroup := message.GetGroupedID()
25+
if isGroup && groupID != 0 {
26+
return handleGroupMediaMessage(ctx, update, message, groupID)
27+
}
1528
logger.Debugf("Got media: %s", message.Media.TypeName())
29+
1630
msg, file, err := shortcut.GetFileFromMessageWithReply(ctx, update, message)
1731
if err != nil {
1832
return err
@@ -38,6 +52,10 @@ func handleSilentSaveMedia(ctx *ext.Context, update *ext.Update) error {
3852
return dispatcher.EndGroups
3953
}
4054
message := update.EffectiveMessage.Message
55+
groupID, isGroup := message.GetGroupedID()
56+
if isGroup && groupID != 0 {
57+
return handleGroupMediaMessage(ctx, update, message, groupID)
58+
}
4159
logger.Debugf("Got media: %s", message.Media.TypeName())
4260
userID := update.GetUserChat().GetID()
4361
msg, file, err := shortcut.GetFileFromMessageWithReply(ctx, update, message)
@@ -46,3 +64,96 @@ func handleSilentSaveMedia(ctx *ext.Context, update *ext.Update) error {
4664
}
4765
return shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userID, stor, "", file, msg.ID)
4866
}
67+
68+
type MediaGroupHandler struct {
69+
groups map[int64][]tfile.TGFileMessage
70+
timers map[int64]*time.Timer
71+
mu sync.Mutex
72+
timeout time.Duration
73+
}
74+
75+
var mediaGroupHandler = &MediaGroupHandler{
76+
groups: make(map[int64][]tfile.TGFileMessage),
77+
timers: make(map[int64]*time.Timer),
78+
timeout: 1 * time.Second,
79+
}
80+
81+
func handleGroupMediaMessage(ctx *ext.Context, update *ext.Update, message *tg.Message, groupID int64) error {
82+
logger := log.FromContext(ctx)
83+
media := message.Media
84+
supported := mediautil.IsSupported(media)
85+
if !supported {
86+
return dispatcher.EndGroups
87+
}
88+
file, err := tfile.FromMediaMessage(media, ctx.Raw, message, tfile.WithNameIfEmpty(
89+
tgutil.GenFileNameFromMessage(*message),
90+
))
91+
if err != nil {
92+
logger.Errorf("Failed to get file from media: %s", err)
93+
return dispatcher.EndGroups
94+
}
95+
mediaGroupHandler.mu.Lock()
96+
defer mediaGroupHandler.mu.Unlock()
97+
if mediaGroupHandler.groups[groupID] == nil {
98+
mediaGroupHandler.groups[groupID] = make([]tfile.TGFileMessage, 0)
99+
}
100+
mediaGroupHandler.groups[groupID] = append(mediaGroupHandler.groups[groupID], file)
101+
102+
if timer, exists := mediaGroupHandler.timers[groupID]; exists {
103+
timer.Stop()
104+
}
105+
mediaGroupHandler.timers[groupID] = time.AfterFunc(mediaGroupHandler.timeout, func() {
106+
processMediaGroup(ctx, update, groupID)
107+
})
108+
return dispatcher.EndGroups
109+
}
110+
111+
func processMediaGroup(ctx *ext.Context, update *ext.Update, groupID int64) {
112+
logger := log.FromContext(ctx)
113+
mediaGroupHandler.mu.Lock()
114+
items := mediaGroupHandler.groups[groupID]
115+
delete(mediaGroupHandler.groups, groupID)
116+
delete(mediaGroupHandler.timers, groupID)
117+
mediaGroupHandler.mu.Unlock()
118+
if len(items) == 0 {
119+
logger.Warn("No media items to process for group", "groupID", groupID)
120+
return
121+
}
122+
logger.Debugf("Processing media group %d with %d items", groupID, len(items))
123+
124+
userId := update.GetUserChat().GetID()
125+
msg, err := ctx.Reply(update, ext.ReplyTextString("正在保存文件..."), nil)
126+
if err != nil {
127+
logger.Errorf("Failed to reply: %s", err)
128+
return
129+
}
130+
stor := storage.FromContext(ctx)
131+
if stor != nil {
132+
// In silent mode
133+
if len(items) == 1 {
134+
shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userId, stor, "", items[0], msg.ID)
135+
return
136+
}
137+
shortcut.CreateAndAddBatchTGFileTaskWithEdit(ctx, userId, stor, "", items, msg.ID)
138+
return
139+
}
140+
141+
stors := storage.GetUserStorages(ctx, userId)
142+
markup, err := msgelem.BuildAddSelectStorageKeyboard(stors, tcbdata.Add{
143+
Files: items,
144+
AsBatch: len(items) > 1,
145+
})
146+
if err != nil {
147+
logger.Errorf("构建存储选择键盘失败: %s", err)
148+
ctx.EditMessage(userId, &tg.MessagesEditMessageRequest{
149+
ID: msg.ID,
150+
Message: "构建存储选择键盘失败: " + err.Error(),
151+
})
152+
return
153+
}
154+
ctx.EditMessage(userId, &tg.MessagesEditMessageRequest{
155+
ID: msg.ID,
156+
Message: fmt.Sprintf("共 %d 个文件, 请选择存储位置", len(items)),
157+
ReplyMarkup: markup,
158+
})
159+
}

client/bot/handlers/utils/ruleutil/rule.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package ruleutil
33
import (
44
"context"
55

6+
"github.com/duke-git/lancet/v2/convertor"
7+
68
"github.com/charmbracelet/log"
79
"github.com/krau/SaveAny-Bot/database"
810
"github.com/krau/SaveAny-Bot/pkg/consts"
@@ -33,11 +35,22 @@ func (m matchedStorName) String() string {
3335
return string(m)
3436
}
3537

36-
func (m matchedStorName) IsValid() bool {
38+
// can we use this storage name directly?
39+
func (m matchedStorName) IsUsable() bool {
3740
return m != "" && m != consts.RuleStorNameChosen
3841
}
3942

40-
func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (matchedStorageName matchedStorName, dirPath string) {
43+
type MatchedDirPath string
44+
45+
func (m MatchedDirPath) String() string {
46+
return string(m)
47+
}
48+
49+
func (m MatchedDirPath) NeedNewForAlbum() bool {
50+
return m != "" && m == consts.RuleDirPathNewForAlbum
51+
}
52+
53+
func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (matchedStorageName matchedStorName, dirPath MatchedDirPath) {
4154
if inputs == nil || len(rules) == 0 {
4255
return "", ""
4356
}
@@ -56,7 +69,7 @@ func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (m
5669
continue
5770
}
5871
if ok {
59-
dirPath = ru.StoragePath()
72+
dirPath = MatchedDirPath(ru.StoragePath())
6073
matchedStorageName = matchedStorName(ru.StorageName())
6174
}
6275
case ruleenum.MessageRegex.String():
@@ -71,7 +84,26 @@ func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (m
7184
continue
7285
}
7386
if ok {
74-
dirPath = ru.StoragePath()
87+
dirPath = MatchedDirPath(ru.StoragePath())
88+
matchedStorageName = matchedStorName(ru.StorageName())
89+
}
90+
case ruleenum.IsAlbum.String():
91+
matchAlbum, err := convertor.ToBool(ur.Data)
92+
if err != nil {
93+
matchAlbum = false
94+
}
95+
ru, err := rule.NewRuleMediaType(ur.StorageName, ur.DirPath, matchAlbum)
96+
if err != nil {
97+
logger.Errorf("Failed to create rule: %s", err)
98+
continue
99+
}
100+
ok, err := ru.Match(inputs.File.Message().GroupedID != 0)
101+
if err != nil {
102+
logger.Errorf("Failed to match rule: %s", err)
103+
continue
104+
}
105+
if ok {
106+
dirPath = MatchedDirPath(ru.StoragePath())
75107
matchedStorageName = matchedStorName(ru.StorageName())
76108
}
77109
}

client/bot/handlers/utils/shortcut/message.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/mediautil"
1515
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
1616
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/re"
17-
"github.com/krau/SaveAny-Bot/client/user"
17+
uc "github.com/krau/SaveAny-Bot/client/user"
1818
"github.com/krau/SaveAny-Bot/common/cache"
1919
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
2020
"github.com/krau/SaveAny-Bot/common/utils/tphutil"
@@ -103,7 +103,7 @@ func GetFilesFromUpdateLinkMessageWithReplyEdit(ctx *ext.Context, update *ext.Up
103103

104104
tctx := ctx
105105
if config.Cfg.Telegram.Userbot.Enable {
106-
tctx = user.GetCtx()
106+
tctx = uc.GetCtx()
107107
}
108108

109109
for _, link := range msgLinks {

0 commit comments

Comments
 (0)