(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)))