• v2.1.0 0383c884c6

    v2.1.0
    All checks were successful
    CI / build-and-test (push) Successful in 2m17s
    CI / docker-build (push) Successful in 29s
    Stable

    joeyr released this 2026-09-26 11:08:13 -05:00 | 46 commits to main since this release

    Added

    • Terror Zone alerts delete themselves when the zone rotates out. The
      channel shows only the current zone, not a stack of old ones whose "Next
      Rotation" reads "3 hours ago". An alert that pinged too many people for one
      message is deleted in full. DClone and walk alerts stay up.
      • A restart doesn't leave any behind. Each alert is saved in a new
        d2emu_tz_alerts table (migration 012, applied at boot) with the time its
        rotation ends. The first poll after a restart deletes the ones that ended
        while the bot was down.
      • A failed delete is retried every poll for two hours past the rotation,
        then given up on with a warn (d2emu/poll). One someone already deleted
        by hand is just forgotten.
    Downloads
  • v2.0.1 b7f1ce9348

    v2.0.1
    All checks were successful
    CI / build-and-test (push) Successful in 2m20s
    CI / docker-build (push) Successful in 30s
    Stable

    joeyr released this 2026-09-25 17:19:32 -05:00 | 47 commits to main since this release

    Fixed

    • Hosted-walk alerts read far more Clone Hunters posts. Checked against
      every one of their posts this server has received (149, April to
      September), 2.0.0 could read only 62. It now reads 113:

      • "Region: NA" reads as USA. It's the word they use most, including on
        the weekly softcore non-ladder walks, and 2.0.0 didn't know it.
      • "Region(s):" counts as a Region line.
      • Every Region line counts, so a post announcing a USA walk and an EU
        walk pings both.
      • Follow-ups with no Region line ("Walk NOW EU", "ASIA ROTW SCL NOW GET
        IN A GAME", "bonus walk … on Asia") have their region read from the rest
        of the text. The short codes (NA, EU, KR) only count in capitals, so
        ordinary prose isn't read as a region.

      The other 36 name no region ("Region: TBD", "We are done for the day",
      ladder-reset notices) and still ping nobody.

    Changed

    • The walk reply is worded as a post, not a walk: "Clone Hunters post for
      ROTW SC Ladder in EU", plus the post's times. It sits right under a
      post that may just as well be a reminder, a time change or a cancellation.
    • A tagged post that names no region logs at info, not warn. That's
      ordinary. The warn now means something the catalog may be missing: no
      known tag, or a Region line with no word it knows.
    Downloads
  • v2.0.0 f7f0ebdf7c

    v2.0.0
    All checks were successful
    CI / build-and-test (push) Successful in 2m19s
    CI / docker-build (push) Successful in 29s
    Stable

    joeyr released this 2026-09-25 09:09:28 -05:00 | 48 commits to main since this release

    D2 alerts now go only to the people who signed up for them. This is a major
    release because four things existing users rely on change. After deploying,
    run /aufadmin d2 signup-board and pin the message it posts.

    Changed

    • Terror Zone alerts stop until someone signs up. Every rotation used to
      post for everyone in the channel. Now each person picks their zones with
      /aufhocker d2 tz-alerts, and a rotation pings only the people who picked
      that zone. If nobody in the server picked it, the bot posts nothing.
    • DClone alerts stop until someone signs up. /aufhocker d2 dclone-alerts
      picks game modes (softcore or hardcore, ladder or non-ladder) and regions
      (USA, EU, KR). A progress change pings the people who picked that game's
      mode and its region, and the post lists only the games someone in the
      server signed up for.
    • /aufhocker d2 channel is now /aufadmin d2 notifications-channel, with
      the same option and the same view-or-set behaviour. The D2 admin commands
      now live together under /aufadmin, which only admins can see.
    • D2EMU_TZ_NOTIFY_AREA_IDS no longer does anything. It was one zone list
      for the whole server, and sign-ups replace it. Nothing carries over from
      it, so everyone signs up fresh. If it's still set, the bot logs a warning at
      boot; remove it from the stack file.

    Added

    • Sign-up menus. tz-alerts shows one dropdown per act, and
      dclone-alerts and walk-alerts show a mode dropdown and a region
      dropdown. Each menu is private, opens with your current picks ticked, and
      saves the moment a dropdown changes. For DClone and walks, it warns you when
      modes or regions are empty, because a ping needs both. Untick everything to
      stop. Sign-ups are per server.
    • A sign-up board. /aufadmin d2 signup-board posts one message in the D2
      channel with a button for each menu. It's the same message for everyone and
      never shows anyone's picks, and its buttons keep working after a restart.
    • Hardcore and non-ladder DClone. The bot now tracks all twelve Reign of
      the Warlock games instead of the three softcore ladder ones, at no extra API
      cost, since they all come back in the one response. /aufhocker d2 dclone
      lists all twelve.
    • Hosted-walk alerts. When D2R Clone Hunters announce a walk in their
      #announcements, which this server follows, the bot replies under the
      copied post and pings the people signed up for that mode and region with
      /aufhocker d2 walk-alerts. The copied post's own role mention arrives as
      plain text and never pinged anyone. A post the bot can't read pings nobody
      and logs a warning with its text. Walk alerts are on when
      D2EMU_WALK_SOURCE_CHANNEL_ID is set (Clone Hunters' #announcements,
      895727392092459049, in .env.config). Leave it empty and they're off.
    • Tip: set the D2 channel to Only @mentions. d2 help, the menus and the
      board all say so. From then on, the only D2 alerts that reach you are the
      ones you picked.
    • The d2emu_subscriptions table (migration 011), and the d2emu/subs and
      d2emu/walks log phases.
    • Plugin contract: buildAdminCommands / handlesAdminSlash /
      handleAdminSlash let a plugin add one group to /aufadmin.
      handleSelectMenu routes string select menus the way handleButton routes
      buttons, by componentPrefix (renamed from buttonPrefix). onCrosspost
      hands plugins messages copied in from a followed channel, which the context
      store never sees. See docs/PLUGINS.md.

    Fixed

    • A DClone walk no longer swallows another game's progress. When one game
      walked and another moved in the same poll, only the walk was posted. Now
      both are. With twelve games tracked, that happens often.
    Downloads
  • v1.23.0 b71000543d

    v1.23.0
    All checks were successful
    CI / build-and-test (push) Successful in 2m29s
    CI / docker-build (push) Successful in 43s
    Stable

    joeyr released this 2026-09-24 23:36:51 -05:00 | 49 commits to main since this release

    Added

    • Follow-up questions remember the conversation. /aufhocker query-chat
      and /aufhocker espnff mcp used to start from nothing on every question, so
      "what about next week?" had no idea what came before it. Now a question
      asked within an hour of your previous one is sent with the questions and
      answers before it. Each person gets their own conversation, per channel and
      per command. new:True on either command starts over, and so does an hour
      of silence or a conversation reaching 20 questions (every follow-up re-sends
      the whole thing, so it cannot grow forever).

      A follow-up adds only what was said in the channel since the previous
      question, rather than re-reading the last hour. Passing context: on a
      follow-up still pulls that window, because asking for it is asking for it.

      Conversations live in a new conversation_turns table and are deleted as a
      whole once their last question is older than the memory window. Set
      CONVERSATION_MEMORY_MINUTES in .env.config (default 60; 0 turns
      memory off and brings back the old behaviour).

    • Follow-ups are cached, so they stay cheap. Each conversation request
      marks the end of the new question as a prompt-cache breakpoint, so the next
      follow-up reads every earlier turn at about a tenth of the normal input
      price. The cache lasts PROMPT_CACHE_TTL (default 1h, matched to the
      memory window; 5m is the other option). Saving to the 1-hour cache costs
      twice the normal input price once, so a question that never gets a
      follow-up costs a little more than before, and every follow-up costs much
      less. The cache only hits if the replayed turns are byte-for-byte what was
      sent before, so each turn is stored exactly as it went out. The
      claude/api success line now logs cacheReadInputTokens, the number that
      shows whether it is working.

    • Public answers become shared context. Pressing 📢 Make Public now also
      records the answer, and every public answer in a channel is context for
      everyone's next question there. query-chat sees them interleaved with the
      chat. espnff mcp sees them and nothing else: it still never reads the
      chat, and its prompt treats them as possibly out of date, with the fantasy
      tools as the source of truth. Private answers never leave the asker's own
      conversation. Public answers sit in a new public_answers table and follow
      RETENTION_DAYS like chat. A long answer is trimmed to 1,500 characters in
      context so one answer cannot crowd out the rest of the history.

    • /aufhocker help, plus /aufhocker lol help and /aufhocker d2 help
      alongside the existing espnff help. There was no way to find out what the
      bot could do from inside Discord except typing /aufhocker and reading
      one-line descriptions. The general help lists every command that is turned
      on (lol and d2 only appear when enabled) and points at each group's own
      help. The group helps list their commands, show admin-only commands only to
      admins, and answer even while the feature is off, saying so, since every
      other command in the group refuses then. espnff help now also mentions
      follow-ups and that it reads public answers.

    Changed

    • README corrected. The usage section still showed /aufhocker query:<question>, which has not been the command for a long time (it is
      /aufhocker query-chat query:<question>), and did not mention espnff at
      all. Both are fixed, and follow-ups, public answers, help and the two new
      settings are documented.
    Downloads
  • v1.22.0 6979d34359

    v1.22.0
    All checks were successful
    CI / build-and-test (push) Successful in 2m10s
    CI / docker-build (push) Successful in 28s
    Stable

    joeyr released this 2026-09-17 15:34:57 -05:00 | 50 commits to main since this release

    Added

    • /aufhocker d2 tz and /aufhocker d2 dclone now offer a "📢 Make Public" button, the same one the Claude commands have had. Both answers still arrive as a whisper, so checking the Terror Zone or DClone progress never spams a channel by itself, but the person who ran the command can now put the embed in the channel with one click instead of screenshotting it.

      The rules match the query plugin's flow exactly, because they are the security-sensitive part: only the user who ran the command may publish its answer, and a pending answer expires after an hour so a stale whisper cannot be dropped into a channel long after the numbers it quotes went out of date. Publishing clears the whisper, so the embed is never on screen twice with a live button on the stale copy.

      Error embeds get no button. There is nothing worth sharing in "the API is down", and a button on it is one more way to spam a channel.

    Downloads
  • v1.21.0 5360ec731b

    v1.21.0 — a missed MCP server is retried
    All checks were successful
    CI / build-and-test (push) Successful in 2m8s
    CI / docker-build (push) Successful in 37s
    Stable

    joeyr released this 2026-09-16 13:35:29 -05:00 | 51 commits to main since this release

    Changed

    • A missed MCP server is retried instead of given up on. The bot asks
      every server in MCP_SERVERS for its tools once, at startup, and a server
      that did not answer was then missing for the life of the process. The case
      that hits is the cheap one: the bot and the espnff server are containers
      on the same host and they start together, so the bot can lose the race by a
      few seconds and then run for days with /aufhocker espnff mcp refusing
      every question. Nothing in Discord announced it — the first sign was a
      fantasy question refused, and the fix was a restart nobody knew to do.

      A server that misses the boot is now retried in the background: after a
      minute, then doubling to a two-hour ceiling. Each reconnect registers that
      server's tools on the live tool executor, so the fantasy command starts
      working mid-process and /aufhocker espnff help flips from "not connected"
      on its own. Both messages now say to wait rather than to restart.

      Backoff rather than a fixed interval because the two failures underneath
      have different lengths: a co-boot race is over inside a minute, while a
      server that is genuinely down is down for hours and polling it every minute
      buys nothing. Only the servers that are actually missing are retried — a
      session that connected is held open as before, since re-handshaking a
      working server would drop its tools mid-flight. Shutdown cancels the timer.

    Downloads
  • v1.20.0 54d021a934

    v1.20.0
    All checks were successful
    CI / build-and-test (push) Successful in 2m9s
    CI / docker-build (push) Successful in 30s
    Stable

    joeyr released this 2026-09-13 16:17:16 -05:00 | 53 commits to main since this release

    Changed

    • Remade games no longer count toward prediction accuracy. A game that
      ends in the first five minutes is a remake: someone failed to connect and
      the team voted it dead. Riot still records it as a loss for all ten players,
      so every remake previously landed in the accuracy record as a real outcome,
      scoring a prediction against a game nobody played.

      The end-of-game embed now says 🔄 Remake with the duration and nothing
      else: no KDA, no CS/min, no vision, no prediction verdict, no running
      record. The prediction row is kept and flagged remake, which leaves it
      visible in the history while actual_winner stays NULL, and NULL is what
      keeps it out of /aufhocker lol predictions and the per-game record line.

      Detection prefers Riot's own gameEndedInEarlySurrender flag and falls back
      to the five-minute duration threshold when it is absent. A zero duration is
      Riot saying "no data", not a zero-second game, so it is never treated as a
      remake.

      The same filter applies to the recent champion W/L shown on the pre-game
      embed and fed to the prediction prompt, which was counting remakes as
      losses and dragging the record it reported on both.

      Migration 009 adds the flag and backfills it: predictions already scored
      against a sub-five-minute game (identifiable because lol_active_games
      keeps the match result as JSONB and is never pruned) are un-scored, so the
      displayed accuracy corrects itself on deploy.

    Downloads
  • v1.19.0 863a90e510

    v1.19.0 — mentions resolve to fantasy teams
    All checks were successful
    CI / build-and-test (push) Successful in 2m13s
    CI / docker-build (push) Successful in 46s
    Stable

    joeyr released this 2026-09-10 22:25:44 -05:00 | 54 commits to main since this release

    Fixed

    • /aufhocker espnff mcp can resolve a Discord mention to a team. Asking
      "how is @Someone's team doing?" got back "I can't resolve a Discord mention
      to an ESPN team on my own" — true, and useless, because the bot had the
      mapping and wasn't passing it on.

      The prompt carried only the asker's own team id. It now carries every mapped
      manager as <@id> (Name) manages team_id N, joining the two halves that are
      configured separately and meet nowhere else: ESPNFF_TEAM_MAP supplies the
      team id, user-mappings.json supplies the name. A manager with no name entry
      is still listed — unnamed and resolvable beats named and absent.

      Worth knowing why it could not work before: a mention arrives in the query as
      raw markup (<@790374074235879454>), not a display name; the fantasy server
      knows only ESPN display names; and user-mappings.json feeds channel
      history, which this command deliberately never sends. Nothing in that chain
      connected a snowflake to a team.

      An unmapped id still cannot be placed, and the model is told to ask which
      team is meant rather than pick one — a plausible wrong team is the failure
      that reads exactly like an answer.

    Downloads
  • v1.18.1 b9d83663a5

    v1.18.1 — make-public clears the whisper
    All checks were successful
    CI / build-and-test (push) Successful in 3m11s
    CI / docker-build (push) Successful in 56s
    Stable

    joeyr released this 2026-09-10 22:21:01 -05:00 | 55 commits to main since this release

    Fixed

    • "Make Public" now removes the whisper it published. Previously the
      ephemeral copy stayed on screen, so the same answer appeared twice — and the
      stale copy kept a live "Make Public" button on it, inviting a second post of
      the same thing. A third ephemeral message ("Response has been made public!")
      sat below both; it is gone, because the public message is its own
      acknowledgement.

      A split answer has its ephemeral follow-ups cleared too, not just the message
      carrying the button.

      Deletion is best-effort by necessity: an interaction token lasts 15 minutes
      and a pending response lasts an hour, so a late click cannot delete anything.
      The fallback collapses the whisper to one line with no button, which is the
      part that matters.

    Downloads
  • v1.18.0 1d55b6dd06

    v1.18.0 — fantasy tools stay out of general chat
    Some checks failed
    CI / docker-build (push) Has been cancelled
    CI / build-and-test (push) Has been cancelled
    Stable

    joeyr released this 2026-09-10 22:16:14 -05:00 | 56 commits to main since this release

    Changed

    • The fantasy tools are no longer offered to /aufhocker query-chat. They
      reach Claude only through the command that names them — today,
      /aufhocker espnff mcp.

      Borrowed tools used to be appended to ClaudeClient's default set, which
      made general chat a second, undocumented way into the league data: no command
      asked for it, nothing in Discord said it was happening, and ten
      league-specific tools sat in the menu for every unrelated question. The
      default set is now AVAILABLE_TOOLS and nothing else.

      The executor still executes borrowed tools — it is simply no longer asked
      what it holds. ToolExecutor.getDynamicTools() is gone from the interface
      ClaudeClient consumes; advertising is the calling command's job.

      The boot line now names who gets what:
      chatTools=web_search,web_fetch espnffTools=get_league_info,…


    CI note: this commit's build-and-test run was cancelled when v1.18.1 was pushed on top of it minutes later. Its changes were verified by the full local gate and shipped in the v1.18.1 image, whose CI passed.

    Downloads