feat: make endpoint uniform. add profiles (#30)

Co-authored-by: Jon Lundy <jon@xuu.cc>
Reviewed-on: prologic/saltyim#30
Co-authored-by: xuu <xuu@noreply@mills.io>
Co-committed-by: xuu <xuu@noreply@mills.io>
bubbletea_tui
xuu 6 months ago committed by James Mills
parent b496aed878
commit 7ccd59efc0
  1. 32
      client.go
  2. 43
      cmd/salty-chat/chat.go
  3. 37
      cmd/salty-chat/read.go
  4. 33
      cmd/salty-chat/root.go
  5. 25
      cmd/salty-chat/send.go
  6. 17
      example-config.yml
  7. 23
      identity.go
  8. 16
      lookup.go
  9. 4
      readmsgs.go
  10. 9
      utils.go

@ -33,12 +33,11 @@ func getUserColor(s string) tcell.Color {
type chatClient struct {
mu sync.RWMutex
key *keys.EdX25519Key
uri string
me Addr
inbox string
user string
config Config
key *keys.EdX25519Key
me Addr
endpoint string
user string
config Config
// Configurations.
palette map[string]string
@ -46,19 +45,18 @@ type chatClient struct {
// 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) {
func NewChatClient(key *keys.EdX25519Key, me Addr, endpoint, 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,
key: key,
me: me,
endpoint: endpoint,
user: user,
config: config,
palette: map[string]string{
"background": "000000",
@ -106,7 +104,7 @@ func (cc *chatClient) updateChatBox(inCh <-chan string, app *tview.Application,
f := formatter.Formatter{
Writer: buf,
Indent: []byte("> "),
Width: width-1,
Width: width - 1,
}
if _, err := f.Write([]byte(s.LiteralText())); err != nil {
@ -136,8 +134,8 @@ func (cc *chatClient) SetScreen(inCh <-chan string, outCh chan<- string) {
app := tview.NewApplication()
title := fmt.Sprintf(
"Chatting as %s via %s/%s with %s via %s",
cc.me, cc.uri, cc.inbox, cc.user, cc.config.Endpoint,
"Chatting as %s via %s with %s via %s",
cc.me, cc.endpoint, cc.user, cc.config.Endpoint,
)
// Generate UI components.
@ -166,7 +164,7 @@ func (cc *chatClient) RunChat(inCh chan<- string, outCh <-chan string) {
// Receives incoming messages on a separate goroutine to be non-blocking.
go func() {
for msg := range Read(ctx, cc.key, cc.uri, cc.inbox, "", "") {
for msg := range Read(ctx, cc.key, cc.endpoint, "", "") {
inCh <- msg
}
}()

@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -18,10 +19,26 @@ to your default inbox (normally $USER) and prompts for input and sends encrypted
messages to the user via their endpoint.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
uri := viper.GetString("broker-uri")
user := viper.GetString("user")
endpoint := viper.GetString("endpoint")
identity := viper.GetString("identity")
inbox := viper.GetString("inbox")
chat(identity, uri, inbox, args[0])
var profiles []profile
viper.UnmarshalKey("profiles", &profiles)
for _, p := range profiles {
if user == p.User {
endpoint = p.Endpoint
identity = p.Identity
}
}
var me saltyim.Addr
if sp := strings.Split(user, "@"); len(sp) > 1 {
me.User = sp[0]
me.Domain = sp[1]
}
chat(me, identity, endpoint, args[0])
},
}
@ -29,14 +46,18 @@ func init() {
rootCmd.AddCommand(chatCmd)
}
func chat(identity, uri, inbox, user string) {
key, me, err := saltyim.GetIdentity(identity)
func chat(me saltyim.Addr, identity, endpoint, user string) {
key, m, err := saltyim.GetIdentity(identity)
if err != nil {
fmt.Fprintf(os.Stderr, "error opening identity: %q", identity)
if me.IsZero() {
fmt.Fprintf(os.Stderr, "unable to find your user addressn in %q", identity)
fmt.Fprintln(os.Stderr, "tip: try adding # user: nick@domain to your identity")
}
fmt.Fprintf(os.Stderr, "error opening identity: %q\n", identity)
os.Exit(2)
}
if me.IsZero() {
me = m
}
if me.IsZero() {
fmt.Fprintf(os.Stderr, "unable to find your user addressn in %q\n", identity)
fmt.Fprintln(os.Stderr, "tip: try adding # user: nick@domain to your identity")
os.Exit(2)
}
@ -45,7 +66,7 @@ func chat(identity, uri, inbox, user string) {
outCh := make(chan string)
// Initialize client.
cc, err := saltyim.NewChatClient(key, uri, me, inbox, user)
cc, err := saltyim.NewChatClient(key, me, endpoint, user)
if err != nil {
fmt.Fprintf(os.Stderr, "error creating chat: %s", err)
os.Exit(2)

@ -2,7 +2,6 @@ package main
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
@ -24,24 +23,32 @@ var readCmd = &cobra.Command{
Long: `This command subscribes to the provided inbox (optiona) which if
not specified defaults to the local user ($USER)`,
Run: func(cmd *cobra.Command, args []string) {
uri := viper.GetString("broker-uri")
inbox := viper.GetString("inbox")
user := viper.GetString("user")
endpoint := viper.GetString("endpoint")
identity := viper.GetString("identity")
prehook, err := cmd.Flags().GetString("pre-hook")
if err != nil {
log.Fatal("error getting --pre-hook flag")
var profiles []profile
viper.UnmarshalKey("profiles", &profiles)
for _, p := range profiles {
if user == p.User {
endpoint = p.Endpoint
identity = p.Identity
}
}
posthook, err := cmd.Flags().GetString("post-hook")
if err != nil {
log.Fatal("error getting --post-hook flag")
}
prehook := viper.GetString("pre-hook")
posthook := viper.GetString("post-hook")
read(uri, inbox, identity, prehook, posthook, args...)
read(endpoint, identity, prehook, posthook, args...)
},
}
type profile struct {
User string
Endpoint string
Identity string
}
func init() {
rootCmd.AddCommand(readCmd)
@ -56,17 +63,13 @@ func init() {
)
}
func read(uri, inbox, identity, prehook, posthook string, args ...string) {
func read(endpoint, identity, prehook, posthook string, args ...string) {
key, _, err := saltyim.GetIdentity(identity)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading identity %q: %s", identity, err)
os.Exit(2)
}
if len(args) > 0 {
inbox = args[0]
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -78,7 +81,7 @@ func read(uri, inbox, identity, prehook, posthook string, args ...string) {
cancel()
}()
for msg := range saltyim.Read(ctx, key, uri, inbox, prehook, posthook) {
for msg := range saltyim.Read(ctx, key, endpoint, prehook, posthook) {
fmt.Println(saltyim.FormatMessage(msg))
}
}

@ -72,6 +72,11 @@ func init() {
"config file (default is $HOME/.salty/config.yml)",
)
rootCmd.PersistentFlags().StringP(
"user", "u", "",
"User name/profile form config to use",
)
rootCmd.PersistentFlags().BoolP(
"debug", "d", false,
"Enable debug logging",
@ -83,25 +88,27 @@ func init() {
)
rootCmd.PersistentFlags().StringP(
"inbox", "i", saltyim.DefaultInbox(),
"Use the inbox with broker",
)
rootCmd.PersistentFlags().StringP(
"broker-uri", "u", "https://msgbus.mills.io",
"URI to connect to saltyim broker",
"endpoint", "i", saltyim.DefaultEndpoint(),
"URI to connect to saltyim endpoint",
)
viper.BindPFlag("broker-uri", rootCmd.PersistentFlags().Lookup("broker-uri"))
viper.SetDefault("broker-uri", "https://msgbus.mills.io")
viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
viper.SetDefault("debug", false)
viper.BindPFlag("user", rootCmd.PersistentFlags().Lookup("user"))
viper.SetDefault("user", "")
viper.BindPFlag("identity", rootCmd.PersistentFlags().Lookup("identity"))
viper.SetDefault("identity", saltyim.DefaultIdentity())
viper.BindPFlag("inbox", rootCmd.PersistentFlags().Lookup("inbox"))
viper.SetDefault("inbox", saltyim.DefaultInbox())
viper.BindPFlag("endpoint", rootCmd.PersistentFlags().Lookup("endpoint"))
viper.SetDefault("endpoint", saltyim.DefaultEndpoint())
viper.BindPFlag("pre-hook", rootCmd.PersistentFlags().Lookup("pre-hook"))
viper.SetDefault("endpoint", saltyim.DefaultEndpoint())
viper.BindPFlag("pre-hook", rootCmd.PersistentFlags().Lookup("pre-hook"))
viper.SetDefault("endpoint", saltyim.DefaultEndpoint())
}
// initConfig reads in config file and ENV variables if set.
@ -117,7 +124,9 @@ func initConfig() {
os.Exit(1)
}
viper.AddConfigPath(filepath.Join(home, "salty"))
viper.AddConfigPath(filepath.Join(home, ".config", "salty"))
viper.AddConfigPath(filepath.Join("$XDG_CONFIG_HOME", "salty"))
viper.SetConfigName("config")
viper.SetConfigType("yml")
}

@ -36,9 +36,24 @@ For example:
https://mills.io/.well-known/salty/prologic.json`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := args[0]
user := viper.GetString("user")
identity := viper.GetString("identity")
send(identity, user, args[1:]...)
var profiles []profile
viper.UnmarshalKey("profiles", &profiles)
for _, p := range profiles {
if user == p.User {
identity = p.Identity
}
}
var me saltyim.Addr
if sp := strings.Split(user, "@"); len(sp) > 1 {
me.User = sp[0]
me.Domain = sp[1]
}
send(me, identity, args[0], args[1:]...)
},
}
@ -46,7 +61,7 @@ func init() {
rootCmd.AddCommand(sendCmd)
}
func send(identity, user string, args ...string) {
func send(me saltyim.Addr, identity, user string, args ...string) {
user = strings.TrimSpace(user)
if user == "" {
fmt.Fprintf(os.Stderr, "error: no user supplied")
@ -61,9 +76,9 @@ func send(identity, user string, args ...string) {
key, me, err := saltyim.GetIdentity(identity)
if err != nil {
fmt.Fprintf(os.Stderr, "error opening identity: %q", identity)
fmt.Fprintf(os.Stderr, "error opening identity: %q\n", identity)
if me.IsZero() {
fmt.Fprintf(os.Stderr, "unable to find your user addressn in %q", identity)
fmt.Fprintf(os.Stderr, "unable to find your user addressn in %q\n", identity)
fmt.Fprintln(os.Stderr, "tip: try adding # user: nick@domain to your identity")
}
os.Exit(2)

@ -0,0 +1,17 @@
# debug enables debug logging
debug: false
identity: ~/.config/salty/example.key
endpoint: https://domain.tld/example
user: example@domain.tld
# profiles are entries of identities and their endpoint config.
# The identity in the root config is default. Use -u to choose alternate profile.
profiles:
# # user is the friendly name for the user in chats.
# - user: user@example.com
# # endpoint is the broker endpoint address
# endpoint: https://example.com/salty/user
# # identity is the location of the private key
# identity: ~/.config/salty/user.key

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/keys-pub/keys"
@ -19,7 +20,7 @@ func readUser(fd io.Reader) (Addr, error) {
if strings.HasPrefix(scan.Text(), "# user:") {
user := strings.Split(strings.TrimSpace(strings.TrimPrefix(scan.Text(), "# user:")), "@")
if len(user) != 2 {
return Addr{}, fmt.Errorf("user not found")
return Addr{}, nil
}
a.User, a.Domain = user[0], user[1]
}
@ -30,13 +31,13 @@ func readUser(fd io.Reader) (Addr, error) {
// DefaultIdentity returns a default identity file (if one exists) otherwise
// returns an empty string
func DefaultIdentity() string {
return os.ExpandEnv("$HOME/.salty/$USER.key")
return os.ExpandEnv("$HOME/.config/salty/$USER.key")
}
// DefaultInbox returns a default inbox file (if one exists) otherwise
// DefaultEndpoint returns a default inbox file (if one exists) otherwise
// returns an empty string
func DefaultInbox() string {
return os.ExpandEnv("$USER")
func DefaultEndpoint() string {
return os.ExpandEnv("https://msgbus.mills.io/$USER")
}
// CreateIdentity ...
@ -64,6 +65,12 @@ func CreateIdentity(fn, user string) error {
// GetIdentity ...
func GetIdentity(fn string) (*keys.EdX25519Key, Addr, error) {
// Handle unix home with `~`
if strings.HasPrefix(fn, "~/") {
dirname, _ := os.UserHomeDir()
fn = filepath.Join(dirname, fn[2:])
}
id, err := os.Open(fn)
if err != nil {
return nil, Addr{}, fmt.Errorf("error opening identity %q: %s", fn, err)
@ -77,9 +84,5 @@ func GetIdentity(fn string) (*keys.EdX25519Key, Addr, error) {
id.Seek(0, 0)
me, err := readUser(id)
if err != nil {
return key, Addr{}, fmt.Errorf("error reading user from identity %q: %s", fn, err)
}
return key, me, nil
return key, me, err
}

@ -68,14 +68,18 @@ func Lookup(addr string) (Config, error) {
log.WithError(err).Errorf("error parsing addr %q", addr)
return Config{}, err
}
// Attempt using hash
res, err := Request(http.MethodGet, a.HashURI(), nil, nil)
config, err := fetchConfig(a.HashURI())
if err != nil {
// Fallback to plain user nick
log.WithError(err).Warn("failed to get hash uri. attempting plaintext.")
res, err = Request(http.MethodGet, a.URI(), nil, nil)
config, err = fetchConfig(a.URI())
}
return config, err
}
func fetchConfig(addr string) (Config, error) {
// Attempt using hash
res, err := Request(http.MethodGet, addr, nil, nil)
if err != nil {
log.WithError(err).Error("error requesting well-known uri")
return Config{}, err
@ -83,13 +87,13 @@ func Lookup(addr string) (Config, error) {
data, err := ioutil.ReadAll(res.Body)
if err != nil {
log.WithError(err).Errorf("error reading well-known config")
// log.WithError(err).Errorf("error reading well-known config")
return Config{}, err
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
log.WithError(err).Error("error parsing well-known config")
// log.WithError(err).Error("error parsing well-known config")
return Config{}, err
}

@ -56,11 +56,11 @@ func handleMessage(key *keys.EdX25519Key, prehook, posthook string, msgs chan st
}
// Read ...
func Read(ctx context.Context, key *keys.EdX25519Key, uri, inbox, prehook, posthook string) chan string {
func Read(ctx context.Context, key *keys.EdX25519Key, endpoint, prehook, posthook string) chan string {
uri, inbox := SplitInbox(endpoint)
client := client.NewClient(uri, nil)
msgs := make(chan string)
s := client.Subscribe(inbox, handleMessage(key, prehook, posthook, msgs))
s.Start()

@ -8,6 +8,7 @@ import (
"net/http"
"os"
"os/exec"
"strings"
"syscall"
"text/template"
"time"
@ -142,3 +143,11 @@ func RunCmd(timeout time.Duration, command string, stdin io.Reader, args ...stri
func SetTerminalTitle(format string, args ...interface{}) {
fmt.Printf("\033]0;%s\007", fmt.Sprintf(format, args...))
}
func SplitInbox(endpoint string) (string, string) {
idx := strings.LastIndex(endpoint, "/")
if idx == -1 {
return "", ""
}
return endpoint[:idx], endpoint[idx+1:]
}

Loading…
Cancel
Save