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 -}