From 113d9c04201bd9faba25d62e2027f7d8e169f910 Mon Sep 17 00:00:00 2001 From: Jesse Kuang Date: Wed, 9 Jan 2019 13:52:43 +0800 Subject: [PATCH 01/11] implement DNS SRV lookup --- _example/example.go | 2 +- xmpp.go | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/_example/example.go b/_example/example.go index 136af5a..fbd0b12 100644 --- a/_example/example.go +++ b/_example/example.go @@ -11,7 +11,7 @@ import ( "strings" ) -var server = flag.String("server", "talk.google.com:443", "server") +var server = flag.String("server", "", "server") var username = flag.String("username", "", "username") var password = flag.String("password", "", "password") var status = flag.String("status", "xa", "status") diff --git a/xmpp.go b/xmpp.go index 078270f..dbec361 100644 --- a/xmpp.go +++ b/xmpp.go @@ -82,12 +82,30 @@ func connect(host, user, passwd string) (net.Conn, error) { if strings.TrimSpace(host) == "" { a := strings.SplitN(user, "@", 2) if len(a) == 2 { - addr = a[1] + if cna, addrs, err := net.LookupSRV("xmpp-client", "tcp", a[1]); err == nil { + fmt.Printf("LookupSRV got: %s, %d SRVs\n", cna, len(addrs)) + if len(addrs) > 0 { + // default to first record + addr = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port) + defP := addrs[0].Priority + for _, adr := range addrs { + if adr.Priority < defP { + addr = fmt.Sprintf("%s:%d", adr.Target, adr.Port) + defP = adr.Priority + } + } + } else { + addr = a[1] + } + } else { + addr = a[1] + } + } + } else { + a := strings.SplitN(host, ":", 2) + if len(a) == 1 { + addr += ":5222" } - } - a := strings.SplitN(host, ":", 2) - if len(a) == 1 { - addr += ":5222" } proxy := os.Getenv("HTTP_PROXY") From 2c5079ea28ef9a12f92f749d859931fed3061f29 Mon Sep 17 00:00:00 2001 From: Jesse Kuang Date: Wed, 9 Jan 2019 15:35:32 +0800 Subject: [PATCH 02/11] fix param of tlsconn.VerifyHostname --- xmpp.go | 56 +++++++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/xmpp.go b/xmpp.go index dbec361..bb58956 100644 --- a/xmpp.go +++ b/xmpp.go @@ -79,33 +79,9 @@ func containsIgnoreCase(s, substr string) bool { func connect(host, user, passwd string) (net.Conn, error) { addr := host - if strings.TrimSpace(host) == "" { - a := strings.SplitN(user, "@", 2) - if len(a) == 2 { - if cna, addrs, err := net.LookupSRV("xmpp-client", "tcp", a[1]); err == nil { - fmt.Printf("LookupSRV got: %s, %d SRVs\n", cna, len(addrs)) - if len(addrs) > 0 { - // default to first record - addr = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port) - defP := addrs[0].Priority - for _, adr := range addrs { - if adr.Priority < defP { - addr = fmt.Sprintf("%s:%d", adr.Target, adr.Port) - defP = adr.Priority - } - } - } else { - addr = a[1] - } - } else { - addr = a[1] - } - } - } else { - a := strings.SplitN(host, ":", 2) - if len(a) == 1 { - addr += ":5222" - } + a := strings.SplitN(addr, ":", 2) + if len(a) == 1 { + addr += ":5222" } proxy := os.Getenv("HTTP_PROXY") @@ -217,13 +193,35 @@ type Options struct { // NewClient establishes a new Client connection based on a set of Options. func (o Options) NewClient() (*Client, error) { host := o.Host + if strings.TrimSpace(host) == "" { + a := strings.SplitN(o.User, "@", 2) + if len(a) == 2 { + if _, addrs, err := net.LookupSRV("xmpp-client", "tcp", a[1]); err == nil { + if len(addrs) > 0 { + // default to first record + host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port) + defP := addrs[0].Priority + for _, adr := range addrs { + if adr.Priority < defP { + host = fmt.Sprintf("%s:%d", adr.Target, adr.Port) + defP = adr.Priority + } + } + } else { + host = a[1] + } + } else { + host = a[1] + } + } + } c, err := connect(host, o.User, o.Password) if err != nil { return nil, err } - if strings.LastIndex(o.Host, ":") > 0 { - host = host[:strings.LastIndex(o.Host, ":")] + if strings.LastIndex(host, ":") > 0 { + host = host[:strings.LastIndex(host, ":")] } client := new(Client) From c18873b8804e70e6a822d84359384e38ed82eb86 Mon Sep 17 00:00:00 2001 From: Jesse Kuang Date: Wed, 9 Jan 2019 17:35:00 +0800 Subject: [PATCH 03/11] fix query roster process subscription="remove" roster improve roster process --- .gitignore | 25 ++++++++++++++++++++ GNUmakefile | 31 +++++++++++++++++++++++++ _example/example.go | 46 +++++++++++++++++++++++++++++++++---- xmpp.go | 56 +++++++++++++++++++++++++++++++++------------ 4 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 .gitignore create mode 100644 GNUmakefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69a7a03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.swp +bin/ diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..00b2b5c --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,31 @@ +# +# Makefile for hookAPI +# +# switches: +# define the ones you want in the CFLAGS definition... +# +# TRACE - turn on tracing/debugging code +# +# +# +# + +# Version for distribution +VER=1_0r1 + +MAKEFILE=GNUmakefile + +# We Use Compact Memory Model + +all: bin/example + @[ -d bin ] || exit + +bin/example: _example/example.go xmpp.go + @[ -d bin ] || mkdir bin + go build -o $@ _example/example.go + @strip $@ || echo "example OK" + +clean: + +distclean: clean + @rm -rf bin diff --git a/_example/example.go b/_example/example.go index fbd0b12..d0da4a3 100644 --- a/_example/example.go +++ b/_example/example.go @@ -5,10 +5,11 @@ import ( "crypto/tls" "flag" "fmt" - "github.com/mattn/go-xmpp" + "github.com/kjx98/go-xmpp" "log" "os" "strings" + "time" ) var server = flag.String("server", "", "server") @@ -16,7 +17,7 @@ var username = flag.String("username", "", "username") var password = flag.String("password", "", "password") var status = flag.String("status", "xa", "status") var statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message") -var notls = flag.Bool("notls", false, "No TLS") +var notls = flag.Bool("notls", true, "No TLS") var debug = flag.Bool("debug", false, "debug output") var session = flag.Bool("session", false, "use server session") @@ -55,6 +56,7 @@ func main() { Debug: *debug, Session: *session, Status: *status, + Resource: "bot", StatusMessage: *statusMessage, } @@ -72,18 +74,52 @@ func main() { } switch v := chat.(type) { case xmpp.Chat: - fmt.Println(v.Remote, v.Text) + if v.Type == "roster" { + fmt.Println("roster", v.Roster) + } else { + for _, element := range v.OtherElem { + if element.XMLName.Space == "jabber:x:conference" { + // if not join + talk.JoinMUCNoHistory(v.Remote, "bot") + } + // composing, paused, active + if element.XMLName.Space == + "http://jabber.org/protocol/chatstates" && + element.XMLName.Local == "composing" { + fmt.Println(v.Remote, "is composing") + } + } + if strings.TrimSpace(v.Text) != "" { + fmt.Println(v.Remote, v.Text) + } + } case xmpp.Presence: - fmt.Println(v.From, v.Show) + fmt.Println("Presence:", v.From, v.Show, v.Type) + case xmpp.Roster, xmpp.Contact: + // TODO: update local roster + fmt.Println("Roster/Contact:", v) + case xmpp.IQ: + // ping ignore + if v.Type == "result" && v.ID == "c2s1" { + fmt.Printf("Got pong from %s to %s\n", v.From, v.To) + } + default: + fmt.Printf("def: %v\n", v) } } }() + // get roster first + talk.Roster() + talk.SendOrg("") for { in := bufio.NewReader(os.Stdin) line, err := in.ReadString('\n') if err != nil { continue } + if len(line) >= 4 && line[:4] == "quit" { + break + } line = strings.TrimRight(line, "\n") tokens := strings.SplitN(line, " ", 2) @@ -91,4 +127,6 @@ func main() { talk.Send(xmpp.Chat{Remote: tokens[0], Type: "chat", Text: tokens[1]}) } } + talk.SendOrg("`)) || bytes.Equal(bytes.TrimSpace(v.Query), []byte(``)) { + if v.Query.XMLName.Space == "urn:xmpp:ping" { + fmt.Println("clientIQ ping") err := c.SendResultPing(v.ID, v.From) if err != nil { return Chat{}, err } } - return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil + // + // TODO: shall we check XMLName.Local is "query"? + if (v.Type == "result" || v.Type == "set") && + v.Query.XMLName.Space == "jabber:iq:roster" { + var item rosterItem + var r Roster + vv := strings.Split(v.Query.InnerXML, "/>") + for _, ss := range vv { + if strings.TrimSpace(ss) == "" { + continue + } + ss += "/>" + if err := xml.Unmarshal([]byte(ss), &item); err != nil { + return nil, errors.New("unmarshal roster : " + err.Error()) + } else { + if item.Subscription == "remove" { + continue + } + r = append(r, Contact{item.Jid, item.Name, item.Group}) + } + } + return Chat{Type: "roster", Roster: r}, nil + } + return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, + Query: []byte(v.Query.InnerXML)}, nil } } } @@ -857,14 +882,15 @@ type clientPresence struct { type clientIQ struct { // info/query - XMLName xml.Name `xml:"jabber:client iq"` - From string `xml:"from,attr"` - ID string `xml:"id,attr"` - To string `xml:"to,attr"` - Type string `xml:"type,attr"` // error, get, result, set - Query []byte `xml:",innerxml"` - Error clientError - Bind bindBind + XMLName xml.Name `xml:"jabber:client iq"` + From string `xml:"from,attr"` + ID string `xml:"id,attr"` + To string `xml:"to,attr"` + Type string `xml:"type,attr"` // error, get, result, set + Query XMLElement `xml:",any"` + + Error clientError + Bind bindBind } type clientError struct { @@ -880,11 +906,11 @@ type clientQuery struct { } type rosterItem struct { - XMLName xml.Name `xml:"jabber:iq:roster item"` - Jid string `xml:",attr"` - Name string `xml:",attr"` - Subscription string `xml:",attr"` - Group []string + XMLName xml.Name `xml:"item"` + Jid string `xml:"jid,attr"` + Name string `xml:"name,attr"` + Subscription string `xml:"subscription,attr"` + Group []string `"xml:"group"` } // Scan XML token stream to find next StartElement. From 1e7b50b41ca89f802b3d79d2ac825e682b7d1b1d Mon Sep 17 00:00:00 2001 From: Jesse Kuang Date: Thu, 10 Jan 2019 14:46:50 +0800 Subject: [PATCH 04/11] add conference support --- _example/example.go | 6 +++++- xmpp.go | 24 +++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/_example/example.go b/_example/example.go index d0da4a3..c1f51f0 100644 --- a/_example/example.go +++ b/_example/example.go @@ -102,6 +102,9 @@ func main() { // ping ignore if v.Type == "result" && v.ID == "c2s1" { fmt.Printf("Got pong from %s to %s\n", v.From, v.To) + } else { + fmt.Printf("Got from %s to %s IQ, tag: (%v), query(%s)\n", + v.From, v.To, v.QueryName, v.Query) } default: fmt.Printf("def: %v\n", v) @@ -110,7 +113,8 @@ func main() { }() // get roster first talk.Roster() - talk.SendOrg("") + // test conf + talk.JoinMUCNoHistory("test@conference.jabb3r.org", "bot") for { in := bufio.NewReader(os.Stdin) line, err := in.ReadString('\n') diff --git a/xmpp.go b/xmpp.go index e704a47..b7c89f3 100644 --- a/xmpp.go +++ b/xmpp.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "io" + "log" "math/big" "net" "net/http" @@ -613,11 +614,12 @@ type Presence struct { } type IQ struct { - ID string - From string - To string - Type string - Query []byte + ID string + From string + To string + Type string + Query string + QueryName xml.Name } // Recv waits to receive the next XMPP stanza. @@ -656,7 +658,7 @@ func (c *Client) Recv() (stanza interface{}, err error) { case *clientIQ: // TODO check more strictly if v.Query.XMLName.Space == "urn:xmpp:ping" { - fmt.Println("clientIQ ping") + log.Print("clientIQ ping") err := c.SendResultPing(v.ID, v.From) if err != nil { return Chat{}, err @@ -664,10 +666,14 @@ func (c *Client) Recv() (stanza interface{}, err error) { } // // TODO: shall we check XMLName.Local is "query"? - if (v.Type == "result" || v.Type == "set") && - v.Query.XMLName.Space == "jabber:iq:roster" { + if v.Query.XMLName.Space == "jabber:iq:roster" { var item rosterItem var r Roster + if v.Type != "result" && v.Type != "set" { + // only result and set processed + return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, + Query: v.Query.InnerXML, QueryName: v.Query.XMLName}, nil + } vv := strings.Split(v.Query.InnerXML, "/>") for _, ss := range vv { if strings.TrimSpace(ss) == "" { @@ -686,7 +692,7 @@ func (c *Client) Recv() (stanza interface{}, err error) { return Chat{Type: "roster", Roster: r}, nil } return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, - Query: []byte(v.Query.InnerXML)}, nil + Query: v.Query.InnerXML, QueryName: v.Query.XMLName}, nil } } } From 224305b3efdd57b61661cb1abeb0a9507fd6837b Mon Sep 17 00:00:00 2001 From: Jesse Kuang <2829915232@qq.com> Date: Thu, 10 Jan 2019 20:43:44 +0800 Subject: [PATCH 05/11] test with local prosody without conferenc --- _example/example.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/_example/example.go b/_example/example.go index c1f51f0..d3f81b8 100644 --- a/_example/example.go +++ b/_example/example.go @@ -45,6 +45,10 @@ func main() { ServerName: serverName(*server), InsecureSkipVerify: false, } + } else { + xmpp.DefaultConfig = tls.Config{ + InsecureSkipVerify: true, + } } var talk *xmpp.Client @@ -114,7 +118,7 @@ func main() { // get roster first talk.Roster() // test conf - talk.JoinMUCNoHistory("test@conference.jabb3r.org", "bot") + //talk.JoinMUCNoHistory("test@conference.jabb3r.org", "bot") for { in := bufio.NewReader(os.Stdin) line, err := in.ReadString('\n') From 66c008d79892996e0263086c3acf1f6f6b32147d Mon Sep 17 00:00:00 2001 From: Jesse Kuang <2829915232@qq.com> Date: Thu, 10 Jan 2019 22:53:01 +0800 Subject: [PATCH 06/11] add iq:version, iq:last --- xmpp.go | 21 ++++++++++++++++----- xmpp_version.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 xmpp_version.go diff --git a/xmpp.go b/xmpp.go index b7c89f3..bb0b454 100644 --- a/xmpp.go +++ b/xmpp.go @@ -62,10 +62,11 @@ func getCookie() Cookie { // Client holds XMPP connection opitons type Client struct { - conn net.Conn // connection to server - jid string // Jabber ID for our connection - domain string - p *xml.Decoder + conn net.Conn // connection to server + jid string // Jabber ID for our connection + domain string + loginTime time.Time + p *xml.Decoder } func (c *Client) JID() string { @@ -257,6 +258,7 @@ func (o Options) NewClient() (*Client, error) { client.Close() return nil, err } + client.loginTime = time.Now() return client, nil } @@ -666,7 +668,16 @@ func (c *Client) Recv() (stanza interface{}, err error) { } // // TODO: shall we check XMLName.Local is "query"? - if v.Query.XMLName.Space == "jabber:iq:roster" { + switch v.Query.XMLName.Space { + case "jabber:iq:version": + if err := c.SendVersion(v.ID, v.From, v.To); err != nil { + return Chat{}, err + } + case "jabber:iq:last": + if err := c.SendIQLast(v.ID, v.From, v.To); err != nil { + return Chat{}, err + } + case "jabber:iq:roster": var item rosterItem var r Roster if v.Type != "result" && v.Type != "set" { diff --git a/xmpp_version.go b/xmpp_version.go new file mode 100644 index 0000000..b13f74e --- /dev/null +++ b/xmpp_version.go @@ -0,0 +1,32 @@ +package xmpp + +import ( + "fmt" + "runtime" + "time" +) + +func (c *Client) SendVersion(id, toServer, fromU string) error { + _, err := fmt.Fprintf(c.conn, "", xmlEscape(fromU), xmlEscape(toServer), xmlEscape(id)) + if err != nil { + return err + } + _, err = fmt.Fprintf(c.conn, ""+ + "go-xmpp0.1%s"+ + "\n", runtime.GOOS) + return err +} + +func (c *Client) SendIQLast(id, toServer, fromU string) error { + _, err := fmt.Fprintf(c.conn, "\n", xmlEscape(fromU), + xmlEscape(toServer), xmlEscape(id)) + if err != nil { + return err + } + tt := time.Now().Sub(c.loginTime) + _, err = fmt.Fprintf(c.conn, "Working\n", int(tt.Seconds())) + return err +} From 51b558cd2cef8c66321eee4b2c2ede5af5df574b Mon Sep 17 00:00:00 2001 From: Jesse Kuang <2829915232@qq.com> Date: Thu, 10 Jan 2019 23:36:50 +0800 Subject: [PATCH 07/11] add urn:xmpp:time; now response jabber GetInfo --- GNUmakefile | 2 +- xmpp.go | 4 ++++ xmpp_version.go | 23 ++++++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 00b2b5c..25808b9 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -20,7 +20,7 @@ MAKEFILE=GNUmakefile all: bin/example @[ -d bin ] || exit -bin/example: _example/example.go xmpp.go +bin/example: _example/example.go xmpp.go xmpp_version.go @[ -d bin ] || mkdir bin go build -o $@ _example/example.go @strip $@ || echo "example OK" diff --git a/xmpp.go b/xmpp.go index bb0b454..a2e6956 100644 --- a/xmpp.go +++ b/xmpp.go @@ -677,6 +677,10 @@ func (c *Client) Recv() (stanza interface{}, err error) { if err := c.SendIQLast(v.ID, v.From, v.To); err != nil { return Chat{}, err } + case "urn:xmpp:time": + if err := c.SendIQtime(v.ID, v.From, v.To); err != nil { + return Chat{}, err + } case "jabber:iq:roster": var item rosterItem var r Roster diff --git a/xmpp_version.go b/xmpp_version.go index b13f74e..05108ef 100644 --- a/xmpp_version.go +++ b/xmpp_version.go @@ -19,14 +19,23 @@ func (c *Client) SendVersion(id, toServer, fromU string) error { } func (c *Client) SendIQLast(id, toServer, fromU string) error { - _, err := fmt.Fprintf(c.conn, "\n", xmlEscape(fromU), - xmlEscape(toServer), xmlEscape(id)) - if err != nil { - return err - } + ss := fmt.Sprintf("\n", xmlEscape(fromU), xmlEscape(toServer), xmlEscape(id)) tt := time.Now().Sub(c.loginTime) - _, err = fmt.Fprintf(c.conn, "Working\n", int(tt.Seconds())) + _, err := fmt.Fprint(c.conn, ss) + return err +} + +func (c *Client) SendIQtime(id, toServer, fromU string) error { + ss := fmt.Sprintf("\n", xmlEscape(fromU), xmlEscape(toServer), xmlEscape(id)) + tt := time.Now() + zoneN, _ := tt.Zone() + ss += fmt.Sprintf("\n", zoneN, + tt.UTC().Format("2006-01-02T15:03:04Z")) + _, err := fmt.Fprint(c.conn, ss) return err } From 5709ddefa8b1a4cd50863bffe272b89c6b1d306b Mon Sep 17 00:00:00 2001 From: Jesse Kuang Date: Fri, 11 Jan 2019 11:20:54 +0800 Subject: [PATCH 08/11] move IQ stuff to xmpp_get_info and example --- GNUmakefile | 2 +- _example/example.go | 96 ++++++++++++++++++++++++++++++++++++++++++++- xmpp.go | 75 +++++++++-------------------------- xmpp_get_info.go | 30 ++++++++++++++ xmpp_version.go | 41 ------------------- 5 files changed, 143 insertions(+), 101 deletions(-) create mode 100644 xmpp_get_info.go delete mode 100644 xmpp_version.go diff --git a/GNUmakefile b/GNUmakefile index 25808b9..be4b08c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -20,7 +20,7 @@ MAKEFILE=GNUmakefile all: bin/example @[ -d bin ] || exit -bin/example: _example/example.go xmpp.go xmpp_version.go +bin/example: _example/example.go xmpp.go xmpp_get_info.go @[ -d bin ] || mkdir bin go build -o $@ _example/example.go @strip $@ || echo "example OK" diff --git a/_example/example.go b/_example/example.go index d3f81b8..844de9f 100644 --- a/_example/example.go +++ b/_example/example.go @@ -3,11 +3,13 @@ package main import ( "bufio" "crypto/tls" + "encoding/xml" "flag" "fmt" "github.com/kjx98/go-xmpp" "log" "os" + "runtime" "strings" "time" ) @@ -21,6 +23,21 @@ var notls = flag.Bool("notls", true, "No TLS") var debug = flag.Bool("debug", false, "debug output") var session = flag.Bool("session", false, "use server session") +type rosterItem struct { + XMLName xml.Name `xml:"item"` + Jid string `xml:"jid,attr"` + Name string `xml:"name,attr"` + Subscription string `xml:"subscription,attr"` + Group []string `"xml:"group"` +} + +type contactType struct { + Jid string + Name string + Subscription string + Online bool +} + func serverName(host string) string { return strings.Split(host, ":")[0] } @@ -69,6 +86,7 @@ func main() { if err != nil { log.Fatal(err) } + loginTime := time.Now() go func() { for { @@ -98,12 +116,84 @@ func main() { } } case xmpp.Presence: - fmt.Println("Presence:", v.From, v.Show, v.Type) + switch v.Type { + case "subscribe": + // Approve all subscription + fmt.Printf("Presence: %s Approve %s subscription\n", + v.To, v.From) + talk.ApproveSubscription(v.From) + talk.RequestSubscription(v.From) + case "unsubscribe": + fmt.Printf("Presence: %s Revoke %s subscription\n", + v.To, v.From) + talk.RevokeSubscription(v.From) + default: + fmt.Printf("Presence: %s %s Type(%s)\n", v.From, v.Show, v.Type) + } case xmpp.Roster, xmpp.Contact: // TODO: update local roster fmt.Println("Roster/Contact:", v) case xmpp.IQ: // ping ignore + switch v.QueryName.Space { + case "jabber:iq:version": + if err := talk.RawVersion(v.To, v.From, v.ID, + "0.1", runtime.GOOS); err != nil { + fmt.Println("RawVersion:", err) + } + continue + case "jabber:iq:last": + tt := time.Now().Sub(loginTime) + last := int(tt.Seconds()) + if err := talk.RawLast(v.To, v.From, v.ID, last); err != nil { + fmt.Println("RawLast:", err) + } + continue + case "urn:xmpp:time": + if err := talk.RawIQtime(v.To, v.From, v.ID); err != nil { + fmt.Println("RawIQtime:", err) + } + continue + case "jabber:iq:roster": + var item rosterItem + if v.Type != "result" && v.Type != "set" { + // only result and set processed + fmt.Println("jabber:iq:roster, type:", v.Type) + continue + } + vv := strings.Split(v.Query, "/>") + for _, ss := range vv { + if strings.TrimSpace(ss) == "" { + continue + } + ss += "/>" + if err := xml.Unmarshal([]byte(ss), &item); err != nil { + fmt.Println("unmarshal roster : ", err) + continue + } else { + if item.Subscription == "remove" { + continue + } + /* + //may loop whiel presence is unavailable + if item.Subscription == "from" { + fmt.Printf("%s Approve %s subscription\n", + v.To, item.Jid) + talk.RequestSubscription(item.Jid) + } + */ + fmt.Printf("roster item %s subscription(%s), %v\n", + item.Jid, item.Subscription, item.Group) + if v.Type == "set" && item.Subscription == "both" { + // shall we check presence unavailable + pr := xmpp.Presence{From: v.To, To: item.Jid, + Show: "xa"} + talk.SendPresence(pr) + } + } + } + continue + } if v.Type == "result" && v.ID == "c2s1" { fmt.Printf("Got pong from %s to %s\n", v.From, v.To) } else { @@ -117,8 +207,10 @@ func main() { }() // get roster first talk.Roster() + //talk.RevokeSubscription("wkpb@hot-chilli.net") + //talk.SendOrg("") // test conf - //talk.JoinMUCNoHistory("test@conference.jabb3r.org", "bot") + talk.JoinMUCNoHistory("test@conference.jabb3r.org", "bot") for { in := bufio.NewReader(os.Stdin) line, err := in.ReadString('\n') diff --git a/xmpp.go b/xmpp.go index a2e6956..57b7f06 100644 --- a/xmpp.go +++ b/xmpp.go @@ -24,7 +24,6 @@ import ( "errors" "fmt" "io" - "log" "math/big" "net" "net/http" @@ -62,11 +61,10 @@ func getCookie() Cookie { // Client holds XMPP connection opitons type Client struct { - conn net.Conn // connection to server - jid string // Jabber ID for our connection - domain string - loginTime time.Time - p *xml.Decoder + conn net.Conn // connection to server + jid string // Jabber ID for our connection + domain string + p *xml.Decoder } func (c *Client) JID() string { @@ -81,7 +79,13 @@ func containsIgnoreCase(s, substr string) bool { func connect(host, user, passwd string) (net.Conn, error) { addr := host - a := strings.SplitN(addr, ":", 2) + if strings.TrimSpace(host) == "" { + a := strings.SplitN(user, "@", 2) + if len(a) == 2 { + addr = a[1] + } + } + a := strings.SplitN(host, ":", 2) if len(a) == 1 { addr += ":5222" } @@ -258,7 +262,6 @@ func (o Options) NewClient() (*Client, error) { client.Close() return nil, err } - client.loginTime = time.Now() return client, nil } @@ -660,52 +663,11 @@ func (c *Client) Recv() (stanza interface{}, err error) { case *clientIQ: // TODO check more strictly if v.Query.XMLName.Space == "urn:xmpp:ping" { - log.Print("clientIQ ping") err := c.SendResultPing(v.ID, v.From) if err != nil { return Chat{}, err } } - // - // TODO: shall we check XMLName.Local is "query"? - switch v.Query.XMLName.Space { - case "jabber:iq:version": - if err := c.SendVersion(v.ID, v.From, v.To); err != nil { - return Chat{}, err - } - case "jabber:iq:last": - if err := c.SendIQLast(v.ID, v.From, v.To); err != nil { - return Chat{}, err - } - case "urn:xmpp:time": - if err := c.SendIQtime(v.ID, v.From, v.To); err != nil { - return Chat{}, err - } - case "jabber:iq:roster": - var item rosterItem - var r Roster - if v.Type != "result" && v.Type != "set" { - // only result and set processed - return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, - Query: v.Query.InnerXML, QueryName: v.Query.XMLName}, nil - } - vv := strings.Split(v.Query.InnerXML, "/>") - for _, ss := range vv { - if strings.TrimSpace(ss) == "" { - continue - } - ss += "/>" - if err := xml.Unmarshal([]byte(ss), &item); err != nil { - return nil, errors.New("unmarshal roster : " + err.Error()) - } else { - if item.Subscription == "remove" { - continue - } - r = append(r, Contact{item.Jid, item.Name, item.Group}) - } - } - return Chat{Type: "roster", Roster: r}, nil - } return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query.InnerXML, QueryName: v.Query.XMLName}, nil } @@ -909,9 +871,8 @@ type clientIQ struct { To string `xml:"to,attr"` Type string `xml:"type,attr"` // error, get, result, set Query XMLElement `xml:",any"` - - Error clientError - Bind bindBind + Error clientError + Bind bindBind } type clientError struct { @@ -927,11 +888,11 @@ type clientQuery struct { } type rosterItem struct { - XMLName xml.Name `xml:"item"` - Jid string `xml:"jid,attr"` - Name string `xml:"name,attr"` - Subscription string `xml:"subscription,attr"` - Group []string `"xml:"group"` + XMLName xml.Name `xml:"jabber:iq:roster item"` + Jid string `xml:",attr"` + Name string `xml:",attr"` + Subscription string `xml:",attr"` + Group []string } // Scan XML token stream to find next StartElement. diff --git a/xmpp_get_info.go b/xmpp_get_info.go new file mode 100644 index 0000000..93f581c --- /dev/null +++ b/xmpp_get_info.go @@ -0,0 +1,30 @@ +package xmpp + +import ( + "fmt" + "time" +) + +func (c *Client) RawVersion(from, to, id, version, osName string) error { + body := "go-xmpp" + version + "" + + osName + "" + _, err := c.RawInformationQuery(from, to, id, "result", "jabber:iq:version", + body) + return err +} + +func (c *Client) RawLast(from, to, id string, last int) error { + body := fmt.Sprintf("Working", last) + _, err := c.RawInformation(from, to, id, "result", body) + return err +} + +func (c *Client) RawIQtime(from, to, id string) error { + tt := time.Now() + zone, _ := tt.Zone() + body := fmt.Sprintf("", zone, tt.UTC().Format("2006-01-02T15:03:04Z")) + _, err := c.RawInformation(from, to, id, "result", body) + return err +} diff --git a/xmpp_version.go b/xmpp_version.go deleted file mode 100644 index 05108ef..0000000 --- a/xmpp_version.go +++ /dev/null @@ -1,41 +0,0 @@ -package xmpp - -import ( - "fmt" - "runtime" - "time" -) - -func (c *Client) SendVersion(id, toServer, fromU string) error { - _, err := fmt.Fprintf(c.conn, "", xmlEscape(fromU), xmlEscape(toServer), xmlEscape(id)) - if err != nil { - return err - } - _, err = fmt.Fprintf(c.conn, ""+ - "go-xmpp0.1%s"+ - "\n", runtime.GOOS) - return err -} - -func (c *Client) SendIQLast(id, toServer, fromU string) error { - ss := fmt.Sprintf("\n", xmlEscape(fromU), xmlEscape(toServer), xmlEscape(id)) - tt := time.Now().Sub(c.loginTime) - ss += fmt.Sprintf("Working\n", int(tt.Seconds())) - _, err := fmt.Fprint(c.conn, ss) - return err -} - -func (c *Client) SendIQtime(id, toServer, fromU string) error { - ss := fmt.Sprintf("\n", xmlEscape(fromU), xmlEscape(toServer), xmlEscape(id)) - tt := time.Now() - zoneN, _ := tt.Zone() - ss += fmt.Sprintf("\n", zoneN, - tt.UTC().Format("2006-01-02T15:03:04Z")) - _, err := fmt.Fprint(c.conn, ss) - return err -} From a79a0e59ef612625c500d48bb2ef026f07ab927d Mon Sep 17 00:00:00 2001 From: Jesse Kuang Date: Fri, 11 Jan 2019 12:02:02 +0800 Subject: [PATCH 09/11] remove GNUmakefile .gitignore mv new example.go to other repo --- .gitignore | 25 -------- GNUmakefile | 31 ---------- _example/example.go | 148 ++------------------------------------------ 3 files changed, 5 insertions(+), 199 deletions(-) delete mode 100644 .gitignore delete mode 100644 GNUmakefile diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 69a7a03..0000000 --- a/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.swp -bin/ diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index be4b08c..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,31 +0,0 @@ -# -# Makefile for hookAPI -# -# switches: -# define the ones you want in the CFLAGS definition... -# -# TRACE - turn on tracing/debugging code -# -# -# -# - -# Version for distribution -VER=1_0r1 - -MAKEFILE=GNUmakefile - -# We Use Compact Memory Model - -all: bin/example - @[ -d bin ] || exit - -bin/example: _example/example.go xmpp.go xmpp_get_info.go - @[ -d bin ] || mkdir bin - go build -o $@ _example/example.go - @strip $@ || echo "example OK" - -clean: - -distclean: clean - @rm -rf bin diff --git a/_example/example.go b/_example/example.go index 844de9f..136af5a 100644 --- a/_example/example.go +++ b/_example/example.go @@ -3,41 +3,23 @@ package main import ( "bufio" "crypto/tls" - "encoding/xml" "flag" "fmt" - "github.com/kjx98/go-xmpp" + "github.com/mattn/go-xmpp" "log" "os" - "runtime" "strings" - "time" ) -var server = flag.String("server", "", "server") +var server = flag.String("server", "talk.google.com:443", "server") var username = flag.String("username", "", "username") var password = flag.String("password", "", "password") var status = flag.String("status", "xa", "status") var statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message") -var notls = flag.Bool("notls", true, "No TLS") +var notls = flag.Bool("notls", false, "No TLS") var debug = flag.Bool("debug", false, "debug output") var session = flag.Bool("session", false, "use server session") -type rosterItem struct { - XMLName xml.Name `xml:"item"` - Jid string `xml:"jid,attr"` - Name string `xml:"name,attr"` - Subscription string `xml:"subscription,attr"` - Group []string `"xml:"group"` -} - -type contactType struct { - Jid string - Name string - Subscription string - Online bool -} - func serverName(host string) string { return strings.Split(host, ":")[0] } @@ -62,10 +44,6 @@ func main() { ServerName: serverName(*server), InsecureSkipVerify: false, } - } else { - xmpp.DefaultConfig = tls.Config{ - InsecureSkipVerify: true, - } } var talk *xmpp.Client @@ -77,7 +55,6 @@ func main() { Debug: *debug, Session: *session, Status: *status, - Resource: "bot", StatusMessage: *statusMessage, } @@ -86,7 +63,6 @@ func main() { if err != nil { log.Fatal(err) } - loginTime := time.Now() go func() { for { @@ -96,130 +72,18 @@ func main() { } switch v := chat.(type) { case xmpp.Chat: - if v.Type == "roster" { - fmt.Println("roster", v.Roster) - } else { - for _, element := range v.OtherElem { - if element.XMLName.Space == "jabber:x:conference" { - // if not join - talk.JoinMUCNoHistory(v.Remote, "bot") - } - // composing, paused, active - if element.XMLName.Space == - "http://jabber.org/protocol/chatstates" && - element.XMLName.Local == "composing" { - fmt.Println(v.Remote, "is composing") - } - } - if strings.TrimSpace(v.Text) != "" { - fmt.Println(v.Remote, v.Text) - } - } + fmt.Println(v.Remote, v.Text) case xmpp.Presence: - switch v.Type { - case "subscribe": - // Approve all subscription - fmt.Printf("Presence: %s Approve %s subscription\n", - v.To, v.From) - talk.ApproveSubscription(v.From) - talk.RequestSubscription(v.From) - case "unsubscribe": - fmt.Printf("Presence: %s Revoke %s subscription\n", - v.To, v.From) - talk.RevokeSubscription(v.From) - default: - fmt.Printf("Presence: %s %s Type(%s)\n", v.From, v.Show, v.Type) - } - case xmpp.Roster, xmpp.Contact: - // TODO: update local roster - fmt.Println("Roster/Contact:", v) - case xmpp.IQ: - // ping ignore - switch v.QueryName.Space { - case "jabber:iq:version": - if err := talk.RawVersion(v.To, v.From, v.ID, - "0.1", runtime.GOOS); err != nil { - fmt.Println("RawVersion:", err) - } - continue - case "jabber:iq:last": - tt := time.Now().Sub(loginTime) - last := int(tt.Seconds()) - if err := talk.RawLast(v.To, v.From, v.ID, last); err != nil { - fmt.Println("RawLast:", err) - } - continue - case "urn:xmpp:time": - if err := talk.RawIQtime(v.To, v.From, v.ID); err != nil { - fmt.Println("RawIQtime:", err) - } - continue - case "jabber:iq:roster": - var item rosterItem - if v.Type != "result" && v.Type != "set" { - // only result and set processed - fmt.Println("jabber:iq:roster, type:", v.Type) - continue - } - vv := strings.Split(v.Query, "/>") - for _, ss := range vv { - if strings.TrimSpace(ss) == "" { - continue - } - ss += "/>" - if err := xml.Unmarshal([]byte(ss), &item); err != nil { - fmt.Println("unmarshal roster : ", err) - continue - } else { - if item.Subscription == "remove" { - continue - } - /* - //may loop whiel presence is unavailable - if item.Subscription == "from" { - fmt.Printf("%s Approve %s subscription\n", - v.To, item.Jid) - talk.RequestSubscription(item.Jid) - } - */ - fmt.Printf("roster item %s subscription(%s), %v\n", - item.Jid, item.Subscription, item.Group) - if v.Type == "set" && item.Subscription == "both" { - // shall we check presence unavailable - pr := xmpp.Presence{From: v.To, To: item.Jid, - Show: "xa"} - talk.SendPresence(pr) - } - } - } - continue - } - if v.Type == "result" && v.ID == "c2s1" { - fmt.Printf("Got pong from %s to %s\n", v.From, v.To) - } else { - fmt.Printf("Got from %s to %s IQ, tag: (%v), query(%s)\n", - v.From, v.To, v.QueryName, v.Query) - } - default: - fmt.Printf("def: %v\n", v) + fmt.Println(v.From, v.Show) } } }() - // get roster first - talk.Roster() - //talk.RevokeSubscription("wkpb@hot-chilli.net") - //talk.SendOrg("") - // test conf - talk.JoinMUCNoHistory("test@conference.jabb3r.org", "bot") for { in := bufio.NewReader(os.Stdin) line, err := in.ReadString('\n') if err != nil { continue } - if len(line) >= 4 && line[:4] == "quit" { - break - } line = strings.TrimRight(line, "\n") tokens := strings.SplitN(line, " ", 2) @@ -227,6 +91,4 @@ func main() { talk.Send(xmpp.Chat{Remote: tokens[0], Type: "chat", Text: tokens[1]}) } } - talk.SendOrg(" Date: Sat, 12 Jan 2019 14:46:18 +0800 Subject: [PATCH 10/11] mv xmpp_get_info to other repo --- xmpp_get_info.go | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 xmpp_get_info.go diff --git a/xmpp_get_info.go b/xmpp_get_info.go deleted file mode 100644 index 93f581c..0000000 --- a/xmpp_get_info.go +++ /dev/null @@ -1,30 +0,0 @@ -package xmpp - -import ( - "fmt" - "time" -) - -func (c *Client) RawVersion(from, to, id, version, osName string) error { - body := "go-xmpp" + version + "" + - osName + "" - _, err := c.RawInformationQuery(from, to, id, "result", "jabber:iq:version", - body) - return err -} - -func (c *Client) RawLast(from, to, id string, last int) error { - body := fmt.Sprintf("Working", last) - _, err := c.RawInformation(from, to, id, "result", body) - return err -} - -func (c *Client) RawIQtime(from, to, id string) error { - tt := time.Now() - zone, _ := tt.Zone() - body := fmt.Sprintf("", zone, tt.UTC().Format("2006-01-02T15:03:04Z")) - _, err := c.RawInformation(from, to, id, "result", body) - return err -} From ef6a1a617c331c8b5b802646e575db1399563a63 Mon Sep 17 00:00:00 2001 From: Jesse Kuang Date: Tue, 15 Jan 2019 10:53:08 +0800 Subject: [PATCH 11/11] keep IQ struct unchange --- xmpp.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/xmpp.go b/xmpp.go index 57b7f06..4634e47 100644 --- a/xmpp.go +++ b/xmpp.go @@ -619,12 +619,11 @@ type Presence struct { } type IQ struct { - ID string - From string - To string - Type string - Query string - QueryName xml.Name + ID string + From string + To string + Type string + Query []byte } // Recv waits to receive the next XMPP stanza. @@ -668,8 +667,15 @@ func (c *Client) Recv() (stanza interface{}, err error) { return Chat{}, err } } - return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, - Query: v.Query.InnerXML, QueryName: v.Query.XMLName}, nil + if v.Query.XMLName.Local == "" { + return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type}, nil + } else if res, err := xml.Marshal(v.Query); err != nil { + // should never occur + return Chat{}, err + } else { + return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, + Query: res}, nil + } } } }