106 Commits

Author SHA1 Message Date
Sam
3e5afcc7f9 Cleanup v1 2020-04-09 14:47:19 -04:00
Sam
274002b616 Use v2 types 2020-04-09 08:54:26 -04:00
Sam
abed5bc066 Version 2.0.0-alpha1 (really) 2020-04-08 22:49:41 -04:00
Sam
a3def6207a AVDL compilec types in v2 2020-04-08 22:29:52 -04:00
Sam
05ac7d7330 Update AVDL compiled types 2020-04-08 13:18:10 -04:00
Sam
71955bb43c Version 2.0.0-alpha1 2020-04-06 13:32:51 -04:00
Sam
e05e93be9f Add avdl compiled types 2020-01-29 16:21:02 -05:00
Sam
c90ef2c03a Allow specifying revision on kvstore methods 2020-01-23 16:49:12 -05:00
Sam
b83a1e0227 Add delete method for kvstore 2020-01-22 08:35:24 -05:00
Sam
3b4b5b66fd Add put method for kvstore 2020-01-22 07:58:32 -05:00
Sam
63f9ef1ee2 Add Get method to kvstore 2020-01-17 17:00:53 -05:00
Sam
14c7ec6cec Add Keys method to kvstore 2020-01-17 16:38:53 -05:00
Sam
77e35028fa Add initial support for Keybase's kv store api 2020-01-17 13:55:21 -05:00
ae285fd0ce added Username to wResult to support reverse wallet lookups 2020-01-14 11:06:49 -07:00
c8c20dc28f added StellarUser function to perform reverse wallet lookups for keybase users 2020-01-14 11:06:30 -07:00
8a5685459a added support for SendEphemeral 2020-01-03 13:29:46 -07:00
Sam
0f7a350f1a Add error handling to general example 2019-12-24 01:02:32 -05:00
Sam
207ad74bdd Previous commit didn't work and broke other things. This is a different implementation to fix the same problems. 2019-12-23 22:27:49 -05:00
Sam
6f53d9c8c6 Catch errors from api-listen
When the `keybase chat api-listen` command returns a message with
an error, the format of the error json is unlike most (or all?)
other errors they return. This commit adds the `ErrorListen` field
to ChatAPI to catch those errors.
2019-12-20 08:29:02 -05:00
Sam
c4525fe850 add UserCard() 2019-12-03 16:49:55 -05:00
Sam
a37a0609eb add ListUserMemberships() 2019-11-30 19:04:07 -05:00
Sam
3e22b70efa Minor docs formatting update 2019-11-27 15:42:09 -05:00
Sam
4b1fcae196 Add devices field to UserLookup 2019-11-27 15:35:40 -05:00
Sam
c2f6e6954d Add UserLookup() 2019-11-27 15:10:17 -05:00
Sam
b247e10f1b Add additional fields to advertisemend methods 2019-11-04 08:19:19 -05:00
Sam
7263387124 Add AdvertiseCommand example to docs 2019-11-03 23:51:33 -05:00
Sam
bab44f8b9e Add inputs to ChatList() method on keybase interface 2019-11-03 23:27:09 -05:00
Sam
2e7df5deef Add bot advertisement methods 2019-11-03 23:24:46 -05:00
Sam
972d197182 Add support for receiving chat replies 2019-10-31 10:37:37 -04:00
Sam
b00e56e8dd Create new jsonData var with every new message instead of reusing the same var. This fixes the bug where IsEphemeral is true for every message after one ephemeral message is received, and probably countless other unknown bugs. 2019-10-22 23:44:10 -04:00
d6fe3a07cd Add Attachment and Object to types.go 2019-10-21 08:27:12 -04:00
Sam
e51bcca5ba Attempt to make the getID func more 32-bit friendly 2019-10-17 21:47:43 -04:00
Sam
a9e004c8b3 Consolidate different member-type structs into one member struct 2019-10-15 23:41:51 -04:00
Sam
4ccaf4c45a Add Reply() 2019-10-09 21:41:46 -04:00
Sam
09d99c227f Add ReadMessage() 2019-10-04 12:40:12 -04:00
Sam
384478af39 Update ChatList() to allow for more filtering 2019-10-01 22:10:53 -04:00
Sam
e4cdbc50c9 Allow TopicType to be specified when calling ChatList 2019-09-28 23:21:42 -04:00
Sam
74a93b765c Consolidate the various functions that call keybase status so it only gets called once 2019-09-27 22:40:38 -04:00
Sam
fc05a8d710 Add device name to Keybase 2019-09-27 22:20:45 -04:00
Sam
9c4b5cf202 Move appropriate methods to wallet interface 2019-09-23 00:59:44 -04:00
Sam
d2bdf62100 Add NewWallet method to keybase interface in types.go 2019-09-23 00:57:12 -04:00
Sam
e64220b071 Add Send and SendXLM methods to wallet interface 2019-09-23 00:47:43 -04:00
Sam
d04c10ae70 Add NewWallet() method 2019-09-23 00:47:14 -04:00
Sam
f3a507b8a8 Add wallet interface and type 2019-09-23 00:46:24 -04:00
Sam
e829386733 Added RequestPayment info 2019-09-22 01:26:35 -04:00
Sam
e3bf9364b8 Add CancelRequest to keybase 2019-09-22 01:18:21 -04:00
Sam
3b0984102c Add Mark method to Chat 2019-09-21 22:30:16 -04:00
Sam
9dbf3b1664 Add Pin and Unpin methods to Chat 2019-09-21 11:20:56 -04:00
Sam
798f98f679 Add additional information and an example to the docs 2019-09-18 00:58:32 -04:00
Sam
fd0188ce3d Add doc strings to Upload, Download, and LoadFlip 2019-09-18 00:13:30 -04:00
Sam
1739a81176 Update version() to use new Exec() command 2019-09-17 23:41:35 -04:00
Sam
30f10d40e1 Update loggedIn() to use new Exec() command 2019-09-17 23:40:36 -04:00
Sam
97cda8c249 Update username() to use new Exec() command 2019-09-17 23:39:51 -04:00
Sam
500a965df2 Increase default channel capacity to 100, and allow it to be set in RunOptions 2019-09-17 22:37:13 -04:00
Sam
a11d16faa0 Add RequestPayment to wallet api 2019-09-13 17:08:03 -04:00
Sam
c624a0d0f8 Update wallet api to use new Exec() command 2019-09-13 17:01:33 -04:00
Sam
05b6b83d3c Update team api to use new Exec() command 2019-09-13 16:56:16 -04:00
Sam
d01fa2a0fb Update chat api to use new Exec() command 2019-09-13 16:55:19 -04:00
Sam
da497dd04a Add Exec() 2019-09-13 16:53:08 -04:00
Sam
d962600f9c Make flipStatus.ResultInfo and flipStatus.ErrorInfo pointers to easily check if they're nil 2019-09-11 17:41:12 -04:00
Sam
e22b187730 First attempt to add LoadFlip() 2019-09-10 17:53:02 -04:00
Sam
d99546ae79 Add upload and download methods to chat 2019-09-07 10:06:50 -04:00
Sam
ca4ea152e9 Add better error handling 2019-08-10 11:31:00 -04:00
Sam
86cde9fc83 Add better error handling to wallet api 2019-08-10 10:36:06 -04:00
Sam
597feee008 Add RemoveUser() 2019-08-01 17:00:48 -04:00
Sam
c468039d94 Add functions for adding groups of Admins, Owners, Readers, and Writers to teams 2019-07-30 17:36:55 -04:00
Sam
881d8cbde1 Add error type to API types 2019-07-30 17:33:28 -04:00
Sam
bbe5e9b8a5 Add MemberList() for teams, and make AddUser() return output from MemberList() 2019-07-30 16:39:39 -04:00
Sam
9d51bd0423 Correct interface description 2019-07-30 16:00:49 -04:00
Sam
00f5e77716 Add AddUser() 2019-07-26 16:34:03 -04:00
Sam
a7d58558b2 Add CreateTeam() 2019-07-26 15:53:42 -04:00
Sam
0a6f33e429 First pass at adding Team API stuff 2019-07-26 15:33:55 -04:00
Sam
6e94b343de Update wallet types, and make wallet methods adhere to new structure 2019-07-26 14:47:44 -04:00
Sam
103b798008 Add additional types for payments 2019-07-26 14:08:46 -04:00
Sam
692a073417 Move wallet types into types.go, and standardize wallet stuff to more closely match chat stuff 2019-07-26 13:42:17 -04:00
Sam
ed6f57d6e6 Cleanup some variable names 2019-07-25 12:23:15 -04:00
Sam
370fce9406 Combine chatIn.go and chatOut.go into just chat.go 2019-07-25 12:20:14 -04:00
Sam
1b089d2833 Add Params to ChatAPI instance so we can fill it with data 2019-07-25 11:28:52 -04:00
Sam
a5a50f79cd Catch any JSON errors 2019-07-25 11:22:38 -04:00
Sam
0ea666392d Structs held in ChatAPI need to be pointers in order to not be included in marshalled JSON 2019-07-24 23:01:45 -04:00
Sam
d870b1aa3b Move Conversations and Offline from ChatAPI to result, where they belong 2019-07-23 15:43:32 -04:00
Sam
7cc8e14fe2 Move more types into types.go 2019-07-23 14:58:33 -04:00
Sam
9e443921c4 Update result struct to include stuff i forgot 2019-07-22 01:02:42 -04:00
Sam
143dde64f6 Added chat.Read(), along with Next() and Previous() 2019-07-21 22:51:18 -04:00
Sam
22f6125e36 Move more types into types.go and general cleanup 2019-07-21 21:34:50 -04:00
Sam
7c4a1b7ba4 Combine ChatIn and ChatOut structs to ChatAPI and move them to types.go 2019-07-21 21:30:39 -04:00
Sam
fc57dcf8ca Change Keybase type to a pointer so it can be re-used 2019-07-03 00:41:24 -04:00
Sam
c182fe7e44 Update comments for documentation 2019-07-03 00:15:14 -04:00
Sam
8c7f74594d Make variables in getNewMessages() more generally descriptive, and (hopefully) finally fix the function so it restarts the api-listen command when it dies 2019-07-02 23:47:55 -04:00
Sam
354fa145e1 Put cmd.Start() and cmd.Wait() in a loop so keybase restarts if it dies 2019-07-02 21:09:37 -04:00
Sam
51796165cf Move cmd.Wait() and put scanner in a go routine 2019-07-02 21:02:33 -04:00
Sam
075fa737af Add cmd.Wait() to Keybase.Run() 2019-07-02 21:02:23 -04:00
Sam
d05601455b Increment heartbeat counter 2019-07-02 21:01:56 -04:00
Sam
dcd697b559 Add option to send a heartbeat throush the channel 2019-07-02 10:54:25 -04:00
Sam
317af3fa39 Change Runner() to Run() and add ability to pass options 2019-06-27 23:08:22 -04:00
Sam
e077e50d6d Add StellarAddress() 2019-06-22 19:24:14 -04:00
Sam
17e3a7ab6d Update go.mod to accurately reflect package url for master branch 2019-06-14 23:07:44 -04:00
Sam
a3c0fbf447 Add support for git push messages 2019-06-12 17:21:32 -04:00
Sam
63e41c70ab Remove balances. Will readd later 2019-06-11 23:19:52 -04:00
Sam
e5fa55e398 Fix wallet output 2019-06-11 23:04:35 -04:00
Sam
399e0b4e94 Add TxDetail for stellar transactions 2019-06-11 21:12:39 -04:00
Sam
db332e733a update go.mod for dev branch 2019-06-11 08:14:28 -04:00
Sam
ae3ed73adc Add support for incoming messages. This breaks NewChat() in previous versions 2019-06-11 07:54:34 -04:00
Sam
4728aa6133 Start work on wallet API 2019-06-08 23:58:37 -04:00
Sam
efd8dab359 Add chat.Edit() 2019-06-08 16:01:46 -04:00
Sam
003ba3a4e0 Re-organize everything to make it cleaner... hopefully 2019-06-08 11:36:04 -04:00
12 changed files with 2138 additions and 331 deletions

556
chat.go Normal file
View File

@ -0,0 +1,556 @@
package keybase
import (
"bufio"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"os/exec"
"strings"
"time"
)
// Returns a string representation of a message id suitable for use in a
// pagination struct
func getID(id uint) 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 == "" {
return ""
}
jsonBytes, _ := json.Marshal(channel)
return string(jsonBytes)
}
// Creates a string of json-encoded channels to pass to keybase chat api-listen --filter-channels
func createFiltersString(channels []Channel) string {
if len(channels) == 0 {
return ""
}
jsonBytes, _ := json.Marshal(channels)
return string(jsonBytes)
}
// Run `keybase chat api-listen` to get new messages coming into keybase and send them into the channel
func getNewMessages(k *Keybase, c chan<- ChatAPI, execOptions []string) {
execString := []string{"chat", "api-listen"}
if len(execOptions) > 0 {
execString = append(execString, execOptions...)
}
for {
execCmd := exec.Command(k.Path, execString...)
stdOut, _ := execCmd.StdoutPipe()
execCmd.Start()
scanner := bufio.NewScanner(stdOut)
go func(scanner *bufio.Scanner, c chan<- ChatAPI) {
for scanner.Scan() {
var jsonData ChatAPI
json.Unmarshal([]byte(scanner.Text()), &jsonData)
if jsonData.ErrorRaw != nil {
var errorListen = string(*jsonData.ErrorRaw)
jsonData.ErrorListen = &errorListen
}
c <- jsonData
}
}(scanner, c)
execCmd.Wait()
}
}
// Run runs `keybase chat api-listen`, and passes incoming messages to the message handler func
func (k *Keybase) Run(handler func(ChatAPI), options ...RunOptions) {
var heartbeatFreq int64
var channelCapacity = 100
runOptions := make([]string, 0)
if len(options) > 0 {
if options[0].Capacity > 0 {
channelCapacity = options[0].Capacity
}
if options[0].Heartbeat > 0 {
heartbeatFreq = options[0].Heartbeat
}
if options[0].Local {
runOptions = append(runOptions, "--local")
}
if options[0].HideExploding {
runOptions = append(runOptions, "--hide-exploding")
}
if options[0].Dev {
runOptions = append(runOptions, "--dev")
}
if len(options[0].FilterChannels) > 0 {
runOptions = append(runOptions, "--filter-channels")
runOptions = append(runOptions, createFiltersString(options[0].FilterChannels))
}
if options[0].FilterChannel.Name != "" {
runOptions = append(runOptions, "--filter-channel")
runOptions = append(runOptions, createFilterString(options[0].FilterChannel))
}
}
c := make(chan ChatAPI, channelCapacity)
defer close(c)
if heartbeatFreq > 0 {
go heartbeat(c, time.Duration(heartbeatFreq)*time.Minute)
}
go getNewMessages(k, c, runOptions)
for {
go handler(<-c)
}
}
// heartbeat sends a message through the channel with a message type of `heartbeat`
func heartbeat(c chan<- ChatAPI, freq time.Duration) {
m := ChatAPI{
Type: "heartbeat",
}
count := 0
for {
time.Sleep(freq)
m.Msg.ID = count
c <- m
count++
}
}
// chatAPIOut sends JSON requests to the chat API and returns its response.
func chatAPIOut(k *Keybase, c ChatAPI) (ChatAPI, error) {
jsonBytes, _ := json.Marshal(c)
cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes))
if err != nil {
return ChatAPI{}, err
}
var r ChatAPI
if err := json.Unmarshal(cmdOut, &r); err != nil {
return ChatAPI{}, err
}
if r.ErrorRaw != nil {
var errorRead Error
json.Unmarshal([]byte(*r.ErrorRaw), &errorRead)
r.ErrorRead = &errorRead
return r, errors.New(r.ErrorRead.Message)
}
return r, nil
}
// Send sends a chat message
func (c Chat) Send(message ...string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Message: &mesg{},
}
m.Method = "send"
m.Params.Options.Channel = &c.Channel
m.Params.Options.Message.Body = strings.Join(message, " ")
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// SendEphemeral sends an exploding chat message, with specified duration
func (c Chat) SendEphemeral(duration time.Duration, message ...string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Message: &mesg{},
}
m.Params.Options.ExplodingLifetime.Duration = duration
m.Method = "send"
m.Params.Options.Channel = &c.Channel
m.Params.Options.Message.Body = strings.Join(message, " ")
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Reply sends a reply to a chat message
func (c Chat) Reply(replyTo int, message ...string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Message: &mesg{},
}
m.Method = "send"
m.Params.Options.Channel = &c.Channel
m.Params.Options.ReplyTo = replyTo
m.Params.Options.Message.Body = strings.Join(message, " ")
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Edit edits a previously sent chat message
func (c Chat) Edit(messageID int, message ...string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Message: &mesg{},
}
m.Method = "edit"
m.Params.Options.Channel = &c.Channel
m.Params.Options.Message.Body = strings.Join(message, " ")
m.Params.Options.MessageID = messageID
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// React sends a reaction to a message.
func (c Chat) React(messageID int, reaction string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Message: &mesg{},
}
m.Method = "reaction"
m.Params.Options.Channel = &c.Channel
m.Params.Options.Message.Body = reaction
m.Params.Options.MessageID = messageID
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Delete deletes a chat message
func (c Chat) Delete(messageID int) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "delete"
m.Params.Options.Channel = &c.Channel
m.Params.Options.MessageID = messageID
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// ChatList returns a list of all conversations.
// You can pass a Channel to use as a filter here, but you'll probably want to
// leave the TopicName empty.
func (k *Keybase) ChatList(opts ...Channel) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
if len(opts) > 0 {
m.Params.Options.Name = opts[0].Name
m.Params.Options.Public = opts[0].Public
m.Params.Options.MembersType = opts[0].MembersType
m.Params.Options.TopicType = opts[0].TopicType
m.Params.Options.TopicName = opts[0].TopicName
}
m.Method = "list"
r, err := chatAPIOut(k, m)
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(uint(messageID - 1))
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return &r, 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.
func (c Chat) Read(count ...int) (*ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Pagination: &pagination{},
}
m.Method = "read"
m.Params.Options.Channel = &c.Channel
if len(count) == 0 {
m.Params.Options.Pagination.Num = 10
} else {
m.Params.Options.Pagination.Num = count[0]
}
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return &r, err
}
r.keybase = *c.keybase
return &r, nil
}
// Next fetches the next page of chat messages that were fetched with Read. By
// default, Next will fetch the same amount of messages that were originally
// fetched with Read. However, if count is passed, then that is the number of
// messages that will be fetched.
func (c *ChatAPI) Next(count ...int) (*ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Pagination: &pagination{},
}
m.Method = "read"
m.Params.Options.Channel = &c.Result.Messages[0].Msg.Channel
if len(count) == 0 {
m.Params.Options.Pagination.Num = c.Result.Pagination.Num
} else {
m.Params.Options.Pagination.Num = count[0]
}
m.Params.Options.Pagination.Next = c.Result.Pagination.Next
result, err := chatAPIOut(&c.keybase, m)
if err != nil {
return &result, err
}
k := c.keybase
*c = result
c.keybase = k
return c, nil
}
// Previous fetches the previous page of chat messages that were fetched with Read.
// By default, Previous will fetch the same amount of messages that were
// originally fetched with Read. However, if count is passed, then that is the
// number of messages that will be fetched.
func (c *ChatAPI) Previous(count ...int) (*ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Params.Options = options{
Pagination: &pagination{},
}
m.Method = "read"
m.Params.Options.Channel = &c.Result.Messages[0].Msg.Channel
if len(count) == 0 {
m.Params.Options.Pagination.Num = c.Result.Pagination.Num
} else {
m.Params.Options.Pagination.Num = count[0]
}
m.Params.Options.Pagination.Previous = c.Result.Pagination.Previous
result, err := chatAPIOut(&c.keybase, m)
if err != nil {
return &result, err
}
k := c.keybase
*c = result
c.keybase = k
return c, nil
}
// Upload attaches a file to a conversation
// The filepath must be an absolute path
func (c Chat) Upload(title string, filepath string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "attach"
m.Params.Options.Channel = &c.Channel
m.Params.Options.Filename = filepath
m.Params.Options.Title = title
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Download downloads a file from a conversation
func (c Chat) Download(messageID int, filepath string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "download"
m.Params.Options.Channel = &c.Channel
m.Params.Options.Output = filepath
m.Params.Options.MessageID = messageID
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// LoadFlip returns the results of a flip
// If the flip is still in progress, this can be expected to change if called again
func (c Chat) LoadFlip(messageID int, conversationID string, flipConversationID string, gameID string) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "loadflip"
m.Params.Options.Channel = &c.Channel
m.Params.Options.MsgID = messageID
m.Params.Options.ConversationID = conversationID
m.Params.Options.FlipConversationID = flipConversationID
m.Params.Options.GameID = gameID
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Pin pins a message to a channel
func (c Chat) Pin(messageID int) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "pin"
m.Params.Options.Channel = &c.Channel
m.Params.Options.MessageID = messageID
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Unpin clears any pinned messages from a channel
func (c Chat) Unpin() (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "unpin"
m.Params.Options.Channel = &c.Channel
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Mark marks a conversation as read up to a specified message
func (c Chat) Mark(messageID int) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "mark"
m.Params.Options.Channel = &c.Channel
m.Params.Options.MessageID = messageID
r, err := chatAPIOut(c.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// ClearCommands clears bot advertisements
func (k *Keybase) ClearCommands() (ChatAPI, error) {
m := ChatAPI{}
m.Method = "clearcommands"
r, err := chatAPIOut(k, m)
if err != nil {
return r, err
}
return r, nil
}
// AdvertiseCommands sets up bot command advertisements
// This method allows you to set up multiple different types of advertisements at once.
// Use this method if you have commands whose visibility differs from each other.
func (k *Keybase) AdvertiseCommands(advertisements []BotAdvertisement) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
}
m.Method = "advertisecommands"
m.Params.Options.BotAdvertisements = advertisements
r, err := chatAPIOut(k, m)
if err != nil {
return r, err
}
return r, nil
}
// AdvertiseCommand sets up bot command advertisements
// This method allows you to set up one type of advertisement.
// Use this method if you have commands whose visibility should all be the same.
func (k *Keybase) AdvertiseCommand(advertisement BotAdvertisement) (ChatAPI, error) {
return k.AdvertiseCommands([]BotAdvertisement{
advertisement,
})
}

View File

@ -1,98 +0,0 @@
package keybase
import ()
type chatIn struct {
Type string `json:"type"`
Source string `json:"source"`
Msg chatInMsg `json:"msg"`
}
type chatInChannel struct {
Name string `json:"name"`
Public bool `json:"public"`
MembersType string `json:"members_type"`
TopicType string `json:"topic_type"`
TopicName string `json:"topic_name"`
}
type chatInSender struct {
UID string `json:"uid"`
Username string `json:"username"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
}
type chatInAddedtoteam struct {
Team string `json:"team"`
Adder string `json:"adder"`
Addee string `json:"addee"`
Owners []string `json:"owners"`
Admins []string `json:"admins"`
Writers []string `json:"writers"`
Readers []string `json:"readers"`
}
type chatInBulkaddtoconv struct {
Usernames []string `json:"usernames"`
}
type chatInSystem struct {
SystemType int `json:"systemType"`
Addedtoteam chatInAddedtoteam `json:"addedtoteam"`
Bulkaddtoconv chatInBulkaddtoconv `json:"bulkaddtoconv"`
}
type chatInResult struct {
ResultTyp int `json:"resultTyp"`
Sent string `json:"sent"`
}
type chatInPayments struct {
Username string `json:"username"`
PaymentText string `json:"paymentText"`
Result chatInResult `json:"result"`
}
type chatInUserMentions struct {
Text string `json:"text"`
UID string `json:"uid"`
}
type chatInTeamMentions struct {
Name string `json:"name"`
Channel string `json:"channel"`
}
type chatInReaction struct {
M int `json:"m"`
B string `json:"b"`
}
type chatInDelete struct {
MessageIDs []int `json:"messageIDs"`
}
type chatInEdit struct {
MessageID int `json:"messageID"`
Body string `json:"body"`
Payments []chatInPayments `json:"payments"`
UserMentions []chatInUserMentions `json:"userMentions"`
TeamMentions []chatInTeamMentions `json:"teamMentions"`
}
type chatInText struct {
Body string `json:"body"`
Payments []chatInPayments `json:"payments"`
UserMentions []chatInUserMentions `json:"userMentions"`
TeamMentions []chatInTeamMentions `json:"teamMentions"`
}
type chatInContent struct {
Type string `json:"type"`
Delete chatInDelete `json:"delete"`
Edit chatInEdit `json:"edit"`
Reaction chatInReaction `json:"reaction"`
System chatInSystem `json:"system"`
Text chatInText `json:"text"`
}
type chatInMsg struct {
ID int `json:"id"`
Channel chatInChannel `json:"channel"`
Sender chatInSender `json:"sender"`
SentAt int `json:"sent_at"`
SentAtMs int64 `json:"sent_at_ms"`
Content chatInContent `json:"content"`
Unread bool `json:"unread"`
AtMentionUsernames []string `json:"at_mention_usernames"`
IsEphemeral bool `json:"is_ephemeral"`
Etime int64 `json:"etime"`
HasPairwiseMacs bool `json:"has_pairwise_macs"`
ChannelMention string `json:"channel_mention"`
}

View File

@ -1,183 +0,0 @@
package keybase
import (
"encoding/json"
"os/exec"
"strings"
)
// ---- Struct for sending to API
type chatOut struct { // not exported
Method string `json:"method"`
Params chatOutParams `json:"params"`
}
type chatOutChannel struct {
Name string `json:"name"`
MembersType string `json:"members_type"`
TopicName string `json:"topic_name"`
}
type chatOutMessage struct {
Body string `json:"body"`
}
type chatOutOptions struct {
Channel chatOutChannel `json:"channel"`
MessageID int `json:"message_id"`
Message chatOutMessage `json:"message"`
}
type chatOutParams struct {
Options chatOutOptions `json:"options"`
}
// ----
// ---- Struct for data received after sending to API
type chatOutResult struct {
Result ChatOut `json:"result"`
}
type chatOutResultRatelimits struct {
Tank string `json:"tank,omitempty"`
Capacity int `json:"capacity,omitempty"`
Reset int `json:"reset,omitempty"`
Gas int `json:"gas,omitempty"`
}
type chatOutResultChannel struct {
Name string `json:"name"`
Public bool `json:"public"`
MembersType string `json:"members_type"`
TopicType string `json:"topic_type,omitempty"`
TopicName string `json:"topic_name,omitempty"`
}
type chatOutResultConversations struct {
ID string `json:"id"`
Channel chatOutResultChannel `json:"channel"`
Unread bool `json:"unread"`
ActiveAt int `json:"active_at"`
ActiveAtMs int64 `json:"active_at_ms"`
MemberStatus string `json:"member_status"`
}
type ChatOut struct { // exported
Message string `json:"message,omitempty"`
ID int `json:"id,omitempty"`
Ratelimits []chatOutResultRatelimits `json:"ratelimits,omitempty"`
Conversations []chatOutResultConversations `json:"conversations,omitempty"`
Offline bool `json:"offline,omitempty"`
}
// ----
// chatAPIOut() sends JSON requests to the chat API and returns its response.
func chatAPIOut(keybasePath string, c chatOut) (chatOutResult, error) {
jsonBytes, _ := json.Marshal(c)
cmd := exec.Command(keybasePath, "chat", "api", "-m", string(jsonBytes))
cmdOut, err := cmd.Output()
if err != nil {
return chatOutResult{}, err
}
var r chatOutResult
json.Unmarshal(cmdOut, &r)
return r, nil
}
// ChatSendText() sends a chat message to a user.
func (k Keybase) ChatSendText(user string, message ...string) (ChatOut, error) {
m := chatOut{}
m.Method = "send"
m.Params.Options.Channel.Name = user
m.Params.Options.Message.Body = strings.Join(message, " ")
r, err := chatAPIOut(k.path, m)
if err != nil {
return ChatOut{}, err
}
return r.Result, nil
}
// ChatSendTextTeam() sends a chat message to a team.
func (k Keybase) ChatSendTextTeam(team, channel string, message ...string) (ChatOut, error) {
m := chatOut{}
m.Method = "send"
m.Params.Options.Channel.Name = team
m.Params.Options.Channel.MembersType = "team"
m.Params.Options.Channel.TopicName = channel
m.Params.Options.Message.Body = strings.Join(message, " ")
r, err := chatAPIOut(k.path, m)
if err != nil {
return ChatOut{}, err
}
return r.Result, nil
}
// ChatReact() sends a reaction to a user's message.
func (k Keybase) ChatReact(user, reaction string, messageId int) (ChatOut, error) {
m := chatOut{}
m.Method = "reaction"
m.Params.Options.Channel.Name = user
m.Params.Options.MessageID = messageId
m.Params.Options.Message.Body = reaction
r, err := chatAPIOut(k.path, m)
if err != nil {
return ChatOut{}, err
}
return r.Result, nil
}
// ChatReactTeam() sends a reaction to a message on a team.
func (k Keybase) ChatReactTeam(team, channel, reaction string, messageId int) (ChatOut, error) {
m := chatOut{}
m.Method = "reaction"
m.Params.Options.Channel.Name = team
m.Params.Options.Channel.MembersType = "team"
m.Params.Options.Channel.TopicName = channel
m.Params.Options.MessageID = messageId
m.Params.Options.Message.Body = reaction
r, err := chatAPIOut(k.path, m)
if err != nil {
return ChatOut{}, err
}
return r.Result, nil
}
// ChatDeleteMessage() deletes a message from a one-on-one conversation.
func (k Keybase) ChatDeleteMessage(user string, messageId int) (ChatOut, error) {
m := chatOut{}
m.Method = "delete"
m.Params.Options.Channel.Name = user
m.Params.Options.MessageID = messageId
r, err := chatAPIOut(k.path, m)
if err != nil {
return ChatOut{}, err
}
return r.Result, nil
}
// ChatDeleteMessageTeam() deletes a message from a team conversation.
func (k Keybase) ChatDeleteMessageTeam(team, channel string, messageId int) (ChatOut, error) {
m := chatOut{}
m.Method = "delete"
m.Params.Options.Channel.Name = team
m.Params.Options.Channel.MembersType = "team"
m.Params.Options.Channel.TopicName = channel
m.Params.Options.MessageID = messageId
r, err := chatAPIOut(k.path, m)
if err != nil {
return ChatOut{}, err
}
return r.Result, nil
}
// ChatList() returns a list of all conversations.
func (k Keybase) ChatList() ([]chatOutResultConversations, error) {
m := chatOut{}
m.Method = "list"
r, err := chatAPIOut(k.path, m)
return r.Result.Conversations, err
}

66
docs.go
View File

@ -1,2 +1,66 @@
// The keybase package implements an interface for interacting with the Keybase Chat, Team, and Wallet APIs /*
The keybase package implements an interface for interacting with the Keybase Chat, Team, and Wallet APIs
I've tried to follow Keybase's JSON API as closely as possible, so if you're stuck on anything, or wondering
why things are organized in a certain way, it's most likely due to that. It may be helpful to look at the
Keybase JSON API docs by running some of the following commands in your terminal:
// Chat API
keybase chat api -h
// Chat Message Stream
keybase chat api-listen -h
// Team API
keybase team api -h
// Wallet API
keybase wallet api -h
The git repo for this code is hosted on Keybase. You can contact me directly (https://keybase.io/dxb),
or join the mkbot team (https://keybase.io/team/mkbot) if you need assistance, or if you'd like to contribute.
Basic Example
Here's a quick example of a bot that will attach a reaction with the sender's device name to every message sent
in @mkbot#test1:
package main
import (
"fmt"
"samhofi.us/x/keybase"
)
var k = keybase.NewKeybase()
func main() {
channel := keybase.Channel{
Name: "mkbot",
TopicName: "test1",
MembersType: keybase.TEAM,
}
opts := keybase.RunOptions{
FilterChannel: channel,
}
fmt.Println("Running...")
k.Run(handler, opts)
}
func handler(m keybase.ChatAPI) {
if m.ErrorListen != nil {
fmt.Printf("Error: %s\n", *m.ErrorListen)
return
}
msgType := m.Msg.Content.Type
msgID := m.Msg.ID
deviceName := m.Msg.Sender.DeviceName
if msgType == "text" {
chat := k.NewChat(m.Msg.Channel)
chat.React(msgID, deviceName)
}
}
*/
package keybase package keybase

20
docs_test.go Normal file
View File

@ -0,0 +1,20 @@
package keybase
func ExampleKeybase_AdvertiseCommand() {
var k = NewKeybase()
// Clear out any previously advertised commands
k.ClearCommands()
// Create BotAdvertisement
c := BotAdvertisement{
Type: "public",
BotCommands: []BotCommand{
NewBotCommand("help", "Get help using this bot", "!help <command>"),
NewBotCommand("hello", "Say hello", "!hello"),
},
}
// Send advertisement
k.AdvertiseCommand(c)
}

2
go.mod
View File

@ -1,3 +1,3 @@
module samhofi.us/x/keybase module samhofi.us/x/keybase
go 1.12 go 1.13

0
go.sum Normal file
View File

View File

@ -2,74 +2,159 @@ package keybase
import ( import (
"encoding/json" "encoding/json"
"fmt"
"os/exec" "os/exec"
"strings"
) )
type Keybase struct { // Possible MemberTypes
path string const (
} TEAM string = "team"
USER string = "impteamnative"
)
type keybase interface { // Possible TopicTypes
ChatSendText(user string, message ...string) (ChatOut, error) const (
ChatSendTextTeam(team, channel, message string) (ChatOut, error) DEV string = "dev"
ChatReact(user, reaction string, messageId int) (ChatOut, error) CHAT string = "chat"
ChatReactTeam(team, channel, reaction string, messageId int) (ChatOut, error) )
ChatDeleteMessage(user string, messageId int) (ChatOut, error)
ChatDeleteMessageTeam(team, channel string, messageId int) (ChatOut, error)
ChatList() ([]chatOutResultConversations, error)
LoggedIn() bool
Username() string
Version() string
}
type status struct { // NewKeybase returns a new Keybase. Optionally, you can pass a string containing the path to the Keybase executable as the first argument.
Username string `json:"Username"` func NewKeybase(path ...string) *Keybase {
LoggedIn bool `json:"LoggedIn"` k := &Keybase{}
}
// New() returns a new instance of Keybase object. Optionally, you can pass a string containing the path to the Keybase executable as the first argument.
func New(path ...string) Keybase {
if len(path) < 1 { if len(path) < 1 {
return Keybase{path: "/usr/bin/keybase"} k.Path = "keybase"
} else {
k.Path = path[0]
} }
return Keybase{path: path[0]}
s := k.status()
k.Version = k.version()
k.LoggedIn = s.LoggedIn
if k.LoggedIn {
k.Username = s.Username
k.Device = s.Device.Name
}
return k
} }
// Username() returns the username of the currently logged-in Keybase user. // NewBotCommand returns a new BotCommand instance
func (k Keybase) Username() string { func NewBotCommand(name, description, usage string, extendedDescription ...BotCommandExtendedDescription) BotCommand {
cmd := exec.Command(k.path, "status", "-j") result := BotCommand{
cmdOut, err := cmd.Output() Name: name,
Description: description,
Usage: usage,
}
if len(extendedDescription) > 0 {
result.ExtendedDescription = &extendedDescription[0]
}
return result
}
// NewBotCommandExtendedDescription
func NewBotCommandExtendedDescription(title, desktopBody, mobileBody string) BotCommandExtendedDescription {
return BotCommandExtendedDescription{
Title: title,
DesktopBody: desktopBody,
MobileBody: mobileBody,
}
}
// Exec executes the given Keybase command
func (k *Keybase) Exec(command ...string) ([]byte, error) {
out, err := exec.Command(k.Path, command...).Output()
if err != nil { if err != nil {
return "" return []byte{}, err
}
return out, nil
}
// NewChat returns a new Chat instance
func (k *Keybase) NewChat(channel Channel) Chat {
return Chat{
keybase: k,
Channel: channel,
}
}
// NewTeam returns a new Team instance
func (k *Keybase) NewTeam(name string) Team {
return Team{
keybase: k,
Name: name,
}
}
// NewKV returns a new KV instance
func (k *Keybase) NewKV(team string) KV {
return KV{
keybase: k,
Team: team,
}
}
// NewWallet returns a new Wallet instance
func (k *Keybase) NewWallet() Wallet {
return Wallet{
keybase: k,
}
}
// status returns the results of the `keybase status` command, which includes
// information about the client, and the currently logged-in Keybase user.
func (k *Keybase) status() status {
cmdOut, err := k.Exec("status", "-j")
if err != nil {
return status{}
} }
var s status var s status
json.Unmarshal(cmdOut, &s) json.Unmarshal(cmdOut, &s)
return s.Username return s
} }
// LoggedIn() returns true if Keybase is currently logged in, otherwise returns false. // version returns the version string of the client.
func (k Keybase) LoggedIn() bool { func (k *Keybase) version() string {
cmd := exec.Command(k.path, "status", "-j") cmdOut, err := k.Exec("version", "-S", "-f", "s")
cmdOut, err := cmd.Output()
if err != nil {
return false
}
var s status
json.Unmarshal(cmdOut, &s)
return s.LoggedIn
}
// Version() returns the version string of the client.
func (k Keybase) Version() string {
cmd := exec.Command(k.path, "version", "-S", "-f", "s")
cmdOut, err := cmd.Output()
if err != nil { if err != nil {
return "" return ""
} }
return string(cmdOut) return string(cmdOut)
} }
// UserLookup pulls information about users.
// The following fields are currently returned: basics, profile, proofs_summary, devices -- See https://keybase.io/docs/api/1.0/call/user/lookup for more info.
func (k *Keybase) UserLookup(users ...string) (UserAPI, error) {
var fields = []string{"basics", "profile", "proofs_summary", "devices"}
cmdOut, err := k.Exec("apicall", "--arg", fmt.Sprintf("usernames=%s", strings.Join(users, ",")), "--arg", fmt.Sprintf("fields=%s", strings.Join(fields, ",")), "user/lookup")
if err != nil {
return UserAPI{}, err
}
var r UserAPI
if err := json.Unmarshal(cmdOut, &r); err != nil {
return UserAPI{}, err
}
return r, nil
}
// UserCard pulls the information that is typically displayed when you open a user's profile.
func (k *Keybase) UserCard(user string) (UserCardAPI, error) {
cmdOut, err := k.Exec("apicall", "--arg", "username="+user, "user/card")
if err != nil {
return UserCardAPI{}, err
}
var r UserCardAPI
if err := json.Unmarshal(cmdOut, &r); err != nil {
return UserCardAPI{}, err
}
return r, nil
}

137
kvstore.go Normal file
View File

@ -0,0 +1,137 @@
package keybase
import (
"encoding/json"
"errors"
)
// kvAPIOut sends a JSON request to the kvstore API and returns its response.
func kvAPIOut(k *Keybase, kv KVAPI) (KVAPI, error) {
jsonBytes, _ := json.Marshal(kv)
cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes))
if err != nil {
return KVAPI{}, err
}
var r KVAPI
if err := json.Unmarshal(cmdOut, &r); err != nil {
return KVAPI{}, err
}
if r.Error != nil {
return KVAPI{}, errors.New(r.Error.Message)
}
return r, nil
}
// Namespaces returns all namespaces for a team
func (kv KV) Namespaces() (KVAPI, error) {
m := KVAPI{
Params: &kvParams{},
}
m.Params.Options = kvOptions{
Team: kv.Team,
}
m.Method = "list"
r, err := kvAPIOut(kv.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Keys returns all non-deleted keys for a namespace
func (kv KV) Keys(namespace string) (KVAPI, error) {
m := KVAPI{
Params: &kvParams{},
}
m.Params.Options = kvOptions{
Team: kv.Team,
Namespace: namespace,
}
m.Method = "list"
r, err := kvAPIOut(kv.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Get returns an entry
func (kv KV) Get(namespace string, key string, revision ...uint) (KVAPI, error) {
m := KVAPI{
Params: &kvParams{},
}
m.Params.Options = kvOptions{
Team: kv.Team,
Namespace: namespace,
EntryKey: key,
}
if len(revision) > 0 {
m.Params.Options.Revision = revision[0]
}
m.Method = "get"
r, err := kvAPIOut(kv.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Put adds an entry
func (kv KV) Put(namespace string, key string, value string, revision ...uint) (KVAPI, error) {
m := KVAPI{
Params: &kvParams{},
}
m.Params.Options = kvOptions{
Team: kv.Team,
Namespace: namespace,
EntryKey: key,
EntryValue: value,
}
if len(revision) > 0 {
m.Params.Options.Revision = revision[0]
}
m.Method = "put"
r, err := kvAPIOut(kv.keybase, m)
if err != nil {
return r, err
}
return r, nil
}
// Delete removes an entry
func (kv KV) Delete(namespace string, key string, revision ...uint) (KVAPI, error) {
m := KVAPI{
Params: &kvParams{},
}
m.Params.Options = kvOptions{
Team: kv.Team,
Namespace: namespace,
EntryKey: key,
}
if len(revision) > 0 {
m.Params.Options.Revision = revision[0]
}
m.Method = "del"
r, err := kvAPIOut(kv.keybase, m)
if err != nil {
return r, err
}
return r, nil
}

189
team.go Normal file
View File

@ -0,0 +1,189 @@
package keybase
import (
"encoding/json"
"errors"
"fmt"
)
// teamAPIOut sends JSON requests to the team API and returns its response.
func teamAPIOut(k *Keybase, t TeamAPI) (TeamAPI, error) {
jsonBytes, _ := json.Marshal(t)
cmdOut, err := k.Exec("team", "api", "-m", string(jsonBytes))
if err != nil {
return TeamAPI{}, err
}
var r TeamAPI
if err := json.Unmarshal(cmdOut, &r); err != nil {
return TeamAPI{}, err
}
if r.Error != nil {
return TeamAPI{}, errors.New(r.Error.Message)
}
return r, nil
}
// AddUser adds a member to a team by username
func (t Team) AddUser(user, role string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "add-members"
m.Params.Options.Team = t.Name
m.Params.Options.Usernames = []usernames{
{
Username: user,
Role: role,
},
}
r, err := teamAPIOut(t.keybase, m)
if err == nil && r.Error == nil {
r, err = t.MemberList()
}
return r, err
}
// RemoveUser removes a member from a team
func (t Team) RemoveUser(user string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "remove-member"
m.Params.Options.Team = t.Name
m.Params.Options.Username = user
r, err := teamAPIOut(t.keybase, m)
return r, err
}
// AddReaders adds members to a team by username, and sets their roles to Reader
func (t Team) AddReaders(users ...string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "add-members"
m.Params.Options.Team = t.Name
addUsers := []usernames{}
for _, u := range users {
addUsers = append(addUsers, usernames{Username: u, Role: "reader"})
}
m.Params.Options.Usernames = addUsers
r, err := teamAPIOut(t.keybase, m)
if err == nil && r.Error == nil {
r, err = t.MemberList()
}
return r, err
}
// AddWriters adds members to a team by username, and sets their roles to Writer
func (t Team) AddWriters(users ...string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "add-members"
m.Params.Options.Team = t.Name
addUsers := []usernames{}
for _, u := range users {
addUsers = append(addUsers, usernames{Username: u, Role: "writer"})
}
m.Params.Options.Usernames = addUsers
r, err := teamAPIOut(t.keybase, m)
if err == nil && r.Error == nil {
r, err = t.MemberList()
}
return r, err
}
// AddAdmins adds members to a team by username, and sets their roles to Writer
func (t Team) AddAdmins(users ...string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "add-members"
m.Params.Options.Team = t.Name
addUsers := []usernames{}
for _, u := range users {
addUsers = append(addUsers, usernames{Username: u, Role: "admin"})
}
m.Params.Options.Usernames = addUsers
r, err := teamAPIOut(t.keybase, m)
if err == nil && r.Error == nil {
r, err = t.MemberList()
}
return r, err
}
// AddOwners adds members to a team by username, and sets their roles to Writer
func (t Team) AddOwners(users ...string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "add-members"
m.Params.Options.Team = t.Name
addUsers := []usernames{}
for _, u := range users {
addUsers = append(addUsers, usernames{Username: u, Role: "owner"})
}
m.Params.Options.Usernames = addUsers
r, err := teamAPIOut(t.keybase, m)
if err == nil && r.Error == nil {
r, err = t.MemberList()
}
return r, err
}
// MemberList returns a list of a team's members
func (t Team) MemberList() (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "list-team-memberships"
m.Params.Options.Team = t.Name
r, err := teamAPIOut(t.keybase, m)
return r, err
}
// CreateSubteam creates a subteam
func (t Team) CreateSubteam(name string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "create-team"
m.Params.Options.Team = fmt.Sprintf("%s.%s", t.Name, name)
r, err := teamAPIOut(t.keybase, m)
return r, err
}
// CreateTeam creates a new team
func (k *Keybase) CreateTeam(name string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "create-team"
m.Params.Options.Team = name
r, err := teamAPIOut(k, m)
return r, err
}
// ListUserMemberships returns information about a given user's team memberships
func (k *Keybase) ListUserMemberships(user string) (TeamAPI, error) {
m := TeamAPI{
Params: &tParams{},
}
m.Method = "list-user-memberships"
m.Params.Options.Username = user
r, err := teamAPIOut(k, m)
return r, err
}

926
types.go Normal file
View File

@ -0,0 +1,926 @@
package keybase
import (
"encoding/json"
"fmt"
"strings"
"time"
)
// RunOptions holds a set of options to be passed to Run
type RunOptions struct {
Capacity int // Channel capacity for the buffered channel that holds messages. Defaults to 100 if not set
Heartbeat int64 // Send a heartbeat through the channel every X minutes (0 = off)
Local bool // Subscribe to local messages
HideExploding bool // Ignore exploding messages
Dev bool // Subscribe to dev channel messages
Wallet bool // Subscribe to wallet events
FilterChannel Channel // Only subscribe to messages from specified channel
FilterChannels []Channel // Only subscribe to messages from specified channels
}
// ChatAPI holds information about a message received by the `keybase chat api-listen` command
type ChatAPI struct {
Type string `json:"type,omitempty"`
Source string `json:"source,omitempty"`
Msg *msg `json:"msg,omitempty"`
Method string `json:"method,omitempty"`
Params *params `json:"params,omitempty"`
Message string `json:"message,omitempty"`
ID int `json:"id,omitempty"`
Ratelimits []rateLimits `json:"ratelimits,omitempty"`
Notification *notification `json:"notification,omitempty"`
Result *result `json:"result,omitempty"`
Pagination *pagination `json:"pagination,omitempty"`
ErrorRaw *json.RawMessage `json:"error,omitempty"` // Raw JSON string containing any errors returned
ErrorRead *Error `json:"-"` // Errors returned by any outgoing chat functions such as Read(), Edit(), etc
ErrorListen *string `json:"-"` // Errors returned by the api-listen command (used in the Run() function)
keybase Keybase // Some methods will need this, so I'm passing it but keeping it unexported
}
type sender struct {
UID string `json:"uid"`
Username string `json:"username"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
}
type addedtoteam struct {
Team string `json:"team"`
Adder string `json:"adder"`
Addee string `json:"addee"`
Owners []string `json:"owners"`
Admins []string `json:"admins"`
Writers []string `json:"writers"`
Readers []string `json:"readers"`
}
type bulkaddtoconv struct {
Usernames []string `json:"usernames"`
}
type commits struct {
CommitHash string `json:"commitHash"`
Message string `json:"message"`
AuthorName string `json:"authorName"`
AuthorEmail string `json:"authorEmail"`
Ctime int `json:"ctime"`
}
type refs struct {
RefName string `json:"refName"`
Commits []commits `json:"commits"`
MoreCommitsAvailable bool `json:"moreCommitsAvailable"`
IsDelete bool `json:"isDelete"`
}
type gitpush struct {
Team string `json:"team"`
Pusher string `json:"pusher"`
RepoName string `json:"repoName"`
RepoID string `json:"repoID"`
Refs []refs `json:"refs"`
PushType int `json:"pushType"`
PreviousRepoName string `json:"previousRepoName"`
}
type system struct {
SystemType int `json:"systemType"`
Addedtoteam addedtoteam `json:"addedtoteam"`
Bulkaddtoconv bulkaddtoconv `json:"bulkaddtoconv"`
Gitpush gitpush `json:"gitpush"`
}
type paymentsResult struct {
ResultTyp int `json:"resultTyp"`
Sent string `json:"sent"`
}
type payments struct {
Username string `json:"username"`
PaymentText string `json:"paymentText"`
Result paymentsResult `json:"result"`
}
type userMentions struct {
Text string `json:"text"`
UID string `json:"uid"`
}
type teamMentions struct {
Name string `json:"name"`
Channel string `json:"channel"`
}
type reaction struct {
M int `json:"m"`
B string `json:"b"`
}
type delete struct {
MessageIDs []int `json:"messageIDs"`
}
type edit struct {
MessageID int `json:"messageID"`
Body string `json:"body"`
Payments []payments `json:"payments"`
UserMentions []userMentions `json:"userMentions"`
TeamMentions []teamMentions `json:"teamMentions"`
}
type text struct {
Body string `json:"body"`
Payments []payments `json:"payments"`
ReplyTo int `json:"replyTo"`
ReplyToUID string `json:"replyToUID"`
UserMentions []userMentions `json:"userMentions"`
TeamMentions []teamMentions `json:"teamMentions"`
}
type flip struct {
Text string `json:"text"`
GameID string `json:"game_id"`
FlipConvID string `json:"flip_conv_id"`
UserMentions interface{} `json:"user_mentions"`
TeamMentions interface{} `json:"team_mentions"`
}
type image struct {
Width int `json:"width"`
Height int `json:"height"`
}
type metadata struct {
AssetType int `json:"assetType"`
Image image `json:"image"`
}
type preview struct {
Filename string `json:"filename"`
Region string `json:"region"`
Endpoint string `json:"endpoint"`
Bucket string `json:"bucket"`
Path string `json:"path"`
Size int `json:"size"`
MimeType string `json:"mimeType"`
EncHash string `json:"encHash"`
Key string `json:"key"`
VerifyKey string `json:"verifyKey"`
Title string `json:"title"`
Nonce string `json:"nonce"`
Metadata metadata `json:"metadata"`
Tag int `json:"tag"`
}
type previews struct {
Filename string `json:"filename"`
Region string `json:"region"`
Endpoint string `json:"endpoint"`
Bucket string `json:"bucket"`
Path string `json:"path"`
Size int `json:"size"`
MimeType string `json:"mimeType"`
EncHash string `json:"encHash"`
Key string `json:"key"`
VerifyKey string `json:"verifyKey"`
Title string `json:"title"`
Nonce string `json:"nonce"`
Metadata metadata `json:"metadata"`
Tag int `json:"tag"`
}
type object struct {
Filename string `json:"filename"`
Region string `json:"region"`
Endpoint string `json:"endpoint"`
Bucket string `json:"bucket"`
Path string `json:"path"`
Size int `json:"size"`
MimeType string `json:"mimeType"`
EncHash string `json:"encHash"`
Key string `json:"key"`
VerifyKey string `json:"verifyKey"`
Title string `json:"title"`
Nonce string `json:"nonce"`
Metadata metadata `json:"metadata"`
Tag int `json:"tag"`
}
type attachment struct {
Object object `json:"object"`
Preview preview `json:"preview"`
Previews []previews `json:"previews"`
Metadata metadata `json:"metadata"`
Uploaded bool `json:"uploaded"`
}
type content struct {
Type string `json:"type"`
Attachment attachment `json:"attachment"`
Delete delete `json:"delete"`
Edit edit `json:"edit"`
Reaction reaction `json:"reaction"`
System system `json:"system"`
Text text `json:"text"`
SendPayment SendPayment `json:"send_payment"`
RequestPayment RequestPayment `json:"request_payment"`
Flip flip `json:"flip"`
}
type msg struct {
ID int `json:"id"`
ConversationID string `json:"conversation_id"`
Channel Channel `json:"channel"`
Sender sender `json:"sender"`
SentAt int `json:"sent_at"`
SentAtMs int64 `json:"sent_at_ms"`
Content content `json:"content"`
Unread bool `json:"unread"`
AtMentionUsernames []string `json:"at_mention_usernames"`
IsEphemeral bool `json:"is_ephemeral"`
Etime int64 `json:"etime"`
HasPairwiseMacs bool `json:"has_pairwise_macs"`
ChannelMention string `json:"channel_mention"`
}
type summary struct {
ID string `json:"id"`
TxID string `json:"txID"`
Time int64 `json:"time"`
StatusSimplified int `json:"statusSimplified"`
StatusDescription string `json:"statusDescription"`
StatusDetail string `json:"statusDetail"`
ShowCancel bool `json:"showCancel"`
AmountDescription string `json:"amountDescription"`
Delta int `json:"delta"`
Worth string `json:"worth"`
WorthAtSendTime string `json:"worthAtSendTime"`
IssuerDescription string `json:"issuerDescription"`
FromType int `json:"fromType"`
ToType int `json:"toType"`
AssetCode string `json:"assetCode"`
FromAccountID string `json:"fromAccountID"`
FromAccountName string `json:"fromAccountName"`
FromUsername string `json:"fromUsername"`
ToAccountID string `json:"toAccountID"`
ToAccountName string `json:"toAccountName"`
ToUsername string `json:"toUsername"`
ToAssertion string `json:"toAssertion"`
OriginalToAssertion string `json:"originalToAssertion"`
Note string `json:"note"`
NoteErr string `json:"noteErr"`
SourceAmountMax string `json:"sourceAmountMax"`
SourceAmountActual string `json:"sourceAmountActual"`
SourceAsset sourceAsset `json:"sourceAsset"`
SourceConvRate string `json:"sourceConvRate"`
IsAdvanced bool `json:"isAdvanced"`
SummaryAdvanced string `json:"summaryAdvanced"`
Operations interface{} `json:"operations"`
Unread bool `json:"unread"`
BatchID string `json:"batchID"`
FromAirdrop bool `json:"fromAirdrop"`
IsInflation bool `json:"isInflation"`
}
type details struct {
PublicNote string `json:"publicNote"`
PublicNoteType string `json:"publicNoteType"`
ExternalTxURL string `json:"externalTxURL"`
FeeChargedDescription string `json:"feeChargedDescription"`
PathIntermediate interface{} `json:"pathIntermediate"`
}
type notification struct {
Summary summary `json:"summary"`
Details details `json:"details"`
}
// Channel holds information about a conversation
type Channel struct {
Name string `json:"name,omitempty"`
Public bool `json:"public,omitempty"`
MembersType string `json:"members_type,omitempty"`
TopicType string `json:"topic_type,omitempty"`
TopicName string `json:"topic_name,omitempty"`
}
type BotCommand struct {
Name string `json:"name"`
Description string `json:"description"`
Usage string `json:"usage"`
ExtendedDescription *BotCommandExtendedDescription `json:"extended_description,omitempty"`
}
type BotCommandExtendedDescription struct {
Title string `json:"title"`
DesktopBody string `json:"desktop_body"`
MobileBody string `json:"mobile_body"`
}
type BotAdvertisement struct {
Type string `json:"type"` // "public", "teamconvs", "teammembers"
TeamName string `json:"team_name,omitempty"` // required if Type is not "public"
BotCommands []BotCommand `json:"commands"`
}
type mesg struct {
Body string `json:"body"`
}
type duration struct {
time.Duration
}
func (d *duration) UnmarshalJSON(b []byte) (err error) {
d.Duration, err = time.ParseDuration(strings.Trim(string(b), `"`))
return
}
func (d *duration) MarshalJSON() (b []byte, err error) {
return []byte(fmt.Sprintf(`"%s"`, d.String())), nil
}
type options struct {
Channel *Channel `json:"channel,omitempty"`
MessageID int `json:"message_id,omitempty"`
Message *mesg `json:"message,omitempty"`
Pagination *pagination `json:"pagination,omitempty"`
Filename string `json:"filename,omitempty,omitempty"`
Title string `json:"title,omitempty,omitempty"`
Output string `json:"output,omitempty,omitempty"`
ConversationID string `json:"conversation_id,omitempty"`
FlipConversationID string `json:"flip_conversation_id,omitempty"`
MsgID int `json:"msg_id,omitempty"`
ReplyTo int `json:"reply_to,omitempty"`
GameID string `json:"game_id,omitempty"`
Alias string `json:"alias,omitempty"`
BotAdvertisements []BotAdvertisement `json:"advertisements,omitempty"`
ExplodingLifetime duration `json:"exploding_lifetime,omitempty"`
Name string `json:"name,omitempty"`
Public bool `json:"public,omitempty"`
MembersType string `json:"members_type,omitempty"`
TopicType string `json:"topic_type,omitempty"`
TopicName string `json:"topic_name,omitempty"`
}
type params struct {
Options options `json:"options"`
}
type pagination struct {
Next string `json:"next"`
Previous string `json:"previous"`
Num int `json:"num"`
Last bool `json:"last,omitempty"`
ForceFirstPage bool `json:"forceFirstPage,omitempty"`
}
type participants struct {
UID string `json:"uid"`
DeviceID string `json:"deviceID"`
Username string `json:"username"`
DeviceName string `json:"deviceName"`
Commitment string `json:"commitment"`
Reveal string `json:"reveal"`
}
type dupreg struct {
User string `json:"user"`
Device string `json:"device"`
}
type errorInfo struct {
Typ int `json:"typ"`
Dupreg dupreg `json:"dupreg"`
}
type resultInfo struct {
Typ int `json:"typ"`
Coin bool `json:"coin"`
}
type flipStatus struct {
GameID string `json:"gameID"`
Phase int `json:"phase"`
ProgressText string `json:"progressText"`
ResultText string `json:"resultText"`
CommitmentVisualization string `json:"commitmentVisualization"`
RevealVisualization string `json:"revealVisualization"`
Participants []participants `json:"participants"`
ResultInfo *resultInfo `json:"resultInfo"`
ErrorInfo *errorInfo `json:"errorInfo"`
}
type result struct {
Messages []messages `json:"messages,omitempty"`
Pagination pagination `json:"pagination"`
Message string `json:"message"`
ID int `json:"id"`
Ratelimits []rateLimits `json:"ratelimits"`
Conversations []conversation `json:"conversations,omitempty"`
Offline bool `json:"offline,omitempty"`
Status flipStatus `json:"status,omitempty"`
IdentifyFailures interface{} `json:"identifyFailures,omitempty"`
}
type messages struct {
Msg msg `json:"msg,omitempty"`
}
type rateLimits struct {
Tank string `json:"tank,omitempty"`
Capacity int `json:"capacity,omitempty"`
Reset int `json:"reset,omitempty"`
Gas int `json:"gas,omitempty"`
}
type conversation struct {
ID string `json:"id"`
Channel Channel `json:"channel"`
Unread bool `json:"unread"`
ActiveAt int `json:"active_at"`
ActiveAtMs int64 `json:"active_at_ms"`
MemberStatus string `json:"member_status"`
}
type SendPayment struct {
PaymentID string `json:"paymentID"`
}
type RequestPayment struct {
RequestID string `json:"requestID"`
Note string `json:"note"`
}
// WalletAPI holds data for sending to API
type WalletAPI struct {
Method string `json:"method,omitempty"`
Params *wParams `json:"params,omitempty"`
Result *wResult `json:"result,omitempty"`
Error *Error `json:"error"`
}
type wOptions struct {
Name string `json:"name"`
Txid string `json:"txid"`
Recipient string `json:"recipient"`
Amount string `json:"amount"`
Currency string `json:"currency"`
Message string `json:"message"`
}
type wParams struct {
Options wOptions `json:"options"`
}
type asset struct {
Type string `json:"type"`
Code string `json:"code"`
Issuer string `json:"issuer"`
VerifiedDomain string `json:"verifiedDomain"`
IssuerName string `json:"issuerName"`
Desc string `json:"desc"`
InfoURL string `json:"infoUrl"`
}
type sourceAsset struct {
Type string `json:"type"`
Code string `json:"code"`
Issuer string `json:"issuer"`
VerifiedDomain string `json:"verifiedDomain"`
IssuerName string `json:"issuerName"`
Desc string `json:"desc"`
InfoURL string `json:"infoUrl"`
InfoURLText string `json:"infoUrlText"`
}
type balance struct {
Asset asset `json:"asset"`
Amount string `json:"amount"`
Limit string `json:"limit"`
}
type exchangeRate struct {
Currency string `json:"currency"`
Rate string `json:"rate"`
}
type wResult struct {
AccountID string `json:"accountID"`
IsPrimary bool `json:"isPrimary"`
Name string `json:"name"`
Balance []balance `json:"balance"`
ExchangeRate exchangeRate `json:"exchangeRate"`
AccountMode int `json:"accountMode"`
TxID string `json:"txID"`
Time int64 `json:"time"`
Status string `json:"status"`
StatusDetail string `json:"statusDetail"`
Amount string `json:"amount"`
Asset asset `json:"asset"`
DisplayAmount string `json:"displayAmount"`
DisplayCurrency string `json:"displayCurrency"`
SourceAmountMax string `json:"sourceAmountMax"`
SourceAmountActual string `json:"sourceAmountActual"`
SourceAsset sourceAsset `json:"sourceAsset"`
FromStellar string `json:"fromStellar"`
ToStellar string `json:"toStellar"`
FromUsername string `json:"fromUsername"`
ToUsername string `json:"toUsername"`
Note string `json:"note"`
NoteErr string `json:"noteErr"`
Unread bool `json:"unread"`
Username string `json:"username"`
}
// TeamAPI holds information sent and received to/from the team api
type TeamAPI struct {
Method string `json:"method,omitempty"`
Params *tParams `json:"params,omitempty"`
Result *tResult `json:"result,omitempty"`
Error *Error `json:"error"`
}
type emails struct {
Email string `json:"email"`
Role string `json:"role"`
}
type usernames struct {
Username string `json:"username"`
Role string `json:"role"`
}
type user struct {
UID string `json:"uid"`
Username string `json:"username"`
}
type uv struct {
UID string `json:"uid"`
EldestSeqno int `json:"eldestSeqno"`
}
type member struct {
Uv uv `json:"uv"`
Username string `json:"username"`
FullName string `json:"fullName"`
NeedsPUK bool `json:"needsPUK"`
Status int `json:"status"`
}
type members struct {
Owners []member `json:"owners"`
Admins []member `json:"admins"`
Writers []member `json:"writers"`
Readers []member `json:"readers"`
}
type annotatedActiveInvites struct {
}
type settings struct {
Open bool `json:"open"`
JoinAs int `json:"joinAs"`
}
type showcase struct {
IsShowcased bool `json:"is_showcased"`
AnyMemberShowcase bool `json:"any_member_showcase"`
}
type tOptions struct {
Team string `json:"team"`
Emails []emails `json:"emails"`
Usernames []usernames `json:"usernames"`
Username string `json:"username"`
}
type tParams struct {
Options tOptions `json:"options"`
}
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
}
type tResult struct {
ChatSent bool `json:"chatSent"`
CreatorAdded bool `json:"creatorAdded"`
Invited bool `json:"invited"`
User user `json:"user"`
EmailSent bool `json:"emailSent"`
ChatSending bool `json:"chatSending"`
Members members `json:"members"`
KeyGeneration int `json:"keyGeneration"`
AnnotatedActiveInvites annotatedActiveInvites `json:"annotatedActiveInvites"`
Settings settings `json:"settings"`
Showcase showcase `json:"showcase"`
Teams []teamInfo `json:"teams"`
}
type implicit struct {
Role int `json:"role"`
Ancestor string `json:"ancestor"`
}
type teamInfo struct {
UID string `json:"uid"`
TeamID string `json:"team_id"`
Username string `json:"username"`
FullName string `json:"full_name"`
FqName string `json:"fq_name"`
IsImplicitTeam bool `json:"is_implicit_team"`
ImplicitTeamDisplayName string `json:"implicit_team_display_name"`
IsOpenTeam bool `json:"is_open_team"`
Role int `json:"role"`
NeedsPUK bool `json:"needsPUK"`
MemberCount int `json:"member_count"`
MemberEldestSeqno int `json:"member_eldest_seqno"`
AllowProfilePromote bool `json:"allow_profile_promote"`
IsMemberShowcased bool `json:"is_member_showcased"`
Status int `json:"status"`
Implicit implicit `json:"implicit,omitempty"`
}
// KVAPI holds information sent and received to/from the kvstore api
type KVAPI struct {
Method string `json:"method,omitempty"`
Params *kvParams `json:"params,omitempty"`
Result *kvResult `json:"result,omitempty"`
Error *Error `json:"error"`
keybase Keybase
}
type kvOptions struct {
Team string `json:"team,omitempty"`
Namespace string `json:"namespace,omitempty"`
EntryKey string `json:"entryKey,omitempty"`
Revision uint `json:"revision,omitempty"`
EntryValue string `json:"entryValue,omitempty"`
}
type kvParams struct {
Options kvOptions `json:"options,omitempty"`
}
type entryKey struct {
EntryKey string `json:"entryKey"`
Revision uint `json:"revision"`
}
type kvResult struct {
TeamName string `json:"teamName"`
Namespaces []string `json:"namespaces"`
EntryKeys []entryKey `json:"entryKeys"`
EntryKey string `json:"entryKey"`
EntryValue string `json:"entryValue"`
Revision uint `json:"revision"`
}
// UserAPI holds information received from the user/lookup api
type UserAPI struct {
Status uStatus `json:"status"`
Them []them `json:"them"`
}
type uStatus struct {
Code int `json:"code"`
Name string `json:"name"`
}
type basics struct {
Ctime int `json:"ctime"`
EldestSeqno int `json:"eldest_seqno"`
IDVersion int `json:"id_version"`
LastIDChange int `json:"last_id_change"`
Mtime int `json:"mtime"`
PassphraseGeneration int `json:"passphrase_generation"`
RandomPw bool `json:"random_pw"`
Salt string `json:"salt"`
Status int `json:"status"`
TrackVersion int `json:"track_version"`
Username string `json:"username"`
UsernameCased string `json:"username_cased"`
}
type profile struct {
Bio string `json:"bio"`
FullName string `json:"full_name"`
Location string `json:"location"`
Mtime int `json:"mtime"`
}
type proof struct {
HumanURL string `json:"human_url"`
Nametag string `json:"nametag"`
PresentationGroup string `json:"presentation_group"`
PresentationTag string `json:"presentation_tag"`
ProofID string `json:"proof_id"`
ProofType string `json:"proof_type"`
ProofURL string `json:"proof_url"`
ServiceURL string `json:"service_url"`
SigID string `json:"sig_id"`
State int `json:"state"`
}
type proofsSummary struct {
All []proof `json:"all"`
HasWeb bool `json:"has_web"`
}
type key struct {
KeyRole int `json:"key_role"`
Kid string `json:"kid"`
SigID string `json:"sig_id"`
}
type uDevice struct {
Ctime int `json:"ctime"`
Keys []key `json:"keys"`
Mtime int `json:"mtime"`
Name string `json:"name"`
Status int `json:"status"`
Type string `json:"type"`
}
type them struct {
Basics basics `json:"basics,omitempty"`
ID string `json:"id"`
Profile profile `json:"profile,omitempty"`
ProofsSummary proofsSummary `json:"proofs_summary"`
Devices map[string]uDevice `json:"devices,omitempty"`
}
// UserCardAPI holds information received from the user/card api
type UserCardAPI struct {
AirdropRegistered bool `json:"airdrop_registered"`
Blocked bool `json:"blocked"`
FollowSummary followSummary `json:"follow_summary"`
Profile cardProfile `json:"profile"`
Status uStatus `json:"status"`
TeamShowcase []teamShowcase `json:"team_showcase"`
TheyFollowYou bool `json:"they_follow_you"`
UserBlocks userBlocks `json:"user_blocks"`
YouFollowThem bool `json:"you_follow_them"`
}
type followSummary struct {
Followers int `json:"followers"`
Following int `json:"following"`
}
type cardProfile struct {
Bio string `json:"bio"`
Comment string `json:"comment"`
CrimeAll int `json:"crime_all"`
CrimeChat int `json:"crime_chat"`
CrimeFollow int `json:"crime_follow"`
CrimeIllegal int `json:"crime_illegal"`
CrimeLegacyAll int `json:"crime_legacy_all"`
CrimeLegacyPorn int `json:"crime_legacy_porn"`
CrimeLegacyStellar int `json:"crime_legacy_stellar"`
CrimePorn int `json:"crime_porn"`
CrimeSmurfing int `json:"crime_smurfing"`
CrimeSpacedrop int `json:"crime_spacedrop"`
CrimeStellarDust int `json:"crime_stellar_dust"`
CrimeStellarPaymentReq int `json:"crime_stellar_payment_req"`
CrimeTeam int `json:"crime_team"`
Ctime time.Time `json:"ctime"`
FullName string `json:"full_name"`
IsAdmin int `json:"is_admin"`
Location string `json:"location"`
Mtime time.Time `json:"mtime"`
Reporter string `json:"reporter"`
Status int `json:"status"`
Twitter string `json:"twitter"`
UID string `json:"uid"`
Website string `json:"website"`
}
type teamShowcase struct {
Description string `json:"description"`
FqName string `json:"fq_name"`
NumMembers int `json:"num_members"`
Open bool `json:"open"`
PublicAdmins []string `json:"public_admins"`
Role int `json:"role"`
TeamID string `json:"team_id"`
TeamIsShowcased bool `json:"team_is_showcased"`
}
type userBlocks struct {
Chat bool `json:"chat"`
Ctime time.Time `json:"ctime"`
Follow bool `json:"follow"`
Mtime time.Time `json:"mtime"`
}
// Keybase holds basic information about the local Keybase executable
type Keybase struct {
Path string
Username string
LoggedIn bool
Version string
Device string
}
// Chat holds basic information about a specific conversation
type Chat struct {
keybase *Keybase
Channel Channel
}
type chat interface {
Delete(messageID int) (ChatAPI, error)
Edit(messageID int, message ...string) (ChatAPI, error)
React(messageID int, reaction string) (ChatAPI, error)
Send(message ...string) (ChatAPI, error)
Reply(replyTo int, message ...string) (ChatAPI, error)
Upload(title string, filepath string) (ChatAPI, error)
Download(messageID int, filepath string) (ChatAPI, error)
LoadFlip(messageID int, conversationID string, flipConversationID string, gameID string) (ChatAPI, error)
Pin(messageID int) (ChatAPI, error)
Unpin() (ChatAPI, error)
Mark(messageID int) (ChatAPI, error)
}
type chatAPI interface {
Next(count ...int) (*ChatAPI, error)
Previous(count ...int) (*ChatAPI, error)
}
// Team holds basic information about a team
type Team struct {
keybase *Keybase
Name string
}
type team interface {
AddAdmins(users ...string) (TeamAPI, error)
AddOwners(users ...string) (TeamAPI, error)
AddReaders(users ...string) (TeamAPI, error)
AddUser(user, role string) (TeamAPI, error)
AddWriters(users ...string) (TeamAPI, error)
CreateSubteam(name string) (TeamAPI, error)
MemberList() (TeamAPI, error)
}
// Wallet holds basic information about a wallet
type Wallet struct {
keybase *Keybase
}
type wallet interface {
CancelRequest(requestID string) error
RequestPayment(user string, amount float64, memo ...string)
Send(recipient string, amount string, currency string, message ...string) (WalletAPI, error)
SendXLM(recipient string, amount string, message ...string) (WalletAPI, error)
StellarAddress(user string) (string, error)
TxDetail(txid string) (WalletAPI, error)
}
// KV holds basic information about a KVStore
type KV struct {
keybase *Keybase
Team string
}
type kvInterface interface {
Namespaces() (KVAPI, error)
Keys(namespace string) (KVAPI, error)
Get(namespace string, key string) (KVAPI, error)
Put(namespace string, key string, value string) (KVAPI, error)
Delete(namespace string, key string) (KVAPI, error)
}
type keybase interface {
AdvertiseCommand(advertisement BotAdvertisement) (ChatAPI, error)
AdvertiseCommands(advertisements []BotAdvertisement) (ChatAPI, error)
ChatList(opts ...Channel) (ChatAPI, error)
ClearCommands() (ChatAPI, error)
CreateTeam(name string) (TeamAPI, error)
NewChat(channel Channel) Chat
NewTeam(name string) Team
NewKV(team string) KV
NewWallet() Wallet
Run(handler func(ChatAPI), options ...RunOptions)
status() status
version() string
UserLookup(users ...string) (UserAPI, error)
ListUserMemberships(user string) (TeamAPI, error)
UserCard(user string) (UserCardAPI, error)
}
type status struct {
Username string `json:"Username"`
LoggedIn bool `json:"LoggedIn"`
Device device `json:"Device"`
}
type device struct {
Name string `json:"name"`
}

111
wallet.go Normal file
View File

@ -0,0 +1,111 @@
package keybase
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
// walletAPIOut sends JSON requests to the wallet API and returns its response.
func walletAPIOut(k *Keybase, w WalletAPI) (WalletAPI, error) {
jsonBytes, _ := json.Marshal(w)
cmdOut, err := k.Exec("wallet", "api", "-m", string(jsonBytes))
if err != nil {
return WalletAPI{}, err
}
var r WalletAPI
json.Unmarshal(cmdOut, &r)
if r.Error != nil {
return WalletAPI{}, errors.New(r.Error.Message)
}
return r, nil
}
// TxDetail returns details of a stellar transaction
func (w Wallet) TxDetail(txid string) (WalletAPI, error) {
m := WalletAPI{
Params: &wParams{},
}
m.Method = "details"
m.Params.Options.Txid = txid
r, err := walletAPIOut(w.keybase, m)
return r, err
}
// StellarAddress returns the primary stellar address of a given user
func (w Wallet) StellarAddress(user string) (string, error) {
m := WalletAPI{
Params: &wParams{},
}
m.Method = "lookup"
m.Params.Options.Name = user
r, err := walletAPIOut(w.keybase, m)
if err != nil {
return "", err
}
return r.Result.AccountID, err
}
// StellarUser returns the keybase username of a given wallet address
func (w Wallet) StellarUser(wallet string) (string, error) {
m := WalletAPI{
Params: &wParams{},
}
m.Method = "lookup"
m.Params.Options.Name = wallet
r, err := walletAPIOut(w.keybase, m)
if err != nil {
return "", err
}
return r.Result.Username, err
}
// RequestPayment sends a request for payment to a user
func (w Wallet) RequestPayment(user string, amount float64, memo ...string) error {
k := w.keybase
if len(memo) > 0 {
_, err := k.Exec("wallet", "request", user, fmt.Sprintf("%f", amount), "-m", memo[0])
return err
}
_, err := k.Exec("wallet", "request", user, fmt.Sprintf("%f", amount))
return err
}
// CancelRequest cancels a request for payment previously sent to a user
func (w Wallet) CancelRequest(requestID string) error {
k := w.keybase
_, err := k.Exec("wallet", "cancel-request", requestID)
return err
}
// Send sends the specified amount of the specified currency to a user
func (w Wallet) Send(recipient string, amount string, currency string, message ...string) (WalletAPI, error) {
m := WalletAPI{
Params: &wParams{},
}
m.Method = "send"
m.Params.Options.Recipient = recipient
m.Params.Options.Amount = amount
m.Params.Options.Currency = currency
if len(message) > 0 {
m.Params.Options.Message = strings.Join(message, " ")
}
r, err := walletAPIOut(w.keybase, m)
if err != nil {
return WalletAPI{}, err
}
return r, err
}
// SendXLM sends the specified amount of XLM to a user
func (w Wallet) SendXLM(recipient string, amount string, message ...string) (WalletAPI, error) {
result, err := w.Send(recipient, amount, "XLM", message...)
return result, err
}