A static web application and indexing system to browse, search, and listen to 3000+ archived shows from PunkRockRadio.ca on Mixcloud.
To load the application: https://fredbourni.github.io/prr-archives/
This project provides a comprehensive archive browser for PunkRockRadio.ca shows, consisting of two main components:
- Indexer (Python): Fetches show data from the Mixcloud API with intelligent rate limiting, categorizes shows using regex patterns, normalizes tags, and generates a static JSON index with incremental update support.
- Web Application (React + TypeScript + Vite): A modern, responsive web interface featuring fuzzy search, advanced filtering, infinite scroll, permalink support, and an embedded Mixcloud player with a premium dark theme.
- Incremental Updates: Only fetches new shows, skipping already-indexed content for efficiency
- Smart Rate Limiting: 1-second delay between API requests to respect Mixcloud's rate limits
- Robust Error Handling: Automatic retry logic with exponential backoff for failed requests
- Regex-Based Categorization: Automatically categorizes shows into 30+ categories (e.g., "Le Pink Punk Show", "Esprit de Core")
- Tag Normalization: Merges and normalizes tags using configurable mappings (e.g., "Hardcore punk" → "Hardcore")
- Extra Tags Support: Add custom tags to specific shows via configuration
- Local Update Mode: Update categories and tags without re-fetching from API
- Verbose Logging: Detailed logging for debugging and monitoring
- Fuzzy Search: Powered by Fuse.js, searches across show titles, tags, and categories with typo tolerance
- Advanced Filtering:
- Text search with debouncing
- Year-based filtering
- Category filtering with icon indicators
- Combined filter support
- Advanced Statistics:
- Comprehensive dashboard (accessible via
?stats=1) - Global archive totals (episodes, minutes, hours, days, weeks, months)
- Individual show-by-show statistics with visual indicators
- Yearly Trends Chart: Interactive dual-axis line chart plotting episodes and total minutes per year
- Comprehensive dashboard (accessible via
- Multiple Sort Options: Sort by newest, oldest, or random
- Infinite Scroll: Smooth pagination loading 12 shows at a time
- Permalink Support: Share specific searches and shows via URL with full state synchronization
- Embedded Mixcloud Player: Picture widget integration with loading states
- Responsive Design: Mobile-optimized Material-UI components
- Dark Theme: Premium Blue Gray color scheme with glassmorphism effects
- Share Functionality: One-click permalink copying with toast notifications
- SEO Optimized: Open Graph meta tags for social media previews (limitation of a static app)
./
├── indexer/ # Python indexing scripts
│ ├── main.py # Entry point with CLI
│ ├── fetcher.py # Mixcloud API client
│ ├── processor.py # Data processing and categorization
│ ├── config.json # Show patterns and tag mappings
│ ├── constants.py # Configuration constants
│ └── indexer_types.py # Type definitions
├── web/ # React web application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── common/ # Shared components
│ │ │ ├── filters/ # Filter components
│ │ │ ├── player/ # Mixcloud player
│ │ │ └── shows/ # Show list and cards
│ │ ├── hooks/ # Custom React hooks
│ │ ├── types/ # TypeScript type definitions
│ │ ├── utils/ # Utility functions
│ │ └── constants/ # App constants
│ ├── public/
│ │ └── shows.json # Generated show index
│ └── package.json # Dependencies and scripts
├── .github/workflows/ # GitHub Actions
│ ├── deploy.yml # Deploy to GitHub Pages
│ └── update_index.yml # Daily indexing job
- Python: 3.10 or later (for the indexer)
- Node.js: 20.x or later (for the web app)
- npm: Comes with Node.js
The indexer fetches and processes show data from Mixcloud.
- Navigate to the project root.
- Install the required Python dependencies:
pip install requests
- Run the indexer:
# Fetch all shows (incremental update if shows.json exists) python indexer/main.py --output web/public/shows.json # Fetch a limited number of shows (useful for testing) python indexer/main.py --limit 10 # Update categories/tags locally without fetching from API python indexer/main.py --local-update --output web/public/shows.json # Enable verbose logging for debugging python indexer/main.py --verbose # Use custom configuration file python indexer/main.py --config custom_config.json
The web application provides the user interface for browsing shows.
-
Navigate to the
webdirectory:cd web -
Install Node.js dependencies:
npm install
-
Generate the index file with the info fetched from mixcloud and saved in the web public folder:
python indexer/main.py --output web/public/shows.json
-
Start the development server:
npm run dev
Open your browser to the URL shown (usually
http://localhost:5173). -
Additional Commands:
# Build for production npm run build # Preview production build npm run preview # Run linter npm run lint # Format code npm run format # Check formatting npm run format:check # Type check npm run type-check
Shows are categorized using regex patterns defined in indexer/config.json. The indexer matches show titles against these patterns to assign categories (shows).
Example configuration:
{
"shows": [
{
"name": "Le Pink Punk Show",
"regex": "P.nk[ ]+P.nk"
},
{
"name": "Esprit de Core",
"regex": "esprit.*core",
"extra_tags": [
"Metalcore",
"Post-Hardcore",
"Hardcore punk"
]
},
{
"name": "Shows Spéciaux",
"regex": "(sp.cial|entrevue|interview|jasette|impro|dimanche)"
}
],
"tag_mappings": {
"Hardcore punk": "Hardcore",
"Ska": "Ska punk"
}
}Configuration Options:
shows: Array of show patternsname: Display name for the categoryregex: Regular expression to match show titles (case-insensitive)extra_tags(optional): Additional tags to add to matching shows
tag_mappings: Tag normalization rules (maps source tag → normalized tag)
- Edit
indexer/config.json - Add a new entry to the
showsarray with a unique regex pattern - Run the indexer with
--local-updateto update existing shows:python indexer/main.py --local-update --output web/public/shows.json
- Language: Python 3.10+
- HTTP Client:
requestswith retry logic and connection pooling - Data Processing: Native Python with regex for pattern matching
- Error Handling: Custom exception hierarchy with detailed logging
- Framework: React 19.2.0
- Language: TypeScript 5.9.3
- Build Tool: Vite 7.2.4
- UI Library: Material-UI (MUI) 7.3.5
- Search: Fuse.js 7.1.0
- Charts: Recharts 2.15.0
- Date Handling: date-fns 4.1.0
- Styling: Emotion (CSS-in-JS)
- Code Quality: ESLint, Prettier, TypeScript strict mode
The indexer interacts with the Mixcloud API to fetch show data.
API Details:
- Base URL:
https://api.mixcloud.com - Endpoint:
/{username}/cloudcasts/ - User:
punkrockradio - Pagination: 100 shows per page
- Rate Limiting: 1-second delay between requests
- Timeout: 30 seconds per request
- Retry Strategy: 3 retries with exponential backoff
The project uses GitHub Actions for automated deployment and maintenance.
File: .github/workflows/update_index.yml
- Schedule: Daily at midnight UTC (
0 0 * * *) - Trigger: Can also be manually triggered via workflow_dispatch
- Process:
- Checks out repository
- Sets up Python 3.10
- Installs dependencies
- Runs indexer (incremental update)
- Commits and pushes
shows.jsonif changes detected
File: .github/workflows/deploy.yml
- Trigger: Push to
mainbranch or manual dispatch - Process:
- Checks out repository
- Sets up Node.js 20
- Installs web app dependencies
- Copies
shows.jsontoweb/public/ - Builds production bundle
- Deploys to GitHub Pages
The web application includes comprehensive tooling:
- ESLint: Enforces code style and catches common errors
- Prettier: Automatic code formatting
- TypeScript: Static type checking
- React Hooks Linting: Ensures proper hook usage
Run quality checks:
cd web
npm run lint # Check for linting errors
npm run format # Auto-format code
npm run format:check # Verify formatting
npm run type-check # TypeScript validation