From c63e14b2470af65019dbfb52302909ec214198db Mon Sep 17 00:00:00 2001 From: Mason Egger Date: Sun, 6 Sep 2026 17:19:38 -0500 Subject: [PATCH] Add the meetup.com event link to both announcements Mason wants the monthly marketing and organizers messages to carry the meetup.com event URL now that the network event exists by send time. Adds the line to both templates and makes meetup_event_url a required month-TOML field. Also persists the Meetup OAuth refresh token rotated by today's GraphQL inspection and dedupe runs. Claude-Session: https://claude.ai/code/session_01Te57uzxNDPxJVoM2mU9mQx --- .ai-sessions/lessons.md | 8 ++- ...sion-20260906-1718-meetup-dedupe-resend.md | 50 +++++++++++++++++++ .../scripts/build_announcements.py | 3 ++ secrets/meetup.sops.env | 6 +-- 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 .ai-sessions/session-20260906-1718-meetup-dedupe-resend.md diff --git a/.ai-sessions/lessons.md b/.ai-sessions/lessons.md index 136d3a0..a266053 100644 --- a/.ai-sessions/lessons.md +++ b/.ai-sessions/lessons.md @@ -2,6 +2,8 @@ ## Recent +- Google Forms' new publish flow defaults the responder audience to the org; a forms.gle link returns 401 anonymously until Responders is set to "Anyone with the link". Always curl responder links unauthenticated before announcing them (2026-09-06) +- The meetup.com pro dashboard's network-events view does not show network events created via the API filterId path, so a dash-created event can silently duplicate a script-created one; check the origin group's ACTIVE events by networkEvent id when duplicates are suspected (2026-09-06) - `mkdocs build --strict` (and `just validate`) fail on this repo even on clean main because `site_url` is unset; CI deploys via `gh-deploy` without strict, so validate changes with a plain `mkdocs build` (2026-09-06) - On a rerun of the monthly update, audit artifact existence first (Mailchimp campaign list, Drive title search, Canva deck page-1 thumbnail, Discord scheduled-events API) instead of walking the skill's from-scratch order (2026-09-06) - A speaker headshot that arrives as a Google Drive link: download it via the Drive MCP and commit it to `docs/assets/images/` so `mkdocs build --strict` and CI are not blocked; the pushed branch's `raw.githubusercontent.com` URL then doubles as the public source for the Canva `upload-asset-from-url` (a private Drive file is not fetchable by Canva) (2026-08-14) @@ -10,8 +12,6 @@ - `str.format(**ctx)` does not re-parse braces inside the substituted values, so a data field like `"Understanding {} in Python"` renders literally and never raises; verify any "format-injection crash" claim empirically before fixing (2026-08-14) - Squash-merged branches look "not in main" by commit ancestry (`merge-base --is-ancestor` fails) because their commits were rewritten; confirm merged-vs-stale by content: a diff dominated by deletions that re-adds older text means main is ahead and the branch is stale (2026-08-14) - When resolving an add/add conflict in a sops-encrypted file, take the side that is the plaintext superset (its MAC already covers the full key set) and verify by actually running `sops --decrypt`; a hand-stitched ciphertext would fail MAC validation (2026-08-14) -- Canva share/export links 403 to link checkers (bot-blocked, not broken); add `canva.com` to `.lycheeignore` rather than editing the reference doc (2026-08-14) -- Canva export URLs are signed and expire within hours; for Discord announcements attach the PNG to the webhook message (`files[0]` multipart) so the image outlives the link, and state the link's expiry next to it (2026-07-31) ## Secrets - `sops set file '["KEY"]' '"value"'` adds a key to a sops dotenv without a decrypt/edit/re-encrypt round trip (2026-07-31) @@ -24,6 +24,10 @@ - Don't verify an API write by GETting it back from the same API; it echoes your write. If the system of record is a UI (Mailchimp builder), the UI is the only honest check (2026-07-31) - Mailchimp Marketing keys end in `-usNN`; Transactional (Mandrill) keys are a separate product and format. The Transactional MCP rejects Marketing keys with "Invalid API key" (2026-07-31) +## Canva +- Canva share/export links 403 to link checkers (bot-blocked, not broken); add `canva.com` to `.lycheeignore` rather than editing the reference doc (2026-08-14) +- Canva export URLs are signed and expire within hours; for Discord announcements attach the PNG to the webhook message (`files[0]` multipart) so the image outlives the link, and state the link's expiry next to it (2026-07-31) + ## Workflow - meetup.com shows "0 upcoming events" to WebFetch because listings render client-side; the raw HTML's `__NEXT_DATA__` Apollo cache has full event data. Use `scripts/scrape_local_meetups.py` in the meetup-update skill, and check for embedded JSON before declaring any Next.js site unscrapeable (2026-07-31) - The meetup-update skill's `references/newsletter.md` is the canonical newsletter playbook; follow it over memory or improvisation (2026-07-31) diff --git a/.ai-sessions/session-20260906-1718-meetup-dedupe-resend.md b/.ai-sessions/session-20260906-1718-meetup-dedupe-resend.md new file mode 100644 index 0000000..3f87598 --- /dev/null +++ b/.ai-sessions/session-20260906-1718-meetup-dedupe-resend.md @@ -0,0 +1,50 @@ +# Session Summary: Meetup.com Dedupe and Announcement Resend + +**Date**: 2026-09-06 +**Duration**: ~45 minutes (same conversation as the October wrap-up) +**Conversation Turns**: ~15 in this stretch +**Estimated Cost**: moderate (GraphQL inspection plus webhook resends) +**Model**: Claude Fable 5 + +## Key Actions + +- Diagnosed duplicate October meetup.com events: two full network events existed (script-created 316447876, network 342e398b; dashboard-created 316448124, network f4f79e3f), each fanned out to all five network groups +- Mason chose to keep the dashboard event; deleted 316447876 via the deleteEvent mutation and confirmed its public page 404s +- Added a Meetup.com event line to both announcement templates in `build_announcements.py` (new required field `meetup_event_url`) at Mason's request +- Resent both Discord announcements (Mason had deleted the originals) with the surviving meetup.com link, merged PR status, fresh card export, and a trimmed Still-manual list +- Earlier in the conversation: sent the initial marketing and organizers announcements after Mason published both forms (a form-settings 401 had to be fixed first) and posted the website-updated follow-up after PR #55 merged + +## Prompt Inventory + +| Prompt/Command | Action Taken | Outcome | +|---|---|---| +| "Were the discord messages sent?" | Explained the deliberate hold on unpublished form links | Mason published the forms | +| "Read the google doc back" | Verified run of show; caught both responder links returning 401 | Mason fixed form audience; links went public | +| "Fixed, try again" | Verified 200s, rebuilt payloads, sent both announcements | Delivered with card attached | +| "I merged the PR... send the link" | Verified deploy live, posted website-updated follow-up | Done | +| "I feel there's duplicates" | GraphQL inspection found two parallel network events | Mason picked the keeper | +| "Resend the entire message with the updated meetup link" | Deleted the duplicate, added template line, resent both | Done | + +## Efficiency Insights + +**What went well:** +- Verifying responder links with an unauthenticated fetch before sending caught the restricted-audience default that would have shipped a login wall to Discord + +**What could improve:** +- The duplicate came from creating the network event both via the script and the pro dashboard in the same cycle; the organizers message should state loudly that the meetup.com draft already exists and only needs publishing + +**Course corrections:** +- "I deleted the old one" turned out to mean the Discord messages, not the meetup event; both events were still live until verified and deleted explicitly + +## Process Improvements + +- Before sending announcements, verify every link in the payload anonymously, not just the form links + +## Observations + +- Google Forms' new publish flow defaults responders to the org audience; forms.gle links return 401 until Responders is set to "Anyone with the link" +- The pro dashboard's network-events view does not list network events created through the API filterId path, which is how the duplicate went unnoticed + +## Suggested Skills for Next Session + +- `meetup-update`: November cycle; the outreach drafts for Sridhar Irujolla and Kriti Faujdar may have replies to process by then diff --git a/.claude/skills/meetup-update/scripts/build_announcements.py b/.claude/skills/meetup-update/scripts/build_announcements.py index b82e3a8..1ec4c54 100755 --- a/.claude/skills/meetup-update/scripts/build_announcements.py +++ b/.claude/skills/meetup-update/scripts/build_announcements.py @@ -57,6 +57,7 @@ "discord_event_url", "website_pr_url", "website_pr_status", + "meetup_event_url", ) MARKETING_TEMPLATE = """\ @@ -69,6 +70,7 @@ * [Attendance form]({attendance_url}) * Questions: in chat tonight * [Discord event]({discord_event_url}) +* [Meetup.com event]({meetup_event_url}) * [RSVP]({rsvp_url})""" ORGANIZERS_TEMPLATE = """\ @@ -77,6 +79,7 @@ * [Run of Show]({run_of_show_url}) * Card: [{deck_name}, page {deck_page}]({deck_url}); image attached, or [download the PNG]({card_download_url}) (link expires {card_expiry}) * [Discord event]({discord_event_url}) +* [Meetup.com event]({meetup_event_url}) * [Attendance form]({attendance_url}) * [Website PR]({website_pr_url}) ({website_pr_status})""" diff --git a/secrets/meetup.sops.env b/secrets/meetup.sops.env index 5494625..99d0495 100644 --- a/secrets/meetup.sops.env +++ b/secrets/meetup.sops.env @@ -3,11 +3,11 @@ PYTEXAS_MEETUP_WEBHOOK=ENC[AES256_GCM,data:o0eUcyJ8464OqJwnwJwfkBshaliya/9s62tRi MAILCHIMP_API_KEY=ENC[AES256_GCM,data:iZo1IvkKpsrv7vAhtu3E3DqFuTGe+nR4eTcwKEpZ1byqWbGsAQ==,iv:tai6OvFB2i26xMpx5vRqdL5Z0Tv5fngcDzw6B1QkLlI=,tag:qmh1lqlndVPbWp/bnsDH6g==,type:str] MEETUP_API_KEY=ENC[AES256_GCM,data:+2sPa/R+VSOtOHioho9c2cKq/3BDA5NSVXA=,iv:SnJCta7ujkSqFDZqkaSsr11pEK253ESqY/U6ePqIs34=,tag:oIUmg68ZjbucVKh6OTYnpg==,type:str] MEETUP_API_SECRET=ENC[AES256_GCM,data:idZPq0DEvxAlCg38YQ4vHUSGIFI40zZNgDk=,iv:OtAFPf6bZVJt/hOP0JyIeG1TDLnA09HZRdXWuu321ro=,tag:BkAHzj5jr1zQP/5puOntxg==,type:str] -MEETUP_REFRESH_TOKEN=ENC[AES256_GCM,data:JqHX5fKxW3rSBYIH8s+wvpqMoAGZ93lSHxa3RpwnfbJ1fPgNnWOdoMNmmUm9p5Ox20nrJakmHJrZmFaR8TwvnzzLcKzbZXp7rMPDOZiVkwKVCXn9nQcHYuXFXQCAd1qiEdgy7RCOLkU7bUogyTUuJ9xjxjhTxUqipux7QaKXw9dT8MpCLgF+GszbWdVFJNvX41CS13U7+jwuWc28HlTc2UXyW+Hu9/v5LZrYYIxpYjdup6dS0Q+UAgzHimRl+ZO+ONJ+bqA+z6e1ePHz2xvO4B1Rjgnr+71Ldymbh2Iq5gvWi2vBiuFMqIsJ+Kzv75gzKZn5wmRmLSk8QZ78GSpANd7Ye1C33JZY/zWioWGcpkYO26X44ZyKNDoOTB5EquGyki7s8S2Sb/k33HuqXV/hvAKHtDv3z2I0P9GyPgVR32uMPWwSTM4Mv8ZQmWTz+HyDU+hXGGXa0aFo+FYxBrjAM8wftRARzFDrHBBk17RSwCjMQQ69JMpKd309ZJa546BKkZdH48+nOGV+1LgwnClJKIPk20J8b/e6yquEfIMrxXglN7lHQXe65vLaHx2K0+qVmJOtZ0MexIShPifDMqILMt23FWkhUm0ztjvPIs0sB+KML3TMMM2BZ6SOs/tYpTg08HRnoQ5UkPNTYKbgSmdLull/oaZL72nkGG6I8twWISOzqS8zrkRIJte01DclbcTECePrJwnCTZKJ4Iu+L97mKZbqN1+IjuO2egRWi3Yv4NftAMAoH3Sh684m/pjSNVHJ8No6iVTkx5Iay4MC4EvupTDZ57PQNbchYBHqxpxoeMq492z2j73+h0kO3IoSRgEUydwdVw==,iv:sinQpTmcUq61pqpcAOAz+qKfPq3YPqYGER2yjd4czKQ=,tag:6R0+dd6EJE+Wb8VoNTDkyg==,type:str] +MEETUP_REFRESH_TOKEN=ENC[AES256_GCM,data:V+JPfZpJxi8oJMWIH6HqNgoS1IfwHku8A3qJA8KPo6TKVa2aNd2ckVevOsDYZFnu6J84O131oFGqXNuXaoxBqPMUlvEs3Wg5tuQvFGEWcIxJayvjgwEF7E1csJTI2770epM637/R9yY2lclXTEijRSBETgLYWHybdJep4jsmJihjy8BR5hX8T6HsWqCeXkWavTf9DS5DKBCmiQ1+wvnDsCMmqAAAk/ocbtFo6+TvI86IbwVsK5zkj/vJEwKm969X6NZZYvdIWEuBq3NmO4RuqBEsd0osdK6ikpAZ+Ck3RlNBxZy+PK2T7sNXMxtrU1oIKlkJ4jsafjgvTt2RdIHTUv3JwWzcM3CRsZVFJOq63DV0cXntvB9o70/XpJ5ZssII28X3sLzY3sgFqUBQD3LEDhC+E+ikCg7u2P8586cVzqxPrRI/S5T+eV3sUTa7OQSfN+mFzBbDaq6cmppjCC1tGFTA0TCQEN76iHDrsQwqSQyav0NMQLPl/L0MaNHk5xP0oihtUOLH9HJrWEbFMz9TSKpBPmGUW2fDTKEc3N/lHmtGoLoBihzmnBEG7QiIhD/2WGPctf6OOeN+D1S6bKF3DOt29z3b0dbxlXXBRtUsDw3J29dTOTi4rwJZuVmcc6EbdSNiPr0MGSj2rP8I+FDXEEC27lER3Q55zksqx/I5KPm2IvVLg+9hkrgbisBbNgBsnkF1zosxUcxAKys2d+Cqr+tlapNeuOTx5UsK6xjHE/Y7iQLz+JrxAbE2/DIIGQWA1smvyeYF2xf24U2TcKcgB5Z+rHL+dyFrO+cqukNX0ZUdinKxyfGyW/CejYSGAN13bsYbhA==,iv:kveheAFQQDAkiDbhK5FYcj2nAcQaJbbtq6jnjejuLCg=,tag:Bdr+KOvT1LvI8A3hCxdgtg==,type:str] PYTEXAS_DISCORD_BOT_TOKEN=ENC[AES256_GCM,data:F0JvIvALzRUF6jrnkhtUPEKYb7HXdpKTEcg3sKY3Xkxf2L9H3i5OSKfgyfE6upWjNdPQKZu4jqJaxKw709Wvdg+pMNFfR69B,iv:tN2srDHO9bCJEW+tc7nl4zDbKkcf7a0DE01nw6Jmq6g=,tag:JERscJ5CEEbdyBC3A2WNBw==,type:str] sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvTlczR3puNFJOa2dJN0hS\ncVp5Tzk1K1ZHMDNIZFJIZG5WM0ordW00MWg4CjFCdWJvWmlDcmlkQ05zZFFqQlVR\nN3RHQzkrbnRteHZYbFM0NzJ3Si9qQlEKLS0tIHIxLzluZ3JCa0dSWG13N2dlYTVn\nMFpycUNHZ3p6SXpzdFRvL3VxeDlOUmsKvgfZuOwDCq+z/q+4m+TVNF2sMKpKZ4zs\nTQS17ua+xa6rgRAk2eG3PBp7GRKAhnGUV5WfA9++TU2O3CKmb6RGww==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age17u5zkzgr4ca49sl9n42zk6aruq8d5xntnuxkhyjwvku80tx93afqm7a0fz -sops_lastmodified=2026-09-06T19:37:06Z -sops_mac=ENC[AES256_GCM,data:E1RSpTv75GzlAPj5mawz7qNNhwJu79M/ovrN3niV3qipgOIHsesvsXe1jpWod9JEOPNrXlbIk2C1V0jlL0Cn0WOBjFjMtqF7ES/lLeQ7BCAvtX7gEmf9NW9H8kSdROOKdZcsRJWP0Gb984mLEwxPirQ6oFP75hcga9/FQGPfNuo=,iv:i8O49khFw8Yd3kGj/88zS1YD3/D1JanBMMUHPC3WEfo=,tag:1bnoPDdVh39zXS6WiU98pA==,type:str] +sops_lastmodified=2026-09-06T22:17:11Z +sops_mac=ENC[AES256_GCM,data:qmjfb+lgwMd0glGOlFddayjeFk9kSL4YuftwI7BNkuFv241voqrGMNP3HPgrRkiRuD/LBtEqzySVP1/dPdVNzU7J+Ov+Yfw90TgoBfF6ZTeXpLfpGJaZykUlRsnP/hhTtoWxjj2bCHY2xK3zgIbh/lF4uOuJNmkOiYPs9rFoGlE=,iv:xcxeuN8DMEJ4Eq6GMvZkZrqsv0t+AtP1L+pzIbhCCIw=,tag:qkOShtLIYXwvUXhn8HrZ4g==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.13.1