feature: add global.torrent settings card
feature: remember make torrent optionspull/3671/head
parent
9ff78ded0e
commit
1d1cc4b7f8
|
@ -17,4 +17,4 @@ EXPOSE 80
|
||||||
COPY docker_config.json /.filebrowser.json
|
COPY docker_config.json /.filebrowser.json
|
||||||
COPY filebrowser /filebrowser
|
COPY filebrowser /filebrowser
|
||||||
|
|
||||||
ENTRYPOINT [ "/filebrowser" ]
|
ENTRYPOINT [ "/filebrowser" ]
|
||||||
|
|
|
@ -14,4 +14,4 @@ COPY filebrowser /usr/bin/filebrowser
|
||||||
|
|
||||||
# ports and volumes
|
# ports and volumes
|
||||||
VOLUME /srv /config /database
|
VOLUME /srv /config /database
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
|
@ -337,6 +337,9 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||||
Download: true,
|
Download: true,
|
||||||
Torrent: true,
|
Torrent: true,
|
||||||
},
|
},
|
||||||
|
CreateBody: users.CreateTorrentBody{
|
||||||
|
Date: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
AuthMethod: "",
|
AuthMethod: "",
|
||||||
Branding: settings.Branding{},
|
Branding: settings.Branding{},
|
||||||
|
|
|
@ -78,7 +78,7 @@ func addUserFlags(flags *pflag.FlagSet) {
|
||||||
flags.String("locale", "en", "locale for users")
|
flags.String("locale", "en", "locale for users")
|
||||||
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
|
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
|
||||||
flags.Bool("singleClick", false, "use single clicks only")
|
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 {
|
func getViewMode(flags *pflag.FlagSet) users.ViewMode {
|
||||||
|
|
|
@ -48,6 +48,7 @@ options you want to change.`,
|
||||||
Perm: user.Perm,
|
Perm: user.Perm,
|
||||||
Sorting: user.Sorting,
|
Sorting: user.Sorting,
|
||||||
Commands: user.Commands,
|
Commands: user.Commands,
|
||||||
|
CreateBody: user.CreateBody,
|
||||||
}
|
}
|
||||||
getUserDefaults(flags, &defaults, false)
|
getUserDefaults(flags, &defaults, false)
|
||||||
user.Scope = defaults.Scope
|
user.Scope = defaults.Scope
|
||||||
|
@ -57,6 +58,7 @@ options you want to change.`,
|
||||||
user.Perm = defaults.Perm
|
user.Perm = defaults.Perm
|
||||||
user.Commands = defaults.Commands
|
user.Commands = defaults.Commands
|
||||||
user.Sorting = defaults.Sorting
|
user.Sorting = defaults.Sorting
|
||||||
|
user.CreateBody = defaults.CreateBody
|
||||||
user.LockPassword = mustGetBool(flags, "lockPassword")
|
user.LockPassword = mustGetBool(flags, "lockPassword")
|
||||||
|
|
||||||
if newUsername != "" {
|
if newUsername != "" {
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils";
|
import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils";
|
||||||
|
|
||||||
|
export async function fetchDefaultOptions() {
|
||||||
|
const url = `/api/torrent`
|
||||||
|
return fetchJSON(url);
|
||||||
|
}
|
||||||
|
|
||||||
export async function makeTorrent(
|
export async function makeTorrent(
|
||||||
url: string,
|
url: string,
|
||||||
announces: string[],
|
announces: string[],
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="card floating">
|
<div class="card floating">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>
|
<p>
|
||||||
{{ $t("prompts.publishMessageSingle") }}
|
{{ $t("prompts.publishMessage") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
|
@ -65,9 +65,9 @@ export default {
|
||||||
this.req.items[this.selected[0]].url
|
this.req.items[this.selected[0]].url
|
||||||
).then(() => {
|
).then(() => {
|
||||||
buttons.success("publish");
|
buttons.success("publish");
|
||||||
this.$showSuccess(this.$t("prompts.publishSuccess"));
|
this.$showSuccess(this.$t("success.torrentPublished"));
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
buttons.done("delete");
|
buttons.done("publish");
|
||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,44 +2,85 @@
|
||||||
<div class="card floating" style="max-width: 50em;" id="share">
|
<div class="card floating" style="max-width: 50em;" id="share">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("buttons.torrent") }}</h2>
|
<h2>{{ $t("buttons.torrent") }}</h2>
|
||||||
|
<button class="button button--flat button--blue" @click="toggleDetailedView">
|
||||||
|
{{ detailedView ? $t("buttons.compact") : $t("buttons.detailed") }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
<!--
|
||||||
|
<p>{{ $t("prompts.name") }}</p>
|
||||||
|
<input
|
||||||
|
class="input input--block"
|
||||||
|
type="text"
|
||||||
|
v-model.trim="name"
|
||||||
|
tabindex="0" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
<p v-if="detailedView">{{ $t("prompts.pieceLength") }}</p>
|
||||||
|
<select
|
||||||
|
v-if="detailedView"
|
||||||
|
class="input input--block"
|
||||||
|
v-model.trim="pieceLen"
|
||||||
|
tabindex="1">
|
||||||
|
<option value="0">{{ $t("prompts.auto") }}</option>
|
||||||
|
<option value="15">32 KiB</option>
|
||||||
|
<option value="16">64 KiB</option>
|
||||||
|
<option value="17">128 KiB</option>
|
||||||
|
<option value="18">256 KiB</option>
|
||||||
|
<option value="19">512 KiB</option>
|
||||||
|
<option value="20">1 MiB</option>
|
||||||
|
<option value="21">2 MiB</option>
|
||||||
|
<option value="22">4 MiB</option>
|
||||||
|
<option value="23">8 MiB</option>
|
||||||
|
<option value="24">16 MiB</option>
|
||||||
|
<option value="25">32 MiB</option>
|
||||||
|
<option value="26">64 MiB</option>
|
||||||
|
<option value="27">128 MiB</option>
|
||||||
|
<option value="28">256 MiB</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<p>{{ $t("prompts.trackersList") }}</p>
|
<p>{{ $t("prompts.trackersList") }}</p>
|
||||||
<textarea
|
<textarea
|
||||||
class="input input--block input--textarea"
|
class="input input--block input--textarea"
|
||||||
style="min-height: 5em;"
|
style="min-height: 4em;"
|
||||||
type="text"
|
type="text"
|
||||||
v-model.trim="announces"
|
v-model.trim="announces"
|
||||||
tabindex="1"></textarea>
|
tabindex="2"></textarea>
|
||||||
|
|
||||||
<p>{{ $t("prompts.webSeeds") }}</p>
|
<p>{{ $t("prompts.webSeeds") }}</p>
|
||||||
<textarea
|
<textarea
|
||||||
class="input input--block input--textarea"
|
class="input input--block input--textarea"
|
||||||
style="min-height: 5em;"
|
style="min-height: 4em;"
|
||||||
type="text"
|
type="text"
|
||||||
v-model.trim="webSeeds"
|
v-model.trim="webSeeds"
|
||||||
tabindex="2"></textarea>
|
tabindex="3"></textarea>
|
||||||
|
|
||||||
<p>{{ $t("prompts.comment") }}</p>
|
<p>{{ $t("prompts.comment") }}</p>
|
||||||
<textarea
|
<textarea
|
||||||
class="input input--block input--textarea"
|
class="input input--block input--textarea"
|
||||||
style="min-height: 5em;"
|
style="min-height: 4em;"
|
||||||
type="text"
|
type="text"
|
||||||
v-model.trim="comment"
|
v-model.trim="comment"
|
||||||
tabindex="3"></textarea>
|
tabindex="4"></textarea>
|
||||||
|
|
||||||
<p>{{ $t("prompts.source") }}</p>
|
<p v-if="detailedView">{{ $t("prompts.source") }}</p>
|
||||||
<input
|
<input
|
||||||
|
v-if="detailedView"
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
type="text"
|
type="text"
|
||||||
v-model.trim="source"
|
v-model.trim="source"
|
||||||
tabindex="4" />
|
tabindex="5" />
|
||||||
|
|
||||||
<label>
|
<p v-if="detailedView">
|
||||||
<input type="checkbox" v-model="privateFlag" tabindex="3" />
|
<input type="checkbox" v-model="date" tabindex="6" />
|
||||||
|
{{ $t("prompts.includeDate") }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p v-if="detailedView">
|
||||||
|
<input type="checkbox" v-model="privateFlag" tabindex="7" />
|
||||||
{{ $t("prompts.privateTorrent") }}
|
{{ $t("prompts.privateTorrent") }}
|
||||||
</label>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
|
@ -70,10 +111,11 @@ export default {
|
||||||
comment: "",
|
comment: "",
|
||||||
date: true,
|
date: true,
|
||||||
name: "",
|
name: "",
|
||||||
pieceLen: 18,
|
pieceLen: 0,
|
||||||
privateFlag: false,
|
privateFlag: false,
|
||||||
source: "",
|
source: "",
|
||||||
webSeeds: [],
|
webSeeds: [],
|
||||||
|
detailedView: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ["$showError", "$showSuccess"],
|
inject: ["$showError", "$showSuccess"],
|
||||||
|
@ -99,25 +141,21 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async beforeMount() {
|
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: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
async fetchTrackers() {
|
toggleDetailedView() {
|
||||||
const url = 'https://cf.trackerslist.com/all.txt';
|
this.detailedView = !this.detailedView;
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
torrent: async function (event) {
|
torrent: async function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -135,10 +173,10 @@ export default {
|
||||||
this.comment,
|
this.comment,
|
||||||
this.date,
|
this.date,
|
||||||
this.name,
|
this.name,
|
||||||
this.pieceLen,
|
parseInt(this.pieceLen),
|
||||||
this.privateFlag,
|
this.privateFlag,
|
||||||
this.source,
|
this.source,
|
||||||
this.webSeeds,
|
this.webSeeds.split("\n").map((t) => t.trim()).filter((t) => t),
|
||||||
).then(
|
).then(
|
||||||
() => {
|
() => {
|
||||||
buttons.success("torrent");
|
buttons.success("torrent");
|
||||||
|
@ -147,9 +185,9 @@ export default {
|
||||||
this.$showSuccess(this.$t("success.torrentCreated"));
|
this.$showSuccess(this.$t("success.torrentCreated"));
|
||||||
}
|
}
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
buttons.done("torrent");
|
buttons.done("torrent");
|
||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.closeHovers();
|
this.closeHovers();
|
||||||
|
|
|
@ -42,7 +42,9 @@
|
||||||
"openFile": "打开文件",
|
"openFile": "打开文件",
|
||||||
"continue": "继续",
|
"continue": "继续",
|
||||||
"fullScreen": "切换全屏",
|
"fullScreen": "切换全屏",
|
||||||
"discardChanges": "放弃更改"
|
"discardChanges": "放弃更改",
|
||||||
|
"detailed": "转到详细视图",
|
||||||
|
"compact": "转到精简视图"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "下载文件",
|
"downloadFile": "下载文件",
|
||||||
|
@ -144,11 +146,15 @@
|
||||||
"resolution": "分辨率",
|
"resolution": "分辨率",
|
||||||
"deleteUser": "你确定要删除这个用户吗?",
|
"deleteUser": "你确定要删除这个用户吗?",
|
||||||
"discardEditorChanges": "你确定要放弃所做的更改吗?",
|
"discardEditorChanges": "你确定要放弃所做的更改吗?",
|
||||||
|
"pieceLength": "分片大小:",
|
||||||
|
"auto": "自动",
|
||||||
"trackersList": "Tracker URL:",
|
"trackersList": "Tracker URL:",
|
||||||
"comment": "注释:",
|
"comment": "注释:",
|
||||||
"webSeeds": "Web 种子 URL:",
|
"webSeeds": "Web 种子 URL:",
|
||||||
"source": "源:",
|
"source": "源:",
|
||||||
"privateTorrent": "私有torrent(不会在DHT网络上分发)"
|
"includeDate": "写入创建日期",
|
||||||
|
"privateTorrent": "私有 torrent (不会在DHT网络上分发)",
|
||||||
|
"publishMessage": "你确定要发布这个种子吗?"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"images": "图像",
|
"images": "图像",
|
||||||
|
@ -245,7 +251,16 @@
|
||||||
"userManagement": "用户管理",
|
"userManagement": "用户管理",
|
||||||
"userUpdated": "用户已更新!",
|
"userUpdated": "用户已更新!",
|
||||||
"username": "用户名",
|
"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": {
|
"sidebar": {
|
||||||
"help": "帮助",
|
"help": "帮助",
|
||||||
|
@ -262,7 +277,8 @@
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"linkCopied": "链接已复制!",
|
"linkCopied": "链接已复制!",
|
||||||
"torrentCreated": "种子已创建!"
|
"torrentCreated": "种子已创建!",
|
||||||
|
"torrentPublished": "种子已发布!"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"days": "天",
|
"days": "天",
|
||||||
|
|
|
@ -8,6 +8,7 @@ interface ISettings {
|
||||||
tus: SettingsTus;
|
tus: SettingsTus;
|
||||||
shell: string[];
|
shell: string[];
|
||||||
commands: SettingsCommand;
|
commands: SettingsCommand;
|
||||||
|
torrent: SettingsTorrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingsDefaults {
|
interface SettingsDefaults {
|
||||||
|
@ -49,6 +50,13 @@ interface SettingsCommand {
|
||||||
before_upload?: string[];
|
before_upload?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SettingsTorrent {
|
||||||
|
trackersListUrl?: string;
|
||||||
|
qbUrl?: string;
|
||||||
|
qbUsername?: string;
|
||||||
|
qbPassword?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface SettingsUnit {
|
interface SettingsUnit {
|
||||||
KB: number;
|
KB: number;
|
||||||
MB: number;
|
MB: number;
|
||||||
|
|
|
@ -225,6 +225,67 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<form class="card" @submit.prevent="save">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ t("settings.torrentSettings") }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<h3>{{ t("settings.makeTorrent") }}</h3>
|
||||||
|
<p class="small">{{ t("settings.makeTorrentSettingsDescription") }}</p>
|
||||||
|
<p>
|
||||||
|
<label for="trackersListUrl">{{ t("settings.trackersListUrl") }}</label>
|
||||||
|
<input
|
||||||
|
class="input input--block"
|
||||||
|
type="text"
|
||||||
|
v-model="settings.torrent.trackersListUrl"
|
||||||
|
id="trackersListUrl"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>{{ t("settings.publish") }}</h3>
|
||||||
|
<p class="small">{{ t("settings.qbSettingsDescription") }}</p>
|
||||||
|
<p>
|
||||||
|
<label for="qbUrl">{{ t("settings.qbUrl") }}</label>
|
||||||
|
<input
|
||||||
|
class="input input--block"
|
||||||
|
v-model.trim="settings.torrent.qbUrl"
|
||||||
|
id="qbUrl"
|
||||||
|
></input>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="qbUsername">{{ t("settings.qbUsername") }}</label>
|
||||||
|
<input
|
||||||
|
class="input input--block"
|
||||||
|
v-model.trim="settings.torrent.qbUsername"
|
||||||
|
id="qbUsername"
|
||||||
|
></input>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="qbPassword">{{ t("settings.qbPassword") }}</label>
|
||||||
|
<input
|
||||||
|
class="input input--block"
|
||||||
|
type="password"
|
||||||
|
placeholder="●●●●●●●●"
|
||||||
|
v-model.trim="settings.torrent.qbPassword"
|
||||||
|
id="qbPassword"
|
||||||
|
></input>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<input
|
||||||
|
class="button button--flat"
|
||||||
|
type="submit"
|
||||||
|
:value="t('buttons.update')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/filebrowser/filebrowser/v2/runner"
|
"github.com/filebrowser/filebrowser/v2/runner"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
"github.com/filebrowser/filebrowser/v2/storage"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/torrent"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"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 {
|
type data struct {
|
||||||
*runner.Runner
|
*runner.Runner
|
||||||
|
*torrent.Torrent
|
||||||
settings *settings.Settings
|
settings *settings.Settings
|
||||||
server *settings.Server
|
server *settings.Server
|
||||||
store *storage.Storage
|
store *storage.Storage
|
||||||
|
@ -61,6 +63,7 @@ func handle(fn handleFunc, prefix string, store *storage.Storage, server *settin
|
||||||
|
|
||||||
status, err := fn(w, r, &data{
|
status, err := fn(w, r, &data{
|
||||||
Runner: &runner.Runner{Enabled: server.EnableExec, Settings: settings},
|
Runner: &runner.Runner{Enabled: server.EnableExec, Settings: settings},
|
||||||
|
Torrent: &torrent.Torrent{Settings: settings},
|
||||||
store: store,
|
store: store,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
server: server,
|
server: server,
|
||||||
|
|
|
@ -78,6 +78,7 @@ func NewHandler(
|
||||||
api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST")
|
api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST")
|
||||||
api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE")
|
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("/torrent").Handler(monkey(torrentPostHandler, "/api/torrent")).Methods("POST")
|
||||||
api.PathPrefix("/publish").Handler(monkey(publishPostHandler, "/api/publish")).Methods("POST")
|
api.PathPrefix("/publish").Handler(monkey(publishPostHandler, "/api/publish")).Methods("POST")
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,11 @@ type settingsData struct {
|
||||||
Tus settings.Tus `json:"tus"`
|
Tus settings.Tus `json:"tus"`
|
||||||
Shell []string `json:"shell"`
|
Shell []string `json:"shell"`
|
||||||
Commands map[string][]string `json:"commands"`
|
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) {
|
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
data := &settingsData{
|
data := settingsData{
|
||||||
Signup: d.settings.Signup,
|
Signup: d.settings.Signup,
|
||||||
CreateUserDir: d.settings.CreateUserDir,
|
CreateUserDir: d.settings.CreateUserDir,
|
||||||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
UserHomeBasePath: d.settings.UserHomeBasePath,
|
||||||
|
@ -31,8 +32,11 @@ var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request,
|
||||||
Tus: d.settings.Tus,
|
Tus: d.settings.Tus,
|
||||||
Shell: d.settings.Shell,
|
Shell: d.settings.Shell,
|
||||||
Commands: d.settings.Commands,
|
Commands: d.settings.Commands,
|
||||||
|
Torrent: d.settings.Torrent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.Torrent.QbPassword = ""
|
||||||
|
|
||||||
return renderJSON(w, r, data)
|
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.Tus = req.Tus
|
||||||
d.settings.Shell = req.Shell
|
d.settings.Shell = req.Shell
|
||||||
d.settings.Commands = req.Commands
|
d.settings.Commands = req.Commands
|
||||||
|
d.settings.Torrent = req.Torrent
|
||||||
|
|
||||||
err = d.store.Settings.Save(d.settings)
|
err = d.store.Settings.Save(d.settings)
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
|
|
153
http/torrent.go
153
http/torrent.go
|
@ -4,11 +4,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
"github.com/filebrowser/filebrowser/v2/files"
|
||||||
"github.com/filebrowser/filebrowser/v2/torrent"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withPermTorrent(fn handleFunc) handleFunc {
|
func withPermTorrent(fn handleFunc) handleFunc {
|
||||||
|
@ -21,97 +20,15 @@ func withPermTorrent(fn handleFunc) handleFunc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
var torrentGetHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
mktorrent 1.1 (c) 2007, 2009 Emil Renner Berthing
|
// return default torrent options
|
||||||
|
s, err := d.GetDefaultCreateBody(d.user.CreateBody)
|
||||||
Usage: mktorrent [OPTIONS] <target directory or filename>
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, fmt.Errorf("failed to get default create body: %w", err)
|
||||||
Options:
|
|
||||||
-a, --announce=<url>[,<url>]* : specify the full announce URLs
|
|
||||||
at least one is required
|
|
||||||
additional -a adds backup trackers
|
|
||||||
-c, --comment=<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=<n> : set the piece length to 2^n bytes,
|
|
||||||
default is 18, that is 2^18 = 256kb
|
|
||||||
-n, --name=<name> : set the name of the torrent
|
|
||||||
default is the basename of the target
|
|
||||||
-o, --output=<filename> : set the path and filename of the created file
|
|
||||||
default is <name>.torrent
|
|
||||||
-p, --private : set the private flag
|
|
||||||
-s, --source=<source> : add source string embedded in infohash
|
|
||||||
-t, --threads=<n> : use <n> threads for calculating hashes
|
|
||||||
default is the number of CPU cores
|
|
||||||
-v, --verbose : be verbose
|
|
||||||
-w, --web-seed=<url>[,<url>]* : 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 != "" {
|
return renderJSON(w, r, s)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
var torrentPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var torrentPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
file, err := files.NewFileInfo(&files.FileOptions{
|
file, err := files.NewFileInfo(&files.FileOptions{
|
||||||
|
@ -128,8 +45,7 @@ var torrentPostHandler = withPermTorrent(func(w http.ResponseWriter, r *http.Req
|
||||||
}
|
}
|
||||||
fPath := file.RealPath()
|
fPath := file.RealPath()
|
||||||
|
|
||||||
var s *torrent.Torrent
|
var body users.CreateTorrentBody
|
||||||
var body torrent.CreateBody
|
|
||||||
if r.Body != nil {
|
if r.Body != nil {
|
||||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
return http.StatusBadRequest, fmt.Errorf("failed to decode body: %w", err)
|
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()
|
defer r.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 mktorrent 命令的选项
|
err = d.Torrent.MakeTorrent(fPath, body)
|
||||||
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()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s = &torrent.Torrent{
|
d.user.CreateBody = body
|
||||||
Path: fPath + ".torrent",
|
|
||||||
|
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) {
|
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
|
// only folder path
|
||||||
fPath := filepath.Dir(tPath)
|
fPath := filepath.Dir(tPath)
|
||||||
|
|
||||||
qbURL := "http://localhost:8081" // 修改为你的qBittorrent URL
|
torrentPath := tPath
|
||||||
username := "moezakura" // 修改为你的用户名
|
savePath := fPath
|
||||||
password := "moezakura" // 修改为你的密码
|
|
||||||
torrentPath := tPath // 修改为你的本地torrent文件路径
|
|
||||||
savePath := fPath // 修改为你的保存路径
|
|
||||||
|
|
||||||
client := &http.Client{}
|
err = d.Torrent.PublishTorrent(torrentPath, savePath)
|
||||||
|
|
||||||
sid, err := torrent.Login(client, qbURL, username, password)
|
|
||||||
if err != nil {
|
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)
|
return renderJSON(w, r, nil)
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,16 +8,16 @@ import (
|
||||||
// UserDefaults is a type that holds the default values
|
// UserDefaults is a type that holds the default values
|
||||||
// for some fields on User.
|
// for some fields on User.
|
||||||
type UserDefaults struct {
|
type UserDefaults struct {
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
Locale string `json:"locale"`
|
Locale string `json:"locale"`
|
||||||
ViewMode users.ViewMode `json:"viewMode"`
|
ViewMode users.ViewMode `json:"viewMode"`
|
||||||
SingleClick bool `json:"singleClick"`
|
SingleClick bool `json:"singleClick"`
|
||||||
Sorting files.Sorting `json:"sorting"`
|
Sorting files.Sorting `json:"sorting"`
|
||||||
Perm users.Permissions `json:"perm"`
|
Perm users.Permissions `json:"perm"`
|
||||||
Commands []string `json:"commands"`
|
Commands []string `json:"commands"`
|
||||||
HideDotfiles bool `json:"hideDotfiles"`
|
HideDotfiles bool `json:"hideDotfiles"`
|
||||||
DateFormat bool `json:"dateFormat"`
|
DateFormat bool `json:"dateFormat"`
|
||||||
TrackerListsUrl string `json:"trackerListsUrl"`
|
CreateBody users.CreateTorrentBody `json:"createBody"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply applies the default options to a user.
|
// Apply applies the default options to a user.
|
||||||
|
@ -31,5 +31,5 @@ func (d *UserDefaults) Apply(u *users.User) {
|
||||||
u.Commands = d.Commands
|
u.Commands = d.Commands
|
||||||
u.HideDotfiles = d.HideDotfiles
|
u.HideDotfiles = d.HideDotfiles
|
||||||
u.DateFormat = d.DateFormat
|
u.DateFormat = d.DateFormat
|
||||||
u.TrackerListsUrl = d.TrackerListsUrl
|
u.CreateBody = d.CreateBody
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ type Settings struct {
|
||||||
Commands map[string][]string `json:"commands"`
|
Commands map[string][]string `json:"commands"`
|
||||||
Shell []string `json:"shell"`
|
Shell []string `json:"shell"`
|
||||||
Rules []rules.Rule `json:"rules"`
|
Rules []rules.Rule `json:"rules"`
|
||||||
|
Torrent Torrent `json:"torrent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRules implements rules.Provider.
|
// GetRules implements rules.Provider.
|
||||||
|
|
|
@ -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"`
|
||||||
|
}
|
|
@ -135,6 +135,9 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
|
||||||
Share: true,
|
Share: true,
|
||||||
Download: true,
|
Download: true,
|
||||||
},
|
},
|
||||||
|
CreateBody: users.CreateTorrentBody{
|
||||||
|
Date: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package torrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
mktorrent 1.1 (c) 2007, 2009 Emil Renner Berthing
|
||||||
|
|
||||||
|
Usage: mktorrent [OPTIONS] <target directory or filename>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-a, --announce=<url>[,<url>]* : specify the full announce URLs
|
||||||
|
at least one is required
|
||||||
|
additional -a adds backup trackers
|
||||||
|
-c, --comment=<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=<n> : set the piece length to 2^n bytes,
|
||||||
|
default is 18, that is 2^18 = 256kb
|
||||||
|
-n, --name=<name> : set the name of the torrent
|
||||||
|
default is the basename of the target
|
||||||
|
-o, --output=<filename> : set the path and filename of the created file
|
||||||
|
default is <name>.torrent
|
||||||
|
-p, --private : set the private flag
|
||||||
|
-s, --source=<source> : add source string embedded in infohash
|
||||||
|
-t, --threads=<n> : use <n> threads for calculating hashes
|
||||||
|
default is the number of CPU cores
|
||||||
|
-v, --verbose : be verbose
|
||||||
|
-w, --web-seed=<url>[,<url>]* : 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
|
||||||
|
}
|
|
@ -10,9 +10,10 @@ import (
|
||||||
"path/filepath"
|
"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)
|
loginURL := fmt.Sprintf("%s/api/v2/auth/login", qbURL)
|
||||||
data := fmt.Sprintf("username=%s&password=%s", username, password)
|
data := fmt.Sprintf("username=%s&password=%s", username, password)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", loginURL, bytes.NewBufferString(data))
|
req, err := http.NewRequest("POST", loginURL, bytes.NewBufferString(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -39,7 +40,7 @@ func Login(client *http.Client, qbURL, username, password string) (string, error
|
||||||
return "", fmt.Errorf("SID cookie not found")
|
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)
|
addTorrentURL := fmt.Sprintf("%s/api/v2/torrents/add", qbURL)
|
||||||
|
|
||||||
file, err := os.Open(torrentPath)
|
file, err := os.Open(torrentPath)
|
||||||
|
|
|
@ -1,30 +1,124 @@
|
||||||
package torrent
|
package torrent
|
||||||
|
|
||||||
type CreateBody struct {
|
import (
|
||||||
Announces []string `json:"announces"`
|
"fmt"
|
||||||
Comment string `json:"comment"`
|
"io"
|
||||||
Date bool `json:"date"`
|
"net/http"
|
||||||
Name string `json:"name"`
|
"os/exec"
|
||||||
PieceLen int `json:"pieceLen"`
|
"strings"
|
||||||
Private bool `json:"private"`
|
|
||||||
Source string `json:"source"`
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
WebSeeds []string `json:"webSeeds"`
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
}
|
)
|
||||||
|
|
||||||
type Torrent struct {
|
type Torrent struct {
|
||||||
Path string `json:"Path"`
|
*settings.Settings
|
||||||
|
*users.User
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link is the information needed to build a shareable link.
|
func (t *Torrent) MakeTorrent(fPath string, body users.CreateTorrentBody) error {
|
||||||
// type Torrent struct {
|
tPath := fPath + ".torrent"
|
||||||
// Hash string `json:"hash" storm:"id,index"`
|
|
||||||
// Path string `json:"path" storm:"index"`
|
// 设置 mktorrent 命令的选项
|
||||||
// UserID uint `json:"userID"`
|
opts := &TorrentOptions{
|
||||||
// Expire int64 `json:"expire"`
|
Target: fPath,
|
||||||
// PasswordHash string `json:"password_hash,omitempty"`
|
Announces: body.Announces,
|
||||||
// // Token is a random value that will only be set when PasswordHash is set. It is
|
Comment: body.Comment,
|
||||||
// // URL-Safe and is used to download links in password-protected shares via a
|
Date: body.Date,
|
||||||
// // query arg.
|
Name: body.Name,
|
||||||
// Token string `json:"token,omitempty"`
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
|
}
|
|
@ -21,22 +21,22 @@ const (
|
||||||
|
|
||||||
// User describes a user.
|
// User describes a user.
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uint `storm:"id,increment" json:"id"`
|
ID uint `storm:"id,increment" json:"id"`
|
||||||
Username string `storm:"unique" json:"username"`
|
Username string `storm:"unique" json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
Locale string `json:"locale"`
|
Locale string `json:"locale"`
|
||||||
LockPassword bool `json:"lockPassword"`
|
LockPassword bool `json:"lockPassword"`
|
||||||
ViewMode ViewMode `json:"viewMode"`
|
ViewMode ViewMode `json:"viewMode"`
|
||||||
SingleClick bool `json:"singleClick"`
|
SingleClick bool `json:"singleClick"`
|
||||||
Perm Permissions `json:"perm"`
|
Perm Permissions `json:"perm"`
|
||||||
Commands []string `json:"commands"`
|
Commands []string `json:"commands"`
|
||||||
Sorting files.Sorting `json:"sorting"`
|
Sorting files.Sorting `json:"sorting"`
|
||||||
Fs afero.Fs `json:"-" yaml:"-"`
|
Fs afero.Fs `json:"-" yaml:"-"`
|
||||||
Rules []rules.Rule `json:"rules"`
|
Rules []rules.Rule `json:"rules"`
|
||||||
HideDotfiles bool `json:"hideDotfiles"`
|
HideDotfiles bool `json:"hideDotfiles"`
|
||||||
DateFormat bool `json:"dateFormat"`
|
DateFormat bool `json:"dateFormat"`
|
||||||
TrackerListsUrl string `json:"trackerListsUrl"`
|
CreateBody CreateTorrentBody `json:"createBody"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRules implements rules.Provider.
|
// GetRules implements rules.Provider.
|
||||||
|
|
Loading…
Reference in New Issue