app
Wiki Notify for Google Chat
Public Made by Adomby adom
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.
← Commit history
Publish 1.7.2
4 files changed
+37−11
bin/__pycache__/wiki-notify-gchatcpython-312.pyc⋯ 1 unchanged line ⋯
bin/wiki-notify-gchat+35−9@@ -172,11 +172,21 @@ def gapi(url, method=None, body=None, params=None): def webhook_post(cfg, payload): """POST a message through the incoming webhook; returns the message name.""" import urllib.request+ import urllib.error req = urllib.request.Request( cfg["webhook"], data=json.dumps(payload).encode(), headers={"Content-Type": "application/json; charset=UTF-8"}, method="POST")- with urllib.request.urlopen(req, timeout=30) as r:- resp = json.loads(r.read().decode())+ try:+ with urllib.request.urlopen(req, timeout=30) as r:+ resp = json.loads(r.read().decode())+ except urllib.error.HTTPError as e:+ # surface Chat's explanation (e.g. "message size is too large"), not just the status line+ detail = ""+ try:+ detail = e.read().decode()[:200]+ except Exception:+ pass+ raise RuntimeError(f"webhook post failed: HTTP {e.code}: {detail}") if resp.get("error"): raise RuntimeError(f"webhook post failed: {resp['error'].get('message')}") return resp.get("name", "")@@ -519,20 +529,22 @@ EVENT_LABEL = {"opened": "opened", "comment": "new comment", "mention": "mention "closed": "closed", "reopened": "reopened", "activity": "activity"} -def render_card(items, cfg):+def render_card(items, cfg, max_rows=None, max_comments=None): tz = tzinfo_of(cfg) # NEEDS YOU floats to the top, then WAITING, then CLOSED; newest-first within each. rows = sorted(items.values(), key=lambda it: (TRIAGE_RANK[triage(it)], -it["lastAt"])) n_needs = sum(1 for it in rows if triage(it) == "needs_you") n_wait = sum(1 for it in rows if triage(it) == "waiting") n_closed = sum(1 for it in rows if triage(it) == "closed")- max_rows = int(cfg.get("maxRows", 40))+ if max_rows is None:+ max_rows = int(cfg.get("maxRows", 40)) shown, overflow = rows[:max_rows], max(0, len(rows) - max_rows) style = cfg.get("style", "table") if style == "blocks": widgets = render_block_widgets(shown, tz) else:- widgets = render_table_widgets(shown, tz, me=cfg.get("meName", "You"))+ widgets = render_table_widgets(shown, tz, me=cfg.get("meName", "You"),+ max_comments=max_comments) if overflow: widgets.append({"textParagraph": {"text": f"<i>+{overflow} older item(s) not shown</i>"}}) if not shown:@@ -550,7 +562,7 @@ EVENT_CAP = {"opened": "Opened", "comment": "Commented", "mention": "Mentioned", "closed": "Closed", "reopened": "Reopened", "activity": "Activity"} -def render_table_widgets(shown, tz, me="You"):+def render_table_widgets(shown, tz, me="You", max_comments=None): """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@@ -607,7 +619,8 @@ def render_table_widgets(shown, tz, me="You"): if inv: body.append(dim(f"{me} {inv}")) comments = it.get("comments", [])- shown_c = comments[-8:]+ n_c = 8 if max_comments is None else max_comments+ shown_c = comments[-n_c:] if n_c > 0 else [] if len(comments) > len(shown_c): body.append(dim(f"<i>+{len(comments) - len(shown_c)} earlier comment(s)</i>")) for c in shown_c:@@ -669,12 +682,25 @@ def items_hash(items): return hashlib.sha256(key.encode()).hexdigest() +CARD_BYTE_BUDGET = 30000 # Chat 400s "message size too large" well above this; leave margin++ def push_board(cfg, state, dry_run=False, force=False):- """Re-render the board; post + swap the card if content changed."""+ """Re-render the board; post + swap the card if content changed. If the+ render exceeds Chat's message size cap (hit live at 38 items / 46KB:+ 'the total message size is too large'), degrade stepwise - fewer+ comments, then fewer rows - until it fits, and say so in the log.""" h = items_hash(state["items"]) if h == state.get("lastRenderHash") and not force: return False- card = render_card(state["items"], cfg)+ ladder = [(None, None), (None, 1), (30, 1), (20, 1), (12, 0), (8, 0), (5, 0)]+ card = None+ for max_rows, max_comments in ladder:+ card = render_card(state["items"], cfg, max_rows=max_rows, max_comments=max_comments)+ if len(json.dumps(card)) <= CARD_BYTE_BUDGET:+ break+ if (max_rows, max_comments) != (None, None):+ log_line(f"WARN board trimmed to fit Chat size cap (rows<={max_rows}, comments<={max_comments})") if dry_run: return True old = state.get("boardMsg", "")
package.json+1−1@@ -2,7 +2,7 @@ "slug": "wiki-notify-gchat", "title": "Wiki Notify for Google Chat", "type": "app",- "version": "1.7.1",+ "version": "1.7.2", "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.7.1",+ "version": "1.7.2", "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",