mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-06-27 10:11:34 +08:00
Compare commits
2 Commits
refactor/p
...
v0.58.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bc460c609 | ||
|
|
f02860ff3f |
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||||
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/strutil"
|
"github.com/krau/SaveAny-Bot/common/utils/strutil"
|
||||||
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/database"
|
"github.com/krau/SaveAny-Bot/database"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/rule"
|
"github.com/krau/SaveAny-Bot/pkg/rule"
|
||||||
)
|
)
|
||||||
@@ -84,6 +85,46 @@ func handleRuleCmd(ctx *ext.Context, update *ext.Update) error {
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgRuleInfoCreateRuleSuccess, nil)), nil)
|
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgRuleInfoCreateRuleSuccess, nil)), nil)
|
||||||
|
case "preset":
|
||||||
|
// /rule preset <storage> [base_path]
|
||||||
|
if len(args) < 3 {
|
||||||
|
ctx.Reply(update, ext.ReplyTextStyledTextArray(msgelem.BuildRuleHelpStyling(user.ApplyRule, user.Rules)), nil)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
storageName := args[2]
|
||||||
|
if !config.C().HasStorage(user.ChatID, storageName) {
|
||||||
|
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgRuleErrorStorageNotFound, map[string]any{
|
||||||
|
"Storage": storageName,
|
||||||
|
})), nil)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
basePath := ""
|
||||||
|
if len(args) >= 4 {
|
||||||
|
basePath = args[3]
|
||||||
|
}
|
||||||
|
presets := rule.PresetCategories(basePath)
|
||||||
|
imported := 0
|
||||||
|
for _, p := range presets {
|
||||||
|
rd := &database.Rule{
|
||||||
|
Type: rule.FileNameRegex.String(),
|
||||||
|
Data: p.Regex,
|
||||||
|
StorageName: storageName,
|
||||||
|
DirPath: p.Dir,
|
||||||
|
UserID: user.ID,
|
||||||
|
}
|
||||||
|
if err := database.CreateRule(ctx, rd); err != nil {
|
||||||
|
logger.Errorf("failed to create preset rule %s: %s", p.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
imported++
|
||||||
|
}
|
||||||
|
if imported == 0 {
|
||||||
|
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgRuleErrorCreateRuleFailed, nil)), nil)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgRuleInfoPresetImported, map[string]any{
|
||||||
|
"Count": imported,
|
||||||
|
})), nil)
|
||||||
case "del":
|
case "del":
|
||||||
// /rule del <id>
|
// /rule del <id>
|
||||||
if len(args) < 3 {
|
if len(args) < 3 {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ func BuildRuleHelpStyling(enabled bool, rules []database.Rule) []styling.StyledT
|
|||||||
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpSwitchSuffix, nil)),
|
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpSwitchSuffix, nil)),
|
||||||
styling.Code("add"),
|
styling.Code("add"),
|
||||||
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpAddSuffix, nil)),
|
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpAddSuffix, nil)),
|
||||||
|
styling.Code("preset"),
|
||||||
|
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpPresetSuffix, nil)),
|
||||||
styling.Code("del"),
|
styling.Code("del"),
|
||||||
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpDelSuffix, nil)),
|
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpDelSuffix, nil)),
|
||||||
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpExistingRulesPrefix, nil)),
|
styling.Plain(i18n.T(i18nk.BotMsgRuleHelpExistingRulesPrefix, nil)),
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ const (
|
|||||||
BotMsgCommonPromptSelectDefaultDir Key = "bot.msg.common.prompt_select_default_dir"
|
BotMsgCommonPromptSelectDefaultDir Key = "bot.msg.common.prompt_select_default_dir"
|
||||||
BotMsgCommonPromptSelectDefaultStorage Key = "bot.msg.common.prompt_select_default_storage"
|
BotMsgCommonPromptSelectDefaultStorage Key = "bot.msg.common.prompt_select_default_storage"
|
||||||
BotMsgCommonPromptSelectDir Key = "bot.msg.common.prompt_select_dir"
|
BotMsgCommonPromptSelectDir Key = "bot.msg.common.prompt_select_dir"
|
||||||
BotMsgConfigButtonFilenameStrategy Key = "bot.msg.config.button_filename_strategy"
|
|
||||||
BotMsgConfigButtonConflictStrategy Key = "bot.msg.config.button_conflict_strategy"
|
BotMsgConfigButtonConflictStrategy Key = "bot.msg.config.button_conflict_strategy"
|
||||||
|
BotMsgConfigButtonFilenameStrategy Key = "bot.msg.config.button_filename_strategy"
|
||||||
BotMsgConfigConflictStrategyAsk Key = "bot.msg.config.conflict_strategy_ask"
|
BotMsgConfigConflictStrategyAsk Key = "bot.msg.config.conflict_strategy_ask"
|
||||||
BotMsgConfigConflictStrategyOverwrite Key = "bot.msg.config.conflict_strategy_overwrite"
|
BotMsgConfigConflictStrategyOverwrite Key = "bot.msg.config.conflict_strategy_overwrite"
|
||||||
BotMsgConfigConflictStrategyRename Key = "bot.msg.config.conflict_strategy_rename"
|
BotMsgConfigConflictStrategyRename Key = "bot.msg.config.conflict_strategy_rename"
|
||||||
@@ -93,8 +93,8 @@ const (
|
|||||||
BotMsgConfigErrorInvalidCallbackData Key = "bot.msg.config.error_invalid_callback_data"
|
BotMsgConfigErrorInvalidCallbackData Key = "bot.msg.config.error_invalid_callback_data"
|
||||||
BotMsgConfigErrorInvalidTemplate Key = "bot.msg.config.error_invalid_template"
|
BotMsgConfigErrorInvalidTemplate Key = "bot.msg.config.error_invalid_template"
|
||||||
BotMsgConfigFnametmplHelp Key = "bot.msg.config.fnametmpl_help"
|
BotMsgConfigFnametmplHelp Key = "bot.msg.config.fnametmpl_help"
|
||||||
BotMsgConfigInfoCurrentTemplatePrefix Key = "bot.msg.config.info_current_template_prefix"
|
|
||||||
BotMsgConfigInfoConflictStrategySet Key = "bot.msg.config.info_conflict_strategy_set"
|
BotMsgConfigInfoConflictStrategySet Key = "bot.msg.config.info_conflict_strategy_set"
|
||||||
|
BotMsgConfigInfoCurrentTemplatePrefix Key = "bot.msg.config.info_current_template_prefix"
|
||||||
BotMsgConfigInfoFilenameStrategySet Key = "bot.msg.config.info_filename_strategy_set"
|
BotMsgConfigInfoFilenameStrategySet Key = "bot.msg.config.info_filename_strategy_set"
|
||||||
BotMsgConfigInfoTemplateUpdated Key = "bot.msg.config.info_template_updated"
|
BotMsgConfigInfoTemplateUpdated Key = "bot.msg.config.info_template_updated"
|
||||||
BotMsgConfigPromptSelectConflictStrategy Key = "bot.msg.config.prompt_select_conflict_strategy"
|
BotMsgConfigPromptSelectConflictStrategy Key = "bot.msg.config.prompt_select_conflict_strategy"
|
||||||
@@ -200,6 +200,7 @@ const (
|
|||||||
BotMsgRuleErrorGetUserRulesFailed Key = "bot.msg.rule.error_get_user_rules_failed"
|
BotMsgRuleErrorGetUserRulesFailed Key = "bot.msg.rule.error_get_user_rules_failed"
|
||||||
BotMsgRuleErrorInvalidRuleId Key = "bot.msg.rule.error_invalid_rule_id"
|
BotMsgRuleErrorInvalidRuleId Key = "bot.msg.rule.error_invalid_rule_id"
|
||||||
BotMsgRuleErrorInvalidRuleType Key = "bot.msg.rule.error_invalid_rule_type"
|
BotMsgRuleErrorInvalidRuleType Key = "bot.msg.rule.error_invalid_rule_type"
|
||||||
|
BotMsgRuleErrorStorageNotFound Key = "bot.msg.rule.error_storage_not_found"
|
||||||
BotMsgRuleErrorUpdateUserFailed Key = "bot.msg.rule.error_update_user_failed"
|
BotMsgRuleErrorUpdateUserFailed Key = "bot.msg.rule.error_update_user_failed"
|
||||||
BotMsgRuleHelpAddSuffix Key = "bot.msg.rule.help_add_suffix"
|
BotMsgRuleHelpAddSuffix Key = "bot.msg.rule.help_add_suffix"
|
||||||
BotMsgRuleHelpAvailableOps Key = "bot.msg.rule.help_available_ops"
|
BotMsgRuleHelpAvailableOps Key = "bot.msg.rule.help_available_ops"
|
||||||
@@ -207,13 +208,16 @@ const (
|
|||||||
BotMsgRuleHelpCurrentModeEnabled Key = "bot.msg.rule.help_current_mode_enabled"
|
BotMsgRuleHelpCurrentModeEnabled Key = "bot.msg.rule.help_current_mode_enabled"
|
||||||
BotMsgRuleHelpDelSuffix Key = "bot.msg.rule.help_del_suffix"
|
BotMsgRuleHelpDelSuffix Key = "bot.msg.rule.help_del_suffix"
|
||||||
BotMsgRuleHelpExistingRulesPrefix Key = "bot.msg.rule.help_existing_rules_prefix"
|
BotMsgRuleHelpExistingRulesPrefix Key = "bot.msg.rule.help_existing_rules_prefix"
|
||||||
|
BotMsgRuleHelpPresetSuffix Key = "bot.msg.rule.help_preset_suffix"
|
||||||
BotMsgRuleHelpSwitchSuffix Key = "bot.msg.rule.help_switch_suffix"
|
BotMsgRuleHelpSwitchSuffix Key = "bot.msg.rule.help_switch_suffix"
|
||||||
BotMsgRuleHelpUsage Key = "bot.msg.rule.help_usage"
|
BotMsgRuleHelpUsage Key = "bot.msg.rule.help_usage"
|
||||||
BotMsgRuleInfoCreateRuleSuccess Key = "bot.msg.rule.info_create_rule_success"
|
BotMsgRuleInfoCreateRuleSuccess Key = "bot.msg.rule.info_create_rule_success"
|
||||||
BotMsgRuleInfoDeleteRuleSuccess Key = "bot.msg.rule.info_delete_rule_success"
|
BotMsgRuleInfoDeleteRuleSuccess Key = "bot.msg.rule.info_delete_rule_success"
|
||||||
|
BotMsgRuleInfoPresetImported Key = "bot.msg.rule.info_preset_imported"
|
||||||
BotMsgRuleInfoRuleModeDisabled Key = "bot.msg.rule.info_rule_mode_disabled"
|
BotMsgRuleInfoRuleModeDisabled Key = "bot.msg.rule.info_rule_mode_disabled"
|
||||||
BotMsgRuleInfoRuleModeEnabled Key = "bot.msg.rule.info_rule_mode_enabled"
|
BotMsgRuleInfoRuleModeEnabled Key = "bot.msg.rule.info_rule_mode_enabled"
|
||||||
BotMsgRulePromptProvideRuleId Key = "bot.msg.rule.prompt_provide_rule_id"
|
BotMsgRulePromptProvideRuleId Key = "bot.msg.rule.prompt_provide_rule_id"
|
||||||
|
BotMsgRulePromptProvideStorageName Key = "bot.msg.rule.prompt_provide_storage_name"
|
||||||
BotMsgSaveErrorInvalidIdOrUsername Key = "bot.msg.save.error_invalid_id_or_username"
|
BotMsgSaveErrorInvalidIdOrUsername Key = "bot.msg.save.error_invalid_id_or_username"
|
||||||
BotMsgSaveHelpText Key = "bot.msg.save_help_text"
|
BotMsgSaveHelpText Key = "bot.msg.save_help_text"
|
||||||
BotMsgStorageInfoFilenamePrefix Key = "bot.msg.storage.info_filename_prefix"
|
BotMsgStorageInfoFilenamePrefix Key = "bot.msg.storage.info_filename_prefix"
|
||||||
|
|||||||
@@ -196,7 +196,11 @@ bot:
|
|||||||
help_switch_suffix: " - Toggle rule mode\n"
|
help_switch_suffix: " - Toggle rule mode\n"
|
||||||
help_add_suffix: " <type> <data> <storage_name> <path> - Add rule\n"
|
help_add_suffix: " <type> <data> <storage_name> <path> - Add rule\n"
|
||||||
help_del_suffix: " <rule_id> - Delete rule\n"
|
help_del_suffix: " <rule_id> - Delete rule\n"
|
||||||
|
help_preset_suffix: " <storage_name> [base_path] - Import built-in filetype rules (video/image/audio/document/archive)\n"
|
||||||
help_existing_rules_prefix: "\nCurrent rules:\n"
|
help_existing_rules_prefix: "\nCurrent rules:\n"
|
||||||
|
prompt_provide_storage_name: "Please provide a storage name"
|
||||||
|
error_storage_not_found: "Storage not found: {{.Storage}}"
|
||||||
|
info_preset_imported: "Imported {{.Count}} built-in classification rules into storage {{.Storage}}"
|
||||||
dir:
|
dir:
|
||||||
error_get_user_dirs_failed: "Failed to get user directories"
|
error_get_user_dirs_failed: "Failed to get user directories"
|
||||||
error_get_user_failed: "Failed to get user"
|
error_get_user_failed: "Failed to get user"
|
||||||
|
|||||||
@@ -197,7 +197,11 @@ bot:
|
|||||||
help_switch_suffix: " - 开关规则模式\n"
|
help_switch_suffix: " - 开关规则模式\n"
|
||||||
help_add_suffix: " <类型> <数据> <存储名> <路径> - 添加规则\n"
|
help_add_suffix: " <类型> <数据> <存储名> <路径> - 添加规则\n"
|
||||||
help_del_suffix: " <规则ID> - 删除规则\n"
|
help_del_suffix: " <规则ID> - 删除规则\n"
|
||||||
|
help_preset_suffix: " <存储名> [基础路径] - 导入内置文件类型分类规则(视频/图片/音频/文档/压缩包)\n"
|
||||||
help_existing_rules_prefix: "\n当前已添加的规则:\n"
|
help_existing_rules_prefix: "\n当前已添加的规则:\n"
|
||||||
|
prompt_provide_storage_name: "请提供存储名称"
|
||||||
|
error_storage_not_found: "未找到存储: {{.Storage}}"
|
||||||
|
info_preset_imported: "已导入 {{.Count}} 条内置分类规则到存储 {{.Storage}}"
|
||||||
dir:
|
dir:
|
||||||
error_get_user_dirs_failed: "获取用户文件夹失败"
|
error_get_user_dirs_failed: "获取用户文件夹失败"
|
||||||
error_get_user_failed: "获取用户失败"
|
error_get_user_failed: "获取用户失败"
|
||||||
|
|||||||
55
pkg/rule/preset.go
Normal file
55
pkg/rule/preset.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import "path"
|
||||||
|
|
||||||
|
// PresetCategory describes a built-in filetype classification: files whose name
|
||||||
|
// matches Regex are routed into the Dir subdirectory (joined with a user base path).
|
||||||
|
type PresetCategory struct {
|
||||||
|
// Name is a stable identifier for the category (used in logs/messages).
|
||||||
|
Name string
|
||||||
|
// Regex is a FILENAME-REGEX rule data string matching this category's extensions.
|
||||||
|
Regex string
|
||||||
|
// Dir is the default subdirectory name for this category.
|
||||||
|
Dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// presetCategories holds the default filetype classification rules.
|
||||||
|
// Regexes are case-insensitive and match common file extensions.
|
||||||
|
var presetCategories = []PresetCategory{
|
||||||
|
{
|
||||||
|
Name: "video",
|
||||||
|
Regex: `(?i)\.(mp4|mkv|ts|avi|flv|mov|webm|wmv|rmvb|m2ts)$`,
|
||||||
|
Dir: "视频",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "image",
|
||||||
|
Regex: `(?i)\.(jpg|jpeg|png|gif|webp|bmp)$`,
|
||||||
|
Dir: "图片",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "audio",
|
||||||
|
Regex: `(?i)\.(mp3|flac|wav|aac|m4a|ogg)$`,
|
||||||
|
Dir: "音频",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "document",
|
||||||
|
Regex: `(?i)\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|md|csv|epub|mobi|azw3|chm)$`,
|
||||||
|
Dir: "文档",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "archive",
|
||||||
|
Regex: `(?i)\.(zip|rar|7z|tar|gz|bz2|xz|r\d{1,3}|z\d{1,3}|\d{3}|part\d+\.rar|7z\.\d{3})$`,
|
||||||
|
Dir: "压缩包",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresetCategories returns the built-in filetype classification rules with each
|
||||||
|
// category's directory joined under basePath. basePath may be empty.
|
||||||
|
func PresetCategories(basePath string) []PresetCategory {
|
||||||
|
out := make([]PresetCategory, len(presetCategories))
|
||||||
|
for i, c := range presetCategories {
|
||||||
|
c.Dir = path.Join(basePath, c.Dir)
|
||||||
|
out[i] = c
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
55
pkg/rule/preset_test.go
Normal file
55
pkg/rule/preset_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPresetCategoriesCompile(t *testing.T) {
|
||||||
|
for _, c := range PresetCategories("") {
|
||||||
|
if _, err := regexp.Compile(c.Regex); err != nil {
|
||||||
|
t.Errorf("preset %q has invalid regex %q: %v", c.Name, c.Regex, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPresetCategoriesMatch(t *testing.T) {
|
||||||
|
cases := map[string]string{
|
||||||
|
"video": "movie.MP4",
|
||||||
|
"image": "photo.jpg",
|
||||||
|
"audio": "song.flac",
|
||||||
|
"document": "report.pdf",
|
||||||
|
"archive": "backup.zip",
|
||||||
|
}
|
||||||
|
|
||||||
|
byName := make(map[string]*regexp.Regexp)
|
||||||
|
for _, c := range PresetCategories("") {
|
||||||
|
byName[c.Name] = regexp.MustCompile(c.Regex)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, filename := range cases {
|
||||||
|
re, ok := byName[name]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("missing preset category %q", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !re.MatchString(filename) {
|
||||||
|
t.Errorf("preset %q did not match %q", name, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPresetCategoriesBasePath(t *testing.T) {
|
||||||
|
presets := PresetCategories("/media")
|
||||||
|
for _, c := range presets {
|
||||||
|
if c.Dir == "" || c.Dir[0] != '/' {
|
||||||
|
t.Errorf("preset %q dir %q not joined under base path", c.Name, c.Dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Empty base path must not prefix a separator.
|
||||||
|
for _, c := range PresetCategories("") {
|
||||||
|
if c.Dir == "" || c.Dir[0] == '/' {
|
||||||
|
t.Errorf("preset %q dir %q should be relative when base path empty", c.Name, c.Dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user