Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion api/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,15 @@ <h1>YouTube RSS Scanner</h1>

if (data.official_feeds) {
html += '<div class="atom-output"><div class="feed-label">Official YouTube Feeds:</div>';
['selected','all','videos','shorts','live'].forEach(function(k){ if (data.official_feeds[k]) { html += '<div class="feed-link">' + k + ': ' + data.official_feeds[k] + '</div>'; }});
Object.entries(data.official_feeds).forEach(function(entry){
var k = entry[0];
var v = entry[1];
html += '<div class="feed-row">';
html += '<div class="feed-label">Official (' + k + '):</div>';
html += '<div class="feed-link">' + v + '</div>';
html += '<button class="copy-btn" data-encoded="' + encodeURIComponent(v) + '">Copy ' + k + ' RSS</button>';
html += '</div>';
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
html += '</div>';
}

Expand Down Expand Up @@ -259,6 +267,16 @@ <h1>YouTube RSS Scanner</h1>
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);
}
}
});
</script>
</body>
</html>
23 changes: 19 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,26 @@ <h1>YouTube RSS Scanner</h1>
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);
Expand Down
26 changes: 21 additions & 5 deletions rss_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +300 to +311

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 build_official_feeds returns 6 identical URLs despite claiming to produce distinct feed-type URLs

build_official_feeds (rss_scanner.py:300-311) calls build_youtube_feed_url with different feed_type values ("all", "videos", "shorts", "live", etc.), but build_youtube_feed_url (rss_scanner.py:294-296) completely ignores its feed_type parameter and always returns the same URL (https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}). This means every entry in the returned dict — "youtube", "all", "videos", "shorts", "live", "selected" — is the exact same URL. The UI (all three index.html variants) iterates Object.entries(data.official_feeds) and renders 6 separate rows with different labels ("Official (youtube)", "Official (all)", etc.), each showing the identical URL. The docstring even says "without duplicating identical links" but does the opposite.

Prompt for agents
The function build_official_feeds (rss_scanner.py:300-311) calls build_youtube_feed_url with different feed_type values, but build_youtube_feed_url (rss_scanner.py:294-296) ignores feed_type entirely and always returns the same URL. This results in 6 identical URLs displayed in the UI with different labels, which is misleading.

Two possible approaches:
1. Make build_youtube_feed_url actually produce different URLs per feed_type (if YouTube supports such URLs). Otherwise,
2. Deduplicate the dict in build_official_feeds so it only contains unique URLs. For example, only return a single entry since they're all the same, or collapse entries that share the same value. The docstring already says 'without duplicating identical links' — the implementation should match that contract.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


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)
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,15 @@ <h1>YouTube RSS Scanner</h1>

if (data.official_feeds) {
html += '<div class="atom-output"><div class="feed-label">Official YouTube Feeds:</div>';
['selected','all','videos','shorts','live'].forEach(function(k){ if (data.official_feeds[k]) { html += '<div class="feed-link">' + k + ': ' + data.official_feeds[k] + '</div>'; }});
Object.entries(data.official_feeds).forEach(function(entry){
var k = entry[0];
var v = entry[1];
html += '<div class="feed-row">';
html += '<div class="feed-label">Official (' + k + '):</div>';
html += '<div class="feed-link">' + v + '</div>';
html += '<button class="copy-btn" data-encoded="' + encodeURIComponent(v) + '">Copy ' + k + ' RSS</button>';
html += '</div>';
});
html += '</div>';
}

Expand Down