1#!/usr/bin/env python
2
3#
4#
5# Copyright 2007-2016, 2018 The Python-Twitter Developers
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18
19"""A library that provides a Python interface to the Twitter API"""
20from __future__ import division
21from __future__ import print_function
22
23import json
24import sys
25import gzip
26import time
27import base64
28import re
29import logging
30import requests
31from requests_oauthlib import OAuth1, OAuth2
32import io
33import warnings
34from uuid import uuid4
35import os
36
37try:
38    # python 3
39    from urllib.parse import urlparse, urlunparse, urlencode, quote_plus
40    from urllib.request import __version__ as urllib_version
41except ImportError:
42    from urlparse import urlparse, urlunparse
43    from urllib import urlencode, quote_plus
44    from urllib import __version__ as urllib_version
45
46from twitter import (
47    __version__,
48    _FileCache,
49    Category,
50    DirectMessage,
51    List,
52    Status,
53    Trend,
54    User,
55    UserStatus,
56)
57
58from twitter.ratelimit import RateLimit
59
60from twitter.twitter_utils import (
61    calc_expected_status_length,
62    is_url,
63    parse_media_file,
64    enf_type,
65    parse_arg_list)
66
67from twitter.error import (
68    TwitterError,
69    PythonTwitterDeprecationWarning330,
70)
71
72if sys.version_info > (3,):
73    long = int  # pylint: disable=invalid-name,redefined-builtin
74
75CHARACTER_LIMIT = 280
76
77# A singleton representing a lazily instantiated FileCache.
78DEFAULT_CACHE = object()
79
80logger = logging.getLogger(__name__)
81
82
83class Api(object):
84    """A python interface into the Twitter API
85
86    By default, the Api caches results for 1 minute.
87
88    Example usage:
89
90      To create an instance of the twitter.Api class, with no authentication:
91
92        >>> import twitter
93        >>> api = twitter.Api()
94
95      To fetch a single user's public status messages, where "user" is either
96      a Twitter "short name" or their user id.
97
98        >>> statuses = api.GetUserTimeline(user)
99        >>> print([s.text for s in statuses])
100
101      To use authentication, instantiate the twitter.Api class with a
102      consumer key and secret; and the oAuth key and secret:
103
104        >>> api = twitter.Api(consumer_key='twitter consumer key',
105                              consumer_secret='twitter consumer secret',
106                              access_token_key='the_key_given',
107                              access_token_secret='the_key_secret')
108
109      To fetch your friends (after being authenticated):
110
111        >>> users = api.GetFriends()
112        >>> print([u.name for u in users])
113
114      To post a twitter status message (after being authenticated):
115
116        >>> status = api.PostUpdate('I love python-twitter!')
117        >>> print(status.text)
118        I love python-twitter!
119
120      There are many other methods, including:
121
122        >>> api.PostUpdates(status)
123        >>> api.PostDirectMessage(user, text)
124        >>> api.GetUser(user)
125        >>> api.GetReplies()
126        >>> api.GetUserTimeline(user)
127        >>> api.GetHomeTimeline()
128        >>> api.GetStatus(status_id)
129        >>> api.GetStatuses(status_ids)
130        >>> api.DestroyStatus(status_id)
131        >>> api.GetFriends(user)
132        >>> api.GetFollowers()
133        >>> api.GetFeatured()
134        >>> api.GetDirectMessages()
135        >>> api.GetSentDirectMessages()
136        >>> api.PostDirectMessage(user, text)
137        >>> api.DestroyDirectMessage(message_id)
138        >>> api.DestroyFriendship(user)
139        >>> api.CreateFriendship(user)
140        >>> api.LookupFriendship(user)
141        >>> api.VerifyCredentials()
142    """
143
144    DEFAULT_CACHE_TIMEOUT = 60  # cache for 1 minute
145    _API_REALM = 'Twitter API'
146
147    def __init__(self,
148                 consumer_key=None,
149                 consumer_secret=None,
150                 access_token_key=None,
151                 access_token_secret=None,
152                 application_only_auth=False,
153                 input_encoding=None,
154                 request_headers=None,
155                 cache=DEFAULT_CACHE,
156                 base_url=None,
157                 stream_url=None,
158                 upload_url=None,
159                 chunk_size=1024 * 1024,
160                 use_gzip_compression=False,
161                 debugHTTP=False,
162                 timeout=None,
163                 sleep_on_rate_limit=False,
164                 tweet_mode='compat',
165                 proxies=None):
166        """Instantiate a new twitter.Api object.
167
168        Args:
169          consumer_key (str):
170            Your Twitter user's consumer_key.
171          consumer_secret (str):
172            Your Twitter user's consumer_secret.
173          access_token_key (str):
174            The oAuth access token key value you retrieved
175            from running get_access_token.py.
176          access_token_secret (str):
177            The oAuth access token's secret, also retrieved
178            from the get_access_token.py run.
179          application_only_auth:
180             Use Application-Only Auth instead of User Auth.
181             Defaults to False [Optional]
182          input_encoding (str, optional):
183            The encoding used to encode input strings.
184          request_header (dict, optional):
185            A dictionary of additional HTTP request headers.
186          cache (object, optional):
187            The cache instance to use. Defaults to DEFAULT_CACHE.
188            Use None to disable caching.
189          base_url (str, optional):
190            The base URL to use to contact the Twitter API.
191            Defaults to https://api.twitter.com.
192          stream_url (str, optional):
193            The base URL to use for streaming endpoints.
194            Defaults to 'https://stream.twitter.com/1.1'.
195          upload_url (str, optional):
196            The base URL to use for uploads. Defaults to 'https://upload.twitter.com/1.1'.
197          chunk_size (int, optional):
198            Chunk size to use for chunked (multi-part) uploads of images/videos/gifs.
199            Defaults to 1MB. Anything under 16KB and you run the risk of erroring out
200            on 15MB files.
201          use_gzip_compression (bool, optional):
202            Set to True to tell enable gzip compression for any call
203            made to Twitter.  Defaults to False.
204          debugHTTP (bool, optional):
205            Set to True to enable debug output from urllib2 when performing
206            any HTTP requests.  Defaults to False.
207          timeout (int, optional):
208            Set timeout (in seconds) of the http/https requests. If None the
209            requests lib default will be used.  Defaults to None.
210          sleep_on_rate_limit (bool, optional):
211            Whether to sleep an appropriate amount of time if a rate limit is hit for
212            an endpoint.
213          tweet_mode (str, optional):
214            Whether to use the new (as of Sept. 2016) extended tweet mode. See docs for
215            details. Choices are ['compatibility', 'extended'].
216          proxies (dict, optional):
217            A dictionary of proxies for the request to pass through, if not specified
218            allows requests lib to use environmental variables for proxy if any.
219        """
220
221        # check to see if the library is running on a Google App Engine instance
222        # see GAE.rst for more information
223        if os.environ:
224            if 'APPENGINE_RUNTIME' in os.environ.keys():
225                # Adapter ensures requests use app engine's urlfetch
226                import requests_toolbelt.adapters.appengine
227                requests_toolbelt.adapters.appengine.monkeypatch()
228                # App Engine does not like this caching strategy, disable caching
229                cache = None
230
231        self.SetCache(cache)
232        self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT
233        self._input_encoding = input_encoding
234        self._use_gzip = use_gzip_compression
235        self._debugHTTP = debugHTTP
236        self._shortlink_size = 19
237        if timeout and timeout < 30:
238            warnings.warn("Warning: The Twitter streaming API sends 30s keepalives, the given timeout is shorter!")
239        self._timeout = timeout
240        self.__auth = None
241
242        self._InitializeRequestHeaders(request_headers)
243        self._InitializeUserAgent()
244        self._InitializeDefaultParameters()
245
246        self.rate_limit = RateLimit()
247        self.sleep_on_rate_limit = sleep_on_rate_limit
248        self.tweet_mode = tweet_mode
249        self.proxies = proxies
250
251        if base_url is None:
252            self.base_url = 'https://api.twitter.com/1.1'
253        else:
254            self.base_url = base_url
255
256        if stream_url is None:
257            self.stream_url = 'https://stream.twitter.com/1.1'
258        else:
259            self.stream_url = stream_url
260
261        if upload_url is None:
262            self.upload_url = 'https://upload.twitter.com/1.1'
263        else:
264            self.upload_url = upload_url
265
266        self.chunk_size = chunk_size
267
268        if self.chunk_size < 1024 * 16:
269            warnings.warn((
270                "A chunk size lower than 16384 may result in too many "
271                "requests to the Twitter API when uploading videos. You are "
272                "strongly advised to increase it above 16384"))
273
274        if (consumer_key and not
275           (application_only_auth or all([access_token_key, access_token_secret]))):
276            raise TwitterError({'message': "Missing oAuth Consumer Key or Access Token"})
277
278        self.SetCredentials(consumer_key, consumer_secret, access_token_key, access_token_secret,
279                            application_only_auth)
280
281        if debugHTTP:
282            try:
283                import http.client as http_client  # python3
284            except ImportError:
285                import httplib as http_client  # python2
286
287            http_client.HTTPConnection.debuglevel = 1
288
289            logging.basicConfig()  # you need to initialize logging, otherwise you will not see anything from requests
290            logging.getLogger().setLevel(logging.DEBUG)
291            requests_log = logging.getLogger("requests.packages.urllib3")
292            requests_log.setLevel(logging.DEBUG)
293            requests_log.propagate = True
294
295        self._session = requests.Session()
296
297    @staticmethod
298    def GetAppOnlyAuthToken(consumer_key, consumer_secret):
299        """
300        Generate a Bearer Token from consumer_key and consumer_secret
301        """
302        key = quote_plus(consumer_key)
303        secret = quote_plus(consumer_secret)
304        bearer_token = base64.b64encode('{}:{}'.format(key, secret).encode('utf8'))
305
306        post_headers = {
307            'Authorization': 'Basic {0}'.format(bearer_token.decode('utf8')),
308            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
309        }
310
311        res = requests.post(url='https://api.twitter.com/oauth2/token',
312                            data={'grant_type': 'client_credentials'},
313                            headers=post_headers)
314        bearer_creds = res.json()
315        return bearer_creds
316
317    def SetCredentials(self,
318                       consumer_key,
319                       consumer_secret,
320                       access_token_key=None,
321                       access_token_secret=None,
322                       application_only_auth=False):
323        """Set the consumer_key and consumer_secret for this instance
324
325        Args:
326          consumer_key:
327            The consumer_key of the twitter account.
328          consumer_secret:
329            The consumer_secret for the twitter account.
330          access_token_key:
331            The oAuth access token key value you retrieved
332            from running get_access_token.py.
333          access_token_secret:
334            The oAuth access token's secret, also retrieved
335            from the get_access_token.py run.
336          application_only_auth:
337            Whether to generate a bearer token and use Application-Only Auth
338        """
339        self._consumer_key = consumer_key
340        self._consumer_secret = consumer_secret
341        self._access_token_key = access_token_key
342        self._access_token_secret = access_token_secret
343
344        if application_only_auth:
345            self._bearer_token = self.GetAppOnlyAuthToken(consumer_key, consumer_secret)
346            self.__auth = OAuth2(token=self._bearer_token)
347        else:
348            auth_list = [consumer_key, consumer_secret,
349                         access_token_key, access_token_secret]
350            if all(auth_list):
351                self.__auth = OAuth1(consumer_key, consumer_secret,
352                                     access_token_key, access_token_secret)
353
354        self._config = None
355
356    def GetHelpConfiguration(self):
357        """Get basic help configuration details from Twitter.
358
359        Args:
360            None
361
362        Returns:
363            dict: Sets self._config and returns dict of help config values.
364        """
365        if self._config is None:
366            url = '%s/help/configuration.json' % self.base_url
367            resp = self._RequestUrl(url, 'GET')
368            data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
369            self._config = data
370        return self._config
371
372    def GetShortUrlLength(self, https=False):
373        """Returns number of characters reserved per URL included in a tweet.
374
375        Args:
376            https (bool, optional):
377                If True, return number of characters reserved for https urls
378                or, if False, return number of character reserved for http urls.
379        Returns:
380            (int): Number of characters reserved per URL.
381        """
382        config = self.GetHelpConfiguration()
383        if https:
384            return config['short_url_length_https']
385        else:
386            return config['short_url_length']
387
388    def ClearCredentials(self):
389        """Clear any credentials for this instance
390        """
391        self._consumer_key = None
392        self._consumer_secret = None
393        self._access_token_key = None
394        self._access_token_secret = None
395        self._bearer_token = None
396        self.__auth = None  # for request upgrade
397
398    def GetSearch(self,
399                  term=None,
400                  raw_query=None,
401                  geocode=None,
402                  since_id=None,
403                  max_id=None,
404                  until=None,
405                  since=None,
406                  count=15,
407                  lang=None,
408                  locale=None,
409                  result_type="mixed",
410                  include_entities=None,
411                  return_json=False):
412        """Return twitter search results for a given term. You must specify one
413        of term, geocode, or raw_query.
414
415        Args:
416          term (str, optional):
417            Term to search by. Optional if you include geocode.
418          raw_query (str, optional):
419            A raw query as a string. This should be everything after the "?" in
420            the URL (i.e., the query parameters). You are responsible for all
421            type checking and ensuring that the query string is properly
422            formatted, as it will only be URL-encoded before be passed directly
423            to Twitter with no other checks performed. For advanced usage only.
424            *This will override any other parameters passed*
425          since_id (int, optional):
426            Returns results with an ID greater than (that is, more recent
427            than) the specified ID. There are limits to the number of
428            Tweets which can be accessed through the API. If the limit of
429            Tweets has occurred since the since_id, the since_id will be
430            forced to the oldest ID available.
431          max_id (int, optional):
432            Returns only statuses with an ID less than (that is, older
433            than) or equal to the specified ID.
434          until (str, optional):
435            Returns tweets generated before the given date. Date should be
436            formatted as YYYY-MM-DD.
437          since (str, optional):
438            Returns tweets generated since the given date. Date should be
439            formatted as YYYY-MM-DD.
440          geocode (str or list or tuple, optional):
441            Geolocation within which to search for tweets. Can be either a
442            string in the form of "latitude,longitude,radius" where latitude
443            and longitude are floats and radius is a string such as "1mi" or
444            "1km" ("mi" or "km" are the only units allowed). For example:
445              >>> api.GetSearch(geocode="37.781157,-122.398720,1mi").
446            Otherwise, you can pass a list of either floats or strings for
447            lat/long and a string for radius:
448              >>> api.GetSearch(geocode=[37.781157, -122.398720, "1mi"])
449              >>> # or:
450              >>> api.GetSearch(geocode=(37.781157, -122.398720, "1mi"))
451              >>> # or:
452              >>> api.GetSearch(geocode=("37.781157", "-122.398720", "1mi"))
453          count (int, optional):
454            Number of results to return.  Default is 15 and maxmimum that
455            Twitter returns is 100 irrespective of what you type in.
456          lang (str, optional):
457            Language for results as ISO 639-1 code.  Default is None
458            (all languages).
459          locale (str, optional):
460            Language of the search query. Currently only 'ja' is effective.
461            This is intended for language-specific consumers and the default
462            should work in the majority of cases.
463          result_type (str, optional):
464            Type of result which should be returned. Default is "mixed".
465            Valid options are "mixed, "recent", and "popular".
466          include_entities (bool, optional):
467            If True, each tweet will include a node called "entities".
468            This node offers a variety of metadata about the tweet in a
469            discrete structure, including: user_mentions, urls, and
470            hashtags.
471          return_json (bool, optional):
472            If True JSON data will be returned, instead of twitter.Userret
473        Returns:
474          list: A sequence of twitter.Status instances, one for each message
475          containing the term, within the bounds of the geocoded area, or
476          given by the raw_query.
477        """
478
479        url = '%s/search/tweets.json' % self.base_url
480        parameters = {}
481
482        if since_id:
483            parameters['since_id'] = enf_type('since_id', int, since_id)
484
485        if max_id:
486            parameters['max_id'] = enf_type('max_id', int, max_id)
487
488        if until:
489            parameters['until'] = enf_type('until', str, until)
490
491        if since:
492            parameters['since'] = enf_type('since', str, since)
493
494        if lang:
495            parameters['lang'] = enf_type('lang', str, lang)
496
497        if locale:
498            parameters['locale'] = enf_type('locale', str, locale)
499
500        if term is None and geocode is None and raw_query is None:
501            return []
502
503        if term is not None:
504            parameters['q'] = term
505
506        if geocode is not None:
507            if isinstance(geocode, list) or isinstance(geocode, tuple):
508                parameters['geocode'] = ','.join([str(geo) for geo in geocode])
509            else:
510                parameters['geocode'] = enf_type('geocode', str, geocode)
511
512        if include_entities:
513            parameters['include_entities'] = enf_type('include_entities',
514                                                      bool,
515                                                      include_entities)
516
517        parameters['count'] = enf_type('count', int, count)
518
519        if result_type in ["mixed", "popular", "recent"]:
520            parameters['result_type'] = result_type
521
522        if raw_query is not None:
523            url = "{url}?{raw_query}".format(
524                url=url,
525                raw_query=raw_query)
526            resp = self._RequestUrl(url, 'GET')
527        else:
528            resp = self._RequestUrl(url, 'GET', data=parameters)
529
530        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
531        if return_json:
532            return data
533        else:
534            return [Status.NewFromJsonDict(x) for x in data.get('statuses', '')]
535
536    def GetUsersSearch(self,
537                       term=None,
538                       page=1,
539                       count=20,
540                       include_entities=None):
541        """Return twitter user search results for a given term.
542
543        Args:
544          term:
545            Term to search by.
546          page:
547            Page of results to return. Default is 1
548            [Optional]
549          count:
550            Number of results to return.  Default is 20
551            [Optional]
552          include_entities:
553            If True, each tweet will include a node called "entities,".
554            This node offers a variety of metadata about the tweet in a
555            discrete structure, including: user_mentions, urls, and hashtags.
556            [Optional]
557
558        Returns:
559          A sequence of twitter.User instances, one for each message containing
560          the term
561        """
562        # Build request parameters
563        parameters = {}
564
565        if term is not None:
566            parameters['q'] = term
567
568        if page != 1:
569            parameters['page'] = page
570
571        if include_entities:
572            parameters['include_entities'] = 1
573
574        try:
575            parameters['count'] = int(count)
576        except ValueError:
577            raise TwitterError({'message': "count must be an integer"})
578
579        # Make and send requests
580        url = '%s/users/search.json' % self.base_url
581        resp = self._RequestUrl(url, 'GET', data=parameters)
582        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
583        return [User.NewFromJsonDict(x) for x in data]
584
585    def GetTrendsCurrent(self, exclude=None):
586        """Get the current top trending topics (global)
587
588        Args:
589          exclude:
590            Appends the exclude parameter as a request parameter.
591            Currently only exclude=hashtags is supported. [Optional]
592
593        Returns:
594          A list with 10 entries. Each entry contains a trend.
595        """
596        return self.GetTrendsWoeid(woeid=1, exclude=exclude)
597
598    def GetTrendsWoeid(self, woeid, exclude=None):
599        """Return the top 10 trending topics for a specific WOEID, if trending
600        information is available for it.
601
602        Args:
603          woeid:
604            the Yahoo! Where On Earth ID for a location.
605          exclude:
606            Appends the exclude parameter as a request parameter.
607            Currently only exclude=hashtags is supported. [Optional]
608
609        Returns:
610          A list with 10 entries. Each entry contains a trend.
611        """
612        url = '%s/trends/place.json' % (self.base_url)
613        parameters = {'id': woeid}
614
615        if exclude:
616            parameters['exclude'] = exclude
617
618        resp = self._RequestUrl(url, verb='GET', data=parameters)
619        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
620        trends = []
621        timestamp = data[0]['as_of']
622
623        for trend in data[0]['trends']:
624            trends.append(Trend.NewFromJsonDict(trend, timestamp=timestamp))
625        return trends
626
627    def GetUserSuggestionCategories(self):
628        """ Return the list of suggested user categories, this can be used in
629            GetUserSuggestion function
630        Returns:
631            A list of categories
632        """
633        url = '%s/users/suggestions.json' % (self.base_url)
634        resp = self._RequestUrl(url, verb='GET')
635        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
636
637        categories = []
638
639        for category in data:
640            categories.append(Category.NewFromJsonDict(category))
641        return categories
642
643    def GetUserSuggestion(self, category):
644        """ Returns a list of users in a category
645        Args:
646            category:
647                The Category object to limit the search by
648        Returns:
649            A list of users in that category
650        """
651        url = '%s/users/suggestions/%s.json' % (self.base_url, category.slug)
652
653        resp = self._RequestUrl(url, verb='GET')
654        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
655
656        users = []
657        for user in data['users']:
658            users.append(User.NewFromJsonDict(user))
659        return users
660
661    def GetHomeTimeline(self,
662                        count=None,
663                        since_id=None,
664                        max_id=None,
665                        trim_user=False,
666                        exclude_replies=False,
667                        contributor_details=False,
668                        include_entities=True):
669        """Fetch a collection of the most recent Tweets and retweets posted
670        by the authenticating user and the users they follow.
671
672        The home timeline is central to how most users interact with Twitter.
673
674        Args:
675          count:
676            Specifies the number of statuses to retrieve. May not be
677            greater than 200. Defaults to 20. [Optional]
678          since_id:
679            Returns results with an ID greater than (that is, more recent
680            than) the specified ID. There are limits to the number of
681            Tweets which can be accessed through the API. If the limit of
682            Tweets has occurred since the since_id, the since_id will be
683            forced to the oldest ID available. [Optional]
684          max_id:
685            Returns results with an ID less than (that is, older than) or
686            equal to the specified ID. [Optional]
687          trim_user:
688            When True, each tweet returned in a timeline will include a user
689            object including only the status authors numerical ID. Omit this
690            parameter to receive the complete user object. [Optional]
691          exclude_replies:
692            This parameter will prevent replies from appearing in the
693            returned timeline. Using exclude_replies with the count
694            parameter will mean you will receive up-to count tweets -
695            this is because the count parameter retrieves that many
696            tweets before filtering out retweets and replies. [Optional]
697          contributor_details:
698            This parameter enhances the contributors element of the
699            status response to include the screen_name of the contributor.
700            By default only the user_id of the contributor is included. [Optional]
701          include_entities:
702            The entities node will be disincluded when set to false.
703            This node offers a variety of metadata about the tweet in a
704            discreet structure, including: user_mentions, urls, and
705            hashtags. [Optional]
706
707        Returns:
708          A sequence of twitter.Status instances, one for each message
709        """
710        url = '%s/statuses/home_timeline.json' % self.base_url
711
712        parameters = {}
713        if count is not None:
714            try:
715                if int(count) > 200:
716                    raise TwitterError({'message': "'count' may not be greater than 200"})
717            except ValueError:
718                raise TwitterError({'message': "'count' must be an integer"})
719            parameters['count'] = count
720        if since_id:
721            try:
722                parameters['since_id'] = int(since_id)
723            except ValueError:
724                raise TwitterError({'message': "'since_id' must be an integer"})
725        if max_id:
726            try:
727                parameters['max_id'] = int(max_id)
728            except ValueError:
729                raise TwitterError({'message': "'max_id' must be an integer"})
730        if trim_user:
731            parameters['trim_user'] = 1
732        if exclude_replies:
733            parameters['exclude_replies'] = 1
734        if contributor_details:
735            parameters['contributor_details'] = 1
736        if not include_entities:
737            parameters['include_entities'] = 'false'
738        resp = self._RequestUrl(url, 'GET', data=parameters)
739        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
740
741        return [Status.NewFromJsonDict(x) for x in data]
742
743    def GetUserTimeline(self,
744                        user_id=None,
745                        screen_name=None,
746                        since_id=None,
747                        max_id=None,
748                        count=None,
749                        include_rts=True,
750                        trim_user=False,
751                        exclude_replies=False):
752        """Fetch the sequence of public Status messages for a single user.
753
754        The twitter.Api instance must be authenticated if the user is private.
755
756        Args:
757          user_id (int, optional):
758            Specifies the ID of the user for whom to return the
759            user_timeline. Helpful for disambiguating when a valid user ID
760            is also a valid screen name.
761          screen_name (str, optional):
762            Specifies the screen name of the user for whom to return the
763            user_timeline. Helpful for disambiguating when a valid screen
764            name is also a user ID.
765          since_id (int, optional):
766            Returns results with an ID greater than (that is, more recent
767            than) the specified ID. There are limits to the number of
768            Tweets which can be accessed through the API. If the limit of
769            Tweets has occurred since the since_id, the since_id will be
770            forced to the oldest ID available.
771          max_id (int, optional):
772            Returns only statuses with an ID less than (that is, older
773            than) or equal to the specified ID.
774          count (int, optional):
775            Specifies the number of statuses to retrieve. May not be
776            greater than 200.
777          include_rts (bool, optional):
778            If True, the timeline will contain native retweets (if they
779            exist) in addition to the standard stream of tweets.
780          trim_user (bool, optional):
781            If True, statuses will only contain the numerical user ID only.
782            Otherwise a full user object will be returned for each status.
783          exclude_replies (bool, optional)
784            If True, this will prevent replies from appearing in the returned
785            timeline. Using exclude_replies with the count parameter will mean you
786            will receive up-to count tweets - this is because the count parameter
787            retrieves that many tweets before filtering out retweets and replies.
788            This parameter is only supported for JSON and XML responses.
789
790        Returns:
791          A sequence of Status instances, one for each message up to count
792        """
793        url = '%s/statuses/user_timeline.json' % (self.base_url)
794        parameters = {}
795
796        if user_id:
797            parameters['user_id'] = enf_type('user_id', int, user_id)
798        elif screen_name:
799            parameters['screen_name'] = screen_name
800        if since_id:
801            parameters['since_id'] = enf_type('since_id', int, since_id)
802        if max_id:
803            parameters['max_id'] = enf_type('max_id', int, max_id)
804        if count:
805            parameters['count'] = enf_type('count', int, count)
806
807        parameters['include_rts'] = enf_type('include_rts', bool, include_rts)
808        parameters['trim_user'] = enf_type('trim_user', bool, trim_user)
809        parameters['exclude_replies'] = enf_type('exclude_replies', bool, exclude_replies)
810
811        resp = self._RequestUrl(url, 'GET', data=parameters)
812        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
813
814        return [Status.NewFromJsonDict(x) for x in data]
815
816    def GetStatus(self,
817                  status_id,
818                  trim_user=False,
819                  include_my_retweet=True,
820                  include_entities=True,
821                  include_ext_alt_text=True):
822        """Returns a single status message, specified by the status_id parameter.
823
824        Args:
825          status_id:
826            The numeric ID of the status you are trying to retrieve.
827          trim_user:
828            When set to True, each tweet returned in a timeline will include
829            a user object including only the status authors numerical ID.
830            Omit this parameter to receive the complete user object. [Optional]
831          include_my_retweet:
832            When set to True, any Tweets returned that have been retweeted by
833            the authenticating user will include an additional
834            current_user_retweet node, containing the ID of the source status
835            for the retweet. [Optional]
836          include_entities:
837            If False, the entities node will be disincluded.
838            This node offers a variety of metadata about the tweet in a
839            discreet structure, including: user_mentions, urls, and
840            hashtags. [Optional]
841        Returns:
842          A twitter.Status instance representing that status message
843        """
844        url = '%s/statuses/show.json' % (self.base_url)
845
846        parameters = {
847            'id': enf_type('status_id', int, status_id),
848            'trim_user': enf_type('trim_user', bool, trim_user),
849            'include_my_retweet': enf_type('include_my_retweet', bool, include_my_retweet),
850            'include_entities': enf_type('include_entities', bool, include_entities),
851            'include_ext_alt_text': enf_type('include_ext_alt_text', bool, include_ext_alt_text)
852        }
853
854        resp = self._RequestUrl(url, 'GET', data=parameters)
855        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
856
857        return Status.NewFromJsonDict(data)
858
859    def GetStatuses(self,
860                    status_ids,
861                    trim_user=False,
862                    include_entities=True,
863                    map=False):
864        """Returns a list of status messages, specified by the status_ids parameter.
865
866        Args:
867          status_ids:
868            A list of the numeric ID of the statuses you are trying to retrieve.
869          trim_user:
870            When set to True, each tweet returned in a timeline will include
871            a user object including only the status authors numerical ID.
872            Omit this parameter to receive the complete user object. [Optional]
873          include_entities:
874            If False, the entities node will be disincluded.
875            This node offers a variety of metadata about the tweet in a
876            discreet structure, including: user_mentions, urls, and
877            hashtags. [Optional]
878          map:
879            If True, returns a dictionary with status id as key and returned
880            status data (or None if tweet does not exist or is inaccessible)
881            as value. Otherwise returns an unordered list of successfully
882            retrieved Tweets. [Optional]
883        Returns:
884          A dictionary or unordered list (depending on the parameter 'map') of
885          twitter Status instances representing the status messages.
886        """
887        url = '%s/statuses/lookup.json' % (self.base_url)
888
889        map = enf_type('map', bool, map)
890
891        if map:
892            result = {}
893        else:
894            result = []
895        offset = 0
896        parameters = {
897            'trim_user': enf_type('trim_user', bool, trim_user),
898            'include_entities': enf_type('include_entities', bool, include_entities),
899            'map': map
900        }
901        while offset < len(status_ids):
902            parameters['id'] = ','.join([str(enf_type('status_id', int, status_id)) for status_id in status_ids[offset:offset + 100]])
903
904            resp = self._RequestUrl(url, 'GET', data=parameters)
905            data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
906            if map:
907                result.update({int(key): (Status.NewFromJsonDict(value) if value else None) for key, value in data['id'].items()})
908            else:
909                result += [Status.NewFromJsonDict(dataitem) for dataitem in data]
910
911            offset += 100
912
913        return result
914
915    def GetStatusOembed(self,
916                        status_id=None,
917                        url=None,
918                        maxwidth=None,
919                        hide_media=False,
920                        hide_thread=False,
921                        omit_script=False,
922                        align=None,
923                        related=None,
924                        lang=None):
925        """Returns information allowing the creation of an embedded representation of a
926        Tweet on third party sites.
927
928        Specify tweet by the id or url parameter.
929
930        Args:
931          status_id:
932            The numeric ID of the status you are trying to embed.
933          url:
934            The url of the status you are trying to embed.
935          maxwidth:
936            The maximum width in pixels that the embed should be rendered at.
937            This value is constrained to be between 250 and 550 pixels. [Optional]
938          hide_media:
939            Specifies whether the embedded Tweet should automatically expand images. [Optional]
940          hide_thread:
941            Specifies whether the embedded Tweet should automatically show the original
942            message in the case that the embedded Tweet is a reply. [Optional]
943          omit_script:
944            Specifies whether the embedded Tweet HTML should include a <script>
945            element pointing to widgets.js. [Optional]
946          align:
947            Specifies whether the embedded Tweet should be left aligned, right aligned,
948            or centered in the page. [Optional]
949          related:
950            A comma sperated string of related screen names. [Optional]
951          lang:
952            Language code for the rendered embed. [Optional]
953
954        Returns:
955          A dictionary with the response.
956        """
957        request_url = '%s/statuses/oembed.json' % (self.base_url)
958
959        parameters = {}
960
961        if status_id is not None:
962            try:
963                parameters['id'] = int(status_id)
964            except ValueError:
965                raise TwitterError({'message': "'status_id' must be an integer."})
966        elif url is not None:
967            parameters['url'] = url
968        else:
969            raise TwitterError({'message': "Must specify either 'status_id' or 'url'"})
970
971        if maxwidth is not None:
972            parameters['maxwidth'] = maxwidth
973        if hide_media is True:
974            parameters['hide_media'] = 'true'
975        if hide_thread is True:
976            parameters['hide_thread'] = 'true'
977        if omit_script is True:
978            parameters['omit_script'] = 'true'
979        if align is not None:
980            if align not in ('left', 'center', 'right', 'none'):
981                raise TwitterError({'message': "'align' must be 'left', 'center', 'right', or 'none'"})
982            parameters['align'] = align
983        if related:
984            if not isinstance(related, str):
985                raise TwitterError({'message': "'related' should be a string of comma separated screen names"})
986            parameters['related'] = related
987        if lang is not None:
988            if not isinstance(lang, str):
989                raise TwitterError({'message': "'lang' should be string instance"})
990            parameters['lang'] = lang
991
992        resp = self._RequestUrl(request_url, 'GET', data=parameters, enforce_auth=False)
993        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
994
995        return data
996
997    def DestroyStatus(self, status_id, trim_user=False):
998        """Destroys the status specified by the required ID parameter.
999
1000        The authenticating user must be the author of the specified
1001        status.
1002
1003        Args:
1004          status_id (int):
1005            The numerical ID of the status you're trying to destroy.
1006          trim_user (bool, optional):
1007            When set to True, each tweet returned in a timeline will include
1008            a user object including only the status authors numerical ID.
1009
1010        Returns:
1011          A twitter.Status instance representing the destroyed status message
1012        """
1013        url = '%s/statuses/destroy/%s.json' % (self.base_url, status_id)
1014
1015        post_data = {
1016            'id': enf_type('status_id', int, status_id),
1017            'trim_user': enf_type('trim_user', bool, trim_user)
1018        }
1019
1020        resp = self._RequestUrl(url, 'POST', data=post_data)
1021        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1022
1023        return Status.NewFromJsonDict(data)
1024
1025    def PostUpdate(self,
1026                   status,
1027                   media=None,
1028                   media_additional_owners=None,
1029                   media_category=None,
1030                   in_reply_to_status_id=None,
1031                   auto_populate_reply_metadata=False,
1032                   exclude_reply_user_ids=None,
1033                   latitude=None,
1034                   longitude=None,
1035                   place_id=None,
1036                   display_coordinates=False,
1037                   trim_user=False,
1038                   verify_status_length=True,
1039                   attachment_url=None):
1040        """Post a twitter status message from the authenticated user.
1041
1042        https://dev.twitter.com/docs/api/1.1/post/statuses/update
1043
1044        Args:
1045            status (str):
1046                The message text to be posted. Must be less than or equal to
1047                CHARACTER_LIMIT characters.
1048            media (int, str, fp, optional):
1049                A URL, a local file, or a file-like object (something with a
1050                read() method), or a list of any combination of the above.
1051            media_additional_owners (list, optional):
1052                A list of user ids representing Twitter users that should be able
1053                to use the uploaded media in their tweets. If you pass a list of
1054                media, then additional_owners will apply to each object. If you
1055                need more granular control, please use the UploadMedia* methods.
1056            media_category (str, optional):
1057                Only for use with the AdsAPI. See
1058                https://dev.twitter.com/ads/creative/promoted-video-overview if
1059                this applies to your application.
1060            in_reply_to_status_id (int, optional):
1061                The ID of an existing status that the status to be posted is
1062                in reply to.  This implicitly sets the in_reply_to_user_id
1063                attribute of the resulting status to the user ID of the
1064                message being replied to.  Invalid/missing status IDs will be
1065                ignored.
1066            auto_populate_reply_metadata (bool, optional):
1067                Automatically include the @usernames of the users mentioned or
1068                participating in the tweet to which this tweet is in reply.
1069            exclude_reply_user_ids (list, optional):
1070                Remove given user_ids (*not* @usernames) from the tweet's
1071                automatically generated reply metadata.
1072            attachment_url (str, optional):
1073                URL to an attachment resource: one to four photos, a GIF,
1074                video, Quote Tweet, or DM deep link. If not specified and
1075                media parameter is not None, we will attach the first media
1076                object as the attachment URL. If a bad URL is passed, Twitter
1077                will raise an error.
1078            latitude (float, optional):
1079                Latitude coordinate of the tweet in degrees. Will only work
1080                in conjunction with longitude argument. Both longitude and
1081                latitude will be ignored by twitter if the user has a false
1082                geo_enabled setting.
1083            longitude (float, optional):
1084                Longitude coordinate of the tweet in degrees. Will only work
1085                in conjunction with latitude argument. Both longitude and
1086                latitude will be ignored by twitter if the user has a false
1087                geo_enabled setting.
1088            place_id (int, optional):
1089                A place in the world. These IDs can be retrieved from
1090                GET geo/reverse_geocode.
1091            display_coordinates (bool, optional):
1092                Whether or not to put a pin on the exact coordinates a tweet
1093                has been sent from.
1094            trim_user (bool, optional):
1095                If True the returned payload will only contain the user IDs,
1096                otherwise the payload will contain the full user data item.
1097            verify_status_length (bool, optional):
1098                If True, api throws a hard error that the status is over
1099                CHARACTER_LIMIT characters. If False, Api will attempt to post
1100                the status.
1101        Returns:
1102            (twitter.Status) A twitter.Status instance representing the
1103            message posted.
1104        """
1105        url = '%s/statuses/update.json' % self.base_url
1106
1107        if isinstance(status, str) or self._input_encoding is None:
1108            u_status = status
1109        else:
1110            u_status = str(status, self._input_encoding)
1111
1112        if verify_status_length and calc_expected_status_length(u_status) > CHARACTER_LIMIT:
1113            raise TwitterError("Text must be less than or equal to CHARACTER_LIMIT characters.")
1114
1115        if auto_populate_reply_metadata and not in_reply_to_status_id:
1116            raise TwitterError("If auto_populate_reply_metadata is True, you must set in_reply_to_status_id")
1117
1118        parameters = {
1119            'status': u_status,
1120            'in_reply_to_status_id': in_reply_to_status_id,
1121            'auto_populate_reply_metadata': auto_populate_reply_metadata,
1122            'place_id': place_id,
1123            'display_coordinates': display_coordinates,
1124            'trim_user': trim_user,
1125            'exclude_reply_user_ids': ','.join([str(u) for u in exclude_reply_user_ids or []]),
1126        }
1127
1128        if attachment_url:
1129            parameters['attachment_url'] = attachment_url
1130
1131        if media:
1132            chunked_types = ['video/mp4', 'video/quicktime', 'image/gif']
1133            media_ids = []
1134            if isinstance(media, (int, long)):
1135                media_ids.append(media)
1136
1137            elif isinstance(media, list):
1138                for media_file in media:
1139
1140                    # If you want to pass just a media ID, it should be an int
1141                    if isinstance(media_file, (int, long)):
1142                        media_ids.append(media_file)
1143                        continue
1144
1145                    _, _, file_size, media_type = parse_media_file(media_file)
1146                    if (media_type == 'image/gif' or media_type == 'video/mp4') and len(media) > 1:
1147                        raise TwitterError(
1148                            'You cannot post more than 1 GIF or 1 video in a single status.')
1149                    if file_size > self.chunk_size or media_type in chunked_types:
1150                        media_id = self.UploadMediaChunked(
1151                            media=media_file,
1152                            additional_owners=media_additional_owners,
1153                            media_category=media_category)
1154                    else:
1155                        media_id = self.UploadMediaSimple(
1156                            media=media_file,
1157                            additional_owners=media_additional_owners,
1158                            media_category=media_category)
1159                    media_ids.append(media_id)
1160            else:
1161                _, _, file_size, media_type = parse_media_file(media)
1162                if file_size > self.chunk_size or media_type in chunked_types:
1163                    media_ids.append(self.UploadMediaChunked(
1164                        media, media_additional_owners, media_category=media_category
1165                    ))
1166                else:
1167                    media_ids.append(self.UploadMediaSimple(
1168                        media, media_additional_owners, media_category=media_category
1169                    ))
1170            parameters['media_ids'] = ','.join([str(mid) for mid in media_ids])
1171
1172        if latitude is not None and longitude is not None:
1173            parameters['lat'] = str(latitude)
1174            parameters['long'] = str(longitude)
1175
1176        resp = self._RequestUrl(url, 'POST', data=parameters)
1177        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1178
1179        return Status.NewFromJsonDict(data)
1180
1181    def UploadMediaSimple(self,
1182                          media,
1183                          additional_owners=None,
1184                          media_category=None):
1185
1186        """ Upload a media file to Twitter in one request. Used for small file
1187        uploads that do not require chunked uploads.
1188
1189        Args:
1190            media:
1191                File-like object to upload.
1192            additional_owners: additional Twitter users that are allowed to use
1193                The uploaded media. Should be a list of integers. Maximum
1194                number of additional owners is capped at 100 by Twitter.
1195            media_category:
1196                Category with which to identify media upload. Only use with Ads
1197                API & video files.
1198
1199        Returns:
1200            media_id:
1201                ID of the uploaded media returned by the Twitter API or 0.
1202
1203        """
1204        url = '%s/media/upload.json' % self.upload_url
1205        parameters = {}
1206
1207        media_fp, _, _, _ = parse_media_file(media)
1208
1209        parameters['media'] = media_fp.read()
1210
1211        if additional_owners and len(additional_owners) > 100:
1212            raise TwitterError({'message': 'Maximum of 100 additional owners may be specified for a Media object'})
1213        if additional_owners:
1214            parameters['additional_owners'] = additional_owners
1215        if media_category:
1216            parameters['media_category'] = media_category
1217
1218        resp = self._RequestUrl(url, 'POST', data=parameters)
1219        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1220
1221        try:
1222            return data['media_id']
1223        except KeyError:
1224            raise TwitterError({'message': 'Media could not be uploaded.'})
1225
1226    def PostMediaMetadata(self,
1227                          media_id,
1228                          alt_text=None):
1229        """Provide addtional data for uploaded media.
1230
1231        Args:
1232            media_id:
1233                ID of a previously uploaded media item.
1234            alt_text:
1235                Image Alternate Text.
1236        """
1237        url = '%s/media/metadata/create.json' % self.upload_url
1238        parameters = {}
1239
1240        parameters['media_id'] = media_id
1241
1242        if alt_text:
1243            parameters['alt_text'] = {"text": alt_text}
1244
1245        resp = self._RequestUrl(url, 'POST', json=parameters)
1246
1247        return resp
1248
1249    def _UploadMediaChunkedInit(self,
1250                                media,
1251                                additional_owners=None,
1252                                media_category=None):
1253        """Start a chunked upload to Twitter.
1254
1255        Args:
1256            media:
1257                File-like object to upload.
1258            additional_owners: additional Twitter users that are allowed to use
1259                The uploaded media. Should be a list of integers. Maximum
1260                number of additional owners is capped at 100 by Twitter.
1261            media_category:
1262                Category with which to identify media upload. Only use with Ads
1263                API & video files.
1264
1265        Returns:
1266            tuple: media_id (returned from Twitter), file-handler object (i.e., has .read()
1267            method), filename media file.
1268        """
1269        url = '%s/media/upload.json' % self.upload_url
1270
1271        media_fp, filename, file_size, media_type = parse_media_file(media, async_upload=True)
1272
1273        if not all([media_fp, filename, file_size, media_type]):
1274            raise TwitterError({'message': 'Could not process media file'})
1275
1276        parameters = {}
1277
1278        if additional_owners and len(additional_owners) > 100:
1279            raise TwitterError({'message': 'Maximum of 100 additional owners may be specified for a Media object'})
1280        if additional_owners:
1281            parameters['additional_owners'] = additional_owners
1282        if media_category:
1283            parameters['media_category'] = media_category
1284
1285        # INIT doesn't read in any data. It's purpose is to prepare Twitter to
1286        # receive the content in APPEND requests.
1287        parameters['command'] = 'INIT'
1288        parameters['media_type'] = media_type
1289        parameters['total_bytes'] = file_size
1290
1291        resp = self._RequestUrl(url, 'POST', data=parameters)
1292        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1293
1294        try:
1295            media_id = data['media_id']
1296        except KeyError:
1297            raise TwitterError({'message': 'Media could not be uploaded'})
1298
1299        return (media_id, media_fp, filename)
1300
1301    def _UploadMediaChunkedAppend(self,
1302                                  media_id,
1303                                  media_fp,
1304                                  filename):
1305        """Appends (i.e., actually uploads) media file to Twitter.
1306
1307        Args:
1308            media_id (int):
1309                ID of the media file received from Init method.
1310            media_fp (file):
1311                File-like object representing media file (must have .read() method)
1312            filename (str):
1313                Filename of the media file being uploaded.
1314
1315        Returns:
1316            True if successful. Raises otherwise.
1317        """
1318        url = '%s/media/upload.json' % self.upload_url
1319
1320        boundary = "--{0}".format(uuid4().hex).encode('utf-8')
1321        media_id_bytes = str(media_id).encode('utf-8')
1322        headers = {'Content-Type': 'multipart/form-data; boundary={0}'.format(
1323            boundary.decode('utf8')[2:]
1324        )}
1325
1326        segment_id = 0
1327        while True:
1328            try:
1329                data = media_fp.read(self.chunk_size)
1330            except ValueError:
1331                break
1332            if not data:
1333                break
1334            body = [
1335                boundary,
1336                b'Content-Disposition: form-data; name="command"',
1337                b'',
1338                b'APPEND',
1339                boundary,
1340                b'Content-Disposition: form-data; name="media_id"',
1341                b'',
1342                media_id_bytes,
1343                boundary,
1344                b'Content-Disposition: form-data; name="segment_index"',
1345                b'',
1346                str(segment_id).encode('utf-8'),
1347                boundary,
1348                'Content-Disposition: form-data; name="media"; filename="{0!r}"'.format(filename).encode('utf8'),
1349                b'Content-Type: application/octet-stream',
1350                b'',
1351                data,
1352                boundary + b'--'
1353            ]
1354            body_data = b'\r\n'.join(body)
1355            headers['Content-Length'] = str(len(body_data))
1356
1357            resp = self._RequestChunkedUpload(url=url,
1358                                              headers=headers,
1359                                              data=body_data)
1360
1361            # The body of the response should be blank, but the normal decoding
1362            # raises a JSONDecodeError, so we should only do error checking
1363            # if the response is not blank.
1364            if resp.content.decode('utf-8'):
1365                return self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1366
1367            segment_id += 1
1368
1369        try:
1370            media_fp.close()
1371        except Exception as e:
1372            pass
1373
1374        return True
1375
1376    def _UploadMediaChunkedFinalize(self, media_id):
1377        """Finalize chunked upload to Twitter.
1378
1379        Args:
1380            media_id (int):
1381                ID of the media file for which to finalize the upload.
1382
1383        Returns:
1384            json: JSON string of data from Twitter.
1385        """
1386        url = '%s/media/upload.json' % self.upload_url
1387
1388        parameters = {
1389            'command': 'FINALIZE',
1390            'media_id': media_id
1391        }
1392
1393        resp = self._RequestUrl(url, 'POST', data=parameters)
1394        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1395
1396        return data
1397
1398    def UploadMediaChunked(self,
1399                           media,
1400                           additional_owners=None,
1401                           media_category=None):
1402        """Upload a media file to Twitter in multiple requests.
1403
1404        Args:
1405            media:
1406                File-like object to upload.
1407            additional_owners: additional Twitter users that are allowed to use
1408                The uploaded media. Should be a list of integers. Maximum
1409                number of additional owners is capped at 100 by Twitter.
1410            media_category:
1411                Category with which to identify media upload. Only use with Ads
1412                API & video files.
1413
1414        Returns:
1415            media_id:
1416                ID of the uploaded media returned by the Twitter API. Raises if
1417                unsuccesful.
1418        """
1419
1420        media_id, media_fp, filename = self._UploadMediaChunkedInit(media=media,
1421                                                                    additional_owners=additional_owners,
1422                                                                    media_category=media_category)
1423
1424        append = self._UploadMediaChunkedAppend(media_id=media_id,
1425                                                media_fp=media_fp,
1426                                                filename=filename)
1427
1428        if not append:
1429            TwitterError('Media could not be uploaded.')
1430
1431        data = self._UploadMediaChunkedFinalize(media_id)
1432
1433        try:
1434            return data['media_id']
1435        except KeyError:
1436            raise TwitterError('Media could not be uploaded.')
1437
1438    def _TweetTextWrap(self,
1439                       status,
1440                       char_lim=CHARACTER_LIMIT):
1441
1442        if not self._config:
1443            self.GetHelpConfiguration()
1444
1445        tweets = []
1446        line = []
1447        line_length = 0
1448        words = re.split(r'\s', status)
1449
1450        if len(words) == 1 and not is_url(words[0]):
1451            if len(words[0]) > CHARACTER_LIMIT:
1452                raise TwitterError("Unable to split status into tweetable parts. Word was: {0}/{1}".format(len(words[0]), char_lim))
1453            else:
1454                tweets.append(words[0])
1455                return tweets
1456
1457        for word in words:
1458            if len(word) > char_lim:
1459                raise TwitterError("Unable to split status into tweetable parts. Word was: {0}/{1}".format(len(word), char_lim))
1460            new_len = line_length
1461
1462            if is_url(word):
1463                new_len = line_length + self._config['short_url_length_https'] + 1
1464            else:
1465                new_len += len(word) + 1
1466
1467            if new_len > CHARACTER_LIMIT:
1468                tweets.append(' '.join(line))
1469                line = [word]
1470                line_length = new_len - line_length
1471            else:
1472                line.append(word)
1473                line_length = new_len
1474
1475        tweets.append(' '.join(line))
1476        return tweets
1477
1478    def PostUpdates(self,
1479                    status,
1480                    continuation=None,
1481                    **kwargs):
1482        """Post one or more twitter status messages from the authenticated user.
1483
1484        Unlike api.PostUpdate, this method will post multiple status updates
1485        if the message is longer than CHARACTER_LIMIT characters.
1486
1487        Args:
1488          status:
1489            The message text to be posted.
1490            May be longer than CHARACTER_LIMIT characters.
1491          continuation:
1492            The character string, if any, to be appended to all but the
1493            last message.  Note that Twitter strips trailing '...' strings
1494            from messages.  Consider using the unicode \u2026 character
1495            (horizontal ellipsis) instead. [Defaults to None]
1496          **kwargs:
1497            See api.PostUpdate for a list of accepted parameters.
1498
1499        Returns:
1500          A of list twitter.Status instance representing the messages posted.
1501        """
1502        results = list()
1503
1504        if continuation is None:
1505            continuation = ''
1506        char_limit = CHARACTER_LIMIT - len(continuation)
1507
1508        tweets = self._TweetTextWrap(status=status, char_lim=char_limit)
1509
1510        if len(tweets) == 1:
1511            results.append(self.PostUpdate(status=tweets[0], **kwargs))
1512            return results
1513
1514        for tweet in tweets[0:-1]:
1515            results.append(self.PostUpdate(status=tweet + continuation, **kwargs))
1516        results.append(self.PostUpdate(status=tweets[-1], **kwargs))
1517
1518        return results
1519
1520    def PostRetweet(self, status_id, trim_user=False):
1521        """Retweet a tweet with the Retweet API.
1522
1523        Args:
1524          status_id:
1525            The numerical id of the tweet that will be retweeted
1526          trim_user:
1527            If True the returned payload will only contain the user IDs,
1528            otherwise the payload will contain the full user data item.
1529            [Optional]
1530
1531        Returns:
1532          A twitter.Status instance representing the original tweet with retweet details embedded.
1533        """
1534        try:
1535            if int(status_id) <= 0:
1536                raise TwitterError({'message': "'status_id' must be a positive number"})
1537        except ValueError:
1538            raise TwitterError({'message': "'status_id' must be an integer"})
1539
1540        url = '%s/statuses/retweet/%s.json' % (self.base_url, status_id)
1541        data = {'id': status_id}
1542        if trim_user:
1543            data['trim_user'] = 'true'
1544        resp = self._RequestUrl(url, 'POST', data=data)
1545        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1546
1547        return Status.NewFromJsonDict(data)
1548
1549    def GetUserRetweets(self,
1550                        count=None,
1551                        since_id=None,
1552                        max_id=None,
1553                        trim_user=False):
1554        """Fetch the sequence of retweets made by the authenticated user.
1555
1556        Args:
1557          count:
1558            The number of status messages to retrieve. [Optional]
1559          since_id:
1560            Returns results with an ID greater than (that is, more recent
1561            than) the specified ID. There are limits to the number of
1562            Tweets which can be accessed through the API. If the limit of
1563            Tweets has occurred since the since_id, the since_id will be
1564            forced to the oldest ID available. [Optional]
1565          max_id:
1566            Returns results with an ID less than (that is, older than) or
1567            equal to the specified ID. [Optional]
1568          trim_user:
1569            If True the returned payload will only contain the user IDs,
1570            otherwise the payload will contain the full user data item.
1571            [Optional]
1572
1573        Returns:
1574          A sequence of twitter.Status instances, one for each message up to count
1575        """
1576        return self.GetUserTimeline(
1577            since_id=since_id,
1578            count=count,
1579            max_id=max_id,
1580            trim_user=trim_user,
1581            exclude_replies=True,
1582            include_rts=True)
1583
1584    def GetReplies(self,
1585                   since_id=None,
1586                   count=None,
1587                   max_id=None,
1588                   trim_user=False):
1589        """Get a sequence of status messages representing the 20 most
1590        recent replies (status updates prefixed with @twitterID) to the
1591        authenticating user.
1592
1593        Args:
1594          since_id:
1595            Returns results with an ID greater than (that is, more recent
1596            than) the specified ID. There are limits to the number of
1597            Tweets which can be accessed through the API. If the limit of
1598            Tweets has occurred since the since_id, the since_id will be
1599            forced to the oldest ID available. [Optional]
1600          max_id:
1601            Returns results with an ID less than (that is, older than) or
1602            equal to the specified ID. [Optional]
1603          trim_user:
1604            If True the returned payload will only contain the user IDs,
1605            otherwise the payload will contain the full user data item.
1606            [Optional]
1607
1608        Returns:
1609          A sequence of twitter.Status instances, one for each reply to the user.
1610        """
1611        return self.GetUserTimeline(since_id=since_id, count=count, max_id=max_id, trim_user=trim_user,
1612                                    exclude_replies=False, include_rts=False)
1613
1614    def GetRetweets(self,
1615                    statusid,
1616                    count=None,
1617                    trim_user=False):
1618        """Returns up to 100 of the first retweets of the tweet identified
1619        by statusid
1620
1621        Args:
1622          statusid (int):
1623            The ID of the tweet for which retweets should be searched for
1624          count (int, optional):
1625            The number of status messages to retrieve.
1626          trim_user (bool, optional):
1627            If True the returned payload will only contain the user IDs,
1628            otherwise the payload will contain the full user data item.
1629
1630        Returns:
1631          A list of twitter.Status instances, which are retweets of statusid
1632        """
1633        url = '%s/statuses/retweets/%s.json' % (self.base_url, statusid)
1634        parameters = {
1635            'trim_user': enf_type('trim_user', bool, trim_user),
1636        }
1637
1638        if count:
1639            parameters['count'] = enf_type('count', int, count)
1640
1641        resp = self._RequestUrl(url, 'GET', data=parameters)
1642        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1643
1644        return [Status.NewFromJsonDict(s) for s in data]
1645
1646    def GetRetweeters(self,
1647                      status_id,
1648                      cursor=None,
1649                      count=100,
1650                      stringify_ids=False):
1651        """Returns a collection of up to 100 user IDs belonging to users who have
1652        retweeted the tweet specified by the status_id parameter.
1653
1654        Args:
1655          status_id:
1656            the tweet's numerical ID
1657          cursor:
1658            breaks the ids into pages of no more than 100.
1659          stringify_ids:
1660            returns the IDs as unicode strings. [Optional]
1661
1662        Returns:
1663          A list of user IDs
1664        """
1665        url = '%s/statuses/retweeters/ids.json' % (self.base_url)
1666        parameters = {
1667            'id': enf_type('id', int, status_id),
1668            'stringify_ids': enf_type('stringify_ids', bool, stringify_ids),
1669            'count': count,
1670        }
1671
1672        result = []
1673
1674        total_count = 0
1675        while True:
1676            if cursor:
1677                try:
1678                    parameters['cursor'] = int(cursor)
1679                except ValueError:
1680                    raise TwitterError({'message': "cursor must be an integer"})
1681            resp = self._RequestUrl(url, 'GET', data=parameters)
1682            data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1683            result += [x for x in data['ids']]
1684            if 'next_cursor' in data:
1685                if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']:
1686                    break
1687                else:
1688                    cursor = data['next_cursor']
1689                    total_count -= len(data['ids'])
1690                    if total_count < 1:
1691                        break
1692            else:
1693                break
1694
1695        return result
1696
1697    def GetRetweetsOfMe(self,
1698                        count=None,
1699                        since_id=None,
1700                        max_id=None,
1701                        trim_user=False,
1702                        include_entities=True,
1703                        include_user_entities=True):
1704        """Returns up to 100 of the most recent tweets of the user that have been
1705        retweeted by others.
1706
1707        Args:
1708          count:
1709            The number of retweets to retrieve, up to 100.
1710            Defaults to 20. [Optional]
1711          since_id:
1712            Returns results with an ID greater than
1713            (newer than) this ID. [Optional]
1714          max_id:
1715            Returns results with an ID less than or equal
1716            to this ID. [Optional]
1717          trim_user:
1718            When True, the user object for each tweet will
1719            only be an ID. [Optional]
1720          include_entities:
1721            When True, the tweet entities will be included. [Optional]
1722          include_user_entities:
1723            When True, the user entities will be included. [Optional]
1724        """
1725        url = '%s/statuses/retweets_of_me.json' % self.base_url
1726        if count is not None:
1727            try:
1728                if int(count) > 100:
1729                    raise TwitterError({'message': "'count' may not be greater than 100"})
1730            except ValueError:
1731                raise TwitterError({'message': "'count' must be an integer"})
1732        parameters = {
1733            'count': count,
1734            'since_id': since_id,
1735            'max_id': max_id,
1736            'trim_user': bool(trim_user),
1737            'include_entities': bool(include_entities),
1738            'include_user_entities': bool(include_user_entities),
1739        }
1740
1741        resp = self._RequestUrl(url, 'GET', data=parameters)
1742        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1743
1744        return [Status.NewFromJsonDict(s) for s in data]
1745
1746    def _GetBlocksMutesPaged(self,
1747                             endpoint,
1748                             action,
1749                             cursor=-1,
1750                             skip_status=False,
1751                             include_entities=True,
1752                             stringify_ids=False):
1753        """ Fetch a page of the users (as twitter.User instances)
1754        blocked or muted by the currently authenticated user.
1755
1756        Args:
1757          endpoint (str):
1758            Either "mute" or "block".
1759          action (str):
1760            Either 'list' or 'ids' depending if you want to return fully-hydrated
1761            twitter.User objects or a list of user IDs as ints.
1762          cursor (int, optional):
1763            Should be set to -1 if you want the first page, thereafter denotes
1764            the page of users that you want to return.
1765          skip_status (bool, optional):
1766            If True the statuses will not be returned in the user items.
1767          include_entities (bool, optional):
1768            When True, the user entities will be included.
1769
1770        Returns:
1771          next_cursor, previous_cursor, list of twitter.User instances,
1772          one for each user.
1773        """
1774        urls = {
1775            'mute': {
1776                'list': '%s/mutes/users/list.json' % self.base_url,
1777                'ids': '%s/mutes/users/ids.json' % self.base_url
1778            },
1779            'block': {
1780                'list': '%s/blocks/list.json' % self.base_url,
1781                'ids': '%s/blocks/ids.json' % self.base_url
1782            }
1783        }
1784
1785        url = urls[endpoint][action]
1786
1787        result = []
1788        parameters = {
1789            'skip_status': bool(skip_status),
1790            'include_entities': bool(include_entities),
1791            'stringify_ids': bool(stringify_ids),
1792            'cursor': cursor,
1793        }
1794
1795        resp = self._RequestUrl(url, 'GET', data=parameters)
1796        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1797
1798        if action == 'ids':
1799            result += data.get('ids')
1800        else:
1801            result += [User.NewFromJsonDict(x) for x in data['users']]
1802        next_cursor = data.get('next_cursor', 0)
1803        previous_cursor = data.get('previous_cursor', 0)
1804
1805        return next_cursor, previous_cursor, result
1806
1807    def GetBlocks(self,
1808                  skip_status=False,
1809                  include_entities=False):
1810        """ Fetch the sequence of all users (as twitter.User instances),
1811        blocked by the currently authenticated user.
1812
1813        Args:
1814          skip_status (bool, optional):
1815            If True the statuses will not be returned in the user items.
1816          include_entities (bool, optional):
1817            When True, the user entities will be included.
1818
1819        Returns:
1820          A list of twitter.User instances, one for each blocked user.
1821        """
1822        result = []
1823        cursor = -1
1824
1825        while True:
1826            next_cursor, previous_cursor, users = self.GetBlocksPaged(
1827                cursor=cursor,
1828                skip_status=skip_status,
1829                include_entities=include_entities)
1830            result += users
1831            if next_cursor == 0 or next_cursor == previous_cursor:
1832                break
1833            else:
1834                cursor = next_cursor
1835
1836        return result
1837
1838    def GetBlocksPaged(self,
1839                       cursor=-1,
1840                       skip_status=False,
1841                       include_entities=False):
1842        """ Fetch a page of the users (as twitter.User instances)
1843        blocked by the currently authenticated user.
1844
1845        Args:
1846          cursor (int, optional):
1847            Should be set to -1 if you want the first page, thereafter denotes
1848            the page of blocked users that you want to return.
1849          skip_status (bool, optional):
1850            If True the statuses will not be returned in the user items.
1851          include_entities (bool, optional):
1852            When True, the user entities will be included.
1853
1854        Returns:
1855          next_cursor, previous_cursor, list of twitter.User instances,
1856          one for each blocked user.
1857        """
1858        return self._GetBlocksMutesPaged(endpoint='block',
1859                                         action='list',
1860                                         cursor=cursor,
1861                                         skip_status=skip_status,
1862                                         include_entities=include_entities)
1863
1864    def GetBlocksIDs(self,
1865                     stringify_ids=False):
1866        """Fetch the sequence of all user IDs blocked by the
1867        currently authenticated user.
1868
1869        Args:
1870          stringify_ids (bool, optional):
1871            If True user IDs will be returned as strings rather than integers.
1872
1873        Returns:
1874          A list of user IDs for all blocked users.
1875        """
1876        result = []
1877        cursor = -1
1878
1879        while True:
1880            next_cursor, previous_cursor, user_ids = self.GetBlocksIDsPaged(
1881                cursor=cursor,
1882                stringify_ids=stringify_ids)
1883            result += user_ids
1884            if next_cursor == 0 or next_cursor == previous_cursor:
1885                break
1886            else:
1887                cursor = next_cursor
1888
1889        return result
1890
1891    def GetBlocksIDsPaged(self,
1892                          cursor=-1,
1893                          stringify_ids=False):
1894        """ Fetch a page of the user IDs blocked by the currently
1895        authenticated user.
1896
1897        Args:
1898          cursor (int, optional):
1899            Should be set to -1 if you want the first page, thereafter denotes
1900            the page of blocked users that you want to return.
1901          stringify_ids (bool, optional):
1902            If True user IDs will be returned as strings rather than integers.
1903
1904        Returns:
1905          next_cursor, previous_cursor, list of user IDs of blocked users.
1906        """
1907        return self._GetBlocksMutesPaged(endpoint='block',
1908                                         action='ids',
1909                                         cursor=cursor,
1910                                         stringify_ids=stringify_ids)
1911
1912    def GetMutes(self,
1913                 skip_status=False,
1914                 include_entities=False):
1915        """ Fetch the sequence of all users (as twitter.User instances),
1916        muted by the currently authenticated user.
1917
1918        Args:
1919          skip_status (bool, optional):
1920            If True the statuses will not be returned in the user items.
1921          include_entities (bool, optional):
1922            When True, the user entities will be included.
1923
1924        Returns:
1925          A list of twitter.User instances, one for each muted user.
1926        """
1927        result = []
1928        cursor = -1
1929
1930        while True:
1931            next_cursor, previous_cursor, users = self.GetMutesPaged(
1932                cursor=cursor,
1933                skip_status=skip_status,
1934                include_entities=include_entities)
1935            result += users
1936            if next_cursor == 0 or next_cursor == previous_cursor:
1937                break
1938            else:
1939                cursor = next_cursor
1940
1941        return result
1942
1943    def GetMutesPaged(self,
1944                      cursor=-1,
1945                      skip_status=False,
1946                      include_entities=False):
1947        """ Fetch a page of the users (as twitter.User instances)
1948        muted by the currently authenticated user.
1949
1950        Args:
1951          cursor (int, optional):
1952            Should be set to -1 if you want the first page, thereafter denotes
1953            the page of muted users that you want to return.
1954          skip_status (bool, optional):
1955            If True the statuses will not be returned in the user items.
1956          include_entities (bool, optional):
1957            When True, the user entities will be included.
1958
1959        Returns:
1960          next_cursor, previous_cursor, list of twitter.User instances,
1961          one for each muted user.
1962        """
1963
1964        return self._GetBlocksMutesPaged(endpoint='mute',
1965                                         action='list',
1966                                         cursor=cursor,
1967                                         skip_status=skip_status,
1968                                         include_entities=include_entities)
1969
1970    def GetMutesIDs(self,
1971                    stringify_ids=False):
1972        """Fetch the sequence of all user IDs muted by the
1973        currently authenticated user.
1974
1975        Args:
1976          stringify_ids (bool, optional):
1977            If True user IDs will be returned as strings rather than integers.
1978
1979        Returns:
1980          A list of user IDs for all muted users.
1981        """
1982        result = []
1983        cursor = -1
1984
1985        while True:
1986            next_cursor, previous_cursor, user_ids = self.GetMutesIDsPaged(
1987                cursor=cursor,
1988                stringify_ids=stringify_ids)
1989            result += user_ids
1990            if next_cursor == 0 or next_cursor == previous_cursor:
1991                break
1992            else:
1993                cursor = next_cursor
1994
1995        return result
1996
1997    def GetMutesIDsPaged(self,
1998                         cursor=-1,
1999                         stringify_ids=False):
2000        """ Fetch a page of the user IDs muted by the currently
2001        authenticated user.
2002
2003        Args:
2004          cursor (int, optional):
2005            Should be set to -1 if you want the first page, thereafter denotes
2006            the page of muted users that you want to return.
2007          stringify_ids (bool, optional):
2008            If True user IDs will be returned as strings rather than integers.
2009
2010        Returns:
2011          next_cursor, previous_cursor, list of user IDs of muted users.
2012        """
2013        return self._GetBlocksMutesPaged(endpoint='mute',
2014                                         action='ids',
2015                                         cursor=cursor,
2016                                         stringify_ids=stringify_ids)
2017
2018    def _BlockMute(self,
2019                   action,
2020                   endpoint,
2021                   user_id=None,
2022                   screen_name=None,
2023                   include_entities=True,
2024                   skip_status=False):
2025        """Create or destroy a block or mute on behalf of the authenticated user.
2026
2027        Args:
2028          action (str):
2029            Either 'create' or 'destroy'.
2030          endpoint (str):
2031            Either 'block' or 'mute'.
2032          user_id (int, optional)
2033            The numerical ID of the user to block/mute.
2034          screen_name (str, optional):
2035            The screen name of the user to block/mute.
2036          include_entities (bool, optional):
2037            The entities node will not be included if set to False.
2038          skip_status (bool, optional):
2039              When set to False, the blocked User's statuses will not be included
2040              with the returned User object.
2041        Returns:
2042          twitter.User: twitter.User object representing the blocked/muted user.
2043        """
2044
2045        urls = {
2046            'block': {
2047                'create': '%s/blocks/create.json' % (self.base_url),
2048                'destroy': '%s/blocks/destroy.json' % (self.base_url),
2049            },
2050            'mute': {
2051                'create': '%s/mutes/users/create.json' % (self.base_url),
2052                'destroy': '%s/mutes/users/destroy.json' % (self.base_url)
2053            }
2054        }
2055
2056        url = urls[endpoint][action]
2057        post_data = {}
2058
2059        if user_id:
2060            post_data['user_id'] = enf_type('user_id', int, user_id)
2061        elif screen_name:
2062            post_data['screen_name'] = screen_name
2063        else:
2064            raise TwitterError("You must specify either a user_id or screen_name")
2065
2066        if include_entities:
2067            post_data['include_entities'] = enf_type('include_entities', bool, include_entities)
2068        if skip_status:
2069            post_data['skip_status'] = enf_type('skip_status', bool, skip_status)
2070
2071        resp = self._RequestUrl(url, 'POST', data=post_data)
2072        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
2073
2074        return User.NewFromJsonDict(data)
2075
2076    def CreateBlock(self,
2077                    user_id=None,
2078                    screen_name=None,
2079                    include_entities=True,
2080                    skip_status=False):
2081        """Blocks the user specified by either user_id or screen_name.
2082
2083        Args:
2084          user_id (int, optional)
2085            The numerical ID of the user to block.
2086          screen_name (str, optional):
2087            The screen name of the user to block.
2088          include_entities (bool, optional):
2089            The entities node will not be included if set to False.
2090          skip_status (bool, optional):
2091            When set to False, the blocked User's statuses will not be included
2092            with the returned User object.
2093
2094        Returns:
2095          A twitter.User instance representing the blocked user.
2096        """
2097        return self._BlockMute(action='create',
2098                               endpoint='block',
2099                               user_id=user_id,
2100                               screen_name=screen_name,
2101                               include_entities=include_entities,
2102                               skip_status=skip_status)
2103
2104    def DestroyBlock(self,
2105                     user_id=None,
2106                     screen_name=None,
2107                     include_entities=True,
2108                     skip_status=False):
2109        """Unlocks the user specified by either user_id or screen_name.
2110
2111        Args:
2112          user_id (int, optional)
2113            The numerical ID of the user to block.
2114          screen_name (str, optional):
2115            The screen name of the user to block.
2116          include_entities (bool, optional):
2117            The entities node will not be included if set to False.
2118          skip_status (bool, optional):
2119            When set to False, the blocked User's statuses will not be included
2120            with the returned User object.
2121
2122        Returns:
2123          A twitter.User instance representing the blocked user.
2124        """
2125        return self._BlockMute(action='destroy',
2126                               endpoint='block',
2127                               user_id=user_id,
2128                               screen_name=screen_name,
2129                               include_entities=include_entities,
2130                               skip_status=skip_status)
2131
2132    def CreateMute(self,
2133                   user_id=None,
2134                   screen_name=None,
2135                   include_entities=True,
2136                   skip_status=False):
2137        """Mutes the user specified by either user_id or screen_name.
2138
2139        Args:
2140          user_id (int, optional)
2141            The numerical ID of the user to mute.
2142          screen_name (str, optional):
2143            The screen name of the user to mute.
2144          include_entities (bool, optional):
2145            The entities node will not be included if set to False.
2146          skip_status (bool, optional):
2147            When set to False, the muted User's statuses will not be included
2148            with the returned User object.
2149
2150        Returns:
2151          A twitter.User instance representing the muted user.
2152        """
2153        return self._BlockMute(action='create',
2154                               endpoint='mute',
2155                               user_id=user_id,
2156                               screen_name=screen_name,
2157                               include_entities=include_entities,
2158                               skip_status=skip_status)
2159
2160    def DestroyMute(self,
2161                    user_id=None,
2162                    screen_name=None,
2163                    include_entities=True,
2164                    skip_status=False):
2165        """Unlocks the user specified by either user_id or screen_name.
2166
2167        Args:
2168          user_id (int, optional)
2169            The numerical ID of the user to mute.
2170          screen_name (str, optional):
2171            The screen name of the user to mute.
2172          include_entities (bool, optional):
2173            The entities node will not be included if set to False.
2174          skip_status (bool, optional):
2175            When set to False, the muted User's statuses will not be included
2176            with the returned User object.
2177
2178        Returns:
2179          A twitter.User instance representing the muted user.
2180        """
2181        return self._BlockMute(action='destroy',
2182                               endpoint='mute',
2183                               user_id=user_id,
2184                               screen_name=screen_name,
2185                               include_entities=include_entities,
2186                               skip_status=skip_status)
2187
2188    def _GetIDsPaged(self,
2189                     url,
2190                     user_id,
2191                     screen_name,
2192                     cursor,
2193                     stringify_ids,
2194                     count):
2195        """
2196        This is the lowest level paging logic for fetching IDs. It is used
2197        solely by GetFollowerIDsPaged and GetFriendIDsPaged. It is not intended
2198        for other use.
2199
2200        See GetFollowerIDsPaged or GetFriendIDsPaged for an explanation of the
2201        input arguments.
2202        """
2203        result = []
2204
2205        parameters = {}
2206        if user_id is not None:
2207            parameters['user_id'] = user_id
2208        if screen_name is not None:
2209            parameters['screen_name'] = screen_name
2210        if count is not None:
2211            parameters['count'] = count
2212        parameters['stringify_ids'] = stringify_ids
2213        parameters['cursor'] = cursor
2214
2215        resp = self._RequestUrl(url, 'GET', data=parameters)
2216        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
2217
2218        if 'ids' in data:
2219            result.extend([x for x in data['ids']])
2220
2221        next_cursor = data.get('next_cursor', 0)
2222        previous_cursor = data.get('previous_cursor', 0)
2223
2224        return next_cursor, previous_cursor, result
2225
2226    def GetFollowerIDsPaged(self,
2227                            user_id=None,
2228                            screen_name=None,
2229                            cursor=-1,
2230                            stringify_ids=False,
2231                            count=5000):
2232        """Make a cursor driven call to return a list of one page followers.
2233
2234        The caller is responsible for handling the cursor value and looping
2235        to gather all of the data
2236
2237        Args:
2238          user_id:
2239            The twitter id of the user whose followers you are fetching.
2240            If not specified, defaults to the authenticated user. [Optional]
2241          screen_name:
2242            The twitter name of the user whose followers you are fetching.
2243            If not specified, defaults to the authenticated user. [Optional]
2244          cursor:
2245            Should be set to -1 for the initial call and then is used to
2246            control what result page Twitter returns.
2247          stringify_ids:
2248            if True then twitter will return the ids as strings instead of
2249            integers. [Optional]
2250          count:
2251            The number of user id's to retrieve per API request. Please be aware
2252            that this might get you rate-limited if set to a small number.
2253            By default Twitter will retrieve 5000 UIDs per call. [Optional]
2254
2255        Returns:
2256          next_cursor, previous_cursor, data sequence of user ids,
2257          one for each follower
2258        """
2259        url = '%s/followers/ids.json' % self.base_url
2260        return self._GetIDsPaged(url=url,
2261                                 user_id=user_id,
2262                                 screen_name=screen_name,
2263                                 cursor=cursor,
2264                                 stringify_ids=stringify_ids,
2265                                 count=count)
2266
2267    def GetFriendIDsPaged(self,
2268                          user_id=None,
2269                          screen_name=None,
2270                          cursor=-1,
2271                          stringify_ids=False,
2272                          count=5000):
2273        """Make a cursor driven call to return the list of all friends
2274
2275        The caller is responsible for handling the cursor value and looping
2276        to gather all of the data
2277
2278        Args:
2279          user_id:
2280            The twitter id of the user whose friends you are fetching.
2281            If not specified, defaults to the authenticated user. [Optional]
2282          screen_name:
2283            The twitter name of the user whose friends you are fetching.
2284            If not specified, defaults to the authenticated user. [Optional]
2285          cursor:
2286            Should be set to -1 for the initial call and then is used to
2287            control what result page Twitter returns.
2288          stringify_ids:
2289            if True then twitter will return the ids as strings instead of
2290            integers. [Optional]
2291          count:
2292            The number of user id's to retrieve per API request. Please be aware
2293            that this might get you rate-limited if set to a small number.
2294            By default Twitter will retrieve 5000 UIDs per call. [Optional]
2295
2296        Returns:
2297          next_cursor, previous_cursor, data sequence of twitter.User instances,
2298          one for each friend
2299        """
2300        url = '%s/friends/ids.json' % self.base_url
2301        return self._GetIDsPaged(url,
2302                                 user_id,
2303                                 screen_name,
2304                                 cursor,
2305                                 stringify_ids,
2306                                 count)
2307
2308    def _GetFriendFollowerIDs(self,
2309                              url=None,
2310                              user_id=None,
2311                              screen_name=None,
2312                              cursor=None,
2313                              count=None,
2314                              stringify_ids=False,
2315                              total_count=None):
2316        """ Common method for GetFriendIDs and GetFollowerIDs """
2317
2318        count = 5000
2319        cursor = -1
2320        result = []
2321
2322        if total_count:
2323            total_count = enf_type('total_count', int, total_count)
2324
2325        if total_count and total_count < count:
2326            count = total_count
2327
2328        while True:
2329            if total_count is not None and len(result) + count > total_count:
2330                break
2331
2332            next_cursor, previous_cursor, data = self._GetIDsPaged(
2333                url=url,
2334                user_id=user_id,
2335                screen_name=screen_name,
2336                cursor=cursor,
2337                stringify_ids=stringify_ids,
2338                count=count)
2339
2340            result.extend([x for x in data])
2341
2342            if next_cursor == 0 or next_cursor == previous_cursor:
2343                break
2344            else:
2345                cursor = next_cursor
2346
2347        return result
2348
2349    def GetFollowerIDs(self,
2350                       user_id=None,
2351                       screen_name=None,
2352                       cursor=None,
2353                       stringify_ids=False,
2354                       count=None,
2355                       total_count=None):
2356        """Returns a list of twitter user id's for every person
2357        that is following the specified user.
2358
2359        Args:
2360          user_id:
2361            The id of the user to retrieve the id list for. [Optional]
2362          screen_name:
2363            The screen_name of the user to retrieve the id list for. [Optional]
2364          cursor:
2365            Specifies the Twitter API Cursor location to start at.
2366            Note: there are pagination limits. [Optional]
2367          stringify_ids:
2368            if True then twitter will return the ids as strings instead of
2369            integers. [Optional]
2370          count:
2371            The number of user id's to retrieve per API request. Please be aware
2372            that this might get you rate-limited if set to a small number.
2373            By default Twitter will retrieve 5000 UIDs per call. [Optional]
2374          total_count:
2375            The total amount of UIDs to retrieve. Good if the account has many
2376            followers and you don't want to get rate limited. The data returned
2377            might contain more UIDs if total_count is not a multiple of count
2378            (5000 by default). [Optional]
2379
2380        Returns:
2381          A list of integers, one for each user id.
2382        """
2383        url = '%s/followers/ids.json' % self.base_url
2384        return self._GetFriendFollowerIDs(url=url,
2385                                          user_id=user_id,
2386                                          screen_name=screen_name,
2387                                          cursor=cursor,
2388                                          stringify_ids=stringify_ids,
2389                                          count=count,
2390                                          total_count=total_count)
2391
2392    def GetFriendIDs(self,
2393                     user_id=None,
2394                     screen_name=None,
2395                     cursor=None,
2396                     count=None,
2397                     stringify_ids=False,
2398                     total_count=None):
2399        """ Fetch a sequence of user ids, one for each friend.
2400        Returns a list of all the given user's friends' IDs. If no user_id or
2401        screen_name is given, the friends will be those of the authenticated
2402        user.
2403
2404        Args:
2405          user_id:
2406            The id of the user to retrieve the id list for. [Optional]
2407          screen_name:
2408            The screen_name of the user to retrieve the id list for. [Optional]
2409          cursor:
2410            Specifies the Twitter API Cursor location to start at.
2411            Note: there are pagination limits. [Optional]
2412          stringify_ids:
2413            if True then twitter will return the ids as strings instead of integers.
2414            [Optional]
2415          count:
2416            The number of user id's to retrieve per API request. Please be aware that
2417            this might get you rate-limited if set to a small number.
2418            By default Twitter will retrieve 5000 UIDs per call. [Optional]
2419          total_count:
2420            The total amount of UIDs to retrieve. Good if the account has many followers
2421            and you don't want to get rate limited. The data returned might contain more
2422            UIDs if total_count is not a multiple of count (5000 by default). [Optional]
2423
2424        Returns:
2425          A list of integers, one for each user id.
2426        """
2427        url = '%s/friends/ids.json' % self.base_url
2428        return self._GetFriendFollowerIDs(url,
2429                                          user_id,
2430                                          screen_name,
2431                                          cursor,
2432                                          count,
2433                                          stringify_ids,
2434                                          total_count)
2435
2436    def _GetFriendsFollowersPaged(self,
2437                                  url=None,
2438                                  user_id=None,
2439                                  screen_name=None,
2440                                  cursor=-1,
2441                                  count=200,
2442                                  skip_status=False,
2443                                  include_user_entities=True):
2444
2445        """Make a cursor driven call to return the list of 1 page of friends
2446        or followers.
2447
2448        Args:
2449          url:
2450            Endpoint from which to get data. Either
2451            base_url+'/followers/list.json' or base_url+'/friends/list.json'.
2452          user_id:
2453            The twitter id of the user whose followers you are fetching.
2454            If not specified, defaults to the authenticated user. [Optional]
2455          screen_name:
2456            The twitter name of the user whose followers you are fetching.
2457            If not specified, defaults to the authenticated user. [Optional]
2458          cursor:
2459            Should be set to -1 for the initial call and then is used to
2460            control what result page Twitter returns.
2461          count:
2462            The number of users to return per page, up to a maximum of 200.
2463            Defaults to 200. [Optional]
2464          skip_status:
2465            If True the statuses will not be returned in the user items.
2466            [Optional]
2467          include_user_entities:
2468            When True, the user entities will be included. [Optional]
2469
2470        Returns:
2471          next_cursor, previous_cursor, data sequence of twitter.User
2472          instances, one for each follower
2473        """
2474
2475        if user_id and screen_name:
2476            warnings.warn(
2477                "If both user_id and screen_name are specified, Twitter will "
2478                "return the followers of the user specified by screen_name, "
2479                "however this behavior is undocumented by Twitter and might "
2480                "change without warning.", stacklevel=2)
2481
2482        parameters = {}
2483
2484        if user_id is not None:
2485            parameters['user_id'] = user_id
2486        if screen_name is not None:
2487            parameters['screen_name'] = screen_name
2488
2489        try:
2490            parameters['count'] = int(count)
2491        except ValueError:
2492            raise TwitterError({'message': "count must be an integer"})
2493
2494        parameters['skip_status'] = skip_status
2495        parameters['include_user_entities'] = include_user_entities
2496        parameters['cursor'] = cursor
2497
2498        resp = self._RequestUrl(url, 'GET', data=parameters)
2499        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
2500
2501        if 'users' in data:
2502            users = [User.NewFromJsonDict(user) for user in data['users']]
2503        else:
2504            users = []
2505
2506        if 'next_cursor' in data:
2507            next_cursor = data['next_cursor']
2508        else:
2509            next_cursor = 0
2510        if 'previous_cursor' in data:
2511            previous_cursor = data['previous_cursor']
2512        else:
2513            previous_cursor = 0
2514
2515        return next_cursor, previous_cursor, users
2516
2517    def GetFollowersPaged(self,
2518                          user_id=None,
2519                          screen_name=None,
2520                          cursor=-1,
2521                          count=200,
2522                          skip_status=False,
2523                          include_user_entities=True):
2524        """Make a cursor driven call to return the list of all followers
2525
2526        Args:
2527          user_id:
2528            The twitter id of the user whose followers you are fetching.
2529            If not specified, defaults to the authenticated user. [Optional]
2530          screen_name:
2531            The twitter name of the user whose followers you are fetching.
2532            If not specified, defaults to the authenticated user. [Optional]
2533          cursor:
2534            Should be set to -1 for the initial call and then is used to
2535            control what result page Twitter returns.
2536          count:
2537            The number of users to return per page, up to a maximum of 200.
2538            Defaults to 200. [Optional]
2539          skip_status:
2540            If True the statuses will not be returned in the user items.
2541            [Optional]
2542          include_user_entities:
2543            When True, the user entities will be included. [Optional]
2544
2545        Returns:
2546          next_cursor, previous_cursor, data sequence of twitter.User
2547          instances, one for each follower
2548        """
2549        url = '%s/followers/list.json' % self.base_url
2550        return self._GetFriendsFollowersPaged(url,
2551                                              user_id,
2552                                              screen_name,
2553                                              cursor,
2554                                              count,
2555                                              skip_status,
2556                                              include_user_entities)
2557
2558    def GetFriendsPaged(self,
2559                        user_id=None,
2560                        screen_name=None,
2561                        cursor=-1,
2562                        count=200,
2563                        skip_status=False,
2564                        include_user_entities=True):
2565        """Make a cursor driven call to return the list of all friends.
2566
2567        Args:
2568          user_id:
2569            The twitter id of the user whose friends you are fetching.
2570            If not specified, defaults to the authenticated user. [Optional]
2571          screen_name:
2572            The twitter name of the user whose friends you are fetching.
2573            If not specified, defaults to the authenticated user. [Optional]
2574          cursor:
2575            Should be set to -1 for the initial call and then is used to
2576            control what result page Twitter returns.
2577          count:
2578            The number of users to return per page, up to a current maximum of
2579            200. Defaults to 200. [Optional]
2580          skip_status:
2581            If True the statuses will not be returned in the user items.
2582            [Optional]
2583          include_user_entities:
2584            When True, the user entities will be included. [Optional]
2585
2586        Returns:
2587          next_cursor, previous_cursor, data sequence of twitter.User
2588          instances, one for each follower
2589        """
2590        url = '%s/friends/list.json' % self.base_url
2591        return self._GetFriendsFollowersPaged(url,
2592                                              user_id,
2593                                              screen_name,
2594                                              cursor,
2595                                              count,
2596                                              skip_status,
2597                                              include_user_entities)
2598
2599    def _GetFriendsFollowers(self,
2600                             url=None,
2601                             user_id=None,
2602                             screen_name=None,
2603                             cursor=None,
2604                             count=None,
2605                             total_count=None,
2606                             skip_status=False,
2607                             include_user_entities=True):
2608
2609        """ Fetch the sequence of twitter.User instances, one for each friend
2610        or follower.
2611
2612        Args:
2613          url:
2614            URL to get. Either base_url + ('/followers/list.json' or
2615            '/friends/list.json').
2616          user_id:
2617            The twitter id of the user whose friends you are fetching.
2618            If not specified, defaults to the authenticated user. [Optional]
2619          screen_name:
2620            The twitter name of the user whose friends you are fetching.
2621            If not specified, defaults to the authenticated user. [Optional]
2622          cursor:
2623            Should be set to -1 for the initial call and then is used to
2624            control what result page Twitter returns.
2625          count:
2626            The number of users to return per page, up to a maximum of 200.
2627            Defaults to 200. [Optional]
2628          total_count:
2629            The upper bound of number of users to return, defaults to None.
2630          skip_status:
2631            If True the statuses will not be returned in the user items.
2632            [Optional]
2633          include_user_entities:
2634            When True, the user entities will be included. [Optional]
2635
2636        Returns:
2637          A sequence of twitter.User instances, one for each friend or follower
2638        """
2639
2640        if cursor is not None or count is not None:
2641            warnings.warn(
2642                "Use of 'cursor' and 'count' parameters are deprecated as of "
2643                "python-twitter 3.0. Please use GetFriendsPaged or "
2644                "GetFollowersPaged instead.",
2645                PythonTwitterDeprecationWarning330)
2646
2647        count = 200
2648        cursor = -1
2649        result = []
2650
2651        if total_count:
2652            try:
2653                total_count = int(total_count)
2654            except ValueError:
2655                raise TwitterError({'message': "total_count must be an integer"})
2656
2657            if total_count <= 200:
2658                count = total_count
2659
2660        while True:
2661            if total_count is not None and len(result) + count > total_count:
2662                break
2663
2664            next_cursor, previous_cursor, data = self._GetFriendsFollowersPaged(
2665                url,
2666                user_id,
2667                screen_name,
2668                cursor,
2669                count,
2670                skip_status,
2671                include_user_entities)
2672
2673            if next_cursor:
2674                cursor = next_cursor
2675
2676            result.extend(data)
2677
2678            if next_cursor == 0 or next_cursor == previous_cursor:
2679                break
2680
2681        return result
2682
2683    def GetFollowers(self,
2684                     user_id=None,
2685                     screen_name=None,
2686                     cursor=None,
2687                     count=None,
2688                     total_count=None,
2689                     skip_status=False,
2690                     include_user_entities=True):
2691        """Fetch the sequence of twitter.User instances, one for each follower.
2692
2693        If both user_id and screen_name are specified, this call will return
2694        the followers of the user specified by screen_name, however this
2695        behavior is undocumented by Twitter and may change without warning.
2696
2697        Args:
2698          user_id:
2699            The twitter id of the user whose followers you are fetching.
2700            If not specified, defaults to the authenticated user. [Optional]
2701          screen_name:
2702            The twitter name of the user whose followers you are fetching.
2703            If not specified, defaults to the authenticated user. [Optional]
2704          cursor:
2705            Should be set to -1 for the initial call and then is used to
2706            control what result page Twitter returns.
2707          count:
2708            The number of users to return per page, up to a maximum of 200.
2709            Defaults to 200. [Optional]
2710          total_count:
2711            The upper bound of number of users to return, defaults to None.
2712          skip_status:
2713            If True the statuses will not be returned in the user items. [Optional]
2714          include_user_entities:
2715            When True, the user entities will be included. [Optional]
2716
2717        Returns:
2718          A sequence of twitter.User instances, one for each follower
2719        """
2720        url = '%s/followers/list.json' % self.base_url
2721        return self._GetFriendsFollowers(url,
2722                                         user_id,
2723                                         screen_name,
2724                                         cursor,
2725                                         count,
2726                                         total_count,
2727                                         skip_status,
2728                                         include_user_entities)
2729
2730    def GetFriends(self,
2731                   user_id=None,
2732                   screen_name=None,
2733                   cursor=None,
2734                   count=None,
2735                   total_count=None,
2736                   skip_status=False,
2737                   include_user_entities=True):
2738        """Fetch the sequence of twitter.User instances, one for each friend.
2739
2740        If both user_id and screen_name are specified, this call will return
2741        the followers of the user specified by screen_name, however this
2742        behavior is undocumented by Twitter and may change without warning.
2743
2744        Args:
2745          user_id:
2746            The twitter id of the user whose friends you are fetching.
2747            If not specified, defaults to the authenticated user. [Optional]
2748          screen_name:
2749            The twitter name of the user whose friends you are fetching.
2750            If not specified, defaults to the authenticated user. [Optional]
2751          cursor:
2752            Should be set to -1 for the initial call and then is used to
2753            control what result page Twitter returns.
2754          count:
2755            The number of users to return per page, up to a maximum of 200.
2756            Defaults to 200. [Optional]
2757          total_count:
2758            The upper bound of number of users to return, defaults to None.
2759          skip_status:
2760            If True the statuses will not be returned in the user items.
2761            [Optional]
2762          include_user_entities:
2763            When True, the user entities will be included. [Optional]
2764
2765        Returns:
2766          A sequence of twitter.User instances, one for each friend
2767        """
2768        url = '%s/friends/list.json' % self.base_url
2769        return self._GetFriendsFollowers(url,
2770                                         user_id,
2771                                         screen_name,
2772                                         cursor,
2773                                         count,
2774                                         total_count,
2775                                         skip_status,
2776                                         include_user_entities)
2777
2778    def UsersLookup(self,
2779                    user_id=None,
2780                    screen_name=None,
2781                    users=None,
2782                    include_entities=True,
2783                    return_json=False):
2784        """Fetch extended information for the specified users.
2785
2786        Users may be specified either as lists of either user_ids,
2787        screen_names, or twitter.User objects. The list of users that
2788        are queried is the union of all specified parameters.
2789
2790        No more than 100 users may be given per request.
2791
2792        Args:
2793          user_id (int, list, optional):
2794            A list of user_ids to retrieve extended information.
2795          screen_name (str, list, optional):
2796            A list of screen_names to retrieve extended information.
2797          users (list, optional):
2798            A list of twitter.User objects to retrieve extended information.
2799          include_entities (bool, optional):
2800            The entities node that may appear within embedded statuses will be
2801            excluded when set to False.
2802          return_json (bool, optional):
2803            If True JSON data will be returned, instead of twitter.User
2804
2805        Returns:
2806          A list of twitter.User objects for the requested users
2807        """
2808        if not any([user_id, screen_name, users]):
2809            raise TwitterError("Specify at least one of user_id, screen_name, or users.")
2810
2811        url = '%s/users/lookup.json' % self.base_url
2812        parameters = {
2813            'include_entities': include_entities
2814        }
2815        uids = list()
2816        if user_id:
2817            uids.extend(user_id)
2818        if users:
2819            uids.extend([u.id for u in users])
2820        if len(uids):
2821            parameters['user_id'] = ','.join([str(u) for u in uids])
2822        if screen_name:
2823            parameters['screen_name'] = parse_arg_list(screen_name, 'screen_name')
2824
2825        if len(uids) > 100:
2826            raise TwitterError("No more than 100 users may be requested per request.")
2827
2828        resp = self._RequestUrl(url, 'GET', data=parameters)
2829        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
2830
2831        if return_json:
2832            return data
2833        else:
2834            return [User.NewFromJsonDict(u) for u in data]
2835
2836    def GetUser(self,
2837                user_id=None,
2838                screen_name=None,
2839                include_entities=True,
2840                return_json=False):
2841        """Returns a single user.
2842
2843        Args:
2844          user_id (int, optional):
2845            The id of the user to retrieve.
2846          screen_name (str, optional):
2847            The screen name of the user for whom to return results for.
2848            Either a user_id or screen_name is required for this method.
2849          include_entities (bool, optional):
2850            The entities node will be omitted when set to False.
2851          return_json (bool, optional):
2852            If True JSON data will be returned, instead of twitter.User
2853
2854        Returns:
2855          A twitter.User instance representing that user
2856        """
2857        url = '%s/users/show.json' % (self.base_url)
2858        parameters = {
2859            'include_entities': include_entities
2860        }
2861        if user_id:
2862            parameters['user_id'] = user_id
2863        elif screen_name:
2864            parameters['screen_name'] = screen_name
2865        else:
2866            raise TwitterError("Specify at least one of user_id or screen_name.")
2867
2868        resp = self._RequestUrl(url, 'GET', data=parameters)
2869        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
2870
2871        if return_json:
2872            return data
2873        else:
2874            return User.NewFromJsonDict(data)
2875
2876    def GetDirectMessages(self,
2877                          since_id=None,
2878                          max_id=None,
2879                          count=None,
2880                          include_entities=True,
2881                          skip_status=False,
2882                          full_text=False,
2883                          page=None,
2884                          return_json=False):
2885        """Returns a list of the direct messages sent to the authenticating user.
2886
2887        Args:
2888          since_id:
2889            Returns results with an ID greater than (that is, more recent
2890            than) the specified ID. There are limits to the number of
2891            Tweets which can be accessed through the API. If the limit of
2892            Tweets has occurred since the since_id, the since_id will be
2893            forced to the oldest ID available. [Optional]
2894          max_id:
2895            Returns results with an ID less than (that is, older than) or
2896            equal to the specified ID. [Optional]
2897          count:
2898            Specifies the number of direct messages to try and retrieve, up to a
2899            maximum of 200. The value of count is best thought of as a limit to the
2900            number of Tweets to return because suspended or deleted content is
2901            removed after the count has been applied. [Optional]
2902          include_entities:
2903            The entities node will be omitted when set to False.
2904            [Optional]
2905          skip_status:
2906            When set to True statuses will not be included in the returned user
2907            objects. [Optional]
2908          full_text:
2909            When set to True full message will be included in the returned message
2910            object if message length is bigger than CHARACTER_LIMIT characters. [Optional]
2911          page:
2912            If you want more than 200 messages, you can use this and get 20 messages
2913            each time. You must recall it and increment the page value until it
2914            return nothing. You can't use count option with it. First value is 1 and
2915            not 0.
2916          return_json (bool, optional):
2917            If True JSON data will be returned, instead of twitter.User
2918
2919        Returns:
2920          A sequence of twitter.DirectMessage instances
2921        """
2922        url = '%s/direct_messages.json' % self.base_url
2923        parameters = {
2924            'full_text': bool(full_text),
2925            'include_entities': bool(include_entities),
2926            'max_id': max_id,
2927            'since_id': since_id,
2928            'skip_status': bool(skip_status),
2929        }
2930
2931        if count:
2932            parameters['count'] = enf_type('count', int, count)
2933        if page:
2934            parameters['page'] = enf_type('page', int, page)
2935
2936        resp = self._RequestUrl(url, 'GET', data=parameters)
2937        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
2938
2939        if return_json:
2940            return data
2941        else:
2942            return [DirectMessage.NewFromJsonDict(x) for x in data]
2943
2944    def GetSentDirectMessages(self,
2945                              since_id=None,
2946                              max_id=None,
2947                              count=None,
2948                              page=None,
2949                              include_entities=True,
2950                              return_json=False):
2951        """Returns a list of the direct messages sent by the authenticating user.
2952
2953        Args:
2954          since_id (int, optional):
2955            Returns results with an ID greater than (that is, more recent
2956            than) the specified ID. There are limits to the number of
2957            Tweets which can be accessed through the API. If the limit of
2958            Tweets has occured since the since_id, the since_id will be
2959            forced to the oldest ID available.
2960          max_id (int, optional):
2961            Returns results with an ID less than (that is, older than) or
2962            equal to the specified ID.
2963          count (int, optional):
2964            Specifies the number of direct messages to try and retrieve, up to a
2965            maximum of 200. The value of count is best thought of as a limit to the
2966            number of Tweets to return because suspended or deleted content is
2967            removed after the count has been applied.
2968          page (int, optional):
2969            Specifies the page of results to retrieve.
2970            Note: there are pagination limits. [Optional]
2971          include_entities (bool, optional):
2972            The entities node will be omitted when set to False.
2973          return_json (bool, optional):
2974            If True JSON data will be returned, instead of twitter.User
2975
2976        Returns:
2977          A sequence of twitter.DirectMessage instances
2978        """
2979        url = '%s/direct_messages/sent.json' % self.base_url
2980
2981        parameters = {
2982            'include_entities': bool(include_entities),
2983            'max_id': max_id,
2984            'since_id': since_id,
2985        }
2986
2987        if count:
2988            parameters['count'] = enf_type('count', int, count)
2989        if page:
2990            parameters['page'] = enf_type('page', int, page)
2991
2992        resp = self._RequestUrl(url, 'GET', data=parameters)
2993        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
2994
2995        if return_json:
2996            return data
2997        else:
2998            return [DirectMessage.NewFromJsonDict(x) for x in data]
2999
3000    def PostDirectMessage(self,
3001                          text,
3002                          user_id=None,
3003                          screen_name=None,
3004                          return_json=False):
3005        """Post a twitter direct message from the authenticated user.
3006
3007        Args:
3008          text: The message text to be posted.
3009          user_id:
3010            The ID of the user who should receive the direct message.
3011          return_json (bool, optional):
3012            If True JSON data will be returned, instead of twitter.DirectMessage
3013        Returns:
3014          A twitter.DirectMessage instance representing the message posted
3015        """
3016        url = '%s/direct_messages/events/new.json' % self.base_url
3017
3018        # Hack to allow some sort of backwards compatibility with older versions
3019        # part of the fix for Issue #587
3020        if user_id is None and screen_name is not None:
3021            user_id = self.GetUser(screen_name=screen_name).id
3022
3023        event = {
3024            'event': {
3025                'type': 'message_create',
3026                'message_create': {
3027                    'target': {
3028                        'recipient_id': user_id,
3029                    },
3030                    'message_data': {
3031                        'text': text
3032                    }
3033                }
3034            }
3035        }
3036
3037        resp = self._RequestUrl(url, 'POST', json=event)
3038        data = resp.json()
3039
3040        if return_json:
3041            return data
3042        else:
3043            dm = DirectMessage(
3044                created_at=data['event']['created_timestamp'],
3045                id=data['event']['id'],
3046                recipient_id=data['event']['message_create']['target']['recipient_id'],
3047                sender_id=data['event']['message_create']['sender_id'],
3048                text=data['event']['message_create']['message_data']['text'],
3049            )
3050            dm._json = data
3051            return dm
3052
3053    def DestroyDirectMessage(self, message_id, include_entities=True, return_json=False):
3054        """Destroys the direct message specified in the required ID parameter.
3055
3056        The twitter.Api instance must be authenticated, and the
3057        authenticating user must be the recipient of the specified direct
3058        message.
3059
3060        Args:
3061          message_id:
3062            The id of the direct message to be destroyed
3063          return_json (bool, optional):
3064            If True JSON data will be returned, instead of twitter.User
3065
3066        Returns:
3067          A twitter.DirectMessage instance representing the message destroyed
3068        """
3069        url = '%s/direct_messages/destroy.json' % self.base_url
3070        data = {
3071            'id': enf_type('message_id', int, message_id),
3072            'include_entities': enf_type('include_entities', bool, include_entities)
3073        }
3074
3075        resp = self._RequestUrl(url, 'POST', data=data)
3076        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3077
3078        if return_json:
3079            return data
3080        else:
3081            return DirectMessage.NewFromJsonDict(data)
3082
3083    def CreateFriendship(self, user_id=None, screen_name=None, follow=True, retweets=True, **kwargs):
3084        """Befriends the user specified by the user_id or screen_name.
3085
3086        Args:
3087          user_id (int, optional):
3088            A user_id to follow
3089          screen_name (str, optional)
3090            A screen_name to follow
3091          follow (bool, optional):
3092            Set to False to disable notifications for the target user
3093          retweets (bool, optional):
3094            Enable or disable retweets from the target user.
3095
3096        Returns:
3097          A twitter.User instance representing the befriended user.
3098        """
3099        return self._AddOrEditFriendship(user_id=user_id,
3100                                         screen_name=screen_name,
3101                                         follow=follow,
3102                                         retweets=retweets,
3103                                         **kwargs)
3104
3105    def _AddOrEditFriendship(self,
3106                             user_id=None,
3107                             screen_name=None,
3108                             uri_end='create',
3109                             follow_key='follow',
3110                             follow=True,
3111                             **kwargs):
3112        """Shared method for Create/Update Friendship."""
3113        url = '%s/friendships/%s.json' % (self.base_url, uri_end)
3114        data = {}
3115        if user_id:
3116            data['user_id'] = user_id
3117        elif screen_name:
3118            data['screen_name'] = screen_name
3119        else:
3120            raise TwitterError("Specify at least one of user_id or screen_name.")
3121
3122        follow_json = json.dumps(follow)
3123        data['{}'.format(follow_key)] = follow_json
3124        data.update(**kwargs)
3125
3126        resp = self._RequestUrl(url, 'POST', data=data)
3127        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3128
3129        return User.NewFromJsonDict(data)
3130
3131    def UpdateFriendship(self,
3132                         user_id=None,
3133                         screen_name=None,
3134                         follow=True,
3135                         retweets=True,
3136                         **kwargs):
3137        """Updates a friendship with the user specified by the user_id or screen_name.
3138
3139        Args:
3140          user_id (int, optional):
3141            A user_id to update
3142          screen_name (str, optional):
3143            A screen_name to update
3144          follow (bool, optional):
3145            Set to False to disable notifications for the target user
3146          retweets (bool, optional):
3147            Enable or disable retweets from the target user.
3148          device:
3149            Set to False to disable notifications for the target user
3150
3151        Returns:
3152          A twitter.User instance representing the befriended user.
3153        """
3154        return self._AddOrEditFriendship(user_id=user_id,
3155                                         screen_name=screen_name,
3156                                         follow=follow,
3157                                         follow_key='device',
3158                                         retweets=retweets,
3159                                         uri_end='update',
3160                                         **kwargs)
3161
3162    def DestroyFriendship(self, user_id=None, screen_name=None):
3163        """Discontinues friendship with a user_id or screen_name.
3164
3165        Args:
3166          user_id:
3167            A user_id to unfollow [Optional]
3168          screen_name:
3169            A screen_name to unfollow [Optional]
3170
3171        Returns:
3172          A twitter.User instance representing the discontinued friend.
3173        """
3174        url = '%s/friendships/destroy.json' % self.base_url
3175        data = {}
3176        if user_id:
3177            data['user_id'] = user_id
3178        elif screen_name:
3179            data['screen_name'] = screen_name
3180        else:
3181            raise TwitterError("Specify at least one of user_id or screen_name.")
3182
3183        resp = self._RequestUrl(url, 'POST', data=data)
3184        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3185
3186        return User.NewFromJsonDict(data)
3187
3188    def ShowFriendship(self,
3189                       source_user_id=None,
3190                       source_screen_name=None,
3191                       target_user_id=None,
3192                       target_screen_name=None):
3193        """Returns information about the relationship between the two users.
3194
3195        Args:
3196          source_id:
3197            The user_id of the subject user [Optional]
3198          source_screen_name:
3199            The screen_name of the subject user [Optional]
3200          target_id:
3201            The user_id of the target user [Optional]
3202          target_screen_name:
3203            The screen_name of the target user [Optional]
3204
3205        Returns:
3206          A Twitter Json structure.
3207        """
3208        url = '%s/friendships/show.json' % self.base_url
3209        data = {}
3210        if source_user_id:
3211            data['source_id'] = source_user_id
3212        elif source_screen_name:
3213            data['source_screen_name'] = source_screen_name
3214        else:
3215            raise TwitterError({'message': "Specify at least one of source_user_id or source_screen_name."})
3216        if target_user_id:
3217            data['target_id'] = target_user_id
3218        elif target_screen_name:
3219            data['target_screen_name'] = target_screen_name
3220        else:
3221            raise TwitterError({'message': "Specify at least one of target_user_id or target_screen_name."})
3222
3223        resp = self._RequestUrl(url, 'GET', data=data)
3224        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3225
3226        return data
3227
3228    def LookupFriendship(self,
3229                         user_id=None,
3230                         screen_name=None,
3231                         return_json=False):
3232        """Lookup friendship status for user to authed user.
3233
3234        Users may be specified either as lists of either user_ids,
3235        screen_names, or twitter.User objects. The list of users that
3236        are queried is the union of all specified parameters.
3237
3238        Up to 100 users may be specified.
3239
3240        Args:
3241          user_id (int, User, or list of ints or Users, optional):
3242            A list of user_ids to retrieve extended information.
3243          screen_name (string, User, or list of strings or Users, optional):
3244            A list of screen_names to retrieve extended information.
3245          return_json (bool, optional):
3246            If True JSON data will be returned, instead of twitter.User
3247
3248        Returns:
3249          list: A list of twitter.UserStatus instance representing the
3250          friendship status between the specified users and the authenticated
3251          user.
3252        """
3253        url = '%s/friendships/lookup.json' % (self.base_url)
3254        parameters = {}
3255
3256        if user_id:
3257            if isinstance(user_id, (list, tuple)):
3258                uids = list()
3259                for user in user_id:
3260                    if isinstance(user, User):
3261                        uids.append(user.id)
3262                    else:
3263                        uids.append(enf_type('user_id', int, user))
3264                parameters['user_id'] = ",".join([str(uid) for uid in uids])
3265            else:
3266                if isinstance(user_id, User):
3267                    parameters['user_id'] = user_id.id
3268                else:
3269                    parameters['user_id'] = enf_type('user_id', int, user_id)
3270        if screen_name:
3271            if isinstance(screen_name, (list, tuple)):
3272                sn_list = list()
3273                for user in screen_name:
3274                    if isinstance(user, User):
3275                        sn_list.append(user.screen_name)
3276                    else:
3277                        sn_list.append(enf_type('screen_name', str, user))
3278                parameters['screen_name'] = ','.join(sn_list)
3279            else:
3280                if isinstance(screen_name, User):
3281                    parameters['screen_name'] = screen_name.screen_name
3282                else:
3283                    parameters['screen_name'] = enf_type('screen_name', str, screen_name)
3284        if not user_id and not screen_name:
3285            raise TwitterError("Specify at least one of user_id or screen_name.")
3286
3287        resp = self._RequestUrl(url, 'GET', data=parameters)
3288        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3289
3290        if return_json:
3291            return data
3292        else:
3293            return [UserStatus.NewFromJsonDict(x) for x in data]
3294
3295    def IncomingFriendship(self,
3296                           cursor=None,
3297                           stringify_ids=None):
3298        """Returns a collection of user IDs belonging to users who have
3299        pending request to follow the authenticated user.
3300
3301        Args:
3302          cursor:
3303            breaks the ids into pages of no more than 5000.
3304          stringify_ids:
3305            returns the IDs as unicode strings. [Optional]
3306
3307        Returns:
3308          A list of user IDs
3309        """
3310        url = '%s/friendships/incoming.json' % (self.base_url)
3311        parameters = {}
3312        if stringify_ids:
3313            parameters['stringify_ids'] = 'true'
3314        result = []
3315
3316        total_count = 0
3317        while True:
3318            if cursor:
3319                try:
3320                    parameters['count'] = int(cursor)
3321                except ValueError:
3322                    raise TwitterError({'message': "cursor must be an integer"})
3323            resp = self._RequestUrl(url, 'GET', data=parameters)
3324            data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3325            result += [x for x in data['ids']]
3326            if 'next_cursor' in data:
3327                if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']:
3328                    break
3329                else:
3330                    cursor = data['next_cursor']
3331                    total_count -= len(data['ids'])
3332                    if total_count < 1:
3333                        break
3334            else:
3335                break
3336
3337        return result
3338
3339    def OutgoingFriendship(self,
3340                           cursor=None,
3341                           stringify_ids=None):
3342        """Returns a collection of user IDs for every protected user
3343        for whom the authenticated user has a pending follow request.
3344
3345        Args:
3346          cursor:
3347            breaks the ids into pages of no more than 5000.
3348          stringify_ids:
3349            returns the IDs as unicode strings. [Optional]
3350
3351        Returns:
3352          A list of user IDs
3353        """
3354        url = '%s/friendships/outgoing.json' % (self.base_url)
3355        parameters = {}
3356        if stringify_ids:
3357            parameters['stringify_ids'] = 'true'
3358        result = []
3359
3360        total_count = 0
3361        while True:
3362            if cursor:
3363                try:
3364                    parameters['count'] = int(cursor)
3365                except ValueError:
3366                    raise TwitterError({'message': "cursor must be an integer"})
3367            resp = self._RequestUrl(url, 'GET', data=parameters)
3368            data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3369            result += [x for x in data['ids']]
3370            if 'next_cursor' in data:
3371                if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']:
3372                    break
3373                else:
3374                    cursor = data['next_cursor']
3375                    total_count -= len(data['ids'])
3376                    if total_count < 1:
3377                        break
3378            else:
3379                break
3380
3381        return result
3382
3383    def CreateFavorite(self,
3384                       status=None,
3385                       status_id=None,
3386                       include_entities=True):
3387        """Favorites the specified status object or id as the authenticating user.
3388
3389        Returns the favorite status when successful.
3390
3391        Args:
3392          status_id (int, optional):
3393            The id of the twitter status to mark as a favorite.
3394          status (twitter.Status, optional):
3395            The twitter.Status object to mark as a favorite.
3396          include_entities (bool, optional):
3397            The entities node will be omitted when set to False.
3398
3399        Returns:
3400          A twitter.Status instance representing the newly-marked favorite.
3401        """
3402        url = '%s/favorites/create.json' % self.base_url
3403        data = {}
3404        if status_id:
3405            data['id'] = status_id
3406        elif status:
3407            data['id'] = status.id
3408        else:
3409            raise TwitterError({'message': "Specify status_id or status"})
3410        data['include_entities'] = enf_type('include_entities', bool, include_entities)
3411
3412        resp = self._RequestUrl(url, 'POST', data=data)
3413        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3414
3415        return Status.NewFromJsonDict(data)
3416
3417    def DestroyFavorite(self,
3418                        status=None,
3419                        status_id=None,
3420                        include_entities=True):
3421        """Un-Favorites the specified status object or id as the authenticating user.
3422
3423        Returns the un-favorited status when successful.
3424
3425        Args:
3426          status_id (int, optional):
3427            The id of the twitter status to mark as a favorite.
3428          status (twitter.Status, optional):
3429            The twitter.Status object to mark as a favorite.
3430          include_entities (bool, optional):
3431            The entities node will be omitted when set to False.
3432
3433        Returns:
3434          A twitter.Status instance representing the newly-unmarked favorite.
3435        """
3436        url = '%s/favorites/destroy.json' % self.base_url
3437        data = {}
3438
3439        if status_id:
3440            data['id'] = status_id
3441        elif status:
3442            data['id'] = status.id
3443        else:
3444            raise TwitterError({'message': "Specify status_id or status"})
3445        data['include_entities'] = enf_type('include_entities', bool, include_entities)
3446
3447        resp = self._RequestUrl(url, 'POST', data=data)
3448        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3449
3450        return Status.NewFromJsonDict(data)
3451
3452    def GetFavorites(self,
3453                     user_id=None,
3454                     screen_name=None,
3455                     count=None,
3456                     since_id=None,
3457                     max_id=None,
3458                     include_entities=True,
3459                     return_json=False):
3460        """Return a list of Status objects representing favorited tweets.
3461
3462        Returns up to 200 most recent tweets for the authenticated user.
3463
3464        Args:
3465          user_id (int, optional):
3466            Specifies the ID of the user for whom to return the
3467            favorites. Helpful for disambiguating when a valid user ID
3468            is also a valid screen name.
3469          screen_name (str, optional):
3470            Specifies the screen name of the user for whom to return the
3471            favorites. Helpful for disambiguating when a valid screen
3472            name is also a user ID.
3473          since_id (int, optional):
3474            Returns results with an ID greater than (that is, more recent
3475            than) the specified ID. There are limits to the number of
3476            Tweets which can be accessed through the API. If the limit of
3477            Tweets has occurred since the since_id, the since_id will be
3478            forced to the oldest ID available.
3479          max_id (int, optional):
3480            Returns only statuses with an ID less than (that is, older
3481            than) or equal to the specified ID.
3482          count (int, optional):
3483            Specifies the number of statuses to retrieve. May not be
3484            greater than 200.
3485          include_entities (bool, optional):
3486            The entities node will be omitted when set to False.
3487          return_json (bool, optional):
3488            If True JSON data will be returned, instead of twitter.User
3489
3490        Returns:
3491          A sequence of Status instances, one for each favorited tweet up to count
3492        """
3493        parameters = {}
3494        url = '%s/favorites/list.json' % self.base_url
3495        if user_id:
3496            parameters['user_id'] = enf_type('user_id', int, user_id)
3497        elif screen_name:
3498            parameters['screen_name'] = screen_name
3499        if since_id:
3500            parameters['since_id'] = enf_type('since_id', int, since_id)
3501        if max_id:
3502            parameters['max_id'] = enf_type('max_id', int, max_id)
3503        if count:
3504            parameters['count'] = enf_type('count', int, count)
3505        parameters['include_entities'] = enf_type('include_entities', bool, include_entities)
3506
3507        resp = self._RequestUrl(url, 'GET', data=parameters)
3508        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3509
3510        if return_json:
3511            return data
3512        else:
3513            return [Status.NewFromJsonDict(x) for x in data]
3514
3515    def GetMentions(self,
3516                    count=None,
3517                    since_id=None,
3518                    max_id=None,
3519                    trim_user=False,
3520                    contributor_details=False,
3521                    include_entities=True,
3522                    return_json=False):
3523        """Returns the 20 most recent mentions (status containing @screen_name)
3524        for the authenticating user.
3525
3526        Args:
3527          count:
3528            Specifies the number of tweets to try and retrieve, up to a maximum of
3529            200. The value of count is best thought of as a limit to the number of
3530            tweets to return because suspended or deleted content is removed after
3531            the count has been applied. [Optional]
3532          since_id:
3533            Returns results with an ID greater than (that is, more recent
3534            than) the specified ID. There are limits to the number of
3535            Tweets which can be accessed through the API. If the limit of
3536            Tweets has occurred since the since_id, the since_id will be
3537            forced to the oldest ID available. [Optional]
3538          max_id:
3539            Returns only statuses with an ID less than
3540            (that is, older than) the specified ID. [Optional]
3541          trim_user:
3542            When set to True, each tweet returned in a timeline will include a user
3543            object including only the status authors numerical ID. Omit this
3544            parameter to receive the complete user object. [Optional]
3545          contributor_details:
3546            If set to True, this parameter enhances the contributors element of the
3547            status response to include the screen_name of the contributor. By
3548            default only the user_id of the contributor is included. [Optional]
3549          include_entities:
3550            The entities node will be disincluded when set to False. [Optional]
3551          return_json (bool, optional):
3552            If True JSON data will be returned, instead of twitter.User
3553
3554        Returns:
3555          A sequence of twitter.Status instances, one for each mention of the user.
3556        """
3557        url = '%s/statuses/mentions_timeline.json' % self.base_url
3558
3559        parameters = {
3560            'contributor_details': bool(contributor_details),
3561            'include_entities': bool(include_entities),
3562            'max_id': max_id,
3563            'since_id': since_id,
3564            'trim_user': bool(trim_user),
3565        }
3566
3567        if count:
3568            parameters['count'] = enf_type('count', int, count)
3569
3570        resp = self._RequestUrl(url, 'GET', data=parameters)
3571        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3572
3573        if return_json:
3574            return data
3575        else:
3576            return [Status.NewFromJsonDict(x) for x in data]
3577
3578    @staticmethod
3579    def _IDList(list_id, slug, owner_id, owner_screen_name):
3580        parameters = {}
3581        if list_id is not None:
3582            parameters['list_id'] = enf_type('list_id', int, list_id)
3583        elif slug is not None:
3584            parameters['slug'] = slug
3585            if owner_id is not None:
3586                parameters['owner_id'] = enf_type('owner_id', int, owner_id)
3587            elif owner_screen_name is not None:
3588                parameters['owner_screen_name'] = owner_screen_name
3589            else:
3590                raise TwitterError({'message': (
3591                    'If specifying a list by slug, an owner_id or '
3592                    'owner_screen_name must also be given.')})
3593        else:
3594            raise TwitterError({'message': (
3595                'Either list_id or slug and one of owner_id and '
3596                'owner_screen_name must be passed.')})
3597
3598        return parameters
3599
3600    def CreateList(self, name, mode=None, description=None):
3601        """Creates a new list with the give name for the authenticated user.
3602
3603        Args:
3604          name (str):
3605            New name for the list
3606          mode (str, optional):
3607            'public' or 'private'. Defaults to 'public'.
3608          description (str, optional):
3609            Description of the list.
3610
3611        Returns:
3612          twitter.list.List: A twitter.List instance representing the new list
3613        """
3614        url = '%s/lists/create.json' % self.base_url
3615        parameters = {'name': name}
3616        if mode is not None:
3617            parameters['mode'] = mode
3618        if description is not None:
3619            parameters['description'] = description
3620
3621        resp = self._RequestUrl(url, 'POST', data=parameters)
3622        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3623
3624        return List.NewFromJsonDict(data)
3625
3626    def DestroyList(self,
3627                    owner_screen_name=None,
3628                    owner_id=None,
3629                    list_id=None,
3630                    slug=None):
3631        """Destroys the list identified by list_id or slug and one of
3632        owner_screen_name or owner_id.
3633
3634        Args:
3635          owner_screen_name (str, optional):
3636            The screen_name of the user who owns the list being requested
3637            by a slug.
3638          owner_id (int, optional):
3639            The user ID of the user who owns the list being requested
3640            by a slug.
3641          list_id (int, optional):
3642            The numerical id of the list.
3643          slug (str, optional):
3644            You can identify a list by its slug instead of its numerical id.
3645            If you decide to do so, note that you'll also have to specify
3646            the list owner using the owner_id or owner_screen_name parameters.
3647
3648        Returns:
3649          twitter.list.List: A twitter.List instance representing the
3650          removed list.
3651        """
3652        url = '%s/lists/destroy.json' % self.base_url
3653        parameters = {}
3654
3655        parameters.update(self._IDList(list_id=list_id,
3656                                       slug=slug,
3657                                       owner_id=owner_id,
3658                                       owner_screen_name=owner_screen_name))
3659
3660        resp = self._RequestUrl(url, 'POST', data=parameters)
3661        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3662
3663        return List.NewFromJsonDict(data)
3664
3665    def CreateSubscription(self,
3666                           owner_screen_name=None,
3667                           owner_id=None,
3668                           list_id=None,
3669                           slug=None):
3670        """Creates a subscription to a list by the authenticated user.
3671
3672        Args:
3673          owner_screen_name (str, optional):
3674            The screen_name of the user who owns the list being requested
3675            by a slug.
3676          owner_id (int, optional):
3677            The user ID of the user who owns the list being requested
3678            by a slug.
3679          list_id (int, optional):
3680            The numerical id of the list.
3681          slug (str, optional):
3682            You can identify a list by its slug instead of its numerical id.
3683            If you decide to do so, note that you'll also have to specify
3684            the list owner using the owner_id or owner_screen_name parameters.
3685
3686        Returns:
3687          twitter.user.User: A twitter.User instance representing the user subscribed
3688        """
3689        url = '%s/lists/subscribers/create.json' % self.base_url
3690        parameters = {}
3691
3692        parameters.update(self._IDList(list_id=list_id,
3693                                       slug=slug,
3694                                       owner_id=owner_id,
3695                                       owner_screen_name=owner_screen_name))
3696
3697        resp = self._RequestUrl(url, 'POST', data=parameters)
3698        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3699
3700        return User.NewFromJsonDict(data)
3701
3702    def DestroySubscription(self,
3703                            owner_screen_name=None,
3704                            owner_id=None,
3705                            list_id=None,
3706                            slug=None):
3707        """Destroys the subscription to a list for the authenticated user.
3708
3709        Args:
3710          owner_screen_name (str, optional):
3711            The screen_name of the user who owns the list being requested
3712            by a slug.
3713          owner_id (int, optional):
3714            The user ID of the user who owns the list being requested
3715            by a slug.
3716          list_id (int, optional):
3717            The numerical id of the list.
3718          slug (str, optional):
3719            You can identify a list by its slug instead of its numerical id.
3720            If you decide to do so, note that you'll also have to specify the
3721            list owner using the owner_id or owner_screen_name parameters.
3722
3723        Returns:
3724          twitter.list.List: A twitter.List instance representing
3725          the removed list.
3726        """
3727        url = '%s/lists/subscribers/destroy.json' % (self.base_url)
3728        parameters = {}
3729
3730        parameters.update(self._IDList(list_id=list_id,
3731                                       slug=slug,
3732                                       owner_id=owner_id,
3733                                       owner_screen_name=owner_screen_name))
3734
3735        resp = self._RequestUrl(url, 'POST', data=parameters)
3736        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3737
3738        return List.NewFromJsonDict(data)
3739
3740    def ShowSubscription(self,
3741                         owner_screen_name=None,
3742                         owner_id=None,
3743                         list_id=None,
3744                         slug=None,
3745                         user_id=None,
3746                         screen_name=None,
3747                         include_entities=False,
3748                         skip_status=False,
3749                         return_json=False):
3750        """Check if the specified user is a subscriber of the specified list.
3751
3752        Returns the user if they are subscriber.
3753
3754        Args:
3755          owner_screen_name (str, optional):
3756            The screen_name of the user who owns the list being requested
3757            by a slug.
3758          owner_id (int, optional):
3759            The user ID of the user who owns the list being requested
3760            by a slug.
3761          list_id (int, optional):
3762            The numerical ID of the list.
3763          slug (str, optional):
3764            You can identify a list by its slug instead of its numerical ID.
3765            If you decide to do so, note that you'll also have to specify
3766            the list owner using the owner_id or owner_screen_name parameters.
3767          user_id (int, optional):
3768            The user_id or a list of user_id's to add to the list.
3769            If not given, then screen_name is required.
3770          screen_name (str, optional):
3771            The screen_name or a list of screen_name's to add to the list.
3772            If not given, then user_id is required.
3773          include_entities (bool, optional):
3774            If False, the timeline will not contain additional metadata.
3775            Defaults to True.
3776          skip_status (bool, optional):
3777            If True the statuses will not be returned in the user items.
3778          return_json (bool, optional):
3779            If True JSON data will be returned, instead of twitter.User
3780
3781        Returns:
3782          twitter.user.User: A twitter.User instance representing the user
3783          requested.
3784        """
3785        url = '%s/lists/subscribers/show.json' % (self.base_url)
3786        parameters = {}
3787
3788        parameters.update(self._IDList(list_id=list_id,
3789                                       slug=slug,
3790                                       owner_id=owner_id,
3791                                       owner_screen_name=owner_screen_name))
3792
3793        if user_id:
3794            parameters['user_id'] = enf_type('user_id', int, user_id)
3795        elif screen_name:
3796            parameters['screen_name'] = screen_name
3797        if skip_status:
3798            parameters['skip_status'] = True
3799        if include_entities:
3800            parameters['include_entities'] = True
3801
3802        resp = self._RequestUrl(url, 'GET', data=parameters)
3803        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3804
3805        if return_json:
3806            return data
3807        else:
3808            return User.NewFromJsonDict(data)
3809
3810    def GetSubscriptions(self,
3811                         user_id=None,
3812                         screen_name=None,
3813                         count=20,
3814                         cursor=-1,
3815                         return_json=False):
3816        """Obtain a collection of the lists the specified user is
3817        subscribed to. If neither user_id or screen_name is specified, the
3818        data returned will be for the authenticated user.
3819
3820        The list will contain a maximum of 20 lists per page by default.
3821
3822        Does not include the user's own lists.
3823
3824        Args:
3825          user_id (int, optional):
3826            The ID of the user for whom to return results for.
3827          screen_name (str, optional):
3828            The screen name of the user for whom to return results for.
3829          count (int, optional):
3830           The amount of results to return per page.
3831           No more than 1000 results will ever be returned in a single
3832           page. Defaults to 20.
3833          cursor (int, optional):
3834            The "page" value that Twitter will use to start building the
3835            list sequence from. Use the value of -1 to start at the
3836            beginning. Twitter will return in the result the values for
3837            next_cursor and previous_cursor.
3838          return_json (bool, optional):
3839            If True JSON data will be returned, instead of twitter.User
3840
3841        Returns:
3842          twitter.list.List: A sequence of twitter.List instances,
3843          one for each list
3844        """
3845        url = '%s/lists/subscriptions.json' % (self.base_url)
3846        parameters = {}
3847        parameters['cursor'] = enf_type('cursor', int, cursor)
3848        parameters['count'] = enf_type('count', int, count)
3849        if user_id is not None:
3850            parameters['user_id'] = enf_type('user_id', int, user_id)
3851        elif screen_name is not None:
3852            parameters['screen_name'] = screen_name
3853
3854        resp = self._RequestUrl(url, 'GET', data=parameters)
3855        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3856
3857        if return_json:
3858            return data
3859        else:
3860            return [List.NewFromJsonDict(x) for x in data['lists']]
3861
3862    def GetMemberships(self,
3863                       user_id=None,
3864                       screen_name=None,
3865                       count=20,
3866                       cursor=-1,
3867                       filter_to_owned_lists=False,
3868                       return_json=False):
3869        """Obtain the lists the specified user is a member of. If no user_id or
3870        screen_name is specified, the data returned will be for the
3871        authenticated user.
3872
3873        Returns a maximum of 20 lists per page by default.
3874
3875        Args:
3876          user_id (int, optional):
3877            The ID of the user for whom to return results for.
3878          screen_name (str, optional):
3879            The screen name of the user for whom to return
3880            results for.
3881          count (int, optional):
3882           The amount of results to return per page.
3883           No more than 1000 results will ever be returned in a single page.
3884           Defaults to 20.
3885          cursor (int, optional):
3886            The "page" value that Twitter will use to start building the list
3887            sequence from. Use the value of -1 to start at the beginning.
3888            Twitter will return in the result the values for next_cursor and
3889            previous_cursor.
3890          filter_to_owned_lists (bool, optional):
3891            Set to True to return only the lists the authenticating user
3892            owns, and the user specified by user_id or screen_name is a
3893            member of. Default value is False.
3894          return_json (bool, optional):
3895            If True JSON data will be returned, instead of twitter.User
3896
3897        Returns:
3898          list: A list of twitter.List instances, one for each list in which
3899          the user specified by user_id or screen_name is a member
3900        """
3901        url = '%s/lists/memberships.json' % (self.base_url)
3902        parameters = {}
3903        if cursor is not None:
3904            parameters['cursor'] = enf_type('cursor', int, cursor)
3905        if count is not None:
3906            parameters['count'] = enf_type('count', int, count)
3907        if filter_to_owned_lists:
3908            parameters['filter_to_owned_lists'] = enf_type(
3909                'filter_to_owned_lists', bool, filter_to_owned_lists)
3910
3911        if user_id is not None:
3912            parameters['user_id'] = enf_type('user_id', int, user_id)
3913        elif screen_name is not None:
3914            parameters['screen_name'] = screen_name
3915
3916        resp = self._RequestUrl(url, 'GET', data=parameters)
3917        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3918
3919        if return_json:
3920            return data
3921        else:
3922            return [List.NewFromJsonDict(x) for x in data['lists']]
3923
3924    def GetListsList(self,
3925                     screen_name=None,
3926                     user_id=None,
3927                     reverse=False,
3928                     return_json=False):
3929        """Returns all lists the user subscribes to, including their own.
3930        If no user_id or screen_name is specified, the data returned will be
3931        for the authenticated user.
3932
3933        Args:
3934          screen_name (str, optional):
3935            Specifies the screen name of the user for whom to return the
3936            user_timeline. Helpful for disambiguating when a valid screen
3937            name is also a user ID.
3938          user_id (int, optional):
3939            Specifies the ID of the user for whom to return the
3940            user_timeline. Helpful for disambiguating when a valid user ID
3941            is also a valid screen name.
3942          reverse (bool, optional):
3943            If False, the owned lists will be returned first, othewise
3944            subscribed lists will be at the top. Returns a maximum of 100
3945            entries regardless. Defaults to False.
3946          return_json (bool, optional):
3947            If True JSON data will be returned, instead of twitter.User
3948
3949        Returns:
3950          list: A sequence of twitter.List instances.
3951        """
3952        url = '%s/lists/list.json' % (self.base_url)
3953        parameters = {}
3954        if user_id:
3955            parameters['user_id'] = enf_type('user_id', int, user_id)
3956        elif screen_name:
3957            parameters['screen_name'] = screen_name
3958        if reverse:
3959            parameters['reverse'] = enf_type('reverse', bool, reverse)
3960
3961        resp = self._RequestUrl(url, 'GET', data=parameters)
3962        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
3963
3964        if return_json:
3965            return data
3966        else:
3967            return [List.NewFromJsonDict(x) for x in data]
3968
3969    def GetListTimeline(self,
3970                        list_id=None,
3971                        slug=None,
3972                        owner_id=None,
3973                        owner_screen_name=None,
3974                        since_id=None,
3975                        max_id=None,
3976                        count=None,
3977                        include_rts=True,
3978                        include_entities=True,
3979                        return_json=False):
3980        """Fetch the sequence of Status messages for a given List ID.
3981
3982        Args:
3983          list_id (int, optional):
3984            Specifies the ID of the list to retrieve.
3985          slug (str, optional):
3986            The slug name for the list to retrieve. If you specify None for the
3987            list_id, then you have to provide either a owner_screen_name or
3988            owner_id.
3989          owner_id (int, optional):
3990            Specifies the ID of the user for whom to return the
3991            list timeline. Helpful for disambiguating when a valid user ID
3992            is also a valid screen name.
3993          owner_screen_name (str, optional):
3994            Specifies the screen name of the user for whom to return the
3995            user_timeline. Helpful for disambiguating when a valid screen
3996            name is also a user ID.
3997          since_id (int, optional):
3998            Returns results with an ID greater than (that is, more recent than)
3999            the specified ID. There are limits to the number of Tweets which
4000            can be accessed through the API.
4001            If the limit of Tweets has occurred since the since_id, the
4002            since_id will be forced to the oldest ID available.
4003          max_id (int, optional):
4004            Returns only statuses with an ID less than (that is, older than) or
4005            equal to the specified ID.
4006          count (int, optional):
4007            Specifies the number of statuses to retrieve.
4008            May not be greater than 200.
4009          include_rts (bool, optional):
4010            If True, the timeline will contain native retweets (if they exist)
4011            in addition to the standard stream of tweets.
4012          include_entities (bool, optional):
4013            If False, the timeline will not contain additional metadata.
4014            Defaults to True.
4015          return_json (bool, optional):
4016            If True JSON data will be returned, instead of twitter.User
4017
4018        Returns:
4019          list: A list of twitter.status.Status instances, one for each
4020          message up to count.
4021        """
4022        url = '%s/lists/statuses.json' % self.base_url
4023        parameters = {}
4024
4025        parameters.update(self._IDList(list_id=list_id,
4026                                       slug=slug,
4027                                       owner_id=owner_id,
4028                                       owner_screen_name=owner_screen_name))
4029
4030        if since_id:
4031            parameters['since_id'] = enf_type('since_id', int, since_id)
4032        if max_id:
4033            parameters['max_id'] = enf_type('max_id', int, max_id)
4034        if count:
4035            parameters['count'] = enf_type('count', int, count)
4036        if not include_rts:
4037            parameters['include_rts'] = enf_type('include_rts', bool, include_rts)
4038        if not include_entities:
4039            parameters['include_entities'] = enf_type('include_entities', bool, include_entities)
4040
4041        resp = self._RequestUrl(url, 'GET', data=parameters)
4042        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4043
4044        if return_json:
4045            return data
4046        else:
4047            return [Status.NewFromJsonDict(x) for x in data]
4048
4049    def GetListMembersPaged(self,
4050                            list_id=None,
4051                            slug=None,
4052                            owner_id=None,
4053                            owner_screen_name=None,
4054                            cursor=-1,
4055                            count=100,
4056                            skip_status=False,
4057                            include_entities=True):
4058        """Fetch the sequence of twitter.User instances, one for each member
4059        of the given list_id or slug.
4060
4061        Args:
4062          list_id (int, optional):
4063            Specifies the ID of the list to retrieve.
4064          slug (str, optional):
4065            The slug name for the list to retrieve. If you specify None for the
4066            list_id, then you have to provide either a owner_screen_name or
4067            owner_id.
4068          owner_id (int, optional):
4069            Specifies the ID of the user for whom to return the
4070            list timeline. Helpful for disambiguating when a valid user ID
4071            is also a valid screen name.
4072          owner_screen_name (str, optional):
4073            Specifies the screen name of the user for whom to return the
4074            user_timeline. Helpful for disambiguating when a valid screen
4075            name is also a user ID.
4076          cursor (int, optional):
4077            Should be set to -1 for the initial call and then is used to
4078            control what result page Twitter returns.
4079          skip_status (bool, optional):
4080            If True the statuses will not be returned in the user items.
4081          include_entities (bool, optional):
4082            If False, the timeline will not contain additional metadata.
4083            Defaults to True.
4084
4085        Returns:
4086          list: A sequence of twitter.user.User instances, one for each
4087          member of the twitter.list.List.
4088        """
4089        url = '%s/lists/members.json' % self.base_url
4090        parameters = {}
4091
4092        parameters.update(self._IDList(list_id=list_id,
4093                                       slug=slug,
4094                                       owner_id=owner_id,
4095                                       owner_screen_name=owner_screen_name))
4096
4097        if count:
4098            parameters['count'] = enf_type('count', int, count)
4099        if cursor:
4100            parameters['cursor'] = enf_type('cursor', int, cursor)
4101
4102        parameters['skip_status'] = enf_type('skip_status', bool, skip_status)
4103        parameters['include_entities'] = enf_type('include_entities', bool, include_entities)
4104
4105        resp = self._RequestUrl(url, 'GET', data=parameters)
4106        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4107        next_cursor = data.get('next_cursor', 0)
4108        previous_cursor = data.get('previous_cursor', 0)
4109        users = [User.NewFromJsonDict(user) for user in data.get('users', [])]
4110
4111        return next_cursor, previous_cursor, users
4112
4113    def GetListMembers(self,
4114                       list_id=None,
4115                       slug=None,
4116                       owner_id=None,
4117                       owner_screen_name=None,
4118                       skip_status=False,
4119                       include_entities=False):
4120        """Fetch the sequence of twitter.User instances, one for each member
4121        of the given list_id or slug.
4122
4123        Args:
4124          list_id (int, optional):
4125            Specifies the ID of the list to retrieve.
4126          slug (str, optional):
4127            The slug name for the list to retrieve. If you specify None for the
4128            list_id, then you have to provide either a owner_screen_name or
4129            owner_id.
4130          owner_id (int, optional):
4131            Specifies the ID of the user for whom to return the
4132            list timeline. Helpful for disambiguating when a valid user ID
4133            is also a valid screen name.
4134          owner_screen_name (str, optional):
4135            Specifies the screen name of the user for whom to return the
4136            user_timeline. Helpful for disambiguating when a valid screen
4137            name is also a user ID.
4138          skip_status (bool, optional):
4139            If True the statuses will not be returned in the user items.
4140          include_entities (bool, optional):
4141            If False, the timeline will not contain additional metadata.
4142            Defaults to True.
4143
4144        Returns:
4145          list: A sequence of twitter.user.User instances, one for each
4146          member of the twitter.list.List.
4147        """
4148        cursor = -1
4149        result = []
4150        while True:
4151            next_cursor, previous_cursor, users = self.GetListMembersPaged(
4152                list_id=list_id,
4153                slug=slug,
4154                owner_id=owner_id,
4155                owner_screen_name=owner_screen_name,
4156                cursor=cursor,
4157                skip_status=skip_status,
4158                include_entities=include_entities)
4159            result += users
4160
4161            if next_cursor == 0 or next_cursor == previous_cursor:
4162                break
4163            else:
4164                cursor = next_cursor
4165
4166        return result
4167
4168    def CreateListsMember(self,
4169                          list_id=None,
4170                          slug=None,
4171                          user_id=None,
4172                          screen_name=None,
4173                          owner_screen_name=None,
4174                          owner_id=None):
4175        """Add a new member (or list of members) to the specified list.
4176
4177        Args:
4178          list_id (int, optional):
4179            The numerical id of the list.
4180          slug (str, optional):
4181            You can identify a list by its slug instead of its numerical id.
4182            If you decide to do so, note that you'll also have to specify the
4183            list owner using the owner_id or owner_screen_name parameters.
4184          user_id (int, optional):
4185            The user_id or a list of user_id's to add to the list.
4186            If not given, then screen_name is required.
4187          screen_name (str, optional):
4188            The screen_name or a list of screen_name's to add to the list.
4189            If not given, then user_id is required.
4190          owner_screen_name (str, optional):
4191            The screen_name of the user who owns the list being requested by
4192            a slug.
4193          owner_id (int, optional):
4194            The user ID of the user who owns the list being requested by
4195            a slug.
4196
4197        Returns:
4198          twitter.list.List: A twitter.List instance representing the list
4199          subscribed to.
4200        """
4201        is_list = False
4202        parameters = {}
4203
4204        parameters.update(self._IDList(list_id=list_id,
4205                                       slug=slug,
4206                                       owner_id=owner_id,
4207                                       owner_screen_name=owner_screen_name))
4208
4209        if user_id:
4210            if isinstance(user_id, list) or isinstance(user_id, tuple):
4211                is_list = True
4212                uids = [str(enf_type('user_id', int, uid)) for uid in user_id]
4213                parameters['user_id'] = ','.join(uids)
4214            else:
4215                parameters['user_id'] = enf_type('user_id', int, user_id)
4216
4217        elif screen_name:
4218            if isinstance(screen_name, list) or isinstance(screen_name, tuple):
4219                is_list = True
4220                parameters['screen_name'] = ','.join(screen_name)
4221            else:
4222                parameters['screen_name'] = screen_name
4223        if is_list:
4224            url = '%s/lists/members/create_all.json' % self.base_url
4225        else:
4226            url = '%s/lists/members/create.json' % self.base_url
4227
4228        resp = self._RequestUrl(url, 'POST', data=parameters)
4229        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4230
4231        return List.NewFromJsonDict(data)
4232
4233    def DestroyListsMember(self,
4234                           list_id=None,
4235                           slug=None,
4236                           owner_screen_name=None,
4237                           owner_id=None,
4238                           user_id=None,
4239                           screen_name=None):
4240        """Destroys the subscription to a list for the authenticated user.
4241
4242        Args:
4243          list_id (int, optional):
4244            The numerical id of the list.
4245          slug (str, optional):
4246            You can identify a list by its slug instead of its numerical id.
4247            If you decide to do so, note that you'll also have to specify
4248            the list owner using the owner_id or owner_screen_name parameters.
4249          owner_screen_name (str, optional):
4250            The screen_name of the user who owns the list being requested by a
4251            slug.
4252          owner_id (int, optional):
4253            The user ID of the user who owns the list being requested by a slug.
4254          user_id (int, optional):
4255            The user_id or a list of user_id's to remove from the list.
4256            If not given, then screen_name is required.
4257          screen_name (str, optional):
4258            The screen_name or a list of Screen_name's to remove from the list.
4259            If not given, then user_id is required.
4260
4261        Returns:
4262          twitter.list.List: A twitter.List instance representing the
4263          removed list.
4264        """
4265        is_list = False
4266        parameters = {}
4267
4268        parameters.update(self._IDList(list_id=list_id,
4269                                       slug=slug,
4270                                       owner_id=owner_id,
4271                                       owner_screen_name=owner_screen_name))
4272
4273        if user_id:
4274            if isinstance(user_id, list) or isinstance(user_id, tuple):
4275                is_list = True
4276                uids = [str(enf_type('user_id', int, uid)) for uid in user_id]
4277                parameters['user_id'] = ','.join(uids)
4278            else:
4279                parameters['user_id'] = int(user_id)
4280        elif screen_name:
4281            if isinstance(screen_name, list) or isinstance(screen_name, tuple):
4282                is_list = True
4283                parameters['screen_name'] = ','.join(screen_name)
4284            else:
4285                parameters['screen_name'] = screen_name
4286
4287        if is_list:
4288            url = '%s/lists/members/destroy_all.json' % self.base_url
4289        else:
4290            url = '%s/lists/members/destroy.json' % self.base_url
4291
4292        resp = self._RequestUrl(url, 'POST', data=parameters)
4293        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4294
4295        return List.NewFromJsonDict(data)
4296
4297    def GetListsPaged(self,
4298                      user_id=None,
4299                      screen_name=None,
4300                      cursor=-1,
4301                      count=20):
4302        """ Fetch the sequence of lists for a user. If no user_id or
4303        screen_name is passed, the data returned will be for the
4304        authenticated user.
4305
4306        Args:
4307          user_id (int, optional):
4308            The ID of the user for whom to return results for.
4309          screen_name (str, optional):
4310            The screen name of the user for whom to return results
4311            for.
4312          count (int, optional):
4313            The amount of results to return per page. No more than 1000 results
4314            will ever be returned in a single page. Defaults to 20.
4315          cursor (int, optional):
4316            The "page" value that Twitter will use to start building the list
4317            sequence from. Use the value of -1 to start at the beginning.
4318            Twitter will return in the result the values for next_cursor and
4319            previous_cursor.
4320
4321        Returns:
4322          next_cursor (int), previous_cursor (int), list of twitter.List
4323          instances, one for each list
4324        """
4325        url = '%s/lists/ownerships.json' % self.base_url
4326        parameters = {}
4327        if user_id is not None:
4328            parameters['user_id'] = enf_type('user_id', int, user_id)
4329        elif screen_name is not None:
4330            parameters['screen_name'] = screen_name
4331
4332        if count is not None:
4333            parameters['count'] = enf_type('count', int, count)
4334
4335        parameters['cursor'] = enf_type('cursor', int, cursor)
4336
4337        resp = self._RequestUrl(url, 'GET', data=parameters)
4338        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4339
4340        next_cursor = data.get('next_cursor', 0)
4341        previous_cursor = data.get('previous_cursor', 0)
4342        lists = [List.NewFromJsonDict(x) for x in data.get('lists', [])]
4343
4344        return next_cursor, previous_cursor, lists
4345
4346    def GetLists(self,
4347                 user_id=None,
4348                 screen_name=None):
4349        """Fetch the sequence of lists for a user. If no user_id or screen_name
4350        is passed, the data returned will be for the authenticated user.
4351
4352        Args:
4353          user_id:
4354            The ID of the user for whom to return results for. [Optional]
4355          screen_name:
4356            The screen name of the user for whom to return results
4357            for. [Optional]
4358          count:
4359            The amount of results to return per page.
4360            No more than 1000 results will ever be returned in a single page.
4361            Defaults to 20. [Optional]
4362          cursor:
4363            The "page" value that Twitter will use to start building the list
4364            sequence from. Use the value of -1 to start at the beginning.
4365            Twitter will return in the result the values for next_cursor and
4366            previous_cursor. [Optional]
4367
4368        Returns:
4369          A sequence of twitter.List instances, one for each list
4370        """
4371        result = []
4372        cursor = -1
4373
4374        while True:
4375            next_cursor, prev_cursor, lists = self.GetListsPaged(
4376                user_id=user_id,
4377                screen_name=screen_name,
4378                cursor=cursor)
4379            result += lists
4380            if next_cursor == 0 or next_cursor == prev_cursor:
4381                break
4382            else:
4383                cursor = next_cursor
4384
4385        return result
4386
4387    def UpdateProfile(self,
4388                      name=None,
4389                      profileURL=None,
4390                      location=None,
4391                      description=None,
4392                      profile_link_color=None,
4393                      include_entities=False,
4394                      skip_status=False):
4395        """Update's the authenticated user's profile data.
4396
4397        Args:
4398          name (str, optional):
4399            Full name associated with the profile.
4400          profileURL (str, optional):
4401            URL associated with the profile.
4402            Will be prepended with "http://" if not present.
4403          location (str, optional):
4404            The city or country describing where the user of the account is located.
4405            The contents are not normalized or geocoded in any way.
4406          description (str, optional):
4407            A description of the user owning the account.
4408          profile_link_color (str, optional):
4409            hex value of profile color theme. formated without '#' or '0x'. Ex:  FF00FF
4410          include_entities (bool, optional):
4411            The entities node will be omitted when set to False.
4412          skip_status (bool, optional):
4413            When set to either True, t or 1 then statuses will not be included
4414            in the returned user objects.
4415
4416        Returns:
4417          A twitter.User instance representing the modified user.
4418        """
4419        url = '%s/account/update_profile.json' % (self.base_url)
4420        data = {
4421            'name': name,
4422            'url': profileURL,
4423            'location': location,
4424            'description': description,
4425            'profile_link_color': profile_link_color,
4426            'include_entities': include_entities,
4427            'skip_status': skip_status,
4428        }
4429
4430        resp = self._RequestUrl(url, 'POST', data=data)
4431        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4432
4433        return User.NewFromJsonDict(data)
4434
4435    def UpdateImage(self,
4436                    image,
4437                    include_entities=False,
4438                    skip_status=False):
4439        """Update a User's profile image. Change may not be immediately
4440        reflected due to image processing on Twitter's side.
4441
4442        Args:
4443            image (str):
4444                Location of local image file to use.
4445            include_entities (bool, optional):
4446                Include the entities node in the return data.
4447            skip_status (bool, optional):
4448                Include the User's last Status in the User entity returned.
4449
4450        Returns:
4451            (twitter.models.User): Updated User object.
4452        """
4453
4454        url = '%s/account/update_profile_image.json' % (self.base_url)
4455        with open(image, 'rb') as image_file:
4456            encoded_image = base64.b64encode(image_file.read())
4457        data = {
4458            'image': encoded_image
4459        }
4460        if include_entities:
4461            data['include_entities'] = 1
4462        if skip_status:
4463            data['skip_status'] = 1
4464
4465        resp = self._RequestUrl(url, 'POST', data=data)
4466
4467        if resp.status_code in [200, 201, 202]:
4468            return True
4469        if resp.status_code == 400:
4470            raise TwitterError({'message': "Image data could not be processed"})
4471        if resp.status_code == 422:
4472            raise TwitterError({'message': "The image could not be resized or is too large."})
4473
4474    def UpdateBanner(self,
4475                     image,
4476                     include_entities=False,
4477                     skip_status=False):
4478        """Updates the authenticated users profile banner.
4479
4480        Args:
4481          image:
4482            Location of image in file system
4483          include_entities:
4484            If True, each tweet will include a node called "entities."
4485            This node offers a variety of metadata about the tweet in a
4486            discrete structure, including: user_mentions, urls, and hashtags.
4487            [Optional]
4488
4489        Returns:
4490          A twitter.List instance representing the list subscribed to
4491        """
4492        url = '%s/account/update_profile_banner.json' % (self.base_url)
4493        with open(image, 'rb') as image_file:
4494            encoded_image = base64.b64encode(image_file.read())
4495        data = {
4496            # When updated for API v1.1 use image, not banner
4497            # https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner
4498            # 'image': encoded_image
4499            'banner': encoded_image
4500        }
4501        if include_entities:
4502            data['include_entities'] = 1
4503        if skip_status:
4504            data['skip_status'] = 1
4505
4506        resp = self._RequestUrl(url, 'POST', data=data)
4507
4508        if resp.status_code in [200, 201, 202]:
4509            return True
4510        if resp.status_code == 400:
4511            raise TwitterError({'message': "Image data could not be processed"})
4512        if resp.status_code == 422:
4513            raise TwitterError({'message': "The image could not be resized or is too large."})
4514
4515        raise TwitterError({'message': "Unkown banner image upload issue"})
4516
4517    def GetStreamSample(self, delimited=False, stall_warnings=True):
4518        """Returns a small sample of public statuses.
4519
4520        Args:
4521          delimited:
4522            Specifies a message length. [Optional]
4523          stall_warnings:
4524            Set to True to have Twitter deliver stall warnings. [Optional]
4525
4526        Returns:
4527          A Twitter stream
4528        """
4529        url = '%s/statuses/sample.json' % self.stream_url
4530        parameters = {
4531            'delimited': bool(delimited),
4532            'stall_warnings': bool(stall_warnings)
4533        }
4534        resp = self._RequestStream(url, 'GET', data=parameters)
4535        for line in resp.iter_lines():
4536            if line:
4537                data = self._ParseAndCheckTwitter(line.decode('utf-8'))
4538                yield data
4539
4540    def GetStreamFilter(self,
4541                        follow=None,
4542                        track=None,
4543                        locations=None,
4544                        languages=None,
4545                        delimited=None,
4546                        stall_warnings=None,
4547                        filter_level=None):
4548        """Returns a filtered view of public statuses.
4549
4550        Args:
4551          follow:
4552            A list of user IDs to track. [Optional]
4553          track:
4554            A list of expressions to track. [Optional]
4555          locations:
4556            A list of Longitude,Latitude pairs (as strings) specifying
4557            bounding boxes for the tweets' origin. [Optional]
4558          delimited:
4559            Specifies a message length. [Optional]
4560          stall_warnings:
4561            Set to True to have Twitter deliver stall warnings. [Optional]
4562          languages:
4563            A list of Languages.
4564            Will only return Tweets that have been detected as being
4565            written in the specified languages. [Optional]
4566          filter_level:
4567            Specifies level of filtering applied to stream.
4568            Set to None, 'low' or 'medium'. [Optional]
4569
4570        Returns:
4571          A twitter stream
4572        """
4573        if all((follow is None, track is None, locations is None)):
4574            raise ValueError({'message': "No filter parameters specified."})
4575        url = '%s/statuses/filter.json' % self.stream_url
4576        data = {}
4577        if follow is not None:
4578            data['follow'] = ','.join(follow)
4579        if track is not None:
4580            data['track'] = ','.join(track)
4581        if locations is not None:
4582            data['locations'] = ','.join(locations)
4583        if delimited is not None:
4584            data['delimited'] = str(delimited)
4585        if stall_warnings is not None:
4586            data['stall_warnings'] = str(stall_warnings)
4587        if languages is not None:
4588            data['language'] = ','.join(languages)
4589        if filter_level is not None:
4590            data['filter_level'] = filter_level
4591
4592        resp = self._RequestStream(url, 'POST', data=data)
4593        for line in resp.iter_lines():
4594            if line:
4595                data = self._ParseAndCheckTwitter(line.decode('utf-8'))
4596                yield data
4597
4598    def GetUserStream(self,
4599                      replies='all',
4600                      withuser='user',
4601                      track=None,
4602                      locations=None,
4603                      delimited=None,
4604                      stall_warnings=None,
4605                      stringify_friend_ids=False,
4606                      filter_level=None,
4607                      session=None,
4608                      include_keepalive=False):
4609        """Returns the data from the user stream.
4610
4611        Args:
4612          replies:
4613            Specifies whether to return additional @replies in the stream.
4614            Defaults to 'all'.
4615          withuser:
4616            Specifies whether to return information for just the authenticating
4617            user, or include messages from accounts the user follows. [Optional]
4618          track:
4619            A list of expressions to track. [Optional]
4620          locations:
4621            A list of Latitude,Longitude pairs (as strings) specifying
4622            bounding boxes for the tweets' origin. [Optional]
4623          delimited:
4624            Specifies a message length. [Optional]
4625          stall_warnings:
4626            Set to True to have Twitter deliver stall warnings. [Optional]
4627          stringify_friend_ids:
4628            Specifies whether to send the friends list preamble as an array of
4629            integers or an array of strings. [Optional]
4630          filter_level:
4631            Specifies level of filtering applied to stream.
4632            Set to None, low or medium. [Optional]
4633
4634        Returns:
4635          A twitter stream
4636        """
4637        url = 'https://userstream.twitter.com/1.1/user.json'
4638        data = {}
4639        if stringify_friend_ids:
4640            data['stringify_friend_ids'] = 'true'
4641        if replies is not None:
4642            data['replies'] = replies
4643        if withuser is not None:
4644            data['with'] = withuser
4645        if track is not None:
4646            data['track'] = ','.join(track)
4647        if locations is not None:
4648            data['locations'] = ','.join(locations)
4649        if delimited is not None:
4650            data['delimited'] = str(delimited)
4651        if stall_warnings is not None:
4652            data['stall_warnings'] = str(stall_warnings)
4653        if filter_level is not None:
4654            data['filter_level'] = filter_level
4655
4656        resp = self._RequestStream(url, 'POST', data=data, session=session)
4657        # The Twitter streaming API sends keep-alive newlines every 30s if there has not been other
4658        # traffic, and specifies that streams should only be reset after three keep-alive ticks.
4659        #
4660        # The original implementation of this API didn't expose keep-alive signals to the user,
4661        # making it difficult to determine whether the connection should be hung up or not.
4662        #
4663        # https://dev.twitter.com/streaming/overview/connecting
4664        for line in resp.iter_lines():
4665            if line:
4666                data = self._ParseAndCheckTwitter(line.decode('utf-8'))
4667                yield data
4668            elif include_keepalive:
4669                yield None
4670
4671    def VerifyCredentials(self, include_entities=None, skip_status=None, include_email=None):
4672        """Returns a twitter.User instance if the authenticating user is valid.
4673
4674        Args:
4675          include_entities:
4676            Specifies whether to return additional @replies in the stream.
4677          skip_status:
4678            When set to either true, t or 1 statuses will not be included in the
4679            returned user object.
4680          include_email:
4681            Use of this parameter requires whitelisting.
4682            When set to true email will be returned in the user objects as a string.
4683            If the user does not have an email address on their account, or if the
4684            email address is un-verified, null will be returned. If your app is
4685            not whitelisted, then the 'email' key will not be present in the json
4686            response.
4687
4688        Returns:
4689          A twitter.User instance representing that user if the
4690          credentials are valid, None otherwise.
4691        """
4692        url = '%s/account/verify_credentials.json' % self.base_url
4693        data = {
4694            'include_entities': enf_type('include_entities', bool, include_entities),
4695            'skip_status': enf_type('skip_status', bool, skip_status),
4696            'include_email': 'true' if enf_type('include_email', bool, include_email) else 'false',
4697        }
4698
4699        resp = self._RequestUrl(url, 'GET', data)
4700        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4701
4702        return User.NewFromJsonDict(data)
4703
4704    def SetCache(self, cache):
4705        """Override the default cache.  Set to None to prevent caching.
4706
4707        Args:
4708          cache:
4709            An instance that supports the same API as the twitter._FileCache
4710        """
4711        if cache == DEFAULT_CACHE:
4712            self._cache = _FileCache()
4713        else:
4714            self._cache = cache
4715
4716    def SetUrllib(self, urllib):
4717        """Override the default urllib implementation.
4718
4719        Args:
4720          urllib:
4721            An instance that supports the same API as the urllib2 module
4722        """
4723        self._urllib = urllib
4724
4725    def SetCacheTimeout(self, cache_timeout):
4726        """Override the default cache timeout.
4727
4728        Args:
4729          cache_timeout:
4730            Time, in seconds, that responses should be reused.
4731        """
4732        self._cache_timeout = cache_timeout
4733
4734    def SetUserAgent(self, user_agent):
4735        """Override the default user agent.
4736
4737        Args:
4738          user_agent:
4739            A string that should be send to the server as the user-agent.
4740        """
4741        self._request_headers['User-Agent'] = user_agent
4742
4743    def SetXTwitterHeaders(self, client, url, version):
4744        """Set the X-Twitter HTTP headers that will be sent to the server.
4745
4746        Args:
4747          client:
4748             The client name as a string.  Will be sent to the server as
4749             the 'X-Twitter-Client' header.
4750          url:
4751             The URL of the meta.xml as a string.  Will be sent to the server
4752             as the 'X-Twitter-Client-URL' header.
4753          version:
4754             The client version as a string.  Will be sent to the server
4755             as the 'X-Twitter-Client-Version' header.
4756        """
4757        self._request_headers['X-Twitter-Client'] = client
4758        self._request_headers['X-Twitter-Client-URL'] = url
4759        self._request_headers['X-Twitter-Client-Version'] = version
4760
4761    def SetSource(self, source):
4762        """Suggest the "from source" value to be displayed on the Twitter web site.
4763
4764        The value of the 'source' parameter must be first recognized by
4765        the Twitter server.
4766
4767        New source values are authorized on a case by case basis by the
4768        Twitter development team.
4769
4770        Args:
4771          source:
4772            The source name as a string.  Will be sent to the server as
4773            the 'source' parameter.
4774        """
4775        self._default_params['source'] = source
4776
4777    def InitializeRateLimit(self):
4778        """ Make a call to the Twitter API to get the rate limit
4779        status for the currently authenticated user or application.
4780
4781        Returns:
4782            None.
4783
4784        """
4785        _sleep = self.sleep_on_rate_limit
4786        if self.sleep_on_rate_limit:
4787            self.sleep_on_rate_limit = False
4788
4789        url = '%s/application/rate_limit_status.json' % self.base_url
4790
4791        resp = self._RequestUrl(url, 'GET')  # No-Cache
4792        data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
4793
4794        self.sleep_on_rate_limit = _sleep
4795        self.rate_limit = RateLimit(**data)
4796
4797    def CheckRateLimit(self, url):
4798        """ Checks a URL to see the rate limit status for that endpoint.
4799
4800        Args:
4801            url (str):
4802                URL to check against the current rate limits.
4803
4804        Returns:
4805            namedtuple: EndpointRateLimit namedtuple.
4806
4807        """
4808        if not self.rate_limit.__dict__.get('resources', None):
4809            self.InitializeRateLimit()
4810
4811        if url:
4812            limit = self.rate_limit.get_limit(url)
4813
4814        return limit
4815
4816    def _BuildUrl(self, url, path_elements=None, extra_params=None):
4817        # Break url into constituent parts
4818        (scheme, netloc, path, params, query, fragment) = urlparse(url)
4819
4820        # Add any additional path elements to the path
4821        if path_elements:
4822            # Filter out the path elements that have a value of None
4823            filtered_elements = [i for i in path_elements if i]
4824            if not path.endswith('/'):
4825                path += '/'
4826            path += '/'.join(filtered_elements)
4827
4828        # Add any additional query parameters to the query string
4829        if extra_params and len(extra_params) > 0:
4830            extra_query = self._EncodeParameters(extra_params)
4831            # Add it to the existing query
4832            if query:
4833                query += '&' + extra_query
4834            else:
4835                query = extra_query
4836
4837        # Return the rebuilt URL
4838        return urlunparse((scheme, netloc, path, params, query, fragment))
4839
4840    def _InitializeRequestHeaders(self, request_headers):
4841        if request_headers:
4842            self._request_headers = request_headers
4843        else:
4844            self._request_headers = {}
4845
4846    def _InitializeUserAgent(self):
4847        user_agent = 'Python-urllib/%s (python-twitter/%s)' % \
4848                     (urllib_version, __version__)
4849        self.SetUserAgent(user_agent)
4850
4851    def _InitializeDefaultParameters(self):
4852        self._default_params = {}
4853
4854    @staticmethod
4855    def _DecompressGzippedResponse(response):
4856        raw_data = response.read()
4857        if response.headers.get('content-encoding', None) == 'gzip':
4858            url_data = gzip.GzipFile(fileobj=io.StringIO(raw_data)).read()
4859        else:
4860            url_data = raw_data
4861        return url_data
4862
4863    @staticmethod
4864    def _EncodeParameters(parameters):
4865        """Return a string in key=value&key=value form.
4866
4867        Values of None are not included in the output string.
4868
4869        Args:
4870          parameters (dict): dictionary of query parameters to be converted into a
4871          string for encoding and sending to Twitter.
4872
4873        Returns:
4874          A URL-encoded string in "key=value&key=value" form
4875        """
4876        if parameters is None:
4877            return None
4878        if not isinstance(parameters, dict):
4879            raise TwitterError("`parameters` must be a dict.")
4880        else:
4881            params = dict()
4882            for k, v in parameters.items():
4883                if v is not None:
4884                    if getattr(v, 'encode', None):
4885                        v = v.encode('utf8')
4886                    params.update({k: v})
4887            return urlencode(params)
4888
4889    def _ParseAndCheckTwitter(self, json_data):
4890        """Try and parse the JSON returned from Twitter and return
4891        an empty dictionary if there is any error.
4892
4893        This is a purely defensive check because during some Twitter
4894        network outages it will return an HTML failwhale page.
4895        """
4896        try:
4897            data = json.loads(json_data)
4898        except ValueError:
4899            if "<title>Twitter / Over capacity</title>" in json_data:
4900                raise TwitterError({'message': "Capacity Error"})
4901            if "<title>Twitter / Error</title>" in json_data:
4902                raise TwitterError({'message': "Technical Error"})
4903            if "Exceeded connection limit for user" in json_data:
4904                raise TwitterError({'message': "Exceeded connection limit for user"})
4905            if "Error 401 Unauthorized" in json_data:
4906                raise TwitterError({'message': "Unauthorized"})
4907            raise TwitterError({'Unknown error': '{0}'.format(json_data)})
4908        self._CheckForTwitterError(data)
4909        return data
4910
4911    @staticmethod
4912    def _CheckForTwitterError(data):
4913        """Raises a TwitterError if twitter returns an error message.
4914
4915        Args:
4916            data (dict):
4917                A python dict created from the Twitter json response
4918
4919        Raises:
4920            (twitter.TwitterError): TwitterError wrapping the twitter error
4921            message if one exists.
4922        """
4923        # Twitter errors are relatively unlikely, so it is faster
4924        # to check first, rather than try and catch the exception
4925        if 'error' in data:
4926            raise TwitterError(data['error'])
4927        if 'errors' in data:
4928            raise TwitterError(data['errors'])
4929
4930    def _RequestChunkedUpload(self, url, headers, data):
4931        try:
4932            return requests.post(
4933                url,
4934                headers=headers,
4935                data=data,
4936                auth=self.__auth,
4937                timeout=self._timeout,
4938                proxies=self.proxies
4939            )
4940        except requests.RequestException as e:
4941            raise TwitterError(str(e))
4942
4943    def _RequestUrl(self, url, verb, data=None, json=None, enforce_auth=True):
4944        """Request a url.
4945
4946        Args:
4947            url:
4948                The web location we want to retrieve.
4949            verb:
4950                Either POST or GET.
4951            data:
4952                A dict of (str, unicode) key/value pairs.
4953
4954        Returns:
4955            A JSON object.
4956        """
4957        if enforce_auth:
4958            if not self.__auth:
4959                raise TwitterError("The twitter.Api instance must be authenticated.")
4960
4961            if url and self.sleep_on_rate_limit:
4962                limit = self.CheckRateLimit(url)
4963
4964                if limit.remaining == 0:
4965                    try:
4966                        stime = max(int(limit.reset - time.time()) + 10, 0)
4967                        logger.debug('Rate limited requesting [%s], sleeping for [%s]', url, stime)
4968                        time.sleep(stime)
4969                    except ValueError:
4970                        pass
4971
4972        if not data:
4973            data = {}
4974
4975        if verb == 'POST':
4976            if data:
4977                if 'media_ids' in data:
4978                    url = self._BuildUrl(url, extra_params={'media_ids': data['media_ids']})
4979                    resp = self._session.post(url, data=data, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
4980                elif 'media' in data:
4981                    resp = self._session.post(url, files=data, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
4982                else:
4983                    resp = self._session.post(url, data=data, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
4984            elif json:
4985                resp = self._session.post(url, json=json, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
4986            else:
4987                resp = 0  # POST request, but without data or json
4988
4989        elif verb == 'GET':
4990            data['tweet_mode'] = self.tweet_mode
4991            url = self._BuildUrl(url, extra_params=data)
4992            resp = self._session.get(url, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
4993
4994        else:
4995            resp = 0  # if not a POST or GET request
4996
4997        if url and self.rate_limit:
4998            limit = resp.headers.get('x-rate-limit-limit', 0)
4999            remaining = resp.headers.get('x-rate-limit-remaining', 0)
5000            reset = resp.headers.get('x-rate-limit-reset', 0)
5001
5002            self.rate_limit.set_limit(url, limit, remaining, reset)
5003
5004        return resp
5005
5006    def _RequestStream(self, url, verb, data=None, session=None):
5007        """Request a stream of data.
5008
5009           Args:
5010             url:
5011               The web location we want to retrieve.
5012             verb:
5013               Either POST or GET.
5014             data:
5015               A dict of (str, unicode) key/value pairs.
5016
5017           Returns:
5018             A twitter stream.
5019        """
5020        session = session or requests.Session()
5021
5022        if verb == 'POST':
5023            try:
5024                return session.post(url, data=data, stream=True,
5025                                    auth=self.__auth,
5026                                    timeout=self._timeout,
5027                                    proxies=self.proxies)
5028            except requests.RequestException as e:
5029                raise TwitterError(str(e))
5030        if verb == 'GET':
5031            url = self._BuildUrl(url, extra_params=data)
5032            try:
5033                return session.get(url, stream=True, auth=self.__auth,
5034                                   timeout=self._timeout, proxies=self.proxies)
5035            except requests.RequestException as e:
5036                raise TwitterError(str(e))
5037        return 0  # if not a POST or GET request
5038