diff --git a/README.md b/README.md index c8eddf2998..21f39399af 100644 --- a/README.md +++ b/README.md @@ -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) | diff --git a/feed_generators/openai_developer_blog.py b/feed_generators/openai_developer_blog.py new file mode 100644 index 0000000000..b5059ec030 --- /dev/null +++ b/feed_generators/openai_developer_blog.py @@ -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/ + 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() diff --git a/feeds/feed_openai_developer.xml b/feeds/feed_openai_developer.xml new file mode 100644 index 0000000000..a905f3f8ed --- /dev/null +++ b/feeds/feed_openai_developer.xml @@ -0,0 +1,124 @@ + + + + OpenAI Developer Blog + https://developers.openai.com/blog + Updates for developers building with OpenAI + + http://www.rssboard.org/rss-specification + python-feedgen + en + Wed, 04 Mar 2026 12:28:03 +0000 + + Building frontend UIs with Codex and Figma + https://developers.openai.com/blog/building-frontend-uis-with-codex-and-figma + Use Codex and Figma to bring real, running interfaces into Figma, refine them, and bring changes back to Codex. + https://developers.openai.com/blog/building-frontend-uis-with-codex-and-figma + Codex + Thu, 26 Feb 2026 00:00:00 +0000 + + + Run long horizon tasks with Codex + https://developers.openai.com/blog/run-long-horizon-tasks-with-codex + https://developers.openai.com/blog/run-long-horizon-tasks-with-codex + Codex + Mon, 23 Feb 2026 00:00:00 +0000 + + + Shell + Skills + Compaction: Tips for long-running agents that do real work + https://developers.openai.com/blog/skills-shell-tips + Practical patterns for building with skills, hosted shell, and server-side compaction in the Responses API. + https://developers.openai.com/blog/skills-shell-tips + API + Wed, 11 Feb 2026 00:00:00 +0000 + + + 15 lessons learned building ChatGPT Apps + https://developers.openai.com/blog/15-lessons-building-chatgpt-apps + And how we incorporated them into a Codex Skill to help you build ChatGPT Apps 10x faster. + https://developers.openai.com/blog/15-lessons-building-chatgpt-apps + Apps SDK + Wed, 04 Feb 2026 00:00:00 +0000 + + + Testing Agent Skills Systematically with Evals + https://developers.openai.com/blog/eval-skills + A practical guide to turning agent skills into something you can test, score, and improve over time. + https://developers.openai.com/blog/eval-skills + Codex + Thu, 22 Jan 2026 00:00:00 +0000 + + + Supercharging Codex with JetBrains MCP at Skyscanner + https://developers.openai.com/blog/skyscanner-codex-jetbrains-mcp + How Skyscanner integrated Codex CLI with JetBrains IDEs to speed up debugging, testing, and development workflows. + https://developers.openai.com/blog/skyscanner-codex-jetbrains-mcp + Codex + Sun, 11 Jan 2026 00:00:00 +0000 + + + OpenAI for Developers in 2025 + https://developers.openai.com/blog/openai-for-developers-2025 + A year-end roundup of the biggest model, API, and platform shifts for building production-grade agents. + https://developers.openai.com/blog/openai-for-developers-2025 + General + Tue, 30 Dec 2025 00:00:00 +0000 + + + Updates for developers building with voice + https://developers.openai.com/blog/updates-audio-models + New audio model snapshots and broader access to Custom Voices for production voice apps. + https://developers.openai.com/blog/updates-audio-models + Audio + Mon, 22 Dec 2025 00:00:00 +0000 + + + What makes a great ChatGPT app + https://developers.openai.com/blog/what-makes-a-great-chatgpt-app + How to build capabilities that make conversations better. + https://developers.openai.com/blog/what-makes-a-great-chatgpt-app + Apps SDK + Mon, 24 Nov 2025 00:00:00 +0000 + + + Using Codex for education at Dagster Labs + https://developers.openai.com/blog/codex-for-documentation-dagster + 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. + https://developers.openai.com/blog/codex-for-documentation-dagster + Codex + Mon, 27 Oct 2025 00:00:00 +0000 + + + How Codex ran OpenAI DevDay 2025 + https://developers.openai.com/blog/codex-at-devday + Learn how Codex helped us build experiences, demos, products, and more + https://developers.openai.com/blog/codex-at-devday + Codex + Fri, 10 Oct 2025 00:00:00 +0000 + + + Why we built the Responses API + https://developers.openai.com/blog/responses-api + How the Responses API unlocks persistent reasoning, hosted tools, and multimodal workflows for GPT-5. + https://developers.openai.com/blog/responses-api + API + Mon, 22 Sep 2025 00:00:00 +0000 + + + Developer notes on the Realtime API + https://developers.openai.com/blog/realtime-api + Details worth noticing in recent realtime speech-to-speech updates + https://developers.openai.com/blog/realtime-api + Audio + Fri, 12 Sep 2025 00:00:00 +0000 + + + Hello, world! + https://developers.openai.com/blog/intro + Introducing our developer blog + https://developers.openai.com/blog/intro + General + Thu, 11 Sep 2025 00:00:00 +0000 + + + diff --git a/makefiles/feeds.mk b/makefiles/feeds.mk index 086d388b33..c042908b19 100644 --- a/makefiles/feeds.mk +++ b/makefiles/feeds.mk @@ -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)