all repos — awesome-indieweb @ 00388647e6a6bf426b6bfe9a0c0ccffb2ecf7aa8

resources for indieweb

feat: automatic link checker
Pablo Murad pblmrd@gmail.com
Thu, 30 Jul 2026 07:00:59 -0300
commit

00388647e6a6bf426b6bfe9a0c0ccffb2ecf7aa8

parent

bbe7183f46a7e140238dbb8e0b838d5cefa16f92

A .github/ISSUE_TEMPLATE/add-resource.md

@@ -0,0 +1,21 @@

+--- +name: Add a resource +about: Suggest a new protocol, platform, service, library, or tool for the list +title: "Add: <resource name>" +labels: new resource +--- + +<!-- Prefer opening a pull request if you can — it is faster to merge. Use this issue if you cannot. --> + +## Resource + +- **Name and URL:** [Name](https://example.com) +- **Suggested section:** <!-- e.g. Static Site Generators --> +- **One-line description:** <!-- Factual, ends with a period. What it does, not why you like it. --> + +## Checklist + +- [ ] It is genuinely useful for building, owning, or connecting an independent personal website. +- [ ] It is not already in the list (I searched first). +- [ ] The project is maintained (recent releases or activity). +- [ ] The URL is the canonical HTTPS project URL, with no tracking or affiliate parameters.
A .github/ISSUE_TEMPLATE/broken-link.md

@@ -0,0 +1,22 @@

+--- +name: Report a broken or outdated entry +about: Flag a dead link, parked domain, renamed project, or discontinued resource +title: "Broken: <resource name>" +labels: broken link +--- + +## Entry + +- **Name and URL as listed:** [Name](https://example.com) +- **Section:** <!-- e.g. Domains and Hosting --> + +## What is wrong + +- [ ] Dead link (404 / connection error) +- [ ] Parked domain or for-sale page +- [ ] Project renamed or moved +- [ ] Project discontinued or unmaintained +- [ ] Description is inaccurate + +**Details / new URL (if any):** +<!-- Paste the new canonical URL or explain what changed. -->
A .github/ISSUE_TEMPLATE/config.yml

@@ -0,0 +1,5 @@

+blank_issues_enabled: true +contact_links: + - name: Contribution guidelines + url: https://github.com/runawaydevil/awesome-indieweb/blob/main/CONTRIBUTING.md + about: Please read the guidelines before adding, updating, or removing an entry.
A .github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,21 @@

+<!-- Thanks for contributing! Please keep this list high quality by checking the boxes below. --> + +## What does this PR do? + +- [ ] Adds a new entry +- [ ] Updates an existing entry +- [ ] Removes a dead or discontinued entry +- [ ] Other (explain below) + +**Summary:** +<!-- Briefly describe the change. --> + +## Checklist + +- [ ] The entry uses the exact format: `- [Name](https://url) - Factual description ending with a period.` +- [ ] It is placed in the single most relevant section, in a sensible order. +- [ ] No duplicate — I searched the list first. +- [ ] The URL is canonical HTTPS, with no tracking or affiliate parameters. +- [ ] The link works and the project is maintained. +- [ ] The Table of Contents still matches the section headings. +- [ ] No trailing whitespace was introduced.
A .github/scripts/check_links.py

@@ -0,0 +1,99 @@

+#!/usr/bin/env python3 + +import concurrent.futures +import re +import socket +import urllib.error +import urllib.request + +README = "README.md" +MARKER = " **(link no longer exists)**" + +# Match a list entry and capture its primary URL: "- [Name](https://...) - ..." +# Table rows ("|...") and Table-of-Contents anchors ("#...") never match. +ENTRY_RE = re.compile(r"^- \[[^\]]+\]\((https?://[^)]+)\)") + +UA = ( + "Mozilla/5.0 (compatible; awesome-indieweb-linkcheck/1.0; " + "+https://github.com/runawaydevil/awesome-indieweb)" +) +TIMEOUT = 20 + + +def is_dead(url): + """Return True only on a definitive dead signal (404/410/NXDOMAIN).""" + req = urllib.request.Request(url, method="GET", headers={"User-Agent": UA}) + try: + urllib.request.urlopen(req, timeout=TIMEOUT) + return False + except urllib.error.HTTPError as e: + return e.code in (404, 410) + except urllib.error.URLError as e: + # DNS resolution failure means the domain itself is gone. + return isinstance(e.reason, socket.gaierror) + except Exception: + # Anything else is inconclusive; never flag on uncertainty. + return False + + +def strip_marker(line): + """Remove a trailing marker (if present), preserving the newline.""" + body = line.rstrip("\n") + if body.endswith(MARKER): + nl = "\n" if line.endswith("\n") else "" + return body[: -len(MARKER)] + nl + return line + + +def main(): + with open(README, encoding="utf-8") as f: + lines = f.readlines() + + # (line index, url) for every entry, ignoring any existing marker. + targets = [] + for i, line in enumerate(lines): + m = ENTRY_RE.match(strip_marker(line)) + if m: + targets.append((i, m.group(1))) + + urls = {url for _, url in targets} + dead = {} + with concurrent.futures.ThreadPoolExecutor(max_workers=16) as ex: + futures = {ex.submit(is_dead, u): u for u in urls} + for fut in concurrent.futures.as_completed(futures): + url = futures[fut] + try: + dead[url] = fut.result() + except Exception: + dead[url] = False + + changed, newly_dead, recovered = 0, [], [] + for i, url in targets: + base = strip_marker(lines[i]) + was_marked = lines[i] != base + if dead.get(url): + nl = "\n" if base.endswith("\n") else "" + updated = base.rstrip("\n") + MARKER + nl + if updated != lines[i]: + lines[i], changed = updated, changed + 1 + newly_dead.append(url) + elif was_marked: + lines[i], changed = base, changed + 1 + recovered.append(url) + + if changed: + with open(README, "w", encoding="utf-8", newline="") as f: + f.writelines(lines) + + print(f"Checked {len(urls)} unique links across {len(targets)} entries.") + print(f"Newly marked dead: {len(newly_dead)}") + for u in newly_dead: + print(f" DEAD {u}") + print(f"Recovered (marker removed): {len(recovered)}") + for u in recovered: + print(f" ALIVE {u}") + print(f"Lines changed: {changed}") + + +if __name__ == "__main__": + main()
M CONTRIBUTING.mdCONTRIBUTING.md

@@ -1,6 +1,6 @@

# Contribution Guidelines -Thanks for taking the time to contribute! Please open a pull request to add, update, or remove an entry. +Thanks for taking the time to contribute! Please [open a pull request](https://github.com/runawaydevil/awesome-indieweb/pulls) to add, update, or remove an entry. If you cannot open a PR, [file an issue](https://github.com/runawaydevil/awesome-indieweb/issues/new/choose) to suggest a resource or report a broken link. ## Adding an entry
M README.mdREADME.md

@@ -1,10 +1,14 @@

# Awesome IndieWeb [![Awesome](https://awesome.re/badge.svg)](https://awesome.re) -> A curated list of protocols, platforms, services, libraries, and tools for publishing and owning an independent personal website — on your own domain, with durable URLs, connected to the rest of the web through open standards. +> A curated list of protocols, platforms, services, libraries, and tools for publishing and owning an independent personal website (on your own domain, with durable URLs, connected to the rest of the web through open standards). The [IndieWeb](https://indieweb.org/) is not a product, a social network, or an official stack. It is an approach centered on **publishing first in a space you control** (normally your own domain), keeping URLs durable, and connecting that site to the wider web through open standards. -This list spans both resources **native to the IndieWeb** (standards and building blocks) and **adjacent** tools that solve important parts of the problem well. Statuses change fast — confirm recent releases, docs, and export procedures before adopting a critical component. +This list spans both resources **native to the IndieWeb** (standards and building blocks) and **adjacent** tools that solve important parts of the problem well. Statuses change fast, confirm recent releases, docs, and export procedures before adopting a critical component. + +**Contributions are welcome.** Open a [pull request](https://github.com/runawaydevil/awesome-indieweb/pulls) to add, update, or remove an entry, or file an [issue](https://github.com/runawaydevil/awesome-indieweb/issues/new/choose) to suggest a resource or report a broken link. Please read the [contribution guidelines](CONTRIBUTING.md) first. + +Links are checked automatically on the first of each month. Entries found permanently unreachable are flagged **(link no longer exists)** next to them — please open a PR to fix or remove any you spot. ## Contents