diff --git a/README.md b/README.md index 1d53322..9607f56 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ That feed is ordered by newest uploads, so fetching the first 3 entries gives th python3 latest3.py "https://www.youtube.com/@ludwig/videos" python3 latest3.py "@ludwig" --json python3 latest3.py "https://www.youtube.com/@ludwig/videos" --limit 3 --json +python3 latest3.py "https://www.youtube.com/@ludwig/videos" --limit 3 --no-shorts ``` ## Output fields @@ -25,6 +26,12 @@ python3 latest3.py "https://www.youtube.com/@ludwig/videos" --limit 3 --json - `url` - watch URL - `published` - ISO-8601 timestamp from the feed +## Flags + +- `--limit N` how many videos to return (1-20) +- `--json` output as JSON +- `--no-shorts` exclude URLs matching `/shorts/` + ## Notes - Works with `@handle` URLs and plain `@handle` input. diff --git a/latest3.py b/latest3.py index 50a9b0b..5bd7927 100755 --- a/latest3.py +++ b/latest3.py @@ -53,7 +53,7 @@ def extract_channel_id(html: str) -> str: raise RuntimeError("Could not resolve channel ID from URL") -def fetch_latest_from_feed(channel_id: str, limit: int = 3): +def fetch_latest_from_feed(channel_id: str, limit: int = 3, no_shorts: bool = False): feed_url = f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}" xml_text = http_get(feed_url) root = ET.fromstring(xml_text) @@ -65,13 +65,16 @@ def fetch_latest_from_feed(channel_id: str, limit: int = 3): } out = [] - for entry in root.findall("atom:entry", ns)[:limit]: + for entry in root.findall("atom:entry", ns): vid = entry.findtext("yt:videoId", default="", namespaces=ns) title = entry.findtext("atom:title", default="", namespaces=ns) published = entry.findtext("atom:published", default="", namespaces=ns) link_el = entry.find("atom:link", ns) url = link_el.attrib.get("href") if link_el is not None else (f"https://www.youtube.com/watch?v={vid}" if vid else "") + if no_shorts and "/shorts/" in (url or ""): + continue + out.append( { "id": vid, @@ -81,16 +84,19 @@ def fetch_latest_from_feed(channel_id: str, limit: int = 3): } ) + if len(out) >= limit: + break + if not out: raise RuntimeError("No videos found in channel feed") return out -def get_latest_videos(channel_url_or_handle: str, limit: int = 3): +def get_latest_videos(channel_url_or_handle: str, limit: int = 3, no_shorts: bool = False): normalized = normalize_channel_url(channel_url_or_handle) html = http_get(normalized) channel_id = extract_channel_id(html) - videos = fetch_latest_from_feed(channel_id, limit=limit) + videos = fetch_latest_from_feed(channel_id, limit=limit, no_shorts=no_shorts) return { "input": channel_url_or_handle, "resolved_url": normalized, @@ -104,6 +110,7 @@ def main(): ap.add_argument("channel", help="YouTube channel URL (including @handle) or @handle") ap.add_argument("--limit", type=int, default=3, help="How many latest videos to return (default: 3)") ap.add_argument("--json", action="store_true", help="Print full JSON output") + ap.add_argument("--no-shorts", action="store_true", help="Exclude Shorts URLs") args = ap.parse_args() if args.limit < 1 or args.limit > 20: @@ -111,7 +118,7 @@ def main(): sys.exit(2) try: - data = get_latest_videos(args.channel, limit=args.limit) + data = get_latest_videos(args.channel, limit=args.limit, no_shorts=args.no_shorts) except (urllib.error.URLError, urllib.error.HTTPError) as e: print(f"Network error: {e}", file=sys.stderr) sys.exit(1)