6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-26 00:38:22 +00:00

Rebuild the shitty UI using tcell/tview

This commit is contained in:
James Mills 2022-03-20 11:19:21 +10:00
parent 166a7c0847
commit 4c7835f394
9 changed files with 274 additions and 114 deletions

167
client.go Normal file

@ -0,0 +1,167 @@
package saltyim
import (
"context"
"fmt"
"log"
"strings"
"sync"
"time"
_ "image/jpeg"
_ "image/png"
"github.com/keys-pub/keys"
"github.com/rivo/tview"
"go.mills.io/salty"
"go.yarn.social/lextwt"
)
const (
dateTimeFormat = "2006-01-02 15:04:05"
)
type chatClient struct {
mu sync.RWMutex
key *keys.EdX25519Key
uri string
me Addr
inbox string
user string
config Config
// Configurations.
palette map[string]string
}
// NewChatClient initializes a new chatClient.
// Sets up connection with broker, and initializes UI.
func NewChatClient(key *keys.EdX25519Key, uri string, me Addr, inbox, user string) (*chatClient, error) {
config, err := Lookup(user)
if err != nil {
return nil, fmt.Errorf("error: failed to lookup user %q: %w", user, err)
}
return &chatClient{
key: key,
uri: uri,
me: me,
inbox: inbox,
user: user,
config: config,
palette: map[string]string{
"background": "C5C3C6",
"border": "4C5C68",
"title": "46494C",
"date": "E76F51",
"name": "1985A1",
"text": "577399",
},
}, nil
}
// newMessageHandler returns an InputHandler that handles outgoing messages.
func (cc *chatClient) newMessageHandler(outCh chan<- string) InputHandler {
handler := func(message string) {
cc.mu.RLock()
outCh <- message
cc.mu.RUnlock()
}
return handler
}
// updateChatBox updates chatBox component with incoming messages.
func (cc *chatClient) updateChatBox(inCh <-chan string, app *tview.Application,
chatBox *tview.TextView) {
var prevSender string
var prevTime time.Time
for in := range inCh {
s, err := lextwt.ParseSalty(in)
if err != nil {
continue
}
switch s := s.(type) {
case *lextwt.SaltyEvent:
case *lextwt.SaltyText:
if s.User.String() != cc.user {
continue
}
cc.mu.RLock()
app.QueueUpdateDraw(func() {
if s.User.String() != prevSender || s.Timestamp.DateTime().After(prevTime.Add(5*time.Minute)) {
fmt.Fprintf(chatBox, "[#%s]%s - [#%s]%s\n",
cc.palette["name"],
s.User.String(),
cc.palette["date"],
s.Timestamp.DateTime().Local().Format(dateTimeFormat),
)
}
fmt.Fprintf(chatBox, "[#%s]%s", cc.palette["text"], s.LiteralText())
})
cc.mu.RUnlock()
prevSender = s.User.String()
prevTime = s.Timestamp.DateTime()
}
}
}
// setScreen initializes the layout and UI components.
func (cc *chatClient) SetScreen(inCh <-chan string, outCh chan<- string) {
app := tview.NewApplication()
// Generate UI components.
cc.mu.RLock()
chatBox := NewChatBox(cc.palette, cc.user)
inputField := NewChatInput(cc.palette, cc.newMessageHandler(outCh))
cc.mu.RUnlock()
// Layout the widgets in flex view.
flex := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(chatBox, 0, 1, false).
AddItem(inputField, 3, 0, false)
go cc.updateChatBox(inCh, app, chatBox)
err := app.SetRoot(flex, true).SetFocus(inputField).EnableMouse(true).Run()
if err != nil {
log.Fatal(err)
}
}
// Open bi-directional stream between client and server.
func (cc *chatClient) RunChat(inCh chan<- string, outCh <-chan string) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Receives incoming messages on a separate goroutine to be non-blocking.
go func() {
for msg := range Read(ctx, cc.key, cc.uri, cc.inbox, "", "") {
inCh <- msg
}
}()
// Forward/send outgoing messages.
for msg := range outCh {
if strings.TrimSpace(msg) == "" {
continue
}
b, err := salty.Encrypt(cc.key, PackMessage(cc.me, msg), []string{cc.config.Key})
if err != nil {
log.Fatalf("error encrypting message: %v", err)
}
err = Send(cc.config.Endpoint, string(b))
if err != nil {
log.Fatalf("error sending message: %v", err)
}
}
}

@ -2,34 +2,14 @@ package main
import (
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
tm "github.com/buger/goterm"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.yarn.social/lextwt"
"go.mills.io/salty"
"go.mills.io/saltyim"
)
func prompt() (string, error) {
prompt := promptui.Prompt{Label: "> "}
result, err := prompt.Run()
if err != nil {
return "", fmt.Errorf("error getting prompt response %w", err)
}
return result, nil
}
var chatCmd = &cobra.Command{
Use: "chat <user>",
Short: "Creates a chat with a specific user",
@ -60,79 +40,17 @@ func chat(identity, uri, inbox, user string) {
os.Exit(2)
}
config, err := saltyim.Lookup(user)
// Initialize necessary channels.
inCh := make(chan string)
outCh := make(chan string)
// Initialize client.
cc, err := saltyim.NewChatClient(key, uri, me, inbox, user)
if err != nil {
fmt.Fprintf(os.Stderr, "error: failed to lookup user %q: %s", user, err)
fmt.Fprintf(os.Stderr, "error creating chat: %s", err)
os.Exit(2)
}
tm.Clear()
tm.Printf("Chatting as %s", me)
tm.Printf(" via %s/%s\n", uri, inbox)
tm.Printf(" with %s", user)
tm.Printf(" via %s\n", config.Endpoint)
tm.Flush()
stop := make(chan struct{})
var wg sync.WaitGroup
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
defer wg.Done()
<-sigs
stop <- struct{}{}
}()
wg.Add(1)
go func() {
defer wg.Done()
for msg := range saltyim.Read(key, uri, inbox, "", "", stop) {
s, err := lextwt.ParseSalty(msg)
if err != nil {
continue
}
switch s := s.(type) {
case *lextwt.SaltyEvent:
case *lextwt.SaltyText:
if s.User.String() != user {
continue
}
tm.Println(formatMsg(msg))
tm.Flush()
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for {
msg, err := prompt()
if err != nil {
break
}
if strings.TrimSpace(msg) == "" {
continue
}
b, err := salty.Encrypt(key, packMsg(me, msg), []string{config.Key})
if err != nil {
log.Fatalf("error encrypting message: %v", err)
}
err = saltyim.Send(config.Endpoint, string(b))
if err != nil {
log.Fatalf("error sending message: %v", err)
}
}
}()
wg.Wait()
go cc.RunChat(inCh, outCh)
cc.SetScreen(inCh, outCh)
}

@ -12,6 +12,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.yarn.social/lextwt"
"golang.org/x/net/context"
"go.mills.io/saltyim"
)
@ -105,17 +106,18 @@ func read(identity, uri, prehook, posthook string, args ...string) {
inbox = os.Getenv("USER")
}
stop := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
stop <- struct{}{}
cancel()
}()
for msg := range saltyim.Read(key, uri, inbox, prehook, posthook, stop) {
for msg := range saltyim.Read(ctx, key, uri, inbox, prehook, posthook) {
fmt.Println(formatMsg(msg))
}
}

@ -7,7 +7,6 @@ import (
"log"
"os"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -47,10 +46,6 @@ func init() {
rootCmd.AddCommand(sendCmd)
}
func packMsg(me saltyim.Addr, msg string) []byte {
return []byte(fmt.Sprint(time.Now().UTC().Format(time.RFC3339), "\t", me.Formatted(), "\t", msg))
}
func send(identity, user string, args ...string) {
user = strings.TrimSpace(user)
if user == "" {
@ -87,7 +82,7 @@ func send(identity, user string, args ...string) {
msg = strings.Join(args, " ")
}
b, err := salty.Encrypt(key, packMsg(me, msg), []string{config.Key})
b, err := salty.Encrypt(key, saltyim.PackMessage(me, msg), []string{config.Key})
if err != nil {
log.Fatalf("error encrypting message: %v", err)
}

6
go.mod

@ -13,7 +13,7 @@ require (
require (
git.mills.io/prologic/msgbus v0.1.3
github.com/Masterminds/sprig/v3 v3.2.2
github.com/buger/goterm v1.0.4
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/uuid v1.3.0 // indirect
@ -25,15 +25,15 @@ require (
github.com/keys-pub/keys v0.1.22
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/magiconair/properties v1.8.6 // indirect
github.com/manifoldco/promptui v0.9.0
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/afero v1.8.2 // indirect
go.yarn.social/lextwt v0.0.0-20220318224940-ae4228bbcb31
go.yarn.social/types v0.0.0-20220304222359-9694f95ad749 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
)

24
go.sum

@ -81,8 +81,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@ -90,11 +88,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
@ -142,6 +137,10 @@ github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwU
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 h1:QqwPZCwh/k1uYqq6uXSb9TRDhTkfQbO80v8zhnIe5zM=
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1 h1:Xh9mvwEmhbdXlRSsgn+N0zj/NqnKvpeqL08oKDHln2s=
github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1/go.mod h1:ElSskYZe3oM8kThaHGJ+kiN2yyUMVXMZ7WxF9QqLDS8=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -333,13 +332,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@ -351,6 +350,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@ -424,6 +425,10 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 h1:xe+mmCnDN82KhC010l3NfYlA8ZbOuzbXAzSYBa6wbMc=
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@ -650,7 +655,6 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -699,11 +703,11 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -727,6 +731,8 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 h1:saXMvIOKvRFwbOMicHXr0B1uwoxq9dGmLe5ExMES6c4=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

@ -2,6 +2,7 @@ package saltyim
import (
"bytes"
"context"
"fmt"
"os"
"time"
@ -55,7 +56,7 @@ func handleMessage(key *keys.EdX25519Key, prehook, posthook string, msgs chan st
}
// Read ...
func Read(key *keys.EdX25519Key, uri, inbox, prehook, posthook string, stop chan struct{}) chan string {
func Read(ctx context.Context, key *keys.EdX25519Key, uri, inbox, prehook, posthook string) chan string {
client := client.NewClient(uri, nil)
msgs := make(chan string)
@ -64,7 +65,7 @@ func Read(key *keys.EdX25519Key, uri, inbox, prehook, posthook string, stop chan
s.Start()
go func() {
<-stop
<-ctx.Done()
s.Stop()
close(msgs)
}()

@ -2,11 +2,19 @@ package saltyim
import (
"bytes"
"fmt"
"net/http"
"time"
log "github.com/sirupsen/logrus"
)
// Packmessage formts an outoing message in the Message Format
// <timestamp>\t(<sender>) <message>
func PackMessage(me Addr, msg string) []byte {
return []byte(fmt.Sprint(time.Now().UTC().Format(time.RFC3339), "\t", me.Formatted(), "\t", msg))
}
// Send sends the encrypted message `msg` to the Endpoint `endpoint` using a
// `POST` request and returns nil on success or an error on failure.
func Send(endpoint, msg string) error {

63
tview.go Normal file

@ -0,0 +1,63 @@
package saltyim
import (
"regexp"
"strconv"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
// InputHandler handles input from tview.InputField.
type InputHandler func(message string)
// hexToTCell converts a hex string to the corresponding TCell.Color.
func hexToTCell(hexStr string) tcell.Color {
result, _ := strconv.ParseInt(hexStr, 16, 64)
return tcell.NewHexColor(int32(result))
}
// NewChatBox initializes and returns a 'chatBox' to display incoming
// chat messages.
func NewChatBox(palette map[string]string, title string) *tview.TextView {
chatBox := tview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
SetWordWrap(true).
SetTextAlign(tview.AlignLeft)
chatBox.SetBorder(true).
SetTitle(title),
SetTitleColor(hexToTCell(palette["title"])).
SetTitleAlign(tview.AlignLeft).
SetBorderColor(hexToTCell(palette["border"])).
SetBackgroundColor(hexToTCell(palette["background"]))
return chatBox
}
// NewChatInput initializes and returns a 'chatInput' component
// that handles user inputs and forwards chat messages.
func NewChatInput(palette map[string]string, handler InputHandler) *tview.InputField {
nLine := regexp.MustCompile(`\\n\s?`)
// Initialize 'inputField' for user inputs.
chatInput := tview.NewInputField()
chatInput.SetFieldBackgroundColor(tcell.ColorBlack).
SetFieldWidth(0).
SetFieldTextColor(hexToTCell(palette["text"])).
SetFieldBackgroundColor(hexToTCell(palette["background"])).
SetDoneFunc(func(key tcell.Key) {
// Send and clear inputField.
if key == tcell.KeyEnter {
message := nLine.ReplaceAllString(chatInput.GetText(), "\n") + "\n"
handler(message)
chatInput.SetText("")
}
}).
SetBorder(true).
SetBorderColor(hexToTCell(palette["border"])).
SetBackgroundColor(hexToTCell(palette["background"]))
return chatInput
}