Merge pull request #138 from remicorniere/Basic_Terminal_Client
Basic terminal client
This commit is contained in:
commit
38f53642ba
15
_examples/xmpp_chat_client/config.yml
Normal file
15
_examples/xmpp_chat_client/config.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Default config for the client
|
||||||
|
Server :
|
||||||
|
- full_address: "localhost:5222"
|
||||||
|
- port: 5222
|
||||||
|
Client :
|
||||||
|
- name: "testuser2"
|
||||||
|
- jid: "testuser2@localhost"
|
||||||
|
- pass: "pass123" #Password in a config file yay
|
||||||
|
|
||||||
|
Contacts : "testuser1@localhost;testuser3@localhost"
|
||||||
|
|
||||||
|
LogStanzas:
|
||||||
|
- logger_on: "true"
|
||||||
|
- logfile_path: "./logs"
|
||||||
|
|
10
_examples/xmpp_chat_client/go.mod
Normal file
10
_examples/xmpp_chat_client/go.mod
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module go-xmpp/_examples/xmpp_chat_client
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
|
github.com/spf13/viper v1.6.1
|
||||||
|
gosrc.io/xmpp v0.3.1-0.20191212145100-27130d72926b
|
||||||
|
)
|
328
_examples/xmpp_chat_client/interface.go
Normal file
328
_examples/xmpp_chat_client/interface.go
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/awesome-gocui/gocui"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Windows
|
||||||
|
chatLogWindow = "clw" // Where (received and sent) messages are logged
|
||||||
|
chatInputWindow = "iw" // Where messages are written
|
||||||
|
rawInputWindow = "rw" // Where raw stanzas are written
|
||||||
|
contactsListWindow = "cl" // Where the contacts list is shown, and contacts are selectable
|
||||||
|
menuWindow = "mw" // Where the menu is shown
|
||||||
|
|
||||||
|
// Menu options
|
||||||
|
disconnect = "Disconnect"
|
||||||
|
askServerForRoster = "Ask server for roster"
|
||||||
|
rawMode = "Switch to Send Raw Mode"
|
||||||
|
messageMode = "Switch to Send Message Mode"
|
||||||
|
contactList = "Contacts list"
|
||||||
|
backFromContacts = "<- Go back"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To store names of views on top
|
||||||
|
type viewsState struct {
|
||||||
|
input string // Which input view is on top
|
||||||
|
side string // Which side view is on top
|
||||||
|
contacts []string // Contacts list
|
||||||
|
currentContact string // Contact we are currently messaging
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Which window is on top currently on top of the other.
|
||||||
|
// This is the init setup
|
||||||
|
viewState = viewsState{
|
||||||
|
input: chatInputWindow,
|
||||||
|
side: menuWindow,
|
||||||
|
}
|
||||||
|
menuOptions = []string{contactList, rawMode, askServerForRoster, disconnect}
|
||||||
|
// Errors
|
||||||
|
servConnFail = errors.New("failed to connect to server. Check your configuration ? Exiting")
|
||||||
|
)
|
||||||
|
|
||||||
|
func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) {
|
||||||
|
if _, err := g.SetCurrentView(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return g.SetViewOnTop(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func layout(g *gocui.Gui) error {
|
||||||
|
maxX, maxY := g.Size()
|
||||||
|
|
||||||
|
if v, err := g.SetView(chatLogWindow, maxX/5, 0, maxX-1, 5*maxY/6-1, 0); err != nil {
|
||||||
|
if !gocui.IsUnknownView(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "Chat log"
|
||||||
|
v.Wrap = true
|
||||||
|
v.Autoscroll = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := g.SetView(contactsListWindow, 0, 0, maxX/5-1, 5*maxY/6-2, 0); err != nil {
|
||||||
|
if !gocui.IsUnknownView(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "Contacts"
|
||||||
|
v.Wrap = true
|
||||||
|
v.Autoscroll = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := g.SetView(menuWindow, 0, 0, maxX/5-1, 5*maxY/6-2, 0); err != nil {
|
||||||
|
if !gocui.IsUnknownView(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "Menu"
|
||||||
|
v.Wrap = true
|
||||||
|
v.Autoscroll = true
|
||||||
|
fmt.Fprint(v, strings.Join(menuOptions, "\n"))
|
||||||
|
if _, err = setCurrentViewOnTop(g, menuWindow); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := g.SetView(rawInputWindow, 0, 5*maxY/6-1, maxX/1-1, maxY-1, 0); err != nil {
|
||||||
|
if !gocui.IsUnknownView(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "Write or paste a raw stanza. Press \"Ctrl+E\" to send :"
|
||||||
|
v.Editable = true
|
||||||
|
v.Wrap = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := g.SetView(chatInputWindow, 0, 5*maxY/6-1, maxX/1-1, maxY-1, 0); err != nil {
|
||||||
|
if !gocui.IsUnknownView(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "Write a message :"
|
||||||
|
v.Editable = true
|
||||||
|
v.Wrap = true
|
||||||
|
|
||||||
|
if _, err = setCurrentViewOnTop(g, chatInputWindow); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gocui.ErrQuit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends an input text from the user to the backend while also printing it in the chatlog window.
|
||||||
|
// KeyEnter is viewed as "\n" by gocui, so messages should only be one line, whereas raw sending has a different key
|
||||||
|
// binding and therefor should work with this too (for multiple lines stanzas)
|
||||||
|
func writeInput(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
chatLogWindow, _ := g.View(chatLogWindow)
|
||||||
|
|
||||||
|
input := strings.Join(v.ViewBufferLines(), "\n")
|
||||||
|
|
||||||
|
fmt.Fprintln(chatLogWindow, "Me : ", input)
|
||||||
|
textChan <- input
|
||||||
|
|
||||||
|
v.Clear()
|
||||||
|
v.EditDeleteToStartOfLine()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setKeyBindings(g *gocui.Gui) {
|
||||||
|
// ==========================
|
||||||
|
// All views
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Chat input
|
||||||
|
if err := g.SetKeybinding(chatInputWindow, gocui.KeyEnter, gocui.ModNone, writeInput); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding(chatInputWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Raw input
|
||||||
|
if err := g.SetKeybinding(rawInputWindow, gocui.KeyCtrlE, gocui.ModNone, writeInput); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding(rawInputWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Menu
|
||||||
|
if err := g.SetKeybinding(menuWindow, gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding(menuWindow, gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding(menuWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding(menuWindow, gocui.KeyEnter, gocui.ModNone, getLine); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Contacts list
|
||||||
|
if err := g.SetKeybinding(contactsListWindow, gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding(contactsListWindow, gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding(contactsListWindow, gocui.KeyEnter, gocui.ModNone, getLine); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding(contactsListWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// General
|
||||||
|
// Used to handle menu selections and navigations
|
||||||
|
func getLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
var l string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
_, cy := v.Cursor()
|
||||||
|
if l, err = v.Line(cy); err != nil {
|
||||||
|
l = ""
|
||||||
|
}
|
||||||
|
if viewState.side == menuWindow {
|
||||||
|
if l == contactList {
|
||||||
|
cv, _ := g.View(contactsListWindow)
|
||||||
|
viewState.side = contactsListWindow
|
||||||
|
g.SetViewOnTop(contactsListWindow)
|
||||||
|
g.SetCurrentView(contactsListWindow)
|
||||||
|
if len(cv.ViewBufferLines()) == 0 {
|
||||||
|
printContactsToWindow(g, viewState.contacts)
|
||||||
|
}
|
||||||
|
} else if l == disconnect || l == askServerForRoster {
|
||||||
|
chlw, _ := g.View(chatLogWindow)
|
||||||
|
fmt.Fprintln(chlw, infoFormat+" Not yet implemented !")
|
||||||
|
} else if l == rawMode {
|
||||||
|
mw, _ := g.View(menuWindow)
|
||||||
|
viewState.input = rawInputWindow
|
||||||
|
g.SetViewOnTop(rawInputWindow)
|
||||||
|
g.SetCurrentView(rawInputWindow)
|
||||||
|
menuOptions[1] = messageMode
|
||||||
|
v.Clear()
|
||||||
|
v.EditDeleteToStartOfLine()
|
||||||
|
fmt.Fprintln(mw, strings.Join(menuOptions, "\n"))
|
||||||
|
message := "Now sending in raw stanza mode"
|
||||||
|
clv, _ := g.View(chatLogWindow)
|
||||||
|
fmt.Fprintln(clv, infoFormat+message)
|
||||||
|
} else if l == messageMode {
|
||||||
|
mw, _ := g.View(menuWindow)
|
||||||
|
viewState.input = chatInputWindow
|
||||||
|
g.SetViewOnTop(chatInputWindow)
|
||||||
|
g.SetCurrentView(chatInputWindow)
|
||||||
|
menuOptions[1] = rawMode
|
||||||
|
v.Clear()
|
||||||
|
v.EditDeleteToStartOfLine()
|
||||||
|
fmt.Fprintln(mw, strings.Join(menuOptions, "\n"))
|
||||||
|
message := "Now sending in messages mode"
|
||||||
|
clv, _ := g.View(chatLogWindow)
|
||||||
|
fmt.Fprintln(clv, infoFormat+message)
|
||||||
|
}
|
||||||
|
} else if viewState.side == contactsListWindow {
|
||||||
|
if l == backFromContacts {
|
||||||
|
viewState.side = menuWindow
|
||||||
|
g.SetViewOnTop(menuWindow)
|
||||||
|
g.SetCurrentView(menuWindow)
|
||||||
|
} else if l == "" {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
// Updating the current correspondent, back-end side.
|
||||||
|
CorrespChan <- l
|
||||||
|
viewState.currentContact = l
|
||||||
|
// Showing the selected contact in contacts list
|
||||||
|
cl, _ := g.View(contactsListWindow)
|
||||||
|
cts := cl.ViewBufferLines()
|
||||||
|
cl.Clear()
|
||||||
|
printContactsToWindow(g, cts)
|
||||||
|
// Showing a message to the user, and switching back to input after the new contact is selected.
|
||||||
|
message := "Now sending messages to : " + l + " in a private conversation"
|
||||||
|
clv, _ := g.View(chatLogWindow)
|
||||||
|
fmt.Fprintln(clv, infoFormat+message)
|
||||||
|
g.SetCurrentView(chatInputWindow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printContactsToWindow(g *gocui.Gui, contactsList []string) {
|
||||||
|
cl, _ := g.View(contactsListWindow)
|
||||||
|
for _, c := range contactsList {
|
||||||
|
c = strings.ReplaceAll(c, " *", "")
|
||||||
|
if c == viewState.currentContact {
|
||||||
|
fmt.Fprintf(cl, c+" *\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(cl, c+"\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changing view between input and "menu/contacts" when pressing the specific key.
|
||||||
|
func nextView(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if v == nil || v.Name() == chatInputWindow || v.Name() == rawInputWindow {
|
||||||
|
_, err := g.SetCurrentView(viewState.side)
|
||||||
|
return err
|
||||||
|
} else if v.Name() == menuWindow || v.Name() == contactsListWindow {
|
||||||
|
_, err := g.SetCurrentView(viewState.input)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should not be reached right now
|
||||||
|
_, err := g.SetCurrentView(chatInputWindow)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if v != nil {
|
||||||
|
cx, cy := v.Cursor()
|
||||||
|
// Avoid going below the list of contacts. Although lines are stored in the view as a slice
|
||||||
|
// in the used lib. Therefor, if the number of lines is too big, the cursor will go past the last line since
|
||||||
|
// increasing slice capacity is done by doubling it. Last lines will be "nil" and reachable by the cursor
|
||||||
|
// in a dynamic context (such as contacts list)
|
||||||
|
cv := g.CurrentView()
|
||||||
|
h := cv.LinesHeight()
|
||||||
|
if cy+1 >= h {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Lower cursor
|
||||||
|
if err := v.SetCursor(cx, cy+1); err != nil {
|
||||||
|
ox, oy := v.Origin()
|
||||||
|
if err := v.SetOrigin(ox, oy+1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cursorUp(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if v != nil {
|
||||||
|
ox, oy := v.Origin()
|
||||||
|
cx, cy := v.Cursor()
|
||||||
|
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
|
||||||
|
if err := v.SetOrigin(ox, oy-1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,94 +2,289 @@ package main
|
||||||
|
|
||||||
/*
|
/*
|
||||||
xmpp_chat_client is a demo client that connect on an XMPP server to chat with other members
|
xmpp_chat_client is a demo client that connect on an XMPP server to chat with other members
|
||||||
Note that this example sends to a very specific user. User logic is not implemented here.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "bufio"
|
"encoding/xml"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"github.com/awesome-gocui/gocui"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"gosrc.io/xmpp"
|
"gosrc.io/xmpp"
|
||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
currentUserAddress = "localhost:5222"
|
infoFormat = "====== "
|
||||||
currentUserJid = "testuser@localhost"
|
// Default configuration
|
||||||
currentUserPass = "testpass"
|
defaultConfigFilePath = "./"
|
||||||
correspondantJid = "testuser2@localhost"
|
|
||||||
|
configFileName = "config"
|
||||||
|
configType = "yaml"
|
||||||
|
logStanzasOn = "logger_on"
|
||||||
|
logFilePath = "logfile_path"
|
||||||
|
// Keys in config
|
||||||
|
serverAddressKey = "full_address"
|
||||||
|
clientJid = "jid"
|
||||||
|
clientPass = "pass"
|
||||||
|
configContactSep = ";"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CorrespChan = make(chan string, 1)
|
||||||
|
textChan = make(chan string, 5)
|
||||||
|
rawTextChan = make(chan string, 5)
|
||||||
|
killChan = make(chan struct{}, 1)
|
||||||
|
errChan = make(chan error)
|
||||||
|
|
||||||
|
logger *log.Logger
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Server map[string]string `mapstructure:"server"`
|
||||||
|
Client map[string]string `mapstructure:"client"`
|
||||||
|
Contacts string `string:"contact"`
|
||||||
|
LogStanzas map[string]string `mapstructure:"logstanzas"`
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := xmpp.Config{
|
|
||||||
|
// ============================================================
|
||||||
|
// Parse the flag with the config directory path as argument
|
||||||
|
flag.String("c", defaultConfigFilePath, "Provide a path to the directory that contains the configuration"+
|
||||||
|
" file you want to use. Config file should be named \"config\" and be of YAML format..")
|
||||||
|
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||||
|
pflag.Parse()
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Read configuration
|
||||||
|
c := readConfig()
|
||||||
|
|
||||||
|
//================================
|
||||||
|
// Setup logger
|
||||||
|
on, err := strconv.ParseBool(c.LogStanzas[logStanzasOn])
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
f, err := os.OpenFile(path.Join(c.LogStanzas[logFilePath], "logs.txt"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
logger = log.New(f, "", log.Lshortfile|log.Ldate|log.Ltime)
|
||||||
|
logger.SetOutput(f)
|
||||||
|
defer f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Create TUI
|
||||||
|
g, err := gocui.NewGui(gocui.OutputNormal, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
defer g.Close()
|
||||||
|
g.Highlight = true
|
||||||
|
g.Cursor = true
|
||||||
|
g.SelFgColor = gocui.ColorGreen
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
setKeyBindings(g)
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Run TUI
|
||||||
|
go func() {
|
||||||
|
errChan <- g.MainLoop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Start XMPP client
|
||||||
|
go startClient(g, c)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
if err == gocui.ErrQuit {
|
||||||
|
log.Println("Closing client.")
|
||||||
|
} else {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startClient(g *gocui.Gui, config *config) {
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Client setup
|
||||||
|
clientCfg := xmpp.Config{
|
||||||
TransportConfiguration: xmpp.TransportConfiguration{
|
TransportConfiguration: xmpp.TransportConfiguration{
|
||||||
Address: currentUserAddress,
|
Address: config.Server[serverAddressKey],
|
||||||
},
|
},
|
||||||
Jid: currentUserJid,
|
Jid: config.Client[clientJid],
|
||||||
Credential: xmpp.Password(currentUserPass),
|
Credential: xmpp.Password(config.Client[clientPass]),
|
||||||
Insecure: true}
|
Insecure: true}
|
||||||
|
|
||||||
var client *xmpp.Client
|
var client *xmpp.Client
|
||||||
var err error
|
var err error
|
||||||
router := xmpp.NewRouter()
|
router := xmpp.NewRouter()
|
||||||
router.HandleFunc("message", handleMessage)
|
|
||||||
if client, err = xmpp.NewClient(config, router, errorHandler); err != nil {
|
handlerWithGui := func(_ xmpp.Sender, p stanza.Packet) {
|
||||||
fmt.Println("Error new client")
|
msg, ok := p.(stanza.Message)
|
||||||
|
if logger != nil {
|
||||||
|
logger.Println(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connecting client and handling messages
|
v, err := g.View(chatLogWindow)
|
||||||
// To use a stream manager, just write something like this instead :
|
if !ok {
|
||||||
//cm := xmpp.NewStreamManager(client, startMessaging)
|
fmt.Fprintf(v, "%sIgnoring packet: %T\n", infoFormat, p)
|
||||||
//log.Fatal(cm.Run()) //=> this will lock the calling goroutine
|
|
||||||
|
|
||||||
if err = client.Connect(); err != nil {
|
|
||||||
fmt.Printf("XMPP connection failed: %s", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
startMessaging(client)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.Update(func(g *gocui.Gui) error {
|
||||||
|
if msg.Error.Code != 0 {
|
||||||
|
_, err := fmt.Fprintf(v, "Error from server : %s : %s \n", msg.Error.Reason, msg.XMLName.Space)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(strings.TrimSpace(msg.Body)) != 0 {
|
||||||
|
_, err := fmt.Fprintf(v, "%s : %s \n", msg.From, msg.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
router.HandleFunc("message", handlerWithGui)
|
||||||
|
if client, err = xmpp.NewClient(clientCfg, router, errorHandler); err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not create a new client ! %s", err))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startMessaging(client xmpp.Sender) {
|
// ==========================
|
||||||
reader := NewReader(os.Stdin)
|
// Client connection
|
||||||
textChan := make(chan string)
|
if err = client.Connect(); err != nil {
|
||||||
|
msg := fmt.Sprintf("%sXMPP connection failed: %s", infoFormat, err)
|
||||||
|
g.Update(func(g *gocui.Gui) error {
|
||||||
|
v, err := g.View(chatLogWindow)
|
||||||
|
fmt.Fprintf(v, msg)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
fmt.Println("Failed to connect to server. Exiting...")
|
||||||
|
errChan <- servConnFail
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Start working
|
||||||
|
//askForRoster(client, g)
|
||||||
|
updateRosterFromConfig(g, config)
|
||||||
|
// Sending the default contact in a channel. Default value is the first contact in the list from the config.
|
||||||
|
viewState.currentContact = strings.Split(config.Contacts, configContactSep)[0]
|
||||||
|
// Informing user of the default contact
|
||||||
|
clw, _ := g.View(chatLogWindow)
|
||||||
|
fmt.Fprintf(clw, infoFormat+"Now sending messages to "+viewState.currentContact+" in a private conversation\n")
|
||||||
|
CorrespChan <- viewState.currentContact
|
||||||
|
startMessaging(client, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startMessaging(client xmpp.Sender, config *config) {
|
||||||
var text string
|
var text string
|
||||||
|
var correspondent string
|
||||||
for {
|
for {
|
||||||
fmt.Print("Enter text: ")
|
|
||||||
go readInput(reader, textChan)
|
|
||||||
select {
|
select {
|
||||||
case <-killChan:
|
case <-killChan:
|
||||||
return
|
return
|
||||||
case text = <-textChan:
|
case text = <-textChan:
|
||||||
reply := stanza.Message{Attrs: stanza.Attrs{To: correspondantJid}, Body: text}
|
reply := stanza.Message{Attrs: stanza.Attrs{To: correspondent, From: config.Client[clientJid], Type: stanza.MessageTypeChat}, Body: text}
|
||||||
|
if logger != nil {
|
||||||
|
raw, _ := xml.Marshal(reply)
|
||||||
|
logger.Println(string(raw))
|
||||||
|
}
|
||||||
err := client.Send(reply)
|
err := client.Send(reply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("There was a problem sending the message : %v", reply)
|
fmt.Printf("There was a problem sending the message : %v", reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case text = <-rawTextChan:
|
||||||
|
if logger != nil {
|
||||||
|
logger.Println(text)
|
||||||
}
|
}
|
||||||
|
err := client.SendRaw(text)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("There was a problem sending the message : %v", text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case crrsp := <-CorrespChan:
|
||||||
|
correspondent = crrsp
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readInput(reader *Reader, textChan chan string) {
|
// Only reads and parses the configuration
|
||||||
text, _ := reader.ReadString('\n')
|
func readConfig() *config {
|
||||||
textChan <- text
|
viper.SetConfigName(configFileName) // name of config file (without extension)
|
||||||
|
viper.BindPFlags(pflag.CommandLine)
|
||||||
|
viper.AddConfigPath(viper.GetString("c")) // path to look for the config file in
|
||||||
|
err := viper.ReadInConfig() // Find and read the config file
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||||
|
log.Fatalf("%s %s", err, "Please make sure you give a path to the directory of the config and not to the config itself.")
|
||||||
|
} else {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var killChan = make(chan struct{})
|
viper.SetConfigType(configType)
|
||||||
|
var config config
|
||||||
|
err = viper.Unmarshal(&config)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Unable to decode Config: %s \n", err))
|
||||||
|
}
|
||||||
|
|
||||||
// If an error occurs, this is used
|
// Check if we have contacts to message
|
||||||
|
if len(strings.TrimSpace(config.Contacts)) == 0 {
|
||||||
|
log.Panicln("You appear to have no contacts to message !")
|
||||||
|
}
|
||||||
|
// Check logging
|
||||||
|
config.LogStanzas[logFilePath] = path.Clean(config.LogStanzas[logFilePath])
|
||||||
|
on, err := strconv.ParseBool(config.LogStanzas[logStanzasOn])
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if d, e := isDirectory(config.LogStanzas[logFilePath]); (e != nil || !d) && on {
|
||||||
|
log.Panicln("The log file path could not be found or is not a directory.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an error occurs, this is used to kill the client
|
||||||
func errorHandler(err error) {
|
func errorHandler(err error) {
|
||||||
fmt.Printf("%v", err)
|
fmt.Printf("%v", err)
|
||||||
killChan <- struct{}{}
|
killChan <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMessage(s xmpp.Sender, p stanza.Packet) {
|
// Read the client roster from the config. This does not check with the server that the roster is correct.
|
||||||
msg, ok := p.(stanza.Message)
|
// If user tries to send a message to someone not registered with the server, the server will return an error.
|
||||||
if !ok {
|
func updateRosterFromConfig(g *gocui.Gui, config *config) {
|
||||||
_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
|
viewState.contacts = append(strings.Split(config.Contacts, configContactSep), backFromContacts)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
|
|
||||||
|
// Updates the menu panel of the view with the current user's roster.
|
||||||
|
// Need to add support for Roster IQ stanzas to make this work.
|
||||||
|
func askForRoster(client *xmpp.Client, g *gocui.Gui) {
|
||||||
|
// Not implemented yet !
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDirectory(path string) (bool, error) {
|
||||||
|
fileInfo, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return fileInfo.IsDir(), err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ require (
|
||||||
github.com/bdlm/log v0.1.19
|
github.com/bdlm/log v0.1.19
|
||||||
github.com/bdlm/std v0.0.0-20180922040903-fd3b596111c7
|
github.com/bdlm/std v0.0.0-20180922040903-fd3b596111c7
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.5
|
||||||
github.com/spf13/viper v1.4.0
|
github.com/spf13/viper v1.6.1
|
||||||
gosrc.io/xmpp v0.1.1
|
gosrc.io/xmpp v0.1.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -3,8 +3,11 @@ module gosrc.io/xmpp
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/awesome-gocui/gocui v0.6.0 // indirect
|
||||||
github.com/google/go-cmp v0.3.1
|
github.com/google/go-cmp v0.3.1
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
|
github.com/spf13/viper v1.6.1 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
|
||||||
nhooyr.io/websocket v1.6.5
|
nhooyr.io/websocket v1.6.5
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue