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