github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/google.golang.org/appengine/xmpp/xmpp.go (about) 1 // Copyright 2011 Google Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 /* 6 Package xmpp provides the means to send and receive instant messages 7 to and from users of XMPP-compatible services. 8 9 To send a message, 10 m := &xmpp.Message{ 11 To: []string{"kaylee@example.com"}, 12 Body: `Hi! How's the carrot?`, 13 } 14 err := m.Send(c) 15 16 To receive messages, 17 func init() { 18 xmpp.Handle(handleChat) 19 } 20 21 func handleChat(c context.Context, m *xmpp.Message) { 22 // ... 23 } 24 */ 25 package xmpp 26 27 import ( 28 "errors" 29 "fmt" 30 "net/http" 31 32 "golang.org/x/net/context" 33 34 "google.golang.org/appengine" 35 "google.golang.org/appengine/internal" 36 pb "google.golang.org/appengine/internal/xmpp" 37 ) 38 39 // Message represents an incoming chat message. 40 type Message struct { 41 // Sender is the JID of the sender. 42 // Optional for outgoing messages. 43 Sender string 44 45 // To is the intended recipients of the message. 46 // Incoming messages will have exactly one element. 47 To []string 48 49 // Body is the body of the message. 50 Body string 51 52 // Type is the message type, per RFC 3921. 53 // It defaults to "chat". 54 Type string 55 56 // RawXML is whether the body contains raw XML. 57 RawXML bool 58 } 59 60 // Presence represents an outgoing presence update. 61 type Presence struct { 62 // Sender is the JID (optional). 63 Sender string 64 65 // The intended recipient of the presence update. 66 To string 67 68 // Type, per RFC 3921 (optional). Defaults to "available". 69 Type string 70 71 // State of presence (optional). 72 // Valid values: "away", "chat", "xa", "dnd" (RFC 3921). 73 State string 74 75 // Free text status message (optional). 76 Status string 77 } 78 79 var ( 80 ErrPresenceUnavailable = errors.New("xmpp: presence unavailable") 81 ErrInvalidJID = errors.New("xmpp: invalid JID") 82 ) 83 84 // Handle arranges for f to be called for incoming XMPP messages. 85 // Only messages of type "chat" or "normal" will be handled. 86 func Handle(f func(c context.Context, m *Message)) { 87 http.HandleFunc("/_ah/xmpp/message/chat/", func(_ http.ResponseWriter, r *http.Request) { 88 f(appengine.NewContext(r), &Message{ 89 Sender: r.FormValue("from"), 90 To: []string{r.FormValue("to")}, 91 Body: r.FormValue("body"), 92 }) 93 }) 94 } 95 96 // Send sends a message. 97 // If any failures occur with specific recipients, the error will be an appengine.MultiError. 98 func (m *Message) Send(c context.Context) error { 99 req := &pb.XmppMessageRequest{ 100 Jid: m.To, 101 Body: &m.Body, 102 RawXml: &m.RawXML, 103 } 104 if m.Type != "" && m.Type != "chat" { 105 req.Type = &m.Type 106 } 107 if m.Sender != "" { 108 req.FromJid = &m.Sender 109 } 110 res := &pb.XmppMessageResponse{} 111 if err := internal.Call(c, "xmpp", "SendMessage", req, res); err != nil { 112 return err 113 } 114 115 if len(res.Status) != len(req.Jid) { 116 return fmt.Errorf("xmpp: sent message to %d JIDs, but only got %d statuses back", len(req.Jid), len(res.Status)) 117 } 118 me, any := make(appengine.MultiError, len(req.Jid)), false 119 for i, st := range res.Status { 120 if st != pb.XmppMessageResponse_NO_ERROR { 121 me[i] = errors.New(st.String()) 122 any = true 123 } 124 } 125 if any { 126 return me 127 } 128 return nil 129 } 130 131 // Invite sends an invitation. If the from address is an empty string 132 // the default (yourapp@appspot.com/bot) will be used. 133 func Invite(c context.Context, to, from string) error { 134 req := &pb.XmppInviteRequest{ 135 Jid: &to, 136 } 137 if from != "" { 138 req.FromJid = &from 139 } 140 res := &pb.XmppInviteResponse{} 141 return internal.Call(c, "xmpp", "SendInvite", req, res) 142 } 143 144 // Send sends a presence update. 145 func (p *Presence) Send(c context.Context) error { 146 req := &pb.XmppSendPresenceRequest{ 147 Jid: &p.To, 148 } 149 if p.State != "" { 150 req.Show = &p.State 151 } 152 if p.Type != "" { 153 req.Type = &p.Type 154 } 155 if p.Sender != "" { 156 req.FromJid = &p.Sender 157 } 158 if p.Status != "" { 159 req.Status = &p.Status 160 } 161 res := &pb.XmppSendPresenceResponse{} 162 return internal.Call(c, "xmpp", "SendPresence", req, res) 163 } 164 165 var presenceMap = map[pb.PresenceResponse_SHOW]string{ 166 pb.PresenceResponse_NORMAL: "", 167 pb.PresenceResponse_AWAY: "away", 168 pb.PresenceResponse_DO_NOT_DISTURB: "dnd", 169 pb.PresenceResponse_CHAT: "chat", 170 pb.PresenceResponse_EXTENDED_AWAY: "xa", 171 } 172 173 // GetPresence retrieves a user's presence. 174 // If the from address is an empty string the default 175 // (yourapp@appspot.com/bot) will be used. 176 // Possible return values are "", "away", "dnd", "chat", "xa". 177 // ErrPresenceUnavailable is returned if the presence is unavailable. 178 func GetPresence(c context.Context, to string, from string) (string, error) { 179 req := &pb.PresenceRequest{ 180 Jid: &to, 181 } 182 if from != "" { 183 req.FromJid = &from 184 } 185 res := &pb.PresenceResponse{} 186 if err := internal.Call(c, "xmpp", "GetPresence", req, res); err != nil { 187 return "", err 188 } 189 if !*res.IsAvailable || res.Presence == nil { 190 return "", ErrPresenceUnavailable 191 } 192 presence, ok := presenceMap[*res.Presence] 193 if ok { 194 return presence, nil 195 } 196 return "", fmt.Errorf("xmpp: unknown presence %v", *res.Presence) 197 } 198 199 // GetPresenceMulti retrieves multiple users' presence. 200 // If the from address is an empty string the default 201 // (yourapp@appspot.com/bot) will be used. 202 // Possible return values are "", "away", "dnd", "chat", "xa". 203 // If any presence is unavailable, an appengine.MultiError is returned 204 func GetPresenceMulti(c context.Context, to []string, from string) ([]string, error) { 205 req := &pb.BulkPresenceRequest{ 206 Jid: to, 207 } 208 if from != "" { 209 req.FromJid = &from 210 } 211 res := &pb.BulkPresenceResponse{} 212 213 if err := internal.Call(c, "xmpp", "BulkGetPresence", req, res); err != nil { 214 return nil, err 215 } 216 217 presences := make([]string, 0, len(res.PresenceResponse)) 218 errs := appengine.MultiError{} 219 220 addResult := func(presence string, err error) { 221 presences = append(presences, presence) 222 errs = append(errs, err) 223 } 224 225 anyErr := false 226 for _, subres := range res.PresenceResponse { 227 if !subres.GetValid() { 228 anyErr = true 229 addResult("", ErrInvalidJID) 230 continue 231 } 232 if !*subres.IsAvailable || subres.Presence == nil { 233 anyErr = true 234 addResult("", ErrPresenceUnavailable) 235 continue 236 } 237 presence, ok := presenceMap[*subres.Presence] 238 if ok { 239 addResult(presence, nil) 240 } else { 241 anyErr = true 242 addResult("", fmt.Errorf("xmpp: unknown presence %q", *subres.Presence)) 243 } 244 } 245 if anyErr { 246 return presences, errs 247 } 248 return presences, nil 249 } 250 251 func init() { 252 internal.RegisterErrorCodeMap("xmpp", pb.XmppServiceError_ErrorCode_name) 253 }