You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
6.7 KiB
217 lines
6.7 KiB
package keybase |
|
|
|
import ( |
|
"bufio" |
|
"encoding/json" |
|
"os/exec" |
|
"time" |
|
) |
|
|
|
type ChatIn struct { |
|
Type string `json:"type"` |
|
Source string `json:"source"` |
|
Msg chatInMsg `json:"msg"` |
|
} |
|
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 chatInCommits struct { |
|
CommitHash string `json:"commitHash"` |
|
Message string `json:"message"` |
|
AuthorName string `json:"authorName"` |
|
AuthorEmail string `json:"authorEmail"` |
|
Ctime int `json:"ctime"` |
|
} |
|
type chatInRefs struct { |
|
RefName string `json:"refName"` |
|
Commits []chatInCommits `json:"commits"` |
|
MoreCommitsAvailable bool `json:"moreCommitsAvailable"` |
|
IsDelete bool `json:"isDelete"` |
|
} |
|
type chatInGitpush struct { |
|
Team string `json:"team"` |
|
Pusher string `json:"pusher"` |
|
RepoName string `json:"repoName"` |
|
RepoID string `json:"repoID"` |
|
Refs []chatInRefs `json:"refs"` |
|
PushType int `json:"pushType"` |
|
PreviousRepoName string `json:"previousRepoName"` |
|
} |
|
type chatInSystem struct { |
|
SystemType int `json:"systemType"` |
|
Addedtoteam chatInAddedtoteam `json:"addedtoteam"` |
|
Bulkaddtoconv chatInBulkaddtoconv `json:"bulkaddtoconv"` |
|
Gitpush chatInGitpush `json:"gitpush"` |
|
} |
|
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 Channel `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"` |
|
} |
|
|
|
// RunOptions holds a set of options to be passed to Run |
|
type RunOptions struct { |
|
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 |
|
} |
|
|
|
// 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) |
|
} |
|
|
|
// Get new messages coming into keybase and send them into the channel |
|
func getNewMessages(k Keybase, c chan<- ChatIn, execOptions []string) { |
|
execCommand := []string{"chat", "api-listen"} |
|
if len(execOptions) > 0 { |
|
execCommand = append(execCommand, execOptions...) |
|
} |
|
keybaseListen := exec.Command(k.Path, execCommand...) |
|
keybaseOutput, _ := keybaseListen.StdoutPipe() |
|
keybaseListen.Start() |
|
keybaseListen.Wait() |
|
scanner := bufio.NewScanner(keybaseOutput) |
|
var jsonData ChatIn |
|
|
|
for scanner.Scan() { |
|
json.Unmarshal([]byte(scanner.Text()), &jsonData) |
|
c <- jsonData |
|
} |
|
} |
|
|
|
// Run() runs keybase chat api-listen, and passes incoming messages to the message handler func |
|
func (k Keybase) Run(handler func(ChatIn), options ...RunOptions) { |
|
var heartbeatFreq int64 |
|
runOptions := make([]string, 0) |
|
if len(options) > 0 { |
|
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 ChatIn, 50) |
|
defer close(c) |
|
if heartbeatFreq > 0 { |
|
go heartbeat(c, time.Duration(heartbeatFreq)*time.Minute) |
|
} |
|
go getNewMessages(k, c, runOptions) |
|
for { |
|
go handler(<-c) |
|
} |
|
} |
|
|
|
func heartbeat(c chan<- ChatIn, freq time.Duration) { |
|
m := ChatIn{ |
|
Type: "heartbeat", |
|
} |
|
count := 0 |
|
for { |
|
time.Sleep(freq) |
|
m.Msg.ID = count |
|
c <- m |
|
count++ |
|
} |
|
}
|
|
|