diff --git a/api/app.py b/api/app.py index bf5b252..db73eea 100644 --- a/api/app.py +++ b/api/app.py @@ -63,7 +63,7 @@ def api_feed(): include_api_endpoints = False base_url = request.host_url.rstrip('/') - youtube_rss, channel_id, channel_name, atom_feed, video_count, _, api_endpoints = rss_scanner.get_rss_feed( + youtube_rss, channel_id, channel_name, atom_feed, video_count, _, api_endpoints, official_feeds = rss_scanner.get_rss_feed( url, include_api_endpoints=include_api_endpoints, base_url=base_url, @@ -94,6 +94,7 @@ def api_feed(): 'atom_feed': atom_feed, 'video_count': video_count, 'api_endpoints': api_endpoints, + 'official_feeds': official_feeds, 'discord': discord_result }), mimetype='application/json') except Exception as e: @@ -167,7 +168,7 @@ def get_feed(feed_type, channel_url=None): full_url = channel_url try: - _, channel_id, channel_name, atom_feed, video_count, _, _ = rss_scanner.get_rss_feed(full_url, feed_type=feed_type) + _, channel_id, channel_name, atom_feed, video_count, _, _, _ = rss_scanner.get_rss_feed(full_url, feed_type=feed_type) if atom_feed: # Fix channel name in feed diff --git a/api/index.html b/api/index.html index f7e5d8d..2d53dc1 100644 --- a/api/index.html +++ b/api/index.html @@ -224,7 +224,15 @@

YouTube RSS Scanner

if (data.official_feeds) { html += '
Official YouTube Feeds:
'; - ['selected','all','videos','shorts','live'].forEach(function(k){ if (data.official_feeds[k]) { html += ''; }}); + Object.entries(data.official_feeds).forEach(function(entry){ + var k = entry[0]; + var v = entry[1]; + html += '
'; + html += '
Official (' + k + '):
'; + html += ''; + html += ''; + html += '
'; + }); html += '
'; } @@ -259,6 +267,16 @@

YouTube RSS Scanner

document.getElementById('channelUrl').addEventListener('keypress', function(e) { if (e.key === 'Enter') getFeed(); }); + + // Event delegation for dynamically added copy buttons + document.addEventListener('click', function(e) { + if (e.target && e.target.classList.contains('copy-btn')) { + const encoded = e.target.getAttribute('data-encoded'); + if (encoded) { + copyEncodedText(encoded); + } + } + }); diff --git a/index.html b/index.html index 044a89c..a873bad 100644 --- a/index.html +++ b/index.html @@ -267,11 +267,26 @@

YouTube RSS Scanner

label.className = 'feed-label'; label.textContent = 'Official YouTube Feeds:'; officialBox.appendChild(label); - ['selected','all','videos','shorts','live'].forEach((key) => { - if (!data.official_feeds[key]) return; + Object.entries(data.official_feeds).forEach(([key, value]) => { const row = document.createElement('div'); - row.className = 'feed-link'; - row.textContent = `${key}: ${data.official_feeds[key]}`; + row.className = 'feed-row'; + + const feedLabel = document.createElement('div'); + feedLabel.className = 'feed-label'; + feedLabel.textContent = `Official (${key}):`; + row.appendChild(feedLabel); + + const feedLink = document.createElement('div'); + feedLink.className = 'feed-link'; + feedLink.textContent = value; + row.appendChild(feedLink); + + const copyBtn = document.createElement('button'); + copyBtn.className = 'copy-btn'; + copyBtn.textContent = `Copy ${key} RSS`; + copyBtn.addEventListener('click', () => copyText(value)); + row.appendChild(copyBtn); + officialBox.appendChild(row); }); resultDiv.appendChild(officialBox); diff --git a/rss_scanner.py b/rss_scanner.py index e2d19e2..96ad749 100644 --- a/rss_scanner.py +++ b/rss_scanner.py @@ -91,7 +91,7 @@ def generate_atom_feed(channel_id: str, channel_name: str, videos: list[dict]) - return feed PATTERNS = [ - (r"/channel/([a-zA-Z0-9_-]{22})", "channel"), + (r"/channel/(UC[a-zA-Z0-9_-]{22})", "channel"), (r"/c/([a-zA-Z0-9_-]+)", "custom"), (r"/user/([a-zA-Z0-9_-]+)", "user"), (r"/@([a-zA-Z0-9_-]+)", "handle"), @@ -295,15 +295,31 @@ def build_youtube_feed_url(channel_id: str, feed_type: str = None) -> str: """Build a YouTube RSS feed URL for the given channel ID.""" return f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}" + + +def build_official_feeds(channel_id: str, feed_type: str = "all") -> dict[str, str]: + """Return official YouTube feed URLs without duplicating identical links.""" + youtube_feed = build_youtube_feed_url(channel_id, feed_type="all") + feeds = { + "youtube": youtube_feed, + "all": build_youtube_feed_url(channel_id, feed_type="all"), + "videos": build_youtube_feed_url(channel_id, feed_type="videos"), + "shorts": build_youtube_feed_url(channel_id, feed_type="shorts"), + "live": build_youtube_feed_url(channel_id, feed_type="live"), + "selected": build_youtube_feed_url(channel_id, feed_type=feed_type), + } + return feeds + def get_rss_feed(url: str, include_api_endpoints: bool = False, base_url: str = "http://localhost:8080", feed_type: str = "all") -> tuple: """Get RSS feed data for a YouTube channel. - Returns: (youtube_rss, channel_id, channel_name, atom_feed, video_count, invidious_rss, api_endpoints) + Returns: (youtube_rss, channel_id, channel_name, atom_feed, video_count, invidious_rss, api_endpoints, official_feeds) """ channel_id, channel_name = extract_channel_id(url) # YouTube RSS URL (supports hidden filtered variants for shorts/live) - youtube_rss = build_youtube_feed_url(channel_id, feed_type=feed_type) + official_feeds = build_official_feeds(channel_id, feed_type=feed_type) + youtube_rss = official_feeds["selected"] # Try to get videos from the selected YouTube channel page videos = get_channel_videos(channel_id, feed_type=feed_type) @@ -337,7 +353,7 @@ def get_rss_feed(url: str, include_api_endpoints: bool = False, base_url: str = "live_feed": f"{base_url.rstrip('/')}/feed/live/{encoded_url}", } - return youtube_rss, channel_id, channel_name, atom_feed, video_count, invidious_rss, api_endpoints + return youtube_rss, channel_id, channel_name, atom_feed, video_count, invidious_rss, api_endpoints, official_feeds def main(): @@ -383,7 +399,7 @@ def main(): url = "https://" + url try: - youtube_rss, channel_id, channel_name, atom_feed, video_count, invidious_rss, api_endpoints = get_rss_feed( + youtube_rss, channel_id, channel_name, atom_feed, video_count, invidious_rss, api_endpoints, _ = get_rss_feed( url, include_api_endpoints=args.include_api_endpoints, base_url=args.base_url diff --git a/templates/index.html b/templates/index.html index a034472..18c7b15 100644 --- a/templates/index.html +++ b/templates/index.html @@ -218,7 +218,15 @@

YouTube RSS Scanner

if (data.official_feeds) { html += '
Official YouTube Feeds:
'; - ['selected','all','videos','shorts','live'].forEach(function(k){ if (data.official_feeds[k]) { html += ''; }}); + Object.entries(data.official_feeds).forEach(function(entry){ + var k = entry[0]; + var v = entry[1]; + html += '
'; + html += '
Official (' + k + '):
'; + html += ''; + html += ''; + html += '
'; + }); html += '
'; }