nautd/webui: scripting, labels, settings, set-location, pause fix
Session checkpoint on webui-plugin: - engine dump (nautctl dump) + engine endgame integration - per-file move locations persistence; torrent-level "Set location" with reset/keep-relative/leave-separate handling + residual prune - Lua: naut.get_labels, define_settings/get_setting (script_host struct) - daemon-owned labels (category+tags) + taxonomy persistence; webui write-through - fix: pausing a completed/seeding torrent now sticks (stop wins over result) - automation tab responsive layout; anime_sort label gating + settings Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6dc711cf57
commit
b633b7d216
40 changed files with 3305 additions and 3267 deletions
|
|
@ -9,6 +9,9 @@
|
|||
-- file's last piece verifies, episodes are sorted the moment they're done —
|
||||
-- without waiting for the rest of the torrent.
|
||||
--
|
||||
-- By default it only sorts torrents you have labelled "anime" (REQUIRE_LABEL
|
||||
-- below), read via naut.get_labels(), so non-anime downloads are left untouched.
|
||||
--
|
||||
-- Install:
|
||||
-- nautd --socket /tmp/nautd.sock
|
||||
-- nautctl --socket /tmp/nautd.sock script examples/anime_sort.lua
|
||||
|
|
@ -19,13 +22,42 @@
|
|||
-- test_anime_sort.lua asserts they agree.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- CONFIG — edit these
|
||||
-- CONFIG — these are exposed in the web UI (Automation ▸ Settings) through
|
||||
-- naut.define_settings, so you can change them there WITHOUT editing this file.
|
||||
-- The values below are only the defaults used until you set them in the UI.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
local SORTED_ROOT = "/workspaces/source/ai-garbo/Naut-Torrent/sorted" -- destination library root
|
||||
local ONLY_VIDEO = true -- skip non-video files (subs, nfo, samples)
|
||||
local KEEP_ORIGINAL_NAME = false -- true: keep the original filename;
|
||||
-- false: rename to "Title - SNNENN.ext"
|
||||
local DEFAULTS = {
|
||||
sorted_root = "/workspaces/source/ai-garbo/Naut-Torrent/Downloads/Sorted/",
|
||||
only_video = true, -- skip non-video files (subs, nfo, samples)
|
||||
keep_original_name = false, -- false: rename to "Title - SNNENN.ext"
|
||||
require_label = "anime", -- only sort torrents with this label
|
||||
-- (case-insensitive); blank = any torrent
|
||||
}
|
||||
|
||||
-- Read a setting from the host live (so UI edits apply without a reload),
|
||||
-- falling back to the default when unset or running on an older daemon.
|
||||
local function setting(key)
|
||||
if type(naut) == "table" and type(naut.get_setting) == "function" then
|
||||
local v = naut.get_setting(key)
|
||||
if v ~= nil then return v end
|
||||
end
|
||||
return DEFAULTS[key]
|
||||
end
|
||||
|
||||
-- Declare the configurable variables so the web UI can render a form for them.
|
||||
if type(naut) == "table" and type(naut.define_settings) == "function" then
|
||||
naut.define_settings({
|
||||
{ key = "sorted_root", label = "Library root", type = "string",
|
||||
default = DEFAULTS.sorted_root },
|
||||
{ key = "only_video", label = "Only video files", type = "bool",
|
||||
default = DEFAULTS.only_video },
|
||||
{ key = "keep_original_name", label = "Keep original filename",
|
||||
type = "bool", default = DEFAULTS.keep_original_name },
|
||||
{ key = "require_label", label = "Required label (blank = any)",
|
||||
type = "string", default = DEFAULTS.require_label },
|
||||
})
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- embedded anitomy parser (== examples/anitomy.lua)
|
||||
|
|
@ -240,6 +272,8 @@ local function sanitize(s)
|
|||
end
|
||||
|
||||
local function destination(parsed)
|
||||
local root = setting("sorted_root")
|
||||
local keep_original = setting("keep_original_name")
|
||||
local title = sanitize(parsed.title)
|
||||
local ext = parsed.extension and ("." .. parsed.extension) or ""
|
||||
local original = sanitize(basename(parsed.file_name))
|
||||
|
|
@ -247,18 +281,18 @@ local function destination(parsed)
|
|||
if parsed.episode == nil then
|
||||
-- movie / special: <root>/<title>/<file>
|
||||
local fname
|
||||
if KEEP_ORIGINAL_NAME then
|
||||
if keep_original then
|
||||
fname = original
|
||||
else
|
||||
fname = title .. (parsed.year and (" (" .. parsed.year .. ")") or "") .. ext
|
||||
end
|
||||
return SORTED_ROOT .. "/" .. title .. "/" .. fname
|
||||
return root .. "/" .. title .. "/" .. fname
|
||||
end
|
||||
|
||||
local season = parsed.season or 1
|
||||
local sdir = string.format("Season %02d", season)
|
||||
local fname
|
||||
if KEEP_ORIGINAL_NAME then
|
||||
if keep_original then
|
||||
fname = original
|
||||
else
|
||||
fname = string.format("%s - S%02dE%02d", title, season, parsed.episode)
|
||||
|
|
@ -267,11 +301,25 @@ local function destination(parsed)
|
|||
end
|
||||
fname = fname .. ext
|
||||
end
|
||||
return SORTED_ROOT .. "/" .. title .. "/" .. sdir .. "/" .. fname
|
||||
return root .. "/" .. title .. "/" .. sdir .. "/" .. fname
|
||||
end
|
||||
|
||||
-- True if the torrent carries `want` among its labels (case-insensitive). When
|
||||
-- `want` is nil the gate is disabled. If the daemon predates naut.get_labels we
|
||||
-- can't check, so we sort anyway rather than silently dropping every file.
|
||||
local function has_label(torrent_id, want)
|
||||
if not want or want == "" then return true end
|
||||
if type(naut.get_labels) ~= "function" then return true end
|
||||
want = want:lower()
|
||||
for _, label in ipairs(naut.get_labels(torrent_id)) do
|
||||
if label:lower() == want then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- exposed for tests; harmless in the daemon
|
||||
_G.anime_sort = { anitomy = anitomy, destination = destination }
|
||||
_G.anime_sort = { anitomy = anitomy, destination = destination,
|
||||
has_label = has_label }
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- event hook
|
||||
|
|
@ -279,10 +327,13 @@ _G.anime_sort = { anitomy = anitomy, destination = destination }
|
|||
|
||||
function on_file_complete(event)
|
||||
if not event.path then return end
|
||||
if not has_label(event.torrent_id, setting("require_label")) then
|
||||
return -- not labelled "anime": leave this torrent's files alone
|
||||
end
|
||||
local name = basename(event.path)
|
||||
local parsed = anitomy.parse(name)
|
||||
|
||||
if ONLY_VIDEO and not anitomy.is_video(parsed.extension) then
|
||||
if setting("only_video") and not anitomy.is_video(parsed.extension) then
|
||||
return -- leave subtitles, .nfo, samples, etc. where they are
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue