~skin/pixie

562406b55d19335d22acbf41c5433c24f234a368 — Daniel Jay Haskin 9 months ago 96c67f1
Start on room names feature
3 files changed, 132 insertions(+), 96 deletions(-)

M src/client.lisp
M src/clients/groupme.lisp
M src/main.lisp
M src/client.lisp => src/client.lisp +16 -6
@@ 10,11 10,12 @@
    ")
    ;(import-from #:pixie/clients/fs)
  (:export
    make-client-system
    make-client
    account-names
    account
    make-account
    whoami
    room-names
    ;conversations
    ;connect
    ;name


@@ 30,7 31,7 @@

(in-package #:skin.djha.pixie/client)

(defgeneric make-account (kind specifics)
(defgeneric make-client (kind specifics)
            (
             :documentation
             "


@@ 48,13 49,22 @@
             )
            )

(defclass root ()
(defgeneric room-names (account)
            (
             :documentation
             "
             Return a list of room names.
             "
             )
            )

(defclass client-system ()
  ((accounts :initarg :accounts
             :initform (error "Must specify accounts.")
             :accessor accounts
             :type hash-table)))

(defun make-client (config)
(defun make-client-system (config)
  (let ((account-objects (make-hash-table :test #'equal)))
    (loop for slug being the hash-keys of (gethash :accounts config)
          using (hash-value payload)


@@ 65,10 75,10 @@
                    using (hash-value specifics)
                    do
                    (setf (gethash slug account-objects)
                          (skin.djha.pixie/client:make-account
                          (skin.djha.pixie/client:make-client
                            kind
                            specifics)))))
  (make-instance 'root :accounts account-objects)))
  (make-instance 'client-system :accounts account-objects)))

(defun account-names (client)
  (loop for acc being the hash-keys of (accounts client)

M src/clients/groupme.lisp => src/clients/groupme.lisp +86 -70
@@ 52,7 52,7 @@
    )
  )

(defmethod skin.djha.pixie/client:make-account ((kind (eql :groupme)) specifics)
(defmethod skin.djha.pixie/client:make-client  ((kind (eql :groupme)) specifics)
  (declare (type hash-table specifics))
  (make-instance 'groupme-client
                 :api-token (gethash :api-token specifics)


@@ 63,11 63,10 @@
(defun simple-get
  (
   client
   path
   &key
   query
   path
   )
      (format t "API Token: ~A" (api-token client))
  (multiple-value-bind
    (response code headers redir)
    (let ((q (acons "token" (api-token client) query)))


@@ 86,15 85,84 @@
      (error "bad")
        (let ((unwrapped (with-input-from-string (strm response)
                          (nrdl:parse-from strm))))
          (format t "Unwrapped ~A~%" (nrdl:nested-to-alist unwrapped))
          (format t "Response ~A~%" (nrdl:nested-to-alist (gethash "response" unwrapped)))
          (gethash "response" unwrapped)))))

(defun adjoin-entity
  (nick id ids)
  "
  This function collapses all known nicknames for a given set of ids,
  choosing the longest nickname for each entity id.
  This helps to build a mapping between ids and nicknames.
  "
  (let ((prior (assoc id ids)))
    (when (or (not prior)
            (< (length (cdr prior))
               (length nick)))
      (acons id nick ids))))

(defun insert-by-name (nick id mapping)
  "
  Insert a `'<nick>' -> id` mapping into a
  larger mapping from nicknames to ids.

  If the nick already exists, attempt to insert '<nick> 1'.
  If that exists, attempt to insert '<nick> 2', and so forth.
  If the id is already present in the mapping under one of the
  attempted nicknames, then do nothing.
  "
  (loop
    for i = 1 then (+ i 1)
    for tried = nick then (format nil "~A ~A" nick i)
    for prior-id = (gethash tried mapping)
    while prior-id
    do
    (when (equal prior-id id)
      (return))
    finally
    (setf
      (gethash tried mapping)
      id))
  mapping)

(defun names-ids (response)
  "
  Return a hash table mapping nicknames to ids given the API response.
  "
  (let ((collapsed-nicks
          (loop for record in response
                for nick = (gethash "name" record)
                for id = (gethash "id" record)
                with accumulate = nil
                do
                (adjoin-entity nick id accumulate)
                finally
                (return 
                  (alexandira:alist-hash-table
                    (sort accumulate (lambda (a b)
                                       ; Sort based on lexicographic sort
                                       ; of nicknames.
                                       (string< (cdr a) (cdr b)))))))))
    (loop for id being the hash-keys of collapsed-nicks
          using (hash-value nick)
          with accumulate = (make-hash-table :test #'equal)
          do
          (insert-by-name
            nick
            id
            accumulate)
          finally
          (return accumulate))))

(defmethod skin.djha.pixie/client:whoami
    ((client groupme-client))
  (simple-get client
              :path '("users" "me")))
              '("users" "me")))

(defmethod skin.djha.pixie/client:room-names
    ((client groupme-client))
  (names-ids (groupme-get '("groups")
                          :query '(("omit" . "memberships")))))
    
;(defun get-group-messages
;  (
;   client


@@ 137,75 205,23 @@
;          :host (host client)
;          :query q)))
;
(defun get-paginated
  (
   client
   &key
   query
   path
   (page 1)
   )
  (declare (type groupme-client client)
           (type list query)
           (type integer page))
  (multiple-value-bind
    (response code headers redir)
    (dexador:get
      (let* ((q (acons "token" (api-token client) query))
             (q (acons "page" page q)))
        (quri:make-uri
          :scheme (scheme client)
          :host (host client)
          :path (format nil "/~A~{/~A~}"
                        (base-path client)
                        path)
          :query q)))
    (declare (ignore headers)
             (ignore redir))
    (if (> code 399)
      (error "bad")
      (gethash
        "response"
        (with-input-from-string (strm response)
          (nrdl:parse-from strm))))))

(defun groupme-get
  (&rest groupme-get-paginated-args)
  (apply
    #'concatenate
    (cons 'vector
  (loop for page = 1 then (+ page 1)
        for response = (apply
                         #'get-paginated
                            (concatenate
                              'list
                              groupme-get-paginated-args
                              `(:page ,page)))
  (client path &key query)
  (loop with accumulate = (make-array 10 :fill-pointer t)
        for page = 1 then (+ page 1)
        for response = (simple-get client path (acons "page" page query))
        while (> (length response) 0)
        collect response))))
        do
        (loop for msg across response
              do
              (vector-push-extend msg accumulate))
        finally
        (return (coerce accumulate 'list))))



(defun adjoin-entity
  (nick id ids)
  (let ((prior (gethash id ids)))
    (when (or (not prior)
            (< (length prior)
               (length nick)))
      (setf (gethash id ids) nick))))

(defun insert-by-name (k v result)
  (loop
    for i = 1 then (+ i 1)
    for tried = k then (format nil "~A ~A" k i)
    for prior = (gethash tried result)
    while prior
    do
    (when (equal prior v)
      (return))
    finally
    (setf
      (gethash tried result)
      v))
  result)

;(defun ids-names
;  (ids)

M src/main.lisp => src/main.lisp +30 -20
@@ 35,32 35,38 @@
                              (:options . ,options))
                            :test #'equal)))))

;; TODO: Account-list
(defun account-list (options)
  (declare (type hash-table options))
  (alexandria:alist-hash-table `((:status . :successful)
                                 (:accounts .
                                  ,(skin.djha.pixie/client:account-names
                                     (skin.djha.pixie/client:make-client
                                     (skin.djha.pixie/client:make-client-system
                                       options))))
                               :test #'equal))

(defun client-operation (options opspec op)
  (declare (type hash-table options)
           (type function op))
  (if (null (gethash :account options))
      (alexandria:alist-hash-table
        `((:status . :cl-usage-error)
          (:options . ,options)
          (:error . "No account specified"))
        :test #'equal)
      (let ((client (skin.djha.pixie/client:make-client-system options)))
        (alexandria:alist-hash-table
          `((:status . :successful)
            (opspec .
             ,(funcall
                op
                (skin.djha.pixie/client:account
                  client
                  (gethash :account options)))))
          :test #'equal))))

(defun whoami (options)
  (declare (type hash-table options))
  (if (null (gethash :account options))
      (alexandria:alist-hash-table `((:status . :cl-usage-error)
                                     (:options . ,options)
                                     (:error . "No account specified"))
                                   :test #'equal)
      (let ((client (skin.djha.pixie/client:make-client options)))
        (format t "~A" (skin.djha.pixie/client:account-names client))
        (alexandria:alist-hash-table `((:status . :successful)
                                       (:whoami .
                                        ,(skin.djha.pixie/client:whoami
                                           (skin.djha.pixie/client:account
                                             client
                                             (gethash :account options)))))
                                     :test #'equal))))
    (client-operation options :whoami #'skin.djha.pixie/client:whoami))

(defun history (options)
  (declare (type hash-table options))


@@ 74,15 80,18 @@
  (declare (type hash-table options))
  (stub options "watch"))

(defun list-conversations (options)
(defun room-list (options)
  (declare (type hash-table options))
  (stub options "list-conversations"))
  (client-operation options :rooms #'skin.djha.pixie/client:room-names))

(defun help (options)
  (declare (type hash-table options))
  (stub options "help"))

(defun main (argv &key (strm t))
  "
  The functional entrypoint of the pixie command.
  "
  (declare (type list argv))
    (cl-i:execute-program
      "pixie"


@@ 94,7 103,7 @@
        (("history") . ,#'history)
        (("post") . ,#'post)
        (("watch") . ,#'watch)
        (("conversations") . ,#'list-conversations)
        (("room" "list") . ,#'room-list)
        (("help") . ,#'help)
        )
      :cli-arguments argv


@@ 109,7 118,8 @@

;(skin.djha.pixie:main '("whoami" "--set-account" "djhaskin987"))
;(skin.djha.pixie:main '("account" "list"))

;; TODO: Aggregate response, deal with symbols.
;(skin.djha.pixie:main '("room" "list" "--set-account" "djhaskin987"))
(defun entrypoint (argv)
  (uiop:quit
    (main argv :strm *standard-output*)))