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