← Commit history
README.md+15−13
@@ -9,19 +9,21 @@ wiki notification email ──poll──> per-item state ──render──> ONE         └──sweep──> trash / archive / label ``` -The board is a single card holding a **table**: a colored STATUS rail column-(OPEN green / CLOSED gray) beside a content column, one divider-ruled row per-issue. The left column holds the **item ref** (`page #N`) with its colored-status beneath; the right column holds the **description** with the **View-link |'d at its end**, then `Created by <name> on <date> <time>`,-`<Event> by <name> on <date> <time>` (Opened / Commented / Closed /-Reopened / Mentioned), and **your involvement** - "Drew owns this fix"-(assigned) > "Drew opened this" > "Drew owns this page" (you authored the-wiki page, looked up via `adom-wiki page stats`) > "Drew was mentioned" /-"commented on this" > "Drew watches this page" - from the mail's-"You're receiving this because..." footer plus page authorship. Set-`fullName` in the config (e.g. "Drew Owens") for the ownership check; it is-otherwise derived from the notification's To header. New activity bubbles to the top.+The board is a single card styled like the **wiki digest posts**: one+divider-ruled row per issue. The left column is the wiki page's **hero+image** (clickable, opens the page - resolved to the public raster blob via+`adom-wiki page stats`, since Chat's image proxy cannot render the SVG+`/hero/` endpoint). The right column leads with the colored **OPEN/CLOSED+status** in the digest's `[new]`/`[updated]` slot, then the **hyperlinked+item ref** (`page #N`, opens the issue), the description, `Created by+<name> on <date> <time>`, `<Event> by <name> on <date> <time>` (Opened /+Commented / Closed / Reopened / Mentioned), and **your involvement** -+"Drew owns this fix" (assigned) > "Drew opened this" > "Drew owns this+page" (you authored the wiki page) > "Drew was mentioned" / "commented on+this" > "Drew watches this page" - from the mail's "You're receiving this+because..." footer plus page authorship. Set `fullName` in the config+(e.g. "Drew Owens") for the ownership check; it is otherwise derived from+the notification's To header. New activity bubbles to the top. (Chat cards have no native table widget: the columns widget caps at 2 and grid tiles are equal-width and unreadable, so the table is this 2-column layout; set `"style": "blocks"` for the compact per-item layout with View
bin/__pycache__/wiki-notify-gchatcpython-312.pyc
⋯ 1 unchanged line ⋯
bin/wiki-notify-gchat+42−24
@@ -193,29 +193,37 @@ EVENT_PATTERNS = [ ]  -def page_author(page, state):-    """Author of a wiki page via `adom-wiki page stats` (cached in state)."""-    cache = state.setdefault("pageAuthors", {})+def page_info(page, state):+    """Author + raster hero URL of a wiki page via `adom-wiki page stats`+    (cached in state). Hero must be a public raster blob - Chat's image proxy+    does not render the SVG /hero/ billboard endpoint."""+    cache = state.setdefault("pageInfo", {})     if page in cache:         return cache[page]-    author = ""+    info = {"author": "", "hero": ""}     code, out, _ = run_cmd(["adom-wiki", "page", "stats", page, "--json"])     if code == 0:         try:             j = json.loads(out)-            author = ((j.get("data", j) or {}).get("page") or {}).get("author_name") or ""+            pg = (j.get("data", j) or {}).get("page") or {}+            info["author"] = pg.get("author_name") or ""+            hp = pg.get("hero_path") or ""+            if hp.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):+                info["hero"] = f"https://wiki.adom.inc/blob/{pg.get('type', 'app')}/{pg.get('slug')}/{hp}"         except Exception:             pass-    cache[page] = author+    cache[page] = info     save_state(state)-    return author+    return info   def annotate_page_ownership(state, me_full):-    """Mark items whose wiki page the user authored (drives 'owns this page')."""+    """Mark items with page authorship (drives 'owns this page') + hero URL."""+    state.pop("pageAuthors", None)  # superseded cache key     for it in state.get("items", {}).values():-        author = page_author(it["page"], state)-        it["ownsPage"] = bool(me_full) and author == me_full+        info = page_info(it["page"], state)+        it["ownsPage"] = bool(me_full) and info["author"] == me_full+        it["heroUrl"] = info["hero"]   def parse_notification(msg):@@ -367,12 +375,12 @@ EVENT_CAP = {"opened": "Opened", "comment": "Commented", "mention": "Mentioned",   def render_table_widgets(shown, tz, me="You"):-    """A table built from columns widgets, one divider-ruled row per item.-    Left column: the item ref (page + issue #) with the colored status-    beneath it. Right column: description with the View link |'d at its end,-    Created-by line, last-event line, and the user's involvement. Chat cards-    have no native table widget and the columns widget caps at 2; this is-    the closest readable equivalent."""+    """Wiki-digest-style rows (the same skeleton the wiki digest posts use):+    left column = the page's hero image (clickable, opens the page); right+    column = a decoratedText with the colored OPEN/CLOSED status in the+    topLabel (the digest's [new]/[updated] slot) and the hyperlinked item+    ref as the title line, followed by description + Created / last-event /+    involvement lines."""     import html as html_mod     widgets = []     for i, it in enumerate(shown):@@ -390,9 +398,9 @@ def render_table_widgets(shown, tz, me="You"):             creator = me  # the mail footer ('you opened the issue') outranks our earliest-email guess         actor = it.get("actor") or "-"         event = EVENT_CAP.get(it["lastEvent"], it["lastEvent"] or "Activity")-        view = f'<a href="{it["url"]}">View</a>' if it["url"] else dim("-")-        fields = [-            f"{title} | {view}",+        linked_item = f'<a href="{it["url"]}">{item}</a>' if it["url"] else f"<b>{item}</b>"+        body = [+            title,             dim(f"Created by {creator} on {fmt_datetime(it['openedAt'], tz)}"),             dim(f"{event} by {actor} on {fmt_datetime(it['lastAt'], tz)}"),         ]@@ -400,12 +408,22 @@ def render_table_widgets(shown, tz, me="You"):         if it.get("ownsPage") and it.get("invRank", 0) < 3:             inv = "owns this page"  # page authorship beats mention/comment/watch, not opened/assigned         if inv:-            fields.append(dim(f"{me} {inv}"))+            body.append(dim(f"{me} {inv}"))+        left_widgets = []+        if it.get("heroUrl"):+            img = {"image": {"imageUrl": it["heroUrl"], "altText": it["page"]}}+            img["image"]["onClick"] = {"openLink": {"url": f"https://wiki.adom.inc/{it['page']}"}}+            left_widgets.append(img)+        else:+            left_widgets.append({"textParagraph": {"text": " "}})         widgets.append({"columns": {"columnItems": [-            {"horizontalSizeStyle": "FILL_MINIMUM_SPACE", "verticalAlignment": "TOP",-             "widgets": [{"textParagraph": {"text": f"<b>{item}</b><br>{status}"}}]},-            {"horizontalSizeStyle": "FILL_AVAILABLE_SPACE", "verticalAlignment": "TOP",-             "widgets": [{"textParagraph": {"text": "<br>".join(fields)}}]},+            {"horizontalSizeStyle": "FILL_MINIMUM_SPACE", "verticalAlignment": "CENTER",+             "widgets": left_widgets},+            {"horizontalSizeStyle": "FILL_AVAILABLE_SPACE", "verticalAlignment": "CENTER",+             "widgets": [+                {"decoratedText": {"topLabel": status, "text": linked_item, "wrapText": True}},+                {"textParagraph": {"text": "<br>".join(body)}},+             ]},         ]}})     return widgets 
docs/board.png
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -2,7 +2,7 @@   "slug": "wiki-notify-gchat",   "title": "Wiki Notify for Google Chat",   "type": "app",-  "version": "1.4.1",+  "version": "1.5.0",   "description": "Turn wiki.adom.inc notification emails into a live Google Chat board. One card, one row per issue - newest first, OPEN/CLOSED status, origination date, actor, and a View link - continuously updated in your own Chat space via your own webhook. Emails are swept out of your inbox (trash, archive, or label). Closed items linger until 6am the next day, then drop off. Delegates all auth to adom-google; the only secret is your webhook URL, kept in your private config.",   "tags": [     "cli",
page.json+1−1
@@ -2,7 +2,7 @@   "slug": "wiki-notify-gchat",   "title": "Wiki Notify for Google Chat",   "type": "app",-  "version": "1.4.1",+  "version": "1.5.0",   "description": "Turn wiki.adom.inc notification emails into a live Google Chat board. One card, one row per issue - newest first, OPEN/CLOSED status, origination date, actor, and a View link - continuously updated in your own Chat space via your own webhook. Emails are swept out of your inbox (trash, archive, or label). Closed items linger until 6am the next day, then drop off. Delegates all auth to adom-google; the only secret is your webhook URL, kept in your private config.",   "tags": [     "cli",