Advertisement
Guest User

Modified PyFacebook

a guest
Jan 27th, 2011
248
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 63.43 KB | None | 0 0
  1. #! /usr/bin/env python
  2. #
  3. # pyfacebook - Python bindings for the Facebook API
  4. #
  5. # Copyright (c) 2008, Samuel Cormier-Iijima
  6. # All rights reserved.
  7. #
  8. # Redistribution and use in source and binary forms, with or without
  9. # modification, are permitted provided that the following conditions are met:
  10. #     * Redistributions of source code must retain the above copyright
  11. #       notice, this list of conditions and the following disclaimer.
  12. #     * Redistributions in binary form must reproduce the above copyright
  13. #       notice, this list of conditions and the following disclaimer in the
  14. #       documentation and/or other materials provided with the distribution.
  15. #     * Neither the name of the author nor the names of its contributors may
  16. #       be used to endorse or promote products derived from this software
  17. #       without specific prior written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY
  20. # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  22. # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
  23. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  24. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29.  
  30. """
  31. Python bindings for the Facebook API (pyfacebook - http://code.google.com/p/pyfacebook)
  32.  
  33. PyFacebook is a client library that wraps the Facebook API.
  34.  
  35. For more information, see
  36.  
  37. Home Page: http://code.google.com/p/pyfacebook
  38. Developer Wiki: http://wiki.developers.facebook.com/index.php/Python
  39. Facebook IRC Channel: #facebook on irc.freenode.net
  40.  
  41. PyFacebook can use simplejson if it is installed, which
  42. is much faster than XML and also uses less bandwith. Go to
  43. http://undefined.org/python/#simplejson to download it, or do
  44. apt-get install python-simplejson on a Debian-like system.
  45. """
  46.  
  47. import sys
  48. import time
  49. import struct
  50. import urllib
  51. import urllib2
  52. import httplib
  53. import hmac
  54. try:
  55.     import hashlib
  56. except ImportError:
  57.     import md5 as hashlib
  58. from django.conf import settings
  59. import binascii
  60. import urlparse
  61. import mimetypes
  62.  
  63. # try to use simplejson first, otherwise fallback to XML
  64. RESPONSE_FORMAT = 'JSON'
  65. try:
  66.     import json as simplejson
  67.     simplejson.loads
  68. except (ImportError, AttributeError):
  69.     try:
  70.         import simplejson
  71.         simplejson.loads
  72.     except (ImportError, AttributeError):
  73.         try:
  74.             from django.utils import simplejson
  75.             simplejson.loads
  76.         except (ImportError, AttributeError):
  77.             try:
  78.                 import jsonlib as simplejson
  79.                 simplejson.loads
  80.             except (ImportError, AttributeError):
  81.                 from xml.dom import minidom
  82.                 RESPONSE_FORMAT = 'XML'
  83.  
  84. # support Google App Engine.  GAE does not have a working urllib.urlopen.
  85. try:
  86.     from google.appengine.api import urlfetch
  87.  
  88.     def urlread(url, data=None, headers=None):
  89.         if data is not None:
  90.             if headers is None:
  91.                 headers = {"Content-type": "application/x-www-form-urlencoded"}
  92.             method = urlfetch.POST
  93.         else:
  94.             if headers is None:
  95.                 headers = {}
  96.             method = urlfetch.GET
  97.  
  98.         result = urlfetch.fetch(url, method=method,
  99.                                 payload=data, headers=headers)
  100.  
  101.         if result.status_code == 200:
  102.             return result.content
  103.         else:
  104.             raise urllib2.URLError("fetch error url=%s, code=%d" % (url, result.status_code))
  105.  
  106. except ImportError:
  107.     def urlread(url, data=None):
  108.         res = urllib2.urlopen(url, data=data)
  109.         return res.read()
  110.  
  111. __all__ = ['Facebook','create_hmac']
  112.  
  113. VERSION = '1.0a2'
  114.  
  115. FACEBOOK_URL = 'http://api.facebook.com/restserver.php'
  116. FACEBOOK_VIDEO_URL = 'http://api-video.facebook.com/restserver.php'
  117. FACEBOOK_SECURE_URL = 'https://api.facebook.com/restserver.php'
  118.  
  119. def create_hmac(tbhashed):
  120.     return hmac.new(settings.SECRET_KEY, tbhashed, hashlib.sha1).hexdigest()
  121.  
  122. class json(object): pass
  123.  
  124. # simple IDL for the Facebook API
  125. METHODS = {
  126.     #dashboard methods
  127.     'dashboard': {
  128.         'incrementCount': [
  129.             ('uid', int, ['optional'])
  130.         ],
  131.         'decrementCount': [
  132.             ('uid', int, ['optional'])
  133.         ],
  134.         'getCount': [
  135.             ('uid', int, ['optional'])
  136.         ],
  137.         'setCount': [
  138.             ('count', int, []),
  139.             ('uid', int, ['optional'])
  140.         ],
  141.         'getActivity': [
  142.             ('activity_ids', list, ['optional']),
  143.             ('uid', int, ['optional'])
  144.         ],
  145.         'publishActivity': [
  146.             ('activity', json, []),
  147.  
  148.         ]
  149.  
  150.     },
  151.     'application': {
  152.         'getPublicInfo': [
  153.             ('application_id', int, ['optional']),
  154.             ('application_api_key', str, ['optional']),
  155.             ('application_canvas_name', str,['optional']),
  156.         ],
  157.     },
  158.  
  159.     # admin methods
  160.     'admin': {
  161.         'getAllocation': [
  162.             ('integration_point_name', str, []),
  163.         ],
  164.         'getRestrictionInfo': [],
  165.         'setRestrictionInfo': [
  166.             ('format', str, ['optional']),
  167.             ('callback', str, ['optional']),
  168.             ('restriction_str', json, ['optional']),
  169.         ],
  170.         # Some methods don't work with access_tokens, the signed option forces
  171.         # use of the secret_key signature (avoids error 15 and, sometimes, 8)
  172.         'getAppProperties': [
  173.             ('properties', list, []),
  174.             'signed'
  175.         ],
  176.         'setAppProperties': [
  177.             ('properties', json, []),
  178.             'signed'
  179.         ],
  180.     },
  181.  
  182.     # auth methods
  183.     'auth': {
  184.         'revokeAuthorization': [
  185.             ('uid', int, ['optional']),
  186.         ],
  187.         'revokeExtendedPermission': [
  188.             ('perm', str, []),
  189.             ('uid', int, ['optional']),
  190.         ],
  191.     },
  192.  
  193.     # feed methods
  194.     'feed': {
  195.         'publishStoryToUser': [
  196.             ('title', str, []),
  197.             ('body', str, ['optional']),
  198.             ('image_1', str, ['optional']),
  199.             ('image_1_link', str, ['optional']),
  200.             ('image_2', str, ['optional']),
  201.             ('image_2_link', str, ['optional']),
  202.             ('image_3', str, ['optional']),
  203.             ('image_3_link', str, ['optional']),
  204.             ('image_4', str, ['optional']),
  205.             ('image_4_link', str, ['optional']),
  206.             ('priority', int, ['optional']),
  207.         ],
  208.  
  209.         'publishActionOfUser': [
  210.             ('title', str, []),
  211.             ('body', str, ['optional']),
  212.             ('image_1', str, ['optional']),
  213.             ('image_1_link', str, ['optional']),
  214.             ('image_2', str, ['optional']),
  215.             ('image_2_link', str, ['optional']),
  216.             ('image_3', str, ['optional']),
  217.             ('image_3_link', str, ['optional']),
  218.             ('image_4', str, ['optional']),
  219.             ('image_4_link', str, ['optional']),
  220.             ('priority', int, ['optional']),
  221.         ],
  222.  
  223.         'publishTemplatizedAction': [
  224.             ('title_template', str, []),
  225.             ('page_actor_id', int, ['optional']),
  226.             ('title_data', json, ['optional']),
  227.             ('body_template', str, ['optional']),
  228.             ('body_data', json, ['optional']),
  229.             ('body_general', str, ['optional']),
  230.             ('image_1', str, ['optional']),
  231.             ('image_1_link', str, ['optional']),
  232.             ('image_2', str, ['optional']),
  233.             ('image_2_link', str, ['optional']),
  234.             ('image_3', str, ['optional']),
  235.             ('image_3_link', str, ['optional']),
  236.             ('image_4', str, ['optional']),
  237.             ('image_4_link', str, ['optional']),
  238.             ('target_ids', list, ['optional']),
  239.         ],
  240.  
  241.         'registerTemplateBundle': [
  242.             ('one_line_story_templates', json, []),
  243.             ('short_story_templates', json, ['optional']),
  244.             ('full_story_template', json, ['optional']),
  245.             ('action_links', json, ['optional']),
  246.         ],
  247.  
  248.         'deactivateTemplateBundleByID': [
  249.             ('template_bundle_id', int, []),
  250.         ],
  251.  
  252.         'getRegisteredTemplateBundles': [],
  253.  
  254.         'getRegisteredTemplateBundleByID': [
  255.             ('template_bundle_id', str, []),
  256.         ],
  257.  
  258.         'publishUserAction': [
  259.             ('template_bundle_id', int, []),
  260.             ('template_data', json, ['optional']),
  261.             ('target_ids', list, ['optional']),
  262.             ('body_general', str, ['optional']),
  263.             ('story_size', int, ['optional']),
  264.         ],
  265.     },
  266.  
  267.     # fql methods
  268.     'fql': {
  269.         'query': [
  270.             ('query', str, []),
  271.         ],
  272.         'multiquery': [
  273.             ('queries', json, []),
  274.         ],
  275.     },
  276.  
  277.     # friends methods
  278.     'friends': {
  279.         'areFriends': [
  280.             ('uids1', list, []),
  281.             ('uids2', list, []),
  282.         ],
  283.  
  284.         'get': [
  285.             ('flid', int, ['optional']),
  286.         ],
  287.  
  288.         'getLists': [],
  289.  
  290.         'getAppUsers': [],
  291.        
  292.         'getMutualFriends': [
  293.             ('target_uid', int, []),
  294.             ('source_uid', int, ['optional']),
  295.         ],
  296.     },
  297.  
  298.     # notifications methods
  299.     'notifications': {
  300.         'get': [],
  301.  
  302.         'send': [
  303.             ('to_ids', list, []),
  304.             ('notification', str, []),
  305.             ('email', str, ['optional']),
  306.             ('type', str, ['optional']),
  307.         ],
  308.  
  309.         'sendRequest': [
  310.             ('to_ids', list, []),
  311.             ('type', str, []),
  312.             ('content', str, []),
  313.             ('image', str, []),
  314.             ('invite', bool, []),
  315.         ],
  316.  
  317.         'sendEmail': [
  318.             ('recipients', list, []),
  319.             ('subject', str, []),
  320.             ('text', str, ['optional']),
  321.             ('fbml', str, ['optional']),
  322.         ]
  323.     },
  324.  
  325.     # profile methods
  326.     'profile': {
  327.         'setFBML': [
  328.             ('markup', str, ['optional']),
  329.             ('uid', int, ['optional']),
  330.             ('profile', str, ['optional']),
  331.             ('profile_action', str, ['optional']),
  332.             ('mobile_fbml', str, ['optional']),
  333.             ('profile_main', str, ['optional']),
  334.         ],
  335.  
  336.         'getFBML': [
  337.             ('uid', int, ['optional']),
  338.             ('type', int, ['optional']),
  339.         ],
  340.  
  341.         'setInfo': [
  342.             ('title', str, []),
  343.             ('type', int, []),
  344.             ('info_fields', json, []),
  345.             ('uid', int, []),
  346.         ],
  347.  
  348.         'getInfo': [
  349.             ('uid', int, []),
  350.         ],
  351.  
  352.         'setInfoOptions': [
  353.             ('field', str, []),
  354.             ('options', json, []),
  355.         ],
  356.  
  357.         'getInfoOptions': [
  358.             ('field', str, []),
  359.         ],
  360.     },
  361.  
  362.     # users methods
  363.     'users': {
  364.         'getInfo': [
  365.             ('uids', list, []),
  366.             ('fields', list, [('default', ['name'])]),
  367.         ],
  368.  
  369.         'getStandardInfo': [
  370.             ('uids', list, []),
  371.             ('fields', list, [('default', ['uid'])]),
  372.         ],
  373.  
  374.         'getLoggedInUser': [],
  375.  
  376.         'isAppAdded': [],
  377.  
  378.         'isAppUser': [
  379.             ('uid', int, []),
  380.         ],
  381.  
  382.         'hasAppPermission': [
  383.             ('ext_perm', str, []),
  384.             ('uid', int, ['optional']),
  385.         ],
  386.  
  387.         'setStatus': [
  388.             ('status', str, []),
  389.             ('clear', bool, []),
  390.             ('status_includes_verb', bool, ['optional']),
  391.             ('uid', int, ['optional']),
  392.         ],
  393.     },
  394.  
  395.     # events methods
  396.     'events': {
  397.         'cancel': [
  398.             ('eid', int, []),
  399.             ('cancel_message', str, ['optional']),
  400.          ],
  401.  
  402.         'create': [
  403.             ('event_info', json, []),
  404.         ],
  405.  
  406.         'edit': [
  407.             ('eid', int, []),
  408.             ('event_info', json, []),
  409.         ],
  410.  
  411.         'get': [
  412.             ('uid', int, ['optional']),
  413.             ('eids', list, ['optional']),
  414.             ('start_time', int, ['optional']),
  415.             ('end_time', int, ['optional']),
  416.             ('rsvp_status', str, ['optional']),
  417.         ],
  418.  
  419.         'getMembers': [
  420.             ('eid', int, []),
  421.         ],
  422.  
  423.         'invite': [
  424.             ('eid', int, []),
  425.             ('uids', list, []),
  426.             ('personal_message', str, ['optional']),
  427.         ],
  428.  
  429.         'rsvp': [
  430.             ('eid', int, []),
  431.             ('rsvp_status', str, []),
  432.         ],
  433.  
  434.         'edit': [
  435.             ('eid', int, []),
  436.             ('event_info', json, []),
  437.         ],
  438.  
  439.         'invite': [
  440.             ('eid', int, []),
  441.             ('uids', list, []),
  442.             ('personal_message', str, ['optional']),
  443.         ],
  444.     },
  445.  
  446.     # update methods
  447.     'update': {
  448.         'decodeIDs': [
  449.             ('ids', list, []),
  450.         ],
  451.     },
  452.  
  453.     # groups methods
  454.     'groups': {
  455.         'get': [
  456.             ('uid', int, ['optional']),
  457.             ('gids', list, ['optional']),
  458.         ],
  459.  
  460.         'getMembers': [
  461.             ('gid', int, []),
  462.         ],
  463.     },
  464.  
  465.     # marketplace methods
  466.     'marketplace': {
  467.         'createListing': [
  468.             ('listing_id', int, []),
  469.             ('show_on_profile', bool, []),
  470.             ('listing_attrs', str, []),
  471.         ],
  472.  
  473.         'getCategories': [],
  474.  
  475.         'getListings': [
  476.             ('listing_ids', list, []),
  477.             ('uids', list, []),
  478.         ],
  479.  
  480.         'getSubCategories': [
  481.             ('category', str, []),
  482.         ],
  483.  
  484.         'removeListing': [
  485.             ('listing_id', int, []),
  486.             ('status', str, []),
  487.         ],
  488.  
  489.         'search': [
  490.             ('category', str, ['optional']),
  491.             ('subcategory', str, ['optional']),
  492.             ('query', str, ['optional']),
  493.         ],
  494.     },
  495.  
  496.     # pages methods
  497.     'pages': {
  498.         'getInfo': [
  499.             ('fields', list, [('default', ['page_id', 'name'])]),
  500.             ('page_ids', list, ['optional']),
  501.             ('uid', int, ['optional']),
  502.         ],
  503.  
  504.         'isAdmin': [
  505.             ('page_id', int, []),
  506.         ],
  507.  
  508.         'isAppAdded': [
  509.             ('page_id', int, []),
  510.         ],
  511.  
  512.         'isFan': [
  513.             ('page_id', int, []),
  514.             ('uid', int, []),
  515.         ],
  516.     },
  517.  
  518.     # photos methods
  519.     'photos': {
  520.         'addTag': [
  521.             ('pid', int, []),
  522.             ('tag_uid', int, [('default', 0)]),
  523.             ('tag_text', str, [('default', '')]),
  524.             ('x', float, [('default', 50)]),
  525.             ('y', float, [('default', 50)]),
  526.             ('tags', json, ['optional']),
  527.         ],
  528.  
  529.         'createAlbum': [
  530.             ('name', str, []),
  531.             ('location', str, ['optional']),
  532.             ('description', str, ['optional']),
  533.         ],
  534.  
  535.         'get': [
  536.             ('subj_id', int, ['optional']),
  537.             ('aid', int, ['optional']),
  538.             ('pids', list, ['optional']),
  539.         ],
  540.  
  541.         'getAlbums': [
  542.             ('uid', int, ['optional']),
  543.             ('aids', list, ['optional']),
  544.         ],
  545.  
  546.         'getTags': [
  547.             ('pids', list, []),
  548.         ],
  549.     },
  550.  
  551.     # videos methods
  552.     'video': {
  553.         'getUploadLimits': [
  554.         ],
  555.     },
  556.  
  557.     # status methods
  558.     'status': {
  559.         'get': [
  560.             ('uid', int, ['optional']),
  561.             ('limit', int, ['optional']),
  562.         ],
  563.         'set': [
  564.             ('status', str, ['optional']),
  565.             ('uid', int, ['optional']),
  566.         ],
  567.     },
  568.  
  569.     # fbml methods
  570.     'fbml': {
  571.         'refreshImgSrc': [
  572.             ('url', str, []),
  573.         ],
  574.  
  575.         'refreshRefUrl': [
  576.             ('url', str, []),
  577.         ],
  578.  
  579.         'setRefHandle': [
  580.             ('handle', str, []),
  581.             ('fbml', str, []),
  582.         ],
  583.     },
  584.  
  585.     # SMS Methods
  586.     'sms' : {
  587.         'canSend' : [
  588.             ('uid', int, []),
  589.         ],
  590.  
  591.         'send' : [
  592.             ('uid', int, []),
  593.             ('message', str, []),
  594.             ('session_id', int, []),
  595.             ('req_session', bool, []),
  596.         ],
  597.     },
  598.  
  599.     'data': {
  600.         'getCookies': [
  601.             ('uid', int, []),
  602.             ('string', str, ['optional']),
  603.         ],
  604.  
  605.         'setCookie': [
  606.             ('uid', int, []),
  607.             ('name', str, []),
  608.             ('value', str, []),
  609.             ('expires', int, ['optional']),
  610.             ('path', str, ['optional']),
  611.         ],
  612.     },
  613.  
  614.     # connect methods
  615.     'connect': {
  616.         'registerUsers': [
  617.             ('accounts', json, []),
  618.         ],
  619.  
  620.         'unregisterUsers': [
  621.             ('email_hashes', json, []),
  622.         ],
  623.  
  624.         'getUnconnectedFriendsCount': [
  625.         ],
  626.     },
  627.  
  628.     'links': {
  629.         'post': [
  630.             ('url', str, []),
  631.             ('comment', str, []),
  632.             ('uid', int, []),
  633.             ('image', str, ['optional']),
  634.             ('callback', str, ['optional']),
  635.         ],
  636.         'preview': [
  637.             ('url', str, []),
  638.             ('callback', str, ['optional']),
  639.         ],
  640.     },
  641.  
  642.     #stream methods (beta)
  643.     'stream' : {
  644.         'addComment' : [
  645.             ('post_id', int, []),
  646.             ('comment', str, []),
  647.             ('uid', int, ['optional']),
  648.         ],
  649.  
  650.         'addLike': [
  651.             ('uid', int, ['optional']),
  652.             ('post_id', int, ['optional']),
  653.         ],
  654.  
  655.         'get' : [
  656.             ('viewer_id', int, ['optional']),
  657.             ('source_ids', list, ['optional']),
  658.             ('start_time', int, ['optional']),
  659.             ('end_time', int, ['optional']),
  660.             ('limit', int, ['optional']),
  661.             ('filter_key', str, ['optional']),
  662.         ],
  663.  
  664.         'getComments' : [
  665.             ('post_id', int, []),
  666.         ],
  667.  
  668.         'getFilters' : [
  669.             ('uid', int, ['optional']),
  670.         ],
  671.  
  672.         'publish' : [
  673.             ('message', str, ['optional']),
  674.             ('attachment', json, ['optional']),
  675.             ('action_links', json, ['optional']),
  676.             ('target_id', str, ['optional']),
  677.             ('uid', str, ['optional']),
  678.         ],
  679.  
  680.         'remove' : [
  681.             ('post_id', int, []),
  682.             ('uid', int, ['optional']),
  683.         ],
  684.  
  685.         'removeComment' : [
  686.             ('comment_id', int, []),
  687.             ('uid', int, ['optional']),
  688.         ],
  689.  
  690.         'removeLike' : [
  691.             ('uid', int, ['optional']),
  692.             ('post_id', int, ['optional']),
  693.         ],
  694.     },
  695.  
  696.     # livemessage methods (beta)
  697.     'livemessage': {
  698.         'send': [
  699.             ('recipient', int, []),
  700.             ('event_name', str, []),
  701.             ('message', str, []),  
  702.         ],
  703.     },
  704.  
  705.     # dashboard methods (beta)
  706.     'dashboard': {
  707.         # setting counters for a single user
  708.         'decrementCount': [
  709.             ('uid', int, []),
  710.         ],
  711.  
  712.         'incrementCount': [
  713.             ('uid', int, []),
  714.         ],
  715.  
  716.         'getCount': [
  717.             ('uid', int, []),
  718.         ],
  719.  
  720.         'setCount': [
  721.             ('uid', int, []),
  722.             ('count', int, []),
  723.         ],
  724.  
  725.         # setting counters for multiple users
  726.         'multiDecrementCount': [
  727.             ('uids', list, []),
  728.         ],
  729.  
  730.         'multiIncrementCount': [
  731.             ('uids', list, []),
  732.         ],
  733.  
  734.         'multiGetCount': [
  735.             ('uids', list, []),
  736.         ],
  737.  
  738.         'multiSetCount': [
  739.             ('uids', list, []),
  740.         ],
  741.  
  742.         # setting news for a single user
  743.         'addNews': [
  744.             ('news', json, []),
  745.             ('image', str, ['optional']),
  746.             ('uid', int, ['optional']),
  747.         ],
  748.  
  749.         'getNews': [
  750.             ('uid', int, []),
  751.             ('news_ids', list, ['optional']),
  752.         ],
  753.  
  754.         'clearNews': [
  755.             ('uid', int, []),
  756.             ('news_ids', list, ['optional']),
  757.         ],
  758.  
  759.         # setting news for multiple users
  760.         'multiAddNews': [
  761.             ('uids', list, []),
  762.             ('news', json, []),
  763.             ('image', str, ['optional']),
  764.         ],
  765.  
  766.         'multiGetNews': [
  767.             ('uids', json, []),
  768.         ],
  769.  
  770.         'multiClearNews': [
  771.             ('uids', json, []),
  772.         ],
  773.  
  774.         # setting application news for all users
  775.         'addGlobalNews': [
  776.             ('news', json, []),
  777.             ('image', str, ['optional']),
  778.         ],
  779.  
  780.         'getGlobalNews': [
  781.             ('news_ids', list, ['optional']),
  782.         ],
  783.  
  784.         'clearGlobalNews': [
  785.             ('news_ids', list, ['optional']),
  786.         ],
  787.  
  788.         # user activity
  789.         'getActivity': [
  790.             ('activity_ids', list, ['optional']),
  791.         ],
  792.  
  793.         'publishActivity': [
  794.             ('activity', json, []),
  795.         ],
  796.  
  797.         'removeActivity': [
  798.             ('activity_ids', list, []),
  799.         ],
  800.     },
  801.  
  802.     # comments methods (beta)
  803.     'comments' : {
  804.         'add': [
  805.             # text should be after xid/object_id, but is required
  806.             ('text', str, []),
  807.             # One of xid and object_is is required. Can this be expressed?
  808.             ('xid', str, ['optional']),
  809.             ('object_id', str, ['optional']),
  810.             ('uid', int, ['optional']),
  811.             ('title', str, ['optional']),
  812.             ('url', str, ['optional']),
  813.             ('publish_to_stream', bool, [('default', False)]),
  814.         ],
  815.  
  816.         'remove': [
  817.             # One of xid and object_is is required. Can this be expressed?
  818.             ('xid', str, ['optional']),
  819.             ('object_id', str, ['optional']),
  820.             # comment_id should be required
  821.             ('comment_id', str, ['optional']),
  822.         ],
  823.  
  824.         'get': [
  825.             # One of xid and object_is is required. Can this be expressed?
  826.             ('xid', str, ['optional']),
  827.             ('object_id', str, ['optional']),
  828.         ],
  829.     }
  830. }
  831.  
  832.  
  833. def __fixup_param(name, klass, options, param):
  834.     optional = 'optional' in options
  835.     default = [x[1] for x in options if isinstance(x, tuple) and x[0] == 'default']
  836.     if default:
  837.         default = default[0]
  838.     else:
  839.         default = None
  840.     if param is None:
  841.         if klass is list and default:
  842.             param = default[:]
  843.         else:
  844.             param = default
  845.     if klass is json and isinstance(param, (list, dict)):
  846.         param = simplejson.dumps(param)
  847.     return param
  848.  
  849. def __generate_facebook_method(namespace, method_name, param_data):
  850.     # This method-level option forces the method to be signed rather than using
  851.     # the access_token
  852.     signed = False
  853.     if 'signed' in param_data:
  854.         signed = True
  855.         param_data.remove('signed')
  856.  
  857.     # a required parameter doesn't have 'optional' in the options,
  858.     # and has no tuple option that starts with 'default'
  859.     required = [x for x in param_data
  860.             if 'optional' not in x[2]
  861.             and not [y for y in x[2] if isinstance(y, tuple) and y and y[0] == 'default']]
  862.  
  863.     def facebook_method(self, *args, **kwargs):
  864.         params = {}
  865.         for i, arg in enumerate(args):
  866.             params[param_data[i][0]] = arg
  867.         params.update(kwargs)
  868.  
  869.         for param in required:
  870.             if param[0] not in params:
  871.                 raise TypeError("missing parameter %s" % param[0])
  872.  
  873.         for name, klass, options in param_data:
  874.             if name in params:
  875.                 params[name] = __fixup_param(name, klass, options, params[name])
  876.  
  877.         return self(method_name, params, signed=signed)
  878.  
  879.     facebook_method.__name__ = method_name
  880.     facebook_method.__doc__ = "Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=%s.%s" % (namespace, method_name)
  881.  
  882.     return facebook_method
  883.  
  884.  
  885. class Proxy(object):
  886.     """Represents a "namespace" of Facebook API calls."""
  887.  
  888.     def __init__(self, client, name):
  889.         self._client = client
  890.         self._name = name
  891.  
  892.     def __call__(self, method=None, args=None, add_session_args=True, signed=False):
  893.         # for Django templates
  894.         if method is None:
  895.             return self
  896.  
  897.         if add_session_args:
  898.             self._client._add_session_args(args)
  899.  
  900.         return self._client('%s.%s' % (self._name, method), args, signed=signed)
  901.  
  902.  
  903. # generate the Facebook proxies
  904. def __generate_proxies():
  905.     for namespace in METHODS:
  906.         methods = {}
  907.  
  908.         for method, param_data in METHODS[namespace].iteritems():
  909.             methods[method] = __generate_facebook_method(namespace, method, param_data)
  910.  
  911.         proxy = type('%sProxy' % namespace.title(), (Proxy,), methods)
  912.  
  913.         globals()[proxy.__name__] = proxy
  914.  
  915. __generate_proxies()
  916.  
  917.  
  918.  
  919.  
  920.  
  921. class FacebookError(Exception):
  922.     """Exception class for errors received from Facebook."""
  923.  
  924.     def __init__(self, code, msg, args=None):
  925.         self.code = code
  926.         self.msg = msg
  927.         self.extra_args = args
  928.         Exception.__init__(self, code, msg, args)
  929.  
  930.     def __str__(self):
  931.         return 'Error %s: %s' % (self.code, self.msg)
  932.  
  933.  
  934. class AuthProxy(AuthProxy):
  935.     """Special proxy for facebook.auth."""
  936.  
  937.     def getSession(self):
  938.         """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.getSession"""
  939.         args = {}
  940.         try:
  941.             args['auth_token'] = self._client.auth_token
  942.         except AttributeError:
  943.             raise RuntimeError('Client does not have auth_token set.')
  944.         try:
  945.             args['generate_session_secret'] = self._client.generate_session_secret
  946.         except AttributeError:
  947.             pass
  948.         result = self._client('%s.getSession' % self._name, args)
  949.         self._client.session_key = result['session_key']
  950.         self._client.uid = result['uid']
  951.         self._client.secret = result.get('secret')
  952.         self._client.session_key_expires = result['expires']
  953.         return result
  954.  
  955.     def createToken(self):
  956.         """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.createToken"""
  957.         token = self._client('%s.createToken' % self._name)
  958.         self._client.auth_token = token
  959.         return token
  960.  
  961.  
  962. class FriendsProxy(FriendsProxy):
  963.     """Special proxy for facebook.friends."""
  964.  
  965.     def get(self, **kwargs):
  966.         """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=friends.get"""
  967.         if not kwargs.get('flid') and self._client._friends:
  968.             return self._client._friends
  969.         return super(FriendsProxy, self).get(**kwargs)
  970.  
  971.  
  972. class PhotosProxy(PhotosProxy):
  973.     """Special proxy for facebook.photos."""
  974.  
  975.     def upload(self, image, aid=None, uid=None, caption=None, size=(720, 720), filename=None, callback=None):
  976.         """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=photos.upload
  977.  
  978.        size -- an optional size (width, height) to resize the image to before uploading. Resizes by default
  979.                to Facebook's maximum display dimension of 720.
  980.        """
  981.         args = {}
  982.  
  983.         if uid is not None:
  984.             args['uid'] = uid
  985.  
  986.         if aid is not None:
  987.             args['aid'] = aid
  988.  
  989.         if caption is not None:
  990.             args['caption'] = caption
  991.  
  992.         if self._client.oauth2:
  993.             url = self._client.facebook_secure_url
  994.         else:
  995.             url = self._client.facebook_url
  996.  
  997.         args = self._client._build_post_args('facebook.photos.upload', self._client._add_session_args(args))
  998.  
  999.         try:
  1000.             import cStringIO as StringIO
  1001.         except ImportError:
  1002.             import StringIO
  1003.  
  1004.         # check for a filename specified...if the user is passing binary data in
  1005.         # image then a filename will be specified
  1006.         if filename is None:
  1007.             try:
  1008.                 import Image
  1009.             except ImportError:
  1010.                 data = StringIO.StringIO(open(image, 'rb').read())
  1011.             else:
  1012.                 img = Image.open(image)
  1013.                 if size:
  1014.                     img.thumbnail(size, Image.ANTIALIAS)
  1015.                 data = StringIO.StringIO()
  1016.                 img.save(data, img.format)
  1017.         else:
  1018.             # there was a filename specified, which indicates that image was not
  1019.             # the path to an image file but rather the binary data of a file
  1020.             data = StringIO.StringIO(image)
  1021.             image = filename
  1022.  
  1023.         content_type, body = self.__encode_multipart_formdata(list(args.iteritems()), [(image, data)])
  1024.         urlinfo = urlparse.urlsplit(url)
  1025.         try:
  1026.             content_length = len(body)
  1027.             chunk_size = 4096
  1028.  
  1029.             if self._client.oauth2:
  1030.                 h = httplib.HTTPSConnection(urlinfo[1])
  1031.             else:
  1032.                 h = httplib.HTTPConnection(urlinfo[1])
  1033.             h.putrequest('POST', urlinfo[2])
  1034.             h.putheader('Content-Type', content_type)
  1035.             h.putheader('Content-Length', str(content_length))
  1036.             h.putheader('MIME-Version', '1.0')
  1037.             h.putheader('User-Agent', 'PyFacebook Client Library')
  1038.             h.endheaders()
  1039.  
  1040.             if callback:
  1041.                 count = 0
  1042.                 while len(body) > 0:
  1043.                     if len(body) < chunk_size:
  1044.                         data = body
  1045.                         body = ''
  1046.                     else:
  1047.                         data = body[0:chunk_size]
  1048.                         body = body[chunk_size:]
  1049.  
  1050.                     h.send(data)
  1051.                     count += 1
  1052.                     callback(count, chunk_size, content_length)
  1053.             else:
  1054.                 h.send(body)
  1055.  
  1056.             response = h.getresponse()
  1057.  
  1058.             if response.status != 200:
  1059.                 raise Exception('Error uploading photo: Facebook returned HTTP %s (%s)' % (response.status, response.reason))
  1060.             response = response.read()
  1061.         except:
  1062.             # sending the photo failed, perhaps we are using GAE
  1063.             try:
  1064.                 from google.appengine.api import urlfetch
  1065.  
  1066.                 try:
  1067.                     response = urlread(url=url,data=body,headers={'POST':urlinfo[2],'Content-Type':content_type,'MIME-Version':'1.0'})
  1068.                 except urllib2.URLError:
  1069.                     raise Exception('Error uploading photo: Facebook returned %s' % (response))
  1070.             except ImportError:
  1071.                 # could not import from google.appengine.api, so we are not running in GAE
  1072.                 raise Exception('Error uploading photo.')
  1073.  
  1074.         return self._client._parse_response(response, 'facebook.photos.upload')
  1075.  
  1076.  
  1077.     def __encode_multipart_formdata(self, fields, files):
  1078.         """Encodes a multipart/form-data message to upload an image."""
  1079.         boundary = '-------tHISiStheMulTIFoRMbOUNDaRY'
  1080.         crlf = '\r\n'
  1081.         l = []
  1082.  
  1083.         for (key, value) in fields:
  1084.             l.append('--' + boundary)
  1085.             l.append('Content-Disposition: form-data; name="%s"' % str(key))
  1086.             l.append('')
  1087.             l.append(str(value))
  1088.         for (filename, value) in files:
  1089.             l.append('--' + boundary)
  1090.             l.append('Content-Disposition: form-data; filename="%s"' % (str(filename), ))
  1091.             l.append('Content-Type: %s' % self.__get_content_type(filename))
  1092.             l.append('')
  1093.             l.append(value.getvalue())
  1094.         l.append('--' + boundary + '--')
  1095.         l.append('')
  1096.         body = crlf.join(l)
  1097.         content_type = 'multipart/form-data; boundary=%s' % boundary
  1098.         return content_type, body
  1099.  
  1100.  
  1101.     def __get_content_type(self, filename):
  1102.         """Returns a guess at the MIME type of the file from the filename."""
  1103.         return str(mimetypes.guess_type(filename)[0]) or 'application/octet-stream'
  1104.  
  1105.  
  1106. class VideoProxy(VideoProxy):
  1107.     """Special proxy for facebook.video."""
  1108.  
  1109.     def upload(self, video, aid=None, title=None, description=None, filename=None, uid=None, privacy=None, callback=None):
  1110.         """Facebook API call. See http://wiki.developers.facebook.com/index.php/Video.upload"""
  1111.         args = {}
  1112.  
  1113.         if aid is not None:
  1114.             args['aid'] = aid
  1115.  
  1116.         if title is not None:
  1117.             args['title'] = title
  1118.  
  1119.         if description is not None:
  1120.             args['description'] = title
  1121.            
  1122.         if uid is not None:
  1123.             args['uid'] = uid
  1124.  
  1125.         if privacy is not None:
  1126.             args['privacy'] = privacy
  1127.  
  1128.         args = self._client._build_post_args('facebook.video.upload', self._client._add_session_args(args))
  1129.  
  1130.         try:
  1131.             import cStringIO as StringIO
  1132.         except ImportError:
  1133.             import StringIO
  1134.  
  1135.         # check for a filename specified...if the user is passing binary data in
  1136.         # video then a filename will be specified
  1137.         if filename is None:
  1138.             data = StringIO.StringIO(open(video, 'rb').read())
  1139.         else:
  1140.             # there was a filename specified, which indicates that video was not
  1141.             # the path to an video file but rather the binary data of a file
  1142.             data = StringIO.StringIO(video)
  1143.             video = filename
  1144.  
  1145.         content_type, body = self.__encode_multipart_formdata(list(args.iteritems()), [(video, data)])
  1146.        
  1147.         # Set the correct End Point Url, note this is different to all other FB EndPoints
  1148.         urlinfo = urlparse.urlsplit(FACEBOOK_VIDEO_URL)
  1149.         try:
  1150.             content_length = len(body)
  1151.             chunk_size = 4096
  1152.  
  1153.             h = httplib.HTTPConnection(urlinfo[1])
  1154.             h.putrequest('POST', urlinfo[2])
  1155.             h.putheader('Content-Type', content_type)
  1156.             h.putheader('Content-Length', str(content_length))
  1157.             h.putheader('MIME-Version', '1.0')
  1158.             h.putheader('User-Agent', 'PyFacebook Client Library')
  1159.             h.endheaders()
  1160.  
  1161.             if callback:
  1162.                 count = 0
  1163.                 while len(body) > 0:
  1164.                     if len(body) < chunk_size:
  1165.                         data = body
  1166.                         body = ''
  1167.                     else:
  1168.                         data = body[0:chunk_size]
  1169.                         body = body[chunk_size:]
  1170.  
  1171.                     h.send(data)
  1172.                     count += 1
  1173.                     callback(count, chunk_size, content_length)
  1174.             else:
  1175.                 h.send(body)
  1176.  
  1177.             response = h.getresponse()
  1178.  
  1179.             if response.status != 200:
  1180.                 raise Exception('Error uploading video: Facebook returned HTTP %s (%s)' % (response.status, response.reason))
  1181.             response = response.read()
  1182.         except:
  1183.             # sending the photo failed, perhaps we are using GAE
  1184.             try:
  1185.                 from google.appengine.api import urlfetch
  1186.  
  1187.                 try:
  1188.                     response = urlread(url=FACEBOOK_VIDEO_URL,data=body,headers={'POST':urlinfo[2],'Content-Type':content_type,'MIME-Version':'1.0'})
  1189.                 except urllib2.URLError:
  1190.                     raise Exception('Error uploading video: Facebook returned %s' % (response))
  1191.             except ImportError:
  1192.                 # could not import from google.appengine.api, so we are not running in GAE
  1193.                 raise Exception('Error uploading video.')
  1194.  
  1195.         return self._client._parse_response(response, 'facebook.video.upload')
  1196.  
  1197.  
  1198.     def __encode_multipart_formdata(self, fields, files):
  1199.         """Encodes a multipart/form-data message to upload an image."""
  1200.         boundary = '-------tHISiStheMulTIFoRMbOUNDaRY'
  1201.         crlf = '\r\n'
  1202.         l = []
  1203.  
  1204.         for (key, value) in fields:
  1205.             l.append('--' + boundary)
  1206.             l.append('Content-Disposition: form-data; name="%s"' % str(key))
  1207.             l.append('')
  1208.             l.append(str(value))
  1209.         for (filename, value) in files:
  1210.             l.append('--' + boundary)
  1211.             l.append('Content-Disposition: form-data; filename="%s"' % (str(filename), ))
  1212.             l.append('Content-Type: %s' % self.__get_content_type(filename))
  1213.             l.append('')
  1214.             l.append(value.getvalue())
  1215.         l.append('--' + boundary + '--')
  1216.         l.append('')
  1217.         body = crlf.join(l)
  1218.         content_type = 'multipart/form-data; boundary=%s' % boundary
  1219.         return content_type, body
  1220.  
  1221.  
  1222.     def __get_content_type(self, filename):
  1223.         """Returns a guess at the MIME type of the file from the filename."""
  1224.         return str(mimetypes.guess_type(filename)[0]) or 'application/octet-stream'
  1225.  
  1226.  
  1227. class Facebook(object):
  1228.     """
  1229.    Provides access to the Facebook API.
  1230.  
  1231.    Instance Variables:
  1232.  
  1233.    added
  1234.        True if the user has added this application.
  1235.  
  1236.    api_key
  1237.        Your API key, as set in the constructor.
  1238.  
  1239.    app_id
  1240.        Your application id, as set in the constructor or fetched from
  1241.        fb_sig_app_id request parameter.
  1242.  
  1243.    app_name
  1244.        Your application's name, i.e. the APP_NAME in http://apps.facebook.com/APP_NAME/ if
  1245.        this is for an internal web application. Optional, but useful for automatic redirects
  1246.        to canvas pages.
  1247.  
  1248.    auth_token
  1249.        The auth token that Facebook gives you, either with facebook.auth.createToken,
  1250.        or through a GET parameter.
  1251.  
  1252.    callback_path
  1253.        The path of the callback set in the Facebook app settings. If your callback is set
  1254.        to http://www.example.com/facebook/callback/, this should be '/facebook/callback/'.
  1255.        Optional, but useful for automatic redirects back to the same page after login.
  1256.  
  1257.    desktop
  1258.        True if this is a desktop app, False otherwise. Used for determining how to
  1259.        authenticate.
  1260.  
  1261.    ext_perms
  1262.        Any extended permissions that the user has granted to your application.
  1263.        This parameter is set only if the user has granted any.
  1264.  
  1265.    facebook_url
  1266.        The url to use for Facebook requests.
  1267.  
  1268.    facebook_secure_url
  1269.        The url to use for secure Facebook requests.
  1270.  
  1271.    in_canvas
  1272.        True if the current request is for a canvas page.
  1273.  
  1274.    in_iframe
  1275.        True if the current request is for an HTML page to embed in Facebook inside an iframe.
  1276.  
  1277.    is_session_from_cookie
  1278.        True if the current request session comes from a session cookie.
  1279.  
  1280.    in_profile_tab
  1281.        True if the current request is for a user's tab for your application.
  1282.  
  1283.    internal
  1284.        True if this Facebook object is for an internal application (one that can be added on Facebook)
  1285.  
  1286.    locale
  1287.        The user's locale. Default: 'en_US'
  1288.  
  1289.    oauth2:
  1290.        Whether to use the new OAuth 2.0 authentication mechanism.  Default: False
  1291.  
  1292.    oauth2_token:
  1293.        The current OAuth 2.0 token.
  1294.    
  1295.    oauth2_token_expires:
  1296.        The UNIX time when the OAuth 2.0 token expires (seconds).
  1297.  
  1298.    page_id
  1299.        Set to the page_id of the current page (if any)
  1300.  
  1301.    profile_update_time
  1302.        The time when this user's profile was last updated. This is a UNIX timestamp. Default: None if unknown.
  1303.  
  1304.    secret
  1305.        Secret that is used after getSession for desktop apps.
  1306.  
  1307.    secret_key
  1308.        Your application's secret key, as set in the constructor.
  1309.  
  1310.    session_key
  1311.        The current session key. Set automatically by auth.getSession, but can be set
  1312.        manually for doing infinite sessions.
  1313.  
  1314.    session_key_expires
  1315.        The UNIX time of when this session key expires, or 0 if it never expires.
  1316.  
  1317.    uid
  1318.        After a session is created, you can get the user's UID with this variable. Set
  1319.        automatically by auth.getSession.
  1320.  
  1321.    ----------------------------------------------------------------------
  1322.  
  1323.    """
  1324.  
  1325.     def __init__(self, api_key, secret_key, auth_token=None, app_name=None,
  1326.                  callback_path=None, internal=None, proxy=None,
  1327.                  facebook_url=None, facebook_secure_url=None,
  1328.                  generate_session_secret=0, app_id=None, oauth2=False):
  1329.         """
  1330.        Initializes a new Facebook object which provides wrappers for the Facebook API.
  1331.  
  1332.        If this is a desktop application, the next couple of steps you might want to take are:
  1333.  
  1334.        facebook.auth.createToken() # create an auth token
  1335.        facebook.login()            # show a browser window
  1336.        wait_login()                # somehow wait for the user to log in
  1337.        facebook.auth.getSession()  # get a session key
  1338.  
  1339.        For web apps, if you are passed an auth_token from Facebook, pass that in as a named parameter.
  1340.        Then call:
  1341.  
  1342.        facebook.auth.getSession()
  1343.  
  1344.        """
  1345.         self.api_key = api_key
  1346.         self.secret_key = secret_key
  1347.         self.app_id = app_id
  1348.         self.oauth2 = oauth2
  1349.         self.oauth2_token = None
  1350.         self.oauth2_token_expires = None
  1351.         self.session_key = None
  1352.         self.session_key_expires = None
  1353.         self.auth_token = auth_token
  1354.         self.secret = None
  1355.         self.generate_session_secret = generate_session_secret
  1356.         self.uid = None
  1357.         self.page_id = None
  1358.         self.in_canvas = False
  1359.         self.in_iframe = False
  1360.         self.is_session_from_cookie = False
  1361.         self.in_profile_tab = False
  1362.         self.added = False
  1363.         self.app_name = app_name
  1364.         self.callback_path = callback_path
  1365.         self.internal = internal
  1366.         self._friends = None
  1367.         self.locale = 'en_US'
  1368.         self.profile_update_time = None
  1369.         self.ext_perms = []
  1370.         self.proxy = proxy
  1371.         if facebook_url is None:
  1372.             self.facebook_url = FACEBOOK_URL
  1373.         else:
  1374.             self.facebook_url = facebook_url
  1375.         if facebook_secure_url is None:
  1376.             self.facebook_secure_url = FACEBOOK_SECURE_URL
  1377.         else:
  1378.             self.facebook_secure_url = facebook_secure_url
  1379.  
  1380.         for namespace in METHODS:
  1381.             self.__dict__[namespace] = eval('%sProxy(self, \'%s\')' % (namespace.title(), 'facebook.%s' % namespace))
  1382.  
  1383.     def _hash_args(self, args, secret=None):
  1384.         """Hashes arguments by joining key=value pairs, appending a secret, and then taking the MD5 hex digest."""
  1385.         # @author: houyr
  1386.         # fix for UnicodeEncodeError
  1387.         hasher = hashlib.md5(''.join(['%s=%s' % (isinstance(x, unicode) and x.encode("utf-8") or x, isinstance(args[x], unicode) and args[x].encode("utf-8") or args[x]) for x in sorted(args.keys())]))
  1388.         if secret:
  1389.             hasher.update(secret)
  1390.         elif self.secret:
  1391.             hasher.update(self.secret)
  1392.         else:
  1393.             hasher.update(self.secret_key)
  1394.         return hasher.hexdigest()
  1395.  
  1396.  
  1397.     def _parse_response_item(self, node):
  1398.         """Parses an XML response node from Facebook."""
  1399.         if node.nodeType == node.DOCUMENT_NODE and \
  1400.             node.childNodes[0].hasAttributes() and \
  1401.             node.childNodes[0].hasAttribute('list') and \
  1402.             node.childNodes[0].getAttribute('list') == "true":
  1403.             return {node.childNodes[0].nodeName: self._parse_response_list(node.childNodes[0])}
  1404.         elif node.nodeType == node.ELEMENT_NODE and \
  1405.             node.hasAttributes() and \
  1406.             node.hasAttribute('list') and \
  1407.             node.getAttribute('list')=="true":
  1408.             return self._parse_response_list(node)
  1409.         elif len(filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes)) > 0:
  1410.             return self._parse_response_dict(node)
  1411.         else:
  1412.             return ''.join(node.data for node in node.childNodes if node.nodeType == node.TEXT_NODE)
  1413.  
  1414.  
  1415.     def _parse_response_dict(self, node):
  1416.         """Parses an XML dictionary response node from Facebook."""
  1417.         result = {}
  1418.         for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
  1419.             result[item.nodeName] = self._parse_response_item(item)
  1420.         if node.nodeType == node.ELEMENT_NODE and node.hasAttributes():
  1421.             if node.hasAttribute('id'):
  1422.                 result['id'] = node.getAttribute('id')
  1423.         return result
  1424.  
  1425.  
  1426.     def _parse_response_list(self, node):
  1427.         """Parses an XML list response node from Facebook."""
  1428.         result = []
  1429.         for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
  1430.             result.append(self._parse_response_item(item))
  1431.         return result
  1432.  
  1433.  
  1434.     def _check_error(self, response):
  1435.         """Checks if the given Facebook response is an error, and then raises the appropriate exception."""
  1436.         if type(response) is dict and response.has_key('error_code'):
  1437.             raise FacebookError(response['error_code'], response['error_msg'], response['request_args'])
  1438.  
  1439.  
  1440.     def _build_post_args(self, method, args=None, signed=False):
  1441.         """Adds to args parameters that are necessary for every call to the API."""
  1442.         if args is None:
  1443.             args = {}
  1444.  
  1445.         for arg in args.items():
  1446.             if type(arg[1]) == list:
  1447.                 args[arg[0]] = ','.join(str(a) for a in arg[1])
  1448.             elif type(arg[1]) == unicode:
  1449.                 args[arg[0]] = arg[1].encode("UTF-8")
  1450.             elif type(arg[1]) == bool:
  1451.                 args[arg[0]] = str(arg[1]).lower()
  1452.  
  1453.         args['method'] = method
  1454.         args['format'] = RESPONSE_FORMAT
  1455.         if not signed and self.oauth2 and self.oauth2_token:
  1456.             args['access_token'] = self.oauth2_token
  1457.         else:
  1458.             args['api_key'] = self.api_key
  1459.             args['v'] = '1.0'
  1460.             args['sig'] = self._hash_args(args)
  1461.  
  1462.         return args
  1463.  
  1464.  
  1465.     def _add_session_args(self, args=None):
  1466.         """Adds 'session_key' and 'call_id' to args, which are used for API calls that need sessions."""
  1467.         if args is None:
  1468.             args = {}
  1469.  
  1470.         if not self.session_key:
  1471.             return args
  1472.             #some calls don't need a session anymore. this might be better done in the markup
  1473.             #raise RuntimeError('Session key not set. Make sure auth.getSession has been called.')
  1474.  
  1475.         if not self.oauth2 or not self.oauth2_token:
  1476.             args['session_key'] = self.session_key
  1477.         args['call_id'] = str(int(time.time() * 1000))
  1478.  
  1479.         return args
  1480.  
  1481.  
  1482.     def _parse_response(self, response, method, format=None):
  1483.         """Parses the response according to the given (optional) format, which should be either 'JSON' or 'XML'."""
  1484.         if not format:
  1485.             format = RESPONSE_FORMAT
  1486.  
  1487.         if format == 'JSON':
  1488.             result = simplejson.loads(response)
  1489.  
  1490.             self._check_error(result)
  1491.         elif format == 'XML':
  1492.             dom = minidom.parseString(response)
  1493.             result = self._parse_response_item(dom)
  1494.             dom.unlink()
  1495.  
  1496.             if 'error_response' in result:
  1497.                 self._check_error(result['error_response'])
  1498.  
  1499.             result = result[method[9:].replace('.', '_') + '_response']
  1500.         else:
  1501.             raise RuntimeError('Invalid format specified.')
  1502.  
  1503.         return result
  1504.  
  1505.  
  1506.     def hash_email(self, email):
  1507.         """
  1508.        Hash an email address in a format suitable for Facebook Connect.
  1509.  
  1510.        """
  1511.         email = email.lower().strip()
  1512.         return "%s_%s" % (
  1513.             struct.unpack("I", struct.pack("i", binascii.crc32(email)))[0],
  1514.             hashlib.md5(email).hexdigest(),
  1515.         )
  1516.  
  1517.  
  1518.     def unicode_urlencode(self, params):
  1519.         """
  1520.        @author: houyr
  1521.        A unicode aware version of urllib.urlencode.
  1522.        """
  1523.         if isinstance(params, dict):
  1524.             params = params.items()
  1525.         return urllib.urlencode([(k, isinstance(v, unicode) and v.encode('utf-8') or v)
  1526.                           for k, v in params])
  1527.  
  1528.  
  1529.     def __call__(self, method=None, args=None, secure=False, signed=False):
  1530.         """Make a call to Facebook's REST server."""
  1531.         # for Django templates, if this object is called without any arguments
  1532.         # return the object itself
  1533.         if method is None:
  1534.             return self
  1535.  
  1536.         # __init__ hard-codes into en_US
  1537.         if args is not None and not args.has_key('locale'):
  1538.             args['locale'] = self.locale
  1539.  
  1540.         # @author: houyr
  1541.         # fix for bug of UnicodeEncodeError
  1542.         post_data = self.unicode_urlencode(self._build_post_args(method, args, signed))
  1543.  
  1544.         if self.proxy:
  1545.             proxy_handler = urllib2.ProxyHandler(self.proxy)
  1546.             opener = urllib2.build_opener(proxy_handler)
  1547.             if self.oauth2 or secure:
  1548.                 response = opener.open(self.facebook_secure_url, post_data).read()
  1549.             else:
  1550.                 response = opener.open(self.facebook_url, post_data).read()
  1551.         else:
  1552.             if self.oauth2 or secure:
  1553.                 response = urlread(self.facebook_secure_url, post_data)
  1554.             else:
  1555.                 response = urlread(self.facebook_url, post_data)
  1556.  
  1557.         return self._parse_response(response, method)
  1558.  
  1559.  
  1560.     def oauth2_access_token(self, code, next, required_permissions=None):
  1561.         """
  1562.        We've called authorize, and received a code, now we need to convert
  1563.        this to an access_token
  1564.        
  1565.        """
  1566.         args = {
  1567.             'client_id': self.app_id,
  1568.             'client_secret': self.secret_key,
  1569.             'redirect_uri': next,
  1570.             'code': code
  1571.         }
  1572.  
  1573.         if required_permissions:
  1574.             args['scope'] = ",".join(required_permissions)
  1575.  
  1576.         # TODO see if immediate param works as per OAuth 2.0 spec?
  1577.         url = self.get_graph_url('oauth/access_token', **args)
  1578.        
  1579.         if self.proxy:
  1580.             proxy_handler = urllib2.ProxyHandler(self.proxy)
  1581.             opener = urllib2.build_opener(proxy_handler)
  1582.             response = opener.open(url).read()
  1583.         else:
  1584.             response = urlread(url)
  1585.  
  1586.         result = urlparse.parse_qs(response)
  1587.         self.oauth2_token = result['access_token'][0]
  1588.         self.oauth2_token_expires = time.time() + int(result['expires'][0])
  1589.        
  1590.  
  1591.     # URL helpers
  1592.     def get_url(self, page, **args):
  1593.         """
  1594.        Returns one of the Facebook URLs (www.facebook.com/SOMEPAGE.php).
  1595.        Named arguments are passed as GET query string parameters.
  1596.  
  1597.        """
  1598.         return 'http://www.facebook.com/%s.php?%s' % (page, urllib.urlencode(args))
  1599.  
  1600.  
  1601.     def get_app_url(self, path=''):
  1602.         """
  1603.        Returns the URL for this app's canvas page, according to app_name.
  1604.  
  1605.        """
  1606.         return 'http://apps.facebook.com/%s/%s' % (self.app_name, path)
  1607.  
  1608.  
  1609.     def get_graph_url(self, path='', **args):
  1610.         """
  1611.        Returns the URL for the graph API with the supplied path and parameters
  1612.  
  1613.        """
  1614.         return 'https://graph.facebook.com/%s?%s' % (path, urllib.urlencode(args))
  1615.  
  1616.  
  1617.     def get_add_url(self, next=None):
  1618.         """
  1619.        Returns the URL that the user should be redirected to in order to add the application.
  1620.  
  1621.        """
  1622.         args = {'api_key': self.api_key, 'v': '1.0'}
  1623.  
  1624.         if next is not None:
  1625.             args['next'] = next
  1626.  
  1627.         return self.get_url('install', **args)
  1628.  
  1629.  
  1630.     def get_authorize_url(self, next=None, next_cancel=None):
  1631.         """
  1632.        Returns the URL that the user should be redirected to in order to
  1633.        authorize certain actions for application.
  1634.  
  1635.        """
  1636.         args = {'api_key': self.api_key, 'v': '1.0'}
  1637.  
  1638.         if next is not None:
  1639.             args['next'] = next
  1640.  
  1641.         if next_cancel is not None:
  1642.             args['next_cancel'] = next_cancel
  1643.  
  1644.         return self.get_url('authorize', **args)
  1645.  
  1646.  
  1647.     def get_login_url(self, next=None, popup=False, canvas=True,
  1648.                       required_permissions=None):
  1649.         """
  1650.        Returns the URL that the user should be redirected to in order to login.
  1651.  
  1652.        next -- the URL that Facebook should redirect to after login
  1653.        required_permissions -- permission required by the application
  1654.  
  1655.        """
  1656.         if self.oauth2:
  1657.             args = {
  1658.                 'client_id': self.app_id,
  1659.                 'redirect_uri': next
  1660.             }
  1661.  
  1662.             if required_permissions:
  1663.                 args['scope'] = required_permissions
  1664.            
  1665.             if popup:
  1666.                 args['display'] = 'popup'
  1667.            
  1668.             return self.get_graph_url('oauth/authorize', **args)
  1669.         else:
  1670.             args = {'api_key': self.api_key, 'v': '1.0'}
  1671.    
  1672.             if next is not None:
  1673.                 args['next'] = next
  1674.    
  1675.             if canvas is True:
  1676.                 args['canvas'] = 1
  1677.    
  1678.             if popup is True:
  1679.                 args['popup'] = 1
  1680.    
  1681.             if required_permissions:
  1682.                 args['req_perms'] = ",".join(required_permissions)
  1683.                
  1684.             if self.auth_token is not None:
  1685.                 args['auth_token'] = self.auth_token
  1686.    
  1687.             return self.get_url('login', **args)
  1688.  
  1689.  
  1690.     def login(self, popup=False):
  1691.         """Open a web browser telling the user to login to Facebook."""
  1692.         import webbrowser
  1693.         webbrowser.open(self.get_login_url(popup=popup))
  1694.  
  1695.  
  1696.     def get_ext_perm_url(self, ext_perm, next=None, popup=False):
  1697.         """
  1698.        Returns the URL that the user should be redirected to in order to grant an extended permission.
  1699.  
  1700.        ext_perm -- the name of the extended permission to request
  1701.        next     -- the URL that Facebook should redirect to after login
  1702.  
  1703.        """
  1704.         args = {'ext_perm': ext_perm, 'api_key': self.api_key, 'v': '1.0'}
  1705.  
  1706.         if next is not None:
  1707.             args['next'] = next
  1708.  
  1709.         if popup is True:
  1710.             args['popup'] = 1
  1711.  
  1712.         return self.get_url('authorize', **args)
  1713.  
  1714.  
  1715.     def request_extended_permission(self, ext_perm, popup=False):
  1716.         """Open a web browser telling the user to grant an extended permission."""
  1717.         import webbrowser
  1718.         webbrowser.open(self.get_ext_perm_url(ext_perm, popup=popup))
  1719.  
  1720.  
  1721.     def check_session(self, request, params=None):
  1722.         """
  1723.        Checks the given Django HttpRequest for Facebook parameters such as
  1724.        POST variables or an auth token. If the session is valid, returns True
  1725.        and this object can now be used to access the Facebook API. Otherwise,
  1726.        it returns False, and the application should take the appropriate action
  1727.        (either log the user in or have him add the application).
  1728.  
  1729.        """
  1730.         self.in_canvas = (request.POST.get('fb_sig_in_canvas') == '1')
  1731.  
  1732.         if not 'auth_token' in request.GET and self.session_key and (self.uid or self.page_id):
  1733.             return True
  1734.  
  1735.         if not params:
  1736.             if request.method == 'POST':
  1737.                 params = self.validate_signature(request.POST)
  1738.             else:
  1739.                 if 'installed' in request.GET:
  1740.                     self.added = True
  1741.  
  1742.                 if 'fb_page_id' in request.GET:
  1743.                     self.page_id = request.GET['fb_page_id']
  1744.  
  1745.                 if 'auth_token' in request.GET:
  1746.                     self.added = True # added by Marinho
  1747.                     self.auth_token = request.GET['auth_token']
  1748.  
  1749.                     try:
  1750.                         self.auth.getSession()
  1751.                     except FacebookError, e:
  1752.                         self.auth_token = None
  1753.                         return False
  1754.  
  1755.                     return True
  1756.  
  1757.                 params = self.validate_signature(request.GET)
  1758.  
  1759.         if not params:
  1760.             cookies = None
  1761.            
  1762.             # first check if we are in django - to check cookies
  1763.             if hasattr(request, 'COOKIES'):
  1764.                 cookies = request.COOKIES
  1765.             else:
  1766.                 if hasattr(request, 'cookies'):
  1767.                     cookies = request.cookies
  1768.  
  1769.             if cookies:
  1770.  
  1771.                 params = self.validate_oauth_cookie_signature(cookies)
  1772.  
  1773.                 if params:
  1774.                     self.is_oauth = True
  1775.                     self.oauth_token = params['access_token']
  1776.                 else:
  1777.                     params = self.validate_cookie_signature(cookies)
  1778.                     self.is_session_from_cookie = True
  1779.  
  1780.         if not params:
  1781.             if self.validate_iframe(request):
  1782.                 return True
  1783.             else:
  1784.                 params = {}
  1785.  
  1786.         if not params:
  1787.             return False
  1788.  
  1789.         if 'in_canvas' in params and params.get('in_canvas') == '1':
  1790.             self.in_canvas = True
  1791.  
  1792.         if 'in_iframe' in params and params.get('in_iframe') == '1':
  1793.             self.in_iframe = True
  1794.  
  1795.         if 'in_profile_tab' in params and params.get('in_profile_tab') == '1':
  1796.             self.in_profile_tab = True
  1797.  
  1798.         if 'added' in params and params.get('added') == '1':
  1799.             self.added = True
  1800.  
  1801.         if params.get('expires'):
  1802.             # Marinho Brandao - fixing problem with no session
  1803.             self.session_key_expires = params.get('expires', '').isdigit() and int(params['expires']) or 0
  1804.  
  1805.         if 'locale' in params:
  1806.             self.locale = params['locale']
  1807.  
  1808.         if 'profile_update_time' in params:
  1809.             try:
  1810.                 self.profile_update_time = int(params['profile_update_time'])
  1811.             except ValueError:
  1812.                 pass
  1813.        
  1814.         if 'ext_perms' in params:
  1815.             if params['ext_perms']:
  1816.                 self.ext_perms = params['ext_perms'].split(',')
  1817.             else:
  1818.                 self.ext_perms = []
  1819.  
  1820.         if 'friends' in params:
  1821.             if params['friends']:
  1822.                 self._friends = params['friends'].split(',')
  1823.             else:
  1824.                 self._friends = []
  1825.  
  1826.         # If app_id is not set explicitly, pick it up from the param
  1827.         if not self.app_id and 'app_id' in params:
  1828.             self.app_id = params['app_id']
  1829.  
  1830.         if 'session_key' in params:
  1831.             self.session_key = params['session_key']
  1832.             if 'user' in params:
  1833.                 self.uid = params['user']
  1834.             elif 'page_id' in params:
  1835.                 self.page_id = params['page_id']
  1836.             elif 'uid' in params:
  1837.                 self.uid = params['uid']
  1838.             else:
  1839.                 return False
  1840.         elif 'profile_session_key' in params:
  1841.             self.session_key = params['profile_session_key']
  1842.             if 'profile_user' in params:
  1843.                 self.uid = params['profile_user']
  1844.             else:
  1845.                 return False
  1846.         elif 'canvas_user' in params:
  1847.             self.uid = params['canvas_user']
  1848.         elif 'uninstall' in params:
  1849.             self.uid = params['user']
  1850.         else:
  1851.             return False
  1852.  
  1853.         return True
  1854.  
  1855.     def validate_oauth_session(self, session_json):
  1856.         session = simplejson.loads(session_json)
  1857.         sig = session.pop('sig')
  1858.         hash = self._hash_args(session)
  1859.         if hash == sig:
  1860.             return session
  1861.         return None
  1862.  
  1863.     def validate_signature(self, post, prefix='fb_sig', timeout=None):
  1864.         """
  1865.        Validate parameters passed to an internal Facebook app from Facebook.
  1866.  
  1867.        """
  1868.         args = post.copy()
  1869.  
  1870.         if prefix not in args:
  1871.             return None
  1872.  
  1873.         del args[prefix]
  1874.  
  1875.         if timeout and '%s_time' % prefix in post and time.time() - float(post['%s_time' % prefix]) > timeout:
  1876.             return None
  1877.  
  1878.         args = dict([(key[len(prefix + '_'):], value) for key, value in args.items() if key.startswith(prefix)])
  1879.  
  1880.         hash = self._hash_args(args)
  1881.  
  1882.         if hash == post[prefix]:
  1883.             return args
  1884.         else:
  1885.             return None
  1886.  
  1887.     def validate_iframe(self, request):
  1888.         request_dict = request.POST if request.method == 'POST' else request.GET
  1889.         if any(not request_dict.has_key(key) for key in ['userid','reqtime','appsig']):
  1890.             return False
  1891.         request_time = request_dict['reqtime']
  1892.         time_now = int(time.time())
  1893.         if time_now - int(request_time) > settings.FACEBOOK_IFRAME_VALIDATION_TIMEOUT:
  1894.             return False
  1895.         userid = int(request_dict['userid'])
  1896.         self.uid = userid
  1897.         app_sig = request_dict['appsig']
  1898.         digest = create_hmac("%s%s" % (str(userid),str(request_time)))
  1899.         return digest == app_sig
  1900.  
  1901.     def validate_oauth_cookie_signature(self, cookies):
  1902.         cookie_name = 'fbs_%s' % self.app_id
  1903.         if cookie_name in cookies:
  1904.             # value like
  1905.             # "access_token=104302089510310%7C2.HYYLow1Vlib0s_sJSAESjw__.3600.1275037200-100000214342553%7CtC1aolM22Lauj_dZhYnv_tF2CK4.&base_domain=yaycy.com&expires=1275037200&secret=FvIHkbAFwEy_0sueRk2ZYQ__&session_key=2.HYYoow1Vlib0s_sJSAESjw__.3600.1275037200-100000214342553&sig=7bb035a0411be7aa801964ae34416f28&uid=100000214342553"
  1906.             params = dict([part.split('=') for part in cookies[cookie_name]])
  1907.             sig = params.pop('sig')
  1908.             hash = self._hash_args(params)
  1909.             if hash == sig:
  1910.                 return params
  1911.         return None
  1912.  
  1913.     def validate_cookie_signature(self, cookies):
  1914.         """
  1915.        Validate parameters passed by cookies, namely facebookconnect or js api.
  1916.        """
  1917.        
  1918.         api_key = self.api_key
  1919.         if api_key not in cookies:
  1920.             return None
  1921.  
  1922.         prefix = api_key + "_"
  1923.  
  1924.         params = {}
  1925.         vals = ''
  1926.         for k in sorted(cookies):
  1927.             if k.startswith(prefix):
  1928.                 key = k.replace(prefix,"")
  1929.                 value = cookies[k]
  1930.                 if value != 'None':
  1931.                     params[key] = value
  1932.                     vals += '%s=%s' % (key, value)
  1933.                
  1934.         hasher = hashlib.md5(vals)
  1935.  
  1936.         hasher.update(self.secret_key)
  1937.         digest = hasher.hexdigest()
  1938.         if digest == cookies[api_key]:
  1939.             params['is_session_from_cookie'] = True
  1940.             return params
  1941.         else:
  1942.             return False
  1943.    
  1944.     # new methods for GAE
  1945.     def check_connect_session(self, request):
  1946.         params = self.validate_cookie(request.cookies)
  1947.        
  1948.         if not params:
  1949.             return False
  1950.        
  1951.         if params.get('expires'):
  1952.             self.session_key_expires = int(params['expires'])
  1953.        
  1954.         if 'session_key' in params and 'user' in params:
  1955.             self.session_key = params['session_key']
  1956.             self.uid = params['user']
  1957.         else:
  1958.             return False
  1959.        
  1960.         return True
  1961.    
  1962.     def validate_cookie(self, cookies):
  1963.         #check for the hashed secret
  1964.         if self.api_key not in cookies:
  1965.             return None
  1966.        
  1967.         #create a dict of the elements that start with the api_key
  1968.         #the resultant dict removes the self.api_key from the beginning
  1969.         args = dict([(key[len(self.api_key) + 1:], value)
  1970.                         for key, value in cookies.items()
  1971.                         if key.startswith(self.api_key+"_")])
  1972.        
  1973.         #check the hashes before returning them
  1974.         if self._hash_args(args) == cookies[self.api_key]:
  1975.             return args
  1976.        
  1977.         return None
  1978.  
  1979.  
  1980.  
  1981. if __name__ == '__main__':
  1982.     # sample desktop application
  1983.  
  1984.     api_key = ''
  1985.     secret_key = ''
  1986.  
  1987.     facebook = Facebook(api_key, secret_key)
  1988.  
  1989.     facebook.auth.createToken()
  1990.  
  1991.     # Show login window
  1992.     # Set popup=True if you want login without navigational elements
  1993.     facebook.login()
  1994.  
  1995.     # Login to the window, then press enter
  1996.     print 'After logging in, press enter...'
  1997.     raw_input()
  1998.  
  1999.     facebook.auth.getSession()
  2000.     print 'Session Key:   ', facebook.session_key
  2001.     print 'Your UID:      ', facebook.uid
  2002.  
  2003.     info = facebook.users.getInfo([facebook.uid], ['name', 'birthday', 'affiliations', 'sex'])[0]
  2004.  
  2005.     print 'Your Name:     ', info['name']
  2006.     print 'Your Birthday: ', info['birthday']
  2007.     print 'Your Gender:   ', info['sex']
  2008.  
  2009.     friends = facebook.friends.get()
  2010.     friends = facebook.users.getInfo(friends[0:5], ['name', 'birthday', 'relationship_status'])
  2011.  
  2012.     for friend in friends:
  2013.         print friend['name'], 'has a birthday on', friend['birthday'], 'and is', friend['relationship_status']
  2014.  
  2015.     arefriends = facebook.friends.areFriends([friends[0]['uid']], [friends[1]['uid']])
  2016.  
  2017.     photos = facebook.photos.getAlbums(facebook.uid)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement