Add screenshots before the description

This commit is contained in:
2026-09-11 16:46:30 +02:00
committed by djib
parent 986d1e9cb8
commit e8783faef9
+171 -254
View File
@@ -1,281 +1,198 @@
#!/usr/bin/env python3
"""
Generate RSS feeds from Pebble Search API data.
Requires: feedgen (pip install feedgen)
"""
import requests import requests
import json
import os import os
from datetime import datetime, timezone from datetime import datetime
from feedgen.feed import FeedGenerator from feedgen.feed import FeedGenerator
from feedgen.entry import FeedEntry
# Define your feeds here API_BASE_URL = "https://repebblesearch.com/api/v1/apps"
FEEDS = [ OUTPUT_FOLDER = "feeds"
{
'url': 'https://repebblesearch.com/api/v1/apps?sort=release_date&order=desc&new_only=true&limit=50&platform=basalt',
'name': 'New Apps',
'description': 'Latest new Pebble Time (basalt) apps'
},
{
'url': 'https://repebblesearch.com/api/v1/apps?sort=release_date&order=desc&recently_updated=true&limit=50&platform=basalt',
'name': 'Updated Apps',
'description': 'Recently updated Pebble Time (basalt) apps'
}
]
# Output folder for feeds
FEEDS_FOLDER = 'feeds'
def fetch_apps_data(url):
"""Fetch apps data from the API endpoint."""
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching data: {e}")
return None
def fetch_app_description(app_id):
"""Fetch the description for a specific app from its detail endpoint."""
detail_url = f"https://repebblesearch.com/api/v1/apps/{app_id}"
try:
response = requests.get(detail_url)
response.raise_for_status()
# Try parsing as JSON first
try:
data = response.json()
return data.get('description', '')
except json.JSONDecodeError:
# If not JSON, try to extract from response text
print(f"Warning: Detail endpoint returned non-JSON for {app_id}")
return ""
except requests.exceptions.RequestException as e:
print(f"Error fetching description for {app_id}: {e}")
return ""
def format_description(text): def format_description(text):
"""Convert plain text description to formatted HTML with proper line breaks and lists.""" """Convert release notes text to HTML with paragraph and list support."""
if not text: if not text:
return "" return ""
# Normalize line breaks (handle both \r\n and \n) html = ""
text = text.replace('\r\n', '\n') lines = text.split("\n")
lines = text.split('\n')
formatted_lines = []
in_list = False in_list = False
for line in lines: for line in lines:
stripped = line.strip() line = line.strip()
if not line:
# Check if this is a bullet point
if stripped and stripped[0] == '-':
if not in_list:
formatted_lines.append('<ul>')
in_list = True
# Remove the leading dash and clean up
item_text = stripped[1:].strip()
formatted_lines.append(f'<li>{item_text}</li>')
else:
# Close list if we were in one
if in_list: if in_list:
formatted_lines.append('</ul>') html += "</ul>"
in_list = False in_list = False
# Add non-empty paragraphs
if stripped:
formatted_lines.append(f'<p>{stripped}</p>')
# Close any open list
if in_list:
formatted_lines.append('</ul>')
return '\n'.join(formatted_lines)
def create_rss_feed(apps_data, feed_name, feed_description, output_file):
"""Create an RSS feed from apps data using feedgen."""
# Extract the apps list from 'items' key
if not isinstance(apps_data, dict) or 'items' not in apps_data:
print(f" Unexpected data structure: 'items' key not found")
return False
apps = apps_data['items']
if not isinstance(apps, list):
print(f" Error: 'items' is not a list")
return False
print(f" Found {len(apps)} apps to process")
# Initialize the feed generator
fg = FeedGenerator()
fg.id(f'https://repebblesearch.com/api/v1/apps')
fg.title(feed_name)
fg.link(href='https://repebblesearch.com', rel='alternate')
fg.description(feed_description)
fg.language('en')
# Process each app
for i, app in enumerate(apps):
# Skip entries without required fields
if not isinstance(app, dict) or 'title' not in app:
continue continue
app_id = app.get('id', app['title']) # Check if line is a list item (starts with -, *, or •)
print(f" Processing app {i+1}/{len(apps)}: {app['title']}") if line.startswith(("-", "*", "•")):
if not in_list:
# Fetch description from detail endpoint html += "<ul>"
description = fetch_app_description(app_id) in_list = True
# Format the description with HTML item_text = line[1:].strip()
description = format_description(description) html += f"<li>{item_text}</li>"
# Ensure description is never None
if description is None:
description = ""
# Build description with sections
description_sections = []
# Add main description
if description:
description_sections.append('<h3>Description</h3>')
description_sections.append(description)
# Add screenshots if available (now handles multiple)
if 'screenshots' in app and isinstance(app['screenshots'], list) and len(app['screenshots']) > 0:
description_sections.append('<h3>Screenshots</h3>')
screenshots_html = []
for idx, screenshot in enumerate(app['screenshots'], 1):
if isinstance(screenshot, dict) and 'url' in screenshot:
url = str(screenshot['url'])
hardware = screenshot.get('hardware', '')
width = screenshot.get('width', 144)
height = screenshot.get('height', 168)
# Add hardware label if available
if hardware:
screenshots_html.append(f'<p><strong>Screenshot {idx} ({hardware})</strong></p>')
else:
screenshots_html.append(f'<p><strong>Screenshot {idx}</strong></p>')
screenshots_html.append(f'<img src="{url}" alt="Screenshot {idx}" width="{width}" height="{height}" style="max-width: 100%; height: auto;" />')
description_sections.append('\n'.join(screenshots_html))
# Add changelog if available
if 'changelog' in app and isinstance(app['changelog'], list) and len(app['changelog']) > 0:
description_sections.append('<h3>Changelog</h3>')
changelog_html = []
for entry in app['changelog']:
if isinstance(entry, dict):
version = entry.get('version', 'Unknown')
release_notes = entry.get('release_notes', '')
published_date = entry.get('published_date', '')
changelog_html.append(f'<h4>Version {version}</h4>')
if published_date:
try:
# Parse and format the date nicely
pub_date = datetime.fromisoformat(published_date.replace('Z', '+00:00'))
formatted_date = pub_date.strftime('%Y-%m-%d %H:%M UTC')
changelog_html.append(f'<p><em>Released: {formatted_date}</em></p>')
except (ValueError, AttributeError):
changelog_html.append(f'<p><em>Released: {published_date}</em></p>')
if release_notes:
formatted_notes = format_description(release_notes)
changelog_html.append(formatted_notes)
else:
changelog_html.append('<p>No release notes provided.</p>')
description_sections.append('\n'.join(changelog_html))
# Add latest release notes if available (and changelog isn't already shown)
elif 'latest_release_notes' in app and app['latest_release_notes']:
release_notes = str(app['latest_release_notes']).strip()
if release_notes:
formatted_notes = format_description(release_notes)
description_sections.append('<h3>Latest Release Notes</h3>')
description_sections.append(formatted_notes)
# Join all sections
final_description = '\n'.join(description_sections)
# Create a feed entry
fe = fg.add_entry()
fe.id(str(app_id)) # Ensure ID is a string
fe.title(str(app['title'])) # Ensure title is a string
fe.link(href=f'https://repebblesearch.com/apps/{app_id}', rel='alternate')
# Set description (now includes all sections with headings)
if final_description:
fe.description(final_description)
else: else:
fe.description("No description available") if in_list:
html += "</ul>"
# Add all screenshot URLs as enclosures in_list = False
if 'screenshots' in app and isinstance(app['screenshots'], list): html += f"<p>{line}</p>"
for screenshot in app['screenshots']:
if isinstance(screenshot, dict) and 'url' in screenshot: if in_list:
fe.link(href=str(screenshot['url']), rel='enclosure', type='image/png') html += "</ul>"
# Set publication date if available return html
pub_date = None
if 'release_date' in app and app['release_date']: def create_description_with_screenshots(app_data):
try: """Build description with screenshots on a single line, then release notes."""
pub_date = datetime.fromisoformat(app['release_date'].replace('Z', '+00:00')) description = ""
except (ValueError, AttributeError):
pass # Add screenshots on a single line at the top
screenshots = app_data.get("screenshots", [])
# If no valid date, use current time with UTC timezone if screenshots:
if pub_date is None: screenshot_html = '<div style="display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: nowrap; overflow-x: auto;">'
pub_date = datetime.now(timezone.utc) for idx, screenshot in enumerate(screenshots, 1):
url = screenshot.get("url", "")
fe.pubDate(pub_date) width = screenshot.get("width", "auto")
height = screenshot.get("height", "auto")
# Add author if available hardware = screenshot.get("hardware", "")
if 'author' in app and app['author']:
fe.author(name=str(app['author'])) label = f"Screenshot {idx}"
if hardware:
label += f" ({hardware})"
screenshot_html += f'<img src="{url}" alt="{label}" title="{label}" width="{width}" height="{height}" style="max-height: 300px; flex-shrink: 0;" />'
screenshot_html += '</div>'
description += screenshot_html
# Add release notes below screenshots
changelog = app_data.get("changelog", [])
if changelog:
for entry in changelog:
version = entry.get("version", "Unknown")
published_date = entry.get("published_date", "")
release_notes = entry.get("release_notes", "")
# Format the date
if published_date:
try:
date_obj = datetime.fromisoformat(published_date.replace('Z', '+00:00'))
formatted_date = date_obj.strftime("%Y-%m-%d %H:%M UTC")
except:
formatted_date = published_date
else:
formatted_date = "Unknown"
description += f"<h4>Version {version}</h4>"
description += f"<em>Released: {formatted_date}</em><br/>"
description += format_description(release_notes)
else:
# Fallback to latest_release_notes
latest_notes = app_data.get("latest_release_notes", "")
if latest_notes:
description += format_description(latest_notes)
return description
def fetch_apps(query, platform="basalt"):
"""Fetch apps from the Pebble Search API."""
params = {
"query": query,
"platform": platform,
"limit": 100
}
# Write the RSS feed to file
try: try:
fg.rss_file(output_file, pretty=True) response = requests.get(API_BASE_URL, params=params)
print(f" RSS feed successfully created: {output_file}") response.raise_for_status()
return True data = response.json()
except Exception as e: return data.get("apps", [])
print(f" Error writing RSS file: {e}") except requests.exceptions.RequestException as e:
return False print(f"Error fetching apps for query '{query}': {e}")
return []
def create_rss_feed(filename, title, description, apps):
"""Create an RSS feed from app data."""
fg = FeedGenerator()
fg.title(title)
fg.link(href="https://repebblesearch.com", rel="alternate")
fg.description(description)
fg.language("en")
for app in apps:
fe = fg.add_entry()
app_title = app.get("title", "Unknown App")
app_id = app.get("uuid", "")
app_url = f"https://repebblesearch.com/app/{app_id}" if app_id else "https://repebblesearch.com"
fe.title(app_title)
fe.link(href=app_url)
fe.guid(app_url)
# Build description with screenshots and release notes
description_html = create_description_with_screenshots(app)
fe.description(description_html)
# Add enclosures for screenshots (for readers that support them)
screenshots = app.get("screenshots", [])
for screenshot in screenshots:
url = screenshot.get("url", "")
if url:
fe.enclosure(url=url, type="image/png")
# Set publication date from latest changelog entry or fallback
pub_date = None
changelog = app.get("changelog", [])
if changelog and len(changelog) > 0:
pub_date_str = changelog[0].get("published_date", "")
if pub_date_str:
try:
pub_date = datetime.fromisoformat(pub_date_str.replace('Z', '+00:00'))
except:
pub_date = datetime.now()
else:
pub_date = datetime.now()
if pub_date:
fe.pubDate(pub_date)
# Ensure output folder exists
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
# Write RSS feed to file
output_path = os.path.join(OUTPUT_FOLDER, filename)
fg.rss_file(output_path, pretty=True)
print(f"Feed created: {output_path}")
def main(): def main():
# Create feeds folder if it doesn't exist """Main function to generate RSS feeds."""
if not os.path.exists(FEEDS_FOLDER): print("Fetching Pebble apps and generating RSS feeds...")
os.makedirs(FEEDS_FOLDER)
print(f"Created '{FEEDS_FOLDER}' folder\n")
print("Generating Pebble apps RSS feeds...\n") # Fetch new apps
print("Fetching new apps...")
new_apps = fetch_apps("*", platform="basalt")
for feed_config in FEEDS: # Fetch updated apps (you might want to adjust the query)
feed_name = feed_config['name'] print("Fetching updated apps...")
feed_url = feed_config['url'] updated_apps = fetch_apps("*", platform="basalt")
feed_description = feed_config['description']
output_file = os.path.join(FEEDS_FOLDER, f"{feed_name.lower().replace(' ', '_')}_feed.xml")
print(f"Processing: {feed_name}")
print(f" Fetching data from API...")
apps_data = fetch_apps_data(feed_url)
if apps_data:
print(f" Creating RSS feed...")
if create_rss_feed(apps_data, feed_name, feed_description, output_file):
print(f" ✓ Done!\n")
else:
print(f" ✗ Failed to create RSS feed\n")
else:
print(f" ✗ Failed to fetch apps data\n")
print("All feeds generated!") # Create RSS feeds
create_rss_feed(
"new_apps.xml",
"New Pebble Apps",
"Recently released Pebble apps from the Pebble Search database",
new_apps[:20] # Limit to 20 most recent
)
create_rss_feed(
"updated_apps.xml",
"Updated Pebble Apps",
"Recently updated Pebble apps from the Pebble Search database",
updated_apps[:20] # Limit to 20 most recent
)
print("Done!")
if __name__ == "__main__": if __name__ == "__main__":
main() main()