import imdb import jsonfile ABOUT_FILE = "about.json" MEDIA_KINDS = { "show": {"key": "shows", "label": "TV show", "omdb_type": "series"}, "movie": {"key": "movies", "label": "movie", "omdb_type": "movie"} } def load_items(kind: str) -> tuple[dict, list[dict]]: data = jsonfile.load(ABOUT_FILE) return data, data.setdefault(MEDIA_KINDS[kind]["key"], []) def find_index(items: list[dict], text: str) -> int | None: identifier = imdb.imdb_id(text) for index, item in enumerate(items): if identifier and imdb.imdb_id(item.get("url", "")) == identifier: return index if item.get("name", "").lower() == text.lower(): return index return None def valid_rating(value: str) -> int: rating = int(value) if not 0 <= rating <= 10: raise ValueError("Rating must be between 0 and 10") return rating def list_items(arguments) -> None: _, items = load_items(arguments.kind) if not items: print(f"No {MEDIA_KINDS[arguments.kind]['label']}s yet.") return for item in sorted(items, key=lambda entry: -entry.get("rating", 0)): print(f"{item.get('rating', '?'):>3}/10 {item.get('name')} {item.get('url')}") def resolve_name(arguments, identifier: str) -> str | None: if arguments.name: return arguments.name details = imdb.lookup(identifier) if not details: print("Could not look up the title. Pass --name, or set OMDB_API_KEY in .env.") return None expected_type = MEDIA_KINDS[arguments.kind]["omdb_type"] if details.get("Type") != expected_type: print(f"Warning: OMDb lists {details.get('Title')} as a {details.get('Type')}, not a {expected_type}.") return details.get("Title") def add_item(arguments) -> None: identifier = imdb.imdb_id(arguments.title) if not identifier: print(f"{arguments.title} is not an IMDb link or ID.") return data, items = load_items(arguments.kind) if find_index(items, identifier) is not None: print(f"{identifier} is already listed. Use rate to change its rating.") return name = resolve_name(arguments, identifier) if not name: return items.append({"name": name, "rating": arguments.rating, "url": imdb.title_url(identifier)}) jsonfile.save(ABOUT_FILE, data) print(f"Added {name} ({arguments.rating}/10)") def remove_item(arguments) -> None: data, items = load_items(arguments.kind) index = find_index(items, arguments.title) if index is None: print(f"{arguments.title} is not listed.") return removed = items.pop(index) jsonfile.save(ABOUT_FILE, data) print(f"Removed {removed.get('name')}") def rate_item(arguments) -> None: data, items = load_items(arguments.kind) index = find_index(items, arguments.title) if index is None: print(f"{arguments.title} is not listed.") return items[index]["rating"] = arguments.rating jsonfile.save(ABOUT_FILE, data) print(f"Rated {items[index].get('name')} {arguments.rating}/10") def register(subparsers) -> None: for kind, settings in MEDIA_KINDS.items(): parser = subparsers.add_parser(kind, help=f"Manage the {settings['label']}s on the about page") parser.set_defaults(kind=kind) actions = parser.add_subparsers(dest="action", required=True) actions.add_parser("list", help=f"List {settings['label']}s").set_defaults(handler=list_items) add_parser = actions.add_parser("add", help=f"Add a {settings['label']} by IMDb link or ID") add_parser.add_argument("title", help="IMDb link or ID, like tt0816692") add_parser.add_argument("--rating", type=valid_rating, required=True, help="Your rating from 0 to 10") add_parser.add_argument("--name", help="Display name, looked up on OMDb when left out") add_parser.set_defaults(handler=add_item) remove_parser = actions.add_parser("remove", help=f"Remove a {settings['label']}") remove_parser.add_argument("title", help="IMDb link, ID or name") remove_parser.set_defaults(handler=remove_item) rate_parser = actions.add_parser("rate", help=f"Change a {settings['label']}'s rating") rate_parser.add_argument("title", help="IMDb link, ID or name") rate_parser.add_argument("rating", type=valid_rating, help="New rating from 0 to 10") rate_parser.set_defaults(handler=rate_item)