Browse Source
- We now actually parse the contact and chat information we get sent after the connection handshake. - A massive protobuf autogenerated file has been added, which takes care of parsing the actual WhatsApp message objects -- i.e. the actual chat messages. We still need to actually translate that protobuf into something more usable though. - Also, we don't disconnect now, because we actually send pings.master
7 changed files with 13537 additions and 29 deletions
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
;;;; Higher-level API types |
||||
|
||||
(in-package :whatscl) |
||||
|
||||
(defclass contact () |
||||
((jid |
||||
:accessor contact-jid |
||||
:initarg :jid |
||||
:initform (error "A contact JID must be provided") |
||||
:documentation "JID (unique address) for this contact.") |
||||
(notify |
||||
:accessor contact-notify |
||||
:initarg :notify |
||||
:initform nil |
||||
:documentation "The name the user set for themselves on WhatsApp (i.e. the ~name).") |
||||
(name |
||||
:accessor contact-name |
||||
:initarg :name |
||||
:initform nil |
||||
:documentation "A name for this contact, as specified in the user's address book.")) |
||||
(:documentation "An entry in the list of the user's WhatsApp contacts.")) |
||||
|
||||
(defun parse-contact (contact) |
||||
"Parse the wire-formatted CONTACT into an actual `contact' object." |
||||
(make-instance 'contact |
||||
:jid (aval :jid contact) |
||||
:notify (cassoc :notify contact) |
||||
:name (cassoc :name contact))) |
||||
|
||||
(defmethod print-object ((obj contact) stream) |
||||
(print-unreadable-object (obj stream :type t) |
||||
(with-slots (jid notify name) obj |
||||
(format stream "~A~@[ (\"~~~A\")~]~@[ (\"~A\")~]" jid notify name)))) |
||||
|
||||
(defclass chat-entry () |
||||
((jid |
||||
:accessor chat-jid |
||||
:initarg :jid |
||||
:initform (error "A groupchat JID must be provided") |
||||
:documentation "JID (unique address) for this chat entry.") |
||||
(subject |
||||
:accessor chat-subject |
||||
:initarg :subject |
||||
:initform nil |
||||
:documentation "If this chat is a groupchat, the subject (topic) of the groupchat.") |
||||
(last-activity |
||||
:accessor chat-last-activity |
||||
:initarg :last-activity |
||||
:initform nil |
||||
:documentation "The Unix timestamp of the last thing which happened in this chat.") |
||||
(is-spam |
||||
:accessor chat-is-spam |
||||
:initarg :is-spam |
||||
:initform nil |
||||
:documentation "Whether the chat is marked as spam.") |
||||
(is-read-only |
||||
:accessor chat-is-read-only |
||||
:initarg :is-read-only |
||||
:initform nil |
||||
:documentation "Whether the chat is read-only (you can't send to it)") |
||||
(muted-until |
||||
:accessor chat-muted-until |
||||
:initarg :muted-until |
||||
:initform nil |
||||
:documentation "The Unix timestamp of when this chat is muted until, or NIL if the chat is not muted.") |
||||
(modify-tag |
||||
:accessor chat-modify-tag |
||||
:initarg :modify-tag |
||||
:initform nil)) |
||||
(:documentation "An entry in the user's list of WhatsApp chats (i.e. private messages and groupchats).")) |
||||
|
||||
(defun parse-chat-entry (entry) |
||||
"Parse the wire-formatted ENTRY into an actual `chat-entry' object." |
||||
(flet ((is-true (x) (equal x "true")) |
||||
(parse-muted (x) |
||||
(let ((ts (parse-integer x))) |
||||
(unless (eql ts 0) ts)))) |
||||
(make-instance 'chat-entry |
||||
:jid (aval :jid entry) |
||||
:subject (cassoc :name entry) |
||||
:last-activity (parse-integer (aval :t entry)) |
||||
:is-spam (map-when #'is-true (cassoc :spam entry)) |
||||
:is-read-only (map-when #'is-true (cassoc :read_only entry)) |
||||
:muted-until (map-when #'parse-muted (cassoc :mute entry)) |
||||
:modify-tag (cassoc :modify_tag entry)))) |
||||
|
||||
(defmethod print-object ((obj chat-entry) stream) |
||||
(print-unreadable-object (obj stream :type t) |
||||
(with-slots (jid subject) obj |
||||
(format stream "~A~@[ (\"~A\")~]" jid subject)))) |
||||
|
||||
(defun parse-pb-object (vector class length) |
||||
"Parse a protobuf object of class CLASS from the bytes stored in VECTOR." |
||||
(let ((ret (make-instance class))) |
||||
(pb:merge-from-array ret vector 0 length) |
||||
ret)) |
||||
|
||||
(defun parse-web-message (vector) |
||||
"Parse a protobuf chat message stored in VECTOR." |
||||
(parse-pb-object vector 'wpb:web-message-info (length vector))) |
@ -1,2 +1,5 @@
@@ -1,2 +1,5 @@
|
||||
(defpackage :whatscl |
||||
(:use :cl :event-emitter :alexandria :split-sequence)) |
||||
|
||||
(defpackage :whatscl/protobuf |
||||
(:nicknames :wpb)) |
||||
|
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
;;;; Utility functions |
||||
|
||||
(in-package :whatscl) |
||||
|
||||
(defmacro cassoc (key alist) |
||||
"Macro for (CDR (ASSOC KEY ALIST))." |
||||
`(cdr (assoc ,key ,alist))) |
||||
|
||||
(defmacro aval (key alist) |
||||
"Retrieves the value associated with KEY from the association list ALIST, throwing an error if the value wasn't found." |
||||
(let ((val-sym (gensym)) |
||||
(key-sym (gensym))) |
||||
`(let* ((,key-sym ,key) |
||||
(,val-sym (cassoc ,key-sym ,alist))) |
||||
(unless ,val-sym |
||||
(error "Malformed WhatsApp response: missing ~A" ,key-sym)) |
||||
,val-sym))) |
||||
|
||||
(defmacro map-when (func value) |
||||
"If VALUE is not NIL, applies FUNC to VALUE and returns the result." |
||||
(let ((val-sym (gensym))) |
||||
`(let ((,val-sym ,value)) |
||||
(when ,val-sym |
||||
(funcall ,func ,val-sym))))) |
@ -1,9 +1,11 @@
@@ -1,9 +1,11 @@
|
||||
(defsystem "whatscl" |
||||
:depends-on ("websocket-driver-client" "ironclad" "cl-qrencode" "qbase64" "cl-json" "babel" "alexandria" "split-sequence" "nibbles" "flexi-streams" "bordeaux-threads") |
||||
:depends-on ("websocket-driver-client" "ironclad" "cl-qrencode" "qbase64" "cl-json" "babel" "alexandria" "split-sequence" "nibbles" "flexi-streams" "bordeaux-threads" "com.google.base" "protobuf" "trivial-timer") |
||||
:serial t |
||||
:components |
||||
((:file "packages") |
||||
(:file "utils") |
||||
(:file "crypto") |
||||
(:file "connection") |
||||
(:file "binproto") |
||||
(:file "message_wire") |
||||
(:file "whatscl"))) |
||||
|
Loading…
Reference in new issue