Skip to content

Commit

Permalink
twitter: add block list support
Browse files Browse the repository at this point in the history
  • Loading branch information
snarfed committed Jul 21, 2017
1 parent 87be228 commit 98737f3
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ TODO: release oauth-dropins 1.8 and bump dependency version here before releasin

* Add [JSON Feed](https://jsonfeed.org/) support to both library and REST API.
* Twitter:
* Add `get_blocklist()`.
* Bug fix for creating replies, favorites, or retweets of video URLs, e.g. https://twitter.com/name/status/123/video/1 .
* Bug fix for parsing favorites HTML to handle a small change on Twitter's side.
* `post_id()` now validates ids more strictly before returning them.
Expand Down
11 changes: 11 additions & 0 deletions granary/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,17 @@ def get_rsvp(self, activity_user_id, event_id, user_id, event=None):
if id and user_id == util.parse_tag_uri(id)[1]:
return rsvp

def get_blocklist(self):
"""Returns the current user's block list.
...ie the users that the current user is blocking. The exact semantics of
"blocking" vary from silo to silo.
Returns:
sequence of actor objects
"""
raise NotImplementedError()

def user_to_actor(self, user):
"""Converts a user to an actor.
Expand Down
60 changes: 54 additions & 6 deletions granary/test/test_twitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@
from granary import source
from granary import twitter
from granary.twitter import (
API_FAVORITES, API_LIST_TIMELINE, API_LOOKUP, API_RETWEETS, API_SEARCH,
API_STATUS, API_TIMELINE, API_USER_TIMELINE, HTML_FAVORITES)
API_BLOCKS,
API_FAVORITES,
API_LIST_TIMELINE,
API_LOOKUP,
API_RETWEETS,
API_SEARCH,
API_STATUS,
API_TIMELINE,
API_USER_TIMELINE,
HTML_FAVORITES,
)

__author__ = ['Ryan Barrett <granary@ryanb.org>']

Expand Down Expand Up @@ -54,14 +63,24 @@ def tag_uri(name):
'url': 'http://t.co/456',
'expanded_url': 'http://link/456',
}]},
},
}
},
}
USER_2 = {
'name': 'Alice Foo',
'screen_name': 'alice',
'id_str': '777',
}
USER_3 = {
'name': 'Bob Bar',
'screen_name': 'bob',
'id_str': '666',
}
ACTOR = { # ActivityStreams
'objectType': 'person',
'displayName': 'Ryan Barrett',
'image': {
'url': 'http://a0.twimg.com/profile_images/866165047/ryan.jpg',
},
},
'id': tag_uri('snarfed_org'),
'numeric_id': '888',
'published': '2010-05-01T21:42:43+00:00',
Expand All @@ -73,7 +92,23 @@ def tag_uri(name):
'location': {'displayName': 'San Francisco'},
'username': 'snarfed_org',
'description': 'my description',
}
}
ACTOR_2 = {
'objectType': 'person',
'displayName': 'Alice Foo',
'id': tag_uri('alice'),
'numeric_id': '777',
'username': 'alice',
'url': 'https://twitter.com/alice',
}
ACTOR_3 = {
'objectType': 'person',
'displayName': 'Bob Bar',
'id': tag_uri('bob'),
'numeric_id': '666',
'username': 'bob',
'url': 'https://twitter.com/bob',
}
# Twitter
# (extended tweet: https://dev.twitter.com/overview/api/upcoming-changes-to-tweets )
TWEET = {
Expand Down Expand Up @@ -1074,6 +1109,19 @@ def test_get_share_bad_id(self):
"""https://github.com/snarfed/bridgy/issues/719"""
self.assertRaises(ValueError, self.twitter.get_share, None, None, '123:abc')

def test_get_blocklist(self):
self.expect_urlopen(API_BLOCKS % '-1', {
'users': [USER],
'next_cursor_str': '9',
})
self.expect_urlopen(API_BLOCKS % '9', {
'users': [USER_2, USER_3],
'next_cursor_str': '0',
})
self.mox.ReplayAll()
self.assert_equals([ACTOR, ACTOR_2, ACTOR_3],
self.twitter.get_blocklist())

def test_tweet_to_activity_full(self):
self.assert_equals(ACTIVITY, self.twitter.tweet_to_activity(TWEET))

Expand Down
39 changes: 29 additions & 10 deletions granary/twitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,22 @@
from oauth_dropins.webutil import util

API_BASE = 'https://api.twitter.com/1.1/'
API_TIMELINE = 'statuses/home_timeline.json?include_entities=true&tweet_mode=extended&count=%d'
API_USER_TIMELINE = 'statuses/user_timeline.json?include_entities=true&tweet_mode=extended&count=%(count)d&screen_name=%(screen_name)s'
API_LIST_TIMELINE = 'lists/statuses.json?include_entities=true&tweet_mode=extended&count=%(count)d&slug=%(slug)s&owner_screen_name=%(owner_screen_name)s'
API_STATUS = 'statuses/show.json?id=%s&include_entities=true&tweet_mode=extended'
API_LOOKUP = 'statuses/lookup.json?id=%s&include_entities=true&tweet_mode=extended'
API_RETWEETS = 'statuses/retweets.json?id=%s&tweet_mode=extended'
API_USER = 'users/show.json?screen_name=%s'
API_BLOCKS = 'blocks/list.json?skip_status=true&cursor=%s'
API_CURRENT_USER = 'account/verify_credentials.json'
API_SEARCH = 'search/tweets.json?q=%(q)s&include_entities=true&tweet_mode=extended&result_type=recent&count=%(count)d'
API_FAVORITES = 'favorites/list.json?screen_name=%s&include_entities=true&tweet_mode=extended'
API_POST_TWEET = 'statuses/update.json'
API_POST_RETWEET = 'statuses/retweet/%s.json'
API_LIST_TIMELINE = 'lists/statuses.json?include_entities=true&tweet_mode=extended&count=%(count)d&slug=%(slug)s&owner_screen_name=%(owner_screen_name)s'
API_LOOKUP = 'statuses/lookup.json?id=%s&include_entities=true&tweet_mode=extended'
API_POST_FAVORITE = 'favorites/create.json'
API_POST_MEDIA = 'statuses/update_with_media.json'
API_POST_RETWEET = 'statuses/retweet/%s.json'
API_POST_TWEET = 'statuses/update.json'
API_RETWEETS = 'statuses/retweets.json?id=%s&tweet_mode=extended'
API_SEARCH = 'search/tweets.json?q=%(q)s&include_entities=true&tweet_mode=extended&result_type=recent&count=%(count)d'
API_STATUS = 'statuses/show.json?id=%s&include_entities=true&tweet_mode=extended'
API_TIMELINE = 'statuses/home_timeline.json?include_entities=true&tweet_mode=extended&count=%d'
API_UPLOAD_MEDIA = 'https://upload.twitter.com/1.1/media/upload.json'
API_USER = 'users/show.json?screen_name=%s'
API_USER_TIMELINE = 'statuses/user_timeline.json?include_entities=true&tweet_mode=extended&count=%(count)d&screen_name=%(screen_name)s'
HTML_FAVORITES = 'https://twitter.com/i/activity/favorited_popup?id=%s'

TWEET_URL_RE = re.compile(r'https://twitter\.com/[^/?]+/status(es)?/[^/?]+$')
Expand Down Expand Up @@ -538,6 +539,24 @@ def get_share(self, activity_user_id, activity_id, share_id, activity=None):
url = API_STATUS % share_id
return self.retweet_to_object(self.urlopen(url))

def get_blocklist(self):
"""Returns the current user's block list.
May make multiple API calls, using cursors, to fully fetch large blocklists.
https://dev.twitter.com/overview/api/cursoring
Returns:
sequence of actor objects
"""
blocks = []
cursor = '-1'
while cursor and cursor != '0':
resp = self.urlopen(API_BLOCKS % cursor)
blocks.extend(self.user_to_actor(user) for user in resp.get('users', []))
cursor = resp.get('next_cursor_str')

return blocks

def create(self, obj, include_link=source.OMIT_LINK,
ignore_formatting=False):
"""Creates a tweet, reply tweet, retweet, or favorite.
Expand Down

0 comments on commit 98737f3

Please sign in to comment.