(in-package :cl-user) (defpackage chatikbot.plugins.gazprom (:use :cl :chatikbot.common :alexandria)) (in-package :chatikbot.plugins.gazprom) (defparameter +gpn-api-url+ "https://api.gpnbonus.ru/ios/v2/") (defvar *api-os* "android") (defvar *api-ver* "1.7.4") (defvar *api-token* "21c733213e747611432154b1bf96e723") (defvar *session* nil "Currently active session") (defvar *credentials-provider* nil "Active credentials provider") (defun filled (alist) (remove nil alist :key #'cdr)) (defun %api (method &optional params) (let* ((response (json-request (concatenate 'string +gpn-api-url+ method) :parameters (filled (append `(("session" . ,*session*) ("os" . ,*api-os*) ("ver" . ,*api-ver*)) params)))) (message (agets response "message"))) (if (equal message "Необходимо авторизоваться") (if *credentials-provider* (progn (funcall *credentials-provider* (lambda (login password) (setf *session* (^auth login password)))) (let (*credentials-provider*) ;; Retry call resetting *credentials-provider* to prevent loop (%api method params))) (error message)) response))) (defun ^auth (login pass) (let* ((resp (json-request (concatenate 'string +gpn-api-url+ "auth.php") :method :post :content `(("login" . ,login) ("passw" . ,pass) ("token" . ,*api-token*) ("os" . ,*api-os*) ("ver" . ,*api-ver*)))) (status (agets resp "status"))) (unless (= status 1) (error (agets resp "message"))) (agets resp "session"))) (defun ^get-card-info () (%api "getCardInfo.php")) (defun ^get-order (&key (count 20) (offset 0)) (%api "getOrder.php" `(("count" . ,count) ("offset" . ,offset)))) ;; Cron (defvar *last-entries* (make-hash-table) "Last per-chat entries") (defvar *sessions* (make-hash-table) "Per-chat sessions") (defmacro with-chat-credentials ((chat-id) &body body) `(let* ((*session* (gethash ,chat-id *sessions*)) (*credentials-provider* (lambda (authenticator) (with-secret (login-pass (list :gazprom ,chat-id)) (if login-pass (apply authenticator login-pass) (error "no gazprom credentials for ~A" ,chat-id)))))) (prog1 (progn ,@body) (setf (gethash chat-id *sessions*) *session*)))) (defun get-chat-last-n-entries (chat-id &optional (count 10)) (with-chat-credentials (chat-id) (let ((accounts (^account)) (transactions (remove nil (agets (^transaction :size count) "list") :key (agetter "type")))) (sort (loop for tr in transactions collect (move->entry (append tr (list (cons "account" (get-transaction-account tr accounts)))))) #'< :key #'pta-ledger:entry-date)))) (defun get-chat-accounts (chat-id) (with-chat-credentials (chat-id) (concatenate 'list (^account) (^loan)))) (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-entries chat-id 20)) (ledger-package (find-package :chatikbot.plugins.ledger))) (when new (when old (when-let (changes (set-difference new old :test #'equalp)) (log:info changes) (if ledger-package (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")))) (setf (gethash chat-id *last-entries*) new))))) (def-message-cmd-handler handler-gazprom (:gpn :gazprom) (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")))))