Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
| [Anthropic Research](https://www.anthropic.com/research) | [feed_anthropic_research.xml](https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_anthropic_research.xml) |
| [Anthropic Frontier Red Team](https://red.anthropic.com/) | [feed_anthropic_red.xml](https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_anthropic_red.xml) |
| [Claude Code Changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md) | [feed_anthropic_changelog_claude_code.xml](https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_anthropic_changelog_claude_code.xml) |
| [OpenAI Developer Blog](https://developers.openai.com/blog) | [feed_openai_developer.xml](https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_openai_developer.xml) |
| [OpenAI Research](https://openai.com/news/research/) | [feed_openai_research.xml](https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_openai_research.xml) |
| [Ollama Blog](https://ollama.com/blog) | [feed_ollama.xml](https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_ollama.xml) |
| [Paul Graham's Articles](https://www.paulgraham.com/articles.html) | [feed_paulgraham.xml](https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_paulgraham.xml) |
Expand Down
165 changes: 165 additions & 0 deletions feed_generators/openai_developer_blog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import logging
from datetime import datetime
from pathlib import Path

import pytz
import requests
from bs4 import BeautifulSoup
from feedgen.feed import FeedGenerator

from utils import get_feeds_dir, setup_feed_links, sort_posts_for_feed

FEED_NAME = "openai_developer"
BLOG_URL = "https://developers.openai.com/blog"

logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)


def fetch_blog_content(url=BLOG_URL):
"""Fetch blog content from the given URL."""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
return response.text


def parse_date(date_text):
"""Parse date text like 'Feb 26' or 'Dec 30', inferring the year.

Posts are listed newest-first. Dates without a year are assumed to be the
most recent occurrence that is not in the future.
"""
today = datetime.now(pytz.UTC)
for fmt in ("%b %d", "%B %d"):
try:
# Use a dummy year to avoid Python 3.14 deprecation warning
parsed = datetime.strptime(date_text.strip() + " 2000", fmt + " %Y")
# Try current year first; if that's in the future, use previous year
candidate = parsed.replace(year=today.year, tzinfo=pytz.UTC)
if candidate > today:
candidate = candidate.replace(year=today.year - 1)
return candidate
except ValueError:
continue

# Try formats that already include a year
for fmt in ("%b %d, %Y", "%B %d, %Y", "%Y-%m-%d"):
try:
return datetime.strptime(date_text.strip(), fmt).replace(tzinfo=pytz.UTC)
except ValueError:
continue

logger.warning(f"Could not parse date: {date_text}")
return None


def parse_blog_html(html_content):
"""Parse the blog HTML content and extract post information."""
soup = BeautifulSoup(html_content, "html.parser")
posts = []
seen_links = set()

# Blog cards use class "resource-item" and link to /blog/<slug>
cards = soup.select('a.resource-item[href*="/blog/"]')
logger.info(f"Found {len(cards)} blog cards")

for card in cards:
href = card.get("href", "")
if not href or "/topic/" in href:
continue

link = "https://developers.openai.com" + href if href.startswith("/") else href
if link in seen_links:
continue
seen_links.add(link)

# Title: inside div.line-clamp-2
title_elem = card.select_one("div.line-clamp-2")
if not title_elem:
continue
title = title_elem.get_text(strip=True)

# Date: first div with class text-secondary (contains "Feb 26" etc.)
date_elem = card.select_one("div.text-secondary")
date = parse_date(date_elem.get_text(strip=True)) if date_elem else None

# Description: p with class line-clamp-3
desc_elem = card.select_one("p.line-clamp-3")
description = desc_elem.get_text(strip=True) if desc_elem else title

# Category: div with pt-2 text-sm text-secondary
cat_elem = card.select_one("div.pt-2.text-sm.text-secondary")
category = cat_elem.get_text(strip=True) if cat_elem else "Developer"

posts.append(
{
"title": title,
"link": link,
"date": date,
"description": description,
"category": category,
}
)

logger.info(f"Successfully parsed {len(posts)} blog posts")
return posts


def generate_rss_feed(posts):
"""Generate RSS feed from blog posts."""
fg = FeedGenerator()
fg.title("OpenAI Developer Blog")
fg.description("Latest developer updates and guides from OpenAI")
fg.language("en")
fg.author({"name": "OpenAI"})
fg.subtitle("Updates for developers building with OpenAI")

setup_feed_links(fg, blog_url=BLOG_URL, feed_name=FEED_NAME)

sorted_posts = sort_posts_for_feed(posts, date_field="date")

for post in sorted_posts:
fe = fg.add_entry()
fe.title(post["title"])
fe.description(post["description"])
fe.link(href=post["link"])
fe.id(post["link"])
fe.category(term=post["category"])
if post["date"]:
fe.published(post["date"])

logger.info("Successfully generated RSS feed")
return fg


def save_rss_feed(feed_generator):
"""Save the RSS feed to a file in the feeds directory."""
feeds_dir = get_feeds_dir()
output_file = feeds_dir / f"feed_{FEED_NAME}.xml"
feed_generator.rss_file(str(output_file), pretty=True)
logger.info(f"Successfully saved RSS feed to {output_file}")
return output_file


def main():
"""Main function to generate RSS feed from OpenAI Developer Blog."""
html_content = fetch_blog_content()
posts = parse_blog_html(html_content)

if not posts:
logger.warning("No posts found. Check the HTML structure.")
return False

feed = generate_rss_feed(posts)
save_rss_feed(feed)
logger.info(f"Successfully generated RSS feed with {len(posts)} posts")
return True


if __name__ == "__main__":
main()
124 changes: 124 additions & 0 deletions feeds/feed_openai_developer.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>OpenAI Developer Blog</title>
<link>https://developers.openai.com/blog</link>
<description>Updates for developers building with OpenAI</description>
<atom:link href="https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_openai_developer.xml" rel="self"/>
<docs>http://www.rssboard.org/rss-specification</docs>
<generator>python-feedgen</generator>
<language>en</language>
<lastBuildDate>Wed, 04 Mar 2026 12:28:03 +0000</lastBuildDate>
<item>
<title>Building frontend UIs with Codex and Figma</title>
<link>https://developers.openai.com/blog/building-frontend-uis-with-codex-and-figma</link>
<description>Use Codex and Figma to bring real, running interfaces into Figma, refine them, and bring changes back to Codex.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/building-frontend-uis-with-codex-and-figma</guid>
<category>Codex</category>
<pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate>
</item>
<item>
<title>Run long horizon tasks with Codex</title>
<link>https://developers.openai.com/blog/run-long-horizon-tasks-with-codex</link>
<guid isPermaLink="false">https://developers.openai.com/blog/run-long-horizon-tasks-with-codex</guid>
<category>Codex</category>
<pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate>
</item>
<item>
<title>Shell + Skills + Compaction: Tips for long-running agents that do real work</title>
<link>https://developers.openai.com/blog/skills-shell-tips</link>
<description>Practical patterns for building with skills, hosted shell, and server-side compaction in the Responses API.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/skills-shell-tips</guid>
<category>API</category>
<pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate>
</item>
<item>
<title>15 lessons learned building ChatGPT Apps</title>
<link>https://developers.openai.com/blog/15-lessons-building-chatgpt-apps</link>
<description>And how we incorporated them into a Codex Skill to help you build ChatGPT Apps 10x faster.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/15-lessons-building-chatgpt-apps</guid>
<category>Apps SDK</category>
<pubDate>Wed, 04 Feb 2026 00:00:00 +0000</pubDate>
</item>
<item>
<title>Testing Agent Skills Systematically with Evals</title>
<link>https://developers.openai.com/blog/eval-skills</link>
<description>A practical guide to turning agent skills into something you can test, score, and improve over time.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/eval-skills</guid>
<category>Codex</category>
<pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate>
</item>
<item>
<title>Supercharging Codex with JetBrains MCP at Skyscanner</title>
<link>https://developers.openai.com/blog/skyscanner-codex-jetbrains-mcp</link>
<description>How Skyscanner integrated Codex CLI with JetBrains IDEs to speed up debugging, testing, and development workflows.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/skyscanner-codex-jetbrains-mcp</guid>
<category>Codex</category>
<pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate>
</item>
<item>
<title>OpenAI for Developers in 2025</title>
<link>https://developers.openai.com/blog/openai-for-developers-2025</link>
<description>A year-end roundup of the biggest model, API, and platform shifts for building production-grade agents.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/openai-for-developers-2025</guid>
<category>General</category>
<pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate>
</item>
<item>
<title>Updates for developers building with voice</title>
<link>https://developers.openai.com/blog/updates-audio-models</link>
<description>New audio model snapshots and broader access to Custom Voices for production voice apps.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/updates-audio-models</guid>
<category>Audio</category>
<pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate>
</item>
<item>
<title>What makes a great ChatGPT app</title>
<link>https://developers.openai.com/blog/what-makes-a-great-chatgpt-app</link>
<description>How to build capabilities that make conversations better.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/what-makes-a-great-chatgpt-app</guid>
<category>Apps SDK</category>
<pubDate>Mon, 24 Nov 2025 00:00:00 +0000</pubDate>
</item>
<item>
<title>Using Codex for education at Dagster Labs</title>
<link>https://developers.openai.com/blog/codex-for-documentation-dagster</link>
<description>Learn how Dagster uses Codex in their open-source projects to accelerate documentation, translate content across mediums, and even measure how complete their docs are.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/codex-for-documentation-dagster</guid>
<category>Codex</category>
<pubDate>Mon, 27 Oct 2025 00:00:00 +0000</pubDate>
</item>
<item>
<title>How Codex ran OpenAI DevDay 2025</title>
<link>https://developers.openai.com/blog/codex-at-devday</link>
<description>Learn how Codex helped us build experiences, demos, products, and more</description>
<guid isPermaLink="false">https://developers.openai.com/blog/codex-at-devday</guid>
<category>Codex</category>
<pubDate>Fri, 10 Oct 2025 00:00:00 +0000</pubDate>
</item>
<item>
<title>Why we built the Responses API</title>
<link>https://developers.openai.com/blog/responses-api</link>
<description>How the Responses API unlocks persistent reasoning, hosted tools, and multimodal workflows for GPT-5.</description>
<guid isPermaLink="false">https://developers.openai.com/blog/responses-api</guid>
<category>API</category>
<pubDate>Mon, 22 Sep 2025 00:00:00 +0000</pubDate>
</item>
<item>
<title>Developer notes on the Realtime API</title>
<link>https://developers.openai.com/blog/realtime-api</link>
<description>Details worth noticing in recent realtime speech-to-speech updates</description>
<guid isPermaLink="false">https://developers.openai.com/blog/realtime-api</guid>
<category>Audio</category>
<pubDate>Fri, 12 Sep 2025 00:00:00 +0000</pubDate>
</item>
<item>
<title>Hello, world!</title>
<link>https://developers.openai.com/blog/intro</link>
<description>Introducing our developer blog</description>
<guid isPermaLink="false">https://developers.openai.com/blog/intro</guid>
<category>General</category>
<pubDate>Thu, 11 Sep 2025 00:00:00 +0000</pubDate>
</item>
</channel>
</rss>
7 changes: 7 additions & 0 deletions makefiles/feeds.mk
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ feeds_openai_research: ## Generate RSS feed for OpenAI Research
$(Q)python feed_generators/openai_research_blog.py
$(call print_success,OpenAI Research feed generated)

.PHONY: feeds_openai_developer
feeds_openai_developer: ## Generate RSS feed for OpenAI Developer Blog
$(call check_venv)
$(call print_info,Generating OpenAI Developer Blog feed)
$(Q)python feed_generators/openai_developer_blog.py
$(call print_success,OpenAI Developer Blog feed generated)

.PHONY: feeds_ollama
feeds_ollama: ## Generate RSS feed for Ollama Blog
$(call check_venv)
Expand Down