(in-package :twitter) ;; ;; Main API ;; (defmethod twitter-op (command &rest args) (let ((cmd (get-command command))) (multiple-value-bind (response code) (send-command cmd args) (if (eq code 200) (parse-command-response response (command-return-type cmd)) (parse-error-response response code))))) (defun send-command (command args) (multiple-value-bind (method url auth post-params) (command-request-arguments command args) (multiple-value-bind (response code) (http-request url :method method :basic-authorization (or auth (user-http-auth *twitter-user*)) :parameters (plist->alist post-params) :want-stream t :additional-headers *twitter-client-headers*) (values (safe-decode-json response) code)))) (defun safe-decode-json (response) (handler-case (decode-json response) (t () nil))) (defun parse-command-response (response type) (cond ((consp type) (mapcar (lambda (r) (parse-record r (first type))) response)) ((null type) nil) (t (parse-record response type)))) ;; ;; Public API for common commands ;; (defun authenticate-user (username password) (let ((user (get-user username))) (setf (twitter-user-password user) password) (handler-case (twitter-op :verify-credentials :user user) (twitter-api-condition () (return-from authenticate-user nil))) (setf *twitter-user* user) (twitter-op :user-show :id (twitter-user-screen-name user)))) (defun authenticated-user () *twitter-user*) ;; ;; Updates ;; (defun public-timeline (&rest args) (apply 'twitter-op :public-timeline args)) (defun timeline (&rest args) (apply 'twitter-op :user-timeline args)) (defun friends-timeline (&rest args) (print-tweets (apply 'twitter-op :friends-timeline args))) (defun send-tweet (text &rest args &key (tiny-url-p t) &allow-other-keys) (let ((newtext (if tiny-url-p (convert-to-tinyurl text) text))) (if (< (length newtext) 139) (apply 'twitter-op :tweet-update :status newtext (rem-keywords args '(:tiny-url-p))) (error "Tweet updates must be less than 140 characters")))) (defun update (text &rest args) (apply 'send-tweet text args)) (defun reply-to (tweet text &rest args) (apply 'send-tweet text :in-reply-to-status-id (tweet-id tweet) args)) (defun @reply-to (tweet text &rest args) (let ((fmt (format nil "@~A ~A" (twitter-user-screen-name (tweet-user tweet)) text))) (apply 'reply-to tweet fmt args))) ;; ;; Messages ;; (defun messages (&rest args) (apply 'twitter-op :messages-received args)) (defun sent-messages (&rest args) (apply 'twitter-op :messages-sent args)) (defun send-message (user message &rest args) (apply 'twitter-op :message-new :user (aif (get-user user) (twitter-user-id it) user) :text message args)) ;; ;; Objects ;; (defun print-tweets (tweets) (mapc #'print-tweet tweets)) ;; ;; Search API ;; (defun do-search (query &rest args) (let ((result (apply 'twitter-op :search :q query args))) (values (search-results result) result))) (defun trends (&rest args) (apply 'twitter-op :trends args)) ;; ;; Tiny URL API ;; ;;; TinyURL-ize ;;; Using the very simple TinyURL API (defparameter *tinyurl-url* "http://tinyurl.com/api-create.php") (defconstant +http-ok+ 200) (defun get-tinyurl (url) "Get a TinyURL for the given URL. Uses the TinyURL API service. (c) by Chaitanaya Gupta via cl-twit" (multiple-value-bind (body status-code) (http-request *tinyurl-url* :parameters `(("url" . ,url))) (if (= status-code +http-ok+) body (error 'http-error :status-code status-code :url url :body body)))) (defun convert-to-tinyurl (text) (let ((result text) (regex (ppcre:create-scanner "http:[^\\s\\)\\]\\'\\\"]+"))) (ppcre:do-matches (start end regex result result) (when (> (- end start) 24) (setf result (ppcre:regex-replace regex result (get-tinyurl (subseq result start end))))))))