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
(defmethod send ((message message) &key users)
  "Send MESSAGE to USERS."
  (loop :with title = (title message)
        :with body = (body message)
        :with first-request? = t
        :with progress = 0
        :with total = (length users)
        :for user :in users
        :for uri = (quri:make-uri :scheme "https" :host "..." :path "..."
                                  :query `(("recipient" . ,user) ("title" . ,title) ("body" . ,body)))
        :do (if (not first-request?)
                (sleep 3)
                (setf first-request? nil))
            (restart-case
                (multiple-value-bind (body status)
                    (dex:post uri :headers *headers*) ;; (ciao:headers *oauth-obj*)
                  (declare (ignore body))
                  (case status
                    (200
                     (setf *rate-limit-timestamp* nil)
                     (incf progress)
                     (format *debug-io* "[~A] Sent to ~S (~A/~A) ~%" (timestamp) user progress total))))
              (report-400 ()
                (format *debug-io* "[~A] Failed to send to ~S: check username? (~A/~A) ~%"
                        (timestamp) user progress total))
              (wait-and-retry (c)
                (let ((60-minutes (* 60 60))
                      wait-duration)
                  (typecase c
                    (dex:http-request-too-many-requests
                     (format *debug-io*
                             "[~A] Failed to send to ~S: 429 (Too Many Requests)~%"
                             (timestamp)
                             user)))
                  (if *rate-limit-timestamp*
                      ;; There's an existing timestamp
                      (let ((time-elapsed (- (lt:timestamp-to-unix (lt:now))
                                             (lt:timestamp-to-unix *rate-limit-timestamp*))))
                        (if (< time-elapsed 60-minutes)
                            ;; It hasn't been an hour yet - retry when it has been
                            (setf wait-duration (- 60-minutes time-elapsed))
                            ;; It has been an hour or more - retry now
                            (setf wait-duration 1)))
                      ;; There's no existing timestamp - wait an hour
                      (setf *rate-limit-timestamp*  (lt:now)
                            wait-duration  60-minutes))
                  (format *debug-io* "Will try after~A~%"
                          (ltd:human-readable-duration (ltd:duration :sec wait-duration)))
                  (sleep wait-duration)
                  ;; First I tried this -
                  ;; (dex:retry-request 1)

                  ;; and then this -
                  ;; (dex:retry-request c)
                  ;; ...but they both failed silently.

                  ;; And this can't find the restart...
                  ;; (invoke-restart 'dex:retry-request)
                  )))
        :finally (format *debug-io* "Finished (~A/~A)~%" progress total)))