mirror of
https://github.com/aserper/masto-rss.git
synced 2025-12-17 13:25:25 +00:00
Modernize codebase: Use pathlib, logging, dataclasses, and update dependencies
This commit is contained in:
185
main.py
185
main.py
@@ -1,80 +1,135 @@
|
||||
"""Mastodon RSS Bot - Entry point"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from bot import MastodonRSSBot
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
handlers=[logging.StreamHandler(sys.stdout)]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
"""Configuration loaded from environment variables."""
|
||||
instance_url: str
|
||||
client_id: str
|
||||
client_secret: str
|
||||
access_token: str
|
||||
feed_urls: List[str] = field(default_factory=list)
|
||||
toot_visibility: str = "public"
|
||||
check_interval: int = 300
|
||||
state_file: Path = field(default_factory=lambda: Path("/state/processed_entries.txt"))
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "Config":
|
||||
"""Load configuration from environment variables."""
|
||||
instance_url = os.environ.get("MASTODON_INSTANCE_URL")
|
||||
client_id = os.environ.get("MASTODON_CLIENT_ID")
|
||||
client_secret = os.environ.get("MASTODON_CLIENT_SECRET")
|
||||
access_token = os.environ.get("MASTODON_ACCESS_TOKEN")
|
||||
|
||||
if not all([instance_url, client_id, client_secret, access_token]):
|
||||
missing = [
|
||||
k for k, v in {
|
||||
"MASTODON_INSTANCE_URL": instance_url,
|
||||
"MASTODON_CLIENT_ID": client_id,
|
||||
"MASTODON_CLIENT_SECRET": client_secret,
|
||||
"MASTODON_ACCESS_TOKEN": access_token
|
||||
}.items() if not v
|
||||
]
|
||||
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
|
||||
|
||||
# Parse feeds
|
||||
feed_urls = []
|
||||
|
||||
# 1. Legacy single feed URL
|
||||
if os.environ.get("RSS_FEED_URL"):
|
||||
feed_urls.append(os.environ["RSS_FEED_URL"])
|
||||
|
||||
# 2. Comma-separated list of feeds
|
||||
if os.environ.get("RSS_FEEDS"):
|
||||
feeds = [
|
||||
url.strip() for url in os.environ["RSS_FEEDS"].split(",") if url.strip()
|
||||
]
|
||||
feed_urls.extend(feeds)
|
||||
|
||||
# 3. File containing list of feeds
|
||||
feeds_file = os.environ.get("FEEDS_FILE")
|
||||
if feeds_file:
|
||||
path = Path(feeds_file)
|
||||
if path.exists():
|
||||
try:
|
||||
content = path.read_text().splitlines()
|
||||
file_feeds = [
|
||||
line.strip()
|
||||
for line in content
|
||||
if line.strip() and not line.startswith("#")
|
||||
]
|
||||
feed_urls.extend(file_feeds)
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading feeds file {feeds_file}: {e}")
|
||||
else:
|
||||
logger.warning(f"Feeds file configured but not found: {feeds_file}")
|
||||
|
||||
# Deduplicate while preserving order
|
||||
unique_feed_urls = list(dict.fromkeys(feed_urls))
|
||||
|
||||
if not unique_feed_urls:
|
||||
raise ValueError("No RSS feeds configured. Please set RSS_FEED_URL, RSS_FEEDS, or FEEDS_FILE.")
|
||||
|
||||
return cls(
|
||||
instance_url=instance_url, # type: ignore # checked above
|
||||
client_id=client_id, # type: ignore
|
||||
client_secret=client_secret,# type: ignore
|
||||
access_token=access_token, # type: ignore
|
||||
feed_urls=unique_feed_urls,
|
||||
toot_visibility=os.environ.get("TOOT_VISIBILITY", "public"),
|
||||
check_interval=int(os.environ.get("CHECK_INTERVAL", "300")),
|
||||
state_file=Path(os.environ.get("PROCESSED_ENTRIES_FILE", "/state/processed_entries.txt"))
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Initialize and run the bot with environment configuration"""
|
||||
print("Starting Mastodon RSS Bot...")
|
||||
logger.info("Starting Mastodon RSS Bot...")
|
||||
|
||||
# Load configuration from environment variables
|
||||
feed_urls = []
|
||||
try:
|
||||
config = Config.from_env()
|
||||
except ValueError as e:
|
||||
logger.critical(str(e))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
logger.critical(f"Failed to load configuration: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# 1. Legacy single feed URL
|
||||
if os.environ.get("RSS_FEED_URL"):
|
||||
feed_urls.append(os.environ["RSS_FEED_URL"])
|
||||
|
||||
# 2. Comma-separated list of feeds
|
||||
if os.environ.get("RSS_FEEDS"):
|
||||
feeds = [
|
||||
url.strip() for url in os.environ["RSS_FEEDS"].split(",") if url.strip()
|
||||
]
|
||||
feed_urls.extend(feeds)
|
||||
|
||||
# 3. File containing list of feeds
|
||||
feeds_file = os.environ.get("FEEDS_FILE")
|
||||
if feeds_file and os.path.exists(feeds_file):
|
||||
try:
|
||||
with open(feeds_file, "r") as f:
|
||||
file_feeds = [
|
||||
line.strip()
|
||||
for line in f
|
||||
if line.strip() and not line.startswith("#")
|
||||
]
|
||||
feed_urls.extend(file_feeds)
|
||||
except Exception as e:
|
||||
print(f"Error reading feeds file {feeds_file}: {e}")
|
||||
|
||||
# Deduplicate while preserving order
|
||||
unique_feed_urls = []
|
||||
seen = set()
|
||||
for url in feed_urls:
|
||||
if url not in seen:
|
||||
unique_feed_urls.append(url)
|
||||
seen.add(url)
|
||||
|
||||
if not unique_feed_urls:
|
||||
print(
|
||||
"Error: No RSS feeds configured. Please set RSS_FEED_URL, RSS_FEEDS, or FEEDS_FILE."
|
||||
)
|
||||
return
|
||||
logger.info("Bot configured successfully:")
|
||||
logger.info(f" Instance: {config.instance_url}")
|
||||
logger.info(f" Monitoring {len(config.feed_urls)} feed(s):")
|
||||
for url in config.feed_urls:
|
||||
logger.info(f" - {url}")
|
||||
logger.info(f" Visibility: {config.toot_visibility}")
|
||||
logger.info(f" Check interval: {config.check_interval} seconds")
|
||||
logger.info(f" State file: {config.state_file}")
|
||||
|
||||
bot = MastodonRSSBot(
|
||||
client_id=os.environ["MASTODON_CLIENT_ID"],
|
||||
client_secret=os.environ["MASTODON_CLIENT_SECRET"],
|
||||
access_token=os.environ["MASTODON_ACCESS_TOKEN"],
|
||||
instance_url=os.environ["MASTODON_INSTANCE_URL"],
|
||||
feed_urls=unique_feed_urls,
|
||||
toot_visibility=os.environ.get("TOOT_VISIBILITY", "public"),
|
||||
check_interval=int(os.environ.get("CHECK_INTERVAL", "300")),
|
||||
state_file=os.environ.get(
|
||||
"PROCESSED_ENTRIES_FILE", "/state/processed_entries.txt"
|
||||
),
|
||||
client_id=config.client_id,
|
||||
client_secret=config.client_secret,
|
||||
access_token=config.access_token,
|
||||
instance_url=config.instance_url,
|
||||
feed_urls=config.feed_urls,
|
||||
toot_visibility=config.toot_visibility,
|
||||
check_interval=config.check_interval,
|
||||
state_file=config.state_file,
|
||||
)
|
||||
|
||||
print("Bot configured successfully:")
|
||||
print(f" Instance: {os.environ['MASTODON_INSTANCE_URL']}")
|
||||
print(f" Monitoring {len(unique_feed_urls)} feed(s):")
|
||||
for url in unique_feed_urls:
|
||||
print(f" - {url}")
|
||||
print(f" Visibility: {os.environ.get('TOOT_VISIBILITY', 'public')}")
|
||||
print(f" Check interval: {os.environ.get('CHECK_INTERVAL', '300')} seconds")
|
||||
print(
|
||||
f" State file: {os.environ.get('PROCESSED_ENTRIES_FILE', '/state/processed_entries.txt')}"
|
||||
)
|
||||
print()
|
||||
|
||||
# Start the bot
|
||||
bot.run()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user