mirror of
https://github.com/Rudi9719/kbtui.git
synced 2026-03-22 17:57:23 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 709ff33ae5 | |||
|
c36b278484
|
|||
|
a7ae3b37bb
|
|||
| 68ab6db1e4 | |||
|
97e055dd61
|
|||
| 9b6bd7442b | |||
| ae9f71be96 | |||
| 88a6f709f9 | |||
| f222464849 | |||
| f55270f423 | |||
| 67fc7c6e5d | |||
| fc48ad5c4a | |||
| 66e2552a7e | |||
| 9714e39751 | |||
| 8abde78bf7 | |||
| ff120c0366 | |||
| 0a4c2614a8 | |||
| 12d41c018e | |||
| 9a71d50ab7 | |||
| db08278780 | |||
| 84e5beada4 | |||
|
cd79a10528
|
|||
| a5f017de45 | |||
| 5c5ebc1d45 | |||
| 3d353b33e8 | |||
| 023f22a1ea | |||
| bc7ef238de | |||
| e5285d9d36 | |||
| e91506d319 | |||
| dc67abb1a3 | |||
| 1d95fea9f3 | |||
| 74da5c530e | |||
| 430c427487 | |||
| d02dd317a5 |
40
.github/workflows/go.yml
vendored
Normal file
40
.github/workflows/go.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
name: Go
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
name: Build
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
go get -v -t -d ./...
|
||||||
|
go get github.com/magefile/mage
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go run build.go buildbeta
|
||||||
|
- name: Upload Artifact
|
||||||
|
if: matrix.platform != 'windows-latest'
|
||||||
|
uses: actions/upload-artifact@v1.0.0
|
||||||
|
with:
|
||||||
|
name: kbtui-${{ matrix.platform }}-buildbeta
|
||||||
|
path: kbtui
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
if: matrix.platform == 'windows-latest'
|
||||||
|
uses: actions/upload-artifact@v1.0.0
|
||||||
|
with:
|
||||||
|
name: kbtui-${{ matrix.platform }}-buildbeta
|
||||||
|
path: kbtui.exe
|
||||||
41
README.md
41
README.md
@ -1,39 +1,2 @@
|
|||||||
# kbtui
|
# Announcement
|
||||||
Keybase TUI written in Go using [@dxb](https://keybase.io/dxb)'s
|
Development on this branch of kbtui has been stopped and migrated to https://git.hugfreevikings.wtf/keybase/kbtui
|
||||||
Keybase [bot framework](https://godoc.org/samhofi.us/x/keybase).
|
|
||||||
It started as a joke, then a bash script, and now here it is!
|
|
||||||
|
|
||||||
For support or suggestions check out the [kbtui team](https://keybase.io/team/kbtui)
|
|
||||||
|
|
||||||
## Features
|
|
||||||
* Dark Mode (or rather mode based on Terminal Theme)
|
|
||||||
* Read and reply to messages
|
|
||||||
* Feed view to see mentions
|
|
||||||
* Stream view to see all incoming messages
|
|
||||||
* List view to show activity
|
|
||||||
* Chat view to interact with the current channel
|
|
||||||
* Marks unread messages in the List view
|
|
||||||
* Reactions to messages
|
|
||||||
* Auto #general teams when not given a channel
|
|
||||||
* Pretty format headers in List view from window size
|
|
||||||
* Message editing
|
|
||||||
* Twitter-style feed reading public messages
|
|
||||||
* Message replies
|
|
||||||
|
|
||||||
## Todo
|
|
||||||
* Track multiple conversations at once
|
|
||||||
|
|
||||||
|
|
||||||
### Building and Running
|
|
||||||
Easiest Way:
|
|
||||||
```
|
|
||||||
go get -u github.com/rudi9719/kbtui
|
|
||||||
```
|
|
||||||
Or you can do the following:
|
|
||||||
```
|
|
||||||
go get github.com/magefile/mage/mage
|
|
||||||
go run build.go {build, buildBeta... etc}
|
|
||||||
./kbtui
|
|
||||||
```
|
|
||||||
Mage is a requirement for building `kbtui` as it will automatically handle/manage imports as well as mage is used to generate the
|
|
||||||
file for emoji completion.
|
|
||||||
|
|||||||
15
cmdConfig.go
15
cmdConfig.go
@ -31,7 +31,7 @@ func cmdConfig(cmd []string) {
|
|||||||
printError(err.Error())
|
printError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
|
printInfoF("Config file loaded: $TEXT", config.Colors.Feed.File.stylize(config.filepath))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case len(cmd) > 2:
|
case len(cmd) > 2:
|
||||||
@ -41,7 +41,7 @@ func cmdConfig(cmd []string) {
|
|||||||
printError(err.Error())
|
printError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
|
printInfoF("Config file loaded: $TEXT", config.Colors.Feed.File.stylize(config.filepath))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ func cmdConfig(cmd []string) {
|
|||||||
|
|
||||||
func readConfig(filepath ...string) (*Config, error) {
|
func readConfig(filepath ...string) (*Config, error) {
|
||||||
var result = new(Config)
|
var result = new(Config)
|
||||||
var configFile string
|
var configFile, path string
|
||||||
var env bool
|
var env bool
|
||||||
|
|
||||||
// Load default config first, this way any values missing from the provided config file will remain the default value
|
// Load default config first, this way any values missing from the provided config file will remain the default value
|
||||||
@ -61,8 +61,13 @@ func readConfig(filepath ...string) (*Config, error) {
|
|||||||
case 0:
|
case 0:
|
||||||
configFile, env = os.LookupEnv("KBTUI_CFG")
|
configFile, env = os.LookupEnv("KBTUI_CFG")
|
||||||
if !env {
|
if !env {
|
||||||
configFile = "~/.config/kbtui.toml"
|
path, env = os.LookupEnv("HOME")
|
||||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
if env {
|
||||||
|
configFile = path + "/.config/kbtui.toml"
|
||||||
|
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||||
|
configFile = "kbtui.toml"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
configFile = "kbtui.toml"
|
configFile = "kbtui.toml"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,9 +48,10 @@ func cmdDownloadFile(cmd []string) {
|
|||||||
|
|
||||||
_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", config.Basics.DownloadPath, fileName))
|
_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", config.Basics.DownloadPath, fileName))
|
||||||
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name)
|
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name)
|
||||||
|
fileNameStylizied := config.Colors.Feed.File.stylize(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printErrorF(fmt.Sprintf("There was an error downloading %s from $TEXT", fileName), channelName)
|
printErrorF("There was an error downloading $TEXT from $TEXT", fileNameStylizied, channelName)
|
||||||
} else {
|
} else {
|
||||||
printInfoF(fmt.Sprintf("Downloaded %s from $TEXT", fileName), channelName)
|
printInfoF("Downloaded $TEXT from $TEXT", fileNameStylizied, channelName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
cmdFollow.go
Normal file
34
cmdFollow.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// +build !rm_basic_commands allcommands followcmd
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command := Command{
|
||||||
|
Cmd: []string{"follow"},
|
||||||
|
Description: "$username - Follows the given user",
|
||||||
|
Help: "",
|
||||||
|
Exec: cmdFollow,
|
||||||
|
}
|
||||||
|
RegisterCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdFollow(cmd []string) {
|
||||||
|
if len(cmd) == 2 {
|
||||||
|
go follow(cmd[1])
|
||||||
|
} else {
|
||||||
|
printFollowHelp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func follow(username string) {
|
||||||
|
k.Exec("follow", username, "-y")
|
||||||
|
printInfoF("Now follows $TEXT", config.Colors.Message.LinkKeybase.stylize(username))
|
||||||
|
followedInSteps[username] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func printFollowHelp() {
|
||||||
|
printInfo(fmt.Sprintf("To follow a user use %sfollow <username>", config.Basics.CmdPrefix))
|
||||||
|
}
|
||||||
135
cmdInspect.go
Normal file
135
cmdInspect.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// +build !rm_basic_commands allcommands inspectcmd
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"samhofi.us/x/keybase"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command := Command{
|
||||||
|
Cmd: []string{"inspect", "id"},
|
||||||
|
Description: "$identifier - shows info about $identifier ($identifier is either username, messageId or team)",
|
||||||
|
Help: "",
|
||||||
|
Exec: cmdInspect,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdInspect(cmd []string) {
|
||||||
|
if len(cmd) == 2 {
|
||||||
|
regexIsNumeric := regexp.MustCompile(`^\d+$`)
|
||||||
|
if regexIsNumeric.MatchString(cmd[1]) {
|
||||||
|
// Then it must be a message id
|
||||||
|
id, _ := strconv.Atoi(cmd[1])
|
||||||
|
go printMessage(id)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
go printUser(strings.Replace(cmd[1], "@", "", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
printInfo(fmt.Sprintf("To inspect something use %sid <username/messageId>", config.Basics.CmdPrefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func printMessage(id int) {
|
||||||
|
chat := k.NewChat(channel)
|
||||||
|
messages, err := chat.ReadMessage(id)
|
||||||
|
if err == nil {
|
||||||
|
var response StyledString
|
||||||
|
if messages != nil && len((*messages).Result.Messages) > 0 {
|
||||||
|
message := (*messages).Result.Messages[0].Msg
|
||||||
|
var apiCast keybase.ChatAPI
|
||||||
|
apiCast.Msg = &message
|
||||||
|
response = formatOutput(apiCast)
|
||||||
|
} else {
|
||||||
|
response = config.Colors.Feed.Error.stylize("message not found")
|
||||||
|
}
|
||||||
|
printToView("Chat", response.string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatProofs(userLookup keybase.UserAPI) StyledString {
|
||||||
|
messageColor := config.Colors.Message
|
||||||
|
message := basicStyle.stylize("")
|
||||||
|
for _, proof := range userLookup.Them[0].ProofsSummary.All {
|
||||||
|
style := config.Colors.Feed.Success
|
||||||
|
if proof.State != 1 {
|
||||||
|
style = config.Colors.Feed.Error
|
||||||
|
}
|
||||||
|
proofString := style.stylize("Proof [$NAME@$SITE]: $URL\n")
|
||||||
|
proofString = proofString.replace("$NAME", messageColor.SenderDefault.stylize(proof.Nametag))
|
||||||
|
proofString = proofString.replace("$SITE", messageColor.SenderDevice.stylize(proof.ProofType))
|
||||||
|
proofString = proofString.replace("$URL", messageColor.LinkURL.stylize(proof.HumanURL))
|
||||||
|
message = message.append(proofString)
|
||||||
|
}
|
||||||
|
return message.appendString("\n")
|
||||||
|
}
|
||||||
|
func formatProfile(userLookup keybase.UserAPI) StyledString {
|
||||||
|
messageColor := config.Colors.Message
|
||||||
|
user := userLookup.Them[0]
|
||||||
|
profileText := messageColor.Body.stylize("Name: $FNAME\nLocation: $LOC\nBio: $BIO\n")
|
||||||
|
profileText = profileText.replaceString("$FNAME", user.Profile.FullName)
|
||||||
|
profileText = profileText.replaceString("$LOC", user.Profile.Location)
|
||||||
|
profileText = profileText.replaceString("$BIO", user.Profile.Bio)
|
||||||
|
|
||||||
|
return profileText
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatFollowState(userLookup keybase.UserAPI) StyledString {
|
||||||
|
username := userLookup.Them[0].Basics.Username
|
||||||
|
followSteps := followedInSteps[username]
|
||||||
|
if followSteps == 1 {
|
||||||
|
return config.Colors.Feed.Success.stylize("<Followed!>\n\n")
|
||||||
|
} else if followSteps > 1 {
|
||||||
|
var steps []string
|
||||||
|
for head := username; head != ""; head = trustTreeParent[head] {
|
||||||
|
steps = append(steps, fmt.Sprintf("[%s]", head))
|
||||||
|
}
|
||||||
|
trustLine := fmt.Sprintf("Indirect follow: <%s>\n\n", strings.Join(steps, " Followed by "))
|
||||||
|
return config.Colors.Message.Body.stylize(trustLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
return basicStyle.stylize("")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatFollowerAndFollowedList(username string, listType string) StyledString {
|
||||||
|
messageColor := config.Colors.Message
|
||||||
|
response := basicStyle.stylize("")
|
||||||
|
bytes, _ := k.Exec("list-"+listType, username)
|
||||||
|
bigString := string(bytes)
|
||||||
|
lines := strings.Split(bigString, "\n")
|
||||||
|
response = response.appendString(fmt.Sprintf("%s (%d): ", listType, len(lines)-1))
|
||||||
|
for i, user := range lines[:len(lines)-1] {
|
||||||
|
if i != 0 {
|
||||||
|
response = response.appendString(", ")
|
||||||
|
}
|
||||||
|
response = response.append(messageColor.LinkKeybase.stylize(user))
|
||||||
|
response = response.append(getUserFlags(user))
|
||||||
|
}
|
||||||
|
return response.appendString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printUser(username string) {
|
||||||
|
messageColor := config.Colors.Message
|
||||||
|
|
||||||
|
userLookup, _ := k.UserLookup(username)
|
||||||
|
|
||||||
|
response := messageColor.Header.stylize("[Inspecting `$USER`]\n")
|
||||||
|
response = response.replace("$USER", messageColor.SenderDefault.stylize(username))
|
||||||
|
response = response.append(formatProfile(userLookup))
|
||||||
|
response = response.append(formatFollowState(userLookup))
|
||||||
|
|
||||||
|
response = response.append(formatProofs(userLookup))
|
||||||
|
response = response.append(formatFollowerAndFollowedList(username, "followers"))
|
||||||
|
response = response.append(formatFollowerAndFollowedList(username, "following"))
|
||||||
|
|
||||||
|
printToView("Chat", response.string())
|
||||||
|
}
|
||||||
19
cmdTags.go
Normal file
19
cmdTags.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// +ignore
|
||||||
|
// +build allcommands tagscmd
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command := Command{
|
||||||
|
Cmd: []string{"tags", "map"},
|
||||||
|
Description: "$- Create map of users following users, to populate $TAGS",
|
||||||
|
Help: "",
|
||||||
|
Exec: cmdTags,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdTags(cmd []string) {
|
||||||
|
go generateFollowersList()
|
||||||
|
}
|
||||||
33
cmdUnfollow.go
Normal file
33
cmdUnfollow.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// +build !rm_basic_commands allcommands followcmd
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command := Command{
|
||||||
|
Cmd: []string{"unfollow"},
|
||||||
|
Description: "$username - Unfollows the given user",
|
||||||
|
Help: "",
|
||||||
|
Exec: cmdUnfollow,
|
||||||
|
}
|
||||||
|
RegisterCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdUnfollow(cmd []string) {
|
||||||
|
if len(cmd) == 2 {
|
||||||
|
go unfollow(cmd[1])
|
||||||
|
} else {
|
||||||
|
printUnfollowHelp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func unfollow(username string) {
|
||||||
|
k.Exec("unfollow", username)
|
||||||
|
printInfoF("Now unfollows $TEXT", config.Colors.Message.LinkKeybase.stylize(username))
|
||||||
|
}
|
||||||
|
|
||||||
|
func printUnfollowHelp() {
|
||||||
|
printInfo(fmt.Sprintf("To unfollow a user use %sunfollow <username>", config.Basics.CmdPrefix))
|
||||||
|
}
|
||||||
@ -40,10 +40,11 @@ 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()
|
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name)
|
||||||
|
fileNameStylized := config.Colors.Feed.File.stylize(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printError(fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channelName, err))
|
printError(fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channel.Name, err))
|
||||||
} else {
|
} else {
|
||||||
printInfo(fmt.Sprintf("Uploaded %s to %s", filePath, channelName))
|
printInfoF("Uploaded $TEXT to $TEXT", fileNameStylized, channelName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,7 +81,7 @@ func cmdPopulateWall(cmd []string) {
|
|||||||
apiCast.Msg = &api.Result.Messages[i].Msg
|
apiCast.Msg = &api.Result.Messages[i].Msg
|
||||||
result[apiCast.Msg.SentAt] = apiCast
|
result[apiCast.Msg.SentAt] = apiCast
|
||||||
newMessage := formatOutput(apiCast)
|
newMessage := formatOutput(apiCast)
|
||||||
printMe = append(printMe, newMessage)
|
printMe = append(printMe, newMessage.string())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ func cmdPopulateWall(cmd []string) {
|
|||||||
sort.Ints(keys)
|
sort.Ints(keys)
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
actuallyPrintMe += formatOutput(result[k]) + "\n"
|
actuallyPrintMe += formatOutput(result[k]).string() + "\n"
|
||||||
}
|
}
|
||||||
printToView("Chat", fmt.Sprintf("\n<Wall>\n\n%s\nYour wall query took %s\n</Wall>\n", actuallyPrintMe, time.Since(start)))
|
printToView("Chat", fmt.Sprintf("\n<Wall>\n\n%s\nYour wall query took %s\n</Wall>\n", actuallyPrintMe, time.Since(start)))
|
||||||
}
|
}
|
||||||
|
|||||||
87
colors.go
87
colors.go
@ -79,6 +79,7 @@ func (s Style) withBackground(color int) Style {
|
|||||||
s.Background = colorFromInt(color)
|
s.Background = colorFromInt(color)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Style) withBold() Style {
|
func (s Style) withBold() Style {
|
||||||
s.Bold = true
|
s.Bold = true
|
||||||
return s
|
return s
|
||||||
@ -107,30 +108,31 @@ func (s Style) toANSI() string {
|
|||||||
if config.Basics.Colorless {
|
if config.Basics.Colorless {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
output := "\x1b[0m\x1b[0"
|
styleSlice := []string{"0"}
|
||||||
|
|
||||||
if colorFromString(s.Foreground) != normal {
|
if colorFromString(s.Foreground) != normal {
|
||||||
output += fmt.Sprintf(";%d", 30+colorFromString(s.Foreground))
|
styleSlice = append(styleSlice, fmt.Sprintf("%d", 30+colorFromString(s.Foreground)))
|
||||||
}
|
}
|
||||||
if colorFromString(s.Background) != normal {
|
if colorFromString(s.Background) != normal {
|
||||||
output += fmt.Sprintf(";%d", 40+colorFromString(s.Background))
|
styleSlice = append(styleSlice, fmt.Sprintf("%d", 40+colorFromString(s.Background)))
|
||||||
}
|
}
|
||||||
if s.Bold {
|
if s.Bold {
|
||||||
output += ";1"
|
styleSlice = append(styleSlice, "1")
|
||||||
}
|
}
|
||||||
if s.Italic {
|
if s.Italic {
|
||||||
output += ";3"
|
styleSlice = append(styleSlice, "3")
|
||||||
}
|
}
|
||||||
if s.Underline {
|
if s.Underline {
|
||||||
output += ";4"
|
styleSlice = append(styleSlice, "4")
|
||||||
}
|
}
|
||||||
if s.Inverse {
|
if s.Inverse {
|
||||||
output += ";7"
|
styleSlice = append(styleSlice, "7")
|
||||||
}
|
}
|
||||||
if s.Strikethrough {
|
if s.Strikethrough {
|
||||||
output += ";9"
|
styleSlice = append(styleSlice, "9")
|
||||||
}
|
}
|
||||||
|
|
||||||
return output + "m"
|
return "\x1b[" + strings.Join(styleSlice, ";") + "m"
|
||||||
}
|
}
|
||||||
|
|
||||||
// End Colors
|
// End Colors
|
||||||
@ -142,6 +144,12 @@ type StyledString struct {
|
|||||||
style Style
|
style Style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss StyledString) withStyle(style Style) StyledString {
|
||||||
|
return StyledString{ss.message, style}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO change StyledString to have styles at start-end indexes.
|
||||||
|
|
||||||
// TODO handle all formatting types
|
// TODO handle all formatting types
|
||||||
func (s Style) sprintf(base string, parts ...StyledString) StyledString {
|
func (s Style) sprintf(base string, parts ...StyledString) StyledString {
|
||||||
text := s.stylize(removeFormatting(base))
|
text := s.stylize(removeFormatting(base))
|
||||||
@ -158,51 +166,58 @@ func (s Style) sprintf(base string, parts ...StyledString) StyledString {
|
|||||||
func (s Style) stylize(msg string) StyledString {
|
func (s Style) stylize(msg string) StyledString {
|
||||||
return StyledString{msg, s}
|
return StyledString{msg, s}
|
||||||
}
|
}
|
||||||
func (t StyledString) stringFollowedByStyle(style Style) string {
|
func (ss StyledString) stringFollowedByStyle(style Style) string {
|
||||||
return t.style.toANSI() + t.message + style.toANSI()
|
return ss.style.toANSI() + ss.message + style.toANSI()
|
||||||
}
|
}
|
||||||
func (t StyledString) string() string {
|
func (ss StyledString) string() string {
|
||||||
return t.stringFollowedByStyle(basicStyle)
|
return ss.stringFollowedByStyle(basicStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t StyledString) replace(match string, value StyledString) StyledString {
|
func (ss StyledString) replace(match string, value StyledString) StyledString {
|
||||||
return t.replaceN(match, value, -1)
|
return ss.replaceN(match, value, -1)
|
||||||
}
|
}
|
||||||
func (t StyledString) replaceN(match string, value StyledString, n int) StyledString {
|
func (ss StyledString) replaceN(match string, value StyledString, n int) StyledString {
|
||||||
t.message = strings.Replace(t.message, match, value.stringFollowedByStyle(t.style), n)
|
ss.message = strings.Replace(ss.message, match, value.stringFollowedByStyle(ss.style), n)
|
||||||
return t
|
return ss
|
||||||
}
|
}
|
||||||
func (t StyledString) replaceString(match string, value string) StyledString {
|
func (ss StyledString) replaceString(match string, value string) StyledString {
|
||||||
t.message = strings.Replace(t.message, match, value, -1)
|
ss.message = strings.Replace(ss.message, match, value, -1)
|
||||||
return t
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overrides current formatting
|
// Overrides current formatting
|
||||||
func (t StyledString) colorRegex(match string, style Style) StyledString {
|
func (ss StyledString) colorRegex(match string, style Style) StyledString {
|
||||||
re := regexp.MustCompile("(" + match + ")")
|
return ss.regexReplaceFunc(match, func(subString string) string {
|
||||||
locations := re.FindAllStringIndex(t.message, -1)
|
return style.stylize(removeFormatting(subString)).stringFollowedByStyle(ss.style)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replacer function takes the current match as input and should return how the match should be preseneted instead
|
||||||
|
func (ss StyledString) regexReplaceFunc(match string, replacer func(string) string) StyledString {
|
||||||
|
re := regexp.MustCompile(match)
|
||||||
|
locations := re.FindAllStringIndex(ss.message, -1)
|
||||||
var newMessage string
|
var newMessage string
|
||||||
var prevIndex int
|
var prevIndex int
|
||||||
for _, loc := range locations {
|
for _, loc := range locations {
|
||||||
cleanSubstring := style.stylize(removeFormatting(string(t.message[loc[0]:loc[1]])))
|
newSubstring := replacer(ss.message[loc[0]:loc[1]])
|
||||||
newMessage += t.message[prevIndex:loc[0]]
|
newMessage += ss.message[prevIndex:loc[0]]
|
||||||
newMessage += cleanSubstring.stringFollowedByStyle(t.style)
|
newMessage += newSubstring
|
||||||
prevIndex = loc[1]
|
prevIndex = loc[1]
|
||||||
}
|
}
|
||||||
// Append any string after the final match
|
// Append any string after the final match
|
||||||
newMessage += t.message[prevIndex:len(t.message)]
|
newMessage += ss.message[prevIndex:len(ss.message)]
|
||||||
t.message = newMessage
|
ss.message = newMessage
|
||||||
return t
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
// Appends the other stylize at the end, but retains same style
|
// Appends the other stylize at the end, but retains same style
|
||||||
func (t StyledString) append(other StyledString) StyledString {
|
func (ss StyledString) append(other StyledString) StyledString {
|
||||||
t.message = t.message + other.stringFollowedByStyle(t.style)
|
ss.message = ss.message + other.stringFollowedByStyle(ss.style)
|
||||||
return t
|
return ss
|
||||||
}
|
}
|
||||||
func (t StyledString) appendString(other string) StyledString {
|
func (ss StyledString) appendString(other string) StyledString {
|
||||||
t.message += other
|
ss.message += other
|
||||||
return t
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin Formatting
|
// Begin Formatting
|
||||||
|
|||||||
@ -11,9 +11,9 @@ cmd_prefix = "/"
|
|||||||
|
|
||||||
[formatting]
|
[formatting]
|
||||||
# BASH-like PS1 variable equivalent
|
# BASH-like PS1 variable equivalent
|
||||||
output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
output_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
output_stream_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
output_mention_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
pm_format = "PM from $USER@$DEVICE: $MSG"
|
pm_format = "PM from $USER@$DEVICE: $MSG"
|
||||||
|
|
||||||
# 02 = Day, Jan = Month, 06 = Year
|
# 02 = Day, Jan = Month, 06 = Year
|
||||||
@ -22,51 +22,66 @@ date_format = "02Jan06"
|
|||||||
# 15 = hours, 04 = minutes, 05 = seconds
|
# 15 = hours, 04 = minutes, 05 = seconds
|
||||||
time_format = "15:04"
|
time_format = "15:04"
|
||||||
|
|
||||||
|
icon_following_user = "[*]"
|
||||||
|
icon_indirect_following_user = "[?]"
|
||||||
|
|
||||||
[colors]
|
[colors]
|
||||||
[colors.channels]
|
[colors.channels]
|
||||||
[colors.channels.basic]
|
[colors.channels.basic]
|
||||||
foreground = "normal"
|
foreground = "normal"
|
||||||
[colors.channels.header]
|
[colors.channels.header]
|
||||||
foreground = "magenta"
|
foreground = "magenta"
|
||||||
bold = true
|
bold = true
|
||||||
[colors.channels.unread]
|
[colors.channels.unread]
|
||||||
foreground = "green"
|
foreground = "green"
|
||||||
italic = true
|
italic = true
|
||||||
|
|
||||||
[colors.message]
|
[colors.message]
|
||||||
[colors.message.body]
|
[colors.message.body]
|
||||||
foreground = "normal"
|
foreground = "normal"
|
||||||
[colors.message.header]
|
[colors.message.header]
|
||||||
foreground = "grey"
|
foreground = "grey"
|
||||||
|
bold = true
|
||||||
[colors.message.mention]
|
[colors.message.mention]
|
||||||
foreground = "green"
|
foreground = "green"
|
||||||
italic = true
|
italic = true
|
||||||
bold = true
|
bold = true
|
||||||
[colors.message.id]
|
[colors.message.id]
|
||||||
foreground = "yellow"
|
foreground = "yellow"
|
||||||
|
bold = true
|
||||||
[colors.message.time]
|
[colors.message.time]
|
||||||
foreground = "magenta"
|
foreground = "magenta"
|
||||||
|
bold = true
|
||||||
[colors.message.sender_default]
|
[colors.message.sender_default]
|
||||||
foreground = "cyan"
|
foreground = "cyan"
|
||||||
bold = true
|
bold = true
|
||||||
[colors.message.sender_device]
|
[colors.message.sender_device]
|
||||||
foreground = "cyan"
|
foreground = "cyan"
|
||||||
|
bold = true
|
||||||
|
[colors.message.sender_tags]
|
||||||
|
foreground = "yellow"
|
||||||
[colors.message.attachment]
|
[colors.message.attachment]
|
||||||
foreground = "red"
|
foreground = "red"
|
||||||
[colors.message.link_url]
|
[colors.message.link_url]
|
||||||
foreground = "yellow"
|
foreground = "yellow"
|
||||||
[colors.message.link_keybase]
|
[colors.message.link_keybase]
|
||||||
foreground = "yellow"
|
foreground = "cyan"
|
||||||
[colors.message.reaction]
|
[colors.message.reaction]
|
||||||
foreground = "magenta"
|
foreground = "magenta"
|
||||||
bold = true
|
bold = true
|
||||||
|
[colors.message.quote]
|
||||||
|
foreground = "green"
|
||||||
[colors.message.code]
|
[colors.message.code]
|
||||||
foreground = "cyan"
|
foreground = "cyan"
|
||||||
background = "grey"
|
background = "grey"
|
||||||
|
|
||||||
[colors.feed]
|
[colors.feed]
|
||||||
[colors.feed.basic]
|
[colors.feed.basic]
|
||||||
foreground = "grey"
|
foreground = "grey"
|
||||||
[colors.feed.error]
|
[colors.feed.error]
|
||||||
foreground = "red"
|
foreground = "red"
|
||||||
|
[colors.feed.success]
|
||||||
|
foreground = "green"
|
||||||
|
[colors.feed.file]
|
||||||
|
foreground = "yellow"
|
||||||
`
|
`
|
||||||
|
|||||||
20
go.mod
20
go.mod
@ -1,12 +1,16 @@
|
|||||||
module github.com/Rudi9719/kbtui
|
module github.com/rudi9719/kbtui
|
||||||
|
|
||||||
go 1.12
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/awesome-gocui/gocui v0.6.0
|
github.com/awesome-gocui/gocui v1.0.1-0.20210720125732-36a608772b4d
|
||||||
github.com/magefile/mage v1.9.0
|
github.com/gdamore/tcell/v2 v2.4.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.5 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/pelletier/go-toml v1.6.0
|
github.com/magefile/mage v1.11.0
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c
|
github.com/pelletier/go-toml v1.9.1
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||||
|
golang.org/x/text v0.3.6 // indirect
|
||||||
|
samhofi.us/x/keybase v1.0.0
|
||||||
)
|
)
|
||||||
|
|||||||
69
go.sum
69
go.sum
@ -1,23 +1,46 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/awesome-gocui/gocui v1.0.0 h1:1bf0DAr2JqWNxGFS8Kex4fM/khICjEnCi+a1+NfWy+w=
|
||||||
github.com/awesome-gocui/gocui v0.6.0 h1:hhDJiQC12tEsJNJ+iZBBVaSSLFYo9llFuYpQlL5JZVI=
|
github.com/awesome-gocui/gocui v1.0.0/go.mod h1:UvP3dP6+UsTGl9IuqP36wzz6Lemo90wn5p3tJvZ2OqY=
|
||||||
github.com/awesome-gocui/gocui v0.6.0/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA=
|
github.com/awesome-gocui/gocui v1.0.1-0.20210720125732-36a608772b4d h1:5TGmGxIeTNcsvqqL1kbcPNP7RMG0wZtvPgmNmqB/UeY=
|
||||||
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
|
github.com/awesome-gocui/gocui v1.0.1-0.20210720125732-36a608772b4d/go.mod h1:UvP3dP6+UsTGl9IuqP36wzz6Lemo90wn5p3tJvZ2OqY=
|
||||||
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
github.com/gdamore/tcell/v2 v2.0.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/gdamore/tcell/v2 v2.3.5 h1:fSiuoOf40N1w1otj2kQf4IlJ7rI/dcF3zVZL+GRmwuQ=
|
||||||
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
|
github.com/gdamore/tcell/v2 v2.3.5/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||||
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-runewidth v0.0.5 h1:jrGtp51JOKTWgvLFzfG6OtZOJcK2sEnzc/U+zw7TtbA=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ=
|
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
|
||||||
github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c h1:qIKOKqYnRCx+O2IOz3a/lplrD0p1e3n/VoGOdrTGrVo=
|
github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc=
|
||||||
samhofi.us/x/keybase v0.0.0-20191023034410-b00e56e8dd3c/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE=
|
github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b h1:qh4f65QIVFjq9eBURLEYWqaEXmOyqdUyiBSgaXWccWk=
|
||||||
|
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||||
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||||
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||||
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
samhofi.us/x/keybase v1.0.0 h1:ht//EtYMS/hQeZCznA1ibQ515JCKaEkvTD/tarw/9k8=
|
||||||
|
samhofi.us/x/keybase v1.0.0/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE=
|
||||||
|
|||||||
41
kbtui.toml
41
kbtui.toml
@ -8,9 +8,9 @@ cmd_prefix = "/"
|
|||||||
|
|
||||||
[formatting]
|
[formatting]
|
||||||
# BASH-like PS1 variable equivalent
|
# BASH-like PS1 variable equivalent
|
||||||
output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
output_format = "$REPL┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
output_stream_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
output_mention_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
|
||||||
pm_format = "PM from $USER@$DEVICE: $MSG"
|
pm_format = "PM from $USER@$DEVICE: $MSG"
|
||||||
|
|
||||||
# 02 = Day, Jan = Month, 06 = Year
|
# 02 = Day, Jan = Month, 06 = Year
|
||||||
@ -19,50 +19,65 @@ date_format = "02Jan06"
|
|||||||
# 15 = hours, 04 = minutes, 05 = seconds
|
# 15 = hours, 04 = minutes, 05 = seconds
|
||||||
time_format = "15:04"
|
time_format = "15:04"
|
||||||
|
|
||||||
|
icon_following_user = "[*]"
|
||||||
|
icon_indirect_following_user = "[?]"
|
||||||
|
|
||||||
[colors]
|
[colors]
|
||||||
[colors.channels]
|
[colors.channels]
|
||||||
[colors.channels.basic]
|
[colors.channels.basic]
|
||||||
foreground = "normal"
|
foreground = "normal"
|
||||||
[colors.channels.header]
|
[colors.channels.header]
|
||||||
foreground = "magenta"
|
foreground = "magenta"
|
||||||
bold = true
|
bold = true
|
||||||
[colors.channels.unread]
|
[colors.channels.unread]
|
||||||
foreground = "green"
|
foreground = "green"
|
||||||
italic = true
|
italic = true
|
||||||
|
|
||||||
[colors.message]
|
[colors.message]
|
||||||
[colors.message.body]
|
[colors.message.body]
|
||||||
foreground = "normal"
|
foreground = "normal"
|
||||||
[colors.message.header]
|
[colors.message.header]
|
||||||
foreground = "grey"
|
foreground = "grey"
|
||||||
|
bold = true
|
||||||
[colors.message.mention]
|
[colors.message.mention]
|
||||||
foreground = "green"
|
foreground = "green"
|
||||||
italic = true
|
italic = true
|
||||||
bold = true
|
bold = true
|
||||||
[colors.message.id]
|
[colors.message.id]
|
||||||
foreground = "yellow"
|
foreground = "yellow"
|
||||||
|
bold = true
|
||||||
[colors.message.time]
|
[colors.message.time]
|
||||||
foreground = "magenta"
|
foreground = "magenta"
|
||||||
|
bold = true
|
||||||
[colors.message.sender_default]
|
[colors.message.sender_default]
|
||||||
foreground = "cyan"
|
foreground = "cyan"
|
||||||
bold = true
|
bold = true
|
||||||
[colors.message.sender_device]
|
[colors.message.sender_device]
|
||||||
foreground = "cyan"
|
foreground = "cyan"
|
||||||
|
bold = true
|
||||||
|
[colors.message.sender_tags]
|
||||||
|
foreground = "yellow"
|
||||||
[colors.message.attachment]
|
[colors.message.attachment]
|
||||||
foreground = "red"
|
foreground = "red"
|
||||||
[colors.message.link_url]
|
[colors.message.link_url]
|
||||||
foreground = "yellow"
|
foreground = "yellow"
|
||||||
[colors.message.link_keybase]
|
[colors.message.link_keybase]
|
||||||
foreground = "yellow"
|
foreground = "cyan"
|
||||||
[colors.message.reaction]
|
[colors.message.reaction]
|
||||||
foreground = "magenta"
|
foreground = "magenta"
|
||||||
bold = true
|
bold = true
|
||||||
|
[colors.message.quote]
|
||||||
|
foreground = "green"
|
||||||
[colors.message.code]
|
[colors.message.code]
|
||||||
foreground = "cyan"
|
foreground = "cyan"
|
||||||
background = "grey"
|
background = "grey"
|
||||||
|
|
||||||
[colors.feed]
|
[colors.feed]
|
||||||
[colors.feed.basic]
|
[colors.feed.basic]
|
||||||
foreground = "grey"
|
foreground = "grey"
|
||||||
[colors.feed.error]
|
[colors.feed.error]
|
||||||
foreground = "red"
|
foreground = "red"
|
||||||
|
[colors.feed.success]
|
||||||
|
foreground = "green"
|
||||||
|
[colors.feed.file]
|
||||||
|
foreground = "yellow"
|
||||||
|
|||||||
148
main.go
148
main.go
@ -4,11 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/awesome-gocui/gocui"
|
"github.com/awesome-gocui/gocui"
|
||||||
"samhofi.us/x/keybase"
|
"samhofi.us/x/keybase"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -52,7 +54,7 @@ func main() {
|
|||||||
if err := initKeybindings(); err != nil {
|
if err := initKeybindings(); err != nil {
|
||||||
fmt.Printf("%+v", err)
|
fmt.Printf("%+v", err)
|
||||||
}
|
}
|
||||||
if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
fmt.Printf("%+v", err)
|
fmt.Printf("%+v", err)
|
||||||
}
|
}
|
||||||
go generateChannelTabCompletionSlice()
|
go generateChannelTabCompletionSlice()
|
||||||
@ -62,7 +64,7 @@ func main() {
|
|||||||
func layout(g *gocui.Gui) error {
|
func layout(g *gocui.Gui) error {
|
||||||
maxX, maxY := g.Size()
|
maxX, maxY := g.Size()
|
||||||
if editView, err := g.SetView("Edit", maxX/2-maxX/3+1, maxY/2, maxX-2, maxY/2+10, 0); err != nil {
|
if editView, err := g.SetView("Edit", maxX/2-maxX/3+1, maxY/2, maxX-2, maxY/2+10, 0); err != nil {
|
||||||
if !gocui.IsUnknownView(err) {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
editView.Editable = true
|
editView.Editable = true
|
||||||
@ -70,7 +72,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
fmt.Fprintln(editView, "Edit window. Should disappear")
|
fmt.Fprintln(editView, "Edit window. Should disappear")
|
||||||
}
|
}
|
||||||
if feedView, err := g.SetView("Feed", maxX/2-maxX/3, 0, maxX-1, maxY/5, 0); err != nil {
|
if feedView, err := g.SetView("Feed", maxX/2-maxX/3, 0, maxX-1, maxY/5, 0); err != nil {
|
||||||
if !gocui.IsUnknownView(err) {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
feedView.Autoscroll = true
|
feedView.Autoscroll = true
|
||||||
@ -79,7 +81,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
printInfo("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 err2 != gocui.ErrUnknownView {
|
||||||
return err2
|
return err2
|
||||||
}
|
}
|
||||||
chatView.Autoscroll = true
|
chatView.Autoscroll = true
|
||||||
@ -90,7 +92,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
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 {
|
||||||
if !gocui.IsUnknownView(err3) {
|
if err3 != gocui.ErrUnknownView {
|
||||||
return err3
|
return err3
|
||||||
}
|
}
|
||||||
if _, err := g.SetCurrentView("Input"); err != nil {
|
if _, err := g.SetCurrentView("Input"); err != nil {
|
||||||
@ -102,7 +104,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
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 {
|
||||||
if !gocui.IsUnknownView(err4) {
|
if err4 != gocui.ErrUnknownView {
|
||||||
return err4
|
return err4
|
||||||
}
|
}
|
||||||
listView.Title = "Channels"
|
listView.Title = "Channels"
|
||||||
@ -269,7 +271,7 @@ func popupView(viewName string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updatingView.MoveCursor(0, 0, true)
|
updatingView.MoveCursor(0, 0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
@ -288,7 +290,7 @@ func moveCursorToEnd(viewName string) {
|
|||||||
y := stringLen / maxX
|
y := stringLen / maxX
|
||||||
inputView.SetCursor(0, 0)
|
inputView.SetCursor(0, 0)
|
||||||
inputView.SetOrigin(0, 0)
|
inputView.SetOrigin(0, 0)
|
||||||
inputView.MoveCursor(x, y, true)
|
inputView.MoveCursor(x, y)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -333,6 +335,9 @@ func printErrorF(message string, parts ...StyledString) {
|
|||||||
func printInfo(message string) {
|
func printInfo(message string) {
|
||||||
printInfoF(message)
|
printInfoF(message)
|
||||||
}
|
}
|
||||||
|
func printInfoStyledString(message StyledString) {
|
||||||
|
printInfoF("$TEXT", message)
|
||||||
|
}
|
||||||
|
|
||||||
// this removes formatting
|
// this removes formatting
|
||||||
func printInfoF(message string, parts ...StyledString) {
|
func printInfoF(message string, parts ...StyledString) {
|
||||||
@ -343,12 +348,12 @@ func printToView(viewName string, message string) {
|
|||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Basics.UnicodeEmojis {
|
||||||
|
message = emojiUnicodeConvert(message)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(updatingView, "%s\n", message)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -372,7 +377,7 @@ func populateChat() {
|
|||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
maxX, _ := g.Size()
|
maxX, _ := g.Size()
|
||||||
api, err := chat.Read(maxX / 2)
|
api, err := chat.Read(maxX / 2)
|
||||||
if err != nil {
|
if err != nil || api.Result == nil {
|
||||||
for _, testChan := range channels {
|
for _, testChan := range channels {
|
||||||
if channel.Name == testChan.Name {
|
if channel.Name == testChan.Name {
|
||||||
channel = testChan
|
channel = testChan
|
||||||
@ -401,7 +406,7 @@ func populateChat() {
|
|||||||
}
|
}
|
||||||
var apiCast keybase.ChatAPI
|
var apiCast keybase.ChatAPI
|
||||||
apiCast.Msg = &message.Msg
|
apiCast.Msg = &message.Msg
|
||||||
newMessage := formatOutput(apiCast)
|
newMessage := formatOutput(apiCast).string()
|
||||||
printMe = append(printMe, newMessage)
|
printMe = append(printMe, newMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,12 +425,16 @@ func populateList() {
|
|||||||
log.Printf("%+v", err)
|
log.Printf("%+v", err)
|
||||||
} else {
|
} else {
|
||||||
clearView("List")
|
clearView("List")
|
||||||
|
conversationSlice := testVar.Result.Conversations
|
||||||
|
sort.SliceStable(conversationSlice, func(i, j int) bool {
|
||||||
|
return conversationSlice[i].ActiveAt > conversationSlice[j].ActiveAt
|
||||||
|
})
|
||||||
var textBase = config.Colors.Channels.Basic.stylize("")
|
var textBase = config.Colors.Channels.Basic.stylize("")
|
||||||
var recentPMs = textBase.append(config.Colors.Channels.Header.stylize("---[PMs]---\n"))
|
var recentPMs = textBase.append(config.Colors.Channels.Header.stylize("---[PMs]---\n"))
|
||||||
var recentPMsCount = 0
|
var recentPMsCount = 0
|
||||||
var recentChannels = textBase.append(config.Colors.Channels.Header.stylize("---[Teams]---\n"))
|
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 conversationSlice {
|
||||||
channels = append(channels, s.Channel)
|
channels = append(channels, s.Channel)
|
||||||
if s.Channel.MembersType == keybase.TEAM {
|
if s.Channel.MembersType == keybase.TEAM {
|
||||||
recentChannelsCount++
|
recentChannelsCount++
|
||||||
@ -459,21 +468,41 @@ func populateList() {
|
|||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
func formatMessageBody(body string) StyledString {
|
func formatMessageBody(body string) StyledString {
|
||||||
output := config.Colors.Message.Body.stylize(body)
|
body = strings.Replace(body, "```", "\n<code>\n", -1)
|
||||||
|
message := config.Colors.Message.Body.stylize(body)
|
||||||
|
|
||||||
output = colorReplaceMentionMe(output)
|
message = message.colorRegex(`@[\w_]*([\.#][\w_]+)*`, config.Colors.Message.LinkKeybase)
|
||||||
output = output.colorRegex(`_[^_]*_`, config.Colors.Message.Body.withItalic())
|
message = colorReplaceMentionMe(message)
|
||||||
output = output.colorRegex(`~[^~]*~`, config.Colors.Message.Body.withStrikethrough())
|
|
||||||
output = output.colorRegex(`@[\w_]*(\.[\w_]+)*`, config.Colors.Message.LinkKeybase)
|
// TODO when gocui actually fixes there shit with formatting, then un comment these lines
|
||||||
|
// message = message.colorRegex(`_[^_]*_`, config.Colors.Message.Body.withItalic())
|
||||||
|
// message = message.colorRegex(`~[^~]*~`, config.Colors.Message.Body.withStrikethrough())
|
||||||
|
message = message.colorRegex(`@[\w_]*([\.#][\w_]+)*`, config.Colors.Message.LinkKeybase)
|
||||||
// TODO change how bold, italic etc works, so it uses boldOn boldOff ([1m and [22m)
|
// TODO change how bold, italic etc works, so it uses boldOn boldOff ([1m and [22m)
|
||||||
output = output.colorRegex(`\*[^\*]*\*`, config.Colors.Message.Body.withBold())
|
message = message.colorRegex(`\*[^\*]*\*`, config.Colors.Message.Body.withBold())
|
||||||
output = output.replaceString("```", "\n<code>\n")
|
message = message.colorRegex("^>.*$", config.Colors.Message.Quote)
|
||||||
// TODO make background color cover whole line
|
message = message.regexReplaceFunc("\n<code>(.*\n)*<code>\n", func(match string) string {
|
||||||
output = output.colorRegex("<code>(.*\n)*<code>", config.Colors.Message.Code)
|
maxWidth, _ := g.Size()
|
||||||
output = output.colorRegex("`[^`]*`", config.Colors.Message.Code)
|
output := ""
|
||||||
|
match = strings.Replace(strings.Replace(match, "```", "\n<code>\n", -1), "\t", " ", -1)
|
||||||
|
match = removeFormatting(match)
|
||||||
|
lines := strings.Split(match, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
maxLineLength := maxWidth/2 + maxWidth/3 - 2
|
||||||
|
spaces := maxLineLength - utf8.RuneCountInString(line)
|
||||||
|
for i := 1; spaces < 0; i++ {
|
||||||
|
spaces = i*maxLineLength - utf8.RuneCountInString(line)
|
||||||
|
}
|
||||||
|
output += line + strings.Repeat(" ", spaces) + "\n"
|
||||||
|
}
|
||||||
|
// TODO stylize should remove formatting - in general everything should
|
||||||
|
|
||||||
|
return config.Colors.Message.Code.stylize(output).stringFollowedByStyle(message.style)
|
||||||
|
})
|
||||||
|
message = message.colorRegex("`[^`]*`", config.Colors.Message.Code)
|
||||||
// mention URL
|
// 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)
|
message = message.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
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO use this more
|
// TODO use this more
|
||||||
@ -497,39 +526,56 @@ func cleanChannelName(c string) string {
|
|||||||
return strings.Replace(newChannelName, fmt.Sprintf(",%s", k.Username), "", 1)
|
return strings.Replace(newChannelName, fmt.Sprintf(",%s", k.Username), "", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatMessage(api keybase.ChatAPI, formatString string) string {
|
func formatMessage(api keybase.ChatAPI, formatString string) StyledString {
|
||||||
|
msg := api.Msg
|
||||||
ret := config.Colors.Message.Header.stylize("")
|
ret := config.Colors.Message.Header.stylize("")
|
||||||
msgType := api.Msg.Content.Type
|
msgType := msg.Content.Type
|
||||||
switch msgType {
|
switch msgType {
|
||||||
case "text", "attachment":
|
case "text", "attachment":
|
||||||
ret = config.Colors.Message.Header.stylize(formatString)
|
ret = config.Colors.Message.Header.stylize(formatString)
|
||||||
tm := time.Unix(int64(api.Msg.SentAt), 0)
|
tm := time.Unix(int64(msg.SentAt), 0)
|
||||||
var msg = formatMessageBody(api.Msg.Content.Text.Body)
|
var body = formatMessageBody(msg.Content.Text.Body)
|
||||||
if msgType == "attachment" {
|
if msgType == "attachment" {
|
||||||
msg = config.Colors.Message.Body.stylize("$TITLE\n$FILE")
|
body = config.Colors.Message.Body.stylize("$TITLE\n$FILE")
|
||||||
attachment := api.Msg.Content.Attachment
|
attachment := msg.Content.Attachment
|
||||||
msg = msg.replaceString("$TITLE", attachment.Object.Title)
|
body = body.replaceString("$TITLE", attachment.Object.Title)
|
||||||
msg = msg.replace("$FILE", config.Colors.Message.Attachment.stylize(fmt.Sprintf("[Attachment: %s]", attachment.Object.Filename)))
|
body = body.replace("$FILE", config.Colors.Message.Attachment.stylize(fmt.Sprintf("[Attachment: %s]", attachment.Object.Filename)))
|
||||||
|
}
|
||||||
|
reply := ""
|
||||||
|
if msg.Content.Text.ReplyTo != 0 {
|
||||||
|
chat := k.NewChat(channel)
|
||||||
|
replyMsg, replErr := chat.ReadMessage(msg.Content.Text.ReplyTo)
|
||||||
|
if replErr == nil {
|
||||||
|
replyUser := replyMsg.Result.Messages[0].Msg.Sender.Username
|
||||||
|
replyBody := ""
|
||||||
|
if replyMsg.Result.Messages[0].Msg.Content.Type == "text" {
|
||||||
|
replyBody = replyMsg.Result.Messages[0].Msg.Content.Text.Body
|
||||||
|
}
|
||||||
|
reply = fmt.Sprintf("\nReplyTo> %s: %s\n", replyUser, replyBody)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user := colorUsername(api.Msg.Sender.Username)
|
user := colorUsername(msg.Sender.Username)
|
||||||
device := config.Colors.Message.SenderDevice.stylize(api.Msg.Sender.DeviceName)
|
device := config.Colors.Message.SenderDevice.stylize(msg.Sender.DeviceName)
|
||||||
msgID := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", api.Msg.ID))
|
msgID := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", msg.ID))
|
||||||
date := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.DateFormat))
|
date := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.DateFormat))
|
||||||
msgTime := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.TimeFormat))
|
msgTime := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.TimeFormat))
|
||||||
|
c0ck := config.Colors.Message.Quote.stylize(reply)
|
||||||
channelName := config.Colors.Message.ID.stylize(fmt.Sprintf("@%s#%s", api.Msg.Channel.Name, api.Msg.Channel.TopicName))
|
channelName := config.Colors.Message.ID.stylize(fmt.Sprintf("@%s#%s", msg.Channel.Name, msg.Channel.TopicName))
|
||||||
ret = ret.replace("$MSG", msg)
|
ret = ret.replace("$REPL", c0ck)
|
||||||
|
ret = ret.replace("$MSG", body)
|
||||||
ret = ret.replace("$USER", user)
|
ret = ret.replace("$USER", user)
|
||||||
ret = ret.replace("$DEVICE", device)
|
ret = ret.replace("$DEVICE", device)
|
||||||
ret = ret.replace("$ID", msgID)
|
ret = ret.replace("$ID", msgID)
|
||||||
ret = ret.replace("$TIME", msgTime)
|
ret = ret.replace("$TIME", msgTime)
|
||||||
ret = ret.replace("$DATE", date)
|
ret = ret.replace("$DATE", date)
|
||||||
ret = ret.replace("$TEAM", channelName)
|
ret = ret.replace("$TEAM", channelName)
|
||||||
|
ret = ret.replace("$TAGS", getUserFlags(api.Msg.Sender.Username))
|
||||||
}
|
}
|
||||||
return ret.string()
|
return ret
|
||||||
}
|
}
|
||||||
func formatOutput(api keybase.ChatAPI) string {
|
|
||||||
|
func formatOutput(api keybase.ChatAPI) StyledString {
|
||||||
format := config.Formatting.OutputFormat
|
format := config.Formatting.OutputFormat
|
||||||
if stream {
|
if stream {
|
||||||
format = config.Formatting.OutputStreamFormat
|
format = config.Formatting.OutputStreamFormat
|
||||||
@ -541,6 +587,10 @@ func formatOutput(api keybase.ChatAPI) string {
|
|||||||
|
|
||||||
// Input handling
|
// Input handling
|
||||||
func handleMessage(api keybase.ChatAPI) {
|
func handleMessage(api keybase.ChatAPI) {
|
||||||
|
if api.ErrorListen != nil {
|
||||||
|
printError(fmt.Sprintf("%+v", api.ErrorListen))
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, ok := typeCommands[api.Msg.Content.Type]; ok {
|
if _, ok := typeCommands[api.Msg.Content.Type]; ok {
|
||||||
if api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name {
|
if api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name {
|
||||||
if channel.MembersType == keybase.TEAM && channel.TopicName != api.Msg.Channel.TopicName {
|
if channel.MembersType == keybase.TEAM && channel.TopicName != api.Msg.Channel.TopicName {
|
||||||
@ -560,7 +610,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 {
|
||||||
printInfo(formatMessage(api, config.Formatting.OutputMentionFormat))
|
printInfoStyledString(formatMessage(api, config.Formatting.OutputMentionFormat))
|
||||||
fmt.Print("\a")
|
fmt.Print("\a")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,7 +619,7 @@ func handleMessage(api keybase.ChatAPI) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if msgSender != channel.Name {
|
if msgSender != channel.Name {
|
||||||
printInfo(formatMessage(api, config.Formatting.PMFormat))
|
printInfoStyledString(formatMessage(api, config.Formatting.PMFormat))
|
||||||
fmt.Print("\a")
|
fmt.Print("\a")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,7 +627,7 @@ func handleMessage(api keybase.ChatAPI) {
|
|||||||
}
|
}
|
||||||
if api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name {
|
if api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name {
|
||||||
if channel.MembersType == keybase.USER || channel.MembersType == keybase.TEAM && channel.TopicName == api.Msg.Channel.TopicName {
|
if channel.MembersType == keybase.USER || channel.MembersType == keybase.TEAM && channel.TopicName == api.Msg.Channel.TopicName {
|
||||||
printToView("Chat", formatOutput(api))
|
printToView("Chat", formatOutput(api).string())
|
||||||
chat := k.NewChat(channel)
|
chat := k.NewChat(channel)
|
||||||
lastMessage.ID = api.Msg.ID
|
lastMessage.ID = api.Msg.ID
|
||||||
chat.Read(api.Msg.ID)
|
chat.Read(api.Msg.ID)
|
||||||
@ -585,9 +635,9 @@ func handleMessage(api keybase.ChatAPI) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if api.Msg.Channel.MembersType == keybase.TEAM {
|
if api.Msg.Channel.MembersType == keybase.TEAM {
|
||||||
printToView("Chat", formatOutput(api))
|
printToView("Chat", formatOutput(api).string())
|
||||||
} else {
|
} else {
|
||||||
printToView("Chat", formatMessage(api, config.Formatting.PMFormat))
|
printToView("Chat", formatMessage(api, config.Formatting.PMFormat).string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
23
types.go
23
types.go
@ -36,12 +36,14 @@ type Basics struct {
|
|||||||
|
|
||||||
// Formatting holds the 'formatting' section of the config file
|
// Formatting holds the 'formatting' section of the config file
|
||||||
type Formatting struct {
|
type Formatting struct {
|
||||||
OutputFormat string `toml:"output_format"`
|
OutputFormat string `toml:"output_format"`
|
||||||
OutputStreamFormat string `toml:"output_stream_format"`
|
OutputStreamFormat string `toml:"output_stream_format"`
|
||||||
OutputMentionFormat string `toml:"output_mention_format"`
|
OutputMentionFormat string `toml:"output_mention_format"`
|
||||||
PMFormat string `toml:"pm_format"`
|
PMFormat string `toml:"pm_format"`
|
||||||
DateFormat string `toml:"date_format"`
|
DateFormat string `toml:"date_format"`
|
||||||
TimeFormat string `toml:"time_format"`
|
TimeFormat string `toml:"time_format"`
|
||||||
|
IconFollowingUser string `toml:"icon_following_user"`
|
||||||
|
IconIndirectFollowUser string `toml:"icon_indirect_following_user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colors holds the 'colors' section of the config file
|
// Colors holds the 'colors' section of the config file
|
||||||
@ -75,18 +77,23 @@ type Message struct {
|
|||||||
Header Style `toml:"header"`
|
Header Style `toml:"header"`
|
||||||
Mention Style `toml:"mention"`
|
Mention Style `toml:"mention"`
|
||||||
ID Style `toml:"id"`
|
ID Style `toml:"id"`
|
||||||
|
Tags Style `toml:"tags"`
|
||||||
Time Style `toml:"time"`
|
Time Style `toml:"time"`
|
||||||
SenderDefault Style `toml:"sender_default"`
|
SenderDefault Style `toml:"sender_default"`
|
||||||
SenderDevice Style `toml:"sender_device"`
|
SenderDevice Style `toml:"sender_device"`
|
||||||
|
SenderTags Style `toml:"sender_tags"`
|
||||||
Attachment Style `toml:"attachment"`
|
Attachment Style `toml:"attachment"`
|
||||||
LinkURL Style `toml:"link_url"`
|
LinkURL Style `toml:"link_url"`
|
||||||
LinkKeybase Style `toml:"link_keybase"`
|
LinkKeybase Style `toml:"link_keybase"`
|
||||||
Reaction Style `toml:"reaction"`
|
Reaction Style `toml:"reaction"`
|
||||||
|
Quote Style `toml:"quote"`
|
||||||
Code Style `toml:"code"`
|
Code Style `toml:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed holds the style information for various elements of the feed window
|
// Feed holds the style information for various elements of the feed window
|
||||||
type Feed struct {
|
type Feed struct {
|
||||||
Basic Style `toml:"basic"`
|
Basic Style `toml:"basic"`
|
||||||
Error Style `toml:"error"`
|
Error Style `toml:"error"`
|
||||||
|
File Style `toml:"file"`
|
||||||
|
Success Style `toml:"success"`
|
||||||
}
|
}
|
||||||
|
|||||||
58
userTags.go
Normal file
58
userTags.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var followedInSteps = make(map[string]int)
|
||||||
|
var trustTreeParent = make(map[string]string)
|
||||||
|
|
||||||
|
func clearFlagCache() {
|
||||||
|
followedInSteps = make(map[string]int)
|
||||||
|
trustTreeParent = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxDepth = 4
|
||||||
|
|
||||||
|
func generateFollowersList() {
|
||||||
|
// Does a BFS of followedInSteps
|
||||||
|
queue := []string{k.Username}
|
||||||
|
printInfo("Generating Tree of Trust...")
|
||||||
|
lastDepth := 1
|
||||||
|
for len(queue) > 0 {
|
||||||
|
head := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
depth := followedInSteps[head] + 1
|
||||||
|
if depth > maxDepth {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if depth > lastDepth {
|
||||||
|
printInfo(fmt.Sprintf("Trust generated at Level #%d", depth-1))
|
||||||
|
lastDepth = depth
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, _ := k.Exec("list-following", head)
|
||||||
|
bigString := string(bytes)
|
||||||
|
following := strings.Split(bigString, "\n")
|
||||||
|
for _, user := range following {
|
||||||
|
if followedInSteps[user] == 0 && user != k.Username {
|
||||||
|
followedInSteps[user] = depth
|
||||||
|
trustTreeParent[user] = head
|
||||||
|
queue = append(queue, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printInfo(fmt.Sprintf("Trust-level estabilished for %d users", len(followedInSteps)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserFlags(username string) StyledString {
|
||||||
|
tags := ""
|
||||||
|
followDepth := followedInSteps[username]
|
||||||
|
if followDepth == 1 {
|
||||||
|
tags += fmt.Sprintf(" %s", config.Formatting.IconFollowingUser)
|
||||||
|
} else if followDepth > 1 {
|
||||||
|
tags += fmt.Sprintf(" %s%d", config.Formatting.IconIndirectFollowUser, followDepth-1)
|
||||||
|
}
|
||||||
|
return config.Colors.Message.SenderTags.stylize(tags)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user