فهرست منبع

Rates charting

Innocenty Enikeew 10 سال پیش
والد
کامیت
1e2c2fb841
5فایلهای تغییر یافته به همراه93 افزوده شده و 4 حذف شده
  1. 1 0
      chatikbot.asd
  2. 30 2
      chatikbot.lisp
  3. 19 1
      finance.lisp
  4. 17 1
      telegram.lisp
  5. 26 0
      utils.lisp

+ 1 - 0
chatikbot.asd

@@ -4,6 +4,7 @@
   :author "Innokentiy Enikeev <me@enikesha.net>"
   :author "Innokentiy Enikeev <me@enikesha.net>"
   :license "MIT"
   :license "MIT"
   :depends-on (#:alexandria
   :depends-on (#:alexandria
+               #:adw-charting-vecto
                #:bordeaux-threads
                #:bordeaux-threads
 ;;               #:cl-oauth
 ;;               #:cl-oauth
                #:clon
                #:clon

+ 30 - 2
chatikbot.lisp

@@ -67,6 +67,7 @@
               (:hourly (handle-cmd-weather chat-id id '("hourly")))
               (:hourly (handle-cmd-weather chat-id id '("hourly")))
               (:daily (handle-cmd-weather chat-id id '("daily")))
               (:daily (handle-cmd-weather chat-id id '("daily")))
               (:rates (handle-cmd-rates chat-id id args))
               (:rates (handle-cmd-rates chat-id id args))
+              (:charts (handle-cmd-charts chat-id id args))
               (:postcheckins (handle-cmd-post-checkins chat-id id args))
               (:postcheckins (handle-cmd-post-checkins chat-id id args))
               (:friends (handle-cmd-fsq-friends chat-id id args))
               (:friends (handle-cmd-fsq-friends chat-id id args))
               (:checkins (handle-cmd-checkins chat-id id args))
               (:checkins (handle-cmd-checkins chat-id id args))
@@ -170,11 +171,31 @@
       (telegram-send-message chat-id "Ошибочка вышла"))))
       (telegram-send-message chat-id "Ошибочка вышла"))))
 
 
 ;; Finance
 ;; Finance
+(defvar *per-minute-rates* (make-circular (make-list 1440))
+  "Circular list for 24h per minute rates")
+
+(defun process-rates ()
+  (handler-case
+      (push-circular (cons (local-time:timestamp-to-unix (local-time:now))
+                           (get-rates))
+                     *per-minute-rates*)
+    (error (e) (log:error e))))
+
 (defun handle-cmd-rates (chat-id message-id args)
 (defun handle-cmd-rates (chat-id message-id args)
   (log:info "handle-cmd-rates" chat-id message-id args)
   (log:info "handle-cmd-rates" chat-id message-id args)
-  (let ((rates (get-rates)))
+  (let ((rates (rest (peek-circular *per-minute-rates*))))
     (telegram-send-message chat-id
     (telegram-send-message chat-id
-			   (format nil "Зеленый ~A, гейро ~A, британец ~A" (cdar rates) (cdadr rates) (cdaddr rates)))))
+                           (format nil "Зеленый ~A, гейро ~A, британец ~A"
+                                   (cdar rates) (cdadr rates) (cdaddr rates)))))
+
+(defun handle-cmd-charts (chat-id message-id args)
+  (log:info "handle-cmd-charts" chat-id message-id args)
+  (telegram-send-chat-action chat-id "upload_photo")
+  (let ((chart (make-chart *per-minute-rates*))
+        (rates (rest (peek-circular *per-minute-rates*))))
+    (telegram-send-photo chat-id chart
+                         :caption (format nil "Зеленый ~A, гейро ~A, британец ~A"
+                                          (cdar rates) (cdadr rates) (cdaddr rates)))))
 
 
 ;; Weather
 ;; Weather
 (defun handle-cmd-weather (chat-id message-id args)
 (defun handle-cmd-weather (chat-id message-id args)
@@ -290,6 +311,7 @@
              (telegram-send-message chat-id (format nil "~{~A~^~%~}" texts))))
              (telegram-send-message chat-id (format nil "~{~A~^~%~}" texts))))
     (error (e) (log:error e))))
     (error (e) (log:error e))))
 
 
+
 (defun save-settings()
 (defun save-settings()
   (with-open-file (s (merge-pathnames "settings.lisp"
   (with-open-file (s (merge-pathnames "settings.lisp"
                                       (asdf:component-pathname
                                       (asdf:component-pathname
@@ -333,6 +355,12 @@
     (clon:make-typed-cron-schedule :minute '* :hour '*)
     (clon:make-typed-cron-schedule :minute '* :hour '*)
     :allow-now-p t)
     :allow-now-p t)
    :thread t)
    :thread t)
+  (clon:schedule-function
+   (lambda () (process-rates))
+   (clon:make-scheduler
+    (clon:make-typed-cron-schedule :minute '* :hour '*)
+    :allow-now-p t)
+   :thread t)
   ;; Start getUpdates thread
   ;; Start getUpdates thread
   (bordeaux-threads:make-thread
   (bordeaux-threads:make-thread
    (lambda ()
    (lambda ()

+ 19 - 1
finance.lisp

@@ -18,4 +18,22 @@
       (error "Error in rates request"))
       (error "Error in rates request"))
     (loop for rate in (aget "rate" (aget "results" (aget "query" response)))
     (loop for rate in (aget "rate" (aget "results" (aget "query" response)))
        collect (cons (aget "Name" rate)
        collect (cons (aget "Name" rate)
-		     (aget "Rate" rate)))))
+                     (read-from-string (aget "Rate" rate))))))
+
+(defun make-chart (series &key (usd t) (eur t) (gbp t))
+  (let ((flat (remove-if #'null (if (alexandria:circular-list-p series)
+                                     (flat-circular series)
+                                     series))))
+    (adw-charting:with-line-chart (1200 900)
+      (when usd
+        (adw-charting:add-series "USD/RUB" (loop for p in flat collect (list (car p) (cdadr p)))))
+      (when eur
+        (adw-charting:add-series "EUR/RUB" (loop for p in flat collect (list (car p) (cdaddr p)))))
+      (when gbp
+        (adw-charting:add-series "GBP/RUB" (loop for p in flat collect (list (car p) (cdr (cadddr p))))))
+      (adw-charting:set-axis
+       :x "Time" :draw-gridlines-p t
+       :label-formatter #'(lambda (v) (local-time:format-timestring nil (local-time:unix-to-timestamp v)
+                                                               :format '((:hour 2) ":" (:min 2)))))
+      (adw-charting:set-axis :y "RUB" :draw-gridlines-p t :label-formatter "~,2F")
+      (adw-charting:save-file "chart.png"))))

+ 17 - 1
telegram.lisp

@@ -23,7 +23,8 @@
 (defun %telegram-api-call (method &optional args)
 (defun %telegram-api-call (method &optional args)
   (let* ((params (loop for (k . v) in args collect (cons
   (let* ((params (loop for (k . v) in args collect (cons
                                                     (princ-to-string k)
                                                     (princ-to-string k)
-                                                    (princ-to-string v))))
+                                                    (if (pathnamep v) v
+                                                        (princ-to-string v)))))
          (timeout (+ 5 (or (cdr (assoc :timeout args))
          (timeout (+ 5 (or (cdr (assoc :timeout args))
                            *telegram-timeout*)))
                            *telegram-timeout*)))
          (response (yason:parse
          (response (yason:parse
@@ -66,3 +67,18 @@
          (cons "sticker" sticker)
          (cons "sticker" sticker)
          (cons "reply_to_message_id" reply-to)
          (cons "reply_to_message_id" reply-to)
          (cons "reply_markup" reply-markup))))
          (cons "reply_markup" reply-markup))))
+
+(defun telegram-send-photo (chat-id photo &key caption reply-to reply-markup)
+  (%telegram-api-call
+   "sendPhoto"
+   (list (cons "chat_id" chat-id)
+         (cons "photo" photo)
+         (cons "caption" caption)
+         (cons "reply_to_message_id" reply-to)
+         (cons "reply_markup" reply-markup))))
+
+(defun telegram-send-chat-action (chat-id action)
+  (%telegram-api-call
+   "sendChatAction"
+   (list (cons "chat_id" chat-id)
+         (cons "action" action))))

+ 26 - 0
utils.lisp

@@ -54,3 +54,29 @@ is replaced with replacement."
   "Append together elements (or lists) in the list."
   "Append together elements (or lists) in the list."
   (mappend #'(lambda (x) (if (listp x) (flatten x) (list x))) the-list))
   (mappend #'(lambda (x) (if (listp x) (flatten x) (list x))) the-list))
 
 
+(defun make-circular (items)
+  "Make items list circular"
+  (setf (cdr (last items)) items))
+
+(defmacro push-circular (obj circ)
+  "Move circ list and set head to obj"
+  `(progn
+     (pop ,circ)
+     (setf (car ,circ) ,obj)))
+
+(defmacro peek-circular (circ)
+  "Get head of circular list"
+  `(car ,circ))
+
+(defmacro pop-circular (circ)
+  "Get head of circular list"
+  `(pop ,circ))
+
+(defun flat-circular (circ)
+  "Flattens circular list"
+  (do ((cur (cdr circ) (cdr cur))
+       (head circ)
+       result)
+      ((eq head cur)
+       (nreverse (push (car cur) result)))
+    (push (car cur) result)))