"""Auto formats in the app: the /start menu's Auto Format screen, Use for auto rename, copying a format to the other kind,
Apply default metadata, and what the files are renamed with."""
import pytest
from bot.db import MAX_FORMATS
from bot.formats import apply_format
from .appkit import ADMIN_CFG, EPISODE_NEW, OWNER, autofmt, click, data_of, jpeg, prefs, say, send_doc
from .test_formats_app import ONE_PIECE, labels_of, make_format
DEFAULT_META = {"title": "My Channel", "author": "@MyChannel", "artist": "", "audio": "{lang} Audio", "subtitle": "{lang}", "video": "HD"}
async def set_default_metadata(ctx, **over):
await ctx.db.update_settings(OWNER, meta=dict(DEFAULT_META), keep_audio=["japanese", "english"], keep_subtitle=["english"], **over)
def latest_job(ctx):
return list(ctx.app.queue.jobs.values())[-1]
# ---------------------------------------------------------------------------
# the Auto Format screen of the /start menu
# ---------------------------------------------------------------------------
async def test_the_start_menu_opens_the_auto_format_screen(ctx):
ev = await say(ctx, OWNER, "/start")
text, buttons, _kw = ev.out[0]
assert "Auto format: none saved" in text and "m:auto" in data_of(buttons) and "Auto Format" in labels_of(buttons)
cb = await click(ctx, OWNER, "m:auto")
text, buttons = cb.edits[-1]
assert "AUTO RENAME FORMAT" in text and "no auto formats yet" in text and cb.answers
data = data_of(buttons)
assert {"fm:new:auto", "fm:l:auto", "m:home", "x:close"} <= set(data) and not any(d.startswith("au:") for d in data)
async def test_the_screen_lists_the_auto_formats_and_marks_the_one_in_use(ctx):
a = await make_format(ctx, "One Piece", kind="auto", template="OP - E{episode}")
b = await make_format(ctx, "Dragon Ball", kind="auto", template="DB - E{episode}")
manual = await make_format(ctx, "Manual one")
cb = await click(ctx, OWNER, "m:auto")
text, buttons = cb.edits[-1]
data = data_of(buttons)
assert f"au:{a['id']}" in data and f"au:{b['id']}" in data and f"au:{manual['id']}" not in data
assert labels_of(buttons)[:2] == ["One Piece ✅", "Dragon Ball"] # the first one is in use from the start
assert f"fm:v:{a['id']}" in data # "Edit the one in use"
cb = await click(ctx, OWNER, f"au:{b['id']}")
assert cb.toasts == ["Auto format: Dragon Ball"] and labels_of(cb.edits[-1][1])[:2] == ["One Piece", "Dragon Ball ✅"]
assert (await prefs(ctx))["auto_format"] == b["id"] and (await autofmt(ctx))["id"] == b["id"]
data = data_of(cb.edits[-1][1])
assert f"fm:v:{b['id']}" in data and f"fm:v:{a['id']}" not in data # the edit button follows the choice
ev = await say(ctx, OWNER, "/start") # and the menu shows what is in use
assert "Auto format: Dragon Ball" in ev.texts[0] and "DB - E{episode}" in ev.texts[0]
async def test_a_stale_or_foreign_choice_is_refused(ctx):
manual = await make_format(ctx, "Manual one")
theirs = await make_format(ctx, "Theirs", uid=ADMIN_CFG, kind="auto")
await make_format(ctx, "Mine", kind="auto")
for data in (f"au:{manual['id']}", f"au:{theirs['id']}", "au:999999", "au:x", "au"):
cb = await click(ctx, OWNER, data)
assert cb.alerts == ["That format no longer exists."] and "AUTO RENAME FORMAT" in cb.edits[-1][0], data
assert (await autofmt(ctx))["name"] == "Mine"
async def test_each_user_has_their_own_auto_format(ctx):
mine = await make_format(ctx, "Mine", kind="auto", template="Mine - {title}")
theirs = await make_format(ctx, "Theirs", uid=ADMIN_CFG, kind="auto", template="Theirs - {title}")
await send_doc(ctx, OWNER, ONE_PIECE, mid=50)
await send_doc(ctx, ADMIN_CFG, ONE_PIECE, mid=51)
jobs = {j.user_id: j for j in ctx.app.queue.jobs.values()}
assert jobs[OWNER].new_name == "Mine - One Piece.mkv" and jobs[OWNER].fmt["id"] == mine["id"]
assert jobs[ADMIN_CFG].new_name == "Theirs - One Piece.mkv" and jobs[ADMIN_CFG].fmt["id"] == theirs["id"]
# ---------------------------------------------------------------------------
# what the files are renamed with
# ---------------------------------------------------------------------------
async def test_the_auto_format_in_use_renames_every_file_sent(ctx):
a = await make_format(ctx, "One Piece", kind="auto", template="OP - E{episode}")
b = await make_format(ctx, "Dragon Ball", kind="auto", template="DB - E{episode}")
ev = await send_doc(ctx, OWNER, ONE_PIECE, mid=50)
job = latest_job(ctx)
assert job.new_name == "OP - E1147.mkv" and job.fmt["id"] == a["id"] and job.fmt["kind"] == "auto"
assert "🗂 Auto format: One Piece" in ev.texts[0]
await click(ctx, OWNER, f"au:{b['id']}")
ev = await send_doc(ctx, OWNER, ONE_PIECE, mid=51)
job = latest_job(ctx)
assert job.new_name == "DB - E1147.mkv" and job.fmt["id"] == b["id"] and "🗂 Auto format: Dragon Ball" in ev.texts[0]
async def test_without_an_auto_format_files_get_the_built_in_template(ctx):
await make_format(ctx, "Manual one") # a manual format is never used by itself
ev = await send_doc(ctx, OWNER)
(job,) = ctx.app.queue.jobs.values()
assert job.fmt is None and job.new_name == EPISODE_NEW and "Auto format" not in ev.texts[0]
async def test_the_padded_episode_variables_in_an_auto_format(ctx):
await make_format(ctx, "Padded", kind="auto", template="{title} - {1episode} {2episode} {3episode}")
for mid, name, expected in (
(50, "One Piece - 4 [1080p].mkv", "One Piece - 04 004 0004.mkv"),
(51, "One Piece - 321 [1080p].mkv", "One Piece - 321 321 0321.mkv"),
(52, "One Piece - 1321 [1080p].mkv", "One Piece - 1321 1321 1321.mkv"),
):
await send_doc(ctx, OWNER, name, mid=mid)
assert latest_job(ctx).new_name == expected
async def test_the_first_auto_format_is_used_at_once_and_deleting_the_one_in_use_hands_over(ctx):
await click(ctx, OWNER, "fm:new:auto", msg_id=500)
await say(ctx, OWNER, "One Piece")
(a,) = await ctx.db.list_formats(OWNER, "auto")
_chat, text, kw = ctx.client.edited[-1]
assert "✅ In use" in text and f"fm:use:{a['id']}" not in data_of(kw["buttons"])
assert (await autofmt(ctx))["id"] == a["id"]
await make_format(ctx, "Dragon Ball", kind="auto")
await click(ctx, OWNER, f"fm:del:{a['id']}")
cb = await click(ctx, OWNER, f"fm:ok:{a['id']}") # the one in use goes: the other takes over
assert "AUTO FORMATS" in cb.edits[-1][0] and "Dragon Ball ✅" in labels_of(cb.edits[-1][1])
last = await autofmt(ctx)
assert last["name"] == "Dragon Ball"
await click(ctx, OWNER, f"fm:ok:{last['id']}") # the last one goes: back to the built-in defaults
assert await autofmt(ctx) is None
await send_doc(ctx, OWNER)
(job,) = ctx.app.queue.jobs.values()
assert job.fmt is None and job.new_name == EPISODE_NEW
async def test_use_for_auto_rename_button(ctx):
a = await make_format(ctx, "One Piece", kind="auto")
b = await make_format(ctx, "Dragon Ball", kind="auto")
cb = await click(ctx, OWNER, f"fm:v:{a['id']}")
assert "✅ In use" in cb.edits[-1][0] and f"fm:use:{a['id']}" not in data_of(cb.edits[-1][1])
cb = await click(ctx, OWNER, f"fm:v:{b['id']}")
text, buttons = cb.edits[-1]
assert "Not in use" in text and f"fm:use:{b['id']}" in data_of(buttons) and "✅ Use for auto rename" in labels_of(buttons)
cb = await click(ctx, OWNER, f"fm:use:{b['id']}")
text, buttons = cb.edits[-1]
assert cb.toasts == ["Auto format: Dragon Ball"] and "now renames your files" in text and "✅ In use" in text
assert f"fm:use:{b['id']}" not in data_of(buttons) and (await autofmt(ctx))["id"] == b["id"]
cb = await click(ctx, OWNER, f"fm:v:{a['id']}") # the other one can be used again
assert f"fm:use:{a['id']}" in data_of(cb.edits[-1][1])
async def test_a_manual_format_cannot_be_used_for_auto_rename(ctx):
auto = await make_format(ctx, "Auto one", kind="auto")
manual = await make_format(ctx, "Manual one")
cb = await click(ctx, OWNER, f"fm:v:{manual['id']}")
assert f"fm:use:{manual['id']}" not in data_of(cb.edits[-1][1]) and "Use it with /rename" in cb.edits[-1][0]
cb = await click(ctx, OWNER, f"fm:use:{manual['id']}") # a stale button
assert cb.alerts == ["Only an auto format can be used for auto rename."] and cb.edits == []
assert (await autofmt(ctx))["id"] == auto["id"]
# ---------------------------------------------------------------------------
# copying a format to the other kind
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("source, target", [("auto", "manual"), ("manual", "auto")])
async def test_a_format_can_be_copied_to_the_other_kind(ctx, source, target):
fmt = await make_format(ctx, "One Piece", kind=source, meta={"title": "OP"}, keep_audio=["japanese"], caption="{filename}")
await ctx.db.set_format_thumb(OWNER, fmt["id"], jpeg())
cb = await click(ctx, OWNER, f"fm:v:{fmt['id']}")
assert f"fm:cp:{fmt['id']}" in data_of(cb.edits[-1][1]) and f"📋 Copy to {target} formats" in labels_of(cb.edits[-1][1])
cb = await click(ctx, OWNER, f"fm:cp:{fmt['id']}")
(copy,) = await ctx.db.list_formats(OWNER, target)
assert copy["id"] != fmt["id"] and copy["kind"] == target and copy["name"] == "One Piece"
assert copy["template"] == "One Piece - E{episode}" and copy["meta"]["title"] == "OP" and copy["keep_audio"] == ["japanese"]
assert copy["caption"] == "{filename}" and await ctx.db.get_format_thumb(OWNER, copy["id"]) == jpeg()
text, buttons = cb.edits[-1]
assert cb.toasts == [f"Copied to your {target} formats."] and f"is copied to your {target} formats" in text
assert data_of(buttons) == [f"fm:v:{copy['id']}", f"fm:v:{fmt['id']}", "x:close"]
assert (await ctx.db.get_format(OWNER, fmt["id"]))["kind"] == source and await ctx.db.count_formats(OWNER, source) == 1
cb = await click(ctx, OWNER, f"fm:v:{copy['id']}") # "Open the copy": it is a format of the other kind
assert f"{'🤖 Auto' if target == 'auto' else '✋ Manual'}" in cb.edits[-1][0].splitlines()[0]
async def test_copies_are_independent_and_get_a_free_name(ctx):
fmt = await make_format(ctx, "One Piece", kind="auto")
await click(ctx, OWNER, f"fm:cp:{fmt['id']}")
await click(ctx, OWNER, f"fm:cp:{fmt['id']}")
first, second = await ctx.db.list_formats(OWNER, "manual")
assert [first["name"], second["name"]] == ["One Piece", "One Piece (2)"]
await click(ctx, OWNER, f"fm:cp:{first['id']}") # and back again: "One Piece" is taken among the auto formats
back = (await ctx.db.list_formats(OWNER, "auto"))[-1]
assert back["name"] == "One Piece (2)" and back["kind"] == "auto" and await ctx.db.count_formats(OWNER, "auto") == 2
await click(ctx, OWNER, f"fm:in:{first['id']}:template")
await say(ctx, OWNER, "Changed {title}")
assert (await ctx.db.get_format(OWNER, first["id"]))["template"] == "Changed {title}"
assert (await ctx.db.get_format(OWNER, fmt["id"]))["template"] == "One Piece - E{episode}"
assert (await ctx.db.get_format(OWNER, second["id"]))["template"] == "One Piece - E{episode}"
@pytest.mark.parametrize("kind", ["auto", "manual"])
async def test_copying_to_a_full_kind_is_refused_and_the_kinds_count_apart(ctx, kind):
other = "manual" if kind == "auto" else "auto"
fmt = await make_format(ctx, "One Piece", kind=kind)
for i in range(MAX_FORMATS):
await make_format(ctx, f"F{i}", kind=other)
cb = await click(ctx, OWNER, f"fm:cp:{fmt['id']}")
assert cb.alerts == [f"You already have {MAX_FORMATS} {other} formats. Delete one first."] and cb.edits == []
assert await ctx.db.count_formats(OWNER, other) == MAX_FORMATS and await ctx.db.count_formats(OWNER, kind) == 1
async def test_a_copy_shows_up_where_its_kind_is_used(ctx):
auto = await make_format(ctx, "From auto", kind="auto")
manual = await make_format(ctx, "From manual")
await click(ctx, OWNER, f"fm:cp:{auto['id']}") # a manual copy of the auto format
await click(ctx, OWNER, f"fm:cp:{manual['id']}") # an auto copy of the manual one
assert {"From manual", "From auto"} <= set(labels_of((await say(ctx, OWNER, "/rename")).out[0][1]))
labels = labels_of((await click(ctx, OWNER, "m:auto")).edits[-1][1])
assert "From auto ✅" in labels and "From manual" in labels
async def test_an_auto_format_made_by_copying_is_the_first_one_and_renames_the_files(ctx):
manual = await make_format(ctx, "One Piece")
assert await autofmt(ctx) is None
await click(ctx, OWNER, f"fm:cp:{manual['id']}")
auto = await autofmt(ctx)
assert auto["kind"] == "auto" and auto["name"] == "One Piece"
ev = await send_doc(ctx, OWNER, ONE_PIECE, mid=50)
assert "🗂 Auto format: One Piece" in ev.texts[0] and latest_job(ctx).new_name == "One Piece - E1147.mkv"
# ---------------------------------------------------------------------------
# Apply default metadata
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("kind", ["auto", "manual"])
async def test_apply_default_metadata_copies_all_of_it_into_the_format(ctx, kind):
await set_default_metadata(ctx) # the metadata switch is Off: copying does not depend on it
fmt = await make_format(
ctx, "One Piece", kind=kind, meta={"title": "Old title", "artist": "Old artist"}, keep_audio=["french"], keep_subtitle=["2"]
)
cb = await click(ctx, OWNER, f"fm:dm:{fmt['id']}")
got = await ctx.db.get_format(OWNER, fmt["id"])
assert got["meta"] == DEFAULT_META and got["keep_audio"] == ["japanese", "english"] and got["keep_subtitle"] == ["english"]
assert got["template"] == "One Piece - E{episode}" and got["kind"] == kind # nothing else changed
text, buttons = cb.edits[-1]
assert cb.toasts == ["Default metadata applied."] and "Your default metadata is now in One Piece" in text
assert "Title ▸ My Channel" in text and "Japanese, English" in text and f"fm:dm:{fmt['id']}" in data_of(buttons)
s = await prefs(ctx) # the default metadata itself is untouched, and the format is not linked to it afterwards
assert s["meta"] == DEFAULT_META and s["keep_audio"] == ["japanese", "english"] and s["metadata_on"] is False
await ctx.db.update_settings(OWNER, meta={"title": "Changed later"})
assert (await ctx.db.get_format(OWNER, fmt["id"]))["meta"]["title"] == "My Channel"
@pytest.mark.parametrize("kind", ["auto", "manual"])
async def test_a_new_format_can_take_the_default_metadata_straight_away(ctx, kind):
await set_default_metadata(ctx)
await click(ctx, OWNER, f"fm:new:{kind}", msg_id=500)
await say(ctx, OWNER, "Fresh")
(fmt,) = await ctx.db.list_formats(OWNER)
_chat, text, kw = ctx.client.edited[-1]
assert "🏷 Apply default metadata" in labels_of(kw["buttons"]) and "Apply default metadata" in text
assert fmt["meta"]["title"] == ""
await click(ctx, OWNER, f"fm:dm:{fmt['id']}")
assert (await ctx.db.get_format(OWNER, fmt["id"]))["meta"] == DEFAULT_META
@pytest.mark.parametrize("kind", ["auto", "manual"])
async def test_apply_default_metadata_says_so_when_there_is_none(ctx, kind):
fmt = await make_format(ctx, "One Piece", kind=kind, meta={"title": "Mine"})
await ctx.db.update_settings(OWNER, metadata_on=True) # the switch alone is not metadata
cb = await click(ctx, OWNER, f"fm:dm:{fmt['id']}")
assert cb.alerts == ["Your default metadata is empty. Set it in /metadata first, then apply it."] and cb.edits == []
assert (await ctx.db.get_format(OWNER, fmt["id"]))["meta"]["title"] == "Mine" # nothing was wiped
async def test_track_lists_alone_count_as_default_metadata(ctx):
await ctx.db.update_settings(OWNER, keep_audio=["english"])
fmt = await make_format(ctx, kind="auto")
cb = await click(ctx, OWNER, f"fm:dm:{fmt['id']}")
assert cb.toasts == ["Default metadata applied."] and (await ctx.db.get_format(OWNER, fmt["id"]))["keep_audio"] == ["english"]
async def test_a_file_renamed_with_a_format_that_took_the_default_metadata_gets_it(ctx):
await set_default_metadata(ctx)
fmt = await make_format(ctx)
await click(ctx, OWNER, f"fm:dm:{fmt['id']}")
await click(ctx, OWNER, f"rn:p:{fmt['id']}")
await send_doc(ctx, OWNER, ONE_PIECE, mid=50)
job = latest_job(ctx)
assert job.fmt["meta"]["title"] == "My Channel" and job.fmt["keep_audio"] == ["japanese", "english"]
out = apply_format(await prefs(ctx), job.fmt)
assert out["metadata_on"] is True and out["meta"]["video"] == "HD" # written, although the default switch is Off
async def test_an_auto_format_says_whether_the_default_metadata_applies_to_it(ctx):
auto = await make_format(ctx, "Auto one", kind="auto")
manual = await make_format(ctx, "Manual one")
cb = await click(ctx, OWNER, f"fm:v:{auto['id']}")
assert "No metadata of its own: your default metadata is used while it is On" in cb.edits[-1][0]
assert "your default metadata is used" not in (await click(ctx, OWNER, f"fm:v:{manual['id']}")).edits[-1][0] # a manual one never uses it
await ctx.db.update_format(OWNER, auto["id"], meta={"title": "Own"})
assert "your default metadata is used" not in (await click(ctx, OWNER, f"fm:v:{auto['id']}")).edits[-1][0]
async def test_the_metadata_screen_calls_it_default_metadata_and_points_to_the_button(ctx):
cb = await click(ctx, OWNER, "m:meta")
text = cb.edits[-1][0]
assert "Your default metadata is currently: Off" in text and "Apply default metadata" in text and "global" not in text.lower()
await click(ctx, OWNER, "set:meta:on")
assert (await prefs(ctx))["metadata_on"] is True # the On/Off switch and the /metadata commands are unchanged
assert "currently: On" in (await say(ctx, OWNER, "/metadata")).texts[0]