def get_activities_response(self, **kwargs): type = self.auth_entity.get().type kwargs.setdefault('fetch_events', True) kwargs.setdefault('fetch_news', type == 'user') kwargs.setdefault('event_owner_id', self.key.id()) try: activities = super(FacebookPage, self).get_activities_response(**kwargs) except urllib2.HTTPError as e: code, body = util.interpret_http_exception(e) # use a function so any new exceptions (JSON decoding, missing keys) don't # clobber the original exception so we can re-raise it below. def dead_token(): try: err = json.loads(body)['error'] return (err.get('code') in DEAD_TOKEN_ERROR_CODES or err.get('error_subcode') in DEAD_TOKEN_ERROR_SUBCODES or err.get('message') in DEAD_TOKEN_ERROR_MESSAGES) except: logging.exception( "Couldn't determine whether token is still valid") return False if code == '401': if not dead_token() and type == 'user': # ask the user to reauthenticate. if this API call fails, it will raise # urllib2.HTTPError instead of DisableSource, so that we don't disable # the source without notifying. # # TODO: for pages, fetch the owners/admins and notify them. self.gr_source.create_notification( self.key.id(), "Bridgy's access to your account has expired. Click here to renew it now!", 'https://brid.gy/facebook/start') raise models.DisableSource() raise # update the resolved_object_ids and post_publics caches def parsed_post_id(id): parsed = gr_facebook.Facebook.parse_id(id) return parsed.post if parsed.post else id resolved = self._load_cache('resolved_object_ids') for activity in activities['items']: obj = activity.get('object', {}) obj_id = parsed_post_id(obj.get('fb_id')) ids = obj.get('fb_object_for_ids') if obj_id and ids: resolved[obj_id] = obj_id for id in ids: resolved[parsed_post_id(id)] = obj_id for activity in activities['items']: self.is_activity_public(activity) return activities
def test_handle_disable_source(self): self.mox.StubOutWithMock(testutil.FakeSource, 'get_activities') testutil.FakeSource.get_activities( activity_id='000', user_id=self.source.key.string_id()).AndRaise( models.DisableSource()) self.mox.ReplayAll() resp = self.check_response('/post/fake/%s/000', expected_status=401) self.assertIn("Bridgy's access to your account has expired", resp.body)
def get_activities_response(self, *args, **kwargs): """Set user_id manually. ...since Reddit sometimes (always?) 400s our calls to https://oauth.reddit.com/api/v1/me (via PRAW's Reddit.user.me() ). """ kwargs.setdefault('user_id', self.username) if kwargs.get('count'): kwargs['count'] = min(kwargs['count'], 10) try: return super().get_activities_response(*args, **kwargs) except NotFound: # this user was deleted or banned raise models.DisableSource()
def poll(self, source): """Actually runs the poll. Stores property names and values to update in source.updates. """ if source.last_activities_etag or source.last_activity_id: logging.debug('Using ETag %s, last activity id %s', source.last_activities_etag, source.last_activity_id) # # Step 1: fetch activities: # * posts by the user # * search all posts for the user's domain URLs to find links # cache = util.CacheDict() if source.last_activities_cache_json: cache.update(json.loads(source.last_activities_cache_json)) try: # search for links first so that the user's activities and responses # override them if they overlap links = source.search_for_links() # this user's own activities (and user mentions) resp = source.get_activities_response( fetch_replies=True, fetch_likes=True, fetch_shares=True, fetch_mentions=True, count=50, etag=source.last_activities_etag, min_id=source.last_activity_id, cache=cache) etag = resp.get('etag') # used later user_activities = resp.get('items', []) # these map ids to AS objects responses = {a['id']: a for a in links} activities = {a['id']: a for a in links + user_activities} except Exception, e: code, body = util.interpret_http_exception(e) if code == '401': msg = 'Unauthorized error: %s' % e logging.warning(msg, exc_info=True) source.updates['poll_status'] = 'ok' raise models.DisableSource(msg) elif code in util.HTTP_RATE_LIMIT_CODES: logging.warning( 'Rate limited. Marking as error and finishing. %s', e) source.updates.update({ 'poll_status': 'error', 'rate_limited': True }) return elif (code and int(code) / 100 == 5) or util.is_connection_failure(e): logging.error( 'API call failed. Marking as error and finishing. %s: %s\n%s', code, body, e) self.abort(ERROR_HTTP_RETURN_CODE) else: raise