|
@@ -4,7 +4,7 @@
|
|
|
(in-package :chatikbot.plugins.raiffeisen)
|
|
(in-package :chatikbot.plugins.raiffeisen)
|
|
|
|
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
|
- (ql:quickload :cxml-stp))
|
|
|
|
|
|
|
+ (ql:quickload '(:cxml-stp :pta-ledger)))
|
|
|
|
|
|
|
|
(defvar *raif-ws* "https://connect.raiffeisen.ru/Mobile-WS/services/" "Base WS URL")
|
|
(defvar *raif-ws* "https://connect.raiffeisen.ru/Mobile-WS/services/" "Base WS URL")
|
|
|
(defvar *ua* "Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MMB29M) Android/3.19.0(459)" "User agent")
|
|
(defvar *ua* "Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MMB29M) Android/3.19.0(459)" "User agent")
|
|
@@ -175,77 +175,98 @@
|
|
|
(defun short-date (date)
|
|
(defun short-date (date)
|
|
|
(cl-ppcre:regex-replace-all "-" (subseq date 0 10) "/"))
|
|
(cl-ppcre:regex-replace-all "-" (subseq date 0 10) "/"))
|
|
|
|
|
|
|
|
-(defun move->journal (move)
|
|
|
|
|
|
|
+(defun move->entry (move)
|
|
|
(let* ((account (agets move :account))
|
|
(let* ((account (agets move :account))
|
|
|
(account1 (get-account-name account))
|
|
(account1 (get-account-name account))
|
|
|
(account2 (get-move-account2 move))
|
|
(account2 (get-move-account2 move))
|
|
|
(currency (get-account-currency account))
|
|
(currency (get-account-currency account))
|
|
|
- (date (short-date (agets move :commit-date)))
|
|
|
|
|
|
|
+ (date (pta-ledger:parse-date (short-date (agets move :commit-date))))
|
|
|
(payee (get-move-payee move))
|
|
(payee (get-move-payee move))
|
|
|
(description (get-move-description move))
|
|
(description (get-move-description move))
|
|
|
(amount (parse-float (agets move :amount)))
|
|
(amount (parse-float (agets move :amount)))
|
|
|
(expense (equal "0" (agets move :type))))
|
|
(expense (equal "0" (agets move :type))))
|
|
|
- (format nil "~A~@[ (~A)~] ~A~@[ ; ~A~]~% ~37A ~A~,2F ~A~% ~37A ~A~,2F ~A"
|
|
|
|
|
- date nil payee description
|
|
|
|
|
- account2 (if expense " " "-") amount currency
|
|
|
|
|
- account1 (if expense "-" " ") amount currency)))
|
|
|
|
|
|
|
+ (pta-ledger:make-entry
|
|
|
|
|
+ :date date
|
|
|
|
|
+ :description payee
|
|
|
|
|
+ :comment description
|
|
|
|
|
+ :postings (list
|
|
|
|
|
+ (pta-ledger:make-posting
|
|
|
|
|
+ :account account2
|
|
|
|
|
+ :amount (pta-ledger:make-amount
|
|
|
|
|
+ :quantity (* amount (if expense 1 -1))
|
|
|
|
|
+ :commodity currency))
|
|
|
|
|
+ (pta-ledger:make-posting
|
|
|
|
|
+ :account account1
|
|
|
|
|
+ :amount (pta-ledger:make-amount
|
|
|
|
|
+ :quantity (* amount (if expense -1 1))
|
|
|
|
|
+ :commodity currency))))))
|
|
|
|
|
|
|
|
-(defun move->balance (move)
|
|
|
|
|
- (let ((account (agets move :account)))
|
|
|
|
|
- (format nil "; balance ~A ~A = ~,2F ~A"
|
|
|
|
|
- (short-date (agets account :balance-date))
|
|
|
|
|
- (get-account-name account)
|
|
|
|
|
- (parse-float (agets account :balance))
|
|
|
|
|
- (get-account-currency account))))
|
|
|
|
|
|
|
+(defun account->balance (account)
|
|
|
|
|
+ (format nil "; balance ~A ~A = ~,2F ~A"
|
|
|
|
|
+ (short-date (agets account :balance-date))
|
|
|
|
|
+ (get-account-name account)
|
|
|
|
|
+ (parse-float (agets account :balance))
|
|
|
|
|
+ (get-account-currency account)))
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun get-last-n-movements (&optional (count 10))
|
|
(defun get-last-n-movements (&optional (count 10))
|
|
|
(loop for account in (get-accounts (rc/get-batch-response))
|
|
(loop for account in (get-accounts (rc/get-batch-response))
|
|
|
- append (loop for move in (nreverse (rc/get-last-account-movements account count))
|
|
|
|
|
|
|
+ append (loop for move in (rc/get-last-account-movements account count)
|
|
|
collect (cons (cons :account (node->alist account t))
|
|
collect (cons (cons :account (node->alist account t))
|
|
|
(node->alist move)))))
|
|
(node->alist move)))))
|
|
|
|
|
|
|
|
(defun get-last-movements (begin-ut end-ut)
|
|
(defun get-last-movements (begin-ut end-ut)
|
|
|
(loop for account in (get-accounts (rc/get-batch-response))
|
|
(loop for account in (get-accounts (rc/get-batch-response))
|
|
|
- append (loop for move in (nreverse (rc/get-account-movements account begin-ut end-ut))
|
|
|
|
|
|
|
+ append (loop for move in (rc/get-account-movements account begin-ut end-ut)
|
|
|
collect (cons (cons :account (node->alist account t))
|
|
collect (cons (cons :account (node->alist account t))
|
|
|
(node->alist move)))))
|
|
(node->alist move)))))
|
|
|
|
|
|
|
|
-(defun format-changes (movements)
|
|
|
|
|
- (format nil "```~%~{~A~^~%~%~}```"
|
|
|
|
|
- (append (mapcar #'move->journal movements)
|
|
|
|
|
- (list (move->balance (car (last movements)))))))
|
|
|
|
|
|
|
+(defun format-changes (entries)
|
|
|
|
|
+ (format nil "```~%~{~A~^~%~%~}```" (mapcar #'pta-ledger:render entries)))
|
|
|
|
|
+
|
|
|
|
|
+(defun format-balance (accounts)
|
|
|
|
|
+ (format nil "```~%~{~A~^~%~}```" (mapcar #'account->balance accounts)))
|
|
|
|
|
|
|
|
;; Cron
|
|
;; Cron
|
|
|
-(defvar *last-movements* (make-hash-table) "Last per-chat movements")
|
|
|
|
|
|
|
+(defvar *last-entries* (make-hash-table) "Last per-chat entries")
|
|
|
(defvar *cookie-jars* (make-hash-table) "Per-chat cookie jars")
|
|
(defvar *cookie-jars* (make-hash-table) "Per-chat cookie jars")
|
|
|
|
|
|
|
|
-(defun get-chat-last-movements (chat-id &optional (offset +day+))
|
|
|
|
|
- (let* ((*cookie-jar* (or (gethash chat-id *cookie-jars*)
|
|
|
|
|
- (cl-cookie:make-cookie-jar)))
|
|
|
|
|
- (*credentials-provider* (lambda (authenticator)
|
|
|
|
|
- (with-secret (login-pass (list :raiffeisen chat-id))
|
|
|
|
|
|
|
+(defmacro with-chat-credentials ((chat-id) &body body)
|
|
|
|
|
+ `(let* ((*cookie-jar* (or (gethash ,chat-id *cookie-jars*)
|
|
|
|
|
+ (cl-cookie:make-cookie-jar)))
|
|
|
|
|
+ (*credentials-provider* (lambda (authenticator)
|
|
|
|
|
+ (with-secret (login-pass (list :raiffeisen ,chat-id))
|
|
|
(if login-pass
|
|
(if login-pass
|
|
|
(apply authenticator login-pass)
|
|
(apply authenticator login-pass)
|
|
|
- (error "no raiffeisen credentials for ~A" chat-id)))))
|
|
|
|
|
- (now (get-universal-time))
|
|
|
|
|
- (pre (- now offset))
|
|
|
|
|
- (new (get-last-movements pre now)))
|
|
|
|
|
- (when new
|
|
|
|
|
- (setf (gethash chat-id *cookie-jars*) *cookie-jar*))
|
|
|
|
|
- new))
|
|
|
|
|
|
|
+ (error "no raiffeisen credentials for ~A" ,chat-id))))))
|
|
|
|
|
+ (prog1 (progn ,@body)
|
|
|
|
|
+ (setf (gethash chat-id *cookie-jars*) *cookie-jar*))))
|
|
|
|
|
+
|
|
|
|
|
+(defun get-chat-last-entries (chat-id &optional (offset +day+))
|
|
|
|
|
+ (with-chat-credentials (chat-id)
|
|
|
|
|
+ (let* ((now (get-universal-time))
|
|
|
|
|
+ (pre (- now offset)))
|
|
|
|
|
+ (sort (mapcar #'move->entry (get-last-movements pre now))
|
|
|
|
|
+ #'< :key #'pta-ledger:entry-date))))
|
|
|
|
|
+
|
|
|
|
|
+(defun get-chat-accounts (chat-id)
|
|
|
|
|
+ (with-chat-credentials (chat-id)
|
|
|
|
|
+ (mapcar #'node->alist (get-accounts (rc/get-batch-response)))))
|
|
|
|
|
|
|
|
(defcron process-raiffeisen (:minute '(member 0 5 10 15 20 25 30 35 40 45 50 55))
|
|
(defcron process-raiffeisen (:minute '(member 0 5 10 15 20 25 30 35 40 45 50 55))
|
|
|
(dolist (chat-id (lists-get :raiffeisen))
|
|
(dolist (chat-id (lists-get :raiffeisen))
|
|
|
- (let ((old (gethash chat-id *last-movements*))
|
|
|
|
|
- (new (get-chat-last-movements chat-id (* 7 +day+))))
|
|
|
|
|
|
|
+ (let ((old (gethash chat-id *last-entries*))
|
|
|
|
|
+ (new (get-chat-last-entries chat-id (* 7 +day+))))
|
|
|
(when new
|
|
(when new
|
|
|
- (log:info "Got ~A raif events" (length new))
|
|
|
|
|
(when old
|
|
(when old
|
|
|
(alexandria:when-let (changes (set-difference new old :test #'equal))
|
|
(alexandria:when-let (changes (set-difference new old :test #'equal))
|
|
|
|
|
+ (log:info changes)
|
|
|
(bot-send-message chat-id (format-changes changes) :parse-mode "markdown")))
|
|
(bot-send-message chat-id (format-changes changes) :parse-mode "markdown")))
|
|
|
- (setf (gethash chat-id *last-movements*) new)))))
|
|
|
|
|
|
|
+ (setf (gethash chat-id *last-entries*) new)))))
|
|
|
|
|
|
|
|
(def-message-cmd-handler handler-raif (:raif)
|
|
(def-message-cmd-handler handler-raif (:raif)
|
|
|
- (let ((last (get-chat-last-movements chat-id (* (if args (parse-integer (car args)) 7) +day+))))
|
|
|
|
|
- (bot-send-message chat-id (format-changes last) :parse-mode "markdown")))
|
|
|
|
|
|
|
+ (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-entries chat-id (* (if arg (parse-integer arg) 7) +day+))))
|
|
|
|
|
+ (bot-send-message chat-id (format-changes last) :parse-mode "markdown")))))
|