-
Notifications
You must be signed in to change notification settings - Fork 0
/
facebewk.py
executable file
·168 lines (144 loc) · 5.86 KB
/
facebewk.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import copy
import json
import requests
BASE_URL = 'https://graph.facebook.com'
class Client(object):
def __init__(self, access_token):
"""Accepts:
access_token: an access token for an authenticated session
"""
self.access_token = access_token
def get(self, id=None, params=None, url=None):
"""Make a GET request to the Graph API, return a Node object"""
if not id and not url:
raise Exception('Either a Node ID or absolute URL must be specified.')
params = self._sanitize_params(params)
fetched = True
if url:
retval = self._get(None, params, full_url=url)
else:
if 'fields' in params:
fetched = False
retval = self._get(id, params)
if 'id' in retval:
return Node(retval, self, fetched=fetched)
else:
return Node._process_datapoint(retval, self)
def _get(self, id, params, full_url=None):
"""Make a GET request to the Graph API, return a JSON object"""
if full_url:
retval = requests.get(full_url, params=params).json()
else:
retval = requests.get('{0}/{1}'.format(BASE_URL, id),
params=params).json()
self._check_error(retval)
return retval
def post(self, node, params):
"""Publish a post or comment to the Graph API"""
params = self._sanitize_params(params)
url = '{0}/{1}/'.format(BASE_URL, node['id'])
if not node['__fetched__']:
node.refresh()
if node.get('type') in ['post', 'status', 'link']:
url += 'comments'
else:
url += 'feed'
url += '?access_token={0}'.format(self.access_token)
retval = requests.post(url, data=params).json()
self._check_error(retval)
return Node(retval, self, fetched=False)
def like(self, node, params=None, delete=False):
"""'Like' a Node (post, link, comment, etc)"""
params = self._sanitize_params(params)
url = '{0}/{1}/likes/'.format(BASE_URL, node['id'])
if delete:
retval = requests.delete(url, data=params).json()
else:
retval = requests.post(url, data=params).json()
# successful 'like' operation should always return True:
if retval is not True:
self._check_error(retval)
return retval
def unlike(self, node, params=None):
"""'Unlike' a Node"""
return self.like(node, params, delete=True)
def delete(self, node, params=None):
"""Delete a node from the Graph"""
params = self._sanitize_params(params)
url = '{0}/{1}/'.format(BASE_URL, node['id'])
retval = requests.delete(url, data=params).json()
if retval is not True:
self._check_error(retval)
return retval
def _sanitize_params(self, params):
"""Set default parameters, sanitize for a possible POST operation"""
if not params:
params = {}
for key in params:
if type(params[key]) in [list, dict]:
params[key] = json.dumps(params[key])
params.setdefault('access_token', self.access_token)
return params
def _check_error(self, data):
"""Check for errors returned by the Graph API.
data: should be a dictionary, or at least iterable.
"""
if 'error' in data:
raise ServerSideException(data['error'].get('message'))
class Node(dict):
def __init__(self, obj, client, fetched=False):
"""Accepts:
obj: JSON-parsed response from Graph API
client: facebewk.Client object
fetched: Whether the entire object has been fetched
"""
self['__client__'] = client
self['__fetched__'] = fetched
if isinstance(obj, basestring):
obj = json.loads(obj)
if 'id' not in obj:
raise KeyError('All Nodes must have an ID %s' % obj)
for key in obj:
self[key] = self._process_datapoint(obj[key], client)
def __getitem__(self, key):
"""Try to return the value for a certain key, and if missing:
Fetch the whole Node if we haven't already and try again.
Otherwise raise an KeyError.
"""
try:
return dict.__getitem__(self, key)
except KeyError:
if not self['__fetched__']:
self.refresh() # this will automatically grab all node data
if key in self:
return self[key]
if 'type' in self:
msg = "Node {0} of type '{1}' has no key '{2}'".format(
self['id'], self['type'], key)
else:
msg = "Node {0} has no key '{1}'".format(self['id'], key)
raise KeyError(msg)
def __repr__(self):
"""Clean __client__ objects from the __repr__() output"""
clean = copy.copy(self)
del clean['__client__']
return dict.__repr__(clean)
def refresh(self):
"""Refresh a node's data"""
self.update(self['__client__'].get(self['id']))
@classmethod
def _process_datapoint(node, data, client):
"""Process json data from facebook, recursively if necessary,
producing Node objects where possible.
"""
if isinstance(data, list):
data = [node._process_datapoint(entry, client) for entry in data]
elif isinstance(data, dict):
if 'id' in data:
data = Node(data, client)
else:
for key in data:
data[key] = node._process_datapoint(data[key], client)
return data
class ServerSideException(Exception):
pass