|
@@ -0,0 +1,99 @@
|
|
|
|
|
+(in-package #:chatikbot)
|
|
|
|
|
+
|
|
|
|
|
+(defsetting *transmission-settings* nil "ALIST of (chat-id . url)")
|
|
|
|
|
+
|
|
|
|
|
+(defvar *transmission-sessions* nil "ALIST of (url . x-transmission-session-id)")
|
|
|
|
|
+
|
|
|
|
|
+(defun transmission-get-session (url)
|
|
|
|
|
+ (multiple-value-bind (body status headers uri stream)
|
|
|
|
|
+ (drakma:http-request url :force-binary t :decode-content t)
|
|
|
|
|
+ (declare (ignore body status uri stream))
|
|
|
|
|
+ (aget :x-transmission-session-id headers)))
|
|
|
|
|
+
|
|
|
|
|
+(defun transmission-set-session (url session-id)
|
|
|
|
|
+ (set-setting '*transmission-sessions*
|
|
|
|
|
+ (cons (cons url session-id)
|
|
|
|
|
+ (remove url *transmission-sessions* :test #'equal :key #'car))))
|
|
|
|
|
+
|
|
|
|
|
+(defun transmission-request (url method &rest arguments)
|
|
|
|
|
+ (let ((session-id (aget url *transmission-sessions*))
|
|
|
|
|
+ (content
|
|
|
|
|
+ (with-output-to-string (stream)
|
|
|
|
|
+ (yason:encode (alexandria:plist-hash-table
|
|
|
|
|
+ (list "method" (dekeyify method t)
|
|
|
|
|
+ "arguments" (alexandria:plist-hash-table
|
|
|
|
|
+ (loop for (key value) on arguments by #'cddr
|
|
|
|
|
+ when value
|
|
|
|
|
+ appending (list (dekeyify key) value)))))
|
|
|
|
|
+ stream))))
|
|
|
|
|
+ (multiple-value-bind (stream status headers uri http-stream)
|
|
|
|
|
+ (drakma:http-request url :method :post :content content :content-type "application/json"
|
|
|
|
|
+ :force-binary t :want-stream t :decode-content t
|
|
|
|
|
+ :additional-headers (list (cons :x-transmission-session-id session-id)))
|
|
|
|
|
+ (declare (ignore uri))
|
|
|
|
|
+ (ecase status
|
|
|
|
|
+ (200 (unwind-protect
|
|
|
|
|
+ (progn
|
|
|
|
|
+ (setf (flex:flexi-stream-external-format stream) :utf-8)
|
|
|
|
|
+ (let* ((response (yason:parse stream :object-as :alist))
|
|
|
|
|
+ (result (aget "result" response)))
|
|
|
|
|
+ (unless (equal "success" result)
|
|
|
|
|
+ (error result))
|
|
|
|
|
+ (aget "arguments" response)))
|
|
|
|
|
+ (ignore-errors (close http-stream))))
|
|
|
|
|
+ (409 (transmission-set-session url (aget :x-transmission-session-id headers))
|
|
|
|
|
+ (apply #'transmission-request url method arguments))))))
|
|
|
|
|
+
|
|
|
|
|
+(defun transmission-get-torrents (url &optional ids (fields '("id" "name" "status" "percentDone" "eta" "totalSize")))
|
|
|
|
|
+ (aget "torrents" (transmission-request url :torrent-get :ids ids :fields fields)))
|
|
|
|
|
+
|
|
|
|
|
+(defun transmission-add-torrent (url &key filename metainfo)
|
|
|
|
|
+ (let ((torrent-added (transmission-request url :torrent-add :filename filename :metainfo metainfo)))
|
|
|
|
|
+ (car (transmission-get-torrents url (list (aget "id" (aget "torrent-added" torrent-added)))))))
|
|
|
|
|
+
|
|
|
|
|
+(defun %format-torrent-status (status)
|
|
|
|
|
+ (case status
|
|
|
|
|
+ (0 "⏹") ;; Stopped
|
|
|
|
|
+ (1 "🔂") ;; Check wait
|
|
|
|
|
+ (2 "🔁") ;; Check
|
|
|
|
|
+ (3 "⏸") ;; Queued to download
|
|
|
|
|
+ (4 "▶️") ;; Downloading
|
|
|
|
|
+ (5 "⏸") ;; Queued to seed
|
|
|
|
|
+ (6 "⏺") ;; Seeding
|
|
|
|
|
+ ))
|
|
|
|
|
+
|
|
|
|
|
+(defun %format-torrent (torrent)
|
|
|
|
|
+ (let ((eta (aget "eta" torrent)))
|
|
|
|
|
+ (format nil "~A ~A ~A (~A)~:[~; (~A%~@[, eta ~A~])~]"
|
|
|
|
|
+ (aget "id" torrent)
|
|
|
|
|
+ (%format-torrent-status (aget "status" torrent))
|
|
|
|
|
+ (aget "name" torrent)
|
|
|
|
|
+ (format-size (aget "totalSize" torrent))
|
|
|
|
|
+ (eq 4 (aget "status" torrent))
|
|
|
|
|
+ (smart-f (* 100 (aget "percentDone" torrent)) 1)
|
|
|
|
|
+ (when (not (= -1 eta)) (format-interval eta)))))
|
|
|
|
|
+
|
|
|
|
|
+(def-message-cmd-handler handle-torrents (:torrents)
|
|
|
|
|
+ (let ((url (aget chat-id *transmission-settings*)))
|
|
|
|
|
+ (if url
|
|
|
|
|
+ (bot-send-message chat-id
|
|
|
|
|
+ (format nil "~{~A~^~%~}" (mapcar #'%format-torrent
|
|
|
|
|
+ (transmission-get-torrents url))))
|
|
|
|
|
+ (bot-send-message chat-id "Бота настрой!"))))
|
|
|
|
|
+
|
|
|
|
|
+(defparameter +magnet-regex+ (ppcre:create-scanner "magnet:\\?\\S+"))
|
|
|
|
|
+
|
|
|
|
|
+(def-message-handler handle-magnet (message)
|
|
|
|
|
+ (alexandria:when-let* ((url (aget chat-id *transmission-settings*))
|
|
|
|
|
+ (magnet (ppcre:scan-to-strings +magnet-regex+ text)))
|
|
|
|
|
+ (bot-send-message chat-id (%format-torrent (transmission-add-torrent url :filename magnet)))))
|
|
|
|
|
+
|
|
|
|
|
+(def-message-handler handle-torrent (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)))
|
|
|
|
|
+ (let ((contents (telegram-file-content file-id)))
|
|
|
|
|
+ (bot-send-message chat-id (%format-torrent (transmission-add-torrent url :metainfo (cl-base64:usb8-array-to-base64-string contents))))))))
|