From 09d99c227f4f39487864829b57fd177f056acbe8 Mon Sep 17 00:00:00 2001
From: Sam <dxb@keybase.io>
Date: Fri, 4 Oct 2019 12:40:12 -0400
Subject: [PATCH] Add ReadMessage()

---
 chat.go  | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 types.go | 10 +++++-----
 2 files changed, 60 insertions(+), 5 deletions(-)

diff --git a/chat.go b/chat.go
index 4d2abf7..d27f3d0 100644
--- a/chat.go
+++ b/chat.go
@@ -2,6 +2,8 @@ package keybase
 
 import (
 	"bufio"
+	"encoding/base64"
+	"encoding/binary"
 	"encoding/json"
 	"errors"
 	"os/exec"
@@ -9,6 +11,36 @@ import (
 	"time"
 )
 
+// Returns a string representation of a message id suitable for use in a
+// pagination struct
+func getID(id int) string {
+	var b []byte
+	switch {
+	case id < 128:
+		// 7-bit int
+		b = make([]byte, 1)
+		b = []byte{byte(id)}
+
+	case id <= 255:
+		// uint8
+		b = make([]byte, 2)
+		b = []byte{204, byte(id)}
+
+	case id > 255 && id <= 65535:
+		// uint16
+		b = make([]byte, 2)
+		binary.BigEndian.PutUint16(b, uint16(id))
+		b = append([]byte{205}, b...)
+
+	case id > 65535 && id <= 4294967295:
+		// uint32
+		b = make([]byte, 4)
+		binary.BigEndian.PutUint32(b, uint32(id))
+		b = append([]byte{206}, b...)
+	}
+	return base64.StdEncoding.EncodeToString(b)
+}
+
 // Creates a string of a json-encoded channel to pass to keybase chat api-listen --filter-channel
 func createFilterString(channel Channel) string {
 	if channel.Name == "" {
@@ -223,6 +255,29 @@ func (k *Keybase) ChatList(opts ...Channel) (ChatAPI, error) {
 	return r, err
 }
 
+// ReadMessage fetches the chat message with the specified message id from a conversation.
+func (c Chat) ReadMessage(messageID int) (*ChatAPI, error) {
+	m := ChatAPI{
+		Params: &params{},
+	}
+	m.Params.Options = options{
+		Pagination: &pagination{},
+	}
+
+	m.Method = "read"
+	m.Params.Options.Channel = &c.Channel
+	m.Params.Options.Pagination.Num = 1
+
+	m.Params.Options.Pagination.Previous = getID(messageID - 1)
+
+	r, err := chatAPIOut(c.keybase, m)
+	if err != nil {
+		return &ChatAPI{}, err
+	}
+	r.keybase = *c.keybase
+	return &r, nil
+}
+
 // Read fetches chat messages from a conversation. By default, 10 messages will
 // be fetched at a time. However, if count is passed, then that is the number of
 // messages that will be fetched.
diff --git a/types.go b/types.go
index 98eea8d..ad279a1 100644
--- a/types.go
+++ b/types.go
@@ -22,10 +22,10 @@ type ChatAPI struct {
 	Message      string        `json:"message,omitempty"`
 	ID           int           `json:"id,omitempty"`
 	Ratelimits   []rateLimits  `json:"ratelimits,omitempty"`
-	Notification *notification `json:"notification"`
+	Notification *notification `json:"notification,omitempty"`
 	Result       *result       `json:"result,omitempty"`
-	Pagination   *pagination   `json:"pagination"`
-	Error        *Error        `json:"error"`
+	Pagination   *pagination   `json:"pagination,omitempty"`
+	Error        *Error        `json:"error,omitempty"`
 	keybase      Keybase       // Some methods will need this, so I'm passing it but keeping it unexported
 }
 
@@ -217,7 +217,7 @@ type notification struct {
 
 // Channel holds information about a conversation
 type Channel struct {
-	Name        string `json:"name"`
+	Name        string `json:"name,omitempty"`
 	Public      bool   `json:"public,omitempty"`
 	MembersType string `json:"members_type,omitempty"`
 	TopicType   string `json:"topic_type,omitempty"`
@@ -241,7 +241,7 @@ type options struct {
 	MsgID              int         `json:"msg_id,omitempty"`
 	GameID             string      `json:"game_id,omitempty"`
 
-	Name        string `json:"name"`
+	Name        string `json:"name,omitempty"`
 	Public      bool   `json:"public,omitempty"`
 	MembersType string `json:"members_type,omitempty"`
 	TopicType   string `json:"topic_type,omitempty"`