nandi/frqpublic Fork 0
972ab37b824749b9600a6c2e866442e2dcc64d16
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Answer to every name a line has been given, and ask for the one we lack 6908605 · on 972ab37b824749b9600a6c2e866442e2dcc64d16 · nandi · 5d ago
replies.cljc · 70 lines · 3.1 KBGDScript3 Blame HistoryRaw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
(ns frq.replies
  "Finding the line a reply points at when the buffer does not hold that name.

  A message keeps the id it was born with through every revision, and the
  server gives each revision a msgid of its own  so an answer to a line that
  has already been rewritten names the revision (see `frq.edits`). While this
  client is connected it watches that happen and records both names. A backlog
  is the case it cannot watch: replay sends one collapsed row per message,
  carrying the surviving msgid, the final text and `+freeq.at/edited=1`, and
  the revision's own msgid never crosses the wire at all. Nothing on the line
  says which other names it used to answer to.

  freeq knows, though. `GET /api/v1/messages/{msgid}` answers with
  `replaces_msgid`  the id the revision collapsed into  so one question per
  unresolved reply is enough to tie the two together. The answer is written
  onto the message as another name it answers to, which is the same place a
  live edit puts it, so `frq.rooms/answers-to?` is still the only rule about
  what names a line.

  Asked once per id and never again: `asked` remembers every id this session
  has put the question to, answer or no answer. A chip that cannot be resolved
  is a chip that stays as it is  the screen is redrawn constantly and a miss
  that re-asked would be a request per frame."
  (:require [frq.atproto.core :as atproto]
            [frq.cells :as cells]
            [frq.io :as io]
            [frq.rooms :as rooms]))

(defonce ^:private asked (atom #{}))

(defn forget-asks!
  "Drop what has been asked, for a client signing in again — a new session has
  a new bearer, and an id that failed under the old one deserves another go."
  []
  (reset! asked #{}))

(defn- learn!
  "Record `id` as another name for the message `holds` names, if we hold it."
  [channel id holds]
  (when (and channel id holds)
    (swap! cells/channels
           (fn [chans]
             (if-let [msgs (get-in chans [channel :messages])]
               (assoc-in chans [channel :messages]
                         (mapv (fn [m]
                                 (if (rooms/answers-to? m holds)
                                   (update m :edit-ids (fnil conj #{}) id)
                                   m))
                               msgs))
               chans)))))

(defn resolve!
  "Ask freeq what `id` was, and tie it to the line it collapsed into.

  Does nothing without a bearer  the endpoint refuses an anonymous read of a
  channel you are in  and nothing for an id already asked about. `on-change`
  is called only when something was actually learned, so a caller can repaint
  on it without repainting on every miss."
  [channel id on-change]
  (when (and channel id (not (contains? @asked id)))
    (when-let [bearer (some-> @cells/api-bearer not-empty)]
      (swap! asked conj id)
      (io/fetch-text!
       (str "https://" @cells/form-host "/api/v1/messages/" id)
       {"Authorization" (str "Bearer " bearer)
        "Accept" "application/json"}
       (fn [body]
         (when-let [holds (some-> body (atproto/json-str "replaces_msgid"))]
           (learn! channel id holds)
           (when on-change (on-change))))))))