diff --git a/_example/example.go b/_example/example.go index 136af5a..1639714 100644 --- a/_example/example.go +++ b/_example/example.go @@ -40,7 +40,7 @@ func main() { } if !*notls { - xmpp.DefaultConfig = tls.Config{ + xmpp.DefaultConfig = &tls.Config{ ServerName: serverName(*server), InsecureSkipVerify: false, } diff --git a/xmpp.go b/xmpp.go index 051c11a..4dd48a6 100644 --- a/xmpp.go +++ b/xmpp.go @@ -1096,6 +1096,29 @@ func (c *Client) Recv() (stanza interface{}, err error) { return handleAvatarMetadata(p.Items[0].Body, v }*/ + case "searchAccount1": + if v.Query.XMLName.Space == nsSearch { + var accountQuery clientSearchAccountQuery + err := xml.Unmarshal(v.InnerXML, &accountQuery) + if err != nil { + return SearchAccountResult{}, err + } + + return SearchAccountResult{ + Jid: v.From, + Accounts: clientSearchAccountItemToReturn(accountQuery.Items), + }, nil + } + case "requestLastActivity1": + if v.Query.XMLName.Space == nsLastActivity { + var lastActivity lastActivity + err := xml.Unmarshal(v.InnerXML, &lastActivity) + if err != nil { + return LastActivityResult{}, err + } + + return handleLastActivityResult(v.From, lastActivity) + } default: res, err := xml.Marshal(v.Query) if err != nil { diff --git a/xmpp_account.go b/xmpp_account.go new file mode 100644 index 0000000..2a78483 --- /dev/null +++ b/xmpp_account.go @@ -0,0 +1,150 @@ +package xmpp + +import ( + "encoding/xml" + "fmt" + "strconv" + "strings" +) + +const ( + nsRegister = "jabber:iq:register" + nsSearch = "jabber:iq:search" + nsLastActivity = "jabber:iq:last" +) + +type clientSearchAccountItem struct { + Jid string `xml:"jid,attr"` + First string `xml:"first"` + Last string `xml:"last"` + Nick string `xml:"nick"` + Email string `xml:"email"` +} + +type clientSearchAccountQuery struct { + XMLName xml.Name `xml:"query"` + Xmlns string `xml:"xmlns,attr"` + Items []clientSearchAccountItem `xml:"item"` +} + +// SearchAccountResultItem represent search account item result +type SearchAccountResultItem struct { + Jid string + FirstName string + LastName string + NickName string + Email string +} + +// SearchAccountResult represent search account item result +type SearchAccountResult struct { + Jid string + Accounts []SearchAccountResultItem +} + +type lastActivity struct { + XMLName xml.Name `xml:"query"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + Seconds string `xml:"seconds,attr"` +} + +// LastActivityResult represent last activity response +// when current entity session is authorized to view the user's presence information +type LastActivityResult struct { + From string + Text string + LastActiveSeconds int +} + +func clientSearchAccountItemToReturn(accounts []clientSearchAccountItem) []SearchAccountResultItem { + var ret []SearchAccountResultItem + for _, account := range accounts { + ret = append(ret, SearchAccountResultItem{ + Jid: account.Jid, + FirstName: account.First, + LastName: account.Last, + NickName: account.Nick, + Email: account.Email, + }) + } + + return ret +} + +func handleLastActivityResult(from string, lastActivity lastActivity) (LastActivityResult, error) { + lastActiveSeconds, err := strconv.Atoi(lastActivity.Seconds) + if err != nil { + return LastActivityResult{}, err + } + + return LastActivityResult{ + From: from, + Text: lastActivity.Text, + LastActiveSeconds: lastActiveSeconds, + }, nil +} + +func buildAccountAttr(username string, password string, attributes map[string]string) string { + var attrBuilder strings.Builder + attrBuilder.WriteString(fmt.Sprintf("%s", xmlEscape(username))) + attrBuilder.WriteString(fmt.Sprintf("%s", xmlEscape(password))) + + if attributes != nil { + for k, v := range attributes { + attrBuilder.WriteString(fmt.Sprintf("<%s>%s", k, v, k)) + } + } + + return attrBuilder.String() +} + +// CreateAccount Creates a new account using the specified username, password and extra attributes +func (c *Client) CreateAccount(username string, password string, attributes map[string]string) error { + attrBuilder := buildAccountAttr(username, password, attributes) + const xmlIQ = "%s" + _, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, nsRegister, attrBuilder) + return err +} + +// ChangePassword enables a user to change his or her password with a server or service. +// With the user's username parameter which will change the password and new password +func (c *Client) ChangePassword(username string, newPassword string) error { + attrBuilder := buildAccountAttr(username, newPassword, nil) + const xmlIQ = "%s" + _, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(c.domain), nsRegister, attrBuilder) + return err +} + +// RemoveAccount cancel or delete the current session registration +// with a host by sending a element in an IQ set. +func (c *Client) RemoveAccount() error { + from := c.jid + const xmlIQ = "" + _, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), nsRegister) + return err +} + +// SearchAccount search information repositories on the Jabber network. +// searchServiceName is the Search Service Properties Name from your server. +func (c *Client) SearchAccount(searchServiceName, fieldName, fieldValue string) error { + from := c.jid + searchService := fmt.Sprintf("%s.%s", searchServiceName, c.domain) + searchQuery := fmt.Sprintf("<%s>%s", fieldName, xmlEscape(fieldValue), fieldName) + const xmlIQ = "%s\n" + _, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), searchService, nsSearch, searchQuery) + return err +} + +// RequestLastActivity request last activity information regarding another entity +func (c *Client) RequestLastActivity(to string) error { + targetEntity := strings.SplitN(to, "@", 2) + if len(targetEntity) < 2 { + to = fmt.Sprintf("%s@%s", to, c.domain) + } + + from := c.jid + const xmlIQ = "" + _, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), nsLastActivity) + return err +}