diff --git a/cmd/dl.go b/cmd/dl.go
index 1c10a9c..b42d7d4 100644
--- a/cmd/dl.go
+++ b/cmd/dl.go
@@ -3,7 +3,6 @@ package cmd
import (
"context"
"fmt"
- "strings"
"github.com/gotd/td/telegram"
"github.com/spf13/cobra"
@@ -48,14 +47,7 @@ func NewDownload() *cobra.Command {
cmd.Flags().StringSliceVarP(&opts.URLs, "url", "u", []string{}, "telegram message links")
cmd.Flags().StringSliceVarP(&opts.Files, file, "f", []string{}, "official client exported files")
- // generate default replacer
- builder := strings.Builder{}
- chars := []string{`/`, `\`, `:`, `*`, `?`, `<`, `>`, `|`, ` `}
- for _, c := range chars {
- builder.WriteString(fmt.Sprintf("`%s` `_` ", c))
- }
- t := fmt.Sprintf(`{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileName %s }}`, builder.String())
- cmd.Flags().String(consts.FlagDlTemplate, t, "download file name template")
+ cmd.Flags().String(consts.FlagDlTemplate, `{{ .DialogID }}_{{ .MessageID }}_{{ filenamify .FileName }}`, "download file name template")
cmd.Flags().StringSliceVarP(&opts.Include, include, "i", []string{}, "include the specified file extensions, and only judge by file name, not file MIME. Example: -i mp4,mp3")
cmd.Flags().StringSliceVarP(&opts.Exclude, exclude, "e", []string{}, "exclude the specified file extensions, and only judge by file name, not file MIME. Example: -e png,jpg")
diff --git a/docs/content/en/guide/template.md b/docs/content/en/guide/template.md
index 66d30f5..b140ad0 100644
--- a/docs/content/en/guide/template.md
+++ b/docs/content/en/guide/template.md
@@ -26,23 +26,24 @@ Template syntax is based on [Go's text/template](https://golang.org/pkg/text/tem
### Functions (beta)
-| Func | Desc | Usage | Example |
-|:------------:|:------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|
-| `repeat` | Repeat `STRING` `N` times | `repeat STRING N` | `{{ repeat "test" 3 }}` |
-| `replace` | Perform replacement on `STRING` with `PAIRS` | `replace STRING PAIRS...` | `{{ replace "Test" "t" "T" "e" "E" }}` |
-| `upper` | Convert `STRING` to uppercase | `upper STRING` | `{{ upper "Test" }}` |
-| `lower` | Convert `STRING` to lowercase | `lower STRING` | `{{ lower "Test" }}` |
-| `snakecase` | Convert `STRING` to snake_case | `snakecase STRING` | `{{ snakecase "Test" }}` |
-| `camelcase` | Convert `STRING` to camelCase | `camelcase STRING` | `{{ camelcase "Test" }}` |
-| `kebabcase` | Convert `STRING` to kebab-case | `kebabcase STRING` | `{{ kebabcase "Test" }}` |
-| `rand` | Generate random number in range `MIN` to `MAX` | `rand MIN MAX` | `{{ rand 1 10 }}` |
-| `now` | Get current timestamp | `now` | `{{ now }}` |
+| Func | Desc | Usage | Example |
+|:------------:|:------------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|
+| `repeat` | Repeat `STRING` `N` times | `repeat STRING N` | `{{ repeat "test" 3 }}` |
+| `replace` | Perform replacement on `STRING` with `PAIRS` | `replace STRING PAIRS...` | `{{ replace "Test" "t" "T" "e" "E" }}` |
+| `upper` | Convert `STRING` to uppercase | `upper STRING` | `{{ upper "Test" }}` |
+| `lower` | Convert `STRING` to lowercase | `lower STRING` | `{{ lower "Test" }}` |
+| `snakecase` | Convert `STRING` to snake_case | `snakecase STRING` | `{{ snakecase "Test" }}` |
+| `camelcase` | Convert `STRING` to camelCase | `camelcase STRING` | `{{ camelcase "Test" }}` |
+| `kebabcase` | Convert `STRING` to kebab-case | `kebabcase STRING` | `{{ kebabcase "Test" }}` |
+| `rand` | Generate random number in range `MIN` to `MAX` | `rand MIN MAX` | `{{ rand 1 10 }}` |
+| `now` | Get current timestamp | `now` | `{{ now }}` |
| `formatDate` | Format `TIMESTAMP` with [format](https://golang.cafe/blog/golang-time-format-example.html)
Default: `20060102150405` | `formatDate TIMESTAMP`
`formatDate TIMESTAMP "format"` | `{{ formatDate 1600000000 }}`
`{{ formatDate 1600000000 "2006-01-02-15-04-05"}}` |
+| `filenamify` | Convert `STRING` to a valid filename with the best effort. | `filenamify STRING` | `{{ filenamify .FileName }}` |
### Examples:
```gotemplate
-{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileName `/` `_` `\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}
+{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileCaption `/` `_` `\` `_` `:` `_` }}
{{ .FileName }}_{{ formatDate .DownloadDate }}_{{ .FileSize }}
@@ -56,5 +57,5 @@ Template syntax is based on [Go's text/template](https://golang.org/pkg/text/tem
### Default:
```gotemplate
-{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileName `/` `_` `\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}
+{{ .DialogID }}_{{ .MessageID }}_{{ filenamify .FileName }}
```
diff --git a/docs/content/zh/guide/template.md b/docs/content/zh/guide/template.md
index 9f4124a..64ce290 100644
--- a/docs/content/zh/guide/template.md
+++ b/docs/content/zh/guide/template.md
@@ -38,11 +38,12 @@ bookToC: false
| `rand` | 在范围 `MIN` 到 `MAX` 生成随机数 | `rand MIN MAX` | `{{ rand 1 10 }}` |
| `now` | 获取当前时间戳 | `now` | `{{ now }}` |
| `formatDate` | [格式化](https://zhuanlan.zhihu.com/p/145009400) `TIMESTAMP` 时间戳
(默认格式: `20060102150405`) | `formatDate TIMESTAMP`
`formatDate TIMESTAMP "format"` | `{{ formatDate 1600000000 }}`
`{{ formatDate 1600000000 "2006-01-02-15-04-05"}}` |
+| `filenamify` | 尽可能将 `STRING` 转换为合法文件名 | `filenamify STRING` | `{{ filenamify .FileName }}` |
### 示例:
```gotemplate
-{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileName `/` `_` `\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}
+{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileCaption `/` `_` `\` `_` `:` `_` }}
{{ .FileName }}_{{ formatDate .DownloadDate }}_{{ .FileSize }}
@@ -56,5 +57,5 @@ bookToC: false
### 默认:
```gotemplate
-{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileName `/` `_` `\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}
+{{ .DialogID }}_{{ .MessageID }}_{{ filenamify .FileName }}
```
diff --git a/go.mod b/go.mod
index 497e1b5..73ba1eb 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
github.com/beevik/ntp v1.4.3
github.com/expr-lang/expr v1.16.9
github.com/fatih/color v1.18.0
+ github.com/flytam/filenamify v1.2.0
github.com/gabriel-vasile/mimetype v1.4.6
github.com/go-faster/errors v0.7.1
github.com/go-faster/jx v1.1.0
diff --git a/go.sum b/go.sum
index fccb6a2..668ea27 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,8 @@ github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
+github.com/flytam/filenamify v1.2.0 h1:7RiSqXYR4cJftDQ5NuvljKMfd/ubKnW/j9C6iekChgI=
+github.com/flytam/filenamify v1.2.0/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
diff --git a/pkg/tplfunc/string.go b/pkg/tplfunc/string.go
index a4f9a93..7a8a6d8 100644
--- a/pkg/tplfunc/string.go
+++ b/pkg/tplfunc/string.go
@@ -4,6 +4,7 @@ import (
"strings"
"text/template"
+ "github.com/flytam/filenamify"
"github.com/iancoleman/strcase"
)
@@ -11,6 +12,7 @@ var String = []Func{
Repeat(), Replace(),
ToUpper(), ToLower(),
SnakeCase(), CamelCase(), KebabCase(),
+ Filenamify(),
}
func Repeat() Func {
@@ -64,3 +66,15 @@ func KebabCase() Func {
}
}
}
+
+func Filenamify() Func {
+ return func(funcMap template.FuncMap) {
+ funcMap["filenamify"] = func(s string) string {
+ res, err := filenamify.FilenamifyV2(s)
+ if err != nil || res == "" {
+ return "invalid-filename"
+ }
+ return res
+ }
+ }
+}