From 1d1cc4b7f8b19598a424581739342a0527eeea04 Mon Sep 17 00:00:00 2001 From: chalkim Date: Fri, 21 Jun 2024 02:28:47 +0800 Subject: [PATCH] feature: add global.torrent settings card feature: remember make torrent options --- Dockerfile | 2 +- Dockerfile.s6 | 2 +- cmd/root.go | 3 + cmd/users.go | 2 +- cmd/users_update.go | 2 + frontend/src/api/torrent.ts | 5 + frontend/src/components/prompts/Publish.vue | 6 +- frontend/src/components/prompts/Torrent.vue | 106 +++++++++----- frontend/src/i18n/zh-cn.json | 24 ++- frontend/src/types/settings.d.ts | 8 + frontend/src/views/settings/Global.vue | 61 ++++++++ http/data.go | 3 + http/http.go | 1 + http/settings.go | 7 +- http/torrent.go | 153 +++----------------- settings/defaults.go | 22 +-- settings/settings.go | 1 + settings/torrent.go | 8 + storage/bolt/importer/conf.go | 3 + torrent/mktorrent.go | 97 +++++++++++++ torrent/qbittorrent.go | 5 +- torrent/torrent.go | 142 +++++++++++++++--- users/torrent.go | 12 ++ users/users.go | 32 ++-- 24 files changed, 476 insertions(+), 231 deletions(-) create mode 100644 settings/torrent.go create mode 100644 torrent/mktorrent.go create mode 100644 users/torrent.go diff --git a/Dockerfile b/Dockerfile index 2539d9e1..50077cca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,4 +17,4 @@ EXPOSE 80 COPY docker_config.json /.filebrowser.json COPY filebrowser /filebrowser -ENTRYPOINT [ "/filebrowser" ] \ No newline at end of file +ENTRYPOINT [ "/filebrowser" ] diff --git a/Dockerfile.s6 b/Dockerfile.s6 index 2bba24d6..9e4a1e9d 100644 --- a/Dockerfile.s6 +++ b/Dockerfile.s6 @@ -14,4 +14,4 @@ COPY filebrowser /usr/bin/filebrowser # ports and volumes VOLUME /srv /config /database -EXPOSE 80 \ No newline at end of file +EXPOSE 80 diff --git a/cmd/root.go b/cmd/root.go index f08ea498..a40a923d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -337,6 +337,9 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { Download: true, Torrent: true, }, + CreateBody: users.CreateTorrentBody{ + Date: true, + }, }, AuthMethod: "", Branding: settings.Branding{}, diff --git a/cmd/users.go b/cmd/users.go index 69241010..7a604f5a 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -78,7 +78,7 @@ func addUserFlags(flags *pflag.FlagSet) { flags.String("locale", "en", "locale for users") flags.String("viewMode", string(users.ListViewMode), "view mode for users") flags.Bool("singleClick", false, "use single clicks only") - flags.String("trackerListsUrl", "https://cf.trackerslist.com/all.txt", "tracker lists url") + flags.String("trackersListUrl", "https://cf.trackerslist.com/all.txt", "tracker lists url") } func getViewMode(flags *pflag.FlagSet) users.ViewMode { diff --git a/cmd/users_update.go b/cmd/users_update.go index 822bb6dc..2ee54e24 100644 --- a/cmd/users_update.go +++ b/cmd/users_update.go @@ -48,6 +48,7 @@ options you want to change.`, Perm: user.Perm, Sorting: user.Sorting, Commands: user.Commands, + CreateBody: user.CreateBody, } getUserDefaults(flags, &defaults, false) user.Scope = defaults.Scope @@ -57,6 +58,7 @@ options you want to change.`, user.Perm = defaults.Perm user.Commands = defaults.Commands user.Sorting = defaults.Sorting + user.CreateBody = defaults.CreateBody user.LockPassword = mustGetBool(flags, "lockPassword") if newUsername != "" { diff --git a/frontend/src/api/torrent.ts b/frontend/src/api/torrent.ts index ba63309b..16cecf9d 100644 --- a/frontend/src/api/torrent.ts +++ b/frontend/src/api/torrent.ts @@ -1,5 +1,10 @@ import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils"; +export async function fetchDefaultOptions() { + const url = `/api/torrent` + return fetchJSON(url); +} + export async function makeTorrent( url: string, announces: string[], diff --git a/frontend/src/components/prompts/Publish.vue b/frontend/src/components/prompts/Publish.vue index e4835fc0..4f8d4fc5 100644 --- a/frontend/src/components/prompts/Publish.vue +++ b/frontend/src/components/prompts/Publish.vue @@ -2,7 +2,7 @@

- {{ $t("prompts.publishMessageSingle") }} + {{ $t("prompts.publishMessage") }}

@@ -65,9 +65,9 @@ export default { this.req.items[this.selected[0]].url ).then(() => { buttons.success("publish"); - this.$showSuccess(this.$t("prompts.publishSuccess")); + this.$showSuccess(this.$t("success.torrentPublished")); }).catch((e) => { - buttons.done("delete"); + buttons.done("publish"); this.$showError(e); }); }; diff --git a/frontend/src/components/prompts/Torrent.vue b/frontend/src/components/prompts/Torrent.vue index 3440223b..2ed3a0b7 100644 --- a/frontend/src/components/prompts/Torrent.vue +++ b/frontend/src/components/prompts/Torrent.vue @@ -2,44 +2,85 @@

{{ $t("buttons.torrent") }}

+
+ + +

{{ $t("prompts.pieceLength") }}

+ +

{{ $t("prompts.trackersList") }}

+ tabindex="2">

{{ $t("prompts.webSeeds") }}

- + tabindex="3">

{{ $t("prompts.comment") }}

+ tabindex="4"> -

{{ $t("prompts.source") }}

+

{{ $t("prompts.source") }}

+ tabindex="5" /> -
@@ -70,10 +111,11 @@ export default { comment: "", date: true, name: "", - pieceLen: 18, + pieceLen: 0, privateFlag: false, source: "", webSeeds: [], + detailedView: false }; }, inject: ["$showError", "$showSuccess"], @@ -99,25 +141,21 @@ export default { }, }, async beforeMount() { - this.fetchTrackers(); + api.fetchDefaultOptions().then((res) => { + this.announces = res.announces.join("\n"); + this.comment = res.comment; + this.date = res.date; + this.name = res.name; + this.pieceLen = res.pieceLen; + this.privateFlag = res.private; + this.source = res.source; + this.webSeeds = res.webSeeds.join("\n"); + }); }, methods: { ...mapActions(useLayoutStore, ["closeHovers"]), - async fetchTrackers() { - const url = 'https://cf.trackerslist.com/all.txt'; - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(this.$t("error.failedToFetchTrackers")); - } - let text = await response.text(); - text = text.replace(/^\s*[\r\n]/gm, ''); - this.announces = text; - } catch (error) { - this.$showError(error); - text = this.$t("error.faildToFetchTrackers"); - this.announces = text; - } + toggleDetailedView() { + this.detailedView = !this.detailedView; }, torrent: async function (event) { event.preventDefault(); @@ -135,10 +173,10 @@ export default { this.comment, this.date, this.name, - this.pieceLen, + parseInt(this.pieceLen), this.privateFlag, this.source, - this.webSeeds, + this.webSeeds.split("\n").map((t) => t.trim()).filter((t) => t), ).then( () => { buttons.success("torrent"); @@ -147,9 +185,9 @@ export default { this.$showSuccess(this.$t("success.torrentCreated")); } ).catch((e) => { - buttons.done("torrent"); - this.$showError(e); - }); + buttons.done("torrent"); + this.$showError(e); + }); } this.closeHovers(); diff --git a/frontend/src/i18n/zh-cn.json b/frontend/src/i18n/zh-cn.json index db998d71..92a7dcab 100644 --- a/frontend/src/i18n/zh-cn.json +++ b/frontend/src/i18n/zh-cn.json @@ -42,7 +42,9 @@ "openFile": "打开文件", "continue": "继续", "fullScreen": "切换全屏", - "discardChanges": "放弃更改" + "discardChanges": "放弃更改", + "detailed": "转到详细视图", + "compact": "转到精简视图" }, "download": { "downloadFile": "下载文件", @@ -144,11 +146,15 @@ "resolution": "分辨率", "deleteUser": "你确定要删除这个用户吗?", "discardEditorChanges": "你确定要放弃所做的更改吗?", + "pieceLength": "分片大小:", + "auto": "自动", "trackersList": "Tracker URL:", "comment": "注释:", "webSeeds": "Web 种子 URL:", "source": "源:", - "privateTorrent": "私有torrent(不会在DHT网络上分发)" + "includeDate": "写入创建日期", + "privateTorrent": "私有 torrent (不会在DHT网络上分发)", + "publishMessage": "你确定要发布这个种子吗?" }, "search": { "images": "图像", @@ -245,7 +251,16 @@ "userManagement": "用户管理", "userUpdated": "用户已更新!", "username": "用户名", - "users": "用户" + "users": "用户", + "torrentSettings": "BT设置", + "makeTorrent": "制种", + "makeTorrentSettingsDescription": "你可以在此设置BT种子(*.torrent)的默认参数。这些参数可以在创建种子时修改。", + "trackersListUrl": "Tracker URL 列表来源", + "publish": "发布", + "qbSettingsDescription": "你可以在此设置 qBittorrent WebUI 的地址、用户名和密码。", + "qbUrl": "qBittorrent WebUI 地址", + "qbUsername": "qBittorrent WebUI 用户名", + "qbPassword": "qBittorrent WebUI 密码" }, "sidebar": { "help": "帮助", @@ -262,7 +277,8 @@ }, "success": { "linkCopied": "链接已复制!", - "torrentCreated": "种子已创建!" + "torrentCreated": "种子已创建!", + "torrentPublished": "种子已发布!" }, "time": { "days": "天", diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts index a2c19f76..ae989e40 100644 --- a/frontend/src/types/settings.d.ts +++ b/frontend/src/types/settings.d.ts @@ -8,6 +8,7 @@ interface ISettings { tus: SettingsTus; shell: string[]; commands: SettingsCommand; + torrent: SettingsTorrent; } interface SettingsDefaults { @@ -49,6 +50,13 @@ interface SettingsCommand { before_upload?: string[]; } +interface SettingsTorrent { + trackersListUrl?: string; + qbUrl?: string; + qbUsername?: string; + qbPassword?: string; +} + interface SettingsUnit { KB: number; MB: number; diff --git a/frontend/src/views/settings/Global.vue b/frontend/src/views/settings/Global.vue index 40a3ec04..a6c30887 100644 --- a/frontend/src/views/settings/Global.vue +++ b/frontend/src/views/settings/Global.vue @@ -225,6 +225,67 @@
+ +
+
+
+

{{ t("settings.torrentSettings") }}

+
+ +
+

{{ t("settings.makeTorrent") }}

+

{{ t("settings.makeTorrentSettingsDescription") }}

+

+ + +

+ +

{{ t("settings.publish") }}

+

{{ t("settings.qbSettingsDescription") }}

+

+ + +

+ +

+ + +

+ +

+ + +

+
+ +
+ +
+
+
diff --git a/http/data.go b/http/data.go index 5ba87313..60e0eba2 100644 --- a/http/data.go +++ b/http/data.go @@ -11,6 +11,7 @@ import ( "github.com/filebrowser/filebrowser/v2/runner" "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/storage" + "github.com/filebrowser/filebrowser/v2/torrent" "github.com/filebrowser/filebrowser/v2/users" ) @@ -18,6 +19,7 @@ type handleFunc func(w http.ResponseWriter, r *http.Request, d *data) (int, erro type data struct { *runner.Runner + *torrent.Torrent settings *settings.Settings server *settings.Server store *storage.Storage @@ -61,6 +63,7 @@ func handle(fn handleFunc, prefix string, store *storage.Storage, server *settin status, err := fn(w, r, &data{ Runner: &runner.Runner{Enabled: server.EnableExec, Settings: settings}, + Torrent: &torrent.Torrent{Settings: settings}, store: store, settings: settings, server: server, diff --git a/http/http.go b/http/http.go index 830f6fbb..c216040d 100644 --- a/http/http.go +++ b/http/http.go @@ -78,6 +78,7 @@ func NewHandler( api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST") api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE") + api.PathPrefix("/torrent").Handler(monkey(torrentGetHandler, "/api/torrent")).Methods("Get") api.PathPrefix("/torrent").Handler(monkey(torrentPostHandler, "/api/torrent")).Methods("POST") api.PathPrefix("/publish").Handler(monkey(publishPostHandler, "/api/publish")).Methods("POST") diff --git a/http/settings.go b/http/settings.go index de3f22ad..002f6b19 100644 --- a/http/settings.go +++ b/http/settings.go @@ -18,10 +18,11 @@ type settingsData struct { Tus settings.Tus `json:"tus"` Shell []string `json:"shell"` Commands map[string][]string `json:"commands"` + Torrent settings.Torrent `json:"torrent"` } var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - data := &settingsData{ + data := settingsData{ Signup: d.settings.Signup, CreateUserDir: d.settings.CreateUserDir, UserHomeBasePath: d.settings.UserHomeBasePath, @@ -31,8 +32,11 @@ var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, Tus: d.settings.Tus, Shell: d.settings.Shell, Commands: d.settings.Commands, + Torrent: d.settings.Torrent, } + data.Torrent.QbPassword = "" + return renderJSON(w, r, data) }) @@ -52,6 +56,7 @@ var settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request, d.settings.Tus = req.Tus d.settings.Shell = req.Shell d.settings.Commands = req.Commands + d.settings.Torrent = req.Torrent err = d.store.Settings.Save(d.settings) return errToStatus(err), err diff --git a/http/torrent.go b/http/torrent.go index 3686bb68..9f146325 100644 --- a/http/torrent.go +++ b/http/torrent.go @@ -4,11 +4,10 @@ import ( "encoding/json" "fmt" "net/http" - "os/exec" "path/filepath" "github.com/filebrowser/filebrowser/v2/files" - "github.com/filebrowser/filebrowser/v2/torrent" + "github.com/filebrowser/filebrowser/v2/users" ) func withPermTorrent(fn handleFunc) handleFunc { @@ -21,97 +20,15 @@ func withPermTorrent(fn handleFunc) handleFunc { }) } -/* -mktorrent 1.1 (c) 2007, 2009 Emil Renner Berthing - -Usage: mktorrent [OPTIONS] - -Options: --a, --announce=[,]* : specify the full announce URLs - at least one is required - additional -a adds backup trackers --c, --comment= : add a comment to the metainfo --d, --no-date : don't write the creation date --h, --help : show this help screen --l, --piece-length= : set the piece length to 2^n bytes, - default is 18, that is 2^18 = 256kb --n, --name= : set the name of the torrent - default is the basename of the target --o, --output= : set the path and filename of the created file - default is .torrent --p, --private : set the private flag --s, --source= : add source string embedded in infohash --t, --threads= : use threads for calculating hashes - default is the number of CPU cores --v, --verbose : be verbose --w, --web-seed=[,]* : add web seed URLs - additional -w adds more URLs -*/ -// TorrentOptions 结构体定义了 mktorrent 命令的选项 -type TorrentOptions struct { - Target string // 目标文件或目录路径 - Name string // 种子名称 - OutputFile string // 输出种子文件的路径和文件名 - Announces []string // 主要的 tracker URLs - Comment string // 添加的注释 - Date bool // 是否写入创建日期 - PieceLen int // 设置块大小 - Private bool // 是否设置为私有种子 - Source string // 添加到 infohash 中的源字符串 - Threads int // 使用的线程数 - WebSeeds []string // Web seed URLs -} - -// buildArgs 函数根据 TorrentOptions 构建 mktorrent 命令的参数列表 -func buildArgs(opts TorrentOptions) []string { - args := []string{} - - for _, announce := range opts.Announces { - args = append(args, "-a", announce) +var torrentGetHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + // return default torrent options + s, err := d.GetDefaultCreateBody(d.user.CreateBody) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("failed to get default create body: %w", err) } - if opts.Comment != "" { - args = append(args, "-c", opts.Comment) - } - - if !opts.Date { - args = append(args, "-d") - } - - if opts.PieceLen > 0 { - args = append(args, "-l", fmt.Sprintf("%d", opts.PieceLen)) - } - - if opts.Name != "" { - args = append(args, "-n", opts.Name) - } - - if opts.OutputFile != "" { - args = append(args, "-o", opts.OutputFile) - } - - if opts.Private { - args = append(args, "-p") - } - - if opts.Source != "" { - args = append(args, "-s", opts.Source) - } - - if opts.Threads > 0 { - args = append(args, "-t", fmt.Sprintf("%d", opts.Threads)) - } - - if len(opts.WebSeeds) > 0 { - for _, ws := range opts.WebSeeds { - args = append(args, "-w", ws) - } - } - - args = append(args, opts.Target) - - return args -} + return renderJSON(w, r, s) +}) var torrentPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { file, err := files.NewFileInfo(&files.FileOptions{ @@ -128,8 +45,7 @@ var torrentPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Req } fPath := file.RealPath() - var s *torrent.Torrent - var body torrent.CreateBody + var body users.CreateTorrentBody if r.Body != nil { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { return http.StatusBadRequest, fmt.Errorf("failed to decode body: %w", err) @@ -137,36 +53,19 @@ var torrentPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Req defer r.Body.Close() } - // 设置 mktorrent 命令的选项 - options := TorrentOptions{ - Target: fPath, - Announces: body.Announces, - Comment: body.Comment, - Date: body.Date, - Name: body.Name, - OutputFile: fPath + ".torrent", - PieceLen: body.PieceLen, - Private: body.Private, - Source: body.Source, - WebSeeds: body.WebSeeds, - } - - // 构建 mktorrent 命令的参数列表 - args := buildArgs(options) - - cmd := exec.Command("mktorrent", args...) - - err = cmd.Run() - + err = d.Torrent.MakeTorrent(fPath, body) if err != nil { return http.StatusInternalServerError, err } - s = &torrent.Torrent{ - Path: fPath + ".torrent", + d.user.CreateBody = body + + err = d.store.Users.Update(d.user) + if err != nil { + return http.StatusInternalServerError, err } - return renderJSON(w, r, s) + return renderJSON(w, r, nil) }) var publishPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { @@ -186,25 +85,13 @@ var publishPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Req // only folder path fPath := filepath.Dir(tPath) - qbURL := "http://localhost:8081" // 修改为你的qBittorrent URL - username := "moezakura" // 修改为你的用户名 - password := "moezakura" // 修改为你的密码 - torrentPath := tPath // 修改为你的本地torrent文件路径 - savePath := fPath // 修改为你的保存路径 + torrentPath := tPath + savePath := fPath - client := &http.Client{} - - sid, err := torrent.Login(client, qbURL, username, password) + err = d.Torrent.PublishTorrent(torrentPath, savePath) if err != nil { - fmt.Printf("Error logging in: %v\n", err) + return http.StatusInternalServerError, err } - err = torrent.AddTorrent(client, qbURL, sid, torrentPath, savePath) - if err != nil { - fmt.Printf("Error adding torrent: %v\n", err) - } - - fmt.Println("Torrent added successfully!") - return renderJSON(w, r, nil) }) diff --git a/settings/defaults.go b/settings/defaults.go index cd0484e0..0d7cd0f4 100644 --- a/settings/defaults.go +++ b/settings/defaults.go @@ -8,16 +8,16 @@ import ( // UserDefaults is a type that holds the default values // for some fields on User. type UserDefaults struct { - Scope string `json:"scope"` - Locale string `json:"locale"` - ViewMode users.ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Sorting files.Sorting `json:"sorting"` - Perm users.Permissions `json:"perm"` - Commands []string `json:"commands"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` - TrackerListsUrl string `json:"trackerListsUrl"` + Scope string `json:"scope"` + Locale string `json:"locale"` + ViewMode users.ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + Sorting files.Sorting `json:"sorting"` + Perm users.Permissions `json:"perm"` + Commands []string `json:"commands"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + CreateBody users.CreateTorrentBody `json:"createBody"` } // Apply applies the default options to a user. @@ -31,5 +31,5 @@ func (d *UserDefaults) Apply(u *users.User) { u.Commands = d.Commands u.HideDotfiles = d.HideDotfiles u.DateFormat = d.DateFormat - u.TrackerListsUrl = d.TrackerListsUrl + u.CreateBody = d.CreateBody } diff --git a/settings/settings.go b/settings/settings.go index 22908396..c4ac803d 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -27,6 +27,7 @@ type Settings struct { Commands map[string][]string `json:"commands"` Shell []string `json:"shell"` Rules []rules.Rule `json:"rules"` + Torrent Torrent `json:"torrent"` } // GetRules implements rules.Provider. diff --git a/settings/torrent.go b/settings/torrent.go new file mode 100644 index 00000000..373da377 --- /dev/null +++ b/settings/torrent.go @@ -0,0 +1,8 @@ +package settings + +type Torrent struct { + TrackersListUrl string `json:"trackersListUrl"` + QbUrl string `json:"qbUrl"` + QbUsername string `json:"qbUsername"` + QbPassword string `json:"qbPassword"` +} diff --git a/storage/bolt/importer/conf.go b/storage/bolt/importer/conf.go index bafeb452..7898bcc4 100644 --- a/storage/bolt/importer/conf.go +++ b/storage/bolt/importer/conf.go @@ -135,6 +135,9 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error { Share: true, Download: true, }, + CreateBody: users.CreateTorrentBody{ + Date: true, + }, }, } diff --git a/torrent/mktorrent.go b/torrent/mktorrent.go new file mode 100644 index 00000000..969820a1 --- /dev/null +++ b/torrent/mktorrent.go @@ -0,0 +1,97 @@ +package torrent + +import ( + "fmt" +) + +/* +mktorrent 1.1 (c) 2007, 2009 Emil Renner Berthing + +Usage: mktorrent [OPTIONS] + +Options: +-a, --announce=[,]* : specify the full announce URLs + at least one is required + additional -a adds backup trackers +-c, --comment= : add a comment to the metainfo +-d, --no-date : don't write the creation date +-h, --help : show this help screen +-l, --piece-length= : set the piece length to 2^n bytes, + default is 18, that is 2^18 = 256kb +-n, --name= : set the name of the torrent + default is the basename of the target +-o, --output= : set the path and filename of the created file + default is .torrent +-p, --private : set the private flag +-s, --source= : add source string embedded in infohash +-t, --threads= : use threads for calculating hashes + default is the number of CPU cores +-v, --verbose : be verbose +-w, --web-seed=[,]* : add web seed URLs + additional -w adds more URLs +*/ +// TorrentOptions 结构体定义了 mktorrent 命令的选项 +type TorrentOptions struct { + Target string // 目标文件或目录路径 + Name string // 种子名称 + OutputFile string // 输出种子文件的路径和文件名 + Announces []string // 主要的 tracker URLs + Comment string // 添加的注释 + Date bool // 是否写入创建日期 + PieceLen int // 设置块大小 + Private bool // 是否设置为私有种子 + Source string // 添加到 infohash 中的源字符串 + Threads int // 使用的线程数 + WebSeeds []string // Web seed URLs +} + +// buildArgs 函数根据 TorrentOptions 构建 mktorrent 命令的参数列表 +func buildArgs(opts *TorrentOptions) []string { + args := []string{} + + for _, announce := range opts.Announces { + args = append(args, "-a", announce) + } + + if opts.Comment != "" { + args = append(args, "-c", opts.Comment) + } + + if !opts.Date { + args = append(args, "-d") + } + + if opts.PieceLen > 0 { + args = append(args, "-l", fmt.Sprintf("%d", opts.PieceLen)) + } + + if opts.Name != "" { + args = append(args, "-n", opts.Name) + } + + if opts.OutputFile != "" { + args = append(args, "-o", opts.OutputFile) + } + + if opts.Private { + args = append(args, "-p") + } + + if opts.Source != "" { + args = append(args, "-s", opts.Source) + } + + if opts.Threads > 0 { + args = append(args, "-t", fmt.Sprintf("%d", opts.Threads)) + } + + if len(opts.WebSeeds) > 0 { + for _, ws := range opts.WebSeeds { + args = append(args, "-w", ws) + } + } + + args = append(args, opts.Target) + + return args +} diff --git a/torrent/qbittorrent.go b/torrent/qbittorrent.go index 52203699..80c6a40c 100644 --- a/torrent/qbittorrent.go +++ b/torrent/qbittorrent.go @@ -10,9 +10,10 @@ import ( "path/filepath" ) -func Login(client *http.Client, qbURL, username, password string) (string, error) { +func login(client *http.Client, qbURL, username, password string) (string, error) { loginURL := fmt.Sprintf("%s/api/v2/auth/login", qbURL) data := fmt.Sprintf("username=%s&password=%s", username, password) + req, err := http.NewRequest("POST", loginURL, bytes.NewBufferString(data)) if err != nil { return "", err @@ -39,7 +40,7 @@ func Login(client *http.Client, qbURL, username, password string) (string, error return "", fmt.Errorf("SID cookie not found") } -func AddTorrent(client *http.Client, qbURL, sid, torrentPath, savePath string) error { +func addTorrent(client *http.Client, qbURL, sid, torrentPath, savePath string) error { addTorrentURL := fmt.Sprintf("%s/api/v2/torrents/add", qbURL) file, err := os.Open(torrentPath) diff --git a/torrent/torrent.go b/torrent/torrent.go index 2dcb9cce..be058d0a 100644 --- a/torrent/torrent.go +++ b/torrent/torrent.go @@ -1,30 +1,124 @@ package torrent -type CreateBody struct { - Announces []string `json:"announces"` - Comment string `json:"comment"` - Date bool `json:"date"` - Name string `json:"name"` - PieceLen int `json:"pieceLen"` - Private bool `json:"private"` - Source string `json:"source"` - WebSeeds []string `json:"webSeeds"` -} +import ( + "fmt" + "io" + "net/http" + "os/exec" + "strings" + + "github.com/filebrowser/filebrowser/v2/settings" + "github.com/filebrowser/filebrowser/v2/users" +) type Torrent struct { - Path string `json:"Path"` + *settings.Settings + *users.User } -// Link is the information needed to build a shareable link. -// type Torrent struct { -// Hash string `json:"hash" storm:"id,index"` -// Path string `json:"path" storm:"index"` -// UserID uint `json:"userID"` -// Expire int64 `json:"expire"` -// PasswordHash string `json:"password_hash,omitempty"` -// // Token is a random value that will only be set when PasswordHash is set. It is -// // URL-Safe and is used to download links in password-protected shares via a -// // query arg. -// Token string `json:"token,omitempty"` -// } -// +func (t *Torrent) MakeTorrent(fPath string, body users.CreateTorrentBody) error { + tPath := fPath + ".torrent" + + // 设置 mktorrent 命令的选项 + opts := &TorrentOptions{ + Target: fPath, + Announces: body.Announces, + Comment: body.Comment, + Date: body.Date, + Name: body.Name, + OutputFile: tPath, + PieceLen: body.PieceLen, + Private: body.Private, + Source: body.Source, + WebSeeds: body.WebSeeds, + } + + args := buildArgs(opts) + + cmd := exec.Command("mktorrent", args...) + + err := cmd.Run() + if err != nil { + return err + } + + return nil +} + +func (t *Torrent) PublishTorrent(torrentPath string, savePath string) error { + qbURL := t.Settings.Torrent.QbUrl + username := t.Settings.Torrent.QbUsername + password := t.Settings.Torrent.QbPassword + + client := &http.Client{} + sid, err := login(client, qbURL, username, password) + if err != nil { + return fmt.Errorf("failed to login: %v", err) + } + + err = addTorrent(client, qbURL, sid, torrentPath, savePath) + if err != nil { + return fmt.Errorf("failed to add torrent: %v", err) + } + + return nil +} + +func (t *Torrent) GetDefaultCreateBody(createBody users.CreateTorrentBody) (*users.CreateTorrentBody, error) { + announces, err := fetchTrackerList(t.Settings.Torrent.TrackersListUrl) + if err != nil { + return nil, fmt.Errorf("failed to fetch tracker list: %v", err) + } + + if createBody.Announces == nil { + createBody.Announces = []string{} + } + + if createBody.WebSeeds == nil { + createBody.WebSeeds = []string{} + } + + return &users.CreateTorrentBody{ + Announces: announces, + Comment: createBody.Comment, + Date: createBody.Date, + Name: "", + PieceLen: createBody.PieceLen, + Private: createBody.Private, + Source: createBody.Source, + WebSeeds: createBody.WebSeeds, + }, nil +} + +func fetchTrackerList(url string) ([]string, error) { + // 发送HTTP GET请求 + response, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("failed to fetch trackers: %v", err) + } + defer response.Body.Close() + + // 检查响应状态 + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch trackers: status code %d", response.StatusCode) + } + + // 读取响应体 + body, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + + // 移除空行 + text := string(body) + text = strings.TrimSpace(text) + lines := strings.Split(text, "\n") + var filteredLines []string + for _, line := range lines { + if strings.TrimSpace(line) != "" { + filteredLines = append(filteredLines, line) + } + } + + return filteredLines, nil +} diff --git a/users/torrent.go b/users/torrent.go new file mode 100644 index 00000000..0a932125 --- /dev/null +++ b/users/torrent.go @@ -0,0 +1,12 @@ +package users + +type CreateTorrentBody struct { + Announces []string `json:"announces"` + Comment string `json:"comment"` + Date bool `json:"date"` + Name string `json:"name"` + PieceLen int `json:"pieceLen"` + Private bool `json:"private"` + Source string `json:"source"` + WebSeeds []string `json:"webSeeds"` +} diff --git a/users/users.go b/users/users.go index 367086aa..86f91c7e 100644 --- a/users/users.go +++ b/users/users.go @@ -21,22 +21,22 @@ const ( // User describes a user. type User struct { - ID uint `storm:"id,increment" json:"id"` - Username string `storm:"unique" json:"username"` - Password string `json:"password"` - Scope string `json:"scope"` - Locale string `json:"locale"` - LockPassword bool `json:"lockPassword"` - ViewMode ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Perm Permissions `json:"perm"` - Commands []string `json:"commands"` - Sorting files.Sorting `json:"sorting"` - Fs afero.Fs `json:"-" yaml:"-"` - Rules []rules.Rule `json:"rules"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` - TrackerListsUrl string `json:"trackerListsUrl"` + ID uint `storm:"id,increment" json:"id"` + Username string `storm:"unique" json:"username"` + Password string `json:"password"` + Scope string `json:"scope"` + Locale string `json:"locale"` + LockPassword bool `json:"lockPassword"` + ViewMode ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + Perm Permissions `json:"perm"` + Commands []string `json:"commands"` + Sorting files.Sorting `json:"sorting"` + Fs afero.Fs `json:"-" yaml:"-"` + Rules []rules.Rule `json:"rules"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + CreateBody CreateTorrentBody `json:"createBody"` } // GetRules implements rules.Provider.