-- simple-pages-microdata Soupault Plugin -- SPDX-FileCopyrightText: 2026 toastal -- SPDX-License-Identifier: MPL-2.0 -- -- Small single-purpose pages, one branch each (URL-gated): -- /community/ → article#Article becomes mainEntity Organization -- (h1 re-typed headline→name) + contactPoint ContactPoint -- (contactType chat + copied xmpp: join-link href) -- /funding/ → article#Article becomes mainEntity DonateAction -- (h1 re-typed headline→name) + recipient link to #Org + -- url link copied from the funding-page link -- /roadmap/ → article#Article becomes mainEntity Article -- (h1 headline kept: valid on Article) + description Plugin.require_version("5.0.0") function url_path() local url = page_url url = Regex.replace(url, "^[a-z]+://[^/]+", "") if url == "" then url = "/" elseif not String.starts_with(url, "/") then url = "/" .. url end return url end function head_meta_content(name) local meta = HTML.select_one(page, format("head meta[name=\"%s\"]", name)) if HTML.is_element(meta) then return HTML.get_attribute(meta, "content") end return nil end function hidden_meta(itemprop, content, parent) local elem = HTML.create_element("meta") HTML.set_attribute(elem, "itemprop", itemprop) HTML.set_attribute(elem, "content", content) HTML.append_child(parent, elem) return elem end function hidden_link(itemprop, href, parent) local elem = HTML.create_element("link") HTML.set_attribute(elem, "itemprop", itemprop) HTML.set_attribute(elem, "href", href) HTML.append_child(parent, elem) return elem end function scope_main_entity(itemtype) local article = HTML.select_one(page, "article#Article") if not HTML.is_element(article) then Plugin.fail(format("No article#Article found in “%s”, cannot apply %s scope", page_file, itemtype)) end HTML.set_attribute(article, "itemscope", "") HTML.set_attribute(article, "itemprop", "mainEntity") HTML.set_attribute(article, "itemtype", itemtype) return article end function compensate_webpage_headline(main) h1 = HTML.select_one(main, "h1[itemprop=\"headline\"]") if not HTML.is_element(h1) then h1 = HTML.select_one(main, "h1") end if HTML.is_element(h1) then headline = HTML.create_element("meta") HTML.set_attribute(headline, "itemprop", "headline") HTML.set_attribute(headline, "content", String.trim(HTML.strip_tags(h1))) HTML.prepend_child(main, headline) return h1 end Log.warning(format("No h1 found in “%s”, skipping WebPage.headline compensation", page_file)) return nil end path = url_path() if path == "/community" or path == "/community/" then main = HTML.select_one(page, "main#Main") if not HTML.is_element(main) then Plugin.fail(format("No main#Main found in “%s”, skipping Organization scope", page_file)) end article = scope_main_entity("https://schema.org/Organization") h1 = compensate_webpage_headline(main) if HTML.is_element(h1) then HTML.set_attribute(h1, "itemprop", "name") end contact = nil links = HTML.select(article, "a[href]") local idx = 1 while Value.is_nil(contact) and links[idx] do local href = HTML.get_attribute(links[idx], "href") if Value.is_string(href) and String.starts_with(href, "xmpp:") then contact = href end idx = idx + 1 end if Value.is_string(contact) then point = HTML.create_element("div") HTML.set_attribute(point, "itemprop", "contactPoint") HTML.set_attribute(point, "itemscope", "") HTML.set_attribute(point, "itemtype", "https://schema.org/ContactPoint") hidden_meta("contactType", "chat", point) hidden_link("url", contact, point) HTML.append_child(article, point) else Log.warning(format("No chatroom join link found in “%s”, skipping contactPoint", page_file)) end elseif path == "/funding" or path == "/funding/" then main = HTML.select_one(page, "main#Main") if not HTML.is_element(main) then Plugin.fail(format("No main#Main found in “%s”, skipping DonateAction scope", page_file)) end article = scope_main_entity("https://schema.org/DonateAction") h1 = compensate_webpage_headline(main) if HTML.is_element(h1) then HTML.set_attribute(h1, "itemprop", "name") end hidden_link("recipient", "#Org", article) funding_url = nil links = HTML.select(article, "a[href]") local idx = 1 while Value.is_nil(funding_url) and links[idx] do local href = HTML.get_attribute(links[idx], "href") if Value.is_string(href) and not Value.is_nil(Regex.match(href, "funding")) then funding_url = href end idx = idx + 1 end if Value.is_string(funding_url) then hidden_link("url", funding_url, article) else Log.warning(format("No funding link found in “%s”, skipping DonateAction url", page_file)) end elseif path == "/roadmap" or path == "/roadmap/" then main = HTML.select_one(page, "main#Main") if not HTML.is_element(main) then Plugin.fail(format("No main#Main found in “%s”, skipping Article scope", page_file)) end article = scope_main_entity("https://schema.org/Article") -- headline is valid on Article: keep it, only compensate WebPage. compensate_webpage_headline(main) description = head_meta_content("description") if Value.is_string(description) and description ~= "" then hidden_meta("description", description, article) end end