1
0
mirror of https://github.com/Rudi9719/kbtui.git synced 2026-03-24 12:37:23 +00:00

29 Commits

Author SHA1 Message Date
5e8a018d1a Testing PR 51 2019-12-26 13:02:51 -05:00
3056ad9a47 Merge branch 'dev' of https://github.com/ellipticcurv3/kbtui into ellipticcurv3/kbtui 2019-12-26 12:41:14 -05:00
ae9f71be96 Remove comment for debugging print 2019-12-26 12:40:25 -05:00
88a6f709f9 Merge branch 'dev' of https://github.com/rudi9719/kbtui into dev 2019-12-20 12:02:36 -05:00
f222464849 Multi platform (#54)
* Test go.yaml

* Update go.yml

* Update go.yml

* Update go.yml
2019-12-20 12:02:04 -05:00
f55270f423 Rudi9719 workflow (#53)
* Update go.yml

* Update go.yml
2019-12-20 10:59:21 -05:00
67fc7c6e5d Check if api.Result is nil before reading it in populateChat() 2019-12-20 09:15:55 -05:00
fc48ad5c4a Add replies to messages 2019-12-20 08:44:51 -05:00
66e2552a7e Don't error out on api errors 2019-12-19 12:19:34 -05:00
9714e39751 Merge pull request #52 from Rudi9719/go-workflow
Go workflow
2019-12-18 14:01:18 -05:00
8abde78bf7 Update go.yml 2019-12-18 13:43:03 -05:00
ff120c0366 Create go.yml 2019-12-18 13:39:00 -05:00
0a4c2614a8 Fix multiline code to have newlines for mkbot gameroom etc 2019-12-18 13:35:14 -05:00
12d41c018e Merge pull request #50 from erAck/fix-home-expansion
"~/" does not work as hoped for
2019-12-16 07:48:00 -05:00
01bb599f56 Amend: put .string() back
I have no idea why it disappeared
2019-12-07 02:39:18 +01:00
3821891898 Made the dev flag actually usable
With this commit it is possible to set the dev flag to true, which will allow you to send and receive chat messages in dev channels only. You can use this to test kbtui without being disruptive.
2019-12-07 02:23:26 +01:00
9a71d50ab7 Remove unused import 2019-12-06 11:29:47 -05:00
db08278780 Remove auto-RAMRAPE and instead have it triggered on command 2019-12-06 11:24:28 -05:00
84e5beada4 "~/" does not work as hoped for
i.e. it is expanded only by a shell, not a system call, so
~/.config/kbtui.toml was never used.
2019-12-03 21:22:16 +01:00
cd79a10528 Fix ReplaceAll so that standard repo go can compile 2019-12-02 18:02:29 -05:00
a5f017de45 Merge pull request #48 from C0DK/show-follow
Show follow
2019-12-01 15:14:37 -05:00
5c5ebc1d45 Merge branch 'dev' into show-follow 2019-12-01 15:14:30 -05:00
3d353b33e8 Merge pull request #49 from C0DK/fix-formatting-shit
minor formatting fixes
2019-12-01 15:12:26 -05:00
023f22a1ea More User Info
Added:
- `/inspect` (`/id`)
- `/follow`
- `/unfollow`
2019-12-01 20:42:59 +01:00
bc7ef238de minor formatting fixes 2019-11-28 20:24:09 +01:00
e5285d9d36 Merge pull request #47 from Rudi9719/dev
Dev
2019-11-19 07:42:34 -05:00
e91506d319 Merge branch 'dev' of https://github.com/rudi9719/kbtui into dev 2019-11-13 19:11:28 +00:00
dc67abb1a3 Merge pull request #45 from C0DK/format-fixes
Revised some styling elements
2019-11-13 11:57:43 -05:00
1d95fea9f3 Revised some styling elements
- Colored block codes in a whole block
- Colored quotes
- Fixed a few bugs
2019-11-13 17:47:45 +01:00
16 changed files with 583 additions and 123 deletions

40
.github/workflows/go.yml vendored Normal file
View 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

View File

@ -31,7 +31,7 @@ func cmdConfig(cmd []string) {
printError(err.Error())
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
}
case len(cmd) > 2:
@ -41,7 +41,7 @@ func cmdConfig(cmd []string) {
printError(err.Error())
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
}
}
@ -50,7 +50,7 @@ func cmdConfig(cmd []string) {
func readConfig(filepath ...string) (*Config, error) {
var result = new(Config)
var configFile string
var configFile, path string
var env bool
// 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:
configFile, env = os.LookupEnv("KBTUI_CFG")
if !env {
configFile = "~/.config/kbtui.toml"
if _, err := os.Stat(configFile); os.IsNotExist(err) {
path, env = os.LookupEnv("HOME")
if env {
configFile = path + "/.config/kbtui.toml"
if _, err := os.Stat(configFile); os.IsNotExist(err) {
configFile = "kbtui.toml"
}
} else {
configFile = "kbtui.toml"
}
}

25
cmdDev.go Normal file
View File

@ -0,0 +1,25 @@
// +build !rm_basic_commands allcommands devcmd
package main
import (
"fmt"
)
func init() {
command := Command{
Cmd: []string{"dev"},
Description: "- Switch to dev channels",
Help: "",
Exec: cmdDev,
}
RegisterCommand(command)
}
func cmdDev(cmd []string) {
dev = !dev
printInfo(fmt.Sprintf("You have toggled the dev flag to %+v", dev))
clearView("Chat")
}

View File

@ -48,9 +48,10 @@ func cmdDownloadFile(cmd []string) {
_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", config.Basics.DownloadPath, fileName))
channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name)
fileNameStylizied := config.Colors.Feed.File.stylize(fileName)
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 {
printInfoF(fmt.Sprintf("Downloaded %s from $TEXT", fileName), channelName)
printInfoF("Downloaded $TEXT from $TEXT", fileNameStylizied, channelName)
}
}

34
cmdFollow.go Normal file
View 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
View 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
View 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
View 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))
}

View File

@ -40,10 +40,11 @@ func cmdUploadFile(cmd []string) {
}
chat := k.NewChat(channel)
_, 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 {
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 {
printInfo(fmt.Sprintf("Uploaded %s to %s", filePath, channelName))
printInfoF("Uploaded $TEXT to $TEXT", fileNameStylized, channelName)
}
}

View File

@ -81,7 +81,7 @@ func cmdPopulateWall(cmd []string) {
apiCast.Msg = &api.Result.Messages[i].Msg
result[apiCast.Msg.SentAt] = apiCast
newMessage := formatOutput(apiCast)
printMe = append(printMe, newMessage)
printMe = append(printMe, newMessage.string())
}
}
@ -96,7 +96,7 @@ func cmdPopulateWall(cmd []string) {
sort.Ints(keys)
time.Sleep(1 * time.Millisecond)
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)))
}

View File

@ -79,6 +79,7 @@ func (s Style) withBackground(color int) Style {
s.Background = colorFromInt(color)
return s
}
func (s Style) withBold() Style {
s.Bold = true
return s
@ -107,30 +108,31 @@ func (s Style) toANSI() string {
if config.Basics.Colorless {
return ""
}
output := "\x1b[0m\x1b[0"
styleSlice := []string{"0"}
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 {
output += fmt.Sprintf(";%d", 40+colorFromString(s.Background))
styleSlice = append(styleSlice, fmt.Sprintf("%d", 40+colorFromString(s.Background)))
}
if s.Bold {
output += ";1"
styleSlice = append(styleSlice, "1")
}
if s.Italic {
output += ";3"
styleSlice = append(styleSlice, "3")
}
if s.Underline {
output += ";4"
styleSlice = append(styleSlice, "4")
}
if s.Inverse {
output += ";7"
styleSlice = append(styleSlice, "7")
}
if s.Strikethrough {
output += ";9"
styleSlice = append(styleSlice, "9")
}
return output + "m"
return "\x1b[" + strings.Join(styleSlice, ";") + "m"
}
// End Colors
@ -142,6 +144,12 @@ type StyledString struct {
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
func (s Style) sprintf(base string, parts ...StyledString) StyledString {
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 {
return StyledString{msg, s}
}
func (t StyledString) stringFollowedByStyle(style Style) string {
return t.style.toANSI() + t.message + style.toANSI()
func (ss StyledString) stringFollowedByStyle(style Style) string {
return ss.style.toANSI() + ss.message + style.toANSI()
}
func (t StyledString) string() string {
return t.stringFollowedByStyle(basicStyle)
func (ss StyledString) string() string {
return ss.stringFollowedByStyle(basicStyle)
}
func (t StyledString) replace(match string, value StyledString) StyledString {
return t.replaceN(match, value, -1)
func (ss StyledString) replace(match string, value StyledString) StyledString {
return ss.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 (ss StyledString) replaceN(match string, value StyledString, n int) StyledString {
ss.message = strings.Replace(ss.message, match, value.stringFollowedByStyle(ss.style), n)
return ss
}
func (t StyledString) replaceString(match string, value string) StyledString {
t.message = strings.Replace(t.message, match, value, -1)
return t
func (ss StyledString) replaceString(match string, value string) StyledString {
ss.message = strings.Replace(ss.message, match, value, -1)
return ss
}
// Overrides current formatting
func (t StyledString) colorRegex(match string, style Style) StyledString {
re := regexp.MustCompile("(" + match + ")")
locations := re.FindAllStringIndex(t.message, -1)
func (ss StyledString) colorRegex(match string, style Style) StyledString {
return ss.regexReplaceFunc(match, func(subString string) string {
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 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)
newSubstring := replacer(ss.message[loc[0]:loc[1]])
newMessage += ss.message[prevIndex:loc[0]]
newMessage += newSubstring
prevIndex = loc[1]
}
// Append any string after the final match
newMessage += t.message[prevIndex:len(t.message)]
t.message = newMessage
return t
newMessage += ss.message[prevIndex:len(ss.message)]
ss.message = newMessage
return ss
}
// 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 (ss StyledString) append(other StyledString) StyledString {
ss.message = ss.message + other.stringFollowedByStyle(ss.style)
return ss
}
func (t StyledString) appendString(other string) StyledString {
t.message += other
return t
func (ss StyledString) appendString(other string) StyledString {
ss.message += other
return ss
}
// Begin Formatting

View File

@ -11,9 +11,9 @@ 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"
output_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_stream_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_mention_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
pm_format = "PM from $USER@$DEVICE: $MSG"
# 02 = Day, Jan = Month, 06 = Year
@ -22,51 +22,66 @@ date_format = "02Jan06"
# 15 = hours, 04 = minutes, 05 = seconds
time_format = "15:04"
icon_following_user = "[*]"
icon_indirect_following_user = "[?]"
[colors]
[colors.channels]
[colors.channels.basic]
foreground = "normal"
[colors.channels.header]
foreground = "magenta"
bold = true
[colors.channels.unread]
foreground = "green"
italic = true
[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"
bold = true
[colors.message.mention]
foreground = "green"
italic = true
bold = true
[colors.message.id]
foreground = "yellow"
bold = true
[colors.message.time]
foreground = "magenta"
bold = true
[colors.message.sender_default]
foreground = "cyan"
bold = true
[colors.message.sender_device]
foreground = "cyan"
bold = true
[colors.message.sender_tags]
foreground = "yellow"
[colors.message.attachment]
foreground = "red"
[colors.message.link_url]
foreground = "yellow"
[colors.message.link_keybase]
foreground = "yellow"
foreground = "cyan"
[colors.message.reaction]
foreground = "magenta"
bold = true
[colors.message.quote]
foreground = "green"
[colors.message.code]
foreground = "cyan"
background = "grey"
[colors.feed]
[colors.feed.basic]
foreground = "grey"
[colors.feed.error]
foreground = "red"
[colors.feed.success]
foreground = "green"
[colors.feed.file]
foreground = "yellow"
`

View File

@ -8,9 +8,9 @@ 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"
output_format = "$REPL┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_stream_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
output_mention_format = "┌──[$USER@$DEVICE$TAGS] [$ID] [$DATE - $TIME]\n└╼ $MSG"
pm_format = "PM from $USER@$DEVICE: $MSG"
# 02 = Day, Jan = Month, 06 = Year
@ -19,50 +19,65 @@ date_format = "02Jan06"
# 15 = hours, 04 = minutes, 05 = seconds
time_format = "15:04"
icon_following_user = "[*]"
icon_indirect_following_user = "[?]"
[colors]
[colors.channels]
[colors.channels.basic]
foreground = "normal"
[colors.channels.header]
foreground = "magenta"
bold = true
[colors.channels.unread]
foreground = "green"
italic = true
[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"
bold = true
[colors.message.mention]
foreground = "green"
italic = true
bold = true
[colors.message.id]
foreground = "yellow"
bold = true
[colors.message.time]
foreground = "magenta"
bold = true
[colors.message.sender_default]
foreground = "cyan"
bold = true
[colors.message.sender_device]
foreground = "cyan"
bold = true
[colors.message.sender_tags]
foreground = "yellow"
[colors.message.attachment]
foreground = "red"
[colors.message.link_url]
foreground = "yellow"
[colors.message.link_keybase]
foreground = "yellow"
foreground = "cyan"
[colors.message.reaction]
foreground = "magenta"
bold = true
[colors.message.quote]
foreground = "green"
[colors.message.code]
foreground = "cyan"
background = "grey"
[colors.feed]
[colors.feed.basic]
foreground = "grey"
[colors.feed.error]
foreground = "red"
[colors.feed.success]
foreground = "green"
[colors.feed.file]
foreground = "yellow"

139
main.go
View File

@ -9,6 +9,7 @@ import (
"github.com/awesome-gocui/gocui"
"samhofi.us/x/keybase"
"unicode/utf8"
)
var (
@ -41,6 +42,11 @@ func main() {
defer g.Close()
g.SetManagerFunc(layout)
RunCommand("config", "load")
if dev {
channel.TopicType = "dev"
} else {
channel.TopicType = "chat"
}
go populateList()
go updateChatWindow()
if len(os.Args) > 1 {
@ -333,6 +339,9 @@ func printErrorF(message string, parts ...StyledString) {
func printInfo(message string) {
printInfoF(message)
}
func printInfoStyledString(message StyledString) {
printInfoF("$TEXT", message)
}
// this removes formatting
func printInfoF(message string, parts ...StyledString) {
@ -343,12 +352,12 @@ func printToView(viewName string, message string) {
updatingView, err := g.View(viewName)
if err != nil {
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
})
}
@ -372,11 +381,16 @@ func populateChat() {
chat := k.NewChat(channel)
maxX, _ := g.Size()
api, err := chat.Read(maxX / 2)
if err != nil {
if err != nil || api.Result == nil {
for _, testChan := range channels {
if channel.Name == testChan.Name {
channel = testChan
channel.TopicName = "general"
if dev {
channel.TopicType = "dev"
} else {
channel.TopicType = "chat"
}
}
}
chat = k.NewChat(channel)
@ -401,7 +415,7 @@ func populateChat() {
}
var apiCast keybase.ChatAPI
apiCast.Msg = &message.Msg
newMessage := formatOutput(apiCast)
newMessage := formatOutput(apiCast).string()
printMe = append(printMe, newMessage)
}
}
@ -420,6 +434,7 @@ func populateList() {
log.Printf("%+v", err)
} else {
clearView("List")
var textBase = config.Colors.Channels.Basic.stylize("")
var recentPMs = textBase.append(config.Colors.Channels.Header.stylize("---[PMs]---\n"))
var recentPMsCount = 0
@ -459,21 +474,41 @@ func populateList() {
// Formatting
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)
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)
message = message.colorRegex(`@[\w_]*([\.#][\w_]+)*`, config.Colors.Message.LinkKeybase)
message = colorReplaceMentionMe(message)
// 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)
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)
message = message.colorRegex(`\*[^\*]*\*`, config.Colors.Message.Body.withBold())
message = message.colorRegex("^>.*$", config.Colors.Message.Quote)
message = message.regexReplaceFunc("\n<code>(.*\n)*<code>\n", func(match string) string {
maxWidth, _ := g.Size()
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
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
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 message
}
// TODO use this more
@ -497,39 +532,56 @@ func cleanChannelName(c string) string {
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("")
msgType := api.Msg.Content.Type
msgType := msg.Content.Type
switch msgType {
case "text", "attachment":
ret = config.Colors.Message.Header.stylize(formatString)
tm := time.Unix(int64(api.Msg.SentAt), 0)
var msg = formatMessageBody(api.Msg.Content.Text.Body)
tm := time.Unix(int64(msg.SentAt), 0)
var body = formatMessageBody(msg.Content.Text.Body)
if msgType == "attachment" {
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)))
body = config.Colors.Message.Body.stylize("$TITLE\n$FILE")
attachment := msg.Content.Attachment
body = body.replaceString("$TITLE", attachment.Object.Title)
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)
device := config.Colors.Message.SenderDevice.stylize(api.Msg.Sender.DeviceName)
msgID := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", api.Msg.ID))
user := colorUsername(msg.Sender.Username)
device := config.Colors.Message.SenderDevice.stylize(msg.Sender.DeviceName)
msgID := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", msg.ID))
date := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.DateFormat))
msgTime := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.TimeFormat))
channelName := config.Colors.Message.ID.stylize(fmt.Sprintf("@%s#%s", api.Msg.Channel.Name, api.Msg.Channel.TopicName))
ret = ret.replace("$MSG", msg)
c0ck := config.Colors.Message.Quote.stylize(reply)
channelName := config.Colors.Message.ID.stylize(fmt.Sprintf("@%s#%s", msg.Channel.Name, msg.Channel.TopicName))
ret = ret.replace("$REPL", c0ck)
ret = ret.replace("$MSG", body)
ret = ret.replace("$USER", user)
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)
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
if stream {
format = config.Formatting.OutputStreamFormat
@ -541,6 +593,10 @@ func formatOutput(api keybase.ChatAPI) string {
// Input handling
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 api.Msg.Channel.MembersType == channel.MembersType && cleanChannelName(api.Msg.Channel.Name) == channel.Name {
if channel.MembersType == keybase.TEAM && channel.TopicName != api.Msg.Channel.TopicName {
@ -560,7 +616,7 @@ func handleMessage(api keybase.ChatAPI) {
if m.Text == k.Username {
// We are in a team
if topicName != channel.TopicName {
printInfo(formatMessage(api, config.Formatting.OutputMentionFormat))
printInfoStyledString(formatMessage(api, config.Formatting.OutputMentionFormat))
fmt.Print("\a")
}
@ -569,15 +625,16 @@ func handleMessage(api keybase.ChatAPI) {
}
} else {
if msgSender != channel.Name {
printInfo(formatMessage(api, config.Formatting.PMFormat))
printInfoStyledString(formatMessage(api, config.Formatting.PMFormat))
fmt.Print("\a")
}
}
}
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 {
printToView("Chat", formatOutput(api))
if channel.MembersType == keybase.USER || channel.MembersType == keybase.TEAM && channel.TopicName == api.Msg.Channel.TopicName &&
channel.TopicType == api.Msg.Channel.TopicType {
printToView("Chat", formatOutput(api).string())
chat := k.NewChat(channel)
lastMessage.ID = api.Msg.ID
chat.Read(api.Msg.ID)
@ -585,9 +642,9 @@ func handleMessage(api keybase.ChatAPI) {
}
} else {
if api.Msg.Channel.MembersType == keybase.TEAM {
printToView("Chat", formatOutput(api))
printToView("Chat", formatOutput(api).string())
} else {
printToView("Chat", formatMessage(api, config.Formatting.PMFormat))
printToView("Chat", formatMessage(api, config.Formatting.PMFormat).string())
}
}
} else {

View File

@ -36,12 +36,14 @@ type Basics struct {
// 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"`
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"`
IconFollowingUser string `toml:"icon_following_user"`
IconIndirectFollowUser string `toml:"icon_indirect_following_user"`
}
// Colors holds the 'colors' section of the config file
@ -75,18 +77,23 @@ type Message struct {
Header Style `toml:"header"`
Mention Style `toml:"mention"`
ID Style `toml:"id"`
Tags Style `toml:"tags"`
Time Style `toml:"time"`
SenderDefault Style `toml:"sender_default"`
SenderDevice Style `toml:"sender_device"`
SenderTags Style `toml:"sender_tags"`
Attachment Style `toml:"attachment"`
LinkURL Style `toml:"link_url"`
LinkKeybase Style `toml:"link_keybase"`
Reaction Style `toml:"reaction"`
Quote Style `toml:"quote"`
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"`
Basic Style `toml:"basic"`
Error Style `toml:"error"`
File Style `toml:"file"`
Success Style `toml:"success"`
}

58
userTags.go Normal file
View 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)
}