- PHP 42.8%
- CSS 21.3%
- HTML 21.3%
- JavaScript 14.6%
| assets | ||
| data | ||
| .gitignore | ||
| .htaccess | ||
| 404.html | ||
| about.html | ||
| admin.php | ||
| aggregated-feed.php | ||
| api.php | ||
| apple-touch-icon.png | ||
| badge.svg | ||
| blog.php | ||
| cron-scan.php | ||
| favicon.ico | ||
| feed.atom.php | ||
| feed.json.php | ||
| feed.xml.php | ||
| index.html | ||
| latest.php | ||
| LICENSE | ||
| migrate-feeds.php | ||
| og-image.png | ||
| opml.php | ||
| privacy.html | ||
| random.php | ||
| README.md | ||
| robots.txt | ||
| set-password.php | ||
| sitemap.php | ||
| sitemap.xml | ||
| submit.html | ||
Pure Blog Discover
A community directory of blogs and websites built on the Pure Blog platform. Users can submit their blog for listing - the system automatically verifies whether the site uses Pure Blog and approves it instantly, without any manual review.
Built with PHP, vanilla JavaScript, and HTML/CSS. No frameworks, no databases - just flat JSON files.
Live: discover.thinkroot.xyz
Features
- Automatic verification - the server fetches the submitted URL and uses an 8-signal scoring system anchored in Pure Blog's source code to detect the platform
- Instant approval - if Pure Blog is detected, the blog is added to the directory immediately
- Blog descriptions - automatically fetched from
<meta name="description">at submit time - Language detection - automatically detects the blog's language from the
<html lang>attribute - Feed detection - automatically detects the blog's RSS/Atom feed and displays an icon on each card
- Favicon fetching - displays each blog's favicon via Google Favicon Service
- "New" badge - blogs added in the last 7 days are marked with a badge in the directory
- Blog profile pages - each blog has a dedicated page with description, meta-data and recent articles
- Latest Articles - aggregated feed of recent posts from all listed blogs, updated hourly with language filter, keyword search, pagination and random article button
- Aggregated RSS feed - subscribe to articles from all listed blogs at once via
/aggregated-feed.php - Dark mode - respects system preference (
prefers-color-scheme), toggleable via header button, persisted inlocalStorage; also available in the admin panel - Random order - blog list is shuffled on every page load
- Search - client-side search by blog name, URL and description
- Language filter - filter the directory by blog language
- Pagination - 21 blogs per page
- RSS, Atom and JSON Feed - subscribe to the directory to follow new additions
- OPML export - import all blog feeds into any RSS reader at once
- Share prompt - after approval, share your listing on Mastodon or Bluesky
- Admin panel - password-protected panel to add, edit, remove and scan blogs
- Periodic scan - automatic cron job checks all blogs and flags those that no longer use Pure Blog
- Rate limiting - max 3 submissions per IP per hour (bypassed in admin)
- Privacy policy - documents data collected, third-party services and user rights
- SEO - meta tags, Open Graph, Twitter Card, JSON-LD structured data, dynamic sitemap,
deferon scripts - Accessible - semantic HTML, skip links, ARIA labels, keyboard navigation, focus-visible styles, reduced-motion support
- Responsive - tested from 320px to 1440px; 3-column grid on desktop, 2 on tablet, 1 on mobile
- Security hardened - CSRF protection, SSRF prevention, honeypot, security headers via
.htaccess - Open source - MIT License
File structure
pureblog-discover/
├── index.html # Directory listing page
├── submit.html # Blog submission form
├── about.html # About page
├── privacy.html # Privacy policy
├── latest.php # Latest articles aggregator (RSS feeds from all blogs)
├── blog.php # Individual blog profile page
├── api.php # REST API - all endpoints
├── admin.php # Password-protected admin panel
├── aggregated-feed.php # RSS feed of latest articles from all blogs
├── random.php # Redirects to a random article
├── sitemap.php # Dynamic XML sitemap
├── sitemap.xml # Static sitemap fallback
├── feed.xml.php # RSS 2.0 feed for the directory
├── feed.atom.php # Atom feed for the directory
├── feed.json.php # JSON Feed for the directory
├── opml.php # OPML export of all blog feeds
├── cron-scan.php # Periodic cron script to scan all blogs
├── set-password.php # CLI utility to generate admin password hash
├── robots.txt # Search engine directives
├── .htaccess # Apache config - security headers, caching, gzip
├── favicon.ico # Multi-size favicon (16/32/48px) - must stay in root
├── apple-touch-icon.png # iOS home screen icon - must stay in root
├── og-image.png # Open Graph image - must stay in root
├── badge.svg # "Listed on Pure Blog Discover" badge for blogs
├── LICENSE
├── README.md
├── assets/
│ ├── css/
│ │ ├── main.css # Public site styles
│ │ ├── latest.css # Latest Articles page styles
│ │ ├── blog.css # Blog profile page styles
│ │ └── admin.css # Admin panel styles
│ ├── js/
│ │ ├── main.js # Public site JavaScript
│ │ └── admin.js # Admin panel JavaScript
│ └── images/
│ ├── logo.svg # Vector logo
│ ├── favicon.svg # Vector favicon
│ ├── favicon-32.png
│ ├── favicon-48.png
│ └── og-image.png # Open Graph image (1200x630)
└── data/
├── .htaccess # Denies all web access to this directory
├── blogs.json # Blog database
├── articles.json # Cached article feed (auto-managed, excluded from git)
└── rate.json # Rate limit tracking (auto-managed)
Requirements
- PHP 7.4 or higher (PHP 8.1+ recommended)
- Apache with
mod_rewrite,mod_headers,mod_deflate,mod_expires - cURL enabled in PHP
- Write permissions on
data/blogs.jsonanddata/rate.json
Deploy on shared hosting
-
Upload all files to
public_html/(or a subdirectory) -
Set file permissions:
data/blogs.json 644 data/rate.json 644 data/articles.json 644 (created automatically on first visit to /latest.php) data/ 755 -
Set the admin password - run this command via SSH or a PHP terminal:
php set-password.phpCopy the generated hash and paste it into
admin.phpas the value ofADMIN_PASSWORD_HASH. -
Set a secret key for the scan endpoint - open
api.phpand change:define('SCAN_SECRET', 'CHANGE_THIS_TO_A_RANDOM_SECRET_KEY'); -
Replace all occurrences of
YOUR_DOMAINwith your actual domain in:index.html,submit.html,about.html- canonical URL, OG tagssitemap.php- BASE_URL constantrobots.txt- Sitemap directive
-
Set up the periodic cron job in cPanel or via SSH:
Minute: 0 | Hour: 8 | Day: * | Month: * | Weekday: 1 Command: /usr/local/bin/php /home/YOUR_USER/public_html/YOUR_DIR/cron-scan.php -
Verify the setup is working by visiting:
https://YOUR_DOMAIN/api.php?action=pingAll values should return
true.
API endpoints
All endpoints are served by api.php.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
?action=list |
- | Returns all approved blogs as JSON |
GET |
?action=token |
- | Returns a CSRF token for the submit form |
GET |
?action=ping |
- | Diagnostic endpoint - checks PHP, cURL, file permissions |
GET |
?action=scan |
Admin session or secret key | Scans all blogs and flags those no longer using Pure Blog |
POST |
?action=submit |
CSRF token | Public blog submission with rate limiting |
POST |
?action=admin-submit |
Admin session | Add a blog with Pure Blog verification, no rate limit |
POST |
?action=admin-force-add |
Admin session | Add a blog without Pure Blog verification |
POST |
?action=admin-edit |
Admin session | Edit a blog's name, URL, language or description |
POST |
?action=admin-approve |
Admin session | Clear the needs_review flag on a flagged blog |
POST |
?action=admin-delete |
Admin session | Remove a blog by ID |
Pure Blog detection
The system uses a two-stage scoring approach. Signals 1-4 are checked in the page HTML; signals 5-8 are checked in the linked CSS file. A blog is approved when htmlScore + cssScore >= 10.
HTML signals
| Signal | Description | Points |
|---|---|---|
| 1 | <meta name="generator" content="Pure Blog"> |
10 (definitive) |
| 2 | data-theme="light|dark|auto" on <html> tag |
+3 |
| 3 | Inline CSS variable pairs: --bg-light/--bg-dark, --text-light/--text-dark, --accent-light/--accent-dark, --border-light/--border-dark, --accent-bg-light/--accent-bg-dark |
+2 per pair (max 8) |
| 4 | Inline SVG sprite containing id="icon-calendar", id="icon-tag", id="icon-edit" |
+7 (all 3) |
Signals 2-4 require htmlScore + cssScore >= 10 to confirm detection.
CSS signals
| Signal | Description | Points |
|---|---|---|
| 5 | /* !pb:9e4f2a817c3d6b05 */ fingerprint in style.css |
10 (definitive) |
| 6 | CSS class combo: .post-nav-next, .admin-edit-link, p.tagline, .post-nav |
+3 per class (max 9) |
| 7 | CSS variable indirection: --bg-color: var(--bg-light) and --accent-bg-color: |
+3-4 |
| 8 | html[data-theme="auto"] in CSS |
+3 |
Signals 1 and 5 are definitive on their own (score = 10). All other signals require combinations to reach the threshold. Signals 3-4 are emitted by includes/header.php and are absent when a user overrides it via Layout Partials; signals 6-8 are read from assets/css/style.css and survive layout customization.
For reliable detection, add this tag to your Pure Blog template:
<meta name="generator" content="Pure Blog">
If your blog is not detected automatically, contact discover@thinkroot.xyz to have it added manually.
Admin panel
Access the admin panel at /admin.php. After logging in you can:
- Add a blog with automatic Pure Blog verification (no rate limit) and optional language override
- Force add a blog without Pure Blog verification (for confirmed Pure Blog sites that cannot be verified automatically)
- Edit a blog's name, URL, language code and description inline
- Remove a blog with a browser confirmation dialog
- Approve a flagged blog and clear its review status
- Filter flagged - show only blogs that need review with one click
- Search the blog list by name, URL or description
- Scan all blogs manually via the scan button - flags blogs that no longer use Pure Blog
- Stats - total, approved and flagged counts at a glance; breakdown by language
Automatic scan
The cron job (cron-scan.php) checks every blog in the directory and:
- Marks blogs that no longer detect Pure Blog as
needs_review - Restores blogs back to
approvedif Pure Blog is detected again - Leaves blogs unreachable due to temporary server errors unchanged
Flagged blogs appear in the admin panel with a yellow highlight and Needs review badge. They remain visible in the public directory until manually removed.
Feeds
The directory provides three feed formats, updated automatically as blogs are added:
| Format | URL |
|---|---|
| RSS 2.0 | /feed.xml.php |
| Atom | /feed.atom.php |
| JSON Feed | /feed.json.php |
| OPML export | /opml.php |
Security
- CSRF protection - all public form submissions require a session-bound CSRF token
- SSRF prevention - submitted URLs are checked against private IP ranges before the server fetches them
- Honeypot - an invisible field traps automated bot submissions
- Rate limiting - public submissions are limited to 3 per IP per hour
- Security headers -
X-Frame-Options,X-Content-Type-Options,Content-Security-Policy,Referrer-Policy,Permissions-Policy - IP hashing - submitter IPs are stored as SHA-256 hashes, never in plain text
- Atomic writes -
blogs.jsonis written via temp file + rename to prevent data corruption - Data directory -
data/is protected by its own.htaccessthat denies all web access
Contributing
Contributions are welcome. To contribute:
- Fork the repository
- Create a branch:
git checkout -b feature/your-feature - Commit your changes:
git commit -m 'Add your feature' - Push to the branch:
git push origin feature/your-feature - Open a pull request
Please keep the spirit of the project: no frameworks, no databases, minimal dependencies.
License
MIT License - see LICENSE for full text.