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