62e9d7d6 |
;;;; hhgbot.lisp
(in-package #:hhgbot)
(eval-when (:compile-toplevel :load-toplevel :execute)
(set-dispatch-macro-character #\# #\{
(lambda (stream char param)
(declare (ignore char param))
(let ((elems (read-delimited-list #\} stream t))
(rest-sym (gensym "rest")))
`(lambda (&rest ,rest-sym)
(apply ',(car elems) ,@(cdr elems) ,rest-sym)))))
(set-macro-character #\} (get-macro-character #\) nil)))
(defparameter *api-token*
|
62e9d7d6 |
(defun write-crlf (stream)
(format stream "~c"
#\linefeed))
(defun write-crlf (stream)
(format stream "~c~c"
#\return
#\linefeed))
; GET /websocket/lEEwhrr2xA4rxHXxY0bPY7Ir06hXul4yhvYfIN2iU8-zBRIHrDIMfTDhIzbs67fAKy0Iw2wrLY1mggMTkO1xAq8WtGTVYMtdE4HhP7jWQrLJNxfSsneGAuvAN_wGWvW9cPxv6hLBjFfA_QQl3FFwHnspEQelNdKGj8ISdIsYYLI= HTTP/1.1
;
; Host: mpmulti-y1d8.slack-msgs.com:443
; Upgrade: WebSocket
; Connection: Upgrade
; Pragma: no-cache
; Cache-Control: no-cache
; Sec-WebSocket-Key: fcat0W4ssKWT29LZoAKgaw==
; Sec-WebSocket-Version: 13
(defun format-with-crlf (s control &rest args)
(apply #'format s control args)
(write-crlf s))
(defun make-connection-string (puri &optional s)
;(let ((s (make-broadcast-stream *standard-output* s)))
(fresh-line s)
(format-with-crlf s "GET ~a HTTP/1.1" (puri:uri-path puri))
(format-with-crlf s "Host: ~a:443" (puri:uri-host puri))
(format-with-crlf s "User-Agent: hhgbot")
(format-with-crlf s "Upgrade: websocket")
(format-with-crlf s "Connection: Upgrade")
(format-with-crlf s "Pragma: no-cache")
(format-with-crlf s "Cache-Control: no-cache")
(format-with-crlf s "Sec-WebSocket-Key: fcat0W4ssKWT29LZoAKgaw==")
(format-with-crlf s "Sec-WebSocket-Version: 13")
(format-with-crlf s "")
(finish-output s)
);)
(defclass content-type ()
((%genus :initarg :genus :initform (error "need a genus") :reader genus)
(%species :initarg :species :initform (error "need a species") :reader species)
(%metadata :initarg :metadata :initform '() :reader metadata)))
(defmethod print-object ((object content-type) s)
(print-unreadable-object (object s :type t :identity t)
(format s "~a/~a ~s"
(genus object)
(species object)
(metadata object)
)))
(defgeneric parse-header-value (key value)
(:method (key value)
value))
(defmethod parse-header-value ((key (eql :content-length)) value)
(parse-integer value))
(defmethod parse-header-value ((key (eql :expires)) value)
(parse-integer value))
(defmethod parse-header-value ((key (eql :content-type)) value)
(let ((parts (mapcar (plambda (string-trim '(#\space #\tab) :1))
(split-sequence:split-sequence #\; value))))
(destructuring-bind (content-type . parameters) parts
(destructuring-bind (type subtype) (split-sequence:split-sequence #\/ content-type)
(make-instance 'content-type
:genus type
:species subtype
:metadata (mapcar (plambda
(funcall (alexandria:compose #'alexandria:make-keyword #'string-upcase)
(split-sequence:split-sequence #\= :1)))
parameters))))))
(defun parse-header (header-string)
(declare (optimize (debug 3)))
(let ((keywords-to-remove '()))
(flet ((temp-keyword (name)
(declare (optimize (debug 3)))
(multiple-value-bind (keyword status) (funcall (alexandria:compose #'alexandria:make-keyword #'string-upcase)
name))))
(let* ((sep-position (position #\: header-string))
(name (alexandria:make-keyword
(string-upcase
(subseq header-string 0 sep-position))))
(value (subseq header-string (+ 2 sep-position))))
(cons name
(parse-header-value name value))))))
(defun get-google (puri char-stream &optional (ostream char-stream))
(make-connection-string puri ostream)
(loop with buf = (make-string 1)
for q = (read-sequence buf char-stream)
when (> q 0)
do (princ buf)))
(defun ssl-connect (puri port continuation)
(let ((hn (puri:uri-host puri)))
(usocket:with-client-socket (socket stream hn port
:element-type '(unsigned-byte 8))
(let* ((ssl-stream (cl+ssl:make-ssl-client-stream stream :hostname hn))
(char-stream (flexi-streams:make-flexi-stream ssl-stream
:external-format '(:utf-8))))
(unwind-protect
(progn (format t "~a~%" puri)
(funcall continuation puri char-stream
char-stream))
(close ssl-stream))))))
(progn (defparameter *ws-url*
(puri:parse-uri
(funcall (alexandria:compose #'cdr
(plambda (assoc "url" :1
:test #'string-equal)))
(yason:parse
(drakma:http-request
(format nil "https://slack.com/api/rtm.start?token=~a"
*api-token*)
:want-stream t)
:object-as :alist)))
)
(make-connection-string *ws-url* t)
(format t "'~a~%" *ws-url*)
)
(let ((headers '())
(body '())
(body-count 0)
(tmp-header-string nil)
(mode :header)
(*ws-url*
(puri:parse-uri
(funcall (alexandria:compose #'cdr
(plambda (assoc "url" :1
:test #'string-equal)))
(yason:parse
(drakma:http-request
(format nil "https://slack.com/api/rtm.start?token=~a"
*api-token*)
:want-stream t)
:object-as :alist)))))
(as:start-event-loop
(lambda ()
(declare (optimize (debug 3)))
(cl-async-ssl:tcp-ssl-connect
"slack.com" 443
(lambda (socket data)
(let* ((data (babel:octets-to-string data)))
(when tmp-header-string
(psetf data (concatenate 'string
tmp-header-string
data)
tmp-header-string nil))
(case mode
(:header
(loop for next-divide = (position #\return data)
while next-divide
for next-header = (subseq data 0 next-divide)
until (string= next-header "")
when (alexandria:starts-with-subseq "HTTP" next-header) do
(format t "Initial line: ~a~%" next-header)
unless (alexandria:starts-with-subseq "HTTP" next-header) do
(push (parse-header next-header)
headers)
(format t "GOT: ~s~%" (car headers))
when (< next-divide (1- (length data))) do
(setf data (subseq data (+ 2 next-divide)))
finally
(when (> (length data) 0)
(setf tmp-header-string data))
(when (string= next-header "")
(setf mode :body)
(push data body)
(incf body-count (length data))
(setf tmp-header-string ""))))
(:body
(push data body)
(incf body-count (length data))))
(format t "loop done, body count: ~d, content-length ~d ~%"
body-count
(cdr (assoc :content-length headers)))
(when (>= body-count
(or (cdr (assoc :content-length headers)) 0))
(as:close-socket socket))))
:event-cb (lambda (ev)
(format t "EV: ~a~%" ev))
:read-timeout 3
:data (with-output-to-string (s)
(make-connection-string *ws-url* s)))))
(values headers (apply #'concatenate 'string body)))
|