-- Ported from anyfeed-server's overwatch-patch-notes.lua. That version -- rendered a patch's markup to markdown via a shared converter; luafeed -- has no HTML->markdown capability, so `description` here is the patch -- block's plain text instead (loses bold/list formatting, keeps the -- actual words) via `:text()`. Everything else -- title, link, date, and -- crucially no second fetch needed at all, since every patch is already -- inline on this one page -- ports directly. -- -- site{ -- name = "overwatch-patch-notes", -- url = "https://overwatch.blizzard.com/en-us/news/patch-notes/", -- method = "overwatch-patch-notes", -- title = "Overwatch Patch Notes", -- } -- -- Markup per patch (as of writing): --
--
--
--
August 20, 2026
--
--

Overwatch Retail Patch Notes - August 20, 2026

-- ...section content... --
-- -- The page only ever renders the current month's patches server-side -- -- the year/month selectors re-fetch via client-side JS, which a plain -- fetch doesn't run -- so this only ever sees recent entries. That's the -- right behavior for a feed tracker: older patches were already seen. local function has_class(node, name) local class = node:attr("class") if not class then return false end for token in class:gmatch("%S+") do if token == name then return true end end return false end local function find_by_class(node, tag, class_name) local matches = {} for _, el in ipairs(node:find_all(tag)) do if has_class(el, class_name) then table.insert(matches, el) end end return matches end -- Strips any query string/fragment so `#patch-...` can be appended cleanly, -- even if the configured url already carries one. local function page_url_of(url) return url:match("^([^?#]+)") end -- os.time() interprets a field table as the host's *local* time, and Lua -- has no timezone-aware alternative in the standard library -- this -- converts a UTC field table to the right epoch by computing the host's -- current UTC offset and applying it, rather than assuming the host runs -- in UTC. (Not exact across a DST transition between now and the target -- date, an acceptable approximation for ordering feed items.) local function utc_time(fields) local now = os.time() local isdst = os.date("*t", now).isdst local utc_now = os.date("!*t", now) utc_now.isdst = isdst local offset = now - os.time(utc_now) fields.isdst = isdst return os.time(fields) + offset end local function rfc822_midnight(date_part) local year, month, day = date_part:match("^(%d%d%d%d)-(%d%d)-(%d%d)$") if not year then return nil end local timestamp = utc_time({ year = tonumber(year), month = tonumber(month), day = tonumber(day), hour = 0 }) return os.date("!%a, %d %b %Y %H:%M:%S GMT", timestamp) end local function build_item(page_url, patch) local anchor_el = find_by_class(patch, "div", "anchor")[1] local title_el = find_by_class(patch, "h3", "PatchNotes-patchTitle")[1] if not anchor_el or not title_el then return nil end local id = anchor_el:attr("id") local date_part = id and id:match("^patch%-(%d%d%d%d%-%d%d%-%d%d)$") return { title = title_el:text(), link = page_url .. "#" .. id, guid = id, description = patch:text(), pub_date = date_part and rfc822_midnight(date_part), } end local root, url = ... local page_url = page_url_of(url) local patches = find_by_class(root, "div", "PatchNotes-patch") local items = {} for _, patch in ipairs(patches) do local item = build_item(page_url, patch) if item then table.insert(items, item) end end return items