SYMBOL INDEX (2027 symbols across 200 files) FILE: backend/src/dev_server.py function stub_status (line 33) | async def stub_status(): FILE: backend/src/main.py function lifespan (line 36) | async def lifespan(app: FastAPI): function create_app (line 46) | def create_app() -> FastAPI: function posters (line 73) | def posters(path: str): function html (line 87) | def html(request: Request, path: str): function index (line 98) | def index(): FILE: backend/src/module/ab_decorator/__init__.py function qb_connect_failed_wait (line 15) | def qb_connect_failed_wait(func): function api_failed (line 42) | def api_failed(func): function locked (line 55) | def locked(func): FILE: backend/src/module/ab_decorator/timeout.py function timeout (line 4) | def timeout(seconds): FILE: backend/src/module/api/auth.py function _issue_token (line 26) | def _issue_token(username: str, response: Response) -> dict: function login (line 36) | async def login(response: Response, form_data=Depends(OAuth2PasswordRequ... function refresh (line 48) | async def refresh(response: Response, token: str = Cookie(None)): function logout (line 63) | async def logout(response: Response, token: str = Cookie(None)): function update_user (line 77) | async def update_user( FILE: backend/src/module/api/bangumi.py class OffsetSuggestion (line 21) | class OffsetSuggestion(BaseModel): class TMDBSummary (line 27) | class TMDBSummary(BaseModel): class OffsetSuggestionDetail (line 36) | class OffsetSuggestionDetail(BaseModel): class SetWeekdayRequest (line 44) | class SetWeekdayRequest(BaseModel): class DetectOffsetRequest (line 48) | class DetectOffsetRequest(BaseModel): class DetectOffsetResponse (line 55) | class DetectOffsetResponse(BaseModel): function str_to_list (line 64) | def str_to_list(data: Bangumi): function get_all_data (line 73) | async def get_all_data(): function get_data (line 83) | async def get_data(bangumi_id: str): function update_rule (line 94) | async def update_rule( function delete_rule (line 108) | async def delete_rule(bangumi_id: str, file: bool = False): function delete_many_rule (line 119) | async def delete_many_rule(bangumi_id: list, file: bool = False): function disable_rule (line 131) | async def disable_rule(bangumi_id: str, file: bool = False): function disable_many_rule (line 142) | async def disable_many_rule(bangumi_id: list, file: bool = False): function enable_rule (line 154) | async def enable_rule(bangumi_id: str): function refresh_poster_all (line 165) | async def refresh_poster_all(): function refresh_poster_one (line 175) | async def refresh_poster_one(bangumi_id: int): function refresh_calendar (line 186) | async def refresh_calendar(): function reset_all (line 195) | async def reset_all(): function archive_rule (line 209) | async def archive_rule(bangumi_id: int): function unarchive_rule (line 221) | async def unarchive_rule(bangumi_id: int): function refresh_metadata (line 233) | async def refresh_metadata(): function suggest_offset (line 245) | async def suggest_offset(bangumi_id: int): function detect_offset (line 257) | async def detect_offset(request: DetectOffsetRequest): function dismiss_review (line 312) | async def dismiss_review(bangumi_id: int): function get_needs_review (line 342) | async def get_needs_review(): function set_weekday (line 353) | async def set_weekday(bangumi_id: int, request: SetWeekdayRequest): FILE: backend/src/module/api/config.py function _is_sensitive (line 17) | def _is_sensitive(key: str) -> bool: function _sanitize_dict (line 21) | def _sanitize_dict(d: dict) -> dict: function _restore_masked (line 38) | def _restore_masked(incoming: dict, current: dict) -> dict: function get_config (line 58) | async def get_config(): function update_config (line 66) | async def update_config(config: Config): FILE: backend/src/module/api/downloader.py class TorrentHashesRequest (line 15) | class TorrentHashesRequest(BaseModel): class TorrentDeleteRequest (line 19) | class TorrentDeleteRequest(BaseModel): class TorrentTagRequest (line 24) | class TorrentTagRequest(BaseModel): function get_torrents (line 31) | async def get_torrents(): function pause_torrents (line 37) | async def pause_torrents(req: TorrentHashesRequest): function resume_torrents (line 45) | async def resume_torrents(req: TorrentHashesRequest): function delete_torrents (line 53) | async def delete_torrents(req: TorrentDeleteRequest): function tag_torrent (line 61) | async def tag_torrent(req: TorrentTagRequest): function auto_tag_torrents (line 89) | async def auto_tag_torrents(): FILE: backend/src/module/api/log.py function get_log (line 15) | async def get_log(): function clear_log (line 38) | async def clear_log(): FILE: backend/src/module/api/notification.py class TestProviderRequest (line 17) | class TestProviderRequest(BaseModel): class TestProviderConfigRequest (line 23) | class TestProviderConfigRequest(BaseModel): class TestResponse (line 39) | class TestResponse(BaseModel): function test_provider (line 51) | async def test_provider(request: TestProviderRequest): function test_provider_config (line 89) | async def test_provider_config(request: TestProviderConfigRequest): FILE: backend/src/module/api/passkey.py function _get_webauthn_from_request (line 33) | def _get_webauthn_from_request(request: Request): function get_registration_options (line 63) | async def get_registration_options( function verify_registration (line 103) | async def verify_registration( function get_passkey_login_options (line 157) | async def get_passkey_login_options( function login_with_passkey (line 211) | async def login_with_passkey( function list_passkeys (line 247) | async def list_passkeys(username: str = Depends(get_current_user)): function delete_passkey (line 272) | async def delete_passkey( FILE: backend/src/module/api/program.py function restart (line 26) | async def restart(): function start (line 45) | async def start(): function stop (line 64) | async def stop(): function program_status (line 70) | async def program_status(): function shutdown_program (line 88) | async def shutdown_program(): function check_downloader_status (line 108) | async def check_downloader_status(): FILE: backend/src/module/api/response.py function u_response (line 7) | def u_response(response_model: ResponseModel): FILE: backend/src/module/api/rss.py function get_rss (line 18) | async def get_rss(): function add_rss (line 26) | async def add_rss(rss: RSSItem): function enable_many_rss (line 37) | async def enable_many_rss( function delete_rss (line 50) | async def delete_rss(rss_id: int): function delete_many_rss (line 69) | async def delete_many_rss( function disable_rss (line 82) | async def disable_rss(rss_id: int): function disable_many_rss (line 101) | async def disable_many_rss(rss_ids: list[int]): function update_rss (line 112) | async def update_rss( function refresh_all (line 135) | async def refresh_all(): function refresh_rss (line 150) | async def refresh_rss(rss_id: int): function get_torrent (line 165) | async def get_torrent( function analysis (line 179) | async def analysis(rss: RSSItem): function download_collection (line 190) | async def download_collection(data: Bangumi): function subscribe (line 199) | async def subscribe(data: Bangumi, rss: RSSItem): FILE: backend/src/module/api/search.py function search_torrents (line 15) | async def search_torrents(site: str = "mikan", keywords: str = Query(Non... function search_provider (line 34) | async def search_provider(): function get_search_provider_config (line 43) | async def get_search_provider_config(): function update_search_provider_config (line 53) | async def update_search_provider_config(providers: dict[str, str]): FILE: backend/src/module/api/setup.py function _require_setup_needed (line 25) | def _require_setup_needed(): function _validate_url (line 34) | def _validate_url(url: str) -> None: class SetupStatusResponse (line 58) | class SetupStatusResponse(BaseModel): class TestDownloaderRequest (line 63) | class TestDownloaderRequest(BaseModel): class TestRSSRequest (line 71) | class TestRSSRequest(BaseModel): class TestNotificationRequest (line 75) | class TestNotificationRequest(BaseModel): class TestResultResponse (line 81) | class TestResultResponse(BaseModel): class SetupCompleteRequest (line 89) | class SetupCompleteRequest(BaseModel): function get_setup_status (line 110) | async def get_setup_status(): function test_downloader (line 121) | async def test_downloader(req: TestDownloaderRequest): function test_rss (line 196) | async def test_rss(req: TestRSSRequest): function test_notification (line 230) | async def test_notification(req: TestNotificationRequest): function complete_setup (line 275) | async def complete_setup(req: SetupCompleteRequest): FILE: backend/src/module/checker/checker.py function _get_default_config_dict (line 16) | def _get_default_config_dict() -> dict: class Checker (line 23) | class Checker: method __init__ (line 24) | def __init__(self): method check_renamer (line 28) | def check_renamer() -> bool: method check_analyser (line 35) | def check_analyser() -> bool: method check_first_run (line 42) | def check_first_run() -> bool: method check_version (line 48) | def check_version() -> tuple[bool, int | None]: method check_database (line 52) | def check_database() -> bool: method check_downloader (line 60) | async def check_downloader() -> bool: method check_img_cache (line 95) | def check_img_cache() -> bool: FILE: backend/src/module/conf/config.py class Settings (line 29) | class Settings(Config): method __init__ (line 39) | def __init__(self): method load (line 47) | def load(self): method _migrate_old_config (line 57) | def _migrate_old_config(config: dict) -> dict: method save (line 84) | def save(self, config_dict: dict | None = None): method init (line 91) | def init(self): method __load_from_env (line 97) | def __load_from_env(self): method __val_from_env (line 117) | def __val_from_env(env: str, attr: tuple | str): method group_rules (line 124) | def group_rules(self): FILE: backend/src/module/conf/const.py class BCOLORS (line 119) | class BCOLORS: method _ (line 123) | def _(color: str, *args: str) -> str: FILE: backend/src/module/conf/log.py function setup_logger (line 15) | def setup_logger(level: int = logging.INFO, reset: bool = False): FILE: backend/src/module/conf/parse.py function parse (line 4) | def parse(): FILE: backend/src/module/conf/search_provider.py function load_provider (line 14) | def load_provider(): function save_provider (line 22) | def save_provider(providers: dict[str, str]): function get_provider (line 29) | def get_provider(): FILE: backend/src/module/core/offset_scanner.py class OffsetScanner (line 14) | class OffsetScanner: method scan_all (line 17) | async def scan_all(self) -> int: method _check_bangumi (line 47) | async def _check_bangumi(self, bangumi: Bangumi) -> bool: method check_single (line 107) | async def check_single(self, bangumi_id: int) -> bool: FILE: backend/src/module/core/program.py class Program (line 32) | class Program(RenameThread, RSSThread, OffsetScanThread, CalendarRefresh... method __init__ (line 33) | def __init__(self): method __start_info (line 38) | def __start_info(): method startup (line 47) | async def startup(self): method start (line 79) | async def start(self): method stop (line 114) | async def stop(self): method restart (line 135) | async def restart(self): method update_database (line 170) | def update_database(self): FILE: backend/src/module/core/status.py class ProgramStatus (line 10) | class ProgramStatus(Checker): method __init__ (line 11) | def __init__(self): method is_running (line 22) | def is_running(self): method is_stopped (line 29) | def is_stopped(self): method downloader_status (line 33) | def downloader_status(self): method check_downloader_status (line 36) | async def check_downloader_status(self) -> bool: method enable_rss (line 47) | def enable_rss(self): method enable_renamer (line 51) | def enable_renamer(self): method first_run (line 55) | def first_run(self): method legacy_data (line 59) | def legacy_data(self): method version_update (line 63) | def version_update(self) -> tuple[bool, int | None]: method database (line 68) | def database(self): method img_cache (line 72) | def img_cache(self): FILE: backend/src/module/core/sub_thread.py class RSSThread (line 19) | class RSSThread(ProgramStatus): method __init__ (line 20) | def __init__(self): method rss_loop (line 26) | async def rss_loop(self): method rss_start (line 49) | def rss_start(self): method rss_stop (line 53) | async def rss_stop(self): class RenameThread (line 64) | class RenameThread(ProgramStatus): method __init__ (line 65) | def __init__(self): method rename_loop (line 70) | async def rename_loop(self): method rename_start (line 89) | def rename_start(self): method rename_stop (line 93) | async def rename_stop(self): class OffsetScanThread (line 108) | class OffsetScanThread(ProgramStatus): method __init__ (line 111) | def __init__(self): method scan_loop (line 117) | async def scan_loop(self): method scan_start (line 138) | def scan_start(self): method scan_stop (line 143) | async def scan_stop(self): class CalendarRefreshThread (line 155) | class CalendarRefreshThread(ProgramStatus): method __init__ (line 158) | def __init__(self): method calendar_loop (line 163) | async def calendar_loop(self): method calendar_start (line 190) | def calendar_start(self): method calendar_stop (line 195) | async def calendar_stop(self): FILE: backend/src/module/database/bangumi.py function _normalize_group_name (line 15) | def _normalize_group_name(group: str | None) -> str: function _groups_are_similar (line 23) | def _groups_are_similar(group1: str | None, group2: str | None) -> bool: function _get_aliases_list (line 44) | def _get_aliases_list(bangumi: Bangumi) -> list[str]: function _set_aliases_list (line 57) | def _set_aliases_list(bangumi: Bangumi, aliases: list[str]) -> None: function _invalidate_bangumi_cache (line 73) | def _invalidate_bangumi_cache(): class BangumiDatabase (line 79) | class BangumiDatabase: method __init__ (line 80) | def __init__(self, session: Session): method find_semantic_duplicate (line 83) | def find_semantic_duplicate(self, data: Bangumi) -> Optional[Bangumi]: method add_title_alias (line 131) | def add_title_alias( method get_all_title_patterns (line 172) | def get_all_title_patterns(self, bangumi: Bangumi) -> list[str]: method _is_duplicate (line 180) | def _is_duplicate(self, data: Bangumi) -> bool: method add (line 191) | def add(self, data: Bangumi) -> bool: method add_all (line 217) | def add_all(self, datas: list[Bangumi]) -> int: method update (line 300) | def update(self, data: Bangumi | BangumiUpdate, _id: int = None) -> bool: method update_all (line 318) | def update_all(self, datas: list[Bangumi]): method update_rss (line 324) | def update_rss(self, title_raw: str, rss_set: str): method update_poster (line 336) | def update_poster(self, title_raw: str, poster_link: str): method delete_one (line 349) | def delete_one(self, _id: int): method delete_all (line 359) | def delete_all(self): method search_all (line 365) | def search_all(self) -> list[Bangumi]: method search_id (line 384) | def search_id(self, _id: int) -> Optional[Bangumi]: method search_official_title (line 393) | def search_official_title(self, official_title: str) -> Optional[Bangu... method search_ids (line 397) | def search_ids(self, ids: list[int]) -> list[Bangumi]: method match_poster (line 405) | def match_poster(self, bangumi_name: str) -> str: method match_list (line 412) | def match_list(self, torrent_list: list, rss_link: str) -> list: method match_torrent (line 464) | def match_torrent(self, torrent_name: str) -> Optional[Bangumi]: method not_complete (line 492) | def not_complete(self) -> list[Bangumi]: method not_added (line 499) | def not_added(self) -> list[Bangumi]: method disable_rule (line 510) | def disable_rule(self, _id: int): method search_rss (line 521) | def search_rss(self, rss_link: str) -> list[Bangumi]: method archive_one (line 526) | def archive_one(self, _id: int) -> bool: method unarchive_one (line 539) | def unarchive_one(self, _id: int) -> bool: method match_by_save_path (line 552) | def match_by_save_path(self, save_path: str) -> Optional[Bangumi]: method get_needs_review (line 601) | def get_needs_review(self) -> list[Bangumi]: method get_active_for_scan (line 612) | def get_active_for_scan(self) -> list[Bangumi]: method set_needs_review (line 623) | def set_needs_review( method clear_needs_review (line 658) | def clear_needs_review(self, _id: int) -> bool: method set_weekday (line 673) | def set_weekday(self, _id: int, weekday: int | None) -> bool: FILE: backend/src/module/database/combine.py class Database (line 116) | class Database(Session): method __init__ (line 117) | def __init__(self, engine=e): method create_table (line 125) | def create_table(self): method _ensure_schema_version_table (line 129) | def _ensure_schema_version_table(self): method _get_schema_version (line 142) | def _get_schema_version(self) -> int: method _set_schema_version (line 154) | def _set_schema_version(self, version: int): method run_migrations (line 165) | def run_migrations(self): method _get_field_default (line 230) | def _get_field_default(self, field_info: FieldInfo) -> tuple[bool, Any]: method _is_optional_field (line 245) | def _is_optional_field(self, model: type[SQLModel], field_name: str) -... method _fill_null_with_defaults (line 257) | def _fill_null_with_defaults(self): method drop_table (line 320) | def drop_table(self): method migrate (line 323) | def migrate(self): FILE: backend/src/module/database/engine.py function _set_sqlite_fk_sync (line 20) | def _set_sqlite_fk_sync(dbapi_conn, connection_record): function _set_sqlite_fk_async (line 27) | def _set_sqlite_fk_async(dbapi_conn, connection_record): FILE: backend/src/module/database/passkey.py class PasskeyDatabase (line 18) | class PasskeyDatabase: method __init__ (line 19) | def __init__(self, session: AsyncSession): method create_passkey (line 22) | async def create_passkey(self, passkey: Passkey) -> Passkey: method get_passkey_by_credential_id (line 30) | async def get_passkey_by_credential_id( method get_passkeys_by_user_id (line 38) | async def get_passkeys_by_user_id(self, user_id: int) -> List[Passkey]: method get_passkey_by_id (line 44) | async def get_passkey_by_id(self, passkey_id: int, user_id: int) -> Pa... method update_passkey_usage (line 55) | async def update_passkey_usage(self, passkey: Passkey, new_sign_count:... method delete_passkey (line 62) | async def delete_passkey(self, passkey_id: int, user_id: int) -> bool: method to_list_model (line 70) | def to_list_model(self, passkey: Passkey) -> PasskeyList: FILE: backend/src/module/database/rss.py class RSSDatabase (line 10) | class RSSDatabase: method __init__ (line 11) | def __init__(self, session: Session): method add (line 14) | def add(self, data: RSSItem) -> bool: method add_all (line 28) | def add_all(self, data: list[RSSItem]): method update (line 41) | def update(self, _id: int, data: RSSUpdate) -> bool: method enable (line 54) | def enable(self, _id: int) -> bool: method enable_batch (line 65) | def enable_batch(self, ids: list[int]): method disable (line 72) | def disable(self, _id: int) -> bool: method disable_batch (line 83) | def disable_batch(self, ids: list[int]): method search_id (line 90) | def search_id(self, _id: int) -> RSSItem | None: method search_all (line 93) | def search_all(self) -> list[RSSItem]: method search_active (line 97) | def search_active(self) -> list[RSSItem]: method search_aggregate (line 103) | def search_aggregate(self) -> list[RSSItem]: method delete (line 109) | def delete(self, _id: int) -> bool: method delete_all (line 119) | def delete_all(self): FILE: backend/src/module/database/torrent.py class TorrentDatabase (line 10) | class TorrentDatabase: method __init__ (line 11) | def __init__(self, session: Session): method add (line 14) | def add(self, data: Torrent): method add_all (line 19) | def add_all(self, datas: list[Torrent]): method update (line 24) | def update(self, data: Torrent): method update_all (line 29) | def update_all(self, datas: list[Torrent]): method update_one_user (line 33) | def update_one_user(self, data: Torrent): method search (line 38) | def search(self, _id: int) -> Torrent | None: method search_all (line 42) | def search_all(self) -> list[Torrent]: method search_rss (line 46) | def search_rss(self, rss_id: int) -> list[Torrent]: method check_new (line 50) | def check_new(self, torrents_list: list[Torrent]) -> list[Torrent]: method search_by_qb_hash (line 59) | def search_by_qb_hash(self, qb_hash: str) -> Torrent | None: method search_by_qb_hashes (line 64) | def search_by_qb_hashes(self, qb_hashes: list[str]) -> list[Torrent]: method delete_by_bangumi_id (line 73) | def delete_by_bangumi_id(self, bangumi_id: int) -> int: method search_by_url (line 89) | def search_by_url(self, url: str) -> Torrent | None: method update_qb_hash (line 94) | def update_qb_hash(self, torrent_id: int, qb_hash: str) -> bool: FILE: backend/src/module/database/user.py class UserDatabase (line 13) | class UserDatabase: method __init__ (line 14) | def __init__(self, session: Session): method get_user (line 17) | def get_user(self, username: str) -> User: method auth_user (line 25) | def auth_user(self, user: User) -> ResponseModel: method update_user (line 57) | def update_user(self, username: str, update_user: UserUpdate) -> User: method add_default_user (line 71) | def add_default_user(self): FILE: backend/src/module/downloader/client/aria2_downloader.py class Aria2Downloader (line 11) | class Aria2Downloader: method __init__ (line 12) | def __init__(self, host: str, username: str, password: str): method _call (line 19) | async def _call(self, method: str, params: list = None): method auth (line 37) | async def auth(self, retry=3): method logout (line 54) | async def logout(self): method torrents_files (line 59) | async def torrents_files(self, torrent_hash: str): method add_torrents (line 62) | async def add_torrents( method check_host (line 81) | async def check_host(self): method prefs_init (line 84) | async def prefs_init(self, prefs): method get_app_prefs (line 87) | async def get_app_prefs(self): method add_category (line 90) | async def add_category(self, category): method torrents_info (line 93) | async def torrents_info(self, status_filter, category, tag=None): method get_torrents_by_tag (line 96) | async def get_torrents_by_tag(self, tag: str) -> list[dict]: method torrents_delete (line 99) | async def torrents_delete(self, hash, delete_files: bool = True): method torrents_pause (line 102) | async def torrents_pause(self, hashes: str): method torrents_resume (line 105) | async def torrents_resume(self, hashes: str): method torrents_rename_file (line 108) | async def torrents_rename_file( method rss_add_feed (line 113) | async def rss_add_feed(self, url, item_path): method rss_remove_item (line 116) | async def rss_remove_item(self, item_path): method rss_get_feeds (line 119) | async def rss_get_feeds(self): method rss_set_rule (line 122) | async def rss_set_rule(self, rule_name, rule_def): method move_torrent (line 125) | async def move_torrent(self, hashes, new_location): method get_download_rule (line 128) | async def get_download_rule(self): method get_torrent_path (line 131) | async def get_torrent_path(self, _hash): method set_category (line 134) | async def set_category(self, _hash, category): method check_connection (line 137) | async def check_connection(self): method remove_rule (line 140) | async def remove_rule(self, rule_name): method add_tag (line 143) | async def add_tag(self, _hash, tag): FILE: backend/src/module/downloader/client/mock_downloader.py class MockDownloader (line 14) | class MockDownloader: method __init__ (line 20) | def __init__(self): method auth (line 34) | async def auth(self, retry=3) -> bool: method logout (line 38) | async def logout(self): method check_host (line 41) | async def check_host(self) -> bool: method prefs_init (line 45) | async def prefs_init(self, prefs: dict): method get_app_prefs (line 49) | async def get_app_prefs(self) -> dict: method add_category (line 53) | async def add_category(self, category: str): method torrents_info (line 57) | async def torrents_info( method torrents_files (line 76) | async def torrents_files(self, torrent_hash: str) -> list[dict]: method add_torrents (line 82) | async def add_torrents( method torrents_delete (line 113) | async def torrents_delete(self, hash: str, delete_files: bool = True): method torrents_pause (line 121) | async def torrents_pause(self, hashes: str): method torrents_resume (line 127) | async def torrents_resume(self, hashes: str): method torrents_rename_file (line 133) | async def torrents_rename_file( method rss_add_feed (line 139) | async def rss_add_feed(self, url: str, item_path: str): method rss_remove_item (line 143) | async def rss_remove_item(self, item_path: str): method rss_get_feeds (line 147) | async def rss_get_feeds(self) -> dict: method rss_set_rule (line 151) | async def rss_set_rule(self, rule_name: str, rule_def: dict): method move_torrent (line 155) | async def move_torrent(self, hashes: str, new_location: str): method get_download_rule (line 161) | async def get_download_rule(self) -> dict: method get_torrent_path (line 165) | async def get_torrent_path(self, _hash: str) -> str: method set_category (line 171) | async def set_category(self, _hash: str, category: str): method remove_rule (line 176) | async def remove_rule(self, rule_name: str): method add_tag (line 180) | async def add_tag(self, _hash: str, tag: str): method check_connection (line 187) | async def check_connection(self) -> str: method add_mock_torrent (line 192) | def add_mock_torrent( method get_state (line 220) | def get_state(self) -> dict[str, Any]: FILE: backend/src/module/downloader/client/qb_downloader.py class QbDownloader (line 12) | class QbDownloader: method __init__ (line 13) | def __init__(self, host: str, username: str, password: str, ssl: bool): method _url (line 24) | def _url(self, endpoint: str) -> str: method auth (line 27) | async def auth(self, retry=3): method logout (line 75) | async def logout(self): method check_host (line 88) | async def check_host(self): method check_rss (line 95) | def check_rss(self, rss_link: str): method prefs_init (line 99) | async def prefs_init(self, prefs): method get_app_prefs (line 107) | async def get_app_prefs(self): method add_category (line 111) | async def add_category(self, category): method torrents_info (line 118) | async def torrents_info(self, status_filter, category, tag=None): method torrents_files (line 130) | async def torrents_files(self, torrent_hash: str): method add_torrents (line 136) | async def add_torrents( method get_torrents_by_tag (line 190) | async def get_torrents_by_tag(self, tag: str) -> list[dict]: method torrents_delete (line 194) | async def torrents_delete(self, hash, delete_files: bool = True): method torrents_pause (line 200) | async def torrents_pause(self, hashes: str): method torrents_resume (line 206) | async def torrents_resume(self, hashes: str): method torrents_rename_file (line 212) | async def torrents_rename_file( method rss_add_feed (line 255) | async def rss_add_feed(self, url, item_path): method rss_remove_item (line 263) | async def rss_remove_item(self, item_path): method rss_get_feeds (line 271) | async def rss_get_feeds(self): method rss_set_rule (line 275) | async def rss_set_rule(self, rule_name, rule_def): method move_torrent (line 281) | async def move_torrent(self, hashes, new_location): method get_download_rule (line 287) | async def get_download_rule(self): method get_torrent_path (line 291) | async def get_torrent_path(self, _hash): method set_category (line 300) | async def set_category(self, _hash, category): method check_connection (line 313) | async def check_connection(self): method remove_rule (line 317) | async def remove_rule(self, rule_name): method add_tag (line 323) | async def add_tag(self, _hash, tag): FILE: backend/src/module/downloader/download_client.py class DownloadClient (line 13) | class DownloadClient(TorrentPath): method __init__ (line 21) | def __init__(self): method __getClient (line 27) | def __getClient(): method __aenter__ (line 51) | async def __aenter__(self): method __aexit__ (line 60) | async def __aexit__(self, exc_type, exc_val, exc_tb): method auth (line 65) | async def auth(self): method check_host (line 72) | async def check_host(self): method init_downloader (line 75) | async def init_downloader(self): method set_rule (line 95) | async def set_rule(self, data: Bangumi): method set_rules (line 120) | async def set_rules(self, bangumi_info: list[Bangumi]): method get_torrent_info (line 125) | async def get_torrent_info( method get_torrent_files (line 132) | async def get_torrent_files(self, torrent_hash: str): method rename_torrent_file (line 135) | async def rename_torrent_file( method delete_torrent (line 147) | async def delete_torrent(self, hashes, delete_files: bool = True): method pause_torrent (line 151) | async def pause_torrent(self, hashes: str): method resume_torrent (line 154) | async def resume_torrent(self, hashes: str): method add_torrent (line 157) | async def add_torrent(self, torrent: Torrent | list, bangumi: Bangumi)... method move_torrent (line 223) | async def move_torrent(self, hashes, location): method add_rss_feed (line 227) | async def add_rss_feed(self, rss_link, item_path="Mikan_RSS"): method remove_rss_feed (line 230) | async def remove_rss_feed(self, item_path): method get_rss_feed (line 233) | async def get_rss_feed(self): method get_download_rules (line 236) | async def get_download_rules(self): method get_torrent_path (line 239) | async def get_torrent_path(self, hashes): method set_category (line 242) | async def set_category(self, hashes, category): method remove_rule (line 245) | async def remove_rule(self, rule_name): method get_torrents_by_tag (line 249) | async def get_torrents_by_tag(self, tag: str) -> list[dict]: method add_tag (line 255) | async def add_tag(self, torrent_hash: str, tag: str): FILE: backend/src/module/downloader/exceptions.py class ConflictError (line 1) | class ConflictError(Exception): FILE: backend/src/module/downloader/path.py class TorrentPath (line 20) | class TorrentPath: method __init__ (line 21) | def __init__(self): method check_files (line 25) | def check_files(files: list[dict]): method _path_to_bangumi (line 38) | def _path_to_bangumi(save_path: PathLike[str] | str, torrent_name: str... method _file_depth (line 55) | def _file_depth(file_path: PathLike[str] | str): method is_ep (line 58) | def is_ep(self, file_path: PathLike[str] | str): method _gen_save_path (line 62) | def _gen_save_path(data: Bangumi | BangumiUpdate): method _rule_name (line 84) | def _rule_name(data: Bangumi): method _join_path (line 93) | def _join_path(*args): FILE: backend/src/module/manager/collector.py class SeasonCollector (line 11) | class SeasonCollector(DownloadClient): method collect_season (line 12) | async def collect_season(self, bangumi: Bangumi, link: str = None): method subscribe_season (line 50) | async def subscribe_season(data: Bangumi, parser: str = "mikan"): function eps_complete (line 65) | async def eps_complete(): FILE: backend/src/module/manager/renamer.py class Renamer (line 22) | class Renamer(DownloadClient): method __init__ (line 23) | def __init__(self): method _cleanup_pending_cache (line 29) | def _cleanup_pending_cache(): method print_result (line 45) | def print_result(torrent_count, rename_count): method gen_path (line 53) | def gen_path( method rename_file (line 98) | async def rename_file( method rename_collection (line 170) | async def rename_collection( method rename_subtitles (line 206) | async def rename_subtitles( method _parse_bangumi_id_from_tags (line 246) | def _parse_bangumi_id_from_tags(tags: str) -> int | None: method _normalize_path (line 263) | def _normalize_path(path: str) -> str: method _batch_lookup_offsets (line 272) | def _batch_lookup_offsets( method _lookup_offsets (line 365) | def _lookup_offsets( method rename (line 442) | async def rename(self) -> list[Notification]: FILE: backend/src/module/manager/torrent.py class TorrentManager (line 14) | class TorrentManager(Database): method __match_torrents_list (line 16) | async def __match_torrents_list(data: Bangumi | BangumiUpdate) -> list: method delete_torrents (line 25) | async def delete_torrents(self, data: Bangumi, client: DownloadClient): method delete_rule (line 44) | async def delete_rule(self, _id: int | str, file: bool = False): method disable_rule (line 70) | async def disable_rule(self, _id: str | int, file: bool = False): method enable_rule (line 94) | def enable_rule(self, _id: str | int): method update_rule (line 114) | async def update_rule(self, bangumi_id, data: BangumiUpdate): method refresh_poster (line 176) | async def refresh_poster(self): method refind_poster (line 189) | async def refind_poster(self, bangumi_id: int): method refresh_calendar (line 200) | async def refresh_calendar(self): method search_all_bangumi (line 231) | def search_all_bangumi(self): method search_one (line 237) | def search_one(self, _id: int | str): method archive_rule (line 250) | def archive_rule(self, _id: int): method unarchive_rule (line 275) | def unarchive_rule(self, _id: int): method refresh_metadata (line 300) | async def refresh_metadata(self): method suggest_offset (line 337) | async def suggest_offset(self, bangumi_id: int) -> dict: FILE: backend/src/module/mcp/resources.py function handle_resource (line 53) | def handle_resource(uri: str) -> str: FILE: backend/src/module/mcp/security.py function _parse_network (line 17) | def _parse_network(cidr: str) -> ipaddress.IPv4Network | ipaddress.IPv6N... function _is_allowed (line 25) | def _is_allowed(host: str, whitelist: list[str]) -> bool: function clear_network_cache (line 38) | def clear_network_cache(): class McpAccessMiddleware (line 43) | class McpAccessMiddleware(BaseHTTPMiddleware): method dispatch (line 51) | async def dispatch(self, request: Request, call_next): FILE: backend/src/module/mcp/server.py function list_tools (line 32) | async def list_tools() -> list[types.Tool]: function call_tool (line 37) | async def call_tool(name: str, arguments: dict) -> list[types.TextContent]: function list_resources (line 43) | async def list_resources() -> list[types.Resource]: function list_resource_templates (line 48) | async def list_resource_templates() -> list[types.ResourceTemplate]: function read_resource (line 53) | async def read_resource(uri: str) -> str: function handle_sse (line 58) | async def handle_sse(request: Request): function create_mcp_starlette_app (line 71) | def create_mcp_starlette_app() -> Starlette: FILE: backend/src/module/mcp/tools.py function _bangumi_to_dict (line 175) | def _bangumi_to_dict(b: Bangumi) -> dict: function handle_tool (line 198) | async def handle_tool(name: str, arguments: dict) -> list[types.TextCont... function _dispatch (line 213) | async def _dispatch(name: str, args: dict) -> dict | list: function _list_anime (line 238) | def _list_anime(active_only: bool) -> list[dict]: function _get_anime (line 247) | def _get_anime(bangumi_id: int) -> dict: function _search_anime (line 255) | async def _search_anime(keywords: str, site: str) -> list[dict]: function _subscribe_anime (line 266) | async def _subscribe_anime(rss_link: str, parser: str) -> dict: function _unsubscribe_anime (line 276) | async def _unsubscribe_anime(bangumi_id: int, delete: bool) -> dict: function _list_downloads (line 285) | async def _list_downloads(status: str) -> list[dict]: function _list_rss_feeds (line 305) | def _list_rss_feeds() -> list[dict]: function _get_program_status (line 324) | def _get_program_status() -> dict: function _refresh_feeds (line 334) | async def _refresh_feeds() -> dict: function _update_anime (line 341) | async def _update_anime(args: dict) -> dict: FILE: backend/src/module/models/api.py class RssLink (line 4) | class RssLink(BaseModel): class AddRule (line 8) | class AddRule(BaseModel): class ChangeConfig (line 13) | class ChangeConfig(BaseModel): class ChangeRule (line 17) | class ChangeRule(BaseModel): FILE: backend/src/module/models/bangumi.py class Bangumi (line 8) | class Bangumi(SQLModel, table=True): class BangumiUpdate (line 57) | class BangumiUpdate(SQLModel): class Notification (line 95) | class Notification(BaseModel): class Episode (line 103) | class Episode: class SeasonInfo (line 117) | class SeasonInfo: FILE: backend/src/module/models/config.py function _expand (line 7) | def _expand(value: str | None) -> str: class Program (line 12) | class Program(BaseModel): class Downloader (line 20) | class Downloader(BaseModel): method host (line 38) | def host(self): method username (line 42) | def username(self): method password (line 46) | def password(self): class RSSParser (line 50) | class RSSParser(BaseModel): class BangumiManage (line 58) | class BangumiManage(BaseModel): class Log (line 68) | class Log(BaseModel): class Proxy (line 74) | class Proxy(BaseModel): method username (line 85) | def username(self): method password (line 89) | def password(self): class NotificationProvider (line 93) | class NotificationProvider(BaseModel): method token (line 127) | def token(self) -> str: method chat_id (line 131) | def chat_id(self) -> str: method webhook_url (line 135) | def webhook_url(self) -> str: method server_url (line 139) | def server_url(self) -> str: method device_key (line 143) | def device_key(self) -> str: method user_key (line 147) | def user_key(self) -> str: method api_token (line 151) | def api_token(self) -> str: method url (line 155) | def url(self) -> str: class Notification (line 159) | class Notification(BaseModel): method token (line 173) | def token(self) -> str: method chat_id (line 177) | def chat_id(self) -> str: method migrate_legacy_config (line 181) | def migrate_legacy_config(self) -> "Notification": class ExperimentalOpenAI (line 195) | class ExperimentalOpenAI(BaseModel): method validate_api_base (line 216) | def validate_api_base(cls, value: str) -> str: class Security (line 222) | class Security(BaseModel): class Config (line 248) | class Config(BaseModel): method model_dump (line 261) | def model_dump(self, *args, by_alias=True, **kwargs): method dict (line 265) | def dict(self, *args, by_alias=True, **kwargs): FILE: backend/src/module/models/passkey.py class Passkey (line 12) | class Passkey(SQLModel, table=True): class PasskeyCreate (line 41) | class PasskeyCreate(BaseModel): class PasskeyList (line 49) | class PasskeyList(BaseModel): class PasskeyDelete (line 60) | class PasskeyDelete(BaseModel): class PasskeyAuthStart (line 66) | class PasskeyAuthStart(BaseModel): class PasskeyAuthFinish (line 72) | class PasskeyAuthFinish(BaseModel): FILE: backend/src/module/models/response.py class ResponseModel (line 4) | class ResponseModel(BaseModel): class APIResponse (line 12) | class APIResponse(BaseModel): FILE: backend/src/module/models/rss.py class RSSItem (line 6) | class RSSItem(SQLModel, table=True): class RSSUpdate (line 18) | class RSSUpdate(SQLModel): FILE: backend/src/module/models/torrent.py class Torrent (line 7) | class Torrent(SQLModel, table=True): class TorrentUpdate (line 18) | class TorrentUpdate(SQLModel): class EpisodeFile (line 22) | class EpisodeFile(BaseModel): class SubtitleFile (line 31) | class SubtitleFile(BaseModel): FILE: backend/src/module/models/user.py class User (line 7) | class User(SQLModel, table=True): class UserUpdate (line 15) | class UserUpdate(SQLModel): class UserLogin (line 22) | class UserLogin(SQLModel): class Token (line 27) | class Token(BaseModel): class TokenData (line 32) | class TokenData(BaseModel): FILE: backend/src/module/network/request_contents.py class RequestContent (line 14) | class RequestContent(RequestURL): method get_torrents (line 15) | async def get_torrents( method get_xml (line 41) | async def get_xml(self, _url, retry: int = 3) -> xml.etree.ElementTree... method get_json (line 51) | async def get_json(self, _url) -> dict: method post_json (line 56) | async def post_json(self, _url, data: dict) -> dict: method post_data (line 60) | async def post_data(self, _url, data: dict): method post_files (line 63) | async def post_files(self, _url, data: dict, files: dict): method get_html (line 66) | async def get_html(self, _url): method get_content (line 70) | async def get_content(self, _url): method check_connection (line 77) | async def check_connection(self, _url): method get_rss_title (line 80) | async def get_rss_title(self, _url): FILE: backend/src/module/network/request_url.py function _proxy_config_key (line 16) | def _proxy_config_key() -> str: function get_shared_client (line 22) | async def get_shared_client() -> httpx.AsyncClient: class RequestURL (line 52) | class RequestURL: method __init__ (line 56) | def __init__(self): method _get_headers (line 60) | def _get_headers(self, url: str) -> dict: method get_url (line 75) | async def get_url(self, url, retry=3): method post_url (line 101) | async def post_url(self, url: str, data: dict, retry=3): method check_url (line 125) | async def check_url(self, url: str): method post_form (line 136) | async def post_form(self, url: str, data: dict, files): method __aenter__ (line 147) | async def __aenter__(self): method __aexit__ (line 151) | async def __aexit__(self, exc_type, exc_val, exc_tb): FILE: backend/src/module/network/site/mikan.py function rss_parser (line 6) | def rss_parser(soup): function mikan_title (line 25) | def mikan_title(soup): FILE: backend/src/module/notification/base.py class NotificationProvider (line 9) | class NotificationProvider(RequestContent, ABC): method send (line 17) | async def send(self, notification: Notification) -> bool: method test (line 29) | async def test(self) -> tuple[bool, str]: method _format_message (line 38) | def _format_message(self, notify: Notification) -> str: FILE: backend/src/module/notification/manager.py class NotificationManager (line 18) | class NotificationManager: method __init__ (line 21) | def __init__(self): method _load_providers (line 25) | def _load_providers(self): method _get_poster (line 44) | async def _get_poster(self, notification: Notification): method send_all (line 57) | async def send_all(self, notification: Notification): method test_provider (line 90) | async def test_provider(self, index: int) -> tuple[bool, str]: method test_provider_config (line 110) | async def test_provider_config(config: "ProviderConfig") -> tuple[bool... method __len__ (line 132) | def __len__(self) -> int: FILE: backend/src/module/notification/notification.py function getClient (line 18) | def getClient(type: str): class PostNotification (line 31) | class PostNotification: method __init__ (line 32) | def __init__(self): method _get_poster_sync (line 39) | def _get_poster_sync(notify: Notification): method send_msg (line 44) | async def send_msg(self, notify: Notification) -> bool: method __aenter__ (line 53) | async def __aenter__(self): method __aexit__ (line 57) | async def __aexit__(self, exc_type, exc_val, exc_tb): FILE: backend/src/module/notification/plugin/bark.py class BarkNotification (line 9) | class BarkNotification(RequestContent): method __init__ (line 10) | def __init__(self, token, **kwargs): method gen_message (line 16) | def gen_message(notify: Notification) -> str: method post_msg (line 22) | async def post_msg(self, notify: Notification) -> bool: FILE: backend/src/module/notification/plugin/server_chan.py class ServerChanNotification (line 9) | class ServerChanNotification(RequestContent): method __init__ (line 12) | def __init__(self, token, **kwargs): method gen_message (line 17) | def gen_message(notify: Notification) -> str: method post_msg (line 23) | async def post_msg(self, notify: Notification) -> bool: FILE: backend/src/module/notification/plugin/slack.py class SlackNotification (line 9) | class SlackNotification(RequestContent): method __init__ (line 10) | def __init__(self, token, **kwargs): method gen_message (line 16) | def gen_message(notify: Notification) -> str: method post_msg (line 22) | async def post_msg(self, notify: Notification) -> bool: FILE: backend/src/module/notification/plugin/telegram.py class TelegramNotification (line 10) | class TelegramNotification(RequestContent): method __init__ (line 11) | def __init__(self, token, chat_id): method gen_message (line 18) | def gen_message(notify: Notification) -> str: method post_msg (line 24) | async def post_msg(self, notify: Notification) -> bool: FILE: backend/src/module/notification/plugin/wecom.py class WecomNotification (line 9) | class WecomNotification(RequestContent): method __init__ (line 12) | def __init__(self, token, chat_id, **kwargs): method gen_message (line 19) | def gen_message(notify: Notification) -> str: method post_msg (line 25) | async def post_msg(self, notify: Notification) -> bool: FILE: backend/src/module/notification/providers/bark.py class BarkProvider (line 15) | class BarkProvider(NotificationProvider): method __init__ (line 20) | def __init__(self, config: "ProviderConfig"): method send (line 27) | async def send(self, notification: Notification) -> bool: method test (line 41) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/notification/providers/discord.py class DiscordProvider (line 15) | class DiscordProvider(NotificationProvider): method __init__ (line 18) | def __init__(self, config: "ProviderConfig"): method send (line 22) | async def send(self, notification: Notification) -> bool: method test (line 45) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/notification/providers/gotify.py class GotifyProvider (line 15) | class GotifyProvider(NotificationProvider): method __init__ (line 18) | def __init__(self, config: "ProviderConfig"): method send (line 24) | async def send(self, notification: Notification) -> bool: method test (line 49) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/notification/providers/pushover.py class PushoverProvider (line 15) | class PushoverProvider(NotificationProvider): method __init__ (line 20) | def __init__(self, config: "ProviderConfig"): method send (line 25) | async def send(self, notification: Notification) -> bool: method test (line 46) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/notification/providers/server_chan.py class ServerChanProvider (line 15) | class ServerChanProvider(NotificationProvider): method __init__ (line 18) | def __init__(self, config: "ProviderConfig"): method send (line 23) | async def send(self, notification: Notification) -> bool: method test (line 35) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/notification/providers/telegram.py class TelegramProvider (line 16) | class TelegramProvider(NotificationProvider): method __init__ (line 19) | def __init__(self, config: "ProviderConfig"): method send (line 26) | async def send(self, notification: Notification) -> bool: method test (line 45) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/notification/providers/webhook.py class WebhookProvider (line 28) | class WebhookProvider(NotificationProvider): method __init__ (line 31) | def __init__(self, config: "ProviderConfig"): method _render_template (line 36) | def _render_template(self, notification: Notification) -> dict: method send (line 72) | async def send(self, notification: Notification) -> bool: method test (line 81) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/notification/providers/wecom.py class WecomProvider (line 21) | class WecomProvider(NotificationProvider): method __init__ (line 24) | def __init__(self, config: "ProviderConfig"): method send (line 30) | async def send(self, notification: Notification) -> bool: method test (line 52) | async def test(self) -> tuple[bool, str]: FILE: backend/src/module/parser/analyser/bgm_calendar.py function fetch_bgm_calendar (line 10) | async def fetch_bgm_calendar() -> list[dict]: function match_weekday (line 43) | def match_weekday(official_title: str, title_raw: str, calendar_items: l... FILE: backend/src/module/parser/analyser/bgm_parser.py function search_url (line 4) | def search_url(e): function bgm_parser (line 8) | async def bgm_parser(title): FILE: backend/src/module/parser/analyser/mikan_parser.py function mikan_parser (line 16) | async def mikan_parser(homepage: str): FILE: backend/src/module/parser/analyser/offset_detector.py class OffsetSuggestion (line 13) | class OffsetSuggestion: function detect_offset_mismatch (line 22) | def detect_offset_mismatch( FILE: backend/src/module/parser/analyser/openai.py class Episode (line 14) | class Episode(BaseModel): class OpenAIParser (line 35) | class OpenAIParser: method __init__ (line 36) | def __init__( method parse (line 77) | def parse( method _prepare_params (line 120) | def _prepare_params(self, text: str, prompt: str) -> dict[str, Any]: FILE: backend/src/module/parser/analyser/raw_parser.py function _fallback_parse (line 24) | def _fallback_parse(content_title: str) -> tuple | None: function get_group (line 50) | def get_group(name: str) -> str: function pre_process (line 57) | def pre_process(raw_name: str) -> str: function prefix_process (line 61) | def prefix_process(raw: str, group: str) -> str: function season_process (line 77) | def season_process(season_info: str): function name_process (line 104) | def name_process(name: str): function find_tags (line 138) | def find_tags(other): function clean_sub (line 152) | def clean_sub(sub: str | None) -> str | None: function process (line 158) | def process(raw_title: str): function raw_parser (line 202) | def raw_parser(raw: str) -> Episode | None: FILE: backend/src/module/parser/analyser/tmdb_parser.py class TMDBInfo (line 22) | class TMDBInfo: method get_offset_for_season (line 36) | def get_offset_for_season(self, season: int) -> int: function search_url (line 50) | def search_url(e): function info_url (line 54) | def info_url(e, key): function season_url (line 58) | def season_url(tv_id, season_number, key): function is_animation (line 62) | async def is_animation(tv_id, language, req: RequestContent) -> bool: function get_season_episode_air_dates (line 72) | async def get_season_episode_air_dates( function detect_virtual_seasons (line 101) | def detect_virtual_seasons(episodes: list[dict], gap_months: int = 6) ->... function get_aired_episode_count (line 140) | async def get_aired_episode_count( function get_season (line 185) | def get_season(seasons: list) -> tuple[int, str]: function tmdb_parser (line 202) | async def tmdb_parser(title, language, test: bool = False) -> TMDBInfo |... FILE: backend/src/module/parser/analyser/torrent_parser.py function get_path_basename (line 32) | def get_path_basename(torrent_path: str) -> str: function get_group (line 47) | def get_group(group_and_title) -> tuple[str | None, str]: function get_season_and_title (line 57) | def get_season_and_title(season_and_title) -> tuple[str, int]: function get_subtitle_lang (line 66) | def get_subtitle_lang(subtitle_name: str) -> str: function torrent_parser (line 73) | def torrent_parser( function _torrent_parser_impl (line 96) | def _torrent_parser_impl( FILE: backend/src/module/parser/title_parser.py class TitleParser (line 17) | class TitleParser: method __init__ (line 18) | def __init__(self): method torrent_parser (line 22) | def torrent_parser( method tmdb_parser (line 34) | async def tmdb_parser(title: str, season: int, language: str): method tmdb_poster_parser (line 46) | async def tmdb_poster_parser(bangumi: Bangumi): method raw_parser (line 60) | def raw_parser(raw: str) -> Bangumi | None: method mikan_parser (line 113) | async def mikan_parser(homepage: str) -> tuple[str, str]: FILE: backend/src/module/rss/analyser.py class RSSAnalyser (line 14) | class RSSAnalyser(TitleParser): method official_title_parser (line 15) | async def official_title_parser(self, bangumi: Bangumi, rss: RSSItem, ... method get_rss_torrents (line 38) | async def get_rss_torrents(rss_link: str, full_parse: bool = True) -> ... method torrents_to_data (line 46) | async def torrents_to_data( method torrent_to_data (line 62) | async def torrent_to_data(self, torrent: Torrent, rss: RSSItem) -> Ban... method rss_to_data (line 69) | async def rss_to_data( method link_to_data (line 86) | async def link_to_data(self, rss: RSSItem) -> Bangumi | ResponseModel: FILE: backend/src/module/rss/engine.py class RSSEngine (line 15) | class RSSEngine(Database): method __init__ (line 16) | def __init__(self, _engine=engine): method _get_torrents (line 22) | async def _get_torrents(rss: RSSItem) -> list[Torrent]: method get_rss_torrents (line 30) | def get_rss_torrents(self, rss_id: int) -> list[Torrent]: method add_rss (line 37) | async def add_rss( method disable_list (line 70) | def disable_list(self, rss_id_list: list[int]): method enable_list (line 79) | def enable_list(self, rss_id_list: list[int]): method delete_list (line 88) | def delete_list(self, rss_id_list: list[int]): method pull_rss (line 98) | async def pull_rss(self, rss_item: RSSItem) -> list[Torrent]: method _pull_rss_with_status (line 103) | async def _pull_rss_with_status( method _get_filter_pattern (line 113) | def _get_filter_pattern(self, filter_str: str) -> re.Pattern: method match_torrent (line 134) | def match_torrent(self, torrent: Torrent) -> Optional[Bangumi]: method refresh_rss (line 145) | async def refresh_rss(self, client: DownloadClient, rss_id: Optional[i... method download_bangumi (line 175) | async def download_bangumi(self, bangumi: Bangumi): FILE: backend/src/module/searcher/provider.py function search_url (line 7) | def search_url(site: str, keywords: list[str]) -> RSSItem: FILE: backend/src/module/searcher/searcher.py class SearchTorrent (line 29) | class SearchTorrent(RequestContent, RSSAnalyser): method search_torrents (line 30) | async def search_torrents(self, rss_item: RSSItem) -> list[Torrent]: method _fetch_tmdb_poster (line 33) | async def _fetch_tmdb_poster(self, title: str) -> str | None: method analyse_keyword (line 49) | async def analyse_keyword( method special_url (line 73) | def special_url(data: Bangumi, site: str) -> RSSItem: method search_season (line 78) | async def search_season(self, data: Bangumi, site: str = "mikan") -> l... FILE: backend/src/module/security/api.py function check_login_ip (line 25) | def check_login_ip(request: Request): function get_current_user (line 41) | async def get_current_user(request: Request, token: str = Cookie(None)): function get_token_data (line 66) | async def get_token_data(token: str = Depends(oauth2_scheme)): function update_user_info (line 76) | def update_user_info(user_data: UserUpdate, current_user): function auth_user (line 86) | def auth_user(user: User): FILE: backend/src/module/security/auth_strategy.py class AuthStrategy (line 15) | class AuthStrategy(ABC): method authenticate (line 19) | async def authenticate( class PasskeyAuthStrategy (line 35) | class PasskeyAuthStrategy(AuthStrategy): method __init__ (line 38) | def __init__(self, webauthn_service): method authenticate (line 41) | async def authenticate( FILE: backend/src/module/security/jwt.py function _load_or_create_secret (line 11) | def _load_or_create_secret() -> str: function create_access_token (line 28) | def create_access_token(data: dict, expires_delta: timedelta | None = No... function decode_token (line 40) | def decode_token(token: str): function verify_token (line 51) | def verify_token(token: str): function verify_password (line 62) | def verify_password(plain_password, hashed_password): function get_password_hash (line 66) | def get_password_hash(password): FILE: backend/src/module/security/webauthn.py class WebAuthnService (line 35) | class WebAuthnService: method __init__ (line 38) | def __init__(self, rp_id: str, rp_name: str, origin: str): method _cleanup_expired (line 54) | def _cleanup_expired(self) -> None: method _store_challenge (line 64) | def _store_challenge(self, logical_key: str, challenge: bytes) -> None: method _pop_challenge_by_key (line 72) | def _pop_challenge_by_key(self, logical_key: str) -> bytes | None: method _pop_challenge_by_value (line 80) | def _pop_challenge_by_value(self, challenge: bytes) -> bytes | None: method generate_registration_options (line 90) | def generate_registration_options( method verify_registration (line 136) | def verify_registration( method generate_authentication_options (line 191) | def generate_authentication_options( method generate_discoverable_authentication_options (line 224) | def generate_discoverable_authentication_options(self) -> dict: method verify_authentication (line 245) | def verify_authentication( method verify_discoverable_authentication (line 285) | def verify_discoverable_authentication( method _parse_transports (line 333) | def _parse_transports( method base64url_encode (line 345) | def base64url_encode(self, data: bytes) -> str: method base64url_decode (line 349) | def base64url_decode(self, data: str) -> bytes: function get_webauthn_service (line 361) | def get_webauthn_service(rp_id: str, rp_name: str, origin: str) -> WebAu... FILE: backend/src/module/update/cross_version.py function from_30_to_31 (line 13) | async def from_30_to_31(): function from_31_to_32 (line 38) | async def from_31_to_32(): function run_migrations (line 46) | def run_migrations(): function cache_image (line 52) | async def cache_image(): FILE: backend/src/module/update/data_migration.py function data_migration (line 7) | def data_migration(): function database_migration (line 22) | def database_migration(): FILE: backend/src/module/update/rss.py function update_main_rss (line 4) | def update_main_rss(rss_link: str): FILE: backend/src/module/update/startup.py function start_up (line 9) | def start_up(): function first_run (line 16) | def first_run(): FILE: backend/src/module/update/version_check.py function version_check (line 6) | def version_check() -> tuple[bool, int | None]: FILE: backend/src/module/utils/cache_image.py function save_image (line 4) | def save_image(img, suffix): function load_image (line 12) | def load_image(img_path): FILE: backend/src/module/utils/json_config.py function load (line 6) | def load(filename): function save (line 11) | def save(filename, obj): function get (line 16) | async def get(url): FILE: backend/src/test/conftest.py function _clear_bangumi_cache (line 23) | def _clear_bangumi_cache(): function db_engine (line 36) | def db_engine(): function db_session (line 45) | def db_session(db_engine): function test_settings (line 57) | def test_settings(): function mock_settings (line 63) | def mock_settings(test_settings): function mock_qb_client (line 76) | def mock_qb_client(): function app (line 110) | def app(): function authed_client (line 118) | def authed_client(app): function unauthed_client (line 131) | def unauthed_client(app): function mock_program (line 142) | def mock_program(): function mock_webauthn (line 173) | def mock_webauthn(): function mock_download_client (line 212) | def mock_download_client(): FILE: backend/src/test/e2e/conftest.py function pytest_collection_modifyitems (line 24) | def pytest_collection_modifyitems(config, items): function e2e_tmpdir (line 48) | def e2e_tmpdir(tmp_path_factory): function docker_services (line 54) | def docker_services(): function qb_password (line 83) | def qb_password(docker_services): function ab_process (line 99) | def ab_process(e2e_tmpdir, docker_services): function api_client (line 167) | def api_client(ab_process): function e2e_state (line 178) | def e2e_state(): FILE: backend/src/test/e2e/mock_rss_server.py function handle_rss (line 11) | async def handle_rss(request: web.Request) -> web.Response: function handle_health (line 22) | async def handle_health(request: web.Request) -> web.Response: function create_app (line 26) | def create_app() -> web.Application: FILE: backend/src/test/e2e/test_e2e_workflow.py class TestE2EWorkflow (line 25) | class TestE2EWorkflow: method test_01_setup_status_needs_setup (line 32) | def test_01_setup_status_needs_setup(self, api_client): method test_02_verify_infrastructure (line 40) | def test_02_verify_infrastructure(self, api_client, qb_password): method test_03_mock_rss_nonexistent_feed (line 59) | def test_03_mock_rss_nonexistent_feed(self): method test_04_test_mock_downloader (line 64) | def test_04_test_mock_downloader(self, api_client): method test_05_setup_validation_username_too_short (line 78) | def test_05_setup_validation_username_too_short(self, api_client): method test_06_setup_validation_password_too_short (line 94) | def test_06_setup_validation_password_too_short(self, api_client): method test_07_complete_setup (line 110) | def test_07_complete_setup(self, api_client, e2e_state): method test_08_setup_status_complete (line 132) | def test_08_setup_status_complete(self, api_client): method test_09_setup_complete_blocked (line 138) | def test_09_setup_complete_blocked(self, api_client): method test_09b_test_downloader_blocked (line 154) | def test_09b_test_downloader_blocked(self, api_client): method test_09c_test_rss_blocked (line 162) | def test_09c_test_rss_blocked(self, api_client): method test_10_login (line 174) | def test_10_login(self, api_client, e2e_state): method test_11_login_cookie_set (line 186) | def test_11_login_cookie_set(self, api_client): method test_12_access_protected_endpoint (line 190) | def test_12_access_protected_endpoint(self, api_client): method test_13_refresh_token (line 199) | def test_13_refresh_token(self, api_client, e2e_state): method test_14_login_wrong_password (line 210) | def test_14_login_wrong_password(self, api_client): method test_15_login_nonexistent_user (line 218) | def test_15_login_nonexistent_user(self, api_client): method test_16_unauthenticated_client (line 226) | def test_16_unauthenticated_client(self): method test_20_get_config (line 240) | def test_20_get_config(self, api_client): method test_21_config_passwords_masked (line 258) | def test_21_config_passwords_masked(self, api_client): method test_22_update_config (line 267) | def test_22_update_config(self, api_client): method test_23_config_update_persisted (line 281) | def test_23_config_update_persisted(self, api_client): method test_30_list_rss_initial (line 290) | def test_30_list_rss_initial(self, api_client, e2e_state): method test_31_add_rss_feed (line 300) | def test_31_add_rss_feed(self, api_client, e2e_state): method test_32_add_rss_duplicate_url (line 313) | def test_32_add_rss_duplicate_url(self, api_client): method test_33_list_rss_after_add (line 327) | def test_33_list_rss_after_add(self, api_client, e2e_state): method test_34_disable_rss (line 340) | def test_34_disable_rss(self, api_client, e2e_state): method test_35_verify_rss_disabled (line 346) | def test_35_verify_rss_disabled(self, api_client, e2e_state): method test_36_enable_rss (line 356) | def test_36_enable_rss(self, api_client, e2e_state): method test_37_verify_rss_enabled (line 362) | def test_37_verify_rss_enabled(self, api_client, e2e_state): method test_38_update_rss (line 370) | def test_38_update_rss(self, api_client, e2e_state): method test_39_verify_rss_updated (line 379) | def test_39_verify_rss_updated(self, api_client, e2e_state): method test_39b_delete_nonexistent_rss (line 387) | def test_39b_delete_nonexistent_rss(self, api_client): method test_39c_disable_nonexistent_rss (line 396) | def test_39c_disable_nonexistent_rss(self, api_client): method test_39d_delete_rss (line 401) | def test_39d_delete_rss(self, api_client, e2e_state): method test_39e_verify_rss_deleted (line 407) | def test_39e_verify_rss_deleted(self, api_client, e2e_state): method test_40_bangumi_get_all_empty (line 418) | def test_40_bangumi_get_all_empty(self, api_client): method test_41_bangumi_needs_review_empty (line 424) | def test_41_bangumi_needs_review_empty(self, api_client): method test_42_bangumi_dismiss_review_nonexistent (line 430) | def test_42_bangumi_dismiss_review_nonexistent(self, api_client): method test_43_bangumi_reset_all (line 435) | def test_43_bangumi_reset_all(self, api_client): method test_50_downloader_check (line 444) | def test_50_downloader_check(self, api_client): method test_51_downloader_torrents_empty (line 449) | def test_51_downloader_torrents_empty(self, api_client): method test_52_downloader_pause_empty (line 455) | def test_52_downloader_pause_empty(self, api_client): method test_53_downloader_resume_empty (line 462) | def test_53_downloader_resume_empty(self, api_client): method test_54_downloader_delete_empty (line 469) | def test_54_downloader_delete_empty(self, api_client): method test_55_downloader_tag_nonexistent_bangumi (line 477) | def test_55_downloader_tag_nonexistent_bangumi(self, api_client): method test_56_downloader_auto_tag (line 486) | def test_56_downloader_auto_tag(self, api_client): method test_57_qbittorrent_direct_connectivity (line 494) | def test_57_qbittorrent_direct_connectivity(self, qb_password): method test_60_program_status_not_running (line 508) | def test_60_program_status_not_running(self, api_client): method test_61_program_stop_when_not_running (line 520) | def test_61_program_stop_when_not_running(self, api_client): method test_62_program_start (line 525) | def test_62_program_start(self, api_client): method test_63_program_stop (line 530) | def test_63_program_stop(self, api_client): method test_64_program_stop_already_stopped (line 535) | def test_64_program_stop_already_stopped(self, api_client): method test_65_program_restart (line 540) | def test_65_program_restart(self, api_client): method test_70_get_log (line 549) | def test_70_get_log(self, api_client): method test_71_clear_log (line 557) | def test_71_clear_log(self, api_client): method test_72_get_log_after_clear (line 563) | def test_72_get_log_after_clear(self, api_client): method test_80_search_providers (line 574) | def test_80_search_providers(self, api_client): method test_81_search_provider_config (line 582) | def test_81_search_provider_config(self, api_client): method test_82_search_empty_keywords (line 589) | def test_82_search_empty_keywords(self, api_client): method test_85_notification_test_invalid_index (line 599) | def test_85_notification_test_invalid_index(self, api_client): method test_86_notification_test_config_unknown_type (line 607) | def test_86_notification_test_config_unknown_type(self, api_client): method test_90_update_credentials (line 620) | def test_90_update_credentials(self, api_client, e2e_state): method test_91_login_with_new_password (line 632) | def test_91_login_with_new_password(self, api_client, e2e_state): method test_92_login_old_password_fails (line 644) | def test_92_login_old_password_fails(self, api_client): method test_93_logout (line 652) | def test_93_logout(self, api_client): method test_94_verify_logged_out (line 657) | def test_94_verify_logged_out(self, api_client): FILE: backend/src/test/factories.py function make_bangumi (line 10) | def make_bangumi(**overrides) -> Bangumi: function make_torrent (line 36) | def make_torrent(**overrides) -> Torrent: function make_rss_item (line 48) | def make_rss_item(**overrides) -> RSSItem: function make_config (line 61) | def make_config(**overrides) -> Config: function make_passkey (line 70) | def make_passkey(**overrides) -> Passkey: FILE: backend/src/test/test_api_auth.py function app (line 21) | def app(): function authed_client (line 29) | def authed_client(app): function unauthed_client (line 42) | def unauthed_client(app): class TestAuthRequired (line 52) | class TestAuthRequired: method test_refresh_token_unauthorized (line 54) | def test_refresh_token_unauthorized(self, unauthed_client): method test_logout_unauthorized (line 60) | def test_logout_unauthorized(self, unauthed_client): method test_update_unauthorized (line 66) | def test_update_unauthorized(self, unauthed_client): class TestLogin (line 80) | class TestLogin: method test_login_success (line 81) | def test_login_success(self, unauthed_client): method test_login_failure (line 97) | def test_login_failure(self, unauthed_client): class TestRefreshToken (line 116) | class TestRefreshToken: method test_refresh_token_success (line 117) | def test_refresh_token_success(self, authed_client): class TestLogout (line 135) | class TestLogout: method test_logout_success (line 136) | def test_logout_success(self, authed_client): class TestUpdateCredentials (line 153) | class TestUpdateCredentials: method test_update_success (line 154) | def test_update_success(self, authed_client): method test_update_failure (line 170) | def test_update_failure(self, authed_client): class TestRefreshTokenCookieBehavior (line 195) | class TestRefreshTokenCookieBehavior: method test_refresh_with_no_cookie_raises_401 (line 196) | def test_refresh_with_no_cookie_raises_401(self, authed_client): method test_refresh_with_valid_cookie_updates_active_user (line 203) | def test_refresh_with_valid_cookie_updates_active_user(self, authed_cl... method test_refresh_returns_new_token (line 213) | def test_refresh_returns_new_token(self, authed_client): class TestLogoutCookieBehavior (line 231) | class TestLogoutCookieBehavior: method test_logout_removes_only_current_user (line 232) | def test_logout_removes_only_current_user(self, authed_client): method test_logout_with_no_cookie_still_succeeds (line 246) | def test_logout_with_no_cookie_still_succeeds(self, authed_client): class TestUpdateCookieBehavior (line 259) | class TestUpdateCookieBehavior: method test_update_with_no_cookie_raises_401 (line 260) | def test_update_with_no_cookie_raises_401(self, authed_client): method test_update_with_valid_cookie_succeeds (line 269) | def test_update_with_valid_cookie_succeeds(self, authed_client): FILE: backend/src/test/test_api_bangumi.py function app (line 24) | def app(): function authed_client (line 32) | def authed_client(app): function unauthed_client (line 44) | def unauthed_client(app): class TestAuthRequired (line 54) | class TestAuthRequired: method test_get_all_unauthorized (line 56) | def test_get_all_unauthorized(self, unauthed_client): method test_get_by_id_unauthorized (line 62) | def test_get_by_id_unauthorized(self, unauthed_client): method test_delete_unauthorized (line 68) | def test_delete_unauthorized(self, unauthed_client): class TestGetBangumi (line 79) | class TestGetBangumi: method test_get_all (line 80) | def test_get_all(self, authed_client): method test_get_by_id (line 95) | def test_get_by_id(self, authed_client): class TestUpdateBangumi (line 114) | class TestUpdateBangumi: method test_update_success (line 115) | def test_update_success(self, authed_client): class TestDeleteBangumi (line 160) | class TestDeleteBangumi: method test_delete_success (line 161) | def test_delete_success(self, authed_client): method test_disable_rule (line 176) | def test_disable_rule(self, authed_client): method test_enable_rule (line 191) | def test_enable_rule(self, authed_client): class TestResetBangumi (line 212) | class TestResetBangumi: method test_reset_all (line 213) | def test_reset_all(self, authed_client): FILE: backend/src/test/test_api_bangumi_extended.py function app (line 22) | def app(): function authed_client (line 30) | def authed_client(app): function unauthed_client (line 43) | def unauthed_client(app): class TestArchiveBangumi (line 53) | class TestArchiveBangumi: method test_archive_success (line 54) | def test_archive_success(self, authed_client): method test_unarchive_success (line 69) | def test_unarchive_success(self, authed_client): class TestRefreshBangumi (line 90) | class TestRefreshBangumi: method test_refresh_poster_all (line 91) | def test_refresh_poster_all(self, authed_client): method test_refresh_poster_one (line 106) | def test_refresh_poster_one(self, authed_client): method test_refresh_calendar (line 121) | def test_refresh_calendar(self, authed_client): method test_refresh_metadata (line 136) | def test_refresh_metadata(self, authed_client): class TestOffsetDetection (line 157) | class TestOffsetDetection: method test_suggest_offset (line 158) | def test_suggest_offset(self, authed_client): method test_detect_offset_no_mismatch (line 173) | def test_detect_offset_no_mismatch(self, authed_client): method test_detect_offset_with_mismatch (line 198) | def test_detect_offset_with_mismatch(self, authed_client): method test_detect_offset_no_tmdb_data (line 232) | def test_detect_offset_no_tmdb_data(self, authed_client): class TestNeedsReview (line 255) | class TestNeedsReview: method test_get_needs_review (line 256) | def test_get_needs_review(self, authed_client): method test_dismiss_review_success (line 274) | def test_dismiss_review_success(self, authed_client): method test_dismiss_review_not_found (line 288) | def test_dismiss_review_not_found(self, authed_client): class TestBatchOperations (line 306) | class TestBatchOperations: method test_delete_many_auth_required (line 307) | def test_delete_many_auth_required(self, unauthed_client): method test_disable_many_auth_required (line 319) | def test_disable_many_auth_required(self, unauthed_client): FILE: backend/src/test/test_api_config.py function app (line 21) | def app(): function authed_client (line 29) | def authed_client(app): function unauthed_client (line 42) | def unauthed_client(app): function mock_settings (line 48) | def mock_settings(): class TestAuthRequired (line 90) | class TestAuthRequired: method test_get_config_unauthorized (line 92) | def test_get_config_unauthorized(self, unauthed_client): method test_update_config_unauthorized (line 98) | def test_update_config_unauthorized(self, unauthed_client): class TestGetConfig (line 109) | class TestGetConfig: method test_get_config_success (line 110) | def test_get_config_success(self, authed_client): class TestUpdateConfig (line 130) | class TestUpdateConfig: method test_update_config_success (line 131) | def test_update_config_success(self, authed_client, mock_settings): method test_update_config_failure (line 193) | def test_update_config_failure(self, authed_client, mock_settings): method test_update_config_partial_validation_error (line 254) | def test_update_config_partial_validation_error(self, authed_client): class TestSanitizeDict (line 274) | class TestSanitizeDict: method test_masks_password_key (line 275) | def test_masks_password_key(self): method test_masks_api_key (line 280) | def test_masks_api_key(self): method test_masks_token_key (line 285) | def test_masks_token_key(self): method test_masks_secret_key (line 290) | def test_masks_secret_key(self): method test_case_insensitive_key_matching (line 295) | def test_case_insensitive_key_matching(self): method test_non_sensitive_keys_pass_through (line 300) | def test_non_sensitive_keys_pass_through(self): method test_nested_dict_recursed (line 307) | def test_nested_dict_recursed(self): method test_deeply_nested_dict (line 320) | def test_deeply_nested_dict(self): method test_non_string_value_not_masked (line 325) | def test_non_string_value_not_masked(self): method test_empty_dict (line 331) | def test_empty_dict(self): method test_mixed_sensitive_and_plain (line 335) | def test_mixed_sensitive_and_plain(self): method test_sanitize_list_of_dicts (line 350) | def test_sanitize_list_of_dicts(self): method test_get_config_masks_sensitive_fields (line 364) | def test_get_config_masks_sensitive_fields(self, authed_client): class TestRestoreMasked (line 382) | class TestRestoreMasked: method test_masked_password_restored (line 385) | def test_masked_password_restored(self): method test_new_password_preserved (line 392) | def test_new_password_preserved(self): method test_nested_masked_password_restored (line 399) | def test_nested_masked_password_restored(self): method test_nested_new_password_preserved (line 406) | def test_nested_new_password_preserved(self): method test_multiple_sensitive_fields (line 413) | def test_multiple_sensitive_fields(self): method test_non_sensitive_mask_value_untouched (line 430) | def test_non_sensitive_mask_value_untouched(self): method test_list_of_dicts_restored (line 437) | def test_list_of_dicts_restored(self): method test_empty_dicts (line 455) | def test_empty_dicts(self): method test_round_trip_preserves_credentials (line 459) | def test_round_trip_preserves_credentials(self): method test_update_config_preserves_password_when_masked (line 475) | def test_update_config_preserves_password_when_masked( FILE: backend/src/test/test_api_downloader.py function app (line 19) | def app(): function authed_client (line 27) | def authed_client(app): function unauthed_client (line 40) | def unauthed_client(app): function mock_download_client (line 46) | def mock_download_client(): class TestAuthRequired (line 74) | class TestAuthRequired: method test_get_torrents_unauthorized (line 76) | def test_get_torrents_unauthorized(self, unauthed_client): method test_pause_torrents_unauthorized (line 82) | def test_pause_torrents_unauthorized(self, unauthed_client): method test_resume_torrents_unauthorized (line 90) | def test_resume_torrents_unauthorized(self, unauthed_client): method test_delete_torrents_unauthorized (line 98) | def test_delete_torrents_unauthorized(self, unauthed_client): class TestGetTorrents (line 112) | class TestGetTorrents: method test_get_torrents_success (line 113) | def test_get_torrents_success(self, authed_client, mock_download_client): method test_get_torrents_empty (line 128) | def test_get_torrents_empty(self, authed_client, mock_download_client): class TestPauseTorrents (line 148) | class TestPauseTorrents: method test_pause_single_torrent (line 149) | def test_pause_single_torrent(self, authed_client, mock_download_client): method test_pause_multiple_torrents (line 166) | def test_pause_multiple_torrents(self, authed_client, mock_download_cl... class TestResumeTorrents (line 189) | class TestResumeTorrents: method test_resume_single_torrent (line 190) | def test_resume_single_torrent(self, authed_client, mock_download_clie... method test_resume_multiple_torrents (line 207) | def test_resume_multiple_torrents(self, authed_client, mock_download_c... class TestDeleteTorrents (line 229) | class TestDeleteTorrents: method test_delete_single_torrent_keep_files (line 230) | def test_delete_single_torrent_keep_files( method test_delete_torrent_with_files (line 252) | def test_delete_torrent_with_files(self, authed_client, mock_download_... method test_delete_multiple_torrents (line 270) | def test_delete_multiple_torrents(self, authed_client, mock_download_c... class TestTagTorrent (line 294) | class TestTagTorrent: method test_tag_torrent_success (line 295) | def test_tag_torrent_success(self, authed_client, mock_download_client): method test_tag_torrent_bangumi_not_found (line 331) | def test_tag_torrent_bangumi_not_found(self, authed_client, mock_downl... class TestAutoTagTorrents (line 353) | class TestAutoTagTorrents: method test_auto_tag_success (line 354) | def test_auto_tag_success(self, authed_client, mock_download_client): method test_auto_tag_no_matches (line 405) | def test_auto_tag_no_matches(self, authed_client, mock_download_client): FILE: backend/src/test/test_api_log.py function app (line 21) | def app(): function authed_client (line 29) | def authed_client(app): function unauthed_client (line 42) | def unauthed_client(app): function temp_log_file (line 48) | def temp_log_file(): class TestAuthRequired (line 61) | class TestAuthRequired: method test_get_log_unauthorized (line 63) | def test_get_log_unauthorized(self, unauthed_client): method test_clear_log_unauthorized (line 69) | def test_clear_log_unauthorized(self, unauthed_client): class TestGetLog (line 80) | class TestGetLog: method test_get_log_success (line 81) | def test_get_log_success(self, authed_client, temp_log_file): method test_get_log_not_found (line 89) | def test_get_log_not_found(self, authed_client): method test_get_log_multiline (line 97) | def test_get_log_multiline(self, authed_client, temp_log_file): class TestClearLog (line 118) | class TestClearLog: method test_clear_log_success (line 119) | def test_clear_log_success(self, authed_client, temp_log_file): method test_clear_log_not_found (line 133) | def test_clear_log_not_found(self, authed_client): FILE: backend/src/test/test_api_passkey.py function app (line 22) | def app(): function authed_client (line 30) | def authed_client(app): function unauthed_client (line 43) | def unauthed_client(app): function mock_webauthn (line 49) | def mock_webauthn(): function mock_user_model (line 83) | def mock_user_model(): class TestAuthRequired (line 96) | class TestAuthRequired: method test_register_options_unauthorized (line 98) | def test_register_options_unauthorized(self, unauthed_client): method test_register_verify_unauthorized (line 104) | def test_register_verify_unauthorized(self, unauthed_client): method test_list_passkeys_unauthorized (line 113) | def test_list_passkeys_unauthorized(self, unauthed_client): method test_delete_passkey_unauthorized (line 119) | def test_delete_passkey_unauthorized(self, unauthed_client): class TestRegisterOptions (line 132) | class TestRegisterOptions: method test_get_registration_options_success (line 133) | def test_get_registration_options_success( method test_get_registration_options_user_not_found (line 165) | def test_get_registration_options_user_not_found( class TestRegisterVerify (line 193) | class TestRegisterVerify: method test_verify_registration_success (line 194) | def test_verify_registration_success( class TestAuthOptions (line 245) | class TestAuthOptions: method test_get_auth_options_with_username (line 246) | def test_get_auth_options_with_username(self, unauthed_client, mock_we... method test_get_auth_options_discoverable (line 283) | def test_get_auth_options_discoverable(self, unauthed_client, mock_web... method test_get_auth_options_user_not_found (line 296) | def test_get_auth_options_user_not_found(self, unauthed_client, mock_w... class TestAuthVerify (line 324) | class TestAuthVerify: method test_login_with_passkey_success (line 325) | def test_login_with_passkey_success(self, unauthed_client, mock_webaut... method test_login_with_passkey_failure (line 365) | def test_login_with_passkey_failure(self, unauthed_client, mock_webaut... class TestListPasskeys (line 404) | class TestListPasskeys: method test_list_passkeys_success (line 405) | def test_list_passkeys_success(self, authed_client, mock_user_model): method test_list_passkeys_empty (line 443) | def test_list_passkeys_empty(self, authed_client, mock_user_model): class TestDeletePasskey (line 471) | class TestDeletePasskey: method test_delete_passkey_success (line 472) | def test_delete_passkey_success(self, authed_client, mock_user_model): FILE: backend/src/test/test_api_program.py function app (line 20) | def app(): function authed_client (line 28) | def authed_client(app): function unauthed_client (line 41) | def unauthed_client(app): function mock_program (line 47) | def mock_program(): class TestAuthRequired (line 76) | class TestAuthRequired: method test_restart_unauthorized (line 78) | def test_restart_unauthorized(self, unauthed_client): method test_start_unauthorized (line 84) | def test_start_unauthorized(self, unauthed_client): method test_stop_unauthorized (line 90) | def test_stop_unauthorized(self, unauthed_client): method test_status_unauthorized (line 96) | def test_status_unauthorized(self, unauthed_client): class TestStartProgram (line 107) | class TestStartProgram: method test_start_success (line 108) | def test_start_success(self, authed_client, mock_program): method test_start_failure (line 115) | def test_start_failure(self, authed_client, mock_program): class TestStopProgram (line 129) | class TestStopProgram: method test_stop_success (line 130) | def test_stop_success(self, authed_client, mock_program): class TestRestartProgram (line 143) | class TestRestartProgram: method test_restart_success (line 144) | def test_restart_success(self, authed_client, mock_program): method test_restart_failure (line 151) | def test_restart_failure(self, authed_client, mock_program): class TestProgramStatus (line 165) | class TestProgramStatus: method test_status_running (line 166) | def test_status_running(self, authed_client, mock_program): method test_status_stopped (line 180) | def test_status_stopped(self, authed_client, mock_program): class TestCheckDownloader (line 199) | class TestCheckDownloader: method test_check_downloader_connected (line 200) | def test_check_downloader_connected(self, authed_client, mock_program): method test_check_downloader_disconnected (line 209) | def test_check_downloader_disconnected(self, authed_client, mock_progr... FILE: backend/src/test/test_api_rss.py function app (line 22) | def app(): function authed_client (line 29) | def authed_client(app): function unauthed_client (line 40) | def unauthed_client(app): class TestAuthRequired (line 49) | class TestAuthRequired: method test_get_rss_unauthorized (line 51) | def test_get_rss_unauthorized(self, unauthed_client): method test_add_rss_unauthorized (line 57) | def test_add_rss_unauthorized(self, unauthed_client): class TestGetRss (line 70) | class TestGetRss: method test_get_all (line 71) | def test_get_all(self, authed_client): class TestAddRss (line 95) | class TestAddRss: method test_add_success (line 96) | def test_add_success(self, authed_client): class TestDeleteRss (line 125) | class TestDeleteRss: method test_delete_success (line 126) | def test_delete_success(self, authed_client): method test_delete_failure (line 138) | def test_delete_failure(self, authed_client): class TestDisableRss (line 156) | class TestDisableRss: method test_disable_success (line 157) | def test_disable_success(self, authed_client): method test_disable_failure (line 169) | def test_disable_failure(self, authed_client): class TestBatchOperations (line 187) | class TestBatchOperations: method test_enable_many (line 188) | def test_enable_many(self, authed_client): method test_disable_many (line 203) | def test_disable_many(self, authed_client): method test_delete_many (line 218) | def test_delete_many(self, authed_client): class TestUpdateRss (line 239) | class TestUpdateRss: method test_update_success (line 240) | def test_update_success(self, authed_client): class TestRefreshRss (line 261) | class TestRefreshRss: method test_refresh_all (line 262) | def test_refresh_all(self, authed_client): method test_refresh_single (line 278) | def test_refresh_single(self, authed_client): class TestGetRssTorrents (line 300) | class TestGetRssTorrents: method test_get_torrents (line 301) | def test_get_torrents(self, authed_client): FILE: backend/src/test/test_api_search.py function app (line 19) | def app(): function authed_client (line 27) | def authed_client(app): function unauthed_client (line 40) | def unauthed_client(app): class TestAuthRequired (line 50) | class TestAuthRequired: method test_search_bangumi_unauthorized (line 52) | def test_search_bangumi_unauthorized(self, unauthed_client): method test_search_provider_unauthorized (line 60) | def test_search_provider_unauthorized(self, unauthed_client): method test_get_provider_config_unauthorized (line 66) | def test_get_provider_config_unauthorized(self, unauthed_client): class TestSearchBangumi (line 77) | class TestSearchBangumi: method test_search_no_keywords (line 78) | def test_search_no_keywords(self, authed_client): method test_search_with_keywords_auth_required (line 85) | def test_search_with_keywords_auth_required(self, unauthed_client): class TestSearchProvider (line 99) | class TestSearchProvider: method test_get_provider_list (line 100) | def test_get_provider_list(self, authed_client): class TestSearchProviderConfig (line 118) | class TestSearchProviderConfig: method test_get_provider_config (line 119) | def test_get_provider_config(self, authed_client): class TestUpdateProviderConfig (line 139) | class TestUpdateProviderConfig: method test_update_provider_config_success (line 140) | def test_update_provider_config_success(self, authed_client): method test_update_provider_config_empty (line 158) | def test_update_provider_config_empty(self, authed_client): FILE: backend/src/test/test_auth.py class TestCreateAccessToken (line 22) | class TestCreateAccessToken: method test_creates_valid_token (line 23) | def test_creates_valid_token(self): method test_token_contains_sub_claim (line 30) | def test_token_contains_sub_claim(self): method test_token_contains_exp_claim (line 37) | def test_token_contains_exp_claim(self): method test_custom_expiry (line 43) | def test_custom_expiry(self): class TestDecodeToken (line 57) | class TestDecodeToken: method test_valid_token (line 58) | def test_valid_token(self): method test_invalid_token (line 65) | def test_invalid_token(self): method test_empty_token (line 70) | def test_empty_token(self): method test_missing_sub_claim (line 75) | def test_missing_sub_claim(self): class TestVerifyToken (line 88) | class TestVerifyToken: method test_valid_fresh_token (line 89) | def test_valid_fresh_token(self): method test_expired_token_returns_none (line 98) | def test_expired_token_returns_none(self): method test_invalid_token_returns_none (line 108) | def test_invalid_token_returns_none(self): class TestPasswordHashing (line 119) | class TestPasswordHashing: method test_hash_and_verify_roundtrip (line 120) | def test_hash_and_verify_roundtrip(self): method test_wrong_password (line 126) | def test_wrong_password(self): method test_hash_is_not_plaintext (line 131) | def test_hash_is_not_plaintext(self): method test_different_hashes_for_same_password (line 137) | def test_different_hashes_for_same_password(self): class TestGetCurrentUser (line 153) | class TestGetCurrentUser: method _mock_request (line 155) | def _mock_request(authorization=""): method test_no_cookie_raises_401 (line 164) | async def test_no_cookie_raises_401(self): method test_invalid_token_raises_401 (line 175) | async def test_invalid_token_raises_401(self): method test_valid_token_user_not_active (line 186) | async def test_valid_token_user_not_active(self): method test_valid_token_active_user_succeeds (line 202) | async def test_valid_token_active_user_succeeds(self): method test_dev_bypass_skips_auth (line 221) | async def test_dev_bypass_skips_auth(self): method test_bearer_token_bypass_valid (line 229) | async def test_bearer_token_bypass_valid(self): method test_bearer_token_bypass_invalid (line 242) | async def test_bearer_token_bypass_invalid(self): class TestCheckLoginIp (line 263) | class TestCheckLoginIp: method _make_request (line 265) | def _make_request(host: str | None): method test_empty_whitelist_allows_all (line 276) | def test_empty_whitelist_allows_all(self): method test_allowed_ip_passes (line 287) | def test_allowed_ip_passes(self): method test_blocked_ip_raises_403 (line 297) | def test_blocked_ip_raises_403(self): method test_no_client_raises_403_when_whitelist_set (line 311) | def test_no_client_raises_403_when_whitelist_set(self): FILE: backend/src/test/test_config.py class TestConfigDefaults (line 28) | class TestConfigDefaults: method test_program_defaults (line 29) | def test_program_defaults(self): method test_downloader_defaults (line 36) | def test_downloader_defaults(self): method test_rss_parser_defaults (line 43) | def test_rss_parser_defaults(self): method test_bangumi_manage_defaults (line 50) | def test_bangumi_manage_defaults(self): method test_proxy_defaults (line 59) | def test_proxy_defaults(self): method test_notification_defaults (line 65) | def test_notification_defaults(self): class TestConfigSerialization (line 77) | class TestConfigSerialization: method test_dict_uses_alias (line 78) | def test_dict_uses_alias(self): method test_roundtrip_json (line 86) | def test_roundtrip_json(self, tmp_path): class TestMigrateOldConfig (line 107) | class TestMigrateOldConfig: method test_sleep_time_to_rss_time (line 108) | def test_sleep_time_to_rss_time(self): method test_times_to_rename_time (line 118) | def test_times_to_rename_time(self): method test_removes_data_version (line 128) | def test_removes_data_version(self): method test_removes_deprecated_rss_parser_fields (line 137) | def test_removes_deprecated_rss_parser_fields(self): method test_no_migration_needed (line 156) | def test_no_migration_needed(self): method test_both_old_and_new_fields (line 166) | def test_both_old_and_new_fields(self): class TestSettingsLoad (line 182) | class TestSettingsLoad: method test_load_from_json_file (line 183) | def test_load_from_json_file(self, tmp_path): method test_save_writes_json (line 199) | def test_save_writes_json(self, tmp_path): class TestEnvOverrides (line 220) | class TestEnvOverrides: method test_downloader_host_from_env (line 221) | def test_downloader_host_from_env(self, tmp_path): class TestSecurityModel (line 240) | class TestSecurityModel: method test_security_defaults (line 241) | def test_security_defaults(self): method test_security_in_config (line 249) | def test_security_in_config(self): method test_security_populated (line 256) | def test_security_populated(self): method test_security_roundtrip_serialization (line 269) | def test_security_roundtrip_serialization(self): class TestNotificationProvider (line 286) | class TestNotificationProvider: method test_minimal_provider (line 287) | def test_minimal_provider(self): method test_telegram_provider_fields (line 293) | def test_telegram_provider_fields(self): method test_discord_provider_fields (line 299) | def test_discord_provider_fields(self): method test_bark_provider_fields (line 306) | def test_bark_provider_fields(self): method test_pushover_provider_fields (line 314) | def test_pushover_provider_fields(self): method test_url_field_property (line 320) | def test_url_field_property(self): method test_optional_fields_default_empty_string (line 325) | def test_optional_fields_default_empty_string(self): method test_provider_can_be_disabled (line 332) | def test_provider_can_be_disabled(self): method test_env_var_expansion_in_token (line 337) | def test_env_var_expansion_in_token(self, monkeypatch): class TestNotificationLegacyMigration (line 349) | class TestNotificationLegacyMigration: method test_new_format_no_migration (line 350) | def test_new_format_no_migration(self): method test_old_format_migrates_to_provider (line 359) | def test_old_format_migrates_to_provider(self): method test_old_format_no_migration_when_providers_already_set (line 372) | def test_old_format_no_migration_when_providers_already_set(self): method test_notification_empty_providers_by_default (line 383) | def test_notification_empty_providers_by_default(self): class TestDownloaderEnvExpansion (line 395) | class TestDownloaderEnvExpansion: method test_host_expands_env_var (line 396) | def test_host_expands_env_var(self, monkeypatch): method test_username_expands_env_var (line 402) | def test_username_expands_env_var(self, monkeypatch): method test_password_expands_env_var (line 408) | def test_password_expands_env_var(self, monkeypatch): method test_literal_host_not_expanded (line 414) | def test_literal_host_not_expanded(self): class TestDefaultSettings (line 425) | class TestDefaultSettings: method test_security_section_present (line 426) | def test_security_section_present(self): method test_security_default_mcp_whitelist (line 430) | def test_security_default_mcp_whitelist(self): method test_security_default_tokens_empty (line 437) | def test_security_default_tokens_empty(self): method test_notification_uses_providers_format (line 442) | def test_notification_uses_providers_format(self): class TestBCOLORS (line 455) | class TestBCOLORS: method test_wrap_single_string (line 456) | def test_wrap_single_string(self): method test_wrap_multiple_strings (line 463) | def test_wrap_multiple_strings(self): method test_wrap_non_string_arg (line 469) | def test_wrap_non_string_arg(self): method test_all_color_constants_are_strings (line 474) | def test_all_color_constants_are_strings(self): class TestMigrateSecuritySection (line 485) | class TestMigrateSecuritySection: method test_adds_security_when_missing (line 486) | def test_adds_security_when_missing(self): method test_preserves_existing_security_section (line 496) | def test_preserves_existing_security_section(self): FILE: backend/src/test/test_database.py function db_session (line 16) | def db_session(): function test_bangumi_database (line 23) | def test_bangumi_database(db_session): function test_torrent_database (line 73) | def test_torrent_database(db_session): function test_rss_database (line 92) | def test_rss_database(db_session): function test_torrent_search_by_qb_hash (line 106) | def test_torrent_search_by_qb_hash(db_session): function test_torrent_search_by_qb_hash_not_found (line 125) | def test_torrent_search_by_qb_hash_not_found(db_session): function test_torrent_search_by_url (line 133) | def test_torrent_search_by_url(db_session): function test_torrent_search_by_url_not_found (line 151) | def test_torrent_search_by_url_not_found(db_session): function test_torrent_update_qb_hash (line 159) | def test_torrent_update_qb_hash(db_session): function test_torrent_update_qb_hash_nonexistent (line 180) | def test_torrent_update_qb_hash_nonexistent(db_session): function test_torrent_with_bangumi_id (line 188) | def test_torrent_with_bangumi_id(db_session): function test_torrent_qb_hash_index_efficient (line 207) | def test_torrent_qb_hash_index_efficient(db_session): function test_add_title_alias (line 239) | def test_add_title_alias(db_session): function test_add_title_alias_duplicate (line 266) | def test_add_title_alias_duplicate(db_session): function test_add_title_alias_same_as_title_raw (line 288) | def test_add_title_alias_same_as_title_raw(db_session): function test_match_torrent_with_alias (line 308) | def test_match_torrent_with_alias(db_session): function test_find_semantic_duplicate_same_official_title (line 339) | def test_find_semantic_duplicate_same_official_title(db_session): function test_find_semantic_duplicate_no_match_different_resolution (line 372) | def test_find_semantic_duplicate_no_match_different_resolution(db_session): function test_add_with_semantic_duplicate_creates_alias (line 402) | def test_add_with_semantic_duplicate_creates_alias(db_session): class TestDeleteByBangumiId (line 443) | class TestDeleteByBangumiId: method test_deletes_matching_torrents (line 446) | def test_deletes_matching_torrents(self, db_session): method test_leaves_other_bangumi_torrents (line 456) | def test_leaves_other_bangumi_torrents(self, db_session): method test_no_match_returns_zero (line 467) | def test_no_match_returns_zero(self, db_session): method test_skips_null_bangumi_id (line 475) | def test_skips_null_bangumi_id(self, db_session): method test_check_new_finds_urls_after_cleanup (line 486) | def test_check_new_finds_urls_after_cleanup(self, db_session): function test_groups_are_similar (line 503) | def test_groups_are_similar(): function test_get_all_title_patterns (line 524) | def test_get_all_title_patterns(db_session): function test_match_list_with_aliases (line 554) | def test_match_list_with_aliases(db_session): FILE: backend/src/test/test_download_client.py function download_client (line 14) | def download_client(mock_qb_client): class TestAuth (line 38) | class TestAuth: method test_auth_success (line 39) | async def test_auth_success(self, download_client, mock_qb_client): method test_auth_failure (line 45) | async def test_auth_failure(self, download_client, mock_qb_client): class TestInitDownloader (line 57) | class TestInitDownloader: method test_sets_prefs_and_category (line 58) | async def test_sets_prefs_and_category(self, download_client, mock_qb_... method test_detects_path_when_empty (line 70) | async def test_detects_path_when_empty(self, download_client, mock_qb_... method test_category_already_exists_no_error (line 80) | async def test_category_already_exists_no_error(self, download_client,... class TestSetRule (line 94) | class TestSetRule: method test_generates_correct_rule (line 95) | async def test_generates_correct_rule(self, download_client, mock_qb_c... method test_marks_bangumi_added (line 118) | async def test_marks_bangumi_added(self, download_client, mock_qb_clie... method test_rule_name_set (line 128) | async def test_rule_name_set(self, download_client, mock_qb_client): method test_rule_name_with_group_tag (line 146) | async def test_rule_name_with_group_tag(self, download_client, mock_qb... class TestAddTorrent (line 167) | class TestAddTorrent: method test_magnet_url (line 168) | async def test_magnet_url(self, download_client, mock_qb_client): method test_file_url_downloads_content (line 185) | async def test_file_url_downloads_content(self, download_client, mock_... method test_list_magnet_urls (line 203) | async def test_list_magnet_urls(self, download_client, mock_qb_client): method test_empty_list_returns_false (line 223) | async def test_empty_list_returns_false(self, download_client, mock_qb... method test_client_rejects_returns_false (line 235) | async def test_client_rejects_returns_false(self, download_client, moc... method test_generates_save_path_if_missing (line 250) | async def test_generates_save_path_if_missing(self, download_client, m... class TestClientDelegation (line 272) | class TestClientDelegation: method test_get_torrent_info (line 273) | async def test_get_torrent_info(self, download_client, mock_qb_client): method test_rename_torrent_file_success (line 284) | async def test_rename_torrent_file_success(self, download_client, mock... method test_rename_torrent_file_failure (line 290) | async def test_rename_torrent_file_failure(self, download_client, mock... method test_rename_torrent_file_passes_verify_flag (line 296) | async def test_rename_torrent_file_passes_verify_flag( method test_delete_torrent (line 307) | async def test_delete_torrent(self, download_client, mock_qb_client): class TestAddTag (line 318) | class TestAddTag: method test_add_tag_delegates_to_client (line 319) | async def test_add_tag_delegates_to_client(self, download_client, mock... method test_add_tag_short_hash_no_error (line 326) | async def test_add_tag_short_hash_no_error(self, download_client, mock... class TestContextManagerAuth (line 339) | class TestContextManagerAuth: method test_aenter_raises_on_auth_failure (line 340) | async def test_aenter_raises_on_auth_failure(self, download_client, mo... method test_aenter_succeeds_when_auth_passes (line 347) | async def test_aenter_succeeds_when_auth_passes(self, download_client,... method test_aexit_calls_logout_when_authed (line 355) | async def test_aexit_calls_logout_when_authed(self, download_client, m... FILE: backend/src/test/test_integration.py function clear_cache (line 18) | def clear_cache(): class TestRssToDownloadFlow (line 29) | class TestRssToDownloadFlow: method test_full_flow (line 32) | async def test_full_flow(self, db_engine): method test_filtered_torrents_not_downloaded (line 93) | async def test_filtered_torrents_not_downloaded(self, db_engine): method test_duplicate_torrents_not_reprocessed (line 126) | async def test_duplicate_torrents_not_reprocessed(self, db_engine): class TestRenameFlow (line 172) | class TestRenameFlow: method test_single_file_rename (line 175) | async def test_single_file_rename(self, mock_qb_client): method test_collection_rename (line 234) | async def test_collection_rename(self, mock_qb_client): class TestDatabaseConsistency (line 298) | class TestDatabaseConsistency: method test_bangumi_uniqueness_by_title_raw (line 301) | def test_bangumi_uniqueness_by_title_raw(self, db_engine): method test_rss_uniqueness_by_url (line 315) | def test_rss_uniqueness_by_url(self, db_engine): method test_torrent_check_new_filters_duplicates (line 325) | def test_torrent_check_new_filters_duplicates(self, db_engine): method test_match_torrent_respects_deleted_flag (line 341) | def test_match_torrent_respects_deleted_flag(self, db_engine): method test_bangumi_disable_and_enable (line 355) | def test_bangumi_disable_and_enable(self, db_engine): FILE: backend/src/test/test_issue_bugs.py class TestIssue986AtlasSubGroupFormat (line 30) | class TestIssue986AtlasSubGroupFormat: method test_get_group_extracts_atlas_group (line 39) | def test_get_group_extracts_atlas_group(self): method test_process_returns_none_for_atlas_format (line 45) | def test_process_returns_none_for_atlas_format(self): method test_raw_parser_returns_none_for_atlas_format (line 55) | def test_raw_parser_returns_none_for_atlas_format(self): method test_atlas_titles_all_fail_to_parse (line 63) | def test_atlas_titles_all_fail_to_parse(self, title): method test_get_group_returns_empty_for_no_brackets (line 68) | def test_get_group_returns_empty_for_no_brackets(self): method test_get_group_does_not_crash_on_empty_string (line 73) | def test_get_group_does_not_crash_on_empty_string(self): class TestIssue977EpisodeZeroOffset (line 89) | class TestIssue977EpisodeZeroOffset: method test_episode_zero_preserved_with_no_offset (line 92) | def test_episode_zero_preserved_with_no_offset(self): method test_episode_zero_immune_to_positive_offset (line 106) | def test_episode_zero_immune_to_positive_offset(self): method test_episode_zero_immune_to_negative_offset (line 120) | def test_episode_zero_immune_to_negative_offset(self): method test_regular_episode_offset_still_works (line 134) | def test_regular_episode_offset_still_works(self): method test_episode_zero_advance_method (line 146) | def test_episode_zero_advance_method(self): class TestIssue976NoneInMatchList (line 171) | class TestIssue976NoneInMatchList: method test_match_list_filters_none_title_raw (line 174) | def test_match_list_filters_none_title_raw(self, db_session): method test_sorted_with_none_key_raises_typeerror (line 202) | def test_sorted_with_none_key_raises_typeerror(self): method test_empty_title_index_produces_empty_pattern (line 208) | def test_empty_title_index_produces_empty_pattern(self): method test_get_group_no_brackets_returns_empty (line 215) | def test_get_group_no_brackets_returns_empty(self): method test_get_group_single_bracket_pair (line 222) | def test_get_group_single_bracket_pair(self): method test_get_group_empty_brackets (line 227) | def test_get_group_empty_brackets(self): class TestIssue974FilterPatternError (line 243) | class TestIssue974FilterPatternError: method test_normal_filter_compiles (line 246) | def test_normal_filter_compiles(self): method test_raw_unterminated_bracket_is_invalid_regex (line 255) | def test_raw_unterminated_bracket_is_invalid_regex(self): method test_engine_handles_unterminated_bracket (line 262) | def test_engine_handles_unterminated_bracket(self): method test_engine_handles_unmatched_parenthesis (line 275) | def test_engine_handles_unmatched_parenthesis(self): method test_engine_handles_trailing_backslash (line 285) | def test_engine_handles_trailing_backslash(self): method test_engine_default_filter_still_uses_regex (line 294) | def test_engine_default_filter_still_uses_regex(self): method test_engine_caches_filter_pattern (line 305) | def test_engine_caches_filter_pattern(self): class TestIssue990NumberPrefixTitle (line 327) | class TestIssue990NumberPrefixTitle: method test_raw_parser_correctly_parses_leading_number_title (line 334) | def test_raw_parser_correctly_parses_leading_number_title(self): method test_title_parser_returns_bangumi_for_number_prefix_title (line 343) | def test_title_parser_returns_bangumi_for_number_prefix_title(self): method test_add_title_alias_rejects_none (line 352) | def test_add_title_alias_rejects_none(self, db_session): method test_add_title_alias_rejects_empty_string (line 371) | def test_add_title_alias_rejects_empty_string(self, db_session): method test_get_aliases_list_filters_null_values (line 388) | def test_get_aliases_list_filters_null_values(self): method test_get_all_title_patterns_skips_none_title_raw (line 402) | def test_get_all_title_patterns_skips_none_title_raw(self, db_session): method test_match_torrent_no_crash_on_none_title_raw (line 415) | def test_match_torrent_no_crash_on_none_title_raw(self, db_session): method test_match_torrent_no_crash_on_null_aliases (line 436) | def test_match_torrent_no_crash_on_null_aliases(self, db_session): method test_match_list_no_crash_on_corrupted_data (line 458) | def test_match_list_no_crash_on_corrupted_data(self, db_session): class TestIssue992NonEpisodicAttributeError (line 497) | class TestIssue992NonEpisodicAttributeError: method test_title_parser_returns_none_for_non_episodic (line 507) | def test_title_parser_returns_none_for_non_episodic(self, title): method test_raw_parser_returns_none_for_unparseable (line 514) | def test_raw_parser_returns_none_for_unparseable(self): class TestIssue1005SearchOfficialTitle (line 526) | class TestIssue1005SearchOfficialTitle: method test_method_exists (line 529) | def test_method_exists(self): method test_search_official_title_finds_match (line 535) | def test_search_official_title_finds_match(self, db_session): method test_search_official_title_returns_none_when_not_found (line 553) | def test_search_official_title_returns_none_when_not_found(self, db_se... FILE: backend/src/test/test_mcp_resources.py function _mock_sync_manager (line 22) | def _mock_sync_manager(bangumi_list=None, single=None): function _mock_rss_engine (line 36) | def _mock_rss_engine(feeds): function _parse (line 47) | def _parse(raw: str) -> dict | list: class TestResourceMetadata (line 56) | class TestResourceMetadata: method test_resources_is_list (line 59) | def test_resources_is_list(self): method test_resources_not_empty (line 62) | def test_resources_not_empty(self): method test_resource_uris_present (line 65) | def test_resource_uris_present(self): method test_resource_templates_is_list (line 71) | def test_resource_templates_is_list(self): method test_anime_id_template_present (line 74) | def test_anime_id_template_present(self): class TestBangumiToDictResources (line 84) | class TestBangumiToDictResources: method sample (line 91) | def sample(self) -> Bangumi: method test_returns_dict (line 109) | def test_returns_dict(self, sample): method test_required_keys_present (line 112) | def test_required_keys_present(self, sample): method test_id_value (line 132) | def test_id_value(self, sample): method test_official_title_value (line 135) | def test_official_title_value(self, sample): method test_eps_collect_true (line 138) | def test_eps_collect_true(self, sample): method test_none_optional_poster (line 141) | def test_none_optional_poster(self): class TestHandleResourceAnimeList (line 151) | class TestHandleResourceAnimeList: method test_returns_json_string (line 154) | def test_returns_json_string(self): method test_empty_database_returns_empty_list (line 162) | def test_empty_database_returns_empty_list(self): method test_multiple_bangumi_serialised (line 169) | def test_multiple_bangumi_serialised(self): method test_ids_are_in_output (line 177) | def test_ids_are_in_output(self): method test_non_ascii_titles_preserved (line 186) | def test_non_ascii_titles_preserved(self): class TestHandleResourceStatus (line 196) | class TestHandleResourceStatus: method mock_program (line 200) | def mock_program(self): method test_returns_json_string (line 206) | def test_returns_json_string(self, mock_program): method test_version_in_output (line 215) | def test_version_in_output(self, mock_program): method test_running_true (line 223) | def test_running_true(self, mock_program): method test_first_run_false (line 232) | def test_first_run_false(self, mock_program): method test_all_keys_present (line 241) | def test_all_keys_present(self, mock_program): class TestHandleResourceRssFeeds (line 250) | class TestHandleResourceRssFeeds: method _make_feed (line 253) | def _make_feed(self, feed_id=1, name="TestFeed", url="https://example.... method test_returns_json_string (line 263) | def test_returns_json_string(self): method test_empty_feeds_returns_empty_list (line 270) | def test_empty_feeds_returns_empty_list(self): method test_feed_fields_present (line 276) | def test_feed_fields_present(self): method test_multiple_feeds (line 289) | def test_multiple_feeds(self): class TestHandleResourceAnimeById (line 305) | class TestHandleResourceAnimeById: method test_valid_id_returns_bangumi_dict (line 308) | def test_valid_id_returns_bangumi_dict(self): method test_not_found_id_returns_error (line 317) | def test_not_found_id_returns_error(self): method test_non_numeric_id_returns_error (line 327) | def test_non_numeric_id_returns_error(self): method test_negative_id_is_passed_to_manager (line 333) | def test_negative_id_is_passed_to_manager(self): method test_result_has_required_fields (line 343) | def test_result_has_required_fields(self): class TestHandleResourceUnknown (line 358) | class TestHandleResourceUnknown: method test_unknown_uri_returns_json_error (line 361) | def test_unknown_uri_returns_json_error(self): method test_unknown_uri_mentions_uri_in_error (line 366) | def test_unknown_uri_mentions_uri_in_error(self): method test_empty_uri_returns_error (line 372) | def test_empty_uri_returns_error(self): method test_completely_different_scheme_returns_error (line 377) | def test_completely_different_scheme_returns_error(self): FILE: backend/src/test/test_mcp_security.py class TestIsAllowed (line 19) | class TestIsAllowed: method setup_method (line 22) | def setup_method(self): method test_ipv4_loopback_allowed (line 37) | def test_ipv4_loopback_allowed(self): method test_ipv4_loopback_range (line 40) | def test_ipv4_loopback_range(self): method test_ipv4_10_network (line 43) | def test_ipv4_10_network(self): method test_ipv4_172_16_network (line 46) | def test_ipv4_172_16_network(self): method test_ipv4_192_168_network (line 49) | def test_ipv4_192_168_network(self): method test_ipv6_loopback (line 52) | def test_ipv6_loopback(self): method test_ipv6_link_local (line 55) | def test_ipv6_link_local(self): method test_ipv6_ula (line 58) | def test_ipv6_ula(self): method test_public_ipv4_denied (line 63) | def test_public_ipv4_denied(self): method test_public_ipv6_denied (line 66) | def test_public_ipv6_denied(self): method test_172_outside_range (line 69) | def test_172_outside_range(self): method test_empty_whitelist_denies_all (line 74) | def test_empty_whitelist_denies_all(self): method test_invalid_hostname (line 79) | def test_invalid_hostname(self): method test_empty_string (line 82) | def test_empty_string(self): method test_malformed_ipv4 (line 85) | def test_malformed_ipv4(self): method test_single_ip_whitelist (line 90) | def test_single_ip_whitelist(self): function _make_mcp_settings (line 100) | def _make_mcp_settings(mcp_whitelist=None, mcp_tokens=None): function _make_app (line 115) | def _make_app() -> Starlette: function _patch_client_ip (line 126) | def _patch_client_ip(app, ip): class TestMcpAccessMiddleware (line 139) | class TestMcpAccessMiddleware: method setup_method (line 142) | def setup_method(self): method test_allowed_ip_passes (line 145) | def test_allowed_ip_passes(self): method test_denied_ip_blocked (line 154) | def test_denied_ip_blocked(self): method test_empty_whitelist_denies_all (line 163) | def test_empty_whitelist_denies_all(self): method test_missing_client_blocked (line 171) | def test_missing_client_blocked(self): method test_bearer_token_bypasses_ip (line 179) | def test_bearer_token_bypasses_ip(self): method test_invalid_bearer_token_denied (line 191) | def test_invalid_bearer_token_denied(self): method test_private_network_with_default_whitelist (line 203) | def test_private_network_with_default_whitelist(self): method test_blocked_response_is_json (line 220) | def test_blocked_response_is_json(self): FILE: backend/src/test/test_mcp_tools.py function _make_response (line 22) | def _make_response(status: bool = True, msg: str = "OK") -> ResponseModel: function _mock_sync_manager (line 26) | def _mock_sync_manager(bangumi_list=None, single=None): class TestBangumiToDict (line 47) | class TestBangumiToDict: method sample_bangumi (line 51) | def sample_bangumi(self) -> Bangumi: method test_returns_dict (line 73) | def test_returns_dict(self, sample_bangumi): method test_id_field (line 78) | def test_id_field(self, sample_bangumi): method test_official_title_field (line 82) | def test_official_title_field(self, sample_bangumi): method test_title_raw_field (line 86) | def test_title_raw_field(self, sample_bangumi): method test_season_field (line 90) | def test_season_field(self, sample_bangumi): method test_episode_offset_field (line 94) | def test_episode_offset_field(self, sample_bangumi): method test_season_offset_field (line 98) | def test_season_offset_field(self, sample_bangumi): method test_rss_link_field (line 102) | def test_rss_link_field(self, sample_bangumi): method test_deleted_field (line 109) | def test_deleted_field(self, sample_bangumi): method test_archived_field (line 113) | def test_archived_field(self, sample_bangumi): method test_eps_collect_field (line 117) | def test_eps_collect_field(self, sample_bangumi): method test_all_expected_keys_present (line 121) | def test_all_expected_keys_present(self, sample_bangumi): method test_none_optional_fields (line 146) | def test_none_optional_fields(self): class TestToolsDefinitions (line 160) | class TestToolsDefinitions: method test_tools_is_list (line 163) | def test_tools_is_list(self): method test_tools_not_empty (line 166) | def test_tools_not_empty(self): method test_all_tools_have_names (line 169) | def test_all_tools_have_names(self): method test_expected_tool_names_present (line 173) | def test_expected_tool_names_present(self): class TestDispatch (line 195) | class TestDispatch: method test_dispatch_list_anime_all (line 200) | async def test_dispatch_list_anime_all(self): method test_dispatch_list_anime_active_only (line 211) | async def test_dispatch_list_anime_active_only(self): method test_dispatch_get_anime_found (line 224) | async def test_dispatch_get_anime_found(self): method test_dispatch_get_anime_not_found (line 235) | async def test_dispatch_get_anime_not_found(self): method test_dispatch_search_anime (line 253) | async def test_dispatch_search_anime(self): method test_dispatch_search_anime_default_site (line 276) | async def test_dispatch_search_anime_default_site(self): method test_dispatch_subscribe_anime_success (line 297) | async def test_dispatch_subscribe_anime_success(self): method test_dispatch_subscribe_anime_failure (line 320) | async def test_dispatch_subscribe_anime_failure(self): method test_dispatch_unsubscribe_disable (line 339) | async def test_dispatch_unsubscribe_disable(self): method test_dispatch_unsubscribe_delete (line 350) | async def test_dispatch_unsubscribe_delete(self): method test_dispatch_list_downloads_all (line 363) | async def test_dispatch_list_downloads_all(self): method test_dispatch_list_downloads_filter_downloading (line 390) | async def test_dispatch_list_downloads_filter_downloading(self): method test_dispatch_list_downloads_keys (line 404) | async def test_dispatch_list_downloads_keys(self): method test_dispatch_list_rss_feeds (line 439) | async def test_dispatch_list_rss_feeds(self): method test_dispatch_get_program_status (line 467) | async def test_dispatch_get_program_status(self): method test_dispatch_refresh_feeds (line 490) | async def test_dispatch_refresh_feeds(self): method test_dispatch_update_anime_success (line 513) | async def test_dispatch_update_anime_success(self): method test_dispatch_update_anime_not_found (line 531) | async def test_dispatch_update_anime_not_found(self): method test_dispatch_unknown_tool (line 544) | async def test_dispatch_unknown_tool(self): class TestHandleTool (line 556) | class TestHandleTool: method test_handle_tool_returns_text_content_list (line 559) | async def test_handle_tool_returns_text_content_list(self): method test_handle_tool_result_is_valid_json (line 572) | async def test_handle_tool_result_is_valid_json(self): method test_handle_tool_exception_returns_error_json (line 583) | async def test_handle_tool_exception_returns_error_json(self): method test_handle_tool_unknown_name_returns_error_json (line 596) | async def test_handle_tool_unknown_name_returns_error_json(self): FILE: backend/src/test/test_migration.py class TestConfigMigration (line 67) | class TestConfigMigration: method test_migrate_old_config_renames_program_fields (line 70) | def test_migrate_old_config_renames_program_fields(self): method test_migrate_old_config_removes_data_version (line 80) | def test_migrate_old_config_removes_data_version(self): method test_migrate_old_config_removes_deprecated_rss_fields (line 85) | def test_migrate_old_config_removes_deprecated_rss_fields(self): method test_migrate_old_config_preserves_valid_fields (line 93) | def test_migrate_old_config_preserves_valid_fields(self): method test_migrate_new_config_no_change (line 106) | def test_migrate_new_config_no_change(self): method test_migrate_does_not_overwrite_new_fields_with_old (line 124) | def test_migrate_does_not_overwrite_new_fields_with_old(self): method test_load_old_config_file (line 142) | def test_load_old_config_file(self): method test_load_old_config_saves_migrated_format (line 172) | def test_load_old_config_saves_migrated_format(self): class TestDatabaseMigration (line 199) | class TestDatabaseMigration: method _create_old_31x_database (line 202) | def _create_old_31x_database(self, engine): method _insert_old_data (line 262) | def _insert_old_data(self, engine): method test_migrate_adds_air_weekday_column (line 301) | def test_migrate_adds_air_weekday_column(self): method test_migrate_preserves_existing_data (line 324) | def test_migrate_preserves_existing_data(self): method test_migrate_preserves_user_data (line 350) | def test_migrate_preserves_user_data(self): method test_migrate_preserves_rss_data (line 366) | def test_migrate_preserves_rss_data(self): method test_migrate_preserves_torrent_data (line 383) | def test_migrate_preserves_torrent_data(self): method test_migrate_idempotent (line 400) | def test_migrate_idempotent(self): method test_new_bangumi_with_air_weekday (line 417) | def test_new_bangumi_with_air_weekday(self): method test_passkey_table_created (line 448) | def test_passkey_table_created(self): method test_schema_version_tracked (line 464) | def test_schema_version_tracked(self): method test_schema_version_skips_applied_migrations (line 481) | def test_schema_version_skips_applied_migrations(self): method test_schema_version_zero_for_old_db (line 499) | def test_schema_version_zero_for_old_db(self): FILE: backend/src/test/test_mock_downloader.py function mock_dl (line 9) | def mock_dl() -> MockDownloader: class TestMockDownloaderInit (line 19) | class TestMockDownloaderInit: method test_initial_state_is_empty (line 20) | def test_initial_state_is_empty(self, mock_dl): method test_initial_categories (line 27) | def test_initial_categories(self, mock_dl): method test_initial_prefs (line 33) | def test_initial_prefs(self, mock_dl): class TestMockDownloaderAuth (line 45) | class TestMockDownloaderAuth: method test_auth_returns_true (line 46) | async def test_auth_returns_true(self, mock_dl): method test_auth_retry_param_accepted (line 50) | async def test_auth_retry_param_accepted(self, mock_dl): method test_logout_does_not_raise (line 54) | async def test_logout_does_not_raise(self, mock_dl): method test_check_host_returns_true (line 57) | async def test_check_host_returns_true(self, mock_dl): method test_check_connection_returns_version_string (line 61) | async def test_check_connection_returns_version_string(self, mock_dl): class TestMockDownloaderPrefs (line 71) | class TestMockDownloaderPrefs: method test_prefs_init_updates_prefs (line 72) | async def test_prefs_init_updates_prefs(self, mock_dl): method test_get_app_prefs_returns_dict (line 78) | async def test_get_app_prefs_returns_dict(self, mock_dl): class TestMockDownloaderCategories (line 89) | class TestMockDownloaderCategories: method test_add_category_persists (line 90) | async def test_add_category_persists(self, mock_dl): method test_add_duplicate_category_no_error (line 95) | async def test_add_duplicate_category_no_error(self, mock_dl): class TestMockDownloaderAddTorrents (line 107) | class TestMockDownloaderAddTorrents: method test_add_torrent_url_returns_true (line 108) | async def test_add_torrent_url_returns_true(self, mock_dl): method test_add_torrent_stores_in_state (line 117) | async def test_add_torrent_stores_in_state(self, mock_dl): method test_add_torrent_with_tag_stored (line 127) | async def test_add_torrent_with_tag_stored(self, mock_dl): method test_add_torrent_with_file_bytes (line 139) | async def test_add_torrent_with_file_bytes(self, mock_dl): method test_two_different_torrents_stored_separately (line 148) | async def test_two_different_torrents_stored_separately(self, mock_dl): class TestMockDownloaderTorrentsInfo (line 165) | class TestMockDownloaderTorrentsInfo: method test_returns_all_when_no_filter (line 166) | async def test_returns_all_when_no_filter(self, mock_dl): method test_filters_by_category (line 172) | async def test_filters_by_category(self, mock_dl): method test_filters_by_tag (line 179) | async def test_filters_by_tag(self, mock_dl): method test_empty_store_returns_empty_list (line 190) | async def test_empty_store_returns_empty_list(self, mock_dl): class TestMockDownloaderTorrentsFiles (line 195) | class TestMockDownloaderTorrentsFiles: method test_returns_files_for_known_hash (line 196) | async def test_returns_files_for_known_hash(self, mock_dl): method test_returns_empty_list_for_unknown_hash (line 203) | async def test_returns_empty_list_for_unknown_hash(self, mock_dl): class TestMockDownloaderDelete (line 208) | class TestMockDownloaderDelete: method test_delete_single_torrent (line 209) | async def test_delete_single_torrent(self, mock_dl): method test_delete_multiple_torrents_pipe_separated (line 214) | async def test_delete_multiple_torrents_pipe_separated(self, mock_dl): method test_delete_nonexistent_hash_no_error (line 221) | async def test_delete_nonexistent_hash_no_error(self, mock_dl): class TestMockDownloaderPauseResume (line 225) | class TestMockDownloaderPauseResume: method test_pause_sets_state (line 226) | async def test_pause_sets_state(self, mock_dl): method test_resume_sets_state (line 231) | async def test_resume_sets_state(self, mock_dl): method test_pause_multiple_pipe_separated (line 236) | async def test_pause_multiple_pipe_separated(self, mock_dl): method test_pause_unknown_hash_no_error (line 243) | async def test_pause_unknown_hash_no_error(self, mock_dl): class TestMockDownloaderRename (line 252) | class TestMockDownloaderRename: method test_rename_returns_true (line 253) | async def test_rename_returns_true(self, mock_dl): method test_rename_with_verify_flag (line 261) | async def test_rename_with_verify_flag(self, mock_dl): class TestMockDownloaderRssFeeds (line 276) | class TestMockDownloaderRssFeeds: method test_add_feed_stored (line 277) | async def test_add_feed_stored(self, mock_dl): method test_remove_feed (line 283) | async def test_remove_feed(self, mock_dl): method test_remove_nonexistent_feed_no_error (line 289) | async def test_remove_nonexistent_feed_no_error(self, mock_dl): method test_get_feeds_initially_empty (line 292) | async def test_get_feeds_initially_empty(self, mock_dl): class TestMockDownloaderRules (line 302) | class TestMockDownloaderRules: method test_set_rule_stored (line 303) | async def test_set_rule_stored(self, mock_dl): method test_remove_rule (line 310) | async def test_remove_rule(self, mock_dl): method test_remove_nonexistent_rule_no_error (line 316) | async def test_remove_nonexistent_rule_no_error(self, mock_dl): method test_get_download_rule_initially_empty (line 319) | async def test_get_download_rule_initially_empty(self, mock_dl): class TestMockDownloaderMovePath (line 329) | class TestMockDownloaderMovePath: method test_move_torrent_updates_save_path (line 330) | async def test_move_torrent_updates_save_path(self, mock_dl): method test_move_multiple_pipe_separated (line 335) | async def test_move_multiple_pipe_separated(self, mock_dl): method test_get_torrent_path_known_hash (line 342) | async def test_get_torrent_path_known_hash(self, mock_dl): method test_get_torrent_path_unknown_hash_returns_default (line 347) | async def test_get_torrent_path_unknown_hash_returns_default(self, moc... class TestMockDownloaderSetCategory (line 357) | class TestMockDownloaderSetCategory: method test_set_category_updates_torrent (line 358) | async def test_set_category_updates_torrent(self, mock_dl): method test_set_category_unknown_hash_no_error (line 363) | async def test_set_category_unknown_hash_no_error(self, mock_dl): class TestMockDownloaderTags (line 372) | class TestMockDownloaderTags: method test_add_tag_appends (line 373) | async def test_add_tag_appends(self, mock_dl): method test_add_tag_no_duplicates (line 378) | async def test_add_tag_no_duplicates(self, mock_dl): method test_add_tag_unknown_hash_no_error (line 384) | async def test_add_tag_unknown_hash_no_error(self, mock_dl): method test_multiple_tags_on_same_torrent (line 387) | async def test_multiple_tags_on_same_torrent(self, mock_dl): class TestAddMockTorrentHelper (line 400) | class TestAddMockTorrentHelper: method test_generates_hash_from_name (line 401) | def test_generates_hash_from_name(self, mock_dl): method test_explicit_hash_used (line 406) | def test_explicit_hash_used(self, mock_dl): method test_torrent_state_is_completed_by_default (line 410) | def test_torrent_state_is_completed_by_default(self, mock_dl): method test_torrent_state_custom (line 415) | def test_torrent_state_custom(self, mock_dl): method test_default_file_is_mkv (line 420) | def test_default_file_is_mkv(self, mock_dl): method test_custom_files_stored (line 426) | def test_custom_files_stored(self, mock_dl): FILE: backend/src/test/test_notification.py class TestProviderRegistry (line 26) | class TestProviderRegistry: method test_telegram (line 27) | def test_telegram(self): method test_discord (line 31) | def test_discord(self): method test_bark (line 35) | def test_bark(self): method test_server_chan (line 39) | def test_server_chan(self): method test_wecom (line 44) | def test_wecom(self): method test_gotify (line 48) | def test_gotify(self): method test_pushover (line 52) | def test_pushover(self): method test_webhook (line 56) | def test_webhook(self): method test_unknown_type (line 60) | def test_unknown_type(self): class TestNotificationManager (line 71) | class TestNotificationManager: method mock_settings (line 73) | def mock_settings(self): method test_empty_providers (line 79) | def test_empty_providers(self, mock_settings): method test_load_single_provider (line 84) | def test_load_single_provider(self, mock_settings): method test_skip_disabled_provider (line 93) | def test_skip_disabled_provider(self, mock_settings): method test_load_multiple_providers (line 101) | def test_load_multiple_providers(self, mock_settings): method test_skip_unknown_provider (line 116) | def test_skip_unknown_provider(self, mock_settings): method test_send_all (line 127) | async def test_send_all(self, mock_settings): method test_test_provider (line 151) | async def test_test_provider(self, mock_settings): method test_test_provider_invalid_index (line 167) | async def test_test_provider_invalid_index(self, mock_settings): class TestTelegramProvider (line 182) | class TestTelegramProvider: method provider (line 184) | def provider(self): method test_send_with_photo (line 188) | async def test_send_with_photo(self, provider): method test_send_without_photo (line 203) | async def test_send_without_photo(self, provider): method test_test_success (line 216) | async def test_test_success(self, provider): class TestDiscordProvider (line 226) | class TestDiscordProvider: method provider (line 228) | def provider(self): method test_send (line 234) | async def test_send(self, provider): class TestBarkProvider (line 249) | class TestBarkProvider: method provider (line 251) | def provider(self): method test_send (line 255) | async def test_send(self, provider): class TestWebhookProvider (line 268) | class TestWebhookProvider: method provider (line 270) | def provider(self): method test_render_template (line 279) | def test_render_template(self, provider): method test_send (line 287) | async def test_send(self, provider): class TestConfigMigration (line 303) | class TestConfigMigration: method test_legacy_config_migration (line 304) | def test_legacy_config_migration(self): method test_new_config_no_migration (line 321) | def test_new_config_no_migration(self): FILE: backend/src/test/test_openai.py class TestOpenAIParser (line 8) | class TestOpenAIParser: method setup_class (line 10) | def setup_class(cls): method test__prepare_params_with_openai (line 15) | def test__prepare_params_with_openai(self): method test__prepare_params_with_azure (line 30) | def test__prepare_params_with_azure(self): method test_parse (line 54) | def test_parse(self): FILE: backend/src/test/test_path.py function torrent_path (line 13) | def torrent_path(): class TestGenSavePath (line 22) | class TestGenSavePath: method test_with_year (line 23) | def test_with_year(self): method test_without_year (line 33) | def test_without_year(self): method test_season_formatting (line 44) | def test_season_formatting(self): method test_with_different_base_path (line 53) | def test_with_different_base_path(self): class TestRuleName (line 70) | class TestRuleName: method test_without_group_tag (line 71) | def test_without_group_tag(self): method test_with_group_tag (line 80) | def test_with_group_tag(self): class TestCheckFiles (line 95) | class TestCheckFiles: method test_separates_media_and_subtitles (line 96) | def test_separates_media_and_subtitles(self): method test_ignores_other_extensions (line 113) | def test_ignores_other_extensions(self): method test_case_insensitive_extensions (line 126) | def test_case_insensitive_extensions(self): method test_empty_file_list (line 139) | def test_empty_file_list(self): method test_nested_paths (line 145) | def test_nested_paths(self): class TestPathToBangumi (line 162) | class TestPathToBangumi: method test_extracts_name_and_season (line 163) | def test_extracts_name_and_season(self): method test_season_1_default (line 175) | def test_season_1_default(self): method test_s_prefix_pattern (line 185) | def test_s_prefix_pattern(self): class TestIsEp (line 200) | class TestIsEp: method test_shallow_file (line 201) | def test_shallow_file(self): method test_one_folder_deep (line 206) | def test_one_folder_deep(self): method test_too_deep (line 211) | def test_too_deep(self): method test_file_depth (line 216) | def test_file_depth(self): FILE: backend/src/test/test_path_parser.py function test_path_to_bangumi (line 6) | def test_path_to_bangumi(): class TestGenSavePath (line 16) | class TestGenSavePath: method test_gen_save_path_no_offset (line 19) | def test_gen_save_path_no_offset(self): method test_gen_save_path_with_positive_offset (line 38) | def test_gen_save_path_with_positive_offset(self): method test_gen_save_path_with_negative_offset (line 57) | def test_gen_save_path_with_negative_offset(self): method test_gen_save_path_offset_below_one_ignored (line 75) | def test_gen_save_path_offset_below_one_ignored(self): method test_gen_save_path_season_two_no_offset (line 93) | def test_gen_save_path_season_two_no_offset(self): method test_gen_save_path_large_positive_offset (line 111) | def test_gen_save_path_large_positive_offset(self): method test_gen_save_path_offset_yields_exactly_season_one (line 129) | def test_gen_save_path_offset_yields_exactly_season_one(self): FILE: backend/src/test/test_qb_downloader.py class TestQbDownloaderConstructor (line 22) | class TestQbDownloaderConstructor: method test_ssl_true_no_scheme_uses_https (line 25) | def test_ssl_true_no_scheme_uses_https(self): method test_ssl_false_no_scheme_uses_http (line 30) | def test_ssl_false_no_scheme_uses_http(self): method test_explicit_http_scheme_preserved_when_ssl_true (line 35) | def test_explicit_http_scheme_preserved_when_ssl_true(self): method test_explicit_https_scheme_preserved_when_ssl_false (line 42) | def test_explicit_https_scheme_preserved_when_ssl_false(self): method test_explicit_http_scheme_preserved_ssl_false (line 49) | def test_explicit_http_scheme_preserved_ssl_false(self): method test_explicit_https_scheme_preserved_ssl_true (line 54) | def test_explicit_https_scheme_preserved_ssl_true(self): method test_credentials_stored (line 59) | def test_credentials_stored(self): method test_client_initially_none (line 68) | def test_client_initially_none(self): function test_scheme_selection_matrix (line 95) | def test_scheme_selection_matrix(host: str, ssl: bool, expected_prefix: ... class TestAuthClientCreation (line 108) | class TestAuthClientCreation: method test_auth_creates_client_with_verify_false_when_ssl_true (line 111) | async def test_auth_creates_client_with_verify_false_when_ssl_true(self): method test_auth_creates_client_with_verify_false_when_ssl_false (line 137) | async def test_auth_creates_client_with_verify_false_when_ssl_false(se... method test_auth_uses_5_second_connect_timeout (line 162) | async def test_auth_uses_5_second_connect_timeout(self): class TestAuthSuccessFailure (line 193) | class TestAuthSuccessFailure: method test_auth_returns_true_on_ok_response (line 196) | async def test_auth_returns_true_on_ok_response(self): method test_auth_returns_false_on_403 (line 214) | async def test_auth_returns_false_on_403(self): method test_auth_retries_up_to_limit_on_server_error (line 234) | async def test_auth_retries_up_to_limit_on_server_error(self): class TestAuthConnectErrorLogging (line 263) | class TestAuthConnectErrorLogging: method test_https_url_logs_https_specific_guidance (line 266) | async def test_https_url_logs_https_specific_guidance(self, caplog): method test_https_url_derived_from_ssl_flag_logs_https_guidance (line 296) | async def test_https_url_derived_from_ssl_flag_logs_https_guidance(sel... method test_http_url_logs_generic_message_without_ssl_hint (line 321) | async def test_http_url_logs_generic_message_without_ssl_hint(self, ca... method test_http_url_derived_from_ssl_flag_false_no_ssl_hint (line 349) | async def test_http_url_derived_from_ssl_flag_false_no_ssl_hint(self, ... method test_connect_error_logs_check_ip_port_info (line 374) | async def test_connect_error_logs_check_ip_port_info(self, caplog): method test_explicit_http_with_ssl_true_still_uses_generic_message (line 399) | async def test_explicit_http_with_ssl_true_still_uses_generic_message(... class TestUrlHelper (line 433) | class TestUrlHelper: method test_url_format_with_http (line 436) | def test_url_format_with_http(self): method test_url_format_with_https (line 441) | def test_url_format_with_https(self): method test_url_with_explicit_http_scheme_overriding_ssl_true (line 446) | def test_url_with_explicit_http_scheme_overriding_ssl_true(self): FILE: backend/src/test/test_raw_parser.py function test_raw_parser (line 6) | def test_raw_parser(): class TestIssue924SpecialPunctuation (line 177) | class TestIssue924SpecialPunctuation: method test_parse_title_with_fullwidth_parens (line 180) | def test_parse_title_with_fullwidth_parens(self): class TestIssue910NeoQswFormat (line 192) | class TestIssue910NeoQswFormat: method test_parse_neo_qsw_format (line 197) | def test_parse_neo_qsw_format(self): class TestIssue876NoSeparator (line 204) | class TestIssue876NoSeparator: method test_parse_without_dash (line 213) | def test_parse_without_dash(self): class TestIssue819ChineseEpisodeMarker (line 221) | class TestIssue819ChineseEpisodeMarker: method test_parse_chinese_episode_marker (line 224) | def test_parse_chinese_episode_marker(self): class TestIssue811ColonInTitle (line 235) | class TestIssue811ColonInTitle: method test_parse_colon_in_english_title (line 238) | def test_parse_colon_in_english_title(self): class TestIssue798VTuberTitle (line 249) | class TestIssue798VTuberTitle: method test_parse_vtuber_title (line 252) | def test_parse_vtuber_title(self): class TestIssue794PreEpisodeFormat (line 265) | class TestIssue794PreEpisodeFormat: method test_parse_pre_episode (line 274) | def test_parse_pre_episode(self): method test_returns_none (line 281) | def test_returns_none(self, title): class TestIssue766Lv2InTitle (line 286) | class TestIssue766Lv2InTitle: method test_parse_lv2_title (line 289) | def test_parse_lv2_title(self): class TestIssue764WesternFormat (line 301) | class TestIssue764WesternFormat: method test_parse_western_format (line 304) | def test_parse_western_format(self): class TestIssue986AtlasFormat (line 318) | class TestIssue986AtlasFormat: method test_parse_atlas_format (line 327) | def test_parse_atlas_format(self): method test_returns_none (line 334) | def test_returns_none(self, title): class TestIssue773CompoundEpisode (line 339) | class TestIssue773CompoundEpisode: method test_parse_compound_episode (line 344) | def test_parse_compound_episode(self): class TestIssue805TitleWithCht (line 351) | class TestIssue805TitleWithCht: method test_parse_cht_title (line 354) | def test_parse_cht_title(self): FILE: backend/src/test/test_renamer.py class TestGenPath (line 15) | class TestGenPath: method test_pn_method (line 16) | def test_pn_method(self): method test_advance_method (line 24) | def test_advance_method(self): method test_none_method (line 32) | def test_none_method(self): method test_subtitle_none_method (line 44) | def test_subtitle_none_method(self): method test_subtitle_pn_method (line 57) | def test_subtitle_pn_method(self): method test_subtitle_advance_method (line 70) | def test_subtitle_advance_method(self): method test_zero_padding_single_digit (line 83) | def test_zero_padding_single_digit(self): method test_no_padding_double_digit (line 91) | def test_no_padding_double_digit(self): method test_unknown_method_returns_original (line 99) | def test_unknown_method_returns_original(self): method test_mp4_suffix (line 107) | def test_mp4_suffix(self): class TestRenameFile (line 121) | class TestRenameFile: method renamer (line 123) | def renamer(self, mock_qb_client): method test_successful_rename (line 143) | async def test_successful_rename(self, renamer): method test_parse_fails_no_remove (line 165) | async def test_parse_fails_no_remove(self, renamer): method test_parse_fails_remove_bad (line 182) | async def test_parse_fails_remove_bad(self, renamer): method test_same_path_skipped (line 200) | async def test_same_path_skipped(self, renamer): class TestRenameCollection (line 228) | class TestRenameCollection: method renamer (line 230) | def renamer(self, mock_qb_client): method test_renames_each_file (line 248) | async def test_renames_each_file(self, renamer): method test_skips_deep_files (line 274) | async def test_skips_deep_files(self, renamer): class TestRenameSubtitles (line 304) | class TestRenameSubtitles: method renamer (line 306) | def renamer(self, mock_qb_client): method test_renames_subtitles_with_language (line 323) | async def test_renames_subtitles_with_language(self, renamer): class TestRenameFlow (line 359) | class TestRenameFlow: method renamer (line 361) | def renamer(self, mock_qb_client): method test_single_file_rename (line 379) | async def test_single_file_rename(self, renamer): method test_collection_sets_category (line 409) | async def test_collection_sets_category(self, renamer): method test_no_media_files_no_crash (line 445) | async def test_no_media_files_no_crash(self, renamer): class TestParseBangumiIdFromTags (line 473) | class TestParseBangumiIdFromTags: method test_single_ab_tag (line 476) | def test_single_ab_tag(self): method test_ab_tag_with_other_tags (line 481) | def test_ab_tag_with_other_tags(self): method test_ab_tag_with_spaces (line 486) | def test_ab_tag_with_spaces(self): method test_empty_string (line 491) | def test_empty_string(self): method test_none_input (line 496) | def test_none_input(self): method test_no_ab_tag (line 501) | def test_no_ab_tag(self): method test_invalid_ab_tag_non_numeric (line 506) | def test_invalid_ab_tag_non_numeric(self): method test_ab_tag_first_match (line 511) | def test_ab_tag_first_match(self): method test_ab_tag_zero (line 516) | def test_ab_tag_zero(self): method test_ab_tag_large_number (line 521) | def test_ab_tag_large_number(self): class TestGenPathWithOffsets (line 532) | class TestGenPathWithOffsets: method test_episode_offset_positive (line 535) | def test_episode_offset_positive(self): method test_episode_offset_negative (line 543) | def test_episode_offset_negative(self): method test_episode_offset_negative_below_zero_ignored (line 551) | def test_episode_offset_negative_below_zero_ignored(self): method test_episode_offset_producing_zero_ignored (line 559) | def test_episode_offset_producing_zero_ignored(self): method test_episode_zero_preserved_without_offset (line 567) | def test_episode_zero_preserved_without_offset(self): method test_season_offset_positive (line 575) | def test_season_offset_positive(self): method test_season_offset_negative (line 591) | def test_season_offset_negative(self): method test_season_offset_negative_below_one_ignored (line 602) | def test_season_offset_negative_below_one_ignored(self): method test_both_offsets_combined (line 610) | def test_both_offsets_combined(self): method test_offset_with_advance_method (line 625) | def test_offset_with_advance_method(self): method test_offset_with_subtitle_method (line 635) | def test_offset_with_subtitle_method(self): method test_offset_none_method_unchanged (line 650) | def test_offset_none_method_unchanged(self): class TestLookupOffsets (line 668) | class TestLookupOffsets: method renamer (line 672) | def renamer(self, mock_qb_client): method test_lookup_by_qb_hash (line 690) | def test_lookup_by_qb_hash(self, renamer, db_session): method test_lookup_by_tag_when_hash_not_found (line 736) | def test_lookup_by_tag_when_hash_not_found(self, renamer, db_session): method test_lookup_by_torrent_name (line 772) | def test_lookup_by_torrent_name(self, renamer, db_session): method test_lookup_by_save_path_fallback (line 808) | def test_lookup_by_save_path_fallback(self, renamer, db_session): method test_lookup_returns_zero_when_not_found (line 845) | def test_lookup_returns_zero_when_not_found(self, renamer, db_session): method test_lookup_skips_deleted_bangumi (line 868) | def test_lookup_skips_deleted_bangumi(self, renamer, db_session): method test_lookup_handles_database_exception (line 906) | def test_lookup_handles_database_exception(self, renamer): method test_lookup_by_save_path_with_trailing_slash (line 921) | def test_lookup_by_save_path_with_trailing_slash(self, renamer, db_ses... method test_lookup_by_save_path_with_backslashes (line 959) | def test_lookup_by_save_path_with_backslashes(self, renamer, db_session): class TestNormalizePath (line 998) | class TestNormalizePath: method test_empty_path (line 1001) | def test_empty_path(self): method test_removes_trailing_slash (line 1006) | def test_removes_trailing_slash(self): method test_removes_trailing_backslash (line 1011) | def test_removes_trailing_backslash(self): method test_converts_backslashes (line 1016) | def test_converts_backslashes(self): method test_preserves_forward_slashes (line 1021) | def test_preserves_forward_slashes(self): FILE: backend/src/test/test_rss_engine.py function test_rss_engine (line 8) | async def test_rss_engine(): FILE: backend/src/test/test_rss_engine_new.py function rss_engine (line 18) | def rss_engine(db_engine): function clear_bangumi_cache (line 25) | def clear_bangumi_cache(): class TestPullRss (line 37) | class TestPullRss: method test_returns_only_new_torrents (line 38) | async def test_returns_only_new_torrents(self, rss_engine): method test_all_existing_returns_empty (line 61) | async def test_all_existing_returns_empty(self, rss_engine): method test_empty_feed_returns_empty (line 78) | async def test_empty_feed_returns_empty(self, rss_engine): class TestMatchTorrent (line 96) | class TestMatchTorrent: method test_matches_by_title_raw_substring (line 97) | def test_matches_by_title_raw_substring(self, rss_engine): method test_no_match_returns_none (line 110) | def test_no_match_returns_none(self, rss_engine): method test_filter_excludes_matching_torrent (line 120) | def test_filter_excludes_matching_torrent(self, rss_engine): method test_empty_filter_allows_match (line 132) | def test_empty_filter_allows_match(self, rss_engine): method test_filter_case_insensitive (line 144) | def test_filter_case_insensitive(self, rss_engine): method test_deleted_bangumi_not_matched (line 157) | def test_deleted_bangumi_not_matched(self, rss_engine): method test_comma_separated_filters (line 167) | def test_comma_separated_filters(self, rss_engine): class TestRefreshRss (line 190) | class TestRefreshRss: method test_downloads_matched_torrents (line 191) | async def test_downloads_matched_torrents(self, rss_engine, mock_qb_cl... method test_unmatched_torrents_stored_not_downloaded (line 220) | async def test_unmatched_torrents_stored_not_downloaded(self, rss_engi... method test_refresh_specific_rss_id (line 240) | async def test_refresh_specific_rss_id(self, rss_engine): method test_refresh_nonexistent_rss_id (line 255) | async def test_refresh_nonexistent_rss_id(self, rss_engine): class TestAddRss (line 269) | class TestAddRss: method test_add_with_name (line 270) | async def test_add_with_name(self, rss_engine): method test_add_without_name_fetches_title (line 285) | async def test_add_without_name_fetches_title(self, rss_engine): method test_add_without_name_fetch_fails (line 304) | async def test_add_without_name_fetch_fails(self, rss_engine): method test_add_duplicate_url_fails (line 322) | async def test_add_duplicate_url_fails(self, rss_engine): FILE: backend/src/test/test_searcher.py class TestSearchUrl (line 15) | class TestSearchUrl: method mock_search_config (line 17) | def mock_search_config(self): method test_mikan_url (line 27) | def test_mikan_url(self): method test_nyaa_url (line 36) | def test_nyaa_url(self): method test_dmhy_url (line 42) | def test_dmhy_url(self): method test_unsupported_site_raises (line 48) | def test_unsupported_site_raises(self): method test_keyword_sanitization (line 53) | def test_keyword_sanitization(self): method test_multiple_keywords_joined (line 60) | def test_multiple_keywords_joined(self): method test_aggregate_is_false (line 69) | def test_aggregate_is_false(self): class TestSpecialUrl (line 80) | class TestSpecialUrl: method test_uses_bangumi_fields (line 81) | def test_uses_bangumi_fields(self): method test_skips_none_fields (line 105) | def test_skips_none_fields(self): FILE: backend/src/test/test_setup.py function client (line 11) | def client(): function mock_first_run (line 21) | def mock_first_run(): function mock_setup_complete (line 35) | def mock_setup_complete(): class TestSetupStatus (line 42) | class TestSetupStatus: method test_status_first_run (line 43) | def test_status_first_run(self, client, mock_first_run): method test_status_setup_complete (line 50) | def test_status_setup_complete(self, client, mock_setup_complete): method test_status_config_changed (line 56) | def test_status_config_changed(self, client): class TestSetupGuard (line 75) | class TestSetupGuard: method test_test_downloader_blocked_after_setup (line 76) | def test_test_downloader_blocked_after_setup(self, client, mock_setup_... method test_test_rss_blocked_after_setup (line 89) | def test_test_rss_blocked_after_setup(self, client, mock_setup_complete): method test_test_notification_blocked_after_setup (line 96) | def test_test_notification_blocked_after_setup(self, client, mock_setu... method test_complete_blocked_after_setup (line 103) | def test_complete_blocked_after_setup(self, client, mock_setup_complete): class TestTestDownloader (line 126) | class TestTestDownloader: method test_private_ip_accepted (line 127) | def test_private_ip_accepted(self, client, mock_first_run): method test_loopback_ip_accepted (line 155) | def test_loopback_ip_accepted(self, client, mock_first_run): method test_connection_timeout (line 181) | def test_connection_timeout(self, client, mock_first_run): method test_connection_refused (line 206) | def test_connection_refused(self, client, mock_first_run): class TestTestRSS (line 232) | class TestTestRSS: method test_invalid_url (line 233) | def test_invalid_url(self, client, mock_first_run): class TestRequestValidation (line 250) | class TestRequestValidation: method test_username_too_short (line 251) | def test_username_too_short(self, client, mock_first_run): method test_password_too_short (line 273) | def test_password_too_short(self, client, mock_first_run): class TestSentinelPath (line 296) | class TestSentinelPath: method test_sentinel_path_is_in_config_dir (line 297) | def test_sentinel_path_is_in_config_dir(self): FILE: backend/src/test/test_title_parser.py class TestTitleParser (line 6) | class TestTitleParser: method test_parse_without_openai (line 7) | def test_parse_without_openai(self): method test_parse_with_openai (line 20) | def test_parse_with_openai(self): FILE: backend/src/test/test_tmdb.py function test_tmdb_parser (line 4) | async def test_tmdb_parser(): FILE: backend/src/test/test_torrent_parser.py function test_torrent_parser (line 8) | def test_torrent_parser(): class TestGetPathBasename (line 131) | class TestGetPathBasename: method test_regular_path (line 132) | def test_regular_path(self): method test_empty_path (line 135) | def test_empty_path(self): method test_path_with_trailing_slash (line 138) | def test_path_with_trailing_slash(self): method test_windows_path (line 142) | def test_windows_path(self): FILE: backend/src/test_passkey_server.py function startup (line 22) | async def startup(): function index (line 30) | def index(): FILE: docs/.vitepress/theme/index.ts method setup (line 16) | setup() { FILE: webui/.storybook/main.ts method viteFinal (line 18) | viteFinal(config) { FILE: webui/src/api/auth.ts method login (line 5) | async login(username: string, password: string) { method refresh (line 24) | async refresh() { method logout (line 29) | async logout() { method update (line 34) | async update(username: string, password: string) { FILE: webui/src/api/bangumi.ts method getAll (line 16) | async getAll() { method getRule (line 32) | async getRule(bangumiId: number) { method updateRule (line 51) | async updateRule(bangumiId: number, bangumiRule: BangumiRule) { method deleteRule (line 71) | async deleteRule(bangumiId: number | number[], file: boolean) { method disableRule (line 97) | async disableRule(bangumiId: number | number[], file: boolean) { method enableRule (line 121) | async enableRule(bangumiId: number) { method resetAll (line 131) | async resetAll() { method refreshPoster (line 139) | async refreshPoster() { method refreshCalendar (line 149) | async refreshCalendar() { method archiveRule (line 160) | async archiveRule(bangumiId: number) { method unarchiveRule (line 171) | async unarchiveRule(bangumiId: number) { method refreshMetadata (line 181) | async refreshMetadata() { method suggestOffset (line 192) | async suggestOffset(bangumiId: number) { method detectOffset (line 203) | async detectOffset(request: DetectOffsetRequest) { method dismissReview (line 215) | async dismissReview(bangumiId: number) { method setWeekday (line 227) | async setWeekday(bangumiId: number, weekday: number | null) { method getNeedsReview (line 238) | async getNeedsReview() { FILE: webui/src/api/check.ts method downloader (line 5) | async downloader() { FILE: webui/src/api/config.ts method getConfig (line 8) | async getConfig() { method updateConfig (line 17) | async updateConfig(newConfig: Config) { FILE: webui/src/api/download.ts method analysis (line 10) | async analysis(rss_item: RSS) { method collection (line 28) | async collection(bangumiData: BangumiRule) { method subscribe (line 45) | async subscribe(bangumiData: BangumiRule, rss: RSS) { FILE: webui/src/api/downloader.ts method getTorrents (line 5) | async getTorrents() { method pause (line 12) | async pause(hashes: string[]) { method resume (line 20) | async resume(hashes: string[]) { method deleteTorrents (line 28) | async deleteTorrents(hashes: string[], deleteFiles = false) { FILE: webui/src/api/log.ts method getLog (line 4) | async getLog() { method clearLog (line 9) | async clearLog() { FILE: webui/src/api/notification.ts type TestProviderRequest (line 4) | interface TestProviderRequest { type TestProviderConfigRequest (line 8) | interface TestProviderConfigRequest { type TestResponse (line 22) | interface TestResponse { method testProvider (line 33) | async testProvider(request: TestProviderRequest) { method testProviderConfig (line 44) | async testProviderConfig(request: TestProviderConfigRequest) { FILE: webui/src/api/passkey.ts method getRegistrationOptions (line 22) | async getRegistrationOptions(): Promise { method verifyRegistration (line 32) | async verifyRegistration(request: PasskeyCreateRequest): Promise { method delete (line 81) | async delete(request: PasskeyDeleteRequest): Promise { FILE: webui/src/api/program.ts method restart (line 7) | async restart() { method start (line 15) | async start() { method stop (line 23) | async stop() { method status (line 31) | async status() { method shutdown (line 42) | async shutdown() { FILE: webui/src/api/rss.ts method get (line 6) | async get() { method add (line 11) | async add(rss: RSS) { method delete (line 16) | async delete(rss_id: number) { method deleteMany (line 23) | async deleteMany(rss_list: number[]) { method disable (line 31) | async disable(rss_id: number) { method disableMany (line 38) | async disableMany(rss_list: number[]) { method update (line 46) | async update(rss_id: number, rss: RSS) { method enableMany (line 54) | async enableMany(rss_list: number[]) { method refreshAll (line 62) | async refreshAll() { method refresh (line 67) | async refresh(rss_id: number) { method getTorrent (line 74) | async getTorrent(rss_id: number) { FILE: webui/src/api/search.ts type EventSourceStatus (line 5) | type EventSourceStatus = 'OPEN' | 'CONNECTING' | 'CLOSED'; method get (line 8) | get() { method getProvider (line 66) | async getProvider() { FILE: webui/src/api/setup.ts method getStatus (line 11) | async getStatus() { method testDownloader (line 16) | async testDownloader(config: TestDownloaderRequest) { method testRSS (line 24) | async testRSS(url: string) { method testNotification (line 31) | async testNotification(config: TestNotificationRequest) { method complete (line 39) | async complete(config: SetupCompleteRequest) { FILE: webui/src/components/basic/__tests__/ab-button.test.ts method setup (line 14) | setup(props, { slots }) { FILE: webui/src/components/basic/__tests__/ab-switch.test.ts method setup (line 15) | setup(props, { emit, slots }) { FILE: webui/src/components/basic/ab-add.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/components/basic/ab-button-multi.stories.ts type Story (line 22) | type Story = StoryObj; method setup (line 27) | setup() { FILE: webui/src/components/basic/ab-button.stories.ts type Story (line 22) | type Story = StoryObj; method setup (line 27) | setup() { FILE: webui/src/components/basic/ab-checkbox.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/components/basic/ab-page-title.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/components/basic/ab-search.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/components/basic/ab-select.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/components/basic/ab-status.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/components/basic/ab-switch.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/components/basic/ab-tag.stories.ts type Story (line 12) | type Story = StoryObj; method setup (line 17) | setup() { FILE: webui/src/hooks/__tests__/useApi.test.ts type Options (line 9) | interface Options { type AnyAsyncFunction (line 18) | type AnyAsyncFunction = (...args: any[]) => Promise; function createUseApi (line 20) | function createUseApi( FILE: webui/src/hooks/__tests__/useAuth.test.ts type User (line 68) | interface User { FILE: webui/src/hooks/useAddRss.ts function useAddRss (line 9) | function useAddRss() { FILE: webui/src/hooks/useApi.ts type AnyAsyncFuntion (line 1) | type AnyAsyncFuntion = (...args: any[]) => Promise; type Options (line 3) | interface Options { function useApi (line 11) | function useApi< FILE: webui/src/hooks/useAppInfo.ts function getStatus (line 8) | function getStatus() { FILE: webui/src/hooks/useAuth.ts function clearUser (line 17) | function clearUser() { function formVerify (line 22) | function formVerify() { function login (line 37) | function login() { method onSuccess (line 57) | onSuccess() { method onSuccess (line 66) | onSuccess() { function update (line 71) | function update() { FILE: webui/src/hooks/useDarkMode.ts type ThemeMode (line 4) | type ThemeMode = 'light' | 'dark' | 'system'; function applyTheme (line 16) | function applyTheme() { function setMode (line 25) | function setMode(newMode: ThemeMode) { function toggle (line 34) | function toggle() { FILE: webui/src/hooks/useMyI18n.ts type Languages (line 12) | type Languages = keyof typeof messages; function normalizeLocale (line 14) | function normalizeLocale(locale: string): Languages { function changeLocale (line 38) | function changeLocale() { function returnUserLangText (line 46) | function returnUserLangText(texts: { function returnUserLangMsg (line 52) | function returnUserLangMsg(res: ApiSuccess) { FILE: webui/src/hooks/usePasskey.ts function loadPasskeys (line 21) | async function loadPasskeys() { function addPasskey (line 35) | async function addPasskey(deviceName: string): Promise { function loginWithPasskey (line 54) | async function loginWithPasskey(username?: string): Promise { function deletePasskey (line 72) | async function deletePasskey(passkeyId: number): Promise { FILE: webui/src/hooks/useSafeArea.ts function useSafeArea (line 3) | function useSafeArea() { FILE: webui/src/services/webauthn.ts function base64UrlToBuffer (line 10) | function base64UrlToBuffer(base64url: string): ArrayBuffer { function bufferToBase64Url (line 24) | function bufferToBase64Url(buffer: ArrayBuffer): string { function registerPasskey (line 39) | async function registerPasskey(deviceName: string): Promise { function loginWithPasskey (line 113) | async function loginWithPasskey(username?: string): Promise { function isWebAuthnSupported (line 167) | function isWebAuthnSupported(): boolean { function isPlatformAuthenticatorAvailable (line 175) | async function isPlatformAuthenticatorAvailable(): Promise { FILE: webui/src/store/bangumi.ts function getAll (line 27) | async function getAll() { function refreshData (line 43) | function refreshData() { method onSuccess (line 50) | onSuccess() { function openEditPopup (line 64) | function openEditPopup(data: BangumiRule) { function ruleManage (line 69) | function ruleManage( function setWeekday (line 85) | async function setWeekday(bangumiId: number, weekday: number | null) { FILE: webui/src/store/config.ts function getConfig (line 6) | async function getConfig() { method onSuccess (line 13) | onSuccess() { function getSettingGroup (line 22) | function getSettingGroup(key: Tkey) { FILE: webui/src/store/downloader.ts function getAll (line 47) | async function getAll() { method onSuccess (line 60) | onSuccess() { function toggleHash (line 80) | function toggleHash(hash: string) { function toggleGroup (line 89) | function toggleGroup(group: TorrentGroup) { function clearSelection (line 106) | function clearSelection() { FILE: webui/src/store/log.ts function getLog (line 10) | function getLog() { method onSuccess (line 20) | onSuccess() { function copy (line 40) | function copy() { FILE: webui/src/store/player.ts type MediaPlayerType (line 3) | type MediaPlayerType = 'jump' | 'iframe'; function normalizeUrl (line 5) | function normalizeUrl(url: string): string { function setUrl (line 24) | function setUrl(value: string) { FILE: webui/src/store/rss.ts function getAll (line 7) | async function getAll() { method onSuccess (line 22) | onSuccess() { FILE: webui/src/store/search.ts type SearchFilters (line 4) | interface SearchFilters { type FilterOptions (line 11) | interface FilterOptions { type GroupedBangumi (line 19) | interface GroupedBangumi { function parseResolution (line 28) | function parseResolution(bangumi: BangumiRule): string { function parseSubtitleType (line 39) | function parseSubtitleType(bangumi: BangumiRule): string { function parseSeason (line 57) | function parseSeason(bangumi: BangumiRule): string { function getProviders (line 218) | async function getProviders() { function openModal (line 223) | function openModal() { function closeModal (line 227) | function closeModal() { function toggleModal (line 233) | function toggleModal() { function clearSearch (line 241) | function clearSearch() { function toggleVariantFilter (line 249) | function toggleVariantFilter(category: keyof SearchFilters, value: strin... function clearVariantFilters (line 259) | function clearVariantFilters() { function selectGroup (line 263) | function selectGroup(group: GroupedBangumi) { function clearSelectedGroup (line 268) | function clearSelectedGroup() { function selectResult (line 273) | function selectResult(bangumi: BangumiRule) { function clearSelectedResult (line 277) | function clearSelectedResult() { function onSearch (line 281) | function onSearch() { FILE: webui/src/store/setup.ts function nextStep (line 55) | function nextStep() { function prevStep (line 61) | function prevStep() { function goToStep (line 67) | function goToStep(index: number) { function buildCompleteRequest (line 74) | function buildCompleteRequest(): SetupCompleteRequest { function $reset (line 93) | function $reset() { FILE: webui/src/test/mocks/api.ts function createMockResponse (line 207) | function createMockResponse(data: T, status = 200) { function createMockError (line 220) | function createMockError(status: number, message: string) { FILE: webui/src/utils/poster.ts function resolvePosterUrl (line 1) | function resolvePosterUrl(link: string | null | undefined): string { FILE: webui/types/api.ts type AuthError (line 1) | type AuthError = 'Not authenticated'; type LoginError (line 3) | type LoginError = 'Password error' | 'User not found'; type ApiErrorMessage (line 5) | type ApiErrorMessage = AuthError | LoginError; type StatusCode (line 13) | type StatusCode = 401 | 404 | 406 | 500; type ApiError (line 15) | interface ApiError { type ApiSuccess (line 21) | interface ApiSuccess { FILE: webui/types/auth.ts type LoginSuccess (line 1) | interface LoginSuccess { type Update (line 7) | interface Update extends LoginSuccess { type User (line 11) | interface User { FILE: webui/types/bangumi.ts type BangumiRule (line 4) | interface BangumiRule { type BangumiAPI (line 32) | interface BangumiAPI extends Omit { type SearchResult (line 37) | interface SearchResult { type BangumiUpdate (line 42) | type BangumiUpdate = Omit; type OffsetSuggestion (line 73) | interface OffsetSuggestion { type TMDBSummary (line 79) | interface TMDBSummary { type OffsetSuggestionDetail (line 87) | interface OffsetSuggestionDetail { type DetectOffsetRequest (line 95) | interface DetectOffsetRequest { type DetectOffsetResponse (line 102) | interface DetectOffsetResponse { FILE: webui/types/components.ts type SelectItem (line 1) | interface SelectItem { type AbSettingProps (line 8) | interface AbSettingProps { type SettingItem (line 16) | type SettingItem = AbSettingProps & { FILE: webui/types/config.ts type DownloaderType (line 4) | type DownloaderType = ['qbittorrent']; type RssParserLang (line 6) | type RssParserLang = ['zh', 'en', 'jp']; type RenameMethod (line 8) | type RenameMethod = ['normal', 'pn', 'advance', 'none']; type ProxyType (line 10) | type ProxyType = ['http', 'https', 'socks5']; type NotificationType (line 12) | type NotificationType = [ type OpenAIModel (line 23) | type OpenAIModel = ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo']; type OpenAIType (line 25) | type OpenAIType = ['openai', 'azure']; type Program (line 27) | interface Program { type Downloader (line 33) | interface Downloader { type RssParser (line 41) | interface RssParser { type BangumiManage (line 46) | interface BangumiManage { type Log (line 53) | interface Log { type Proxy (line 56) | interface Proxy { type NotificationProviderConfig (line 65) | interface NotificationProviderConfig { type Notification (line 81) | interface Notification { type ExperimentalOpenAI (line 89) | interface ExperimentalOpenAI { type Security (line 104) | interface Security { type Config (line 111) | interface Config { FILE: webui/types/downloader.ts type QbTorrentState (line 1) | type QbTorrentState = type QbTorrentInfo (line 22) | interface QbTorrentInfo { type TorrentGroup (line 38) | interface TorrentGroup { FILE: webui/types/dts/components.d.ts type GlobalComponents (line 11) | interface GlobalComponents { FILE: webui/types/dts/html.d.ts type HTMLAttributes (line 8) | interface HTMLAttributes extends AttributifyAttributes {} FILE: webui/types/dts/router-type.d.ts type RouteNamedMap (line 41) | interface RouteNamedMap { type RouterTyped (line 58) | type RouterTyped = _RouterTyped type RouteLocationNormalized (line 64) | type RouteLocationNormalized = RouteNamedMap[Name]... type RouteParamsRaw (line 98) | type RouteParamsRaw = RouteNamedMap[Na... type TypesConfig (line 141) | interface TypesConfig { FILE: webui/types/passkey.ts type PasskeyItem (line 6) | interface PasskeyItem { type RegistrationOptions (line 16) | interface RegistrationOptions { type AuthenticationOptions (line 38) | interface AuthenticationOptions { type PasskeyCreateRequest (line 51) | interface PasskeyCreateRequest { type PasskeyDeleteRequest (line 57) | interface PasskeyDeleteRequest { type PasskeyAuthStartRequest (line 62) | interface PasskeyAuthStartRequest { type PasskeyAuthFinishRequest (line 67) | interface PasskeyAuthFinishRequest { FILE: webui/types/rss.ts type RSS (line 1) | interface RSS { FILE: webui/types/setup.ts type SetupStatus (line 1) | interface SetupStatus { type TestDownloaderRequest (line 6) | interface TestDownloaderRequest { type TestRSSRequest (line 14) | interface TestRSSRequest { type TestNotificationRequest (line 18) | interface TestNotificationRequest { type TestResult (line 24) | interface TestResult { type SetupCompleteRequest (line 32) | interface SetupCompleteRequest { type WizardStep (line 49) | type WizardStep = FILE: webui/types/torrent.ts type Torrent (line 1) | interface Torrent { FILE: webui/types/utils.ts type TupleToUnion (line 1) | type TupleToUnion = T[number];