mirror of
https://github.com/Rudi9719/kbtui.git
synced 2026-03-22 15:37:23 +00:00
Compare commits
68 Commits
bugs/edit-
...
v2/dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 5898dd617c | |||
| 69b5442ac9 | |||
| 27261223f0 | |||
|
aa01e9cb40
|
|||
| e4784e4fb2 | |||
| 80896e5323 | |||
| e1c8890721 | |||
| 4ac523e7f7 | |||
| 37dc6b8d4f | |||
| 60c5302aec | |||
| bf0c271d2a | |||
| 5e6e97d7f6 | |||
| 4a7818c79e | |||
| f890a6e56c | |||
| e60f65d59c | |||
| 938d6c855c | |||
| 549e9f8ad6 | |||
| a03b5ff630 | |||
| e67b0e4265 | |||
| d74ac7b1e1 | |||
| e1f0314b87 | |||
| 384d0cfd9c | |||
| 33551d9c8b | |||
| 876668d3aa | |||
| 05afd348d0 | |||
| a8e0ed17b9 | |||
| 99922c2582 | |||
| 0812c57a6a | |||
| 8c0e6211aa | |||
| de5f7da139 | |||
| 2fb6bb0bc0 | |||
| 4cc825860e | |||
| 1e6e34fbc5 | |||
| de05697c89 | |||
| bb3ec46a43 | |||
| 790d295e2e | |||
| 2484578775 | |||
| 33a68670c9 | |||
| e017ffcb55 | |||
| 0c866e24c4 | |||
|
e033215cc9
|
|||
|
029799494b
|
|||
| f09f2969d9 | |||
|
1a75ac8a49
|
|||
|
55ac19beb3
|
|||
|
a083eb3ca6
|
|||
|
87d1b19aeb
|
|||
|
987eba51cf
|
|||
| 7408db2625 | |||
| 299f5103a5 | |||
| 439f09aa1c | |||
|
3f33070635
|
|||
|
f04bf8e545
|
|||
| 005d737fee | |||
| 44310d09f5 | |||
| 5557c17ad4 | |||
| ade1799115 | |||
|
20a687208a
|
|||
| e692500606 | |||
| b0ea0c0b63 | |||
| 0ba2507891 | |||
| bd02981b52 | |||
| 6d86fdd578 | |||
| f7f0e61ecd | |||
| 0ccf655aa4 | |||
| f65ac0e231 | |||
| 517e8ecd46 | |||
| dd81d08db7 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ emojiList.go
|
|||||||
.idea/*
|
.idea/*
|
||||||
.idea
|
.idea
|
||||||
*.log
|
*.log
|
||||||
|
.travis.yml
|
||||||
|
|||||||
13
README.md
13
README.md
@ -31,16 +31,9 @@ go get -u github.com/rudi9719/kbtui
|
|||||||
```
|
```
|
||||||
Or you can do the following:
|
Or you can do the following:
|
||||||
```
|
```
|
||||||
go get ./...
|
go get github.com/magefile/mage/mage
|
||||||
go run build.go
|
|
||||||
go run build.go {build, buildBeta... etc}
|
go run build.go {build, buildBeta... etc}
|
||||||
./kbtui
|
./kbtui
|
||||||
```
|
```
|
||||||
|
Mage is a requirement for building `kbtui` as it will automatically handle/manage imports as well as mage is used to generate the
|
||||||
You may see an error with `go get ./...` about PATHs, that may be safely ignored.
|
file for emoji completion.
|
||||||
|
|
||||||
If you see an error about a missing dependancy during a build, you'll want to resolve that.
|
|
||||||
|
|
||||||
|
|
||||||
Occasionally when [@dxb](https://keybase.io/dxb) updates his API it will be necessary to run
|
|
||||||
`go get -u ./...` or `go get -u samhofi.us/x/keybase`
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ func init() {
|
|||||||
|
|
||||||
func cmdClean(cmd []string) {
|
func cmdClean(cmd []string) {
|
||||||
clearView("Chat")
|
clearView("Chat")
|
||||||
|
clearView("List")
|
||||||
go populateChat()
|
go populateChat()
|
||||||
|
go populateList()
|
||||||
}
|
}
|
||||||
|
|||||||
88
cmdConfig.go
Normal file
88
cmdConfig.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// +build !rm_basic_commands allcommands setcmd
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command := Command{
|
||||||
|
Cmd: []string{"config"},
|
||||||
|
Description: "Change various settings",
|
||||||
|
Help: "",
|
||||||
|
Exec: cmdConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdConfig(cmd []string) {
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(cmd) == 2:
|
||||||
|
if cmd[1] == "load" {
|
||||||
|
config, err = readConfig()
|
||||||
|
if err != nil {
|
||||||
|
printError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case len(cmd) > 2:
|
||||||
|
if cmd[1] == "load" {
|
||||||
|
config, err = readConfig(cmd[3])
|
||||||
|
if err != nil {
|
||||||
|
printError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printError("Must pass a valid command")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfig(filepath ...string) (*Config, error) {
|
||||||
|
var result = new(Config)
|
||||||
|
var configFile string
|
||||||
|
var env bool
|
||||||
|
|
||||||
|
// Load default config first, this way any values missing from the provided config file will remain the default value
|
||||||
|
d := []byte(defaultConfig)
|
||||||
|
toml.Unmarshal(d, result)
|
||||||
|
|
||||||
|
switch len(filepath) {
|
||||||
|
case 0:
|
||||||
|
configFile, env = os.LookupEnv("KBTUI_CFG")
|
||||||
|
if !env {
|
||||||
|
configFile = "~/.config/kbtui.toml"
|
||||||
|
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||||
|
configFile = "kbtui.toml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
configFile = filepath[0]
|
||||||
|
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||||
|
return result, fmt.Errorf("Unable to load config: %s not found", configFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := ioutil.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
f = []byte(defaultConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.Unmarshal(f, result)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.filepath = configFile
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@ -3,7 +3,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ func cmdDelete(cmd []string) {
|
|||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
_, err := chat.Delete(messageID)
|
_, err := chat.Delete(messageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error deleting your message."))
|
printError("There was an error deleting your message.")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,22 +21,22 @@ func init() {
|
|||||||
func cmdDownloadFile(cmd []string) {
|
func cmdDownloadFile(cmd []string) {
|
||||||
|
|
||||||
if len(cmd) < 2 {
|
if len(cmd) < 2 {
|
||||||
printToView("Feed", fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", cmdPrefix, cmd[0]))
|
printInfo(fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", config.Basics.CmdPrefix, cmd[0]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messageID, err := strconv.Atoi(cmd[1])
|
messageID, err := strconv.Atoi(cmd[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", "There was an error converting your messageID to an int")
|
printError("There was an error converting your messageID to an int")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
api, err := chat.ReadMessage(messageID)
|
api, err := chat.ReadMessage(messageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error pulling message %d", messageID))
|
printError(fmt.Sprintf("There was an error pulling message %d", messageID))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if api.Result.Messages[0].Msg.Content.Type != "attachment" {
|
if api.Result.Messages[0].Msg.Content.Type != "attachment" {
|
||||||
printToView("Feed", "No attachment detected")
|
printError("No attachment detected")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var fileName string
|
var fileName string
|
||||||
@ -46,10 +46,11 @@ func cmdDownloadFile(cmd []string) {
|
|||||||
fileName = api.Result.Messages[0].Msg.Content.Attachment.Object.Filename
|
fileName = api.Result.Messages[0].Msg.Content.Attachment.Object.Filename
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", downloadPath, fileName))
|
_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", config.Basics.DownloadPath, fileName))
|
||||||
|
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error downloading %s from %s", fileName, channel.Name))
|
printErrorF(fmt.Sprintf("There was an error downloading %s from $TEXT", fileName), channelName)
|
||||||
} else {
|
} else {
|
||||||
printToView("Feed", fmt.Sprintf("Downloaded %s from %s", fileName, channel.Name))
|
printInfoF(fmt.Sprintf("Downloaded %s from $TEXT", fileName), channelName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
cmdEdit.go
14
cmdEdit.go
@ -26,22 +26,24 @@ func cmdEdit(cmd []string) {
|
|||||||
if len(cmd) == 2 {
|
if len(cmd) == 2 {
|
||||||
messageID, _ = strconv.Atoi(cmd[1])
|
messageID, _ = strconv.Atoi(cmd[1])
|
||||||
} else if lastMessage.ID != 0 {
|
} else if lastMessage.ID != 0 {
|
||||||
|
message, _ := chat.ReadMessage(lastMessage.ID)
|
||||||
|
lastMessage.Type = message.Result.Messages[0].Msg.Content.Type
|
||||||
if lastMessage.Type != "text" {
|
if lastMessage.Type != "text" {
|
||||||
printToView("Feed", "Last message isn't editable (is it an edit?)")
|
printError("Last message isn't editable (is it an edit?)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messageID = lastMessage.ID
|
messageID = lastMessage.ID
|
||||||
} else {
|
} else {
|
||||||
printToView("Feed", "No message to edit")
|
printError("No message to edit")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
origMessage, _ := chat.ReadMessage(messageID)
|
origMessage, _ := chat.ReadMessage(messageID)
|
||||||
if origMessage.Result.Messages[0].Msg.Content.Type != "text" {
|
if origMessage.Result.Messages[0].Msg.Content.Type != "text" {
|
||||||
printToView("Feed", fmt.Sprintf("%+v", origMessage))
|
printInfo(fmt.Sprintf("%+v", origMessage))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if origMessage.Result.Messages[0].Msg.Sender.Username != k.Username {
|
if origMessage.Result.Messages[0].Msg.Sender.Username != k.Username {
|
||||||
printToView("Feed", "You cannot edit another user's messages.")
|
printError("You cannot edit another user's messages.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
editString := origMessage.Result.Messages[0].Msg.Content.Text.Body
|
editString := origMessage.Result.Messages[0].Msg.Content.Text.Body
|
||||||
@ -53,14 +55,14 @@ func cmdEdit(cmd []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(cmd) < 3 {
|
if len(cmd) < 3 {
|
||||||
printToView("Feed", "Not enough options for Edit")
|
printError("Not enough options for Edit")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messageID, _ = strconv.Atoi(cmd[1])
|
messageID, _ = strconv.Atoi(cmd[1])
|
||||||
newMessage := strings.Join(cmd[2:], " ")
|
newMessage := strings.Join(cmd[2:], " ")
|
||||||
_, err := chat.Edit(messageID, newMessage)
|
_, err := chat.Edit(messageID, newMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("Error editing message %d, %+v", messageID, err))
|
printError(fmt.Sprintf("Error editing message %d, %+v", messageID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
55
cmdExec.go
Normal file
55
cmdExec.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// +build !rm_basic_commands allcommands execcmd
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command := Command{
|
||||||
|
Cmd: []string{"exec", "ex"},
|
||||||
|
Description: "$keybase args - executes keybase $args and returns the output",
|
||||||
|
Help: "",
|
||||||
|
Exec: cmdExec,
|
||||||
|
}
|
||||||
|
RegisterCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdExec(cmd []string) {
|
||||||
|
l := len(cmd)
|
||||||
|
switch {
|
||||||
|
case l >= 2:
|
||||||
|
if cmd[1] == "keybase" {
|
||||||
|
// if the user types /exec keybase wallet list
|
||||||
|
// only send ["wallet", "list"]
|
||||||
|
runKeybaseExec(cmd[2:])
|
||||||
|
} else {
|
||||||
|
// send everything except the command
|
||||||
|
runKeybaseExec(cmd[1:])
|
||||||
|
}
|
||||||
|
case l == 1:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
printExecHelp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runKeybaseExec(args []string) {
|
||||||
|
outputBytes, err := k.Exec(args...)
|
||||||
|
if err != nil {
|
||||||
|
printToView("Feed", fmt.Sprintf("Exec error: %+v", err))
|
||||||
|
} else {
|
||||||
|
channel.Name = ""
|
||||||
|
// unjoin the chat
|
||||||
|
clearView("Chat")
|
||||||
|
setViewTitle("Input", fmt.Sprintf(" /exec %s ", strings.Join(args, " ")))
|
||||||
|
output := string(outputBytes)
|
||||||
|
printToView("Chat", fmt.Sprintf("%s", output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printExecHelp() {
|
||||||
|
printInfo(fmt.Sprintf("To execute a keybase command use %sexec <keybase args>", config.Basics.CmdPrefix))
|
||||||
|
}
|
||||||
@ -25,7 +25,7 @@ func cmdHelp(cmd []string) {
|
|||||||
if len(cmd) == 1 {
|
if len(cmd) == 1 {
|
||||||
sort.Strings(baseCommands)
|
sort.Strings(baseCommands)
|
||||||
for _, c := range baseCommands {
|
for _, c := range baseCommands {
|
||||||
helpText = fmt.Sprintf("%s%s%s\t\t%s\n", helpText, cmdPrefix, c, commands[c].Description)
|
helpText = fmt.Sprintf("%s%s%s\t\t%s\n", helpText, config.Basics.CmdPrefix, c, commands[c].Description)
|
||||||
}
|
}
|
||||||
if len(typeCommands) > 0 {
|
if len(typeCommands) > 0 {
|
||||||
for c := range typeCommands {
|
for c := range typeCommands {
|
||||||
|
|||||||
@ -41,12 +41,14 @@ func cmdJoin(cmd []string) {
|
|||||||
channel.TopicName = ""
|
channel.TopicName = ""
|
||||||
channel.MembersType = keybase.USER
|
channel.MembersType = keybase.USER
|
||||||
}
|
}
|
||||||
printToView("Feed", fmt.Sprintf("You are joining: %s", joinedName))
|
printInfoF("You are joining: $TEXT", config.Colors.Message.LinkKeybase.stylize(joinedName))
|
||||||
clearView("Chat")
|
clearView("Chat")
|
||||||
setViewTitle("Input", fmt.Sprintf(" %s ", joinedName))
|
setViewTitle("Input", fmt.Sprintf(" %s ", joinedName))
|
||||||
|
lastChat = joinedName
|
||||||
|
autoScrollView("Chat")
|
||||||
go populateChat()
|
go populateChat()
|
||||||
default:
|
default:
|
||||||
printToView("Feed", fmt.Sprintf("To join a team use %sjoin <team> <channel>", cmdPrefix))
|
printInfo(fmt.Sprintf("To join a team use %sjoin <team> <channel>", config.Basics.CmdPrefix))
|
||||||
printToView("Feed", fmt.Sprintf("To join a PM use %sjoin <user>", cmdPrefix))
|
printInfo(fmt.Sprintf("To join a PM use %sjoin <user>", config.Basics.CmdPrefix))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,8 +29,8 @@ func cmdPost(cmd []string) {
|
|||||||
chat := k.NewChat(pubChan)
|
chat := k.NewChat(pubChan)
|
||||||
_, err := chat.Send(post)
|
_, err := chat.Send(post)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error with your post: %+v", err))
|
printError(fmt.Sprintf("There was an error with your post: %+v", err))
|
||||||
} else {
|
} else {
|
||||||
printToView("Feed", "You have publically posted to your wall, signed by your current device.")
|
printInfo("You have publically posted to your wall, signed by your current device.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,6 @@ func doReact(messageID int, reaction string) {
|
|||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
_, err := chat.React(messageID, reaction)
|
_, err := chat.React(messageID, reaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", "There was an error reacting to the message.")
|
printError("There was an error reacting to the message.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,18 +22,17 @@ func init() {
|
|||||||
func cmdReply(cmd []string) {
|
func cmdReply(cmd []string) {
|
||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
if len(cmd) < 2 {
|
if len(cmd) < 2 {
|
||||||
printToView("Feed", fmt.Sprintf("%s%s $ID - Reply to message $ID", cmdPrefix, cmd[0]))
|
printInfo(fmt.Sprintf("%s%s $ID - Reply to message $ID", config.Basics.CmdPrefix, cmd[0]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messageID, err := strconv.Atoi(cmd[1])
|
messageID, err := strconv.Atoi(cmd[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error determining message ID %s", cmd[1]))
|
printError(fmt.Sprintf("There was an error determining message ID %s", cmd[1]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = chat.Reply(messageID, strings.Join(cmd[2:], " "))
|
_, err = chat.Reply(messageID, strings.Join(cmd[2:], " "))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", "There was an error with your reply.")
|
printError("There was an error with your reply.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
94
cmdSet.go
94
cmdSet.go
@ -1,94 +0,0 @@
|
|||||||
// +build !rm_basic_commands allcommands setcmd
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pelletier/go-toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
command := Command{
|
|
||||||
Cmd: []string{"set", "config"},
|
|
||||||
Description: "Change various settings",
|
|
||||||
Help: "",
|
|
||||||
Exec: cmdSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterCommand(command)
|
|
||||||
}
|
|
||||||
func printSetting(cmd []string) {
|
|
||||||
switch cmd[1] {
|
|
||||||
case "load":
|
|
||||||
loadFromToml()
|
|
||||||
printToView("Feed", fmt.Sprintf("Loading config from toml"))
|
|
||||||
case "downloadPath":
|
|
||||||
printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], downloadPath))
|
|
||||||
case "outputFormat":
|
|
||||||
printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], outputFormat))
|
|
||||||
case "dateFormat":
|
|
||||||
printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], dateFormat))
|
|
||||||
case "timeFormat":
|
|
||||||
printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], timeFormat))
|
|
||||||
case "cmdPrefix":
|
|
||||||
printToView("Feed", fmt.Sprintf("Setting for %s -> %s", cmd[1], cmdPrefix))
|
|
||||||
default:
|
|
||||||
printToView("Feed", fmt.Sprintf("Unknown config value %s", cmd[1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func cmdSet(cmd []string) {
|
|
||||||
if len(cmd) < 2 {
|
|
||||||
printToView("Feed", "No config value specified")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(cmd) < 3 {
|
|
||||||
printSetting(cmd)
|
|
||||||
}
|
|
||||||
switch cmd[1] {
|
|
||||||
case "downloadPath":
|
|
||||||
if len(cmd) != 3 {
|
|
||||||
printToView("Feed", "Invalid download path.")
|
|
||||||
}
|
|
||||||
downloadPath = cmd[2]
|
|
||||||
case "outputFormat":
|
|
||||||
outputFormat = strings.Join(cmd[1:], " ")
|
|
||||||
case "dateFormat":
|
|
||||||
dateFormat = strings.Join(cmd[1:], " ")
|
|
||||||
case "timeFormat":
|
|
||||||
timeFormat = strings.Join(cmd[1:], " ")
|
|
||||||
case "cmdPrefix":
|
|
||||||
cmdPrefix = cmd[2]
|
|
||||||
default:
|
|
||||||
printToView("Feed", fmt.Sprintf("Unknown config value %s", cmd[1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func loadFromToml() {
|
|
||||||
config, err := toml.LoadFile("kbtui.tml")
|
|
||||||
if err != nil {
|
|
||||||
printToView("Feed", fmt.Sprintf("Could not read config file: %+v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if config.Has("Basics.colorless") {
|
|
||||||
colorless = config.Get("Basics.colorless").(bool)
|
|
||||||
}
|
|
||||||
if config.Has("Basics.downloadPath") {
|
|
||||||
downloadPath = config.Get("Basics.downloadPath").(string)
|
|
||||||
}
|
|
||||||
if config.Has("Basics.cmdPrefix") {
|
|
||||||
cmdPrefix = config.Get("Basics.cmdPrefix").(string)
|
|
||||||
}
|
|
||||||
if config.Has("Formatting.outputFormat") {
|
|
||||||
outputFormat = config.Get("Formatting.outputFormat").(string)
|
|
||||||
}
|
|
||||||
if config.Has("Formatting.dateFormat") {
|
|
||||||
dateFormat = config.Get("Formatting.dateFormat").(string)
|
|
||||||
}
|
|
||||||
if config.Has("Formatting.timeFormat") {
|
|
||||||
timeFormat = config.Get("Formatting.timeFormat").(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
command := Command{
|
command := Command{
|
||||||
Cmd: []string{"stream", "s"},
|
Cmd: []string{"stream", "s"},
|
||||||
@ -17,7 +21,7 @@ func cmdStream(cmd []string) {
|
|||||||
stream = true
|
stream = true
|
||||||
channel.Name = ""
|
channel.Name = ""
|
||||||
|
|
||||||
printToView("Feed", "You are now viewing the formatted stream")
|
printInfo("You are now viewing the formatted stream")
|
||||||
setViewTitle("Input", " Stream - Not in a chat /j to join ")
|
setViewTitle("Input", fmt.Sprintf(" Stream - Not in a chat. %sj to join ", config.Basics.CmdPrefix))
|
||||||
clearView("Chat")
|
clearView("Chat")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,14 +21,14 @@ func init() {
|
|||||||
|
|
||||||
func cmdUploadFile(cmd []string) {
|
func cmdUploadFile(cmd []string) {
|
||||||
if len(cmd) < 2 {
|
if len(cmd) < 2 {
|
||||||
printToView("Feed", fmt.Sprintf("%s%s $filePath $fileName - Upload file from absolute path with optional name", cmdPrefix, cmd[0]))
|
printInfo(fmt.Sprintf("%s%s $filePath $fileName - Upload file from absolute path with optional name", config.Basics.CmdPrefix, cmd[0]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
filePath := cmd[1]
|
filePath := cmd[1]
|
||||||
if !strings.HasPrefix(filePath, "/") {
|
if !strings.HasPrefix(filePath, "/") {
|
||||||
dir, err := os.Getwd()
|
dir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error determining path %+v", err))
|
printError(fmt.Sprintf("There was an error determining path %+v", err))
|
||||||
}
|
}
|
||||||
filePath = fmt.Sprintf("%s/%s", dir, filePath)
|
filePath = fmt.Sprintf("%s/%s", dir, filePath)
|
||||||
}
|
}
|
||||||
@ -40,9 +40,10 @@ func cmdUploadFile(cmd []string) {
|
|||||||
}
|
}
|
||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
_, err := chat.Upload(fileName, filePath)
|
_, err := chat.Upload(fileName, filePath)
|
||||||
|
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name).string()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channel.Name, err))
|
printError(fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channelName, err))
|
||||||
} else {
|
} else {
|
||||||
printToView("Feed", fmt.Sprintf("Uploaded %s to %s", filePath, channel.Name))
|
printInfo(fmt.Sprintf("Uploaded %s to %s", filePath, channelName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,13 +64,14 @@ func cmdPopulateWall(cmd []string) {
|
|||||||
if len(users) < 1 {
|
if len(users) < 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
printToView("Feed", fmt.Sprintf("Displaying public messages for user %s", requestedUsers))
|
|
||||||
|
printInfoF("Displaying public messages for user $TEXT", config.Colors.Message.LinkKeybase.stylize(requestedUsers))
|
||||||
for _, chann := range users {
|
for _, chann := range users {
|
||||||
chat := k.NewChat(chann)
|
chat := k.NewChat(chann)
|
||||||
api, err := chat.Read()
|
api, err := chat.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(users) < 6 {
|
if len(users) < 6 {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error for user %s: %+v", cleanChannelName(chann.Name), err))
|
printError(fmt.Sprintf("There was an error for user %s: %+v", cleanChannelName(chann.Name), err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -42,20 +42,20 @@ func cmdWallet(cmd []string) {
|
|||||||
walletConfirmationCode = b.String()
|
walletConfirmationCode = b.String()
|
||||||
walletConfirmationUser = cmd[1]
|
walletConfirmationUser = cmd[1]
|
||||||
walletTransactionAmnt = cmd[2]
|
walletTransactionAmnt = cmd[2]
|
||||||
printToView("Feed", fmt.Sprintf("To confirm sending %s to %s, type /confirm %s %s", cmd[2], cmd[1], cmd[1], walletConfirmationCode))
|
printInfo(fmt.Sprintf("To confirm sending %s to %s, type /confirm %s %s", cmd[2], cmd[1], cmd[1], walletConfirmationCode))
|
||||||
|
|
||||||
} else if cmd[0] == "confirm" {
|
} else if cmd[0] == "confirm" {
|
||||||
if cmd[1] == walletConfirmationUser && cmd[2] == walletConfirmationCode {
|
if cmd[1] == walletConfirmationUser && cmd[2] == walletConfirmationCode {
|
||||||
txWallet := k.NewWallet()
|
txWallet := k.NewWallet()
|
||||||
wAPI, err := txWallet.SendXLM(walletConfirmationUser, walletTransactionAmnt, "")
|
wAPI, err := txWallet.SendXLM(walletConfirmationUser, walletTransactionAmnt, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error with your wallet tx:\n\t%+v", err))
|
printError(fmt.Sprintf("There was an error with your wallet tx:\n\t%+v", err))
|
||||||
} else {
|
} else {
|
||||||
printToView("Feed", fmt.Sprintf("You have sent %sXLM to %s with tx ID: %s", wAPI.Result.Amount, wAPI.Result.ToUsername, wAPI.Result.TxID))
|
printInfo(fmt.Sprintf("You have sent %sXLM to %s with tx ID: %s", wAPI.Result.Amount, wAPI.Result.ToUsername, wAPI.Result.TxID))
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
printToView("Feed", "There was an error validating your confirmation. Your wallet has been untouched.")
|
printError("There was an error validating your confirmation. Your wallet has been untouched.")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
228
colors.go
228
colors.go
@ -3,41 +3,211 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO maybe datastructure
|
const (
|
||||||
// BASH-like PS1 variable equivalent (without colours)
|
black int = iota
|
||||||
// TODO bold? cursive etc?
|
red
|
||||||
func color(c int) string {
|
green
|
||||||
if colorless {
|
yellow
|
||||||
|
purple
|
||||||
|
magenta
|
||||||
|
cyan
|
||||||
|
grey
|
||||||
|
normal int = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
var colorMapString = map[string]int{
|
||||||
|
"black": black,
|
||||||
|
"red": red,
|
||||||
|
"green": green,
|
||||||
|
"yellow": yellow,
|
||||||
|
"purple": purple,
|
||||||
|
"magenta": magenta,
|
||||||
|
"cyan": cyan,
|
||||||
|
"grey": grey,
|
||||||
|
"normal": normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
var colorMapInt = map[int]string{
|
||||||
|
black: "black",
|
||||||
|
red: "red",
|
||||||
|
green: "green",
|
||||||
|
yellow: "yellow",
|
||||||
|
purple: "purple",
|
||||||
|
magenta: "magenta",
|
||||||
|
cyan: "cyan",
|
||||||
|
grey: "grey",
|
||||||
|
normal: "normal",
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorFromString(color string) int {
|
||||||
|
var result int
|
||||||
|
color = strings.ToLower(color)
|
||||||
|
result, ok := colorMapString[color]
|
||||||
|
if !ok {
|
||||||
|
return normal
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorFromInt(color int) string {
|
||||||
|
var result string
|
||||||
|
result, ok := colorMapInt[color]
|
||||||
|
if !ok {
|
||||||
|
return "normal"
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var basicStyle = Style{
|
||||||
|
Foreground: colorMapInt[normal],
|
||||||
|
Background: colorMapInt[normal],
|
||||||
|
Italic: false,
|
||||||
|
Bold: false,
|
||||||
|
Underline: false,
|
||||||
|
Strikethrough: false,
|
||||||
|
Inverse: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) withForeground(color int) Style {
|
||||||
|
s.Foreground = colorFromInt(color)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Style) withBackground(color int) Style {
|
||||||
|
s.Background = colorFromInt(color)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Style) withBold() Style {
|
||||||
|
s.Bold = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Style) withInverse() Style {
|
||||||
|
s.Inverse = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Style) withItalic() Style {
|
||||||
|
s.Italic = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Style) withStrikethrough() Style {
|
||||||
|
s.Strikethrough = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Style) withUnderline() Style {
|
||||||
|
s.Underline = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO create both as `reset` (which it is now) as well as `append`
|
||||||
|
// which essentially just adds on top. that is relevant in the case of
|
||||||
|
// bold/italic etc - it should add style - not clear.
|
||||||
|
func (s Style) toANSI() string {
|
||||||
|
if config.Basics.Colorless {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if c < 0 {
|
output := "\x1b[0m\x1b[0"
|
||||||
return "\033[0m"
|
if colorFromString(s.Foreground) != normal {
|
||||||
} else {
|
output += fmt.Sprintf(";%d", 30+colorFromString(s.Foreground))
|
||||||
return fmt.Sprintf("\033[0;%dm", 29+c)
|
|
||||||
}
|
}
|
||||||
}
|
if colorFromString(s.Background) != normal {
|
||||||
|
output += fmt.Sprintf(";%d", 40+colorFromString(s.Background))
|
||||||
// TODO maybe make the text into some datastructure which remembers the color
|
|
||||||
func colorText(text string, color string, offColor string) string {
|
|
||||||
return fmt.Sprintf("%s%s%s", color, text, offColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func colorUsername(username string, offColor string) string {
|
|
||||||
var color = messageSenderDefaultColor
|
|
||||||
if username == k.Username {
|
|
||||||
color = mentionColor
|
|
||||||
}
|
}
|
||||||
return colorText(username, color, offColor)
|
if s.Bold {
|
||||||
}
|
output += ";1"
|
||||||
func colorRegex(msg string, match string, color string, offColor string) string {
|
}
|
||||||
var re = regexp.MustCompile(match)
|
if s.Italic {
|
||||||
return re.ReplaceAllString(msg, colorText(`$1`, color, offColor))
|
output += ";3"
|
||||||
|
}
|
||||||
|
if s.Underline {
|
||||||
|
output += ";4"
|
||||||
|
}
|
||||||
|
if s.Inverse {
|
||||||
|
output += ";7"
|
||||||
|
}
|
||||||
|
if s.Strikethrough {
|
||||||
|
output += ";9"
|
||||||
|
}
|
||||||
|
|
||||||
|
return output + "m"
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorReplaceMentionMe(msg string, offColor string) string {
|
// End Colors
|
||||||
//var coloredOwnName = colorText(k.Username, mentionColor, offColor)
|
// Begin StyledString
|
||||||
//return strings.Replace(msg, k.Username, coloredOwnName, -1)
|
|
||||||
return colorRegex(msg, "(@?"+k.Username+")", mentionColor, offColor)
|
// StyledString is used to save a message with a style, which can then later be rendered to a string
|
||||||
|
type StyledString struct {
|
||||||
|
message string
|
||||||
|
style Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle all formatting types
|
||||||
|
func (s Style) sprintf(base string, parts ...StyledString) StyledString {
|
||||||
|
text := s.stylize(removeFormatting(base))
|
||||||
|
//TODO handle posibility to escape
|
||||||
|
re := regexp.MustCompile(`\$TEXT`)
|
||||||
|
for len(re.FindAllString(text.message, 1)) > 0 {
|
||||||
|
part := parts[0]
|
||||||
|
parts = parts[1:]
|
||||||
|
text = text.replaceN("$TEXT", part, 1)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) stylize(msg string) StyledString {
|
||||||
|
return StyledString{msg, s}
|
||||||
|
}
|
||||||
|
func (t StyledString) stringFollowedByStyle(style Style) string {
|
||||||
|
return t.style.toANSI() + t.message + style.toANSI()
|
||||||
|
}
|
||||||
|
func (t StyledString) string() string {
|
||||||
|
return t.stringFollowedByStyle(basicStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t StyledString) replace(match string, value StyledString) StyledString {
|
||||||
|
return t.replaceN(match, value, -1)
|
||||||
|
}
|
||||||
|
func (t StyledString) replaceN(match string, value StyledString, n int) StyledString {
|
||||||
|
t.message = strings.Replace(t.message, match, value.stringFollowedByStyle(t.style), n)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
func (t StyledString) replaceString(match string, value string) StyledString {
|
||||||
|
t.message = strings.Replace(t.message, match, value, -1)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides current formatting
|
||||||
|
func (t StyledString) colorRegex(match string, style Style) StyledString {
|
||||||
|
re := regexp.MustCompile("(" + match + ")")
|
||||||
|
locations := re.FindAllStringIndex(t.message, -1)
|
||||||
|
var newMessage string
|
||||||
|
var prevIndex int
|
||||||
|
for _, loc := range locations {
|
||||||
|
cleanSubstring := style.stylize(removeFormatting(string(t.message[loc[0]:loc[1]])))
|
||||||
|
newMessage += t.message[prevIndex:loc[0]]
|
||||||
|
newMessage += cleanSubstring.stringFollowedByStyle(t.style)
|
||||||
|
prevIndex = loc[1]
|
||||||
|
}
|
||||||
|
// Append any string after the final match
|
||||||
|
newMessage += t.message[prevIndex:len(t.message)]
|
||||||
|
t.message = newMessage
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends the other stylize at the end, but retains same style
|
||||||
|
func (t StyledString) append(other StyledString) StyledString {
|
||||||
|
t.message = t.message + other.stringFollowedByStyle(t.style)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
func (t StyledString) appendString(other string) StyledString {
|
||||||
|
t.message += other
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin Formatting
|
||||||
|
|
||||||
|
func removeFormatting(s string) string {
|
||||||
|
reFormatting := regexp.MustCompile(`(?m)\x1b\[(\d*;?)*m`)
|
||||||
|
return reFormatting.ReplaceAllString(s, "")
|
||||||
}
|
}
|
||||||
|
|||||||
72
defaultConfig.go
Normal file
72
defaultConfig.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
var defaultConfig = `
|
||||||
|
[basics]
|
||||||
|
download_path = "/tmp/"
|
||||||
|
colorless = false
|
||||||
|
unicode_emojis = true
|
||||||
|
|
||||||
|
# The prefix before evaluating a command
|
||||||
|
cmd_prefix = "/"
|
||||||
|
|
||||||
|
[formatting]
|
||||||
|
# BASH-like PS1 variable equivalent
|
||||||
|
output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
|
output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
|
output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
|
pm_format = "PM from $USER@$DEVICE: $MSG"
|
||||||
|
|
||||||
|
# 02 = Day, Jan = Month, 06 = Year
|
||||||
|
date_format = "02Jan06"
|
||||||
|
|
||||||
|
# 15 = hours, 04 = minutes, 05 = seconds
|
||||||
|
time_format = "15:04"
|
||||||
|
|
||||||
|
|
||||||
|
[colors]
|
||||||
|
[colors.channels]
|
||||||
|
[colors.channels.basic]
|
||||||
|
foreground = "normal"
|
||||||
|
[colors.channels.header]
|
||||||
|
foreground = "magenta"
|
||||||
|
bold = true
|
||||||
|
[colors.channels.unread]
|
||||||
|
foreground = "green"
|
||||||
|
italic = true
|
||||||
|
|
||||||
|
[colors.message]
|
||||||
|
[colors.message.body]
|
||||||
|
foreground = "normal"
|
||||||
|
[colors.message.header]
|
||||||
|
foreground = "grey"
|
||||||
|
[colors.message.mention]
|
||||||
|
foreground = "green"
|
||||||
|
italic = true
|
||||||
|
bold = true
|
||||||
|
[colors.message.id]
|
||||||
|
foreground = "yellow"
|
||||||
|
[colors.message.time]
|
||||||
|
foreground = "magenta"
|
||||||
|
[colors.message.sender_default]
|
||||||
|
foreground = "cyan"
|
||||||
|
bold = true
|
||||||
|
[colors.message.sender_device]
|
||||||
|
foreground = "cyan"
|
||||||
|
[colors.message.attachment]
|
||||||
|
foreground = "red"
|
||||||
|
[colors.message.link_url]
|
||||||
|
foreground = "yellow"
|
||||||
|
[colors.message.link_keybase]
|
||||||
|
foreground = "yellow"
|
||||||
|
[colors.message.reaction]
|
||||||
|
foreground = "magenta"
|
||||||
|
bold = true
|
||||||
|
[colors.message.code]
|
||||||
|
foreground = "cyan"
|
||||||
|
background = "grey"
|
||||||
|
[colors.feed]
|
||||||
|
[colors.feed.basic]
|
||||||
|
foreground = "grey"
|
||||||
|
[colors.feed.error]
|
||||||
|
foreground = "red"
|
||||||
|
`
|
||||||
47
emojiMap.go
Normal file
47
emojiMap.go
Normal file
File diff suppressed because one or more lines are too long
12
go.mod
Normal file
12
go.mod
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module github.com/Rudi9719/kbtui
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/awesome-gocui/gocui v0.6.0
|
||||||
|
github.com/magefile/mage v1.9.0
|
||||||
|
github.com/mattn/go-runewidth v0.0.5 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.6.0
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
|
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c
|
||||||
|
)
|
||||||
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/awesome-gocui/gocui v0.6.0 h1:hhDJiQC12tEsJNJ+iZBBVaSSLFYo9llFuYpQlL5JZVI=
|
||||||
|
github.com/awesome-gocui/gocui v0.6.0/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA=
|
||||||
|
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
|
||||||
|
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
|
||||||
|
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.5 h1:jrGtp51JOKTWgvLFzfG6OtZOJcK2sEnzc/U+zw7TtbA=
|
||||||
|
github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ=
|
||||||
|
github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||||
|
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||||
|
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c h1:qIKOKqYnRCx+O2IOz3a/lplrD0p1e3n/VoGOdrTGrVo=
|
||||||
|
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE=
|
||||||
30
kbtui.tml
30
kbtui.tml
@ -1,30 +0,0 @@
|
|||||||
[Basics]
|
|
||||||
downloadPath = "/tmp/"
|
|
||||||
colorless = false
|
|
||||||
# The prefix before evaluating a command
|
|
||||||
cmdPrefix = "/"
|
|
||||||
|
|
||||||
[Formatting]
|
|
||||||
# BASH-like PS1 variable equivalent
|
|
||||||
outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
|
||||||
|
|
||||||
# 02 = Day, Jan = Month, 06 = Year
|
|
||||||
dateFormat = "02Jan06"
|
|
||||||
|
|
||||||
# 15 = hours, 04 = minutes, 05 = seconds
|
|
||||||
timeFormat = "15:04"
|
|
||||||
|
|
||||||
|
|
||||||
[Colors]
|
|
||||||
channelsColor = 8
|
|
||||||
channelsHeaderColor = 6
|
|
||||||
noColor = -1
|
|
||||||
mentionColor = 3
|
|
||||||
messageHeaderColor = 8
|
|
||||||
messageIdColor = 7
|
|
||||||
messageTimeColor = 6
|
|
||||||
messageSenderDefaultColor = 8
|
|
||||||
messageSenderDeviceColor = 8
|
|
||||||
messageBodyColor = -1
|
|
||||||
messageAttachmentColor = 2
|
|
||||||
messageLinkColor = 4
|
|
||||||
68
kbtui.toml
Normal file
68
kbtui.toml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
[basics]
|
||||||
|
download_path = "/tmp/"
|
||||||
|
colorless = false
|
||||||
|
unicode_emojis = true
|
||||||
|
|
||||||
|
# The prefix before evaluating a command
|
||||||
|
cmd_prefix = "/"
|
||||||
|
|
||||||
|
[formatting]
|
||||||
|
# BASH-like PS1 variable equivalent
|
||||||
|
output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
|
output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
|
output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
|
pm_format = "PM from $USER@$DEVICE: $MSG"
|
||||||
|
|
||||||
|
# 02 = Day, Jan = Month, 06 = Year
|
||||||
|
date_format = "02Jan06"
|
||||||
|
|
||||||
|
# 15 = hours, 04 = minutes, 05 = seconds
|
||||||
|
time_format = "15:04"
|
||||||
|
|
||||||
|
|
||||||
|
[colors]
|
||||||
|
[colors.channels]
|
||||||
|
[colors.channels.basic]
|
||||||
|
foreground = "normal"
|
||||||
|
[colors.channels.header]
|
||||||
|
foreground = "magenta"
|
||||||
|
bold = true
|
||||||
|
[colors.channels.unread]
|
||||||
|
foreground = "green"
|
||||||
|
italic = true
|
||||||
|
|
||||||
|
[colors.message]
|
||||||
|
[colors.message.body]
|
||||||
|
foreground = "normal"
|
||||||
|
[colors.message.header]
|
||||||
|
foreground = "grey"
|
||||||
|
[colors.message.mention]
|
||||||
|
foreground = "green"
|
||||||
|
italic = true
|
||||||
|
bold = true
|
||||||
|
[colors.message.id]
|
||||||
|
foreground = "yellow"
|
||||||
|
[colors.message.time]
|
||||||
|
foreground = "magenta"
|
||||||
|
[colors.message.sender_default]
|
||||||
|
foreground = "cyan"
|
||||||
|
bold = true
|
||||||
|
[colors.message.sender_device]
|
||||||
|
foreground = "cyan"
|
||||||
|
[colors.message.attachment]
|
||||||
|
foreground = "red"
|
||||||
|
[colors.message.link_url]
|
||||||
|
foreground = "yellow"
|
||||||
|
[colors.message.link_keybase]
|
||||||
|
foreground = "yellow"
|
||||||
|
[colors.message.reaction]
|
||||||
|
foreground = "magenta"
|
||||||
|
bold = true
|
||||||
|
[colors.message.code]
|
||||||
|
foreground = "cyan"
|
||||||
|
background = "grey"
|
||||||
|
[colors.feed]
|
||||||
|
[colors.feed.basic]
|
||||||
|
foreground = "grey"
|
||||||
|
[colors.feed.error]
|
||||||
|
foreground = "red"
|
||||||
79
mage.go
79
mage.go
@ -3,65 +3,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/magefile/mage/mg"
|
"github.com/magefile/mage/mg"
|
||||||
"github.com/magefile/mage/sh"
|
"github.com/magefile/mage/sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// emoji related constants
|
|
||||||
const emojiList = "https://raw.githubusercontent.com/CodeFreezr/emojo/master/db/v5/emoji-v5.json"
|
|
||||||
const emojiFileName = "emojiList.go"
|
|
||||||
|
|
||||||
// json parsing structure
|
|
||||||
type emoji struct {
|
|
||||||
Num int `json:"No"`
|
|
||||||
Emoji string `json:"Emoji"`
|
|
||||||
Category string `json:"Category"`
|
|
||||||
SubCategory string `json:"SubCategory"`
|
|
||||||
Unicode string `json:"Unicode"`
|
|
||||||
Name string `json:"Name"`
|
|
||||||
Tags string `json:"Tags"`
|
|
||||||
Shortcode string `json:"Shortcode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// This func downloaded and parses the emojis from online into a slice of all shortnames
|
|
||||||
// to be used as a lookup for tab completion for emojis
|
|
||||||
// this way the pull from GitHub only has to be done at build time.
|
|
||||||
func createEmojiSlice() ([]string, error) {
|
|
||||||
result, err := http.Get(emojiList)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer result.Body.Close()
|
|
||||||
|
|
||||||
emojiList, err := ioutil.ReadAll(result.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var emojis []emoji
|
|
||||||
if err := json.Unmarshal(emojiList, &emojis); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var emojiSlice []string
|
|
||||||
|
|
||||||
for _, emj := range emojis {
|
|
||||||
if len(emj.Shortcode) == 0 || strings.Contains(emj.Shortcode, "_tone") {
|
|
||||||
// dont add them
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
emojiSlice = append(emojiSlice, emj.Shortcode)
|
|
||||||
}
|
|
||||||
return emojiSlice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRemotePackages() error {
|
func getRemotePackages() error {
|
||||||
var packages = []string{
|
var packages = []string{
|
||||||
"samhofi.us/x/keybase",
|
"samhofi.us/x/keybase",
|
||||||
@ -87,28 +35,6 @@ func exit(err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build kbtui with emoji lookup support
|
|
||||||
func BuildEmoji() error {
|
|
||||||
mg.Deps(getRemotePackages)
|
|
||||||
emojis, err := createEmojiSlice()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := os.Create(emojiFileName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
fileContent := fmt.Sprintf("package main\n\nvar emojiSlice = %#v", emojis)
|
|
||||||
_, err = f.WriteString(fileContent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Sync()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build kbtui with just the basic commands.
|
// Build kbtui with just the basic commands.
|
||||||
func Build() {
|
func Build() {
|
||||||
mg.Deps(getRemotePackages)
|
mg.Deps(getRemotePackages)
|
||||||
@ -157,7 +83,7 @@ func BuildAllCommands() {
|
|||||||
// Build kbtui with all Commands and TypeCommands enabled.
|
// Build kbtui with all Commands and TypeCommands enabled.
|
||||||
func BuildAllCommandsT() {
|
func BuildAllCommandsT() {
|
||||||
mg.Deps(getRemotePackages)
|
mg.Deps(getRemotePackages)
|
||||||
if err := sh.Run("go", "build", "-tags", "type_commands,allcommands"); err != nil {
|
if err := sh.Run("go", "build", "-tags", "type_commands allcommands"); err != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
exit(err)
|
exit(err)
|
||||||
}()
|
}()
|
||||||
@ -167,8 +93,7 @@ func BuildAllCommandsT() {
|
|||||||
// Build kbtui with beta functionality
|
// Build kbtui with beta functionality
|
||||||
func BuildBeta() {
|
func BuildBeta() {
|
||||||
mg.Deps(getRemotePackages)
|
mg.Deps(getRemotePackages)
|
||||||
mg.Deps(BuildEmoji)
|
if err := sh.Run("go", "build", "-tags", "allcommands showreactionscmd tabcompletion execcmd"); err != nil {
|
||||||
if err := sh.Run("go", "build", "-tags", "allcommands,showreactionscmd,emojiList,tabcompletion"); err != nil {
|
|
||||||
defer func() {
|
defer func() {
|
||||||
exit(err)
|
exit(err)
|
||||||
}()
|
}()
|
||||||
|
|||||||
248
main.go
248
main.go
@ -22,9 +22,12 @@ var (
|
|||||||
channels []keybase.Channel
|
channels []keybase.Channel
|
||||||
stream = false
|
stream = false
|
||||||
lastMessage keybase.ChatAPI
|
lastMessage keybase.ChatAPI
|
||||||
|
lastChat = ""
|
||||||
g *gocui.Gui
|
g *gocui.Gui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var config *Config
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if !k.LoggedIn {
|
if !k.LoggedIn {
|
||||||
fmt.Println("You are not logged in.")
|
fmt.Println("You are not logged in.")
|
||||||
@ -37,6 +40,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
g.SetManagerFunc(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
RunCommand("config", "load")
|
||||||
go populateList()
|
go populateList()
|
||||||
go updateChatWindow()
|
go updateChatWindow()
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
@ -72,7 +76,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
feedView.Autoscroll = true
|
feedView.Autoscroll = true
|
||||||
feedView.Wrap = true
|
feedView.Wrap = true
|
||||||
feedView.Title = "Feed Window"
|
feedView.Title = "Feed Window"
|
||||||
fmt.Fprintln(feedView, "Feed Window - If you are mentioned or receive a PM it will show here")
|
printInfo("Feed Window - If you are mentioned or receive a PM it will show here")
|
||||||
}
|
}
|
||||||
if chatView, err2 := g.SetView("Chat", maxX/2-maxX/3, maxY/5+1, maxX-1, maxY-5, 0); err2 != nil {
|
if chatView, err2 := g.SetView("Chat", maxX/2-maxX/3, maxY/5+1, maxX-1, maxY-5, 0); err2 != nil {
|
||||||
if !gocui.IsUnknownView(err2) {
|
if !gocui.IsUnknownView(err2) {
|
||||||
@ -80,7 +84,9 @@ func layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
chatView.Autoscroll = true
|
chatView.Autoscroll = true
|
||||||
chatView.Wrap = true
|
chatView.Wrap = true
|
||||||
fmt.Fprintf(chatView, "Welcome %s!\n\nYour chats will appear here.\nSupported commands are as follows:\n\n", k.Username)
|
welcomeText := basicStyle.stylize("Welcome $USER!\n\nYour chats will appear here.\nSupported commands are as follows:\n")
|
||||||
|
welcomeText = welcomeText.replace("$USER", config.Colors.Message.Mention.stylize(k.Username))
|
||||||
|
fmt.Fprintln(chatView, welcomeText.string())
|
||||||
RunCommand("help")
|
RunCommand("help")
|
||||||
}
|
}
|
||||||
if inputView, err3 := g.SetView("Input", maxX/2-maxX/3, maxY-4, maxX-1, maxY-1, 0); err3 != nil {
|
if inputView, err3 := g.SetView("Input", maxX/2-maxX/3, maxY-4, maxX-1, maxY-1, 0); err3 != nil {
|
||||||
@ -92,7 +98,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
inputView.Editable = true
|
inputView.Editable = true
|
||||||
inputView.Wrap = true
|
inputView.Wrap = true
|
||||||
inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", cmdPrefix)
|
inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", config.Basics.CmdPrefix)
|
||||||
g.Cursor = true
|
g.Cursor = true
|
||||||
}
|
}
|
||||||
if listView, err4 := g.SetView("List", 0, 0, maxX/2-maxX/3-1, maxY-1, 0); err4 != nil {
|
if listView, err4 := g.SetView("List", 0, 0, maxX/2-maxX/3-1, maxY-1, 0); err4 != nil {
|
||||||
@ -104,7 +110,68 @@ func layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func scrollViewUp(v *gocui.View) error {
|
||||||
|
scrollView(v, -1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func scrollViewDown(v *gocui.View) error {
|
||||||
|
scrollView(v, 1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func scrollView(v *gocui.View, delta int) error {
|
||||||
|
if v != nil {
|
||||||
|
_, y := v.Size()
|
||||||
|
ox, oy := v.Origin()
|
||||||
|
if oy+delta > strings.Count(v.ViewBuffer(), "\n")-y {
|
||||||
|
v.Autoscroll = true
|
||||||
|
} else {
|
||||||
|
v.Autoscroll = false
|
||||||
|
if err := v.SetOrigin(ox, oy+delta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func autoScrollView(vn string) error {
|
||||||
|
v, err := g.View(vn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if v != nil {
|
||||||
|
v.Autoscroll = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func initKeybindings() error {
|
func initKeybindings() error {
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone,
|
||||||
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
cv, _ := g.View("Chat")
|
||||||
|
err := scrollViewUp(cv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone,
|
||||||
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
cv, _ := g.View("Chat")
|
||||||
|
err := scrollViewDown(cv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone,
|
||||||
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
autoScrollView("Chat")
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
|
||||||
func(g *gocui.Gui, v *gocui.View) error {
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
input, err := getInputString("Input")
|
input, err := getInputString("Input")
|
||||||
@ -119,6 +186,13 @@ func initKeybindings() error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyCtrlZ, gocui.ModNone,
|
||||||
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
cmdJoin([]string{"/join", lastChat})
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := g.SetKeybinding("Edit", gocui.KeyCtrlC, gocui.ModNone,
|
if err := g.SetKeybinding("Edit", gocui.KeyCtrlC, gocui.ModNone,
|
||||||
func(g *gocui.Gui, v *gocui.View) error {
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
popupView("Chat")
|
popupView("Chat")
|
||||||
@ -176,20 +250,19 @@ func getViewTitle(viewName string) string {
|
|||||||
view, err := g.View(viewName)
|
view, err := g.View(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// in case there is active tab completion, filter that to just the view title and not the completion options.
|
// in case there is active tab completion, filter that to just the view title and not the completion options.
|
||||||
printToView("Feed", fmt.Sprintf("Error getting view title: %s", err))
|
printError(fmt.Sprintf("Error getting view title: %s", err))
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return strings.Split(view.Title, "||")[0]
|
return strings.Split(view.Title, "||")[0]
|
||||||
|
|
||||||
}
|
}
|
||||||
func popupView(viewName string) {
|
func popupView(viewName string) {
|
||||||
_, err := g.SetCurrentView(viewName)
|
_, err := g.SetCurrentView(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("%+v", err))
|
printError(fmt.Sprintf("%+v", err))
|
||||||
}
|
}
|
||||||
_, err = g.SetViewOnTop(viewName)
|
_, err = g.SetViewOnTop(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("%+v", err))
|
printError(fmt.Sprintf("%+v", err))
|
||||||
}
|
}
|
||||||
g.Update(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
updatingView, err := g.View(viewName)
|
updatingView, err := g.View(viewName)
|
||||||
@ -247,13 +320,35 @@ func writeToView(viewName string, message string) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this removes formatting
|
||||||
|
func printError(message string) {
|
||||||
|
printErrorF(message)
|
||||||
|
}
|
||||||
|
func printErrorF(message string, parts ...StyledString) {
|
||||||
|
printToView("Feed", config.Colors.Feed.Error.sprintf(removeFormatting(message), parts...).string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// this removes formatting
|
||||||
|
func printInfo(message string) {
|
||||||
|
printInfoF(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this removes formatting
|
||||||
|
func printInfoF(message string, parts ...StyledString) {
|
||||||
|
printToView("Feed", config.Colors.Feed.Basic.sprintf(removeFormatting(message), parts...).string())
|
||||||
|
}
|
||||||
func printToView(viewName string, message string) {
|
func printToView(viewName string, message string) {
|
||||||
g.Update(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
updatingView, err := g.View(viewName)
|
updatingView, err := g.View(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
if config.Basics.UnicodeEmojis {
|
||||||
|
message = emojiUnicodeConvert(message)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(updatingView, "%s\n", message)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(updatingView, "%s\n", message)
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -287,13 +382,12 @@ func populateChat() {
|
|||||||
chat = k.NewChat(channel)
|
chat = k.NewChat(channel)
|
||||||
_, err2 := chat.Read(2)
|
_, err2 := chat.Read(2)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
printToView("Feed", fmt.Sprintf("%+v", err))
|
printError(fmt.Sprintf("%+v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go populateChat()
|
go populateChat()
|
||||||
go generateChannelTabCompletionSlice()
|
go generateChannelTabCompletionSlice()
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
var printMe []string
|
var printMe []string
|
||||||
var actuallyPrintMe string
|
var actuallyPrintMe string
|
||||||
@ -318,82 +412,129 @@ func populateChat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
printToView("Chat", actuallyPrintMe)
|
printToView("Chat", actuallyPrintMe)
|
||||||
|
go populateList()
|
||||||
}
|
}
|
||||||
func populateList() {
|
func populateList() {
|
||||||
_, maxY := g.Size()
|
_, maxY := g.Size()
|
||||||
if testVar, err := k.ChatList(); err != nil {
|
if testVar, err := k.ChatList(); err != nil {
|
||||||
log.Printf("%+v", err)
|
log.Printf("%+v", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
clearView("List")
|
clearView("List")
|
||||||
var recentPMs = fmt.Sprintf("%s---[PMs]---%s\n", channelsHeaderColor, channelsColor)
|
var textBase = config.Colors.Channels.Basic.stylize("")
|
||||||
|
var recentPMs = textBase.append(config.Colors.Channels.Header.stylize("---[PMs]---\n"))
|
||||||
var recentPMsCount = 0
|
var recentPMsCount = 0
|
||||||
var recentChannels = fmt.Sprintf("%s---[Teams]---%s\n", channelsHeaderColor, channelsColor)
|
var recentChannels = textBase.append(config.Colors.Channels.Header.stylize("---[Teams]---\n"))
|
||||||
var recentChannelsCount = 0
|
var recentChannelsCount = 0
|
||||||
for _, s := range testVar.Result.Conversations {
|
for _, s := range testVar.Result.Conversations {
|
||||||
channels = append(channels, s.Channel)
|
channels = append(channels, s.Channel)
|
||||||
if s.Channel.MembersType == keybase.TEAM {
|
if s.Channel.MembersType == keybase.TEAM {
|
||||||
recentChannelsCount++
|
recentChannelsCount++
|
||||||
if recentChannelsCount <= ((maxY - 2) / 3) {
|
if recentChannelsCount <= ((maxY - 2) / 3) {
|
||||||
|
channel := fmt.Sprintf("%s\n\t#%s\n", s.Channel.Name, s.Channel.TopicName)
|
||||||
if s.Unread {
|
if s.Unread {
|
||||||
recentChannels += fmt.Sprintf("%s*", color(0))
|
recentChannels = recentChannels.append(config.Colors.Channels.Unread.stylize("*" + channel))
|
||||||
|
} else {
|
||||||
|
recentChannels = recentChannels.appendString(channel)
|
||||||
}
|
}
|
||||||
recentChannels += fmt.Sprintf("%s\n\t#%s\n%s", s.Channel.Name, s.Channel.TopicName, channelsColor)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
recentPMsCount++
|
recentPMsCount++
|
||||||
if recentPMsCount <= ((maxY - 2) / 3) {
|
if recentPMsCount <= ((maxY - 2) / 3) {
|
||||||
|
pmName := fmt.Sprintf("%s\n", cleanChannelName(s.Channel.Name))
|
||||||
if s.Unread {
|
if s.Unread {
|
||||||
recentChannels += fmt.Sprintf("%s*", color(0))
|
recentPMs = recentPMs.append(config.Colors.Channels.Unread.stylize("*" + pmName))
|
||||||
|
} else {
|
||||||
|
recentPMs = recentPMs.appendString(pmName)
|
||||||
}
|
}
|
||||||
recentPMs += fmt.Sprintf("%s\n%s", cleanChannelName(s.Channel.Name), channelsColor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
printToView("List", fmt.Sprintf("%s%s%s%s", channelsColor, recentPMs, recentChannels, noColor))
|
printToView("List", fmt.Sprintf("%s%s", recentPMs.string(), recentChannels.string()))
|
||||||
go generateRecentTabCompletionSlice()
|
generateRecentTabCompletionSlice()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// End update/populate views automatically
|
// End update/populate views automatically
|
||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
|
func formatMessageBody(body string) StyledString {
|
||||||
|
output := config.Colors.Message.Body.stylize(body)
|
||||||
|
|
||||||
|
output = colorReplaceMentionMe(output)
|
||||||
|
output = output.colorRegex(`_[^_]*_`, config.Colors.Message.Body.withItalic())
|
||||||
|
output = output.colorRegex(`~[^~]*~`, config.Colors.Message.Body.withStrikethrough())
|
||||||
|
output = output.colorRegex(`@[\w_]*(\.[\w_]+)*`, config.Colors.Message.LinkKeybase)
|
||||||
|
// TODO change how bold, italic etc works, so it uses boldOn boldOff ([1m and [22m)
|
||||||
|
output = output.colorRegex(`\*[^\*]*\*`, config.Colors.Message.Body.withBold())
|
||||||
|
output = output.replaceString("```", "\n<code>\n")
|
||||||
|
// TODO make background color cover whole line
|
||||||
|
output = output.colorRegex("<code>(.*\n)*<code>", config.Colors.Message.Code)
|
||||||
|
output = output.colorRegex("`[^`]*`", config.Colors.Message.Code)
|
||||||
|
// mention URL
|
||||||
|
output = output.colorRegex(`(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, config.Colors.Message.LinkURL)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use this more
|
||||||
|
func formatChannel(ch keybase.Channel) StyledString {
|
||||||
|
return config.Colors.Message.LinkKeybase.stylize(fmt.Sprintf("@%s#%s", ch.Name, ch.TopicName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorReplaceMentionMe(msg StyledString) StyledString {
|
||||||
|
return msg.colorRegex(`(@?\b`+k.Username+`\b)`, config.Colors.Message.Mention)
|
||||||
|
}
|
||||||
|
func colorUsername(username string) StyledString {
|
||||||
|
var color = config.Colors.Message.SenderDefault
|
||||||
|
if username == k.Username {
|
||||||
|
color = config.Colors.Message.Mention
|
||||||
|
}
|
||||||
|
return color.stylize(username)
|
||||||
|
}
|
||||||
|
|
||||||
func cleanChannelName(c string) string {
|
func cleanChannelName(c string) string {
|
||||||
newChannelName := strings.Replace(c, fmt.Sprintf("%s,", k.Username), "", 1)
|
newChannelName := strings.Replace(c, fmt.Sprintf("%s,", k.Username), "", 1)
|
||||||
return strings.Replace(newChannelName, fmt.Sprintf(",%s", k.Username), "", 1)
|
return strings.Replace(newChannelName, fmt.Sprintf(",%s", k.Username), "", 1)
|
||||||
}
|
}
|
||||||
func formatOutput(api keybase.ChatAPI) string {
|
|
||||||
ret := ""
|
func formatMessage(api keybase.ChatAPI, formatString string) string {
|
||||||
|
ret := config.Colors.Message.Header.stylize("")
|
||||||
msgType := api.Msg.Content.Type
|
msgType := api.Msg.Content.Type
|
||||||
switch msgType {
|
switch msgType {
|
||||||
case "text", "attachment":
|
case "text", "attachment":
|
||||||
var c = messageHeaderColor
|
ret = config.Colors.Message.Header.stylize(formatString)
|
||||||
ret = colorText(outputFormat, c, noColor)
|
|
||||||
tm := time.Unix(int64(api.Msg.SentAt), 0)
|
tm := time.Unix(int64(api.Msg.SentAt), 0)
|
||||||
var msg = api.Msg.Content.Text.Body
|
var msg = formatMessageBody(api.Msg.Content.Text.Body)
|
||||||
// mention teams or users
|
|
||||||
msg = colorRegex(msg, `(@\w*(\.\w+)*)`, messageLinkColor, messageBodyColor)
|
|
||||||
// mention URL
|
|
||||||
msg = colorRegex(msg, `(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, messageLinkColor, messageBodyColor)
|
|
||||||
msg = colorText(colorReplaceMentionMe(msg, messageBodyColor), messageBodyColor, c)
|
|
||||||
if msgType == "attachment" {
|
if msgType == "attachment" {
|
||||||
msg = fmt.Sprintf("%s\n%s", api.Msg.Content.Attachment.Object.Title, colorText(fmt.Sprintf("[Attachment: %s]", api.Msg.Content.Attachment.Object.Filename), messageAttachmentColor, c))
|
msg = config.Colors.Message.Body.stylize("$TITLE\n$FILE")
|
||||||
|
attachment := api.Msg.Content.Attachment
|
||||||
|
msg = msg.replaceString("$TITLE", attachment.Object.Title)
|
||||||
|
msg = msg.replace("$FILE", config.Colors.Message.Attachment.stylize(fmt.Sprintf("[Attachment: %s]", attachment.Object.Filename)))
|
||||||
}
|
}
|
||||||
user := colorUsername(api.Msg.Sender.Username, c)
|
|
||||||
device := colorText(api.Msg.Sender.DeviceName, messageSenderDeviceColor, c)
|
user := colorUsername(api.Msg.Sender.Username)
|
||||||
msgID := colorText(fmt.Sprintf("%d", api.Msg.ID), messageIdColor, c)
|
device := config.Colors.Message.SenderDevice.stylize(api.Msg.Sender.DeviceName)
|
||||||
ts := colorText(tm.Format(timeFormat), messageTimeColor, c)
|
msgID := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", api.Msg.ID))
|
||||||
ret = strings.Replace(ret, "$MSG", msg, 1)
|
date := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.DateFormat))
|
||||||
ret = strings.Replace(ret, "$USER", user, 1)
|
msgTime := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.TimeFormat))
|
||||||
ret = strings.Replace(ret, "$DEVICE", device, 1)
|
|
||||||
ret = strings.Replace(ret, "$ID", msgID, 1)
|
channelName := config.Colors.Message.ID.stylize(fmt.Sprintf("@%s#%s", api.Msg.Channel.Name, api.Msg.Channel.TopicName))
|
||||||
ret = strings.Replace(ret, "$TIME", ts, 1)
|
ret = ret.replace("$MSG", msg)
|
||||||
ret = strings.Replace(ret, "$DATE", colorText(tm.Format(dateFormat), messageTimeColor, c), 1)
|
ret = ret.replace("$USER", user)
|
||||||
ret = strings.Replace(ret, "```", fmt.Sprintf("\n<code>\n"), -1)
|
ret = ret.replace("$DEVICE", device)
|
||||||
|
ret = ret.replace("$ID", msgID)
|
||||||
|
ret = ret.replace("$TIME", msgTime)
|
||||||
|
ret = ret.replace("$DATE", date)
|
||||||
|
ret = ret.replace("$TEAM", channelName)
|
||||||
}
|
}
|
||||||
return ret
|
return ret.string()
|
||||||
|
}
|
||||||
|
func formatOutput(api keybase.ChatAPI) string {
|
||||||
|
format := config.Formatting.OutputFormat
|
||||||
|
if stream {
|
||||||
|
format = config.Formatting.OutputStreamFormat
|
||||||
|
}
|
||||||
|
return formatMessage(api, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
// End formatting
|
// End formatting
|
||||||
@ -410,9 +551,7 @@ func handleMessage(api keybase.ChatAPI) {
|
|||||||
}
|
}
|
||||||
if api.Msg.Content.Type == "text" || api.Msg.Content.Type == "attachment" {
|
if api.Msg.Content.Type == "text" || api.Msg.Content.Type == "attachment" {
|
||||||
go populateList()
|
go populateList()
|
||||||
msgBody := api.Msg.Content.Text.Body
|
|
||||||
msgSender := api.Msg.Sender.Username
|
msgSender := api.Msg.Sender.Username
|
||||||
channelName := api.Msg.Channel.Name
|
|
||||||
if !stream {
|
if !stream {
|
||||||
if msgSender != k.Username {
|
if msgSender != k.Username {
|
||||||
if api.Msg.Channel.MembersType == keybase.TEAM {
|
if api.Msg.Channel.MembersType == keybase.TEAM {
|
||||||
@ -421,7 +560,7 @@ func handleMessage(api keybase.ChatAPI) {
|
|||||||
if m.Text == k.Username {
|
if m.Text == k.Username {
|
||||||
// We are in a team
|
// We are in a team
|
||||||
if topicName != channel.TopicName {
|
if topicName != channel.TopicName {
|
||||||
printToView("Feed", fmt.Sprintf("[ %s#%s ] %s: %s", channelName, topicName, msgSender, msgBody))
|
printInfo(formatMessage(api, config.Formatting.OutputMentionFormat))
|
||||||
fmt.Print("\a")
|
fmt.Print("\a")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +569,7 @@ func handleMessage(api keybase.ChatAPI) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if msgSender != channel.Name {
|
if msgSender != channel.Name {
|
||||||
printToView("Feed", fmt.Sprintf("PM from @%s: %s", cleanChannelName(channelName), msgBody))
|
printInfo(formatMessage(api, config.Formatting.PMFormat))
|
||||||
fmt.Print("\a")
|
fmt.Print("\a")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,10 +585,9 @@ func handleMessage(api keybase.ChatAPI) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if api.Msg.Channel.MembersType == keybase.TEAM {
|
if api.Msg.Channel.MembersType == keybase.TEAM {
|
||||||
topicName := api.Msg.Channel.TopicName
|
printToView("Chat", formatOutput(api))
|
||||||
printToView("Chat", fmt.Sprintf("@%s#%s [%s]: %s", channelName, topicName, msgSender, msgBody))
|
|
||||||
} else {
|
} else {
|
||||||
printToView("Chat", fmt.Sprintf("PM @%s [%s]: %s", cleanChannelName(channelName), msgSender, msgBody))
|
printToView("Chat", formatMessage(api, config.Formatting.PMFormat))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -483,8 +621,8 @@ func handleInput(viewName string) error {
|
|||||||
if inputString == "" {
|
if inputString == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(inputString, cmdPrefix) {
|
if strings.HasPrefix(inputString, config.Basics.CmdPrefix) {
|
||||||
cmd := deleteEmpty(strings.Split(inputString[len(cmdPrefix):], " "))
|
cmd := deleteEmpty(strings.Split(inputString[len(config.Basics.CmdPrefix):], " "))
|
||||||
if len(cmd) < 1 {
|
if len(cmd) < 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -494,7 +632,7 @@ func handleInput(viewName string) error {
|
|||||||
} else if cmd[0] == "q" || cmd[0] == "quit" {
|
} else if cmd[0] == "q" || cmd[0] == "quit" {
|
||||||
return gocui.ErrQuit
|
return gocui.ErrQuit
|
||||||
} else {
|
} else {
|
||||||
printToView("Feed", fmt.Sprintf("Command '%s' not recognized", cmd[0]))
|
printError(fmt.Sprintf("Command '%s' not recognized", cmd[0]))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -503,6 +641,7 @@ func handleInput(viewName string) error {
|
|||||||
cmd[0] = inputString[:1]
|
cmd[0] = inputString[:1]
|
||||||
RunCommand(cmd...)
|
RunCommand(cmd...)
|
||||||
} else {
|
} else {
|
||||||
|
inputString = resolveRootEmojis(inputString)
|
||||||
go sendChat(inputString)
|
go sendChat(inputString)
|
||||||
}
|
}
|
||||||
// restore any tab completion view titles on input commit
|
// restore any tab completion view titles on input commit
|
||||||
@ -514,10 +653,11 @@ func handleInput(viewName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func sendChat(message string) {
|
func sendChat(message string) {
|
||||||
|
autoScrollView("Chat")
|
||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
_, err := chat.Send(message)
|
_, err := chat.Send(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printToView("Feed", fmt.Sprintf("There was an error %+v", err))
|
printError(fmt.Sprintf("There was an error %+v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
111
tabComplete.go
111
tabComplete.go
@ -20,53 +20,54 @@ func handleTab(viewName string) error {
|
|||||||
inputString, err := getInputString(viewName)
|
inputString, err := getInputString(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
// if you successfully get an input string, grab the last word from the string
|
||||||
|
ss := regexp.MustCompile(`[ #]`).Split(inputString, -1)
|
||||||
|
s := ss[len(ss)-1]
|
||||||
|
// create a variable in which to store the result
|
||||||
|
var resultSlice []string
|
||||||
|
// if the word starts with a : its an emoji lookup
|
||||||
|
if strings.HasPrefix(s, ":") {
|
||||||
|
resultSlice = getEmojiTabCompletionSlice(s)
|
||||||
|
} else if strings.HasPrefix(s, "/") {
|
||||||
|
generateCommandTabCompletionSlice()
|
||||||
|
s = strings.Replace(s, "/", "", 1)
|
||||||
|
resultSlice = getCommandTabCompletionSlice(s)
|
||||||
} else {
|
} else {
|
||||||
// if you successfully get an input string, grab the last word from the string
|
if strings.HasPrefix(s, "@") {
|
||||||
ss := regexp.MustCompile(`[ #]`).Split(inputString, -1)
|
// now in case the word (s) is a mention @something, lets remove it to normalize
|
||||||
s := ss[len(ss)-1]
|
s = strings.Replace(s, "@", "", 1)
|
||||||
// create a variable in which to store the result
|
|
||||||
var resultSlice []string
|
|
||||||
// if the word starts with a : its an emoji lookup
|
|
||||||
if strings.HasPrefix(s, ":") {
|
|
||||||
resultSlice = getEmojiTabCompletionSlice(s)
|
|
||||||
} else if strings.HasPrefix(s, "/") {
|
|
||||||
generateCommandTabCompletionSlice()
|
|
||||||
s = strings.Replace(s, "/", "", 1)
|
|
||||||
resultSlice = getCommandTabCompletionSlice(s)
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(s, "@") {
|
|
||||||
// now in case the word (s) is a mention @something, lets remove it to normalize
|
|
||||||
s = strings.Replace(s, "@", "", 1)
|
|
||||||
}
|
|
||||||
// now call get the list of all possible cantidates that have that as a prefix
|
|
||||||
resultSlice = getChannelTabCompletionSlice(s)
|
|
||||||
}
|
}
|
||||||
rLen := len(resultSlice)
|
// now call get the list of all possible cantidates that have that as a prefix
|
||||||
lcp := longestCommonPrefix(resultSlice)
|
resultSlice = getChannelTabCompletionSlice(s)
|
||||||
if lcp != "" {
|
}
|
||||||
originalViewTitle := getViewTitle("Input")
|
rLen := len(resultSlice)
|
||||||
newViewTitle := ""
|
lcp := longestCommonPrefix(resultSlice)
|
||||||
if rLen >= 1 && originalViewTitle != "" {
|
if lcp != "" {
|
||||||
if rLen == 1 {
|
originalViewTitle := getViewTitle("Input")
|
||||||
newViewTitle = originalViewTitle
|
newViewTitle := ""
|
||||||
} else if rLen <= 5 {
|
if rLen >= 1 && originalViewTitle != "" {
|
||||||
newViewTitle = fmt.Sprintf("%s|| %s", originalViewTitle, strings.Join(resultSlice, " "))
|
if rLen == 1 {
|
||||||
} else if rLen > 5 {
|
newViewTitle = originalViewTitle
|
||||||
newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5)
|
} else if rLen <= 5 {
|
||||||
}
|
newViewTitle = fmt.Sprintf("%s|| %s", originalViewTitle, strings.Join(resultSlice, " "))
|
||||||
setViewTitle(viewName, newViewTitle)
|
} else if rLen > 5 {
|
||||||
remainder := stringRemainder(s, lcp)
|
newViewTitle = fmt.Sprintf("%s|| %s +%d more", originalViewTitle, strings.Join(resultSlice[:6], " "), rLen-5)
|
||||||
writeToView(viewName, remainder)
|
|
||||||
}
|
}
|
||||||
|
setViewTitle(viewName, newViewTitle)
|
||||||
|
remainder := stringRemainder(s, lcp)
|
||||||
|
writeToView(viewName, remainder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main tab completion functions
|
// Main tab completion functions
|
||||||
func getEmojiTabCompletionSlice(inputWord string) []string {
|
func getEmojiTabCompletionSlice(inputWord string) []string {
|
||||||
// use the emojiSlice from emojiList.go and filter it for the input word
|
// use the emojiSlice from emojiList.go and filter it for the input word
|
||||||
resultSlice := filterStringSlice(emojiSlice, inputWord)
|
//resultSlice := filterStringSlice(emojiSlice, inputWord)
|
||||||
|
resultSlice := filterEmojiMap(emojiMap, inputWord)
|
||||||
return resultSlice
|
return resultSlice
|
||||||
}
|
}
|
||||||
func getChannelTabCompletionSlice(inputWord string) []string {
|
func getChannelTabCompletionSlice(inputWord string) []string {
|
||||||
@ -123,22 +124,23 @@ func getCurrentChannelMembership() []string {
|
|||||||
var rs []string
|
var rs []string
|
||||||
if channel.Name != "" {
|
if channel.Name != "" {
|
||||||
t := k.NewTeam(channel.Name)
|
t := k.NewTeam(channel.Name)
|
||||||
if testVar, err := t.MemberList(); err != nil {
|
testVar, err := t.MemberList()
|
||||||
|
if err != nil {
|
||||||
return rs // then this isn't a team, its a PM or there was an error in the API call
|
return rs // then this isn't a team, its a PM or there was an error in the API call
|
||||||
} else {
|
|
||||||
for _, m := range testVar.Result.Members.Owners {
|
|
||||||
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
|
||||||
}
|
|
||||||
for _, m := range testVar.Result.Members.Admins {
|
|
||||||
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
|
||||||
}
|
|
||||||
for _, m := range testVar.Result.Members.Writers {
|
|
||||||
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
|
||||||
}
|
|
||||||
for _, m := range testVar.Result.Members.Readers {
|
|
||||||
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for _, m := range testVar.Result.Members.Owners {
|
||||||
|
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
||||||
|
}
|
||||||
|
for _, m := range testVar.Result.Members.Admins {
|
||||||
|
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
||||||
|
}
|
||||||
|
for _, m := range testVar.Result.Members.Writers {
|
||||||
|
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
||||||
|
}
|
||||||
|
for _, m := range testVar.Result.Members.Readers {
|
||||||
|
rs = append(rs, fmt.Sprintf("%+v", m.Username))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return rs
|
return rs
|
||||||
}
|
}
|
||||||
@ -151,6 +153,15 @@ func filterStringSlice(ss []string, fv string) []string {
|
|||||||
}
|
}
|
||||||
return rs
|
return rs
|
||||||
}
|
}
|
||||||
|
func filterEmojiMap(eMap map[string]emojiData, fv string) []string {
|
||||||
|
var rs []string
|
||||||
|
for k, _ := range eMap {
|
||||||
|
if strings.HasPrefix(k, fv) {
|
||||||
|
rs = append(rs, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
func longestCommonPrefix(ss []string) string {
|
func longestCommonPrefix(ss []string) string {
|
||||||
// cover the case where the slice has no or one members
|
// cover the case where the slice has no or one members
|
||||||
switch len(ss) {
|
switch len(ss) {
|
||||||
|
|||||||
@ -20,15 +20,17 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tcmdShowReactions(m keybase.ChatAPI) {
|
func tcmdShowReactions(m keybase.ChatAPI) {
|
||||||
where := ""
|
|
||||||
team := false
|
team := false
|
||||||
|
user := colorUsername(m.Msg.Sender.Username)
|
||||||
|
id := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", m.Msg.Content.Reaction.M))
|
||||||
|
reaction := config.Colors.Message.Reaction.stylize(m.Msg.Content.Reaction.B)
|
||||||
|
where := config.Colors.Message.LinkKeybase.stylize("a PM")
|
||||||
if m.Msg.Channel.MembersType == keybase.TEAM {
|
if m.Msg.Channel.MembersType == keybase.TEAM {
|
||||||
team = true
|
team = true
|
||||||
where = fmt.Sprintf("in @%s#%s", m.Msg.Channel.Name, m.Msg.Channel.TopicName)
|
where = formatChannel(m.Msg.Channel)
|
||||||
} else {
|
} else {
|
||||||
where = fmt.Sprintf("in a PM")
|
|
||||||
}
|
}
|
||||||
printToView("Feed", fmt.Sprintf("%s reacted to %d with %s %s", m.Msg.Sender.Username, m.Msg.Content.Reaction.M, m.Msg.Content.Reaction.B, where))
|
printInfoF("$TEXT reacted to [$TEXT] with $TEXT in $TEXT", user, id, reaction, where)
|
||||||
if channel.Name == m.Msg.Channel.Name {
|
if channel.Name == m.Msg.Channel.Name {
|
||||||
if team {
|
if team {
|
||||||
if channel.TopicName == m.Msg.Channel.TopicName {
|
if channel.TopicName == m.Msg.Channel.TopicName {
|
||||||
|
|||||||
73
types.go
73
types.go
@ -17,3 +17,76 @@ type TypeCommand struct {
|
|||||||
Description string // A short description of the command
|
Description string // A short description of the command
|
||||||
Exec func(keybase.ChatAPI) // A function that takes a raw chat message as input
|
Exec func(keybase.ChatAPI) // A function that takes a raw chat message as input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config holds user-configurable values
|
||||||
|
type Config struct {
|
||||||
|
filepath string `toml:"-"` // filepath is not stored in the config file, but is written to the Config struct so it's known where the config was loaded from
|
||||||
|
Basics Basics `toml:"basics"`
|
||||||
|
Formatting Formatting `toml:"formatting"`
|
||||||
|
Colors Colors `toml:"colors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basics holds the 'basics' section of the config file
|
||||||
|
type Basics struct {
|
||||||
|
DownloadPath string `toml:"download_path"`
|
||||||
|
Colorless bool `toml:"colorless"`
|
||||||
|
CmdPrefix string `toml:"cmd_prefix"`
|
||||||
|
UnicodeEmojis bool `toml:"unicode_emojis"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatting holds the 'formatting' section of the config file
|
||||||
|
type Formatting struct {
|
||||||
|
OutputFormat string `toml:"output_format"`
|
||||||
|
OutputStreamFormat string `toml:"output_stream_format"`
|
||||||
|
OutputMentionFormat string `toml:"output_mention_format"`
|
||||||
|
PMFormat string `toml:"pm_format"`
|
||||||
|
DateFormat string `toml:"date_format"`
|
||||||
|
TimeFormat string `toml:"time_format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors holds the 'colors' section of the config file
|
||||||
|
type Colors struct {
|
||||||
|
Channels Channels `toml:"channels"`
|
||||||
|
Message Message `toml:"message"`
|
||||||
|
Feed Feed `toml:"feed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style holds basic style information
|
||||||
|
type Style struct {
|
||||||
|
Foreground string `toml:"foreground"`
|
||||||
|
Background string `toml:"background"`
|
||||||
|
Italic bool `toml:"italic"`
|
||||||
|
Bold bool `toml:"bold"`
|
||||||
|
Underline bool `toml:"underline"`
|
||||||
|
Strikethrough bool `toml:"strikethrough"`
|
||||||
|
Inverse bool `toml:"inverse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channels holds the style information for various elements of a channel
|
||||||
|
type Channels struct {
|
||||||
|
Basic Style `toml:"basic"`
|
||||||
|
Header Style `toml:"header"`
|
||||||
|
Unread Style `toml:"unread"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message holds the style information for various elements of a message
|
||||||
|
type Message struct {
|
||||||
|
Body Style `toml:"body"`
|
||||||
|
Header Style `toml:"header"`
|
||||||
|
Mention Style `toml:"mention"`
|
||||||
|
ID Style `toml:"id"`
|
||||||
|
Time Style `toml:"time"`
|
||||||
|
SenderDefault Style `toml:"sender_default"`
|
||||||
|
SenderDevice Style `toml:"sender_device"`
|
||||||
|
Attachment Style `toml:"attachment"`
|
||||||
|
LinkURL Style `toml:"link_url"`
|
||||||
|
LinkKeybase Style `toml:"link_keybase"`
|
||||||
|
Reaction Style `toml:"reaction"`
|
||||||
|
Code Style `toml:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed holds the style information for various elements of the feed window
|
||||||
|
type Feed struct {
|
||||||
|
Basic Style `toml:"basic"`
|
||||||
|
Error Style `toml:"error"`
|
||||||
|
}
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// Path where Downloaded files will default to
|
|
||||||
var downloadPath = "/tmp/"
|
|
||||||
|
|
||||||
var colorless = false
|
|
||||||
var channelsColor = color(8)
|
|
||||||
var channelsHeaderColor = color(6)
|
|
||||||
var noColor = color(-1)
|
|
||||||
var mentionColor = color(3)
|
|
||||||
var messageHeaderColor = color(8)
|
|
||||||
var messageIdColor = color(7)
|
|
||||||
var messageTimeColor = color(6)
|
|
||||||
var messageSenderDefaultColor = color(8)
|
|
||||||
var messageSenderDeviceColor = color(8)
|
|
||||||
var messageBodyColor = noColor
|
|
||||||
var messageAttachmentColor = color(2)
|
|
||||||
var messageLinkColor = color(4)
|
|
||||||
|
|
||||||
// BASH-like PS1 variable equivalent
|
|
||||||
var outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
|
||||||
|
|
||||||
// 02 = Day, Jan = Month, 06 = Year
|
|
||||||
var dateFormat = "02Jan06"
|
|
||||||
|
|
||||||
// 15 = hours, 04 = minutes, 05 = seconds
|
|
||||||
var timeFormat = "15:04"
|
|
||||||
|
|
||||||
// The prefix before evaluating a command
|
|
||||||
var cmdPrefix = "/"
|
|
||||||
Reference in New Issue
Block a user