Ver Fonte

[CORE] Refactor free vars in macros.

Innocenty Enikeew há 6 anos atrás
pai
commit
10cad2f9fe

+ 11 - 13
bot.lisp

@@ -39,34 +39,32 @@
 
 (defvar *start-message* "Hello" "Welcome message. Override it")
 (def-message-cmd-handler handle-start (:start)
-  (bot-send-message chat-id *start-message*))
+  (bot-send-message *start-message*))
 
 (def-message-admin-cmd-handler handle-admin-settings (:settings)
-  (bot-send-message chat-id
-                    (format nil "~{~{~A~@[ (~A)~]: ~A~}~^~%~}"
+  (bot-send-message (format nil "~{~{~A~@[ (~A)~]: ~A~}~^~%~}"
                             (loop for symbol in *settings*
                                collect (list symbol (documentation symbol 'variable) (symbol-value symbol))))))
 
 (def-message-admin-cmd-handler handle-admin-setsetting (:setsetting)
   (let* ((*package* (find-package :chatikbot))
-         (var (read-from-string (car args)))
-         (val (read-from-string (format nil "~{~A~^ ~}" (rest args)))))
-    (bot-send-message chat-id (format nil "OK, ~A" (set-setting var val)))))
+         (var (read-from-string (car *args*)))
+         (val (read-from-string (format nil "~{~A~^ ~}" (rest *args*)))))
+    (bot-send-message (format nil "OK, ~A" (set-setting var val)))))
 
 (defvar *chat-next-message-handlers* (make-hash-table) "Out-of-order chat message handler")
-(defmacro on-next-message (chat-id &body body)
-  `(setf (gethash ,chat-id *chat-next-message-handlers*)
+(defmacro on-next-message (&body body)
+  `(setf (gethash *chat-id* *chat-next-message-handlers*)
          (lambda (message)
            (with-parsed-message message
              ,@body))))
 
 (def-message-handler chat-next-message-handler (message 1000)
-  (let ((handler (gethash chat-id *chat-next-message-handlers*)))
+  (let ((handler (gethash *chat-id* *chat-next-message-handlers*)))
     (when handler
-      (remhash chat-id *chat-next-message-handlers*)
+      (remhash *chat-id* *chat-next-message-handlers*)
       (handler-case (funcall handler message)
         (error (e)
           (log:error "~A" e)
-          (bot-send-message chat-id
-                            (format nil "Ошибочка вышла~@[: ~A~]"
-                                    (when (member chat-id *admins*) e))))))))
+          (bot-send-message (format nil "Ошибочка вышла~@[: ~A~]"
+                                    (when (is-admin) e))))))))

+ 1 - 0
chatikbot.asd

@@ -35,6 +35,7 @@
                (:file "macros")
                (:file "bot")
                (:file "inline")
+               (:file "poller")
                (:file "chat-cron")
                (:file "server")
                (:file "common")

+ 50 - 28
common.lisp

@@ -15,20 +15,47 @@
            :db-execute
            :db-select
            :db-single
+           :db-init
+           :load-settings
            :set-setting
            :lists-set-entry
            :lists-get
+
+           :*message-id*
+           :*from-id*
+           :*chat-id*
+           :*text*
+           :*message*
+           :*cmd*
+           :*args*
+           :*query-id*
+           :*from*
+           :*raw-data*
+           :*inline-message-id*
+           :*source-message*
+           :*source-chat-id*
+           :*source-message-id*
+           :*callback*
+           :*data*
+           :*section*
+           :*code*
+           :*error*
+           :*raw-state*
+           :*state*
            :*admins*
            :*bot-name*
+           :*hooks*
            :+hour+
            :+day+
+           :*chat-default-timezone*
+           :run-hooks
            :add-hook
            :remove-hook
+           :is-admin
            :keyify
            :dekeyify
            :*settings*
            :defsetting
-           :*chat-default-timezone*
            :*backoff-start*
            :*backoff-max*
            :loop-with-error-backoff
@@ -37,6 +64,9 @@
            :agets
            :agetter
            :preprocess-input
+           :punctuation-p
+           :read-from-string-no-punct
+           :print-with-spaces
            :spaced
            :text-chunks
            :http-request
@@ -52,6 +82,7 @@
            :plist-hash
            :plist-json
            :format-ts
+           :parse-cmd
            :parse-float
            :smart-f
            :format-size
@@ -62,7 +93,13 @@
            :same-time-in-chat
            :group-by
            :pmapcar
+           :filled
+
+           :*telegram-token*
+           :+telegram-max-callback-data-length+
+           :telegram-get-updates
            :telegram-get-me
+           :telegram-set-webhook
            :telegram-send-message
            :telegram-forward-message
            :telegram-send-photo
@@ -92,60 +129,45 @@
            :telegram-reply-keyboard-hide
            :telegram-force-reply
            :bot-send-message
+
            :token-hmac
            :encode-callback-data
            :decode-callback-data
            :encode-oauth-state
            :decode-oauth-state
+
+           :*secret-ring*
+           :*secret-pass-store*
+           :*secret-pass-bin*
            :secret-get
            :secret-set
            :secret-del
            :secret-wipe
            :with-secret
-           :def-db-init
+
            :def-webhook-handler
            :get-webhook-url
            :get-oauth-url
-           :hook
-           :data
-           :headers
-           :paths
+
            :def-db-init
+           :with-parsed-message
            :def-message-handler
-           :message-id
-           :from-id
-           :chat-id
-           :text
            :def-message-cmd-handler
            :def-message-admin-cmd-handler
-           :cmd
-           :args
+           :with-parsed-callback
            :def-callback-handler
-           :callback
-           :query-id
-           :from
-           :raw-data
-           :message
-           :inline-message-id
-           :source-message
-           :source-message-id
-           :source-chat-id
            :def-callback-section-handler
-           :data
-           :section
            :def-oauth-handler
            :def-oauth-section-handler
-           :code
-           :error
-           :raw-state
-           :state
            :with-random-state
            :defcron
-           :on-next-message
+
            :get-inline-keyboard
            :inline-button
+
            :add-chat-cron
            :get-chat-crons
            :delete-chat-cron
            :def-chat-cron-handler))
+
 (in-package :chatikbot.common)

+ 8 - 8
inline.lisp

@@ -7,7 +7,7 @@
 (in-package :chatikbot.inline)
 
 (defstruct button callback created)
-(defparameter +max-id+ 2000000000 "Buttons store key range")
+(defparameter +max-id+ 2000000 "Buttons store key range")
 (defparameter +max-tries+ 100 "Tries to find free store key")
 (defparameter +ttl+ (* 60 60 24 7) "Weekly TTL for buttons in store")
 
@@ -46,11 +46,11 @@
                (error (e)
                  (log:error "~A" e)
                  (telegram-answer-callback-query
-                  query-id
+                  *query-id*
                   :text (format nil "Ошибочка вышла~@[: ~A~]"
-                                (when (member source-chat-id *admins*) e)))))))))
+                                (when (is-admin *source-chat-id*) e)))))))))
 
-(defun get-inline-keyboard (chat-id buttons)
+(defun get-inline-keyboard (buttons &optional (chat-id *chat-id*))
   (telegram-inline-keyboard-markup
    (loop for row in buttons
       when row
@@ -63,9 +63,9 @@
                            chat-id :inline (add-button callback) +ttl+))))))
 
 (def-callback-section-handler cb-handle-inline (:inline)
-  (let ((button (gethash (parse-integer data) *inline-buttons*)))
+  (let ((button (gethash (parse-integer *data*) *inline-buttons*)))
     (if button
-        (funcall (button-callback button) callback)
+        (funcall (button-callback button) *callback*)
         (progn
-          (log:error "Can't find button id ~A" data)
-          (telegram-answer-callback-query query-id :text "Ошибка")))))
+          (log:error "Can't find button id ~A" *data*)
+          (telegram-answer-callback-query *query-id* :text "Ошибка")))))

+ 39 - 43
macros.lisp

@@ -22,11 +22,10 @@
                           (values))))
 
 (defmacro with-parsed-message (message &body body)
-  `(let ((message-id (agets ,message "message_id"))
-         (from-id (agets ,message "from" "id"))
-         (chat-id (agets ,message "chat" "id"))
-         (text (agets ,message "text")))
-     (declare (ignorable message-id from-id chat-id text))
+  `(let ((*message-id* (agets ,message "message_id"))
+         (*from-id* (agets ,message "from" "id"))
+         (*chat-id* (agets ,message "chat" "id"))
+         (*text* (agets ,message "text")))
      ,@body))
 
 (defmacro def-message-handler (name (message &optional prio) &body body)
@@ -36,42 +35,39 @@
          (handler-case (progn ,@body)
            (error (e)
              (log:error "~A" e)
-             (bot-send-message chat-id
-                               (format nil "Ошибочка вышла~@[: ~A~]"
-                                       (when (member chat-id *admins*) e)))))))
+             (bot-send-message (format nil "Ошибочка вышла~@[: ~A~]"
+                                       (when (is-admin) e)))))))
      (when ,prio (setf (get ',name :prio) ,prio))
      (add-hook :update-message ',name)))
 
 (defmacro def-message-cmd-handler (name (&rest commands) &body body)
-  `(def-message-handler ,name (message)
-     (when (and text (equal #\/ (char text 0)))
-       (multiple-value-bind (cmd args) (parse-cmd text)
-         (when (member cmd (list ,@commands))
-           (log:info cmd message-id chat-id from-id args)
+  `(def-message-handler ,name (*message*)
+     (when (and *text* (equal #\/ (char *text* 0)))
+       (multiple-value-bind (*cmd* *args*) (parse-cmd *text*)
+         (when (member *cmd* (list ,@commands))
+           (log:info *cmd* *message-id* *chat-id* *from-id* *args*)
            ,@body
            t)))))
 
 (defmacro def-message-admin-cmd-handler (name (&rest commands) &body body)
-  `(def-message-handler ,name (message)
-     (when (and (member chat-id *admins*)
-                text (equal #\/ (char text 0)))
-       (multiple-value-bind (cmd args) (parse-cmd text)
-         (when (member cmd (list ,@commands))
-           (log:info cmd message-id chat-id from-id args)
+  `(def-message-handler ,name (*message*)
+     (when (and (is-admin)
+                *text* (equal #\/ (char *text* 0)))
+       (multiple-value-bind (*cmd* *args*) (parse-cmd *text*)
+         (when (member *cmd* (list ,@commands))
+           (log:info *cmd* *message-id* *chat-id* *from-id* *args*)
            ,@body
            t)))))
 
 (defmacro with-parsed-callback (callback &body body)
-  `(let* ((query-id (agets ,callback "id"))
-          (from (agets ,callback "from"))
-          (raw-data (agets ,callback "data"))
-          (source-message (agets ,callback "message"))
-          (inline-message-id (agets ,callback "inline_message_id"))
-          (from-id (agets from "id"))
-          (source-chat-id (agets source-message "chat" "id"))
-          (source-message-id (agets source-message "message_id")))
-     (declare (ignorable query-id from raw-data source-message inline-message-id
-                         from-id source-chat-id source-message-id))
+  `(let* ((*query-id* (agets ,callback "id"))
+          (*from* (agets ,callback "from"))
+          (*raw-data* (agets ,callback "data"))
+          (*source-message* (agets ,callback "message"))
+          (*inline-message-id* (agets ,callback "inline_message_id"))
+          (*from-id* (agets *from* "id"))
+          (*source-chat-id* (agets *source-message* "chat" "id"))
+          (*source-message-id* (agets *source-message* "message_id")))
      ,@body))
 
 (defmacro def-callback-handler (name (callback &optional prio) &body body)
@@ -81,18 +77,18 @@
          (handler-case (progn ,@body)
            (error (e)
              (log:error "~A" e)
-             (bot-send-message (or source-chat-id from-id)
-                               (format nil "Ошибочка вышла~@[: ~A~]"
-                                       (when (member source-chat-id *admins*) e)))))))
+             (bot-send-message (format nil "Ошибочка вышла~@[: ~A~]"
+                                       (when (member *source-chat-id* *admins*) e))
+                               :chat-id (or *source-chat-id* *from-id*))))))
      (when ,prio (setf (get ',name :prio) ,prio))
      (add-hook :update-callback-query ',name)))
 
 (defmacro def-callback-section-handler (name (&rest sections) &body body)
-  `(def-callback-handler ,name (callback)
-     (when source-chat-id
-       (multiple-value-bind (data section) (decode-callback-data source-chat-id raw-data)
-         (when (member section (list ,@sections))
-           (log:info query-id from-id source-chat-id source-message-id section data)
+  `(def-callback-handler ,name (*callback*)
+     (when *source-chat-id*
+       (multiple-value-bind (*data* *section*) (decode-callback-data *source-chat-id* *raw-data*)
+         (when (member *section* (list ,@sections))
+           (log:info *query-id* *from-id* *source-chat-id* *source-message-id* *section* *data*)
            ,@body
            t)))))
 
@@ -108,9 +104,9 @@
      (add-hook :oauth ',name)))
 
 (defmacro def-oauth-section-handler (name (&rest sections) &body body)
-  `(def-oauth-handler ,name (code error raw-state)
-     (multiple-value-bind (state section) (decode-oauth-state raw-state)
-       (when (member section (list ,@sections))
+  `(def-oauth-handler ,name (*code* *error* *raw-state*)
+     (multiple-value-bind (*state* *section*) (decode-oauth-state *raw-state*)
+       (when (member *section* (list ,@sections))
          ,@body
          t))))
 
@@ -140,9 +136,9 @@
          (with-random-state
            (unwind-protect
                 (handler-case
-		    #+sbcl (sb-sys:with-interrupts ,@body)
-		    #-sbcl (progn ,@body)
-                  (error (e) (log:error "~A" e)))
+                    #+sbcl (sb-sys:with-interrupts ,@body)
+                    #-sbcl (progn ,@body)
+                    (error (e) (log:error "~A" e)))
              (dex:clear-connection-pool))))
        (defun ,scheduler ()
          (clon:schedule-function

+ 1 - 1
plugins/admin.lisp

@@ -25,4 +25,4 @@
                   (multiple-value-list (eval (read-from-string input)))))))))
 
 (def-message-admin-cmd-handler handle-admin-eval (:eval)
-  (bot-send-message chat-id (rep (format nil "~{~A~^ ~}" args))))
+  (bot-send-message (rep (format nil "~{~A~^ ~}" *args*))))

+ 47 - 50
plugins/finance.lisp

@@ -200,12 +200,11 @@
    (string-upcase label)))
 
 (def-message-cmd-handler handler-rates (:rates)
-  (let* ((symbols (or (mapcar #'get-label-symbol args)
-                      (db/get-chat-symbols chat-id)
+  (let* ((symbols (or (mapcar #'get-label-symbol *args*)
+                      (db/get-chat-symbols *chat-id*)
                       *default-symbols*))
          (last (db/get-last-finance symbols)))
-    (bot-send-message chat-id
-                      (if last
+    (bot-send-message (if last
                           (format nil "~{~A *~$*~^, ~} @ _~A_"
                                   (loop for (ts symbol price) in last
                                      append (list (get-symbol-label symbol) price))
@@ -262,48 +261,47 @@
           (or (agets info :income) 0)
           (* 100 (or (agets info :roi) 0))))
 
-(defun handle-hodl-show (chat-id)
-  (let ((orders (db/orders-get chat-id)))
-    (bot-send-message chat-id
-                      (if orders (format nil "~{~A~%~} 😇"
+(defun handle-hodl-show ()
+  (let ((orders (db/orders-get *chat-id*)))
+    (bot-send-message (if orders (format nil "~{~A~%~} 😇"
                                          (loop for order in orders for idx from 1
                                             collect (print-order-info idx (get-order-info order))))
                           "Нет ходлеров :(")
                       :parse-mode "markdown")))
 
-(defun handle-hodl-buy (chat-id from args)
+(defun handle-hodl-buy ()
   (handler-case
-      (let ((symbol (get-label-symbol (nth 1 args)))
-            (amount (* (parse-float (nth 2 args)) 1))
-            (open (* (parse-float (nth 3 args)) 1))
-            (open-at (if (nth 4 args) (local-time:parse-timestring (nth 4 args)) (local-time:now))))
-        (db/order-buy chat-id (agets from "id") (agets from "username")
+      (let ((symbol (get-label-symbol (nth 1 *args*)))
+            (amount (* (parse-float (nth 2 *args*)) 1))
+            (open (* (parse-float (nth 3 *args*)) 1))
+            (open-at (if (nth 4 *args*) (local-time:parse-timestring (nth 4 *args*)) (local-time:now))))
+        (db/order-buy *chat-id* *from-id* (agets *from* "username")
                       symbol (princ-to-string amount) (princ-to-string open)
                       (local-time:timestamp-to-unix open-at))
-        (handle-hodl-show chat-id))
+        (handle-hodl-show))
     (error (e)
       (log:error "~A" e)
-      (bot-send-message chat-id "/hodl buy <SYM> <AMT> <PRICE> [YYYY-MM-DD]"))))
+      (bot-send-message "/hodl buy <SYM> <AMT> <PRICE> [YYYY-MM-DD]"))))
 
-(defun handle-hodl-sell (chat-id from args)
+(defun handle-hodl-sell ()
   (handler-case
-      (let* ((symbol (get-label-symbol (nth 1 args)))
-             (amount (* (parse-float (nth 2 args)) 1))
-             (close (* (parse-float (nth 3 args)) 1))
-             (close-at (if (nth 4 args) (local-time:parse-timestring (nth 4 args)) (local-time:now)))
-             (user-id (agets from "id"))
-             (orders (db/orders-get chat-id user-id symbol t))
+      (let* ((symbol (get-label-symbol (nth 1 *args*)))
+             (amount (* (parse-float (nth 2 *args*)) 1))
+             (close (* (parse-float (nth 3 *args*)) 1))
+             (close-at (if (nth 4 *args*) (local-time:parse-timestring (nth 4 *args*)) (local-time:now)))
+             (user-id (agets *from* "id"))
+             (orders (db/orders-get *chat-id* user-id symbol t))
              (total-amount (apply #'+ (mapcar #'(lambda (o) (parse-float (nth 4 o))) orders))))
         (if (> amount total-amount)
-            (bot-send-message chat-id (format nil "Нет у тебя столько ~A! (не хватает ~A)"
-                                              (nth 1 args)
-                                              (- amount total-amount)))
+            (bot-send-message (format nil "Нет у тебя столько ~A! (не хватает ~A)"
+                                      (nth 1 *args*)
+                                      (- amount total-amount)))
             (progn
               (loop for order in orders
                  for order-amount = (parse-float (nth 4 order))
                  while (> amount 0)
                  when (> order-amount amount)
-                 do (db/order-buy chat-id (nth 1 order) (nth 2 order) (nth 3 order)
+                 do (db/order-buy *chat-id* (nth 1 order) (nth 2 order) (nth 3 order)
                                   (princ-to-string (- order-amount amount))
                                   (nth 5 order) (nth 6 order))
                  do (progn
@@ -312,41 +310,41 @@
                                           (princ-to-string close)
                                           (local-time:timestamp-to-unix close-at))
                       (decf amount order-amount)))
-              (handle-hodl-show chat-id))))
+              (handle-hodl-show))))
     (error (e)
       (log:error "~A" e)
-      (bot-send-message chat-id "/hodl sell <SYM> <AMT> <PRICE> [YYYY-MM-DD]"))))
+      (bot-send-message "/hodl sell <SYM> <AMT> <PRICE> [YYYY-MM-DD]"))))
 
-(defun handle-hodl-del (chat-id from args)
+(defun handle-hodl-del ()
   (handler-case
-      (let* ((symbol (get-label-symbol (nth 1 args)))
-             (amount (or (nth 2 args) (error "no amount")))
+      (let* ((symbol (get-label-symbol (nth 1 *args*)))
+             (amount (or (nth 2 *args*) (error "no amount")))
              (order (find amount (db/orders-get chat-id (agets from "id") symbol)
                           :key #'(lambda (r) (nth 4 r)) :test #'equal)))
         (if order
             (progn
               (db/order-del (car order))
-              (handle-hodl-show chat-id))
-            (bot-send-message chat-id (format nil "Не нашёл ~A ~A :(" amount (nth 1 args)))))
+              (handle-hodl-show))
+            (bot-send-message (format nil "Не нашёл ~A ~A :(" amount (nth 1 args)))))
     (error (e)
       (log:error "~A" e)
-      (bot-send-message chat-id "/hodl del <SYM> <AMT>"))))
+      (bot-send-message "/hodl del <SYM> <AMT>"))))
 
 (def-message-cmd-handler handle-hodl (:hodl :hodlers)
   (cond
-    ((equal (car args) "buy") (handle-hodl-buy chat-id (agets message "from") args))
-    ((equal (car args) "sell") (handle-hodl-sell chat-id (agets message "from") args))
-    ((equal (car args) "del") (handle-hodl-del chat-id (agets message "from") args))
-    (:otherwise (handle-hodl-show chat-id))))
+    ((equal (car *args*) "buy") (handle-hodl-buy))
+    ((equal (car *args*) "sell") (handle-hodl-sell))
+    ((equal (car *args*) "del") (handle-hodl-del))
+    (:otherwise (handle-hodl-show))))
 
 
 (def-message-cmd-handler handler-charts (:charts)
-  (telegram-send-chat-action chat-id "upload_photo")
-  (let* ((args (mapcar 'string-downcase args))
+  (telegram-send-chat-action *chat-id* "upload_photo")
+  (let* ((args (mapcar 'string-downcase *args*))
          (all-symbols (db/get-chat-symbols))
-         (symbols (or (intersection (mapcar #'get-label-symbol args) all-symbols :test 'equal) all-symbols))
-         (day-range (some #'(lambda (a) (aget a +chart-ranges+)) args))
-         (number (some #'(lambda (a) (parse-integer a :junk-allowed t)) args))
+         (symbols (or (intersection (mapcar #'get-label-symbol *args*) all-symbols :test 'equal) all-symbols))
+         (day-range (some #'(lambda (a) (aget a +chart-ranges+)) *args*))
+         (number (some #'(lambda (a) (parse-integer a :junk-allowed t)) *args*))
          (avg (* 60 (cond
                       (day-range (round day-range *chart-points*))
                       (number)
@@ -355,7 +353,7 @@
                                           (* avg *chart-points*) :sec))
          (rates (db/get-last-finance symbols))
          (chart (make-chart (db/get-series after-ts symbols avg) symbols)))
-    (telegram-send-photo chat-id chart
+    (telegram-send-photo *chat-id* chart
                          :caption
                          (format nil "~{~A ~$~^, ~} @ ~A"
                                  (loop for (ts symbol price) in rates
@@ -363,9 +361,8 @@
                                  (format-ts (local-time:unix-to-timestamp (caar rates)))))))
 
 (def-message-cmd-handler handler-set-rates (:setrates)
-  (when args
-      (db/set-chat-symbols chat-id (mapcar #'get-label-symbol args)))
-  (bot-send-message chat-id
-                    (format nil "Пишем ~{_~a_~#[~; и ~:;, ~]~}"
-                            (mapcar #'get-symbol-label (db/get-chat-symbols chat-id)))
+  (when *args*
+      (db/set-chat-symbols *chat-id* (mapcar #'get-label-symbol *args*)))
+  (bot-send-message (format nil "Пишем ~{_~a_~#[~; и ~:;, ~]~}"
+                            (mapcar #'get-symbol-label (db/get-chat-symbols *chat-id*)))
                     :parse-mode "markdown"))

+ 4 - 5
plugins/forecast.lisp

@@ -1,7 +1,6 @@
 (in-package :cl-user)
 (defpackage chatikbot.plugins.forecast
-  (:use :cl :chatikbot.common)
-  (:export :*forecast-api-key*))
+  (:use :cl :chatikbot.common))
 (in-package :chatikbot.plugins.forecast)
 
 (defsetting *forecast-api-key* nil "forecast.io APIKEY")
@@ -81,11 +80,11 @@
     (when location
       (log:info "handler-location" chat-id location)
       (set-setting '*chat-locations* (cons (cons chat-id location) *chat-locations*))
-      (bot-send-message chat-id "Взял на карандаш")
+      (bot-send-message "Взял на карандаш" :chat-id chat-id)
       t)))
 
 (def-message-cmd-handler handle-cmd-weather (:weather :hourly :daily)
-  (let* ((location (cdr (assoc chat-id *chat-locations*)))
+  (let* ((location (cdr (assoc *chat-id* *chat-locations*)))
          (response (if location
                        (forecast-format
                         (forecast
@@ -94,4 +93,4 @@
                          :hourly (find "hourly" (cons (string cmd) args) :key #'string-downcase :test #'equal)
                          :daily (find "daily" (cons (string cmd) args) :key #'string-downcase :test #'equal)))
                        "Так а ты чьих будешь?")))
-    (bot-send-message chat-id response)))
+    (bot-send-message response)))

+ 24 - 26
plugins/foursquare.lisp

@@ -24,12 +24,12 @@
     (quri:uri +fsq-oauth-url+))))
 
 (def-oauth-section-handler oauth-handler (:fsq)
-  (if code
+  (if *code*
       (progn
-        (log:info code state)
+        (log:info *code* *state*)
         (let* ((resp (json-request +fsq-token-url+ :method :post
                                    :content (list
-                                             (cons "code" code)
+                                             (cons "code" *code*)
                                              (cons "client_id" *client-id*)
                                              (cons "client_secret" *client-secret*)
                                              (cons "redirect_uri" (get-oauth-url))
@@ -47,7 +47,8 @@
         (hunchentoot:redirect "/oauth/fail"))))
 
 (defun %send-auth (chat-id)
-  (bot-send-message chat-id "Нет токена"
+  (bot-send-message "Нет токена"
+                    :chat-id chat-id
                     :reply-markup (telegram-inline-keyboard-markup
                                    (list (list (list :text "Авторизоваться!"
                                                      :url (get-authorization-url chat-id)))))))
@@ -174,21 +175,21 @@
                 (db/fsq-add-seen token-id checkin-id created-at)))))
     (loop for chat-id being the hash-keys in checkins using (hash-value texts)
        do (log:info "Sending checkins" chat-id texts)
-         (telegram-send-message chat-id (format nil "~{~A~^~%~}" texts)))))
+         (bot-send-message (format nil "~{~A~^~%~}" texts)
+                           :chat-id chat-id))))
 
 ;; Hooks
-(defmacro with-fsq-token ((token chat-id) &body body)
-  `(let ((,token (db/fsq-get-chat-token ,chat-id)))
+(defmacro with-fsq-token ((token) &body body)
+  `(let ((,token (db/fsq-get-chat-token *chat-id*)))
      (if ,token
          (progn ,@body)
-         (%send-auth chat-id))))
+         (%send-auth *chat-id*))))
 
 (def-message-cmd-handler handle-cmd-post-checkins (:postcheckins)
-  (with-fsq-token (token chat-id)
-    (let ((users (db/fsq-get-chat-users chat-id)))
-      (if (null args)
-          (bot-send-message chat-id
-                            (if (null users)
+  (with-fsq-token (token)
+    (let ((users (db/fsq-get-chat-users *chat-id*)))
+      (if (null *args*)
+          (bot-send-message (if (null users)
                                 "Пока никого не палим"
                                 (format nil "Палим ~{~A~^, ~}"
                                         (loop for user in (fsq-fetch-friends token)
@@ -196,7 +197,7 @@
                                                         users :test #'equal)
                                            collect (fsq-user-name user)))))
           (let ((friends (fsq-fetch-friends token)))
-            (dolist (user args)
+            (dolist (user *args*)
               (let ((username (fsq-user-name
                                (find user friends
                                      :test #'equal
@@ -205,19 +206,17 @@
                   (if (member user users :test #'equal)
                       (progn
                         (setf users (remove user users :test #'equal))
-                        (bot-send-message chat-id
-                                          (format nil "Больше не палим ~A" username)))
+                        (bot-send-message (format nil "Больше не палим ~A" username)))
                       (progn
                         (push user users)
-                        (bot-send-message chat-id (format nil "Теперь палим ~A" username)))))))
-            (db/fsq-set-chat-users chat-id users))))))
+                        (bot-send-message (format nil "Теперь палим ~A" username)))))))
+            (db/fsq-set-chat-users *chat-id* users))))))
 
 (def-message-cmd-handler handle-cmd-friends (:friends)
-  (with-fsq-token (token chat-id)
-    (let ((users (db/fsq-get-chat-users chat-id))
+  (with-fsq-token (token)
+    (let ((users (db/fsq-get-chat-users *chat-id*))
           (friends (fsq-fetch-friends token)))
-      (bot-send-message chat-id
-                        (text-chunks (loop for user in friends
+      (bot-send-message (text-chunks (loop for user in friends
                                          collect (format nil "~A: ~:[~;📍 ~]~A"
                                                          (aget "id" user)
                                                          (member (aget "id" user) users :test #'equal)
@@ -227,11 +226,10 @@
 
 
 (def-message-cmd-handler handle-cmd-checkins (:checkins)
-  (with-fsq-token (token chat-id)
-    (let ((users (db/fsq-get-chat-users chat-id)))
+  (with-fsq-token (token)
+    (let ((users (db/fsq-get-chat-users *chat-id*)))
       (when users
-        (bot-send-message chat-id
-                          (format nil "~{~A~^~%~}"
+        (bot-send-message (format nil "~{~A~^~%~}"
                                   (or
                                    (loop for checkin in (fsq-fetch-checkins token)
                                       if (member (aget "id" (aget "user" checkin)) users :test #'equal)

+ 10 - 12
plugins/gazprom.lisp

@@ -14,8 +14,6 @@
 (defvar *other-expenses-account* "expenses:food:snacks")
 (defvar *expenses-currency* "RUB")
 (defvar *entry-description* "ГазпромНефть")
-(defun filled (alist)
-  (remove nil alist :key #'cdr))
 
 (defun %api (method &optional params)
   (let* ((response
@@ -173,9 +171,9 @@
      collect (orders->entry date orders)))
 
 (defcron process-gazprom (:minute '(member 0 5 10 15 20 25 30 35 40 45 50 55))
-  (dolist (chat-id (lists-get :gazprom))
-    (let* ((old (gethash chat-id *last-entries*))
-           (new (get-chat-last-n-orders chat-id 20))
+  (dolist (*chat-id* (lists-get :gazprom))
+    (let* ((old (gethash *chat-id* *last-entries*))
+           (new (get-chat-last-n-orders *chat-id* 20))
            (changes (sort (set-difference new old :test #'equalp)
                           #'< :key (agetter "date")))
            (ledger-package (find-package :chatikbot.plugins.ledger)))
@@ -186,15 +184,15 @@
               (let ((new-chat-entry (symbol-function
                                      (intern "LEDGER/NEW-CHAT-ENTRY" ledger-package))))
                 (dolist (entry (prepare-entries changes))
-                  (funcall new-chat-entry chat-id (pta-ledger:clone-entry entry))))
-              (bot-send-message chat-id (format-entries (prepare-entries changes)) :parse-mode "markdown")))
+                  (funcall new-chat-entry *chat-id* (pta-ledger:clone-entry entry))))
+              (bot-send-message (format-entries (prepare-entries changes)) :parse-mode "markdown")))
         (let ((merged (merge 'list old changes #'< :key (agetter "date"))))
-          (setf (gethash chat-id *last-entries*)
+          (setf (gethash *chat-id* *last-entries*)
                 (subseq merged (max (- (length merged) 200) 0))))))))
 
 (def-message-cmd-handler handler-gazprom (:gpn :gazprom)
-  (let ((arg (car args)))
+  (let ((arg (car *args*)))
     (if (string= arg "bal")
-        (bot-send-message chat-id (format-card (get-chat-card chat-id)) :parse-mode "markdown")
-        (let ((last (prepare-entries (get-chat-last-n-orders chat-id (if arg (parse-integer arg) 10)))))
-          (bot-send-message chat-id (format-entries last) :parse-mode "markdown")))))
+        (bot-send-message (format-card (get-chat-card *chat-id*)) :parse-mode "markdown")
+        (let ((last (prepare-entries (get-chat-last-n-orders *chat-id* (if arg (parse-integer arg) 10)))))
+          (bot-send-message (format-entries last) :parse-mode "markdown")))))

+ 2 - 3
plugins/google.lisp

@@ -32,10 +32,9 @@
 
 ;;; Hooks
 (def-message-cmd-handler handler-cmd-google (:google :g :г :q)
-  (bot-send-message chat-id
-                    (google-format-search-results
+  (bot-send-message (google-format-search-results
                      (subseq
-                      (google-search (format nil "~{~A~^ ~}" args))
+                      (google-search (format nil "~{~A~^ ~}" *args*))
                       0 3))
                     :parse-mode "markdown"
                     :disable-web-preview 1))

+ 27 - 28
plugins/gsheets.lisp

@@ -183,9 +183,9 @@
 
 (def-message-cmd-handler sheets-handler (:sheets :sheet)
   (cond
-    (args
+    (*args*
      ;; Set active sheet
-     (let* ((uri (quri:uri (car args)))
+     (let* ((uri (quri:uri (car *args*)))
             (scheme (quri:uri-scheme uri))
             (path (quri:uri-path uri))
             (sheet-id (cond
@@ -193,48 +193,47 @@
                               (equal (subseq path 0 (min 16 (length path))) "/spreadsheets/d/"))
                          (subseq path 16 (position #\/ path :start 16)))
                         ((null scheme) path)))
-            (title (%gsheets-get-file-title-update-session chat-id from-id sheet-id)))
-       (bot-send-message chat-id
-                         (if title
+            (title (%gsheets-get-file-title-update-session *chat-id* *from-id* sheet-id)))
+       (bot-send-message (if title
                              (format nil "Выбран 📑 ~A" title)
                              "Нет такого документа (или доступа)"))))
     (:otherwise
      ;; Show list of available files
-     (let ((files-markup (%gsheets-files-markup-update-session chat-id from-id)))
+     (let ((files-markup (%gsheets-files-markup-update-session *chat-id* *from-id*)))
        (if files-markup
-           (bot-send-message chat-id "Выбери документ:" :reply-markup files-markup)
-           (%gsheets-send-auth chat-id from-id))))))
+           (bot-send-message "Выбери документ:" :reply-markup files-markup)
+           (%gsheets-send-auth *chat-id* *from-id*))))))
 
 (def-message-cmd-handler sheet-read-handler (:sheet-read)
-  (let ((sheet-id (aget chat-id *gsheets-chat-sheets*)))
+  (let ((sheet-id (aget *chat-id* *gsheets-chat-sheets*)))
     (if sheet-id
-        (let* ((range (car args))
-               (resp (gsheets-get-sheet-values from-id sheet-id range))
+        (let* ((range (car *args*))
+               (resp (gsheets-get-sheet-values *from-id* sheet-id range))
                (values (aget "values" resp)))
-          (bot-send-message chat-id (format nil "```~%~{~{~A~^ ~}~^~%~}```" values)
+          (bot-send-message (format nil "```~%~{~{~A~^ ~}~^~%~}```" values)
                             :parse-mode "markdown"))
-        (bot-send-message chat-id "Выбери документ сначала: /sheets"))))
+        (bot-send-message "Выбери документ сначала: /sheets"))))
 
 (def-message-cmd-handler sheet-write-handler (:sheet-write)
-  (let ((sheet-id (aget chat-id *gsheets-chat-sheets*)))
+  (let ((sheet-id (aget *chat-id* *gsheets-chat-sheets*)))
     (if sheet-id
-        (let* ((range (car args))
-               (values (list (cdr args)))
-               (resp (gsheets-put-sheet-values from-id sheet-id (list (cons range values)))))
-          (bot-send-message chat-id (format nil "Записал в ~A" (aget "updatedRange" resp))))
-        (bot-send-message chat-id "Выбери документ сначала: /sheets"))))
+        (let* ((range (car *args*))
+               (values (list (cdr *args*)))
+               (resp (gsheets-put-sheet-values *from-id* sheet-id (list (cons range values)))))
+          (bot-send-message (format nil "Записал в ~A" (aget "updatedRange" resp))))
+        (bot-send-message "Выбери документ сначала: /sheets"))))
 
 (def-callback-section-handler cb-handle-gss (:gs)
-  (destructuring-bind (type id) (split-sequence:split-sequence #\- data :count 2)
+  (destructuring-bind (type id) (split-sequence:split-sequence #\- *data* :count 2)
     (case (intern (string-upcase type) "KEYWORD")
-      (:s (let* ((file (elt (car (aget source-chat-id *gsheets-chat-sheet-sessions*)) (parse-integer id)))
-                 (title (%gsheets-get-file-title-update-session source-chat-id from-id (aget "id" file))))
+      (:s (let* ((file (elt (car (aget *source-chat-id* *gsheets-chat-sheet-sessions*)) (parse-integer id)))
+                 (title (%gsheets-get-file-title-update-session *source-chat-id* *from-id* (aget "id" file))))
             (telegram-edit-message-text
              (if title (format nil "Выбран 📑 ~A" title) "Чот лажа")
-             :chat-id source-chat-id :message-id source-message-id)
-            (update-alist-settings '*gsheets-chat-sheet-sessions* source-chat-id nil)))
-      (:n (let* ((token-next (cdr (aget source-chat-id *gsheets-chat-sheet-sessions*)))
-                 (files-markup (%gsheets-files-markup-update-session source-chat-id from-id token-next)))
+             :chat-id *source-chat-id* :message-id *source-message-id*)
+            (update-alist-settings '*gsheets-chat-sheet-sessions* *source-chat-id* nil)))
+      (:n (let* ((token-next (cdr (aget *source-chat-id* *gsheets-chat-sheet-sessions*)))
+                 (files-markup (%gsheets-files-markup-update-session *source-chat-id* *from-id* token-next)))
             (if files-markup
-                (telegram-edit-message-reply-markup files-markup :chat-id source-chat-id :message-id source-message-id)
-                (telegram-edit-message-text "Чот лажа" :chat-id source-chat-id :message-id source-message-id)))))))
+                (telegram-edit-message-reply-markup files-markup :chat-id *source-chat-id* :message-id *source-message-id*)
+                (telegram-edit-message-text "Чот лажа" :chat-id *source-chat-id* :message-id *source-message-id*)))))))

+ 3 - 3
plugins/huiza.lisp

@@ -46,13 +46,13 @@
      (:sticker . "BQADAgADEgADOoERAAFtU3uF9HvtQgI") ;; Бухнём?
      (:sticker . "BQADBAADQAEAAnscSQABqWydSKTnASoC"))))
 
-(defun send-dont-understand (chat-id &optional text reply-id)
+(defun send-dont-understand (&optional text reply-id)
   (let* ((*package* (find-package :chatikbot.plugins.huiza))
          (resp (eliza (read-from-string-no-punct text) *huiza-rules*)))
     (when resp
-      (bot-send-message chat-id resp :reply-to reply-id))))
+      (bot-send-message resp :reply-to reply-id))))
 
 (def-message-handler huiza-handler (message -1000)
   (log:info "handle-unknown-message" message)
-  (send-dont-understand chat-id (preprocess-input text))
+  (send-dont-understand (preprocess-input text))
   t)

+ 116 - 121
plugins/ledger.lisp

@@ -117,42 +117,41 @@
               (with-secret (info (list :ledger chat-id))
                 (process-info info)))))))
 
-(Defun ledger/handle-info (chat-id)
-  (with-secret (info (list :ledger chat-id))
+(defun ledger/handle-info ()
+  (with-secret (info (list :ledger *chat-id*))
     (if info
         (destructuring-bind (journal . ut)
-            (get-chat-journal-info chat-id info)
+            (get-chat-journal-info *chat-id* info)
           (let ((uri (cond
                        ((consp info) (car info))
                        ((stringp info) info))))
-            (bot-send-message chat-id (format nil "Журнал с ~D записями, из ~A, обновлён ~A.~%Веб-хук: ~A"
-                                              (length journal)
-                                              (ledger/format-uri uri)
-                                              (ledger/format-time ut)
-                                              (ledger/get-hook-url chat-id))
+            (bot-send-message (format nil "Журнал с ~D записями, из ~A, обновлён ~A.~%Веб-хук: ~A"
+                                      (length journal)
+                                      (ledger/format-uri uri)
+                                      (ledger/format-time ut)
+                                      (ledger/get-hook-url *chat-id*))
                               :disable-web-preview t)))
-        (bot-send-message chat-id "Добавь журнал: uri - /ledger <url>; git - /ledger <remote> <path>"))))
+        (bot-send-message "Добавь журнал: uri - /ledger <url>; git - /ledger <remote> <path>"))))
 
-(defun ledger/handle-set-info (chat-id info)
-  (setf (gethash chat-id *ledger/chat-journals*) nil)
+(defun ledger/handle-set-info (info)
+  (setf (gethash *chat-id* *ledger/chat-journals*) nil)
   (handler-case
       (destructuring-bind (journal . ut)
-          (get-chat-journal-info chat-id info)
+          (get-chat-journal-info *chat-id* info)
         (declare (ignore ut))
-        (secret-set (list :ledger chat-id) info)
-        (bot-send-message chat-id (format nil "Добавил журнал с ~D записями. Веб-хук для обновления: ~A"
-                                       (length journal)
-                                       (ledger/get-hook-url chat-id))))
+        (secret-set (list :ledger *chat-id*) info)
+        (bot-send-message (format nil "Добавил журнал с ~D записями. Веб-хук для обновления: ~A"
+                                  (length journal)
+                                  (ledger/get-hook-url *chat-id*))))
     (pta-ledger:journal-failed (e)
-      (bot-send-message chat-id (format nil "Не смог спарсить: ~A" e)))
+      (bot-send-message (format nil "Не смог спарсить: ~A" e)))
     (dex:http-request-failed (e)
-      (bot-send-message chat-id (format nil "Не смог в урл: ~A" (dex:response-body e))))))
-
+      (bot-send-message (format nil "Не смог в урл: ~A" (dex:response-body e))))))
 
 (def-message-cmd-handler handler-ledger (:ledger)
   (cond
-    ((<= 1 (length args) 2) (ledger/handle-set-info chat-id args))
-    (:otherwise (ledger/handle-info chat-id))))
+    ((<= 1 (length *args*) 2) (ledger/handle-set-info *args*))
+    (:otherwise (ledger/handle-info))))
 
 (defmacro with-chat-journal ((chat-id journal updated) &body body)
   (let ((info (gensym "info")))
@@ -161,12 +160,12 @@
            (destructuring-bind (,journal . ,updated) ,info
              (declare (ignorable ,journal ,updated))
              ,@body)
-           (bot-send-message ,chat-id "Добавь журнал: uri - /ledger <url>; git - /ledger <remote> <path>")))))
+           (bot-send-message "Добавь журнал: uri - /ledger <url>; git - /ledger <remote> <path>"
+                             :chat-id chat-id)))))
 
-(defun ledger/handle-balance (chat-id query)
-  (with-chat-journal (chat-id journal updated)
-    (bot-send-message chat-id
-                      (text-chunks (split-sequence:split-sequence
+(defun ledger/handle-balance (query)
+  (with-chat-journal (*chat-id* journal updated)
+    (bot-send-message (text-chunks (split-sequence:split-sequence
                                     #\Newline
                                     (or (pta-ledger:journal-balance journal query)
                                         "Не нашлось"))
@@ -176,69 +175,67 @@
 
 (def-message-cmd-handler handler-balance (:balance :bal :budget)
   (cond
-    ((null args) (ledger/handle-balance chat-id (if (eql cmd :budget) "budget" "assets")))
-    (:otherwise (ledger/handle-balance chat-id (spaced args)))))
+    ((null *args*) (ledger/handle-balance (if (eql *cmd* :budget) "budget" "assets")))
+    (:otherwise (ledger/handle-balance (spaced *args*)))))
 
-(defun ledger/handle-journal (chat-id query)
-  (with-chat-journal (chat-id journal updated)
+(defun ledger/handle-journal (query)
+  (with-chat-journal (*chat-id* journal updated)
     (let* ((pta-ledger:*posting-length* 40)
            (entries (pta-ledger:journal-print journal query))
            (len (length entries))
            (count (min len 50)))
-      (bot-send-message chat-id
-                        (if entries
+      (bot-send-message (if entries
                             (text-chunks (subseq entries (- len count) len))
                             "Не нашлось")
                         :parse-mode "markdown"))))
 
 (def-message-cmd-handler handler-journal (:journal)
   (cond
-    ((null args) (ledger/handle-journal chat-id "date:thisweek"))
-    (:otherwise (ledger/handle-journal chat-id (spaced args)))))
+    ((null *args*) (ledger/handle-journal "date:thisweek"))
+    (:otherwise (ledger/handle-journal (spaced *args*)))))
 
 (def-message-cmd-handler hander-add-report (:add_report)
-  (bot-send-message chat-id "Введи название"
+  (bot-send-message "Введи название"
                     :reply-markup (telegram-force-reply))
-  (on-next-message chat-id
-    (let ((name text))
-      (bot-send-message chat-id "Введи запрос"
-                        :reply-markup (telegram-force-reply))
-      (on-next-message chat-id
-        (let ((query text))
-          (pta-ledger:parse-query query) ;; Validate query
-          (bot-send-message chat-id "Введи расписание, ex: (:day-of-week 0 :hour 10), (:day * :hour 10)"
-                            :reply-markup (telegram-force-reply))
-          (on-next-message chat-id
-            (let* ((schedule text))
-              (add-chat-cron :ledger-report chat-id schedule name query)
-              (bot-send-message chat-id "Добавил"))))))))
+  (on-next-message
+      (let ((name *text*))
+        (bot-send-message "Введи запрос"
+                          :reply-markup (telegram-force-reply))
+        (on-next-message
+            (let ((query *text*))
+              (pta-ledger:parse-query query) ;; Validate query
+              (bot-send-message "Введи расписание, ex: (:day-of-week 0 :hour 10), (:day * :hour 10)"
+                                :reply-markup (telegram-force-reply))
+              (on-next-message
+                  (let* ((schedule *text*))
+                    (add-chat-cron :ledger-report *chat-id* schedule name query)
+                    (bot-send-message "Добавил"))))))))
 
 (defun run-report (chat-id name query)
   (with-chat-journal (chat-id journal updated)
-    (bot-send-message chat-id
-                      (text-chunks (split-sequence:split-sequence
+    (bot-send-message (text-chunks (split-sequence:split-sequence
                                     #\Newline
                                     (format nil "*~A*~%~%~A" name
                                             (pta-ledger:journal-balance journal query)))
                                    :text-sep "
 ")
-                      :parse-mode "markdown")))
+                      :parse-mode "markdown"
+                      :chat-id chat-id)))
 
 (def-chat-cron-handler handler-chat-cron (:ledger-report chat-id schedule name query)
   (run-report chat-id name query))
 
 (def-message-cmd-handler handler-reports (:reports)
-  (let ((crons (get-chat-crons :ledger-report chat-id)))
+  (let ((crons (get-chat-crons :ledger-report *chat-id*)))
     (if crons
         (bot-send-message
-         chat-id (format nil "Отчёты~%~%~{~A) ~A _~A_: ~A~^~%~}"
-                         (loop for (schedule name query) in crons
-                            for idx from 1
-                            append (list idx name schedule query)))
+         (format nil "Отчёты~%~%~{~A) ~A _~A_: ~A~^~%~}"
+                 (loop for (schedule name query) in crons
+                    for idx from 1
+                    append (list idx name schedule query)))
          :parse-mode "markdown"
          :reply-markup
          (get-inline-keyboard
-          chat-id
           (append
            (loop for (schedule name query) in crons
               for idx from 0
@@ -246,16 +243,17 @@
                 (let ((cron-index idx)
                       (cron-name name))
                   (list (inline-button ((format nil "Удалить '~A'" cron-name))
-                          (delete-chat-cron :ledger-report chat-id cron-index)
+                          (delete-chat-cron :ledger-report *source-chat-id* cron-index)
                           (telegram-edit-message-reply-markup
-                           nil :chat-id source-chat-id :message-id source-message-id)
-                          (bot-send-message chat-id (format nil "Удалил *~A*" cron-name)
+                           nil :chat-id *source-chat-id* :message-id *source-message-id*)
+                          (bot-send-message (format nil "Удалил *~A*" cron-name)
+                                            :chat-id *source-chat-id*
                                             :parse-mode "markdown")))))
            (list (list (inline-button ("Отмена")
                          (telegram-edit-message-reply-markup
-                          nil :chat-id source-chat-id :message-id source-message-id)))))))
+                          nil :chat-id *source-chat-id* :message-id source-message-id)))))))
         (bot-send-message
-         chat-id "Отчётов пока нет"
+         "Отчётов пока нет"
          :reply-markup (telegram-reply-keyboard-markup (list (list (list :text "/add_report")))
                                                        :one-time-keyboard t)))))
 
@@ -329,16 +327,15 @@
 
 (def-message-cmd-handler handler-create (:rub :usd :eur :thb :btc :czk)
   (cond
-    ((>= (length args) 2)
-     (ledger/new-chat-entry chat-id (create-entry chat-id
-                                                  (spaced (subseq args 1))
-                                                  (parse-float (car args))
-                                                  (symbol-name cmd))))
-    (:otherwise (bot-send-message chat-id (format nil "/~A <amount> <description>" cmd)))))
+    ((>= (length *args*) 2)
+     (ledger/new-chat-entry *chat-id* (create-entry *chat-id*
+                                                  (spaced (subseq *args* 1))
+                                                  (parse-float (car *args*)))))
+    (:otherwise (bot-send-message format nil "/~A <amount> <description>" *cmd*))))
 
 (def-webhook-handler ledger/handle-webhook ("ledger")
-  (when (= 2 (length paths))
-    (destructuring-bind (chat-id hmac) paths
+  (when (= 2 (length *paths*))
+    (destructuring-bind (chat-id hmac) *paths*
       (let ((true-hmac (token-hmac chat-id)))
         (when (string= true-hmac hmac)
           (with-secret (info (list :ledger chat-id))
@@ -380,7 +377,8 @@
 (defun ledger/new-chat-entry (chat-id entry)
   (let ((entry (extend-entry! chat-id entry)))
     (bot-send-message
-     chat-id (format-entry entry)
+     (format-entry entry)
+     :chat-id chat-id
      :parse-mode "markdown"
      :reply-markup (keyboard/entry chat-id entry))))
 
@@ -394,18 +392,17 @@
 
 (defun keyboard/entry (chat-id entry)
   (get-inline-keyboard
-   chat-id
    (list
     (list (inline-button ("➕")
-            (telegram-answer-callback-query query-id :text "Добавляю...")
-            (telegram-send-message source-chat-id (ledger/process-add-entry
-                                                   source-chat-id from entry))
+            (telegram-answer-callback-query *query-id* :text "Добавляю...")
+            (telegram-send-message *source-chat-id* (ledger/process-add-entry
+                                                     *source-chat-id* *from* entry))
             (telegram-edit-message-reply-markup
-             nil :chat-id source-chat-id :message-id source-message-id))
+             nil :chat-id *source-chat-id* :message-id *source-message-id*))
           (inline-button ("💲")
-            (entry-edit source-chat-id source-message-id entry))
+            (entry-edit *source-chat-id* *source-message-id* entry))
           (inline-button ("✖️")
-            (telegram-delete-message source-chat-id source-message-id))))))
+            (telegram-delete-message *source-chat-id* *source-message-id*))))))
 
 (defun def (str default)
   (if (or (null str) (string= "" str)) default
@@ -413,57 +410,56 @@
 
 (defun keyboard/edit-entry (chat-id entry)
   (get-inline-keyboard
-   chat-id
    (append
     (list
      (list (inline-button ((format-date (pta-ledger:entry-date entry)))
-             (bot-send-message chat-id "Введите дату"
+             (bot-send-message "Введите дату" :chat-id chat-id
                                :reply-markup (telegram-force-reply))
-             (on-next-message chat-id
-               (let ((date (pta-ledger:parse-date text (get-year
-                                                        (pta-ledger:entry-date entry)))))
-                 (if date
-                     (progn
-                       (setf (pta-ledger:entry-date entry) date)
-                       (entry-edit chat-id source-message-id entry))
-                     (bot-send-message chat-id "Не разобрал")))))
+             (on-next-message
+                 (let ((date (pta-ledger:parse-date *text* (get-year
+                                                            (pta-ledger:entry-date entry)))))
+                   (if date
+                       (progn
+                         (setf (pta-ledger:entry-date entry) date)
+                         (entry-edit chat-id *source-message-id* entry))
+                       (bot-send-message "Не разобрал" :chat-id chat-id)))))
            (inline-button ((def (pta-ledger:entry-description entry) "Описание"))
-             (bot-send-message chat-id "Введите описание"
+             (bot-send-message "Введите описание" :chat-id chat-id
                                :reply-markup (telegram-force-reply))
-             (on-next-message chat-id
-               (setf (pta-ledger:entry-description entry) text)
-               (entry-edit chat-id source-message-id entry)))
+             (on-next-message
+               (setf (pta-ledger:entry-description entry) *text*)
+               (entry-edit chat-id *source-message-id* entry)))
            (inline-button ((def (pta-ledger:entry-comment entry) "Коммент"))
-             (bot-send-message chat-id "Введите комментарий"
+             (bot-send-message "Введите комментарий" :chat-id chat-id
                                :reply-markup (telegram-force-reply))
-             (on-next-message chat-id
-               (setf (pta-ledger:entry-comment entry) text)
-               (entry-edit chat-id source-message-id entry)))))
+             (on-next-message
+                 (setf (pta-ledger:entry-comment entry) *text*)
+               (entry-edit chat-id *source-message-id* entry)))))
     (loop for posting in (pta-ledger:entry-postings entry)
        collect
          (let ((this-posting posting))
            (list (inline-button ((pta-ledger:posting-account posting))
-                   (account-edit chat-id source-message-id entry this-posting
+                   (account-edit chat-id *source-message-id* entry this-posting
                                  (pta-ledger:posting-account this-posting)))
                  (inline-button ((def (pta-ledger:render
                                        (pta-ledger:posting-amount posting))
                                      "Сумма"))
-                   (bot-send-message chat-id "Введите сумму"
+                   (bot-send-message "Введите сумму" :chat-id chat-id
                                      :reply-markup (telegram-force-reply))
-                   (on-next-message chat-id
-                     (let ((amount (pta-ledger:parse-amount text)))
-                       (setf (pta-ledger:posting-amount this-posting) amount
-                             (pta-ledger:posting-status this-posting) nil)
-                       (entry-edit chat-id source-message-id entry))))
+                   (on-next-message
+                       (let ((amount (pta-ledger:parse-amount *text*)))
+                         (setf (pta-ledger:posting-amount this-posting) amount
+                               (pta-ledger:posting-status this-posting) nil)
+                         (entry-edit chat-id *source-message-id* entry))))
                  (inline-button ("❌")
                    (setf (pta-ledger:entry-postings entry)
                          (remove this-posting (pta-ledger:entry-postings entry)
                                  :test #'equalp))
-                   (entry-edit chat-id source-message-id entry)))))
+                   (entry-edit chat-id *source-message-id* entry)))))
     (list (list (inline-button ("Готово")
                   (telegram-edit-message-reply-markup
                    (keyboard/entry chat-id entry)
-                   :chat-id chat-id :message-id source-message-id)))))))
+                   :chat-id chat-id :message-id *source-message-id*)))))))
 
 (defun accounts/nav (account accounts &optional (offset 0) (count 5))
   (let* ((len (1+ (length account)))
@@ -513,43 +509,42 @@
 
 (defun keyboard/account (chat-id entry posting account parent children prev next)
   (get-inline-keyboard
-   chat-id
    (append
     (list (list (when account
                   (inline-button (account)
                     (setf (pta-ledger:posting-account posting) account
                           (pta-ledger:posting-status posting) nil)
-                    (entry-edit chat-id source-message-id entry)))
+                    (entry-edit chat-id *source-message-id* entry)))
                 (inline-button ("Ввести")
-                  (bot-send-message chat-id "Введите счёт"
+                  (bot-send-message "Введите счёт" :chat-id chat-id
                                     :reply-markup (telegram-force-reply))
-                  (on-next-message chat-id
-                    (let ((account (pta-ledger:parse-account text)))
-                      (if account
-                          (progn
-                            (setf (pta-ledger:posting-account posting) account
-                                  (pta-ledger:posting-status posting) nil)
-                            (entry-edit chat-id source-message-id entry))
-                          (bot-send-message chat-id "Не разобрал")))))))
+                  (on-next-message
+                      (let ((account (pta-ledger:parse-account *text*)))
+                        (if account
+                            (progn
+                              (setf (pta-ledger:posting-account posting) account
+                                    (pta-ledger:posting-status posting) nil)
+                              (entry-edit chat-id *source-message-id* entry))
+                            (bot-send-message "Не разобрал" :chat-id chat-id)))))))
     (loop for (acc . leaf) in children
        collect (let ((this-account acc))
                  (list (if leaf
                            (inline-button (this-account)
                              (setf (pta-ledger:posting-account posting) this-account
                                    (pta-ledger:posting-status posting) nil)
-                             (entry-edit chat-id source-message-id entry))
+                             (entry-edit chat-id *source-message-id* entry))
                            (inline-button ((format nil "~A ..." this-account))
-                             (account-edit chat-id source-message-id entry posting
+                             (account-edit chat-id *source-message-id* entry posting
                                            this-account))))))
     (list (list (when prev
                   (inline-button ("<<")
-                    (account-edit chat-id source-message-id entry posting
+                    (account-edit chat-id *source-message-id* entry posting
                                   account prev)))
                 (when (or parent account)
                   (inline-button ((or parent "ACC"))
-                    (account-edit chat-id source-message-id entry posting
+                    (account-edit chat-id *source-message-id* entry posting
                                   parent account)))
                 (when next
                   (inline-button (">>")
-                    (account-edit chat-id source-message-id entry posting
+                    (account-edit chat-id *source-message-id* entry posting
                                   account next))))))))

+ 10 - 13
plugins/raiffeisen.lisp

@@ -25,9 +25,6 @@
             (agets response "expires_in")
             (agets response "resource_owner"))))
 
-(defun filled (alist)
-  (remove nil alist :key #'cdr))
-
 (defparameter +raif-rest-url+ "https://online.raiffeisen.ru/rest/")
 (defun %rest (method &optional parameters)
   (handler-case
@@ -190,9 +187,9 @@
     (concatenate 'list (^account) (^loan))))
 
 (defcron process-raiffeisen (:minute '(member 0 5 10 15 20 25 30 35 40 45 50 55))
-  (dolist (chat-id (lists-get :raiffeisen))
-    (let* ((old (gethash chat-id *last-entries*))
-           (new (get-chat-last-n-entries chat-id 20))
+  (dolist (*chat-id* (lists-get :raiffeisen))
+    (let* ((old (gethash *chat-id* *last-entries*))
+           (new (get-chat-last-n-entries *chat-id* 20))
            (changes (sort (set-difference new old :test #'equalp)
                           #'< :key #'pta-ledger:entry-date))
            (ledger-package (find-package :chatikbot.plugins.ledger)))
@@ -203,15 +200,15 @@
               (let ((new-chat-entry (symbol-function
                                      (intern "LEDGER/NEW-CHAT-ENTRY" ledger-package))))
                 (dolist (entry changes)
-                  (funcall new-chat-entry chat-id (pta-ledger:clone-entry entry))))
-              (bot-send-message chat-id (format-entries changes) :parse-mode "markdown")))
+                  (funcall new-chat-entry *chat-id* (pta-ledger:clone-entry entry))))
+              (bot-send-message (format-entries changes) :parse-mode "markdown")))
         (let ((merged (merge 'list old changes #'< :key #'pta-ledger:entry-date)))
-          (setf (gethash chat-id *last-entries*)
+          (setf (gethash *chat-id* *last-entries*)
                 (subseq merged (max (- (length merged) 200) 0))))))))
 
 (def-message-cmd-handler handler-raif (:raif)
-  (let ((arg (car args)))
+  (let ((arg (car *args*)))
     (if (string= arg "bal")
-        (bot-send-message chat-id (format-balance (get-chat-accounts chat-id)) :parse-mode "markdown")
-        (let ((last (get-chat-last-n-entries chat-id (if arg (parse-integer arg) 10))))
-          (bot-send-message chat-id (format-entries last) :parse-mode "markdown")))))
+        (bot-send-message (format-balance (get-chat-accounts *chat-id*)) :parse-mode "markdown")
+        (let ((last (get-chat-last-n-entries *chat-id* (if arg (parse-integer arg) 10))))
+          (bot-send-message (format-entries last) :parse-mode "markdown")))))

+ 22 - 25
plugins/rss.lisp

@@ -194,29 +194,27 @@
 (defcron process-feeds ()
   (dolist (feed (remove-if-not #'need-fetch-p (db/rss-get-active-feeds)))
     (dolist (item (%fetch-new-items feed))
-      (dolist (chat-id (db/rss-get-feed-chats feed))
-        (telegram-send-message chat-id
-                               (format-feed-item item)
-                               :parse-mode "Markdown"
-                               :disable-web-preview 1)))
+      (dolist (*chat-id* (db/rss-get-feed-chats feed))
+        (bot-send-message (format-feed-item item)
+                          :parse-mode "Markdown"
+                          :disable-web-preview 1)))
     (db/rss-update-feed feed))) ;; Update next fetch and period
 
 ;; Hooks
 (def-message-cmd-handler handler-cmd-feeds (:feeds)
   (bot-send-message
-   chat-id
-   (if (null args)
+    (if (null *args*)
        "URL давай"
        (format nil "~:[Не нашел RSS там~;~:*~{~{~A - ~A~}~^~%~}~]"
-               (find-rss-links (car args))))
-   :disable-web-preview 1))
+               (find-rss-links (car *args*))))
+    :disable-web-preview 1))
 
 (def-message-cmd-handler handler-cmd-rss (:rss)
-  (let ((feeds (db/rss-get-chat-feeds chat-id)))
-    (if (null args)
-        (%send-feeds chat-id feeds)
+  (let ((feeds (db/rss-get-chat-feeds *chat-id*)))
+    (if (null *args*)
+        (%send-feeds *chat-id* feeds)
         (progn
-          (dolist (url args)
+          (dolist (url *args*)
             (handler-case
                 (let ((idx (parse-integer url)))
                   (when (<= idx (length feeds))
@@ -230,18 +228,17 @@
                         (setf feeds (remove existing feeds))
                         (push feed feeds)))))
               (error (e) (log:error "~A: ~A" url e))))
-          (db/rss-set-chat-feeds chat-id feeds)
-          (%send-feeds chat-id (db/rss-get-chat-feeds chat-id))))))
+          (db/rss-set-chat-feeds *chat-id* feeds)
+          (%send-feeds *chat-id* (db/rss-get-chat-feeds *chat-id*))))))
 
 (def-message-cmd-handler handler-cmd-last-rss (:lastrss)
-  (let ((feeds (db/rss-get-chat-feeds chat-id)))
-    (if (null args)
-        (%send-feeds chat-id feeds)
-        (let* ((idx (1- (parse-integer (car args))))
-               (limit (min 20 (if (> (length args) 1) (parse-integer (second args)) 5)))
+  (let ((feeds (db/rss-get-chat-feeds *chat-id*)))
+    (if (null *args*)
+        (%send-feeds *chat-id* feeds)
+        (let* ((idx (1- (parse-integer (car *args*))))
+               (limit (min 20 (if (> (length *args*) 1) (parse-integer (second *args*)) 5)))
                (items (db/rss-last-feed-items (nth idx feeds) limit)))
-          (telegram-send-message chat-id
-                                 (format nil "~{~A~^~%~%~}"
-                                         (mapcar #'format-feed-item items))
-                                 :parse-mode "Markdown"
-                                 :disable-web-preview 1)))))
+          (bot-send-message (format nil "~{~A~^~%~%~}"
+                                    (mapcar #'format-feed-item items))
+                            :parse-mode "Markdown"
+                            :disable-web-preview 1)))))

+ 60 - 60
plugins/saver.lisp

@@ -29,11 +29,11 @@
              (or (and (zerop (mod year 4))
                       (not (zerop (mod year 100))))
                  (zerop (mod year 400))))
-	   (month-days (month year)
-	     (case month
-	       (2 (if (leapp year) 29 28))
-	       ((1 3 5 7 8 10 12) 31)
-	       (otherwise 30)))
+       (month-days (month year)
+         (case month
+           (2 (if (leapp year) 29 28))
+           ((1 3 5 7 8 10 12) 31)
+           (otherwise 30)))
            (clamp-day-rule (day-rule month year)
              (destructuring-bind (lst &optional (min 0) max (step 1)) day-rule
                (let ((max-day (month-days month year)))
@@ -65,13 +65,13 @@
              (funcall (if dir #'+ #'-) v (if of 1 0)))
            (reset-rule (rule def-min def-max)
              (destructuring-bind (lst &optional min max (step 1)) rule
-	       (let ((min (or min def-min))
-		     (max (or max def-max)))
-		 (if (consp lst)
-		     (car (if dir lst (last lst)))
-		     (if dir min
-			 (let ((m (mod (- max min) step)))
-			   (- max (if (zerop m) 0 m)))))))))
+           (let ((min (or min def-min))
+             (max (or max def-max)))
+         (if (consp lst)
+             (car (if dir lst (last lst)))
+             (if dir min
+             (let ((m (mod (- max min) step)))
+               (- max (if (zerop m) 0 m)))))))))
     (multiple-value-bind (second minute hour day month year) (decode-universal-time universal-time)
       (declare (ignore second minute hour))
       (destructuring-bind (&optional (day-rule '(nil 1 31)) (month-rule '(nil 1 12)) (year-rule '(nil 1900)))
@@ -86,7 +86,7 @@
                    0 0 0
                    (if (and (= next-month month) (= next-year year)) next-day
                        (reset-rule (clamp-day-rule day-rule next-month next-year) 1
-				   (month-days next-month next-year)))
+                   (month-days next-month next-year)))
                    next-month
                    next-year))))))))))
 
@@ -243,16 +243,15 @@
 
 (defcron process-saver (:hour '*)
   (let ((moment (get-universal-time)))
-    (loop for (chat-id . payment) in (saver/find-today-payments moment)
-       when (%saver/is-ok-to-notify chat-id moment)
-       do (let ((payments (db/saver/get-payments chat-id)))
+    (loop for (*chat-id* . payment) in (saver/find-today-payments moment)
+       when (%saver/is-ok-to-notify *chat-id* moment)
+       do (let ((payments (db/saver/get-payments *chat-id*)))
             (db-transaction
-              (bot-send-message chat-id
-                                (if (saver/payment-income-p payment)
+              (bot-send-message (if (saver/payment-income-p payment)
                                     (%saver/format-income-notification payment payments moment)
                                     (%saver/format-expense-notification payment moment))
                                 :parse-mode "markdown")
-              (db/saver/set-payment-notified chat-id (saver/payment-name payment) moment))))))
+              (db/saver/set-payment-notified *chat-id* (saver/payment-name payment) moment))))))
 
 
 ;; Bot subcommands
@@ -301,16 +300,18 @@
                 (and closest-income-info (/ (getf closest-income-info :cur-saved) 100)))
         "Нет активных платежей.")))
 
-(defun saver/send-info (chat-id)
-  (let ((payments (db/saver/get-payments chat-id)))
-    (if (find t payments :key #'saver/payment-income-p)
-        (bot-send-message chat-id (%saver/format-info payments) :parse-mode "markdown")
-        (bot-send-message chat-id (format nil "Нет поступлений, ничего не посчитать :( /saver add ...")))))
+(defun saver/send-info ()
+  (let ((payments (db/saver/get-payments *chat-id*)))
+    (bot-send-message
+     (if (find t payments :key #'saver/payment-income-p)
+         (%saver/format-info payments)
+         "Нет поступлений, ничего не посчитать :( /saver add ...")
+     :parse-mode "markdown")))
 
 (defparameter +saver/add-scanner+ (cl-ppcre:create-scanner "^(.+?) (-?\\d+(?:\\.\\d*)?) ((?:\\d+(?:,\\d+)*(?:-(?:\\d+)?)?|\\*)(?:\\/\\d+)?(?: (?:\\d+(?:,\\d+)*(?:-(?:\\d+)?)?|\\*)(?:\\/\\d+)?){0,2})(?: (\\d{4}-\\d{2}-\\d{2}))?$"))
 (defparameter +saver/schedule-scanner+ (cl-ppcre:create-scanner "^((?:\\d+(?:,\\d+)*(?:-(?:\\d+)?)?|\\*)(?:\\/\\d+)?(?: (?:\\d+(?:,\\d+)*(?:-(?:\\d+)?)?|\\*)(?:\\/\\d+)?){0,2})$"))
 
-(defun saver/add-payment (chat-id args)
+(defun saver/add-payment (args)
   (multiple-value-bind (matched groups) (cl-ppcre:scan-to-strings +saver/add-scanner+ (spaced args))
     (if matched
         (let ((payment (make-saver/payment :name (elt groups 0)
@@ -325,67 +326,66 @@
                                                         (get-universal-time)))))
           (saver/payment-next-time payment (get-universal-time))
           (handler-case
-              (db/saver/add-payment chat-id payment)
-            (error () (bot-send-message chat-id (format nil "Платёж '~A' уже есть!"
-                                                        (saver/payment-name payment)))))
-          (saver/send-info chat-id))
-        (bot-send-message chat-id "Неправильно. /saver add <title> <amount> <cron> [started]"))))
+              (db/saver/add-payment *chat-id* payment)
+            (error () (bot-send-message (format nil "Платёж '~A' уже есть!"
+                                                (saver/payment-name payment)))))
+          (saver/send-info))
+        (bot-send-message "Неправильно. /saver add <title> <amount> <cron> [started]"))))
 
-(defun saver/del-payment (chat-id args)
+(defun saver/del-payment (args)
   (handler-case
-      (let* ((payments (db/saver/get-payments chat-id))
+      (let* ((payments (db/saver/get-payments *chat-id*))
              (payment (elt payments (1- (parse-integer (car args)))))
              (incomes (remove-if-not #'saver/payment-income-p payments)))
-        (db/saver/del-payment chat-id (saver/payment-name payment))
-        (bot-send-message chat-id
-                          (format nil "'~A' удалил.~@[ Забрать _~$_ из накопленого.~]"
+        (db/saver/del-payment *chat-id* (saver/payment-name payment))
+        (bot-send-message (format nil "'~A' удалил.~@[ Забрать _~$_ из накопленого.~]"
                                   (saver/payment-name payment)
                                   (and (not (saver/payment-income-p payment))
                                        incomes
                                        (/ (getf (saver/get-expense-info payment incomes) :saved-amount)
                                           100)))
                           :parse-mode "markdown"))
-    (error (e) (bot-send-message chat-id (format nil "/saver del <idx> [~A]" e)))))
+    (error (e) (bot-send-message (format nil "/saver del <idx> [~A]" e)))))
 
-(defun saver/set-payment-name (chat-id args)
+(defun saver/set-payment-name (args)
   (handler-case
-      (let* ((payments (db/saver/get-payments chat-id))
+      (let* ((payments (db/saver/get-payments *chat-id*))
              (payment (elt payments (1- (parse-integer (car args)))))
              (new-name (spaced (rest args))))
-        (db/saver/set-payment-name chat-id (saver/payment-name payment) new-name)
-        (saver/send-info chat-id))
-    (error (e) (bot-send-message chat-id (format nil "/saver rename <idx> <new name> [~A]" e)))))
+        (db/saver/set-payment-name *chat-id* (saver/payment-name payment) new-name)
+        (saver/send-info))
+    (error (e) (bot-send-message (format nil "/saver rename <idx> <new name> [~A]" e)))))
 
-(defun saver/set-payment-amount (chat-id args)
+(defun saver/set-payment-amount (args)
   (handler-case
-      (let* ((payments (db/saver/get-payments chat-id))
+      (let* ((payments (db/saver/get-payments *chat-id*))
              (payment (elt payments (1- (parse-integer (car args)))))
              (new-amount (round (* 100 (parse-float (second args))))))
-        (db/saver/set-payment-amount chat-id (saver/payment-name payment) new-amount)
-        (saver/send-info chat-id))
-    (error (e) (bot-send-message chat-id (format nil "/saver amount <idx> <new amount> [~A]" e)))))
+        (db/saver/set-payment-amount *chat-id* (saver/payment-name payment) new-amount)
+        (saver/send-info))
+    (error (e) (bot-send-message (format nil "/saver amount <idx> <new amount> [~A]" e)))))
 
-(defun saver/set-payment-schedule (chat-id args)
+(defun saver/set-payment-schedule (args)
   (handler-case
-      (let* ((payments (db/saver/get-payments chat-id))
+      (let* ((payments (db/saver/get-payments *chat-id*))
              (payment (elt payments (1- (parse-integer (car args)))))
              (new-schedule (spaced (rest args))))
         (unless (cl-ppcre:scan +saver/schedule-scanner+ new-schedule)
           (error "format ex: 1-30/3 * *"))
         (setf (saver/payment-schedule payment) new-schedule)
         (saver/payment-next-time payment (get-universal-time)) ;; Perform additional schedule checks
-        (db/saver/set-payment-schedule chat-id (saver/payment-name payment) new-schedule)
-        (saver/send-info chat-id))
-    (error (e) (bot-send-message chat-id (format nil "/saver cron <idx> <new cron> [~A]" e)))))
+        (db/saver/set-payment-schedule *chat-id* (saver/payment-name payment) new-schedule)
+        (saver/send-info))
+    (error (e) (bot-send-message (format nil "/saver cron <idx> <new cron> [~A]" e)))))
 
 ;; Hooks
 (def-message-cmd-handler handler-cmd-save (:save :saver)
-  (if (null args)
-      (saver/send-info chat-id)
-      (case (keyify (car args))
-        (:add (saver/add-payment chat-id (rest args)))
-        (:del (saver/del-payment chat-id (rest args)))
-        (:rename (saver/set-payment-name chat-id (rest args)))
-        (:amount (saver/set-payment-amount chat-id (rest args)))
-        (:cron (saver/set-payment-schedule chat-id (rest args)))
-        (t (bot-send-message chat-id "Надо /saver [add|del|rename|amount|cron] ...")))))
+  (if (null *args*)
+      (saver/send-info)
+      (case (keyify (car *args*))
+        (:add (saver/add-payment (rest *args*)))
+        (:del (saver/del-payment (rest *args*)))
+        (:rename (saver/set-payment-name (rest *args*)))
+        (:amount (saver/set-payment-amount (rest args)))
+        (:cron (saver/set-payment-schedule (rest args)))
+        (t (bot-send-message "Надо /saver [add|del|rename|amount|cron] ...")))))

+ 5 - 5
plugins/shopper.lisp

@@ -144,14 +144,14 @@
                                   (agets result :price)
                                   (agets result :link)))))))
 
-(defun handle-search (chat-id query)
-  (telegram-send-chat-action chat-id "typing")
+(defun handle-search (query)
+  (telegram-send-chat-action *chat-id* "typing")
   (let ((results (merge-search-results
                   (search-shops query +shops+ 3))))
-    (bot-send-message chat-id (if results (format-merged results) "Ничего не нашлось")
+    (bot-send-message (if results (format-merged results) "Ничего не нашлось")
                       :parse-mode "markdown" :disable-web-preview "true")))
 
 (def-message-cmd-handler handler-cmd-wall (:bike)
   (cond
-    ((null args) (bot-send-message chat-id "/bike <query>"))
-    (:otherwise (handle-search chat-id (spaced args)))))
+    ((null *args*) (bot-send-message "/bike <query>"))
+    (:otherwise (handle-search (spaced *args*)))))

+ 10 - 11
plugins/tescort.lisp

@@ -105,19 +105,18 @@
                     (loop for i from 1 for p in (agets item :photos)
                        append (list i p))))))
 
-(defun handle-blacklist (chat-id query)
-  (with-chat-cookies (chat-id *chat-cookie-jars*)
-    (telegram-send-chat-action chat-id "typing")
+(defun handle-blacklist (query)
+  (with-chat-cookies (*chat-id* *chat-cookie-jars*)
+    (telegram-send-chat-action *chat-id* "typing")
     (let ((results (merge-results (search-blacklists query))))
-      (bot-send-message chat-id (if results
-                                    (text-chunks (mapcar #'format-blacklist results)
-                                                 :pre-pre "" :pre-post "")
-                                    "Not found")
+      (bot-send-message (if results
+                            (text-chunks (mapcar #'format-blacklist results)
+                                         :pre-pre "" :pre-post "")
+                            "Not found")
                         :parse-mode "markdown"))))
 
 (def-message-admin-cmd-handler handler-cmd-blacklist (:blacklist :bl)
   (cond
-    ((null args) (bot-send-message chat-id "Enter query")
-     (on-next-message chat-id
-       (handle-blacklist chat-id text)))
-    (:otherwise (handle-blacklist chat-id (spaced args)))))
+    ((null *args*) (bot-send-message "Enter query")
+     (on-next-message (handle-blacklist *text*)))
+    (:otherwise (handle-blacklist (spaced *args*)))))

+ 10 - 10
plugins/tinkoff.lisp

@@ -208,9 +208,9 @@
         (princ "```" s)))
 
 (defcron process-tinkoff (:minute '(member 0 5 10 15 20 25 30 35 40 45 50 55))
-  (dolist (chat-id (lists-get :tinkoff))
-    (let* ((old (gethash chat-id *last-entries*))
-           (new (get-chat-last-entries chat-id (* 7 24 60 60)))
+  (dolist (*chat-id* (lists-get :tinkoff))
+    (let* ((old (gethash *chat-id* *last-entries*))
+           (new (get-chat-last-entries *chat-id* (* 7 24 60 60)))
            (changes (sort (set-difference new old :test #'equalp)
                           #'< :key #'pta-ledger:entry-date))
            (ledger-package (find-package :chatikbot.plugins.ledger)))
@@ -221,15 +221,15 @@
               (let ((new-chat-entry (symbol-function
                                      (intern "LEDGER/NEW-CHAT-ENTRY" ledger-package))))
                 (dolist (entry changes)
-                  (funcall new-chat-entry chat-id (pta-ledger:clone-entry entry))))
-              (bot-send-message chat-id (format-entries changes) :parse-mode "markdown")))
+                  (funcall new-chat-entry *chat-id* (pta-ledger:clone-entry entry))))
+              (bot-send-message (format-entries changes) :parse-mode "markdown")))
         (let ((merged (merge 'list old changes #'< :key #'pta-ledger:entry-date)))
-          (setf (gethash chat-id *last-entries*)
+          (setf (gethash *chat-id* *last-entries*)
                 (subseq merged (max (- (length merged) 200) 0))))))))
 
 (def-message-cmd-handler handler-tink (:tink)
-  (let ((arg (car args)))
+  (let ((arg (car *args*)))
     (if (string= arg "bal")
-        (bot-send-message chat-id (format-accounts (get-chat-accounts chat-id)) :parse-mode "markdown")
-        (let ((last (get-chat-last-entries chat-id (* (if arg (parse-integer arg) 7) +day+))))
-          (bot-send-message chat-id (format-entries last) :parse-mode "markdown")))))
+        (bot-send-message (format-accounts (get-chat-accounts *chat-id*)) :parse-mode "markdown")
+        (let ((last (get-chat-last-entries *chat-id* (* (if arg (parse-integer arg) 7) +day+))))
+          (bot-send-message (format-entries last) :parse-mode "markdown")))))

+ 25 - 26
plugins/transmission.lisp

@@ -79,69 +79,68 @@
 
 ;; Command handlers
 (def-message-cmd-handler handle-torrents (:torrents)
-  (let ((url (aget chat-id *transmission-settings*)))
+  (let ((url (aget *chat-id* *transmission-settings*)))
     (if url
         (let ((torrents (transmission-get-torrents url)))
-          (bot-send-message chat-id
-                            (if torrents (format nil "~{~A~^~%~}" (mapcar #'%format-torrent torrents))
+          (bot-send-message (if torrents (format nil "~{~A~^~%~}" (mapcar #'%format-torrent torrents))
                                 "Пустой список!")))
-        (bot-send-message chat-id "Бота настрой!"))))
+        (bot-send-message "Бота настрой!"))))
 
-(defun %torrent-add-and-respond (chat-id &key filename metainfo)
-  (alexandria:when-let (url (aget chat-id *transmission-settings*))
+(defun %torrent-add-and-respond (&key filename metainfo)
+  (alexandria:when-let (url (aget *chat-id* *transmission-settings*))
     (let* ((new-torrent (transmission-add-torrent url :filename filename :metainfo metainfo))
            (markup (loop for (name . location) in *transmission-locations*
                       collect (list :text (format nil "💾 ~A" name)
                                     :callback-data (encode-callback-data
-                                                    chat-id :tm
+                                                    *chat-id* :tm
                                                     (format nil "l-~A-~A" name
                                                             (aget "id" new-torrent))
                                                     86400)))))
-      (bot-send-message chat-id (%format-torrent new-torrent)
+      (bot-send-message (%format-torrent new-torrent)
                         :reply-markup (and markup (telegram-inline-keyboard-markup (list markup)))))))
 
 (defparameter +magnet-regex+ (ppcre:create-scanner "magnet:\\?\\S+"))
 (def-message-handler handle-magnet (message)
-  (alexandria:when-let (magnet (ppcre:scan-to-strings +magnet-regex+ text))
-    (%torrent-add-and-respond chat-id :filename magnet)))
+  (alexandria:when-let (magnet (ppcre:scan-to-strings +magnet-regex+ *text*))
+    (%torrent-add-and-respond :filename magnet)))
 
 (def-message-handler handle-torrent (message)
-  (alexandria:when-let* ((url (aget chat-id *transmission-settings*))
-                         (doc (aget "document" message))
+  (alexandria:when-let* ((url (aget *chat-id* *transmission-settings*))
+                         (doc (aget "document" *message*))
                          (file-name (aget "file_name" doc))
                          (file-id (aget "file_id" doc)))
     (when (and (equal "torrent" (pathname-type (pathname file-name)))
                (< (aget "file_size" doc) (* 512 1024)))
-      (%torrent-add-and-respond chat-id :metainfo (cl-base64:usb8-array-to-base64-string
-                                                   (telegram-file-contents file-id))))))
+      (%torrent-add-and-respond :metainfo (cl-base64:usb8-array-to-base64-string
+                                           (telegram-file-contents file-id))))))
 
 ;; Callback handlers
-(defun %handle-torrent-move (query-id chat-id torrent-id name)
-  (alexandria:when-let* ((url (aget chat-id *transmission-settings*))
+(defun %handle-torrent-move (torrent-id name)
+  (alexandria:when-let* ((url (aget *source-chat-id* *transmission-settings*))
                          (location (aget name *transmission-locations*)))
     (transmission-request url :torrent-set-location
                           :ids torrent-id
                           :location location
                           :move t)
-    (telegram-answer-callback-query query-id :text (format nil "Moved to '~A'" location))))
+    (telegram-answer-callback-query *query-id* :text (format nil "Moved to '~A'" location))))
 
 (def-callback-section-handler cb-handle-tm (:tm)
   (destructuring-bind (type val id)
-      (split-sequence:split-sequence #\- data :count 3)
+      (split-sequence:split-sequence #\- *data* :count 3)
     (case (intern (string-upcase type) "KEYWORD")
-      (:l (%handle-torrent-move query-id source-chat-id (parse-integer id) val)))))
+      (:l (%handle-torrent-move (parse-integer id) val)))))
 
 ;; Cron
 (defvar *transmission-last-results* (make-hash-table) "Last check results for each chat-id")
 (defcron process-transmission ()
-  (dolist (chat-id *transmission-monitor*)
-    (alexandria:when-let (url (aget chat-id *transmission-settings*))
-      (let ((old-result (gethash chat-id *transmission-last-results*))
+  (dolist (*chat-id* *transmission-monitor*)
+    (alexandria:when-let (url (aget *chat-id* *transmission-settings*))
+      (let ((old-result (gethash *chat-id* *transmission-last-results*))
             (new-result (loop for torrent in (transmission-get-torrents url nil '("id" "status"))
                            collect (cons (aget "id" torrent) (aget "status" torrent)))))
         (when old-result
           (alexandria:when-let (updated (mapcar #'car (set-difference new-result old-result :test #'equal)))
-            (bot-send-message chat-id (format nil "~{~A~^~%~}"
-                                              (mapcar #'%format-torrent
-                                                      (transmission-get-torrents url updated))))))
-        (setf (gethash chat-id *transmission-last-results*) new-result)))))
+            (bot-send-message (format nil "~{~A~^~%~}"
+                                      (mapcar #'%format-torrent
+                                              (transmission-get-torrents url updated))))))
+        (setf (gethash *chat-id* *transmission-last-results*) new-result)))))

+ 1 - 1
plugins/tumblr.lisp

@@ -60,4 +60,4 @@
          (resp (eliza (read-from-string-no-punct text) *tumblr-rules*)))
     (when resp
       (log:info resp)
-      (bot-send-message chat-id resp))))
+      (bot-send-message resp))))

+ 14 - 14
plugins/vk.lisp

@@ -189,11 +189,11 @@
                 (dolist (post new-posts)
                   (multiple-value-bind (text disable)
                       (%format-wall-post domain name post)
-                    (dolist (chat-id (db/vk-get-domain-chats domain))
+                    (dolist (*chat-id* (db/vk-get-domain-chats domain))
                       (ignore-errors
-                        (telegram-send-message chat-id text
-                                               :parse-mode "Markdown"
-                                               :disable-web-preview disable))))
+                        (bot-send-message text
+                                          :parse-mode "Markdown"
+                                          :disable-web-preview disable))))
                   (setf last-id (max last-id (aget "id" post)))))
             (error (e) (log:error "~A" e)))
           (db/vk-update-wall domain last-id
@@ -217,25 +217,25 @@
          (aget "count" (vk-wall-get :domain +akb-vk-domain+ :count 1 :offset 10000000))))
     (dolist (post (aget "items" (vk-wall-get :domain +akb-vk-domain+
                                              :count (min *akb-max-posts*
-                                                         (or (ignore-errors (parse-integer (car args)))
+                                                         (or (ignore-errors (parse-integer (car *args*)))
                                                              1))
                                              :offset (random total-aneks))))
-      (bot-send-message chat-id (format-akb post) :disable-web-preview 1))))
+      (bot-send-message (format-akb post) :disable-web-preview 1))))
 
 (def-message-cmd-handler handler-cmd-wall (:wall)
-  (let ((domains (db/vk-get-chat-domains chat-id)))
-    (if (null args)
-        (%send-domains chat-id domains)
+  (let ((domains (db/vk-get-chat-domains *chat-id*)))
+    (if (null *args*)
+        (%send-domains *chat-id* domains)
         (progn
-          (dolist (url args)
+          (dolist (url *args*)
             (handler-case
                 (let ((idx (parse-integer url)))
-                  (db/vk-remove-chat-domain chat-id (nth (1- idx) domains)))
+                  (db/vk-remove-chat-domain *chat-id* (nth (1- idx) domains)))
               (parse-error ()
                 (let* ((domain (%ensure-domain (%find-vk-domain url)))
                        (existing (find domain domains :test #'equal)))
                   (if existing
-                      (db/vk-remove-chat-domain chat-id domain)
-                      (db/vk-add-chat-domain chat-id domain))))
+                      (db/vk-remove-chat-domain *chat-id* domain)
+                      (db/vk-add-chat-domain *chat-id* domain))))
               (error (e) (log:error "~A" e))))
-          (%send-domains chat-id (db/vk-get-chat-domains chat-id))))))
+          (%send-domains *chat-id* (db/vk-get-chat-domains *chat-id*))))))

+ 11 - 11
plugins/zhanna.lisp

@@ -99,8 +99,8 @@
                  (msg (format nil "У Жанны сменились планы~%~{~A~^~%~}" day-changes)))
             (when day-changes
               (log:info msg)
-              (dolist (chat-id *zhanna-subscriptions*)
-                (bot-send-message chat-id msg :parse-mode "markdown"))))
+              (dolist (*chat-id* *zhanna-subscriptions*)
+                (bot-send-message msg :parse-mode "markdown"))))
         (error (e) (log:error e))))
     (setf *zhanna-last-schedule* schedule)
     (values)))
@@ -114,21 +114,21 @@
          (texts (loop for (dow . day-schedule) in schedule
                    collect (format nil "*~A*~%~{~A~^, ~}" dow
                                    (mapcar #'car (remove nil day-schedule :key #'second :test-not #'eql))))))
-    (bot-send-message chat-id (format nil "Жанна свободна:~%~{~A~^~%~}" texts) :parse-mode "markdown")))
+    (bot-send-message (format nil "Жанна свободна:~%~{~A~^~%~}" texts) :parse-mode "markdown")))
 
 (def-message-cmd-handler zhanna-set-handler (:set :записать)
-  (if (null (gsheets-get-tokens from-id))
-      (%gsheets-send-auth chat-id from-id)
+  (if (null (gsheets-get-tokens *from-id*))
+      (%gsheets-send-auth *chat-id* *from-id*)
       (handler-case
-          (destructuring-bind (name day time &optional (sets "1")) args
+          (destructuring-bind (name day time &optional (sets "1")) *args*
             (let ((dow (position day +zhanna-dows+ :test #'equal))
                   (count (parse-integer sets :junk-allowed t)))
               (cond
-                ((null dow) (bot-send-message chat-id "День надо кратко: 'пн', 'вт', 'ср' и т.д"))
-                ((null count) (bot-send-message chat-id "Число сетов - числом!"))
+                ((null dow) (bot-send-message "День надо кратко: 'пн', 'вт', 'ср' и т.д"))
+                ((null count) (bot-send-message "Число сетов - числом!"))
                 (:otherwise
-                 (let ((err (zhanna-set-schedule from-id name dow time count)))
-                   (bot-send-message chat-id (or err "Записал!")))))))
+                 (let ((err (zhanna-set-schedule *from-id* name dow time count)))
+                   (bot-send-message (or err "Записал!")))))))
         (error (e)
           (log:info e)
-          (bot-send-message chat-id "Надо: /set <имя> <день> <время> [кол-во]")))))
+          (bot-send-message "Надо: /set <имя> <день> <время> [кол-во]")))))

+ 20 - 22
plugins/zsd.lisp

@@ -59,25 +59,23 @@
               (loop for item in wall-diff
                  collect (%zsd/format-wall item (aget "pan" new)))))))
 
-(defun zsd/handle-set-cron (chat-id enable)
-  (lists-set-entry :zsd chat-id enable)
-  (bot-send-message chat-id
-                    (if enable
+(defun zsd/handle-set-cron (enable)
+  (lists-set-entry :zsd *chat-id* enable)
+  (bot-send-message (if enable
                         "Включил рассылку. '/zsd off' чтобы выключить, /zsd - показать последние."
                         "Без рассылки. '/zsd on' - включить, /zsd - последние.")))
 
-(defun zsd/handle-auth (chat-id login pass)
+(defun zsd/handle-auth (login pass)
   (let ((token (zsd/auth login pass)))
     (if token
         (progn
-          (secret-set `(:zsd ,chat-id) token)
-          (zsd/handle-set-cron chat-id t))
-        (bot-send-message chat-id "Чот не смог, пропробуй другие."))))
+          (secret-set `(:zsd ,*chat-id*) token)
+          (zsd/handle-set-cron t))
+        (bot-send-message "Чот не смог, пропробуй другие."))))
 
-(defun zsd/handle-recent (chat-id)
-  (with-secret (token (list :zsd chat-id))
-    (bot-send-message chat-id
-                      (if token
+(defun zsd/handle-recent ()
+  (with-secret (token (list :zsd *chat-id*))
+    (bot-send-message (if token
                           (let ((data (zsd/load-data token)))
                             (if data
                                 (zsd/format-changes nil data)
@@ -87,22 +85,22 @@
 
 (def-message-cmd-handler handle-cmd-zsd (:zsd)
   (cond
-    ((= 1 (length args))
-     (zsd/handle-set-cron chat-id (equal "on" (car args))))
-    ((= 2 (length args)) (apply 'zsd/handle-auth chat-id args))
-    (:otherwise (zsd/handle-recent chat-id))))
+    ((= 1 (length *args*))
+     (zsd/handle-set-cron (equal "on" (car *args*))))
+    ((= 2 (length *args*)) (apply 'zsd/handle-auth *args*))
+    (:otherwise (zsd/handle-recent))))
 
 (defvar *zsd/last-results* (make-hash-table) "Last check results")
 (defcron process-zsd (:minute '(member 0 10 20 30 40 50))
-  (dolist (chat-id (lists-get :zsd))
-    (with-secret (token (list :zsd chat-id))
+  (dolist (*chat-id* (lists-get :zsd))
+    (with-secret (token (list :zsd *chat-id*))
       (if token
-          (let ((old (gethash chat-id *zsd/last-results*))
+          (let ((old (gethash *chat-id* *zsd/last-results*))
                 (new (zsd/load-data token)))
             (when new
               (when old
                 (alexandria:when-let ((changes (zsd/format-changes old new)))
-                  (bot-send-message chat-id changes :parse-mode "markdown")))
-              (setf (gethash chat-id *zsd/last-results*) new)))
+                  (bot-send-message changes :parse-mode "markdown")))
+              (setf (gethash *chat-id* *zsd/last-results*) new)))
           (progn
-            (log:warn "zsd no token for" chat-id))))))
+            (log:warn "zsd no token for" *chat-id*))))))

+ 3 - 2
telegram.lisp

@@ -327,7 +327,7 @@
 
 ;; Simplified interface
 ;;
-(defun bot-send-message (chat-id message &key parse-mode disable-web-preview disable-notification reply-to reply-markup duration)
+(defun bot-send-message (message &key (chat-id *chat-id*) parse-mode disable-web-preview disable-notification reply-to reply-markup duration)
   (handler-case (if (consp message)
                     (if (keywordp (car message))
                         (case (car message)
@@ -344,7 +344,8 @@
                           (:sticker (telegram-send-sticker chat-id (cdr message)
                                                            :reply-to reply-to
                                                            :reply-markup reply-markup)))
-                        (mapc #'(lambda (m) (bot-send-message chat-id m
+                        (mapc #'(lambda (m) (bot-send-message m
+                                                              :chat-id chat-id
                                                               :parse-mode parse-mode
                                                               :disable-web-preview disable-web-preview
                                                               :disable-notification disable-notification

+ 70 - 36
utils.lisp

@@ -1,7 +1,29 @@
 (in-package :cl-user)
 (defpackage chatikbot.utils
   (:use :cl)
-  (:export :*admins*
+  (:export :*message-id*
+           :*from-id*
+           :*chat-id*
+           :*text*
+           :*message*
+           :*cmd*
+           :*args*
+           :*query-id*
+           :*from*
+           :*raw-data*
+           :*inline-message-id*
+           :*source-message*
+           :*source-chat-id*
+           :*source-message-id*
+           :*callback*
+           :*data*
+           :*section*
+           :*code*
+           :*error*
+           :*raw-state*
+           :*state*
+
+           :*admins*
            :*bot-name*
            :*hooks*
            :+hour+
@@ -10,6 +32,7 @@
            :run-hooks
            :add-hook
            :remove-hook
+           :is-admin
            :keyify
            :dekeyify
            :*settings*
@@ -50,33 +73,33 @@
            :get-chat-timezone
            :same-time-in-chat
            :group-by
-           :pmapcar
-           :message-id
-           :from-id
-           :chat-id
-           :text
-           :cmd
-           :args
-           :callback
-           :query-id
-           :from
-           :raw-data
-           :message
-           :data
-           :section
-           :code
-           :error
-           :raw-state
-           :state
-           :inline-message-id
-           :source-message
-           :source-message-id
-           :source-chat-id
-           :hook
-           :headers
-           :paths))
+           :pmapcar))
 (in-package #:chatikbot.utils)
 
+;; Special variables
+(defvar *message-id*)
+(defvar *from-id*)
+(defvar *chat-id*)
+(defvar *text*)
+(defvar *message*)
+(defvar *cmd*)
+(defvar *args*)
+(defvar *query-id*)
+(defvar *from*)
+(defvar *raw-data*)
+(defvar *source-message*)
+(defvar *inline-message-id*)
+(defvar *source-chat-id*)
+(defvar *source-message-id*)
+(defvar *callback*)
+(defvar *data*)
+(defvar *section*)
+(defvar *code*)
+(defvar *error*)
+(defvar *raw-state*)
+(defvar *state*)
+
+
 (defvar *admins* nil "Admins chat-ids")
 (defvar *bot-name* nil "bot name to properly handle text input")
 (defvar *hooks* (make-hash-table) "Hooks storage")
@@ -108,12 +131,16 @@
   (setf (gethash event *hooks*)
         (remove hook (gethash event *hooks*))))
 
+(defun is-admin (&optional (chat-id *chat-id*))
+  (member chat-id *admins*))
+
 (defun keyify (key)
   (intern (string-upcase (substitute #\- #\_ key)) :keyword))
 
 (defun dekeyify (keyword &optional preserve-dash)
-  (let ((text (string-downcase (string keyword))))
-    (if preserve-dash text (substitute #\_ #\- text))))
+  (if (typep keyword 'string) keyword
+      (let ((text (string-downcase (string keyword))))
+        (if preserve-dash text (substitute #\_ #\- text)))))
 
 ;; Settings
 (defvar *settings* nil "List of plugin's settings symbols")
@@ -304,7 +331,7 @@ is replaced with replacement."
                            do (r child))))))
       (r node))))
 
-(let ((ws-regex (cl-ppcre:create-scanner "[\\n  	]+" :multi-line-mode t)))
+(let ((ws-regex (cl-ppcre:create-scanner "[\\n   ]+" :multi-line-mode t)))
   (defun unspacify (text)
     (string-trim " " (cl-ppcre:regex-replace-all ws-regex text " "))))
 
@@ -316,13 +343,17 @@ is replaced with replacement."
   (when text (trim-nil (plump:text (plump:parse text)))))
 
 ;; JSON processing
-(defun json-request (url &rest args &key method parameters content headers basic-auth cookie-jar keep-alive use-connection-pool timeout ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path user-agent (object-as :alist))
+(defun json-request (url &rest args &key method parameters content headers basic-auth cookie-jar keep-alive use-connection-pool timeout ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path user-agent (object-as :alist) json-content)
   (declare (ignore method parameters basic-auth cookie-jar keep-alive use-connection-pool timeout ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path user-agent))
-  (remf args :object-as)
-  (when content
+  (when (and (consp content) json-content)
+    (setf content (yason:encode-alist content))
     (push (cons :content-type "application/json") headers))
+  (remf args :object-as)
+  (remf args :headers)
+  (remf args :json-content)
+  (remf args :content)
   (multiple-value-bind (body status headers uri)
-      (apply #'http-request url args)
+      (apply #'http-request url :content content :headers headers args)
     (unless (stringp body)
       (setf body (babel:octets-to-string body :encoding :utf-8)))
     (values (yason:parse body :object-as object-as) status headers uri)))
@@ -397,7 +428,7 @@ is replaced with replacement."
   (intern (apply #'concatenate 'string
                  (mapcar #'symbol-name symbols))))
 
-(defun get-chat-location (chat-id)
+(defun get-chat-location (&optional (chat-id *chat-id*))
   (let* ((forecast-package (find-package :chatikbot.plugins.forecast))
          (chat-locations-sym (when forecast-package
                                (intern "*CHAT-LOCATIONS*" forecast-package)))
@@ -405,13 +436,13 @@ is replaced with replacement."
                            (symbol-value chat-locations-sym))))
     (when chat-locations (aget chat-id chat-locations))))
 
-(defun get-chat-timezone (chat-id)
+(defun get-chat-timezone (&optional (chat-id *chat-id*))
   (or (let ((chat-loc (get-chat-location chat-id)))
         (when chat-loc
           (round (- 7.5 (aget "latitude" chat-loc)) 15))) ;; Nautical time
       *chat-default-timezone*))
 
-(defun same-time-in-chat (ut chat-id)
+(defun same-time-in-chat (ut &optional (chat-id *chat-id*))
   (let ((chat-tz (get-chat-timezone chat-id))
         (current-tz (nth-value 8 (get-decoded-time))))
     (+ ut (* (- chat-tz current-tz) +hour+))))
@@ -431,6 +462,9 @@ is replaced with replacement."
   (let ((result (mapcar (lambda (n) (eager-future2:pexec (funcall f n))) list)))
     (map-into result #'eager-future2:yield result)))
 
+(defun filled (alist)
+  (remove nil alist :key #'cdr))
+
 ;; Fix bug in local-time (following symlinks in /usr/share/zoneinfo/
 ;; leads to bad cutoff)
 (in-package #:local-time)