deltacredit.lisp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. (in-package :cl-user)
  2. (defpackage chatikbot.plugins.deltacredit
  3. (:use :cl :chatikbot.common :alexandria))
  4. (in-package :chatikbot.plugins.deltacredit)
  5. (defparameter +api-uri+ "https://info.deltacredit.ru/webby_mobile/MobileService.svc/v1/")
  6. ;; poller methods
  7. (defun is-post (method)
  8. (or (equal method "auth")
  9. (equal method "bill/extract")))
  10. (defun is-raw-params (method)
  11. (equal method "bill/extract"))
  12. (defmethod poller-request ((module (eql :deltacredit)) method &rest params)
  13. (handler-case
  14. (let* ((is-post (is-post method))
  15. (is-raw (is-raw-params method))
  16. (res
  17. (json-request (concatenate 'string +api-uri+ method)
  18. :cookie-jar *poller-token*
  19. :method (if is-post :post :get)
  20. :json-content is-post
  21. (if is-post :content :parameters) (rest-parameters params is-raw))))
  22. (unless (agets res "result")
  23. (error (agets res "errMsg")))
  24. (agets res "data"))
  25. (dex:http-request-failed (e) e)))
  26. (defmethod poller-validate ((module (eql :deltacredit)) response)
  27. (not (typep response 'dex:http-request-failed)))
  28. (defmethod poller-get-token ((module (eql :deltacredit)) secret)
  29. (destructuring-bind (username . password) secret
  30. (let* ((*poller-token* (cl-cookie:make-cookie-jar)))
  31. (ignore-errors
  32. (poller-request :deltacredit "auth"
  33. :login username
  34. :password password)
  35. *poller-token*))))
  36. (defun list-credit ()
  37. (poller-call :deltacredit "credit/list"))
  38. (defun profile-info ()
  39. (poller-call :deltacredit "profile/info"))
  40. (defun bill-list ()
  41. (poller-call :deltacredit "bill/list"))
  42. (defun bill-extract (bill-id &key start-date finish-date)
  43. (poller-call :deltacredit "bill/extract" :|billId| bill-id :|startDate| start-date :|finishDate| finish-date))
  44. (defun format-credit (credit)
  45. (let ((cur (agets credit "creditCurrency")))
  46. (format nil "Кредит '~A' № ~A (~A)
  47. Всего: ~$ ~A ~A - ~A
  48. Осталось: ~$ ~A, след. платёж ~$ ~A ~A"
  49. (agets credit "contractType")
  50. (agets credit "contractNumber")
  51. (agets credit "contractStatus")
  52. (agets credit "creditAmount") cur
  53. (agets credit "sinceDate") (agets credit "untilDate")
  54. (agets credit "remainingDebt") cur
  55. (agets credit "nextPaymentAmount") cur (agets credit "nextPaymentDate"))))
  56. (defun format-bill (bill)
  57. (format nil "~A ~A (~A)
  58. Баланс: ~$ ~A"
  59. (agets bill "billTypeDescription")
  60. (agets bill "billNumber")
  61. (agets bill "statusDescription")
  62. (agets bill "balance") (agets bill "currency")))
  63. (defun format-entries (changes)
  64. (text-chunks (mapcar #'pta-ledger:render changes)))
  65. (defvar *entry-description* "deltacredit")
  66. (defvar *income-account* "assets")
  67. (defvar *expense-account* "expenses")
  68. (defvar *expense-account-interest* "expenses:Banking:Interest:Mortage")
  69. (defvar *liabilities-account* "liabilities:delta:mortage")
  70. (defun get-op-date (tr)
  71. (local-time:timestamp-to-universal
  72. (apply #'local-time:encode-timestamp 0 0 0 0
  73. (mapcar #'parse-integer
  74. (split-sequence:split-sequence
  75. #\. (agets tr "operationDate"))))))
  76. (defun format-op-date (timestamp)
  77. (local-time:format-timestring
  78. nil timestamp
  79. :format '((:day 2) "/" (:month 2) "/" (:year 4))))
  80. (defun transaction->entry (tr)
  81. (let* ((pta-ledger (find-package :pta-ledger))
  82. (make-entry (symbol-function (intern "MAKE-ENTRY" pta-ledger)))
  83. (make-posting (symbol-function (intern "MAKE-POSTING" pta-ledger)))
  84. (make-amount (symbol-function (intern "MAKE-AMOUNT" pta-ledger)))
  85. (date (get-op-date tr))
  86. (comment (agets tr "paymentDescription"))
  87. (is-income (not (zerop (agets tr "inAmount"))))
  88. (amount (agets tr (if is-income "inAmount" "outAmount")))
  89. (expense-account (if is-income *income-account*
  90. (if (equal (subseq comment 0 17)
  91. "Гашение процентов")
  92. *expense-account-interest*
  93. *expense-account*)))
  94. (currency "RUB"))
  95. (funcall make-entry
  96. :date date
  97. :description *entry-description*
  98. :comment comment
  99. :postings (list
  100. (funcall make-posting
  101. :account expense-account
  102. :amount (funcall make-amount
  103. :quantity (* (if is-income -1 1) amount)
  104. :commodity currency))
  105. (funcall make-posting
  106. :account *liabilities-account*
  107. :amount (funcall make-amount
  108. :quantity (* (if is-income 1 -1) amount)
  109. :commodity currency))))))
  110. (defun get-transactions (&optional (days 30))
  111. (let* ((now (local-time:now))
  112. (finish-date (format-op-date now))
  113. (start-date (format-op-date (local-time:timestamp- now days :day)))
  114. (bills (bill-list)))
  115. (loop for bill in bills
  116. append (bill-extract (agets bill "id") :start-date start-date :finish-date finish-date))))
  117. (defun process-new (diff)
  118. (let ((transactions (mapcar #'transaction->entry diff))
  119. (ledger-package (find-package :chatikbot.plugins.ledger)))
  120. (if ledger-package
  121. (let ((new-chat-entry (symbol-function
  122. (intern "LEDGER/NEW-CHAT-ENTRY" ledger-package))))
  123. (dolist (tr transactions)
  124. (funcall new-chat-entry *chat-id* tr)))
  125. (bot-send-message (format-entries transactions) :parse-mode "markdown"))))
  126. ;; Cron
  127. (defcron process-deltacredit (:minute '(member 0 10 20 30 40 50))
  128. (poller-poll-lists :deltacredit
  129. #'get-transactions
  130. #'process-new
  131. :key #'get-op-date))
  132. (defun handle-auth (login pass)
  133. (handler-case
  134. (progn
  135. (poller-authenticate :deltacredit (cons login pass))
  136. (handle-balance))
  137. (poller-cant-authenticate ()
  138. (bot-send-message "Чот не смог, пропробуй другие."))))
  139. (defun handle-balance ()
  140. (bot-send-message
  141. (handler-case
  142. (let ((entries (append (mapcar 'format-credit (list-credit))
  143. (mapcar 'format-bill (bill-list)))))
  144. (if entries (text-chunks entries) "Не нашлось"))
  145. (poller-no-secret () "Нужен логин-пароль. /delta <login> <pass>")
  146. (poller-cant-get-token () "Не смог получить данные. Попробуй перелогинься. /delta <login> <pass>"))
  147. :parse-mode "markdown"))
  148. (defun handle-recent (&optional (days 30))
  149. (bot-send-message
  150. (handler-case
  151. (format-entries (mapcar #'transaction->entry (get-transactions days)))
  152. (poller-no-secret () "Нужен логин-пароль. /delta <login> <pass>")
  153. (poller-cant-get-token () "Не смог получить данные. Попробуй перелогинься. /delta <login> <pass>"))
  154. :parse-mode "markdown"))
  155. (def-message-cmd-handler handle-cmd-delta (:deltacredit :delta)
  156. (let ((a0 (car *args*)))
  157. (cond
  158. ((= 2 (length *args*)) (apply 'handle-auth *args*))
  159. ((or (null *args*) (equal a0 "bal")) (handle-balance))
  160. (:otherwise (handle-recent (parse-integer a0 :junk-allowed t))))))