"""IndieWeb.Rocks web app."""
import collections
import hashlib
import easyuri
import micropub
import web
from web import tx
from indieweb_rocks import agent, app, refresh_domain, sites_path
from .utils import silos
@app.control("")
class Landing:
"""Site landing."""
def get(self):
"""Return a search box and short description."""
return app.view.landing()
@app.control("bot")
class Bot:
"""Site bot."""
def get(self):
"""Return ."""
web.header("Content-Type", "text/html")
return "This site uses a bot with <code>User-Agent: IndieWebRocksBot</code>"
@app.control("featured")
class Featured:
"""Featured sites."""
def get(self):
"""Return a list of sites with high test scores."""
return app.view.featured()
@app.control("silos")
class Silos:
"""."""
def get(self):
"""."""
return app.view.silos()
@app.control("silos/url_summaries.json")
class SiloSummaries:
"""."""
def get(self):
"""."""
web.header("Content-Type", "application/json")
return silos
@app.control("features")
class Features:
"""Features of sites."""
def get(self):
"""Return a list of features and sites that support them."""
return app.view.features()
@app.control("screens")
class Screens:
"""Site screenshots."""
def get(self):
"""Return a list of site screenshots."""
urls = [url for url in app.model.get_people()]
return app.view.screens(urls)
@app.control("the-street")
class TheStreet:
"""The Street."""
def get(self):
"""Return a list of ."""
subdomains = collections.defaultdict(list)
for url in app.model.get_people():
url = easyuri.parse(url)
domain = subdomains[f"{url.domain}.{url.suffix}"]
if url.subdomain:
domain.append(url.subdomain)
domains = sorted(
[
(hashlib.sha256(d.encode("utf-8")).hexdigest().upper(), d)
for d in subdomains
]
)
return app.view.the_street(domains, subdomains)
@app.control("search")
class Search:
"""Search the IndieWeb."""
def get(self):
"""Return a query's results."""
try:
query = web.form("q").q
except web.BadRequest:
raise web.SeeOther("/search")
try:
url = easyuri.parse(query)
except (ValueError, easyuri.SuffixNotFoundError):
pass
else:
if url.suffix:
raise web.SeeOther(f"/{url.minimized}")
# people = tx.db.select(
# "people", where="url LIKE ? OR name LIKE ?", vals=[f"%{query}%"] * 2
# )
people = tx.db.select(
"resources",
what="json_extract(resources.details, '$.card') AS card",
where="json_extract(resources.details, '$.card.name') LIKE ?",
vals=[f"%{query}%"],
)
posts = tx.db.select(
"resources",
# what="json_extract(resources.details, '$.card')",
where="json_extract(resources.details, '$.card.name') LIKE ?",
vals=[f"%{query}%"],
)
return app.view.results(query, people, posts)
@app.control("posts")
class Posts:
"""Show indexed posts."""
def get(self):
"""Return a chronological list of posts."""
return app.view.posts(app.model.get_posts())
@app.control("people")
class People:
"""Show indexed people and organizations."""
def get(self):
"""Return an alphabetical list of people and organizations."""
return app.view.people(app.model.get_people())
@app.control("people.txt")
class PeopleTXT:
"""Index of people as plaintext."""
def get(self):
"""Return a list of indexed sites."""
# TODO # accept a
# TODO tx.db.select(
# TODO tx.db.subquery(
# TODO "crawls", where="url not like '%/%'", order="crawled desc"
# TODO ),
# TODO group="url",
# TODO )
return "\n".join([url for url in app.model.get_people()])
@app.control("places")
class Places:
"""Show indexed places."""
def get(self):
"""Return an alphabetical list of places."""
return "places"
@app.control("events")
class Events:
"""Show indexed events."""
def get(self):
"""Return a chronological list of events."""
return "events"
@app.control("recipes")
class Recipes:
"""Show indexed recipes."""
def get(self):
"""Return a list of recipes."""
return ""
@app.control("reviews")
class Reviews:
"""Show indexed reviews."""
def get(self):
"""Return a list of reviews."""
return "reviews"
@app.control("projects")
class Projects:
"""Show indexed projects."""
def get(self):
"""Return a list of projects."""
return "projects"
@app.control("categories")
class Categories:
"""Browse by category."""
def get(self):
"""Return an alphabetical list of categories."""
return app.view.categories(app.model.get_categories())
@app.control("micropub")
class Micropub:
"""Proxy a Micropub request to the signed in user's endpoint."""
def post(self):
form = web.form()
client = micropub.Client(tx.user.session["micropub_endpoint"])
permalink = client.create_post(form)
raise web.SeeOther(permalink)
@app.control("crawler")
class Crawler:
"""Crawler."""
def get(self):
"""Return a log of crawls and form to post a new one."""
return app.view.crawler() # tx.db.select("crawls"))
def post(self):
urls = web.form("url").url.splitlines()
for url in urls:
web.enqueue(refresh_domain, url)
raise web.Accepted(f"enqueued {len(urls)}")
@app.control("crawler/all")
class RecrawlAll:
"""Recrawl all people."""
def post(self):
for person in tx.db.select("people"):
web.enqueue(refresh_domain, person["url"])
raise web.Accepted("enqueued")
@app.control("stats")
class Stats:
"""Show stats."""
def get(self):
"""Return site/IndieWeb statistics."""
properties = collections.Counter()
for person in app.model.get_people_details():
for prop in person["details"]:
properties[prop] += 1
return app.view.stats(properties)
@app.control("sites")
class Sites:
"""Index of sites as HTML."""
def get(self):
"""Return a list of indexed sites."""
# TODO # accept a
# TODO tx.db.select(
# TODO tx.db.subquery(
# TODO "crawls", where="url not like '%/%'", order="crawled desc"
# TODO ),
# TODO group="url",
# TODO )
with tx.db.transaction as cur:
urls = cur.cur.execute(
" select * from ("
+ "select * from crawls where url not like '%/%' order by crawled desc"
+ ") group by url"
)
return app.view.sites(urls)
@app.control("sites.txt")
class SitesTXT:
"""Index of sites as plaintext."""
def get(self):
"""Return a list of indexed sites."""
# TODO # accept a
# TODO tx.db.select(
# TODO tx.db.subquery(
# TODO "crawls", where="url not like '%/%'", order="crawled desc"
# TODO ),
# TODO group="url",
# TODO )
with tx.db.transaction as cur:
urls = cur.cur.execute(
" select * from ("
+ "select * from crawls where url not like '%/%' order by crawled desc"
+ ") group by url"
)
return "\n".join([url[0] for url in urls])
@app.control("commons")
class Commons:
""""""
def get(self):
return app.view.commons()
@app.control("robots.txt")
class RobotsTxt:
""""""
def get(self):
return "User-agent: *\nDisallow: /"
@app.control("terms")
class TermsOfService:
"""Terms of service."""
def get(self):
"""Return a terms of service."""
return app.view.terms()
@app.control("privacy")
class PrivacyPolicy:
"""Privacy policy."""
def get(self):
"""Return a privacy policy."""
subprocess.run(["notify-send", "-u", "critical", "PRIVACY POLICY"])
return app.view.privacy()
@app.control("map")
class Map:
"""Map view."""
def get(self):
"""Render a map view."""
return app.view.map()
@app.control("toolbox")
class Toolbox:
"""Tools."""
def get(self):
"""Display tools."""
return app.view.toolbox()
@app.control("toolbox/representative-card")
class RepresentativeCard:
"""Representative card tool."""
def get(self):
"""Parse representative card."""
try:
url = web.form("url").url
except web.BadRequest:
return app.view.toolbox.representative_card()
web.header("Content-Type", "application/json")
return web.dump(agent.get(url).card.data, indent=2)
@app.control("toolbox/representative-feed")
class RepresentativeFeed:
"""Representative feed tool."""
def get(self):
"""Parse representative feed."""
try:
url = web.form("url").url
except web.BadRequest:
return app.view.toolbox.representative_feed()
web.header("Content-Type", "application/json")
return web.dump(agent.get(url).feed.data, indent=2)
@app.control("indieauth")
class IndieAuth:
"""IndieAuth support."""
def get(self):
"""Return sites with IndieAuth support."""
sites = tx.db.select("resources", order="url ASC")
# for site in sites:
# details = site["details"]
# if indieauth := details.get("indieauth"):
# domain = details["domain"]["name"]
return app.view.indieauth(sites)
@app.control("details/{site}(/{page})?")
class SiteDetails:
"""A web resource."""
def get(self, site, page=None):
web.header("Content-Type", "application/json")
return tx.db.select("resources", where="url = ?", vals=[site])[0]["details"]
@app.control("a11y/{site}(/{page})?")
class Accessibility:
"""A web resource."""
def get(self, site, page=None):
try:
a11y = web.load(path=sites_path / site / "a11y.json")
except FileNotFoundError:
a11y = None
return app.view.a11y(site, a11y)
# @app.control("{site}")
@app.control("{site}(/{page})?")
class URL:
"""A web resource."""
def get(self, site, page=None):
"""Return a site analysis."""
page_url = easyuri.parse(site)
if page:
page_url = easyuri.parse(f"{site}/{page}")
redirect = tx.db.select(
"redirects",
what="outgoing",
where="incoming = ?",
vals=[page_url.minimized],
)
try:
raise web.SeeOther(redirect[0]["outgoing"])
except IndexError:
pass
if page:
return app.view.page(page_url, {})
try:
details = tx.db.select("resources", where="url = ?", vals=[site])[0][
"details"
]
# XXX web.load(path=sites_path / site / "details.json")
except IndexError:
web.enqueue(refresh_domain, site)
return app.view.crawl_enqueued()
if site in [s[0] for s in silos.values()]:
return app.view.silo(page_url, details)
try:
audits = web.load(path=sites_path / site / "audits.json")
except FileNotFoundError:
audits = None
try:
a11y = web.load(path=sites_path / site / "a11y.json")
except FileNotFoundError:
a11y = None
try:
manifest = web.load(path=sites_path / site / "manifest.json")
except FileNotFoundError:
manifest = None
return app.view.site(page_url, details, audits, a11y, manifest)
def post(self, site):
web.enqueue(refresh_domain, site)
return app.view.crawl_enqueued()
# TODO
# if no-flash-header or use form argument:
# raise web.SeeOther(); flash user's session with message to insert as CSS
# elif flash-header:
# return just message as JSON
raise web.flash("crawl enqueued")
# XXX @app.control("sites/{site}/scoreboard.svg")
# XXX class SiteScoreboard:
# XXX """A site's scoreboard."""
# XXX
# XXX def get(self, site):
# XXX """Return an SVG document rendering given site's scoreboard."""
# XXX web.header("Content-Type", "image/svg+xml")
# XXX web.header("X-Accel-Redirect", f"/data/{site}/scoreboard.svg")