| // Copyright 2011 Google Inc. All rights reserved. |
| // Use of this source code is governed by the Apache 2.0 |
| // license that can be found in the LICENSE file. |
| |
| /* |
| Package xmpp provides the means to send and receive instant messages |
| to and from users of XMPP-compatible services. |
| |
| To send a message, |
| |
| m := &xmpp.Message{ |
| To: []string{"[email protected]"}, |
| Body: `Hi! How's the carrot?`, |
| } |
| err := m.Send(c) |
| |
| To receive messages, |
| |
| func init() { |
| xmpp.Handle(handleChat) |
| } |
| |
| func handleChat(c context.Context, m *xmpp.Message) { |
| // ... |
| } |
| */ |
| package xmpp // import "google.golang.org/appengine/xmpp" |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "net/http" |
| |
| "google.golang.org/appengine" |
| "google.golang.org/appengine/internal" |
| pb "google.golang.org/appengine/internal/xmpp" |
| ) |
| |
| // Message represents an incoming chat message. |
| type Message struct { |
| // Sender is the JID of the sender. |
| // Optional for outgoing messages. |
| Sender string |
| |
| // To is the intended recipients of the message. |
| // Incoming messages will have exactly one element. |
| To []string |
| |
| // Body is the body of the message. |
| Body string |
| |
| // Type is the message type, per RFC 3921. |
| // It defaults to "chat". |
| Type string |
| |
| // RawXML is whether the body contains raw XML. |
| RawXML bool |
| } |
| |
| // Presence represents an outgoing presence update. |
| type Presence struct { |
| // Sender is the JID (optional). |
| Sender string |
| |
| // The intended recipient of the presence update. |
| To string |
| |
| // Type, per RFC 3921 (optional). Defaults to "available". |
| Type string |
| |
| // State of presence (optional). |
| // Valid values: "away", "chat", "xa", "dnd" (RFC 3921). |
| State string |
| |
| // Free text status message (optional). |
| Status string |
| } |
| |
| var ( |
| ErrPresenceUnavailable = errors.New("xmpp: presence unavailable") |
| ErrInvalidJID = errors.New("xmpp: invalid JID") |
| ) |
| |
| // Handle arranges for f to be called for incoming XMPP messages. |
| // Only messages of type "chat" or "normal" will be handled. |
| func Handle(f func(c context.Context, m *Message)) { |
| http.HandleFunc("/_ah/xmpp/message/chat/", func(_ http.ResponseWriter, r *http.Request) { |
| f(appengine.NewContext(r), &Message{ |
| Sender: r.FormValue("from"), |
| To: []string{r.FormValue("to")}, |
| Body: r.FormValue("body"), |
| }) |
| }) |
| } |
| |
| // Send sends a message. |
| // If any failures occur with specific recipients, the error will be an appengine.MultiError. |
| func (m *Message) Send(c context.Context) error { |
| req := &pb.XmppMessageRequest{ |
| Jid: m.To, |
| Body: &m.Body, |
| RawXml: &m.RawXML, |
| } |
| if m.Type != "" && m.Type != "chat" { |
| req.Type = &m.Type |
| } |
| if m.Sender != "" { |
| req.FromJid = &m.Sender |
| } |
| res := &pb.XmppMessageResponse{} |
| if err := internal.Call(c, "xmpp", "SendMessage", req, res); err != nil { |
| return err |
| } |
| |
| if len(res.Status) != len(req.Jid) { |
| return fmt.Errorf("xmpp: sent message to %d JIDs, but only got %d statuses back", len(req.Jid), len(res.Status)) |
| } |
| me, any := make(appengine.MultiError, len(req.Jid)), false |
| for i, st := range res.Status { |
| if st != pb.XmppMessageResponse_NO_ERROR { |
| me[i] = errors.New(st.String()) |
| any = true |
| } |
| } |
| if any { |
| return me |
| } |
| return nil |
| } |
| |
| // Invite sends an invitation. If the from address is an empty string |
| // the default ([email protected]/bot) will be used. |
| func Invite(c context.Context, to, from string) error { |
| req := &pb.XmppInviteRequest{ |
| Jid: &to, |
| } |
| if from != "" { |
| req.FromJid = &from |
| } |
| res := &pb.XmppInviteResponse{} |
| return internal.Call(c, "xmpp", "SendInvite", req, res) |
| } |
| |
| // Send sends a presence update. |
| func (p *Presence) Send(c context.Context) error { |
| req := &pb.XmppSendPresenceRequest{ |
| Jid: &p.To, |
| } |
| if p.State != "" { |
| req.Show = &p.State |
| } |
| if p.Type != "" { |
| req.Type = &p.Type |
| } |
| if p.Sender != "" { |
| req.FromJid = &p.Sender |
| } |
| if p.Status != "" { |
| req.Status = &p.Status |
| } |
| res := &pb.XmppSendPresenceResponse{} |
| return internal.Call(c, "xmpp", "SendPresence", req, res) |
| } |
| |
| var presenceMap = map[pb.PresenceResponse_SHOW]string{ |
| pb.PresenceResponse_NORMAL: "", |
| pb.PresenceResponse_AWAY: "away", |
| pb.PresenceResponse_DO_NOT_DISTURB: "dnd", |
| pb.PresenceResponse_CHAT: "chat", |
| pb.PresenceResponse_EXTENDED_AWAY: "xa", |
| } |
| |
| // GetPresence retrieves a user's presence. |
| // If the from address is an empty string the default |
| // ([email protected]/bot) will be used. |
| // Possible return values are "", "away", "dnd", "chat", "xa". |
| // ErrPresenceUnavailable is returned if the presence is unavailable. |
| func GetPresence(c context.Context, to string, from string) (string, error) { |
| req := &pb.PresenceRequest{ |
| Jid: &to, |
| } |
| if from != "" { |
| req.FromJid = &from |
| } |
| res := &pb.PresenceResponse{} |
| if err := internal.Call(c, "xmpp", "GetPresence", req, res); err != nil { |
| return "", err |
| } |
| if !*res.IsAvailable || res.Presence == nil { |
| return "", ErrPresenceUnavailable |
| } |
| presence, ok := presenceMap[*res.Presence] |
| if ok { |
| return presence, nil |
| } |
| return "", fmt.Errorf("xmpp: unknown presence %v", *res.Presence) |
| } |
| |
| // GetPresenceMulti retrieves multiple users' presence. |
| // If the from address is an empty string the default |
| // ([email protected]/bot) will be used. |
| // Possible return values are "", "away", "dnd", "chat", "xa". |
| // If any presence is unavailable, an appengine.MultiError is returned |
| func GetPresenceMulti(c context.Context, to []string, from string) ([]string, error) { |
| req := &pb.BulkPresenceRequest{ |
| Jid: to, |
| } |
| if from != "" { |
| req.FromJid = &from |
| } |
| res := &pb.BulkPresenceResponse{} |
| |
| if err := internal.Call(c, "xmpp", "BulkGetPresence", req, res); err != nil { |
| return nil, err |
| } |
| |
| presences := make([]string, 0, len(res.PresenceResponse)) |
| errs := appengine.MultiError{} |
| |
| addResult := func(presence string, err error) { |
| presences = append(presences, presence) |
| errs = append(errs, err) |
| } |
| |
| anyErr := false |
| for _, subres := range res.PresenceResponse { |
| if !subres.GetValid() { |
| anyErr = true |
| addResult("", ErrInvalidJID) |
| continue |
| } |
| if !*subres.IsAvailable || subres.Presence == nil { |
| anyErr = true |
| addResult("", ErrPresenceUnavailable) |
| continue |
| } |
| presence, ok := presenceMap[*subres.Presence] |
| if ok { |
| addResult(presence, nil) |
| } else { |
| anyErr = true |
| addResult("", fmt.Errorf("xmpp: unknown presence %q", *subres.Presence)) |
| } |
| } |
| if anyErr { |
| return presences, errs |
| } |
| return presences, nil |
| } |
| |
| func init() { |
| internal.RegisterErrorCodeMap("xmpp", pb.XmppServiceError_ErrorCode_name) |
| } |