# aim An **AIM-style buddy list for Bluesky**. Type your handle and see which of your mutuals are online right now — in real time, with the door chime when someone signs on. 🔗 **Live:** [aim.cee.wtf](https://aim.cee.wtf) No login, no password, no backend. It reads your *public* follows and followers, then watches the Bluesky firehose for what they do. Pure static HTML + ES modules — open it on any web server and it runs. ## What it does - **Presence, the honest way.** One filtered [Jetstream](https://github.com/bluesky-social/jetstream) firehose socket streams only your mutuals' events. Presence is *behavioral*: - 🟢 **online** — posted, replied or reposted within the window - 🟡 **idle** — only liked or followed (lurking) - ⚪ **offline** — quiet A door chime plays when a buddy flips to online. - **IM windows = your conversation.** Click a buddy to open an AIM-style chat log scoped to the two of you — their replies/likes/reposts/quotes/mentions of your stuff, and yours of theirs. Not their global feed; just the relationship. - **Deep history.** That window also pulls *both* full repos (`com.atproto.sync.getRepo`) in the background and decodes them, surfacing interactions going back years — including **likes**, which no public API otherwise exposes (and your own likes, normally auth-gated). - **"Last seen 3d ago"** for dormant buddies, derived from each repo's latest-commit `rev` (a TID that timestamps their last write of any kind). - **Door chimes** are synthesized in WebAudio — original sounds, not AIM's copyrighted samples. ## Run it ES modules must be served over HTTP (not `file://`). Any static server works: ```bash python3 -m http.server 8137 # then open http://localhost:8137 ``` No build step, no dependencies, no API keys. ## How it works (atproto notes) Everything hits public, CORS-enabled, unauthenticated endpoints: | What | Endpoint | |------|----------| | Handle → DID, profiles, follows/followers, post text | `public.api.bsky.app` (AppView XRPC) | | Live presence + activity | `jetstream*.bsky.network` (firehose websocket) | | Full-history interactions incl. likes | `com.atproto.sync.getRepo` (PDS, CARv1 + DAG-CBOR) | | "Last seen" for dormant accounts | `com.atproto.sync.getLatestCommit` `rev` (relay) | | DID → PDS resolution | `plc.directory` | Two details worth stealing: - **`wantedDids` goes in a hello message, not the URL.** Hundreds of DIDs as query params blow past the WebSocket handshake URL-length limit and the server refuses the connection. Connect with `?requireHello=true` and send `{type:'options_update', payload:{wantedDids:[...]}}` on open. See `jetstream.js`. - **Likes live only in the repo.** They're absent from feeds and `getActorLikes` is auth-gated, so the only way to see who-liked-what across history is to decode the CAR. See `atproto/` + `deep-history.js`. ## Layout ``` index.html markup + Win98/AIM chrome main.js orchestrator: state map, firehose connection, DOM wiring jetstream.js filtered firehose client (hello-message wantedDids, cursor replay) status.js presence taxonomy (online/idle/offline) buddy-list.js incremental list rendering (reused rows — no avatar flicker) chat.js IM window lifecycle (fast pass + deep pass + live updates) chat-actions.js raw event/record -> normalized entry (with interaction targets) chat-render.js entry -> IM line HTML deep-history.js full-repo download + decode -> interaction entries last-seen.js "last seen" backfill via repo rev (TID decode) sound.js WebAudio door open/close chimes about.js the (?) window drag.js draggable windows util.js esc / relTime / postUrl / TID helpers api.js thin atproto AppView client typeahead.js handle autocomplete atproto/ CARv1 + DAG-CBOR + MST decoder (repo parsing) style.css the teal desktop ``` ## License MIT — see [LICENSE](LICENSE). --- a [cee.wtf](https://cee.wtf) thing