thirdparty/google_appengine/google/appengine/api/xmpp/__init__.py
changeset 2864 2e0b0af889be
equal deleted inserted replaced
2862:27971a13089f 2864:2e0b0af889be
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 #
       
    17 
       
    18 """XMPP API.
       
    19 
       
    20 This module allows AppEngine apps to interact with a bot representing that app
       
    21 on the Google Talk network.
       
    22 
       
    23 Functions defined in this module:
       
    24   get_presence: Gets the presence for a JID.
       
    25   send_message: Sends a chat message to any number of JIDs.
       
    26   send_invite: Sends an invitation to chat to a JID.
       
    27 
       
    28 Classes defined in this module:
       
    29   Message: A class to encapsulate received messages.
       
    30 """
       
    31 
       
    32 
       
    33 
       
    34 from google.appengine.api import apiproxy_stub_map
       
    35 from google.appengine.api.xmpp import xmpp_service_pb
       
    36 from google.appengine.runtime import apiproxy_errors
       
    37 
       
    38 
       
    39 NO_ERROR    = xmpp_service_pb.XmppMessageResponse.NO_ERROR
       
    40 INVALID_JID = xmpp_service_pb.XmppMessageResponse.INVALID_JID
       
    41 OTHER_ERROR = xmpp_service_pb.XmppMessageResponse.OTHER_ERROR
       
    42 
       
    43 
       
    44 MESSAGE_TYPE_NONE = ""
       
    45 MESSAGE_TYPE_CHAT = "chat"
       
    46 MESSAGE_TYPE_ERROR = "error"
       
    47 MESSAGE_TYPE_GROUPCHAT = "groupchat"
       
    48 MESSAGE_TYPE_HEADLINE = "headline"
       
    49 MESSAGE_TYPE_NORMAL = "normal"
       
    50 
       
    51 _VALID_MESSAGE_TYPES = frozenset([MESSAGE_TYPE_NONE, MESSAGE_TYPE_CHAT,
       
    52                                   MESSAGE_TYPE_ERROR, MESSAGE_TYPE_GROUPCHAT,
       
    53                                   MESSAGE_TYPE_HEADLINE, MESSAGE_TYPE_NORMAL])
       
    54 
       
    55 
       
    56 class Error(Exception):
       
    57   """Base error class for this module."""
       
    58 
       
    59 
       
    60 class InvalidJidError(Error):
       
    61   """Error that indicates a request for an invalid JID."""
       
    62 
       
    63 
       
    64 class InvalidTypeError(Error):
       
    65   """Error that indicates a send message request has an invalid type."""
       
    66 
       
    67 
       
    68 class InvalidXmlError(Error):
       
    69   """Error that indicates a send message request has invalid XML."""
       
    70 
       
    71 
       
    72 class NoBodyError(Error):
       
    73   """Error that indicates a send message request has no body."""
       
    74 
       
    75 
       
    76 class InvalidMessageError(Error):
       
    77   """Error that indicates a received message was invalid or incomplete."""
       
    78 
       
    79 
       
    80 def get_presence(jid, from_jid=None):
       
    81   """Gets the presence for a JID.
       
    82 
       
    83   Args:
       
    84     jid: The JID of the contact whose presence is requested.
       
    85     from_jid: The optional custom JID to use for sending. Currently, the default
       
    86       is <appid>@appspot.com. This is supported as a value. Custom JIDs can be
       
    87       of the form <anything>@<appid>.appspotchat.com.
       
    88 
       
    89   Returns:
       
    90     bool, Whether the user is online.
       
    91 
       
    92   Raises:
       
    93     InvalidJidError if any of the JIDs passed are invalid.
       
    94     Error if an unspecified error happens processing the request.
       
    95   """
       
    96   if not jid:
       
    97     raise InvalidJidError()
       
    98 
       
    99   request = xmpp_service_pb.PresenceRequest()
       
   100   response = xmpp_service_pb.PresenceResponse()
       
   101 
       
   102   request.set_jid(_to_str(jid))
       
   103   if from_jid:
       
   104     request.set_from_jid(_to_str(from_jid))
       
   105 
       
   106   try:
       
   107     apiproxy_stub_map.MakeSyncCall("xmpp",
       
   108                                    "GetPresence",
       
   109                                    request,
       
   110                                    response)
       
   111   except apiproxy_errors.ApplicationError, e:
       
   112     if (e.application_error ==
       
   113         xmpp_service_pb.XmppServiceError.INVALID_JID):
       
   114       raise InvalidJidError()
       
   115     else:
       
   116       raise Error()
       
   117 
       
   118   return bool(response.is_available())
       
   119 
       
   120 
       
   121 def send_invite(jid, from_jid=None):
       
   122   """Sends an invitation to chat to a JID.
       
   123 
       
   124   Args:
       
   125     jid: The JID of the contact to invite.
       
   126     from_jid: The optional custom JID to use for sending. Currently, the default
       
   127       is <appid>@appspot.com. This is supported as a value. Custom JIDs can be
       
   128       of the form <anything>@<appid>.appspotchat.com.
       
   129 
       
   130   Raises:
       
   131     InvalidJidError if the JID passed is invalid.
       
   132     Error if an unspecified error happens processing the request.
       
   133   """
       
   134   if not jid:
       
   135     raise InvalidJidError()
       
   136 
       
   137   request = xmpp_service_pb.XmppInviteRequest()
       
   138   response = xmpp_service_pb.XmppInviteResponse()
       
   139 
       
   140   request.set_jid(_to_str(jid))
       
   141   if from_jid:
       
   142     request.set_from_jid(_to_str(from_jid))
       
   143 
       
   144   try:
       
   145     apiproxy_stub_map.MakeSyncCall("xmpp",
       
   146                                    "SendInvite",
       
   147                                    request,
       
   148                                    response)
       
   149   except apiproxy_errors.ApplicationError, e:
       
   150     if (e.application_error ==
       
   151         xmpp_service_pb.XmppServiceError.INVALID_JID):
       
   152       raise InvalidJidError()
       
   153     else:
       
   154       raise Error()
       
   155 
       
   156   return
       
   157 
       
   158 
       
   159 def send_message(jids, body, from_jid=None, message_type=MESSAGE_TYPE_CHAT,
       
   160                  raw_xml=False):
       
   161   """Sends a chat message to a list of JIDs.
       
   162 
       
   163   Args:
       
   164     jids: A list of JIDs to send the message to, or a single JID to send the
       
   165       message to.
       
   166     from_jid: The optional custom JID to use for sending. Currently, the default
       
   167       is <appid>@appspot.com. This is supported as a value. Custom JIDs can be
       
   168       of the form <anything>@<appid>.appspotchat.com.
       
   169     body: The body of the message.
       
   170     message_type: Optional type of the message. Should be one of the types
       
   171       specified in RFC 3921, section 2.1.1. An empty string will result in a
       
   172       message stanza without a type attribute. For convenience, all of the
       
   173       valid types are in the MESSAGE_TYPE_* constants in this file. The
       
   174       default is MESSAGE_TYPE_CHAT. Anything else will throw an exception.
       
   175     raw_xml: Optionally specifies that the body should be interpreted as XML. If
       
   176       this is false, the contents of the body will be escaped and placed inside
       
   177       of a body element inside of the message. If this is true, the contents
       
   178       will be made children of the message.
       
   179 
       
   180   Returns:
       
   181     list, A list of statuses, one for each JID, corresponding to the result of
       
   182       sending the message to that JID. Or, if a single JID was passed in,
       
   183       returns the status directly.
       
   184 
       
   185   Raises:
       
   186     InvalidJidError if there is no valid JID in the list.
       
   187     InvalidTypeError if the type argument is invalid.
       
   188     InvalidXmlError if the body is malformed XML and raw_xml is True.
       
   189     NoBodyError if there is no body.
       
   190     Error if another error occurs processing the request.
       
   191   """
       
   192   request = xmpp_service_pb.XmppMessageRequest()
       
   193   response = xmpp_service_pb.XmppMessageResponse()
       
   194 
       
   195   if not body:
       
   196     raise NoBodyError()
       
   197 
       
   198   if not jids:
       
   199     raise InvalidJidError()
       
   200 
       
   201   if not message_type in _VALID_MESSAGE_TYPES:
       
   202     raise InvalidTypeError()
       
   203 
       
   204   single_jid = False
       
   205   if isinstance(jids, basestring):
       
   206     single_jid = True
       
   207     jids = [jids]
       
   208 
       
   209   for jid in jids:
       
   210     if not jid:
       
   211       raise InvalidJidError()
       
   212     request.add_jid(_to_str(jid))
       
   213 
       
   214   request.set_body(_to_str(body))
       
   215   request.set_type(_to_str(message_type))
       
   216   request.set_raw_xml(raw_xml)
       
   217   if from_jid:
       
   218     request.set_from_jid(_to_str(from_jid))
       
   219 
       
   220   try:
       
   221     apiproxy_stub_map.MakeSyncCall("xmpp",
       
   222                                    "SendMessage",
       
   223                                    request,
       
   224                                    response)
       
   225   except apiproxy_errors.ApplicationError, e:
       
   226     if (e.application_error ==
       
   227         xmpp_service_pb.XmppServiceError.INVALID_JID):
       
   228       raise InvalidJidError()
       
   229     elif (e.application_error ==
       
   230           xmpp_service_pb.XmppServiceError.INVALID_TYPE):
       
   231       raise InvalidTypeError()
       
   232     elif (e.application_error ==
       
   233           xmpp_service_pb.XmppServiceError.INVALID_XML):
       
   234       raise InvalidXmlError()
       
   235     elif (e.application_error ==
       
   236           xmpp_service_pb.XmppServiceError.NO_BODY):
       
   237       raise NoBodyError()
       
   238     raise Error()
       
   239 
       
   240   if single_jid:
       
   241     return response.status_list()[0]
       
   242   return response.status_list()
       
   243 
       
   244 
       
   245 class Message(object):
       
   246   """Encapsulates an XMPP message received by the application."""
       
   247 
       
   248   def __init__(self, vars):
       
   249     """Constructs a new XMPP Message from an HTTP request.
       
   250 
       
   251     Args:
       
   252       vars: A dict-like object to extract message arguments from.
       
   253     """
       
   254     try:
       
   255       self.__sender = vars["from"]
       
   256       self.__to = vars["to"]
       
   257       self.__body = vars["body"]
       
   258     except KeyError, e:
       
   259       raise InvalidMessageError(e[0])
       
   260     self.__command = None
       
   261     self.__arg = None
       
   262 
       
   263   @property
       
   264   def sender(self):
       
   265     return self.__sender
       
   266 
       
   267   @property
       
   268   def to(self):
       
   269     return self.__to
       
   270 
       
   271   @property
       
   272   def body(self):
       
   273     return self.__body
       
   274 
       
   275   def __parse_command(self):
       
   276     if self.__arg != None:
       
   277       return
       
   278 
       
   279     body = self.__body
       
   280     if body.startswith('\\'):
       
   281       body = '/' + body[1:]
       
   282 
       
   283     self.__arg = ''
       
   284     if body.startswith('/'):
       
   285       parts = body.split(' ', 1)
       
   286       self.__command = parts[0][1:]
       
   287       if len(parts) > 1:
       
   288         self.__arg = parts[1].strip()
       
   289     else:
       
   290       self.__arg = self.__body.strip()
       
   291 
       
   292   @property
       
   293   def command(self):
       
   294     self.__parse_command()
       
   295     return self.__command
       
   296 
       
   297   @property
       
   298   def arg(self):
       
   299     self.__parse_command()
       
   300     return self.__arg
       
   301 
       
   302   def reply(self, body, message_type=MESSAGE_TYPE_CHAT, raw_xml=False,
       
   303             send_message=send_message):
       
   304     """Convenience function to reply to a message.
       
   305 
       
   306     Args:
       
   307       body: str: The body of the message
       
   308       message_type, raw_xml: As per send_message.
       
   309       send_message: Used for testing.
       
   310 
       
   311     Returns:
       
   312       A status code as per send_message.
       
   313 
       
   314     Raises:
       
   315       See send_message.
       
   316     """
       
   317     return send_message([self.sender], body, from_jid=self.to,
       
   318                         message_type=message_type, raw_xml=raw_xml)
       
   319 
       
   320 
       
   321 def _to_str(value):
       
   322   """Helper function to make sure unicode values converted to utf-8
       
   323 
       
   324   Args:
       
   325     value: str or unicode to convert to utf-8.
       
   326 
       
   327   Returns:
       
   328     UTF-8 encoded str of value, otherwise value unchanged.
       
   329   """
       
   330   if isinstance(value, unicode):
       
   331     return value.encode('utf-8')
       
   332   return value