├── examples ├── __init__.py ├── banner.png ├── video.mp4 ├── animated.gif ├── streaming.py └── oauth.py ├── tests ├── __init__.py ├── test_utils.py ├── test_resultset.py ├── test_auth.py ├── config.py ├── test_cursors.py ├── test_streaming.py └── test_rate_limit.py ├── MANIFEST.in ├── requirements.txt ├── .coveragerc ├── setup.cfg ├── tweepy ├── utils.py ├── mixins.py ├── __init__.py ├── errors.py └── parsers.py ├── ASCII_LOGO.txt ├── tox.ini ├── docs ├── locale │ ├── ko │ │ └── LC_MESSAGES │ │ │ ├── TRANSLATORS_ko_KR.txt │ │ │ ├── parameters.po │ │ │ ├── install.po │ │ │ ├── index.po │ │ │ ├── code_snippet.po │ │ │ ├── running_tests.po │ │ │ ├── getting_started.po │ │ │ └── cursor_tutorial.po │ └── pl │ │ └── LC_MESSAGES │ │ ├── parameters.po │ │ ├── index.po │ │ ├── install.po │ │ ├── code_snippet.po │ │ ├── running_tests.po │ │ ├── getting_started.po │ │ └── cursor_tutorial.po ├── install.rst ├── index.rst ├── Makefile ├── make.bat ├── running_tests.rst ├── getting_started.rst ├── code_snippet.rst ├── cursor_tutorial.rst ├── parameters.rst ├── streaming_how_to.rst ├── auth_tutorial.rst └── extended_tweets.rst ├── .github └── workflows │ ├── deploy.yml │ └── test.yml ├── CONTRIBUTORS ├── bindings_url_parser.py ├── LICENSE ├── README.md ├── .gitignore ├── cassettes ├── testfollowersids.yaml ├── testfriendsids.yaml ├── testfavorites.json ├── testretweetsofme.json ├── testmentionstimeline.json ├── testlistdirectmessages.json ├── testlistsmemberships.json ├── testblocks.json ├── testblocksids.json ├── testlistssubscriptions.json ├── testgetoembed.json ├── testshowfriendship.json ├── testcursorcursoritems.yaml ├── testcursorcursorpages.yaml ├── testretweeters.json ├── testfollowers.yaml ├── testlistsall.json ├── testgetlist.json ├── testlistsownerships.json ├── testgetstatus.json ├── testshowlistmember.json ├── testshowlistsubscriber.json ├── testcreatedestroyfriendship.yaml └── testgetuser.yaml └── setup.py /examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --index-url https://pypi.python.org/simple/ 2 | 3 | -e . 4 | -------------------------------------------------------------------------------- /examples/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitnr/tweepy/master/examples/banner.png -------------------------------------------------------------------------------- /examples/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitnr/tweepy/master/examples/video.mp4 -------------------------------------------------------------------------------- /examples/animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitnr/tweepy/master/examples/animated.gif -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = tweepy 3 | 4 | [report] 5 | omit = 6 | */python?.?/* 7 | */site-packages/* 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [nosetests] 5 | tests = tests.test_api,tests.test_cursors,tests.test_resultset,tests.test_utils 6 | verbosity = 2 7 | with-coverage = 1 8 | -------------------------------------------------------------------------------- /tweepy/utils.py: -------------------------------------------------------------------------------- 1 | # Tweepy 2 | # Copyright 2010-2021 Joshua Roesslein 3 | # See LICENSE for details. 4 | 5 | 6 | def list_to_csv(item_list): 7 | if item_list: 8 | return ','.join(map(str, item_list)) 9 | -------------------------------------------------------------------------------- /ASCII_LOGO.txt: -------------------------------------------------------------------------------- 1 | ______ 2 | /_ __/_ __ ___ ___ ____ __ __ 3 | / / | | /| / // _ \ / _ \ / __ \ / / / / 4 | / / | |/ |/ // __// __// /_/ // /_/ / 5 | /_/ |__/|__/ \___/ \___// .___/ \__, / 6 | /_/ /____/ 7 | 8 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py36, py37, py38, py39 8 | 9 | [testenv] 10 | commands = python setup.py nosetests 11 | extras = test 12 | -------------------------------------------------------------------------------- /tweepy/mixins.py: -------------------------------------------------------------------------------- 1 | # Tweepy 2 | # Copyright 2009-2021 Joshua Roesslein 3 | # See LICENSE for details. 4 | 5 | 6 | class EqualityComparableID: 7 | __slots__ = () 8 | 9 | def __eq__(self, other): 10 | if isinstance(other, self.__class__): 11 | return self.id == other.id 12 | 13 | return NotImplemented 14 | 15 | 16 | class HashableID(EqualityComparableID): 17 | __slots__ = () 18 | 19 | def __hash__(self): 20 | return self.id 21 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/TRANSLATORS_ko_KR.txt: -------------------------------------------------------------------------------- 1 | # TRANSLATORS of Tweepy's documentation. 2 | # Language: Korean (Korea, Republic of) 3 | 4 | # Please write down your name below, with the format: 5 | # (<@GitHub Username>) 6 | # 7 | # GitHub Username in place of Name is acceptable. 8 | 9 | 악동분홍토끼 (@pinkrabbit412) 10 | https://github.com/pinkrabbit412 11 | 12 | thdkrhk99 (@thdkrhk99) 13 | https://github.com/thdkrhk99 14 | 15 | ifeve (@ifeve) 16 | https://github.com/ifeve -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The easiest way to install the latest version from PyPI is by using pip:: 5 | 6 | pip install tweepy 7 | 8 | You can also use Git to clone the repository from GitHub to install the latest 9 | development version:: 10 | 11 | git clone https://github.com/tweepy/tweepy.git 12 | cd tweepy 13 | pip install . 14 | 15 | Alternatively, install directly from the GitHub repository:: 16 | 17 | pip install git+https://github.com/tweepy/tweepy.git 18 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import unittest 4 | 5 | from tweepy.utils import * 6 | 7 | 8 | def mock_tweet(): 9 | """Generate some random tweet text.""" 10 | count = random.randint(70, 140) 11 | return ''.join([random.choice(string.ascii_letters) for _ in range(count)]) 12 | 13 | 14 | class TweepyUtilsTests(unittest.TestCase): 15 | 16 | def testlist_to_csv(self): 17 | self.assertEqual("1,2,3", list_to_csv([1,2,3])) 18 | self.assertEqual("bird,tweet,nest,egg", 19 | list_to_csv(["bird", "tweet", "nest", "egg"])) 20 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/parameters.po: -------------------------------------------------------------------------------- 1 | # Parameters 2 | # Copyright (C) 2009-2020, Joshua Roesslein 3 | # This file is distributed under the same license as the Tweepy package. 4 | # 악동분홍토끼 , 2019. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Tweepy-ko\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-12-28 09:41+0900\n" 12 | "PO-Revision-Date: 2019-12-28 10:59+0900\n" 13 | "Last-Translator: 악동분홍토끼 \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.7.0\n" 19 | 20 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. tweepy documentation master file, created by 2 | sphinx-quickstart on Sun Dec 6 11:13:52 2009. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Tweepy Documentation 7 | ==================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | install.rst 15 | getting_started.rst 16 | auth_tutorial.rst 17 | code_snippet.rst 18 | cursor_tutorial.rst 19 | extended_tweets.rst 20 | streaming_how_to.rst 21 | api.rst 22 | running_tests.rst 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`genindex` 28 | * :ref:`search` 29 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/locale/pl/LC_MESSAGES/parameters.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2020, Joshua Roesslein 2 | # This file is distributed under the same license as the Tweepy package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Tweepy-pl\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2020-01-14 14:12+0100\n" 8 | "PO-Revision-Date: 2020-01-19 09:28+0100\n" 9 | "Last-Translator: krzysztofturtle \n" 10 | "Language: pl\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " 13 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Generated-By: Babel 2.8.0\n" 18 | "X-Generator: POEditor.com\n" -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.x' 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install twine wheel 21 | - name: Build and publish 22 | env: 23 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 24 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 25 | run: | 26 | python setup.py sdist bdist_wheel 27 | twine upload dist/* 28 | 29 | -------------------------------------------------------------------------------- /tests/test_resultset.py: -------------------------------------------------------------------------------- 1 | from .config import TweepyTestCase 2 | 3 | from tweepy.models import ResultSet 4 | 5 | class NoIdItem: 6 | pass 7 | 8 | class IdItem: 9 | def __init__(self, id): 10 | self.id = id 11 | 12 | ids_fixture = [1, 10, 8, 50, 2, 100, 5] 13 | 14 | class TweepyResultSetTests(TweepyTestCase): 15 | def setUp(self): 16 | self.results = ResultSet() 17 | for i in ids_fixture: 18 | self.results.append(IdItem(i)) 19 | self.results.append(NoIdItem()) 20 | 21 | def testids(self): 22 | ids = self.results.ids() 23 | self.assertListEqual(ids, ids_fixture) 24 | 25 | def testmaxid(self): 26 | self.assertEqual(self.results.max_id, 0) 27 | 28 | def testsinceid(self): 29 | self.assertEqual(self.results.since_id, 100) 30 | 31 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Thank you to all who have contributed to this project! 2 | If you contributed and not listed below please let me know. 3 | 4 | Aaron Hill 5 | Aaron Swartz 6 | Adam Miskiewicz 7 | AlanBell 8 | Arthur Debert 9 | Bas Westerbaan 10 | Can Duruk 11 | Chris Kelly 12 | Clay McClure 13 | Ferenc Szalai 14 | Gergely Imreh 15 | gilles 16 | Guan Yang 17 | Harmon (@harmon758) 18 | Ivo Wetzel 19 | Jan Schaumann (@jschauma) 20 | Jared Stefanowicz 21 | James Rowe 22 | Jeff Hull (@jsh2134) 23 | Jenny Loomis 24 | Johannes Faigle 25 | Kohei YOSHIDA 26 | Kumar Appaiah 27 | Mark Smith (@judy2k) 28 | Michael (Doc) Norton 29 | Mike (mikeandmore) 30 | Pascal Jürgens 31 | Patrick A. Levell (@palevell) 32 | Robin Houston 33 | Sam Kaufman 34 | Samuel (@obskyr) 35 | Sayed Mohammad Hossein Torabi (@blcksrx) 36 | Steven Skoczen (@skoczen) 37 | Stuart Powers 38 | Thomas Bohmbach, Jr 39 | Wayne Moore 40 | Will McCutchen 41 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/install.po: -------------------------------------------------------------------------------- 1 | # Installation 2 | # Copyright (C) 2009-2020, Joshua Roesslein 3 | # This file is distributed under the same license as the Tweepy package. 4 | # 악동분홍토끼 , 2019. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Tweepy-ko\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-12-28 09:41+0900\n" 12 | "PO-Revision-Date: 2019-12-28 11:01+0900\n" 13 | "Last-Translator: 악동분홍토끼 \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.7.0\n" 19 | 20 | #: ../../install.rst:2 21 | msgid "Installation" 22 | msgstr "설치하기" 23 | 24 | #: ../../install.rst:4 25 | msgid "Install from PyPI::" 26 | msgstr "PyPI로부터 설치하기::" 27 | 28 | #: ../../install.rst:8 29 | msgid "Install from source::" 30 | msgstr "원본 소스코드로부터 설치하기::" 31 | 32 | -------------------------------------------------------------------------------- /tweepy/__init__.py: -------------------------------------------------------------------------------- 1 | # Tweepy 2 | # Copyright 2009-2021 Joshua Roesslein 3 | # See LICENSE for details. 4 | 5 | """ 6 | Tweepy Twitter API library 7 | """ 8 | __version__ = '3.10.0' 9 | __author__ = 'Joshua Roesslein' 10 | __license__ = 'MIT' 11 | 12 | from tweepy.api import API 13 | from tweepy.auth import AppAuthHandler, OAuthHandler 14 | from tweepy.cache import Cache, FileCache, MemoryCache 15 | from tweepy.cursor import Cursor 16 | from tweepy.errors import ( 17 | BadRequest, Forbidden, HTTPException, NotFound, TooManyRequests, 18 | TweepyException, TwitterServerError, Unauthorized 19 | ) 20 | from tweepy.models import ( 21 | DirectMessage, Friendship, ModelFactory, SavedSearch, SearchResults, 22 | Status, User 23 | ) 24 | from tweepy.streaming import Stream 25 | 26 | # Global, unauthenticated instance of API 27 | api = API() 28 | 29 | def debug(enable=True, level=1): 30 | from http.client import HTTPConnection 31 | HTTPConnection.debuglevel = level 32 | -------------------------------------------------------------------------------- /bindings_url_parser.py: -------------------------------------------------------------------------------- 1 | """ script to parse the url of bindings and find if the page exists or not """ 2 | 3 | import os 4 | import pprint 5 | import re 6 | import requests 7 | 8 | __author__ = 'jordiriera' 9 | 10 | url_root = 'https://dev.twitter.com' 11 | reference_line = re.compile(f':reference: ({url_root}.*) "') 12 | 13 | 14 | def parse(filename): 15 | dead_links = [] 16 | with open(filename, 'r') as file_: 17 | for line in file_.readlines(): 18 | res = reference_line.search(line) 19 | if res: 20 | if not exists(res.group(1)): 21 | dead_links.append(res.group(1)) 22 | 23 | return dead_links 24 | 25 | 26 | def exists(path): 27 | r = requests.head(path) 28 | return r.status_code == requests.codes.ok 29 | 30 | 31 | if __name__ == '__main__': 32 | root = os.path.dirname(os.path.abspath(__file__)) 33 | filename = os.path.join(root, 'tweepy', 'api.py') 34 | pprint.pprint(parse(filename)) 35 | -------------------------------------------------------------------------------- /docs/running_tests.rst: -------------------------------------------------------------------------------- 1 | .. _running_tests: 2 | 3 | ************* 4 | Running Tests 5 | ************* 6 | 7 | These steps outline how to run tests for Tweepy: 8 | 9 | 1. Download Tweepy's source code to a directory. 10 | 11 | 2. Install from the downloaded source with the ``test`` extra, e.g. 12 | ``pip install .[test]``. Optionally install the ``dev`` extra as well, for 13 | ``tox`` and ``coverage``, e.g. ``pip install .[dev,test]``. 14 | 15 | 3. Run ``python setup.py nosetests`` or simply ``nosetests`` in the source 16 | directory. With the ``dev`` extra, coverage will be shown, and ``tox`` can 17 | also be run to test different Python versions. 18 | 19 | To record new cassettes, the following environment variables can be used: 20 | 21 | ``TWITTER_USERNAME`` 22 | ``CONSUMER_KEY`` 23 | ``CONSUMER_SECRET`` 24 | ``ACCESS_KEY`` 25 | ``ACCESS_SECRET`` 26 | ``USE_REPLAY`` 27 | 28 | Simply set ``USE_REPLAY`` to ``False`` and provide the app and account 29 | credentials and username. 30 | -------------------------------------------------------------------------------- /examples/streaming.py: -------------------------------------------------------------------------------- 1 | from tweepy import OAuthHandler, Stream, StreamListener 2 | 3 | # Go to http://apps.twitter.com and create an app. 4 | # The consumer key and secret will be generated for you after 5 | consumer_key="" 6 | consumer_secret="" 7 | 8 | # After the step above, you will be redirected to your app's page. 9 | # Create an access token under the the "Your access token" section 10 | access_token="" 11 | access_token_secret="" 12 | 13 | class StdOutListener(StreamListener): 14 | """ A listener handles tweets that are received from the stream. 15 | This is a basic listener that just prints received tweets to stdout. 16 | 17 | """ 18 | def on_data(self, data): 19 | print(data) 20 | return True 21 | 22 | def on_error(self, status): 23 | print(status) 24 | 25 | if __name__ == '__main__': 26 | l = StdOutListener() 27 | auth = OAuthHandler(consumer_key, consumer_secret) 28 | auth.set_access_token(access_token, access_token_secret) 29 | 30 | stream = Stream(auth, l) 31 | stream.filter(track=['basketball']) 32 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/index.po: -------------------------------------------------------------------------------- 1 | # Index 2 | # Copyright (C) 2009-2020, Joshua Roesslein 3 | # This file is distributed under the same license as the Tweepy package. 4 | # 악동분홍토끼 , 2019. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Tweepy-ko\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-12-28 09:41+0900\n" 12 | "PO-Revision-Date: 2019-12-28 11:03+0900\n" 13 | "Last-Translator: 악동분홍토끼 \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.7.0\n" 19 | 20 | #: ../../index.rst:7 21 | msgid "Tweepy Documentation" 22 | msgstr "Tweepy 기술 문서" 23 | 24 | #: ../../index.rst:9 25 | msgid "Contents:" 26 | msgstr "내용:" 27 | 28 | #: ../../index.rst:24 29 | msgid "Indices and tables" 30 | msgstr "인덱스와 Tables" 31 | 32 | #: ../../index.rst:26 33 | msgid ":ref:`genindex`" 34 | msgstr ":ref:`genindex`" 35 | 36 | #: ../../index.rst:27 37 | msgid ":ref:`search`" 38 | msgstr ":ref:`search`" 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2009-2021 Joshua Roesslein 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/oauth.py: -------------------------------------------------------------------------------- 1 | import tweepy 2 | 3 | # == OAuth Authentication == 4 | # 5 | # This mode of authentication is the new preferred way 6 | # of authenticating with Twitter. 7 | 8 | # The consumer keys can be found on your application's Details 9 | # page located at https://dev.twitter.com/apps (under "OAuth settings") 10 | consumer_key="" 11 | consumer_secret="" 12 | 13 | # The access tokens can be found on your applications's Details 14 | # page located at https://dev.twitter.com/apps (located 15 | # under "Your access token") 16 | access_token="" 17 | access_token_secret="" 18 | 19 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret) 20 | auth.set_access_token(access_token, access_token_secret) 21 | 22 | api = tweepy.API(auth) 23 | 24 | # If the authentication was successful, you should 25 | # see the name of the account print out 26 | print(api.me().name) 27 | 28 | # If the application settings are set for "Read and Write" then 29 | # this line should tweet out the message to your account's 30 | # timeline. The "Read and Write" setting is on https://dev.twitter.com/apps 31 | api.update_status(status='Updating using OAuth authentication via Tweepy!') 32 | -------------------------------------------------------------------------------- /tests/test_auth.py: -------------------------------------------------------------------------------- 1 | import random 2 | import unittest 3 | 4 | from .config import * 5 | from tweepy import API, OAuthHandler 6 | 7 | 8 | class TweepyAuthTests(unittest.TestCase): 9 | 10 | def testoauth(self): 11 | auth = OAuthHandler(oauth_consumer_key, oauth_consumer_secret) 12 | 13 | # test getting access token 14 | auth_url = auth.get_authorization_url() 15 | print('Please authorize: ' + auth_url) 16 | verifier = input('PIN: ').strip() 17 | self.assertTrue(len(verifier) > 0) 18 | access_token = auth.get_access_token(verifier) 19 | self.assertTrue(access_token is not None) 20 | 21 | # build api object test using oauth 22 | api = API(auth) 23 | s = api.update_status(f'test {random.randint(0, 1000)}') 24 | api.destroy_status(s.id) 25 | 26 | def testaccesstype(self): 27 | auth = OAuthHandler(oauth_consumer_key, oauth_consumer_secret) 28 | auth_url = auth.get_authorization_url(access_type='read') 29 | print('Please open: ' + auth_url) 30 | answer = input('Did Twitter only request read permissions? (y/n) ') 31 | self.assertEqual('y', answer.lower()) 32 | -------------------------------------------------------------------------------- /docs/locale/pl/LC_MESSAGES/index.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2020, Joshua Roesslein 2 | # This file is distributed under the same license as the Tweepy package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Tweepy-pl\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2020-01-14 14:12+0100\n" 8 | "PO-Revision-Date: 2020-01-19 09:28+0100\n" 9 | "Last-Translator: krzysztofturtle \n" 10 | "Language: pl\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " 13 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Generated-By: Babel 2.8.0\n" 18 | "X-Generator: POEditor.com\n" 19 | 20 | #: ../../index.rst:7 21 | msgid "Tweepy Documentation" 22 | msgstr "Dokumentacja Tweepy" 23 | 24 | #: ../../index.rst:9 25 | msgid "Contents:" 26 | msgstr "Zawartość:" 27 | 28 | #: ../../index.rst:25 29 | msgid "Indices and tables" 30 | msgstr "Indeksy i tabele" 31 | 32 | #: ../../index.rst:27 33 | msgid ":ref:`genindex`" 34 | msgstr ":ref:`genindex`" 35 | 36 | #: ../../index.rst:28 37 | msgid ":ref:`search`" 38 | msgstr ":ref:`search`" 39 | 40 | -------------------------------------------------------------------------------- /tests/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | import vcr 5 | 6 | from tweepy.api import API 7 | from tweepy.auth import OAuthHandler 8 | 9 | 10 | username = os.environ.get('TWITTER_USERNAME', 'TweepyDev') 11 | oauth_consumer_key = os.environ.get('CONSUMER_KEY', '') 12 | oauth_consumer_secret = os.environ.get('CONSUMER_SECRET', '') 13 | oauth_token = os.environ.get('ACCESS_KEY', '') 14 | oauth_token_secret = os.environ.get('ACCESS_SECRET', '') 15 | use_replay = os.environ.get('USE_REPLAY', True) 16 | 17 | 18 | tape = vcr.VCR( 19 | cassette_library_dir='cassettes', 20 | filter_headers=['Authorization'], 21 | serializer='json', 22 | # Either use existing cassettes, or never use recordings: 23 | record_mode='none' if use_replay else 'all', 24 | ) 25 | 26 | 27 | class TweepyTestCase(unittest.TestCase): 28 | def setUp(self): 29 | self.auth = create_auth() 30 | self.api = API(self.auth) 31 | self.api.retry_count = 2 32 | self.api.retry_delay = 0 if use_replay else 5 33 | 34 | 35 | def create_auth(): 36 | auth = OAuthHandler(oauth_consumer_key, oauth_consumer_secret) 37 | auth.set_access_token(oauth_token, oauth_token_secret) 38 | return auth 39 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: [3.6, 3.7, 3.8, 3.9] 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup Python ${{ matrix.python-version }} 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install .[dev,test] 22 | - name: Run tests 23 | run: | 24 | python setup.py nosetests 25 | - name: Send coverage to Coveralls 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | COVERALLS_FLAG_NAME: python-${{ matrix.python-version }} 29 | COVERALLS_PARALLEL: true 30 | COVERALLS_SERVICE_NAME: github 31 | if: ${{ github.event_name == 'push' }} 32 | run: | 33 | coveralls 34 | coveralls: 35 | needs: test 36 | if: ${{ github.event_name == 'push' }} 37 | runs-on: ubuntu-latest 38 | container: python:3-slim 39 | steps: 40 | - name: Finish sending coverage to Coveralls 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | run: | 44 | pip install coveralls 45 | coveralls --finish 46 | -------------------------------------------------------------------------------- /docs/locale/pl/LC_MESSAGES/install.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2020, Joshua Roesslein 2 | # This file is distributed under the same license as the Tweepy package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Tweepy-pl\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2020-01-14 14:12+0100\n" 8 | "PO-Revision-Date: 2020-01-19 09:28+0100\n" 9 | "Last-Translator: krzysztofturtle \n" 10 | "Language: pl\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " 13 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Generated-By: Babel 2.8.0\n" 18 | "X-Generator: POEditor.com\n" 19 | 20 | #: ../../install.rst:2 21 | msgid "Installation" 22 | msgstr "Instalacja" 23 | 24 | #: ../../install.rst:4 25 | msgid "The easiest way to install the latest version from PyPI is by using pip::" 26 | msgstr "Użycie pip jest najprostszym sposobem na instalację najnowszej wersji z PyPI::" 27 | 28 | #: ../../install.rst:8 29 | msgid "" 30 | "You can also use Git to clone the repository from GitHub to install the " 31 | "latest development version::" 32 | msgstr "Możesz także użyć Git do zklonowania repozytorium z GithHub i zainstalować najnowszą wersję deweloperską::" 33 | 34 | #: ../../install.rst:15 35 | msgid "Alternatively, install directly from the GitHub repository::" 36 | msgstr "Możesz także zainstalować prosto z repozytorium GitHub::" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tweepy: Twitter for Python! 2 | ====== 3 | 4 | [![Test Status](https://github.com/tweepy/tweepy/workflows/Test/badge.svg)](https://github.com/tweepy/tweepy/actions?query=workflow%3ATest) 5 | [![Documentation Status](https://readthedocs.org/projects/tweepy/badge/?version=latest)](https://tweepy.readthedocs.io/en/latest/) 6 | [![PyPI Version](https://img.shields.io/pypi/v/tweepy?label=PyPI)](https://pypi.org/project/tweepy/) 7 | [![Python Versions](https://img.shields.io/pypi/pyversions/tweepy?label=Python)](https://pypi.org/project/tweepy/) 8 | [![Coverage Status](https://img.shields.io/coveralls/tweepy/tweepy/master.svg?style=flat)](https://coveralls.io/github/tweepy/tweepy?branch=master) 9 | [![Discord Server](https://discord.com/api/guilds/432685901596852224/embed.png)](https://discord.gg/bJvqnhg) 10 | 11 | Installation 12 | ------------ 13 | The easiest way to install the latest version from PyPI is by using pip: 14 | 15 | pip install tweepy 16 | 17 | You can also use Git to clone the repository from GitHub to install the latest 18 | development version: 19 | 20 | git clone https://github.com/tweepy/tweepy.git 21 | cd tweepy 22 | pip install . 23 | 24 | Alternatively, install directly from the GitHub repository: 25 | 26 | pip install git+https://github.com/tweepy/tweepy.git 27 | 28 | Python 3.6 - 3.9 are supported. 29 | 30 | Links 31 | ----- 32 | 33 | - [Documentation](https://tweepy.readthedocs.io/en/latest/) 34 | - [Official Discord Server](https://discord.gg/bJvqnhg) 35 | - [Twitter API Documentation](https://developer.twitter.com/en/docs/twitter-api) 36 | 37 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | .. _getting_started: 2 | 3 | 4 | *************** 5 | Getting started 6 | *************** 7 | 8 | Introduction 9 | ============ 10 | 11 | If you are new to Tweepy, this is the place to begin. The goal of this 12 | tutorial is to get you set-up and rolling with Tweepy. We won't go 13 | into too much detail here, just some important basics. 14 | 15 | Hello Tweepy 16 | ============ 17 | 18 | .. code-block :: python 19 | 20 | import tweepy 21 | 22 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret) 23 | auth.set_access_token(access_token, access_token_secret) 24 | 25 | api = tweepy.API(auth) 26 | 27 | public_tweets = api.home_timeline() 28 | for tweet in public_tweets: 29 | print(tweet.text) 30 | 31 | This example will download your home timeline tweets and print each 32 | one of their texts to the console. Twitter requires all requests to 33 | use OAuth for authentication. 34 | The :ref:`auth_tutorial` goes into more details about authentication. 35 | 36 | API 37 | === 38 | 39 | The API class provides access to the entire twitter RESTful API 40 | methods. Each method can accept various parameters and return 41 | responses. For more information about these methods please refer to 42 | :ref:`API Reference `. 43 | 44 | Models 45 | ====== 46 | 47 | When we invoke an API method most of the time returned back to us will 48 | be a Tweepy model class instance. This will contain the data returned 49 | from Twitter which we can then use inside our application. For example 50 | the following code returns to us a User model:: 51 | 52 | # Get the User object for twitter... 53 | user = api.get_user('twitter') 54 | 55 | Models contain the data and some helper methods which we can then 56 | use:: 57 | 58 | print(user.screen_name) 59 | print(user.followers_count) 60 | for friend in user.friends(): 61 | print(friend.screen_name) 62 | 63 | For more information about models please see ModelsReference. 64 | 65 | -------------------------------------------------------------------------------- /tests/test_cursors.py: -------------------------------------------------------------------------------- 1 | from .config import tape, TweepyTestCase, username 2 | from tweepy import Cursor 3 | 4 | 5 | class TweepyCursorTests(TweepyTestCase): 6 | @tape.use_cassette('testidcursoritems.json') 7 | def testidcursoritems(self): 8 | items = list(Cursor(self.api.user_timeline).items(2)) 9 | self.assertEqual(len(items), 2) 10 | 11 | @tape.use_cassette('testidcursorpages.json') 12 | def testidcursorpages(self): 13 | pages = list(Cursor(self.api.user_timeline, count=1).pages(2)) 14 | self.assertEqual(len(pages), 2) 15 | 16 | @tape.use_cassette('testcursorcursoritems.yaml', serializer='yaml') 17 | def testcursorcursoritems(self): 18 | items = list(Cursor(self.api.friends_ids).items(2)) 19 | self.assertEqual(len(items), 2) 20 | 21 | items = list(Cursor(self.api.followers_ids, screen_name=username).items(1)) 22 | self.assertEqual(len(items), 1) 23 | 24 | @tape.use_cassette('testcursorcursorpages.yaml', serializer='yaml') 25 | def testcursorcursorpages(self): 26 | pages = list(Cursor(self.api.friends_ids).pages(1)) 27 | self.assertTrue(len(pages) == 1) 28 | 29 | pages = list(Cursor(self.api.followers_ids, screen_name=username).pages(1)) 30 | self.assertTrue(len(pages) == 1) 31 | 32 | @tape.use_cassette('testcursorsetstartcursor.json') 33 | def testcursorsetstartcursor(self): 34 | c = Cursor(self.api.friends_ids, cursor=123456) 35 | self.assertEqual(c.iterator.next_cursor, 123456) 36 | self.assertFalse('cursor' in c.iterator.kwargs) 37 | 38 | @tape.use_cassette('testcursornext.json') 39 | def testcursornext(self): 40 | """ 41 | Test next(cursor) behavior, id being passed correctly. 42 | Regression test for issue #518 43 | """ 44 | cursor = Cursor(self.api.user_timeline, id='Twitter').items(5) 45 | status = next(cursor) 46 | 47 | self.assertEqual(status.user.screen_name, 'Twitter') 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env.sh 2 | .idea 3 | .pdbrc 4 | tweepy.egg-info 5 | 6 | # Created by https://www.gitignore.io/api/vim,python 7 | 8 | ### Python ### 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | 111 | ### Vim ### 112 | # swap 113 | [._]*.s[a-v][a-z] 114 | [._]*.sw[a-p] 115 | [._]s[a-v][a-z] 116 | [._]sw[a-p] 117 | # session 118 | Session.vim 119 | # temporary 120 | .netrwhist 121 | *~ 122 | # auto-generated tag files 123 | tags 124 | 125 | # End of https://www.gitignore.io/api/vim,python 126 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/code_snippet.po: -------------------------------------------------------------------------------- 1 | # Code Snippets 2 | # Copyright (C) 2009-2020, Joshua Roesslein 3 | # This file is distributed under the same license as the Tweepy package. 4 | # 악동분홍토끼 , 2019. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Tweepy-ko\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-12-28 09:41+0900\n" 12 | "PO-Revision-Date: 2019-12-28 11:31+0900\n" 13 | "Last-Translator: 악동분홍토끼 \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.7.0\n" 19 | 20 | #: ../../code_snippet.rst:6 21 | msgid "Code Snippets" 22 | msgstr "코드 조각" 23 | 24 | #: ../../code_snippet.rst:9 25 | msgid "Introduction" 26 | msgstr "들어가며" 27 | 28 | #: ../../code_snippet.rst:11 29 | msgid "" 30 | "Here are some code snippets to help you out with using Tweepy. Feel free " 31 | "to contribute your own snippets or improve the ones here!" 32 | msgstr "" 33 | "여기에는 당신이 Tweepy를 사용하는 데에 도움을 줄 몇 개의 코드 조각들이 있습니다. " 34 | "직접 만든 코드를 기여하거나, 여기 있는 코드를 개선해주세요!" 35 | 36 | #: ../../code_snippet.rst:15 37 | msgid "OAuth" 38 | msgstr "OAuth" 39 | 40 | #: ../../code_snippet.rst:31 41 | msgid "Pagination" 42 | msgstr "페이지 나누기" 43 | 44 | #: ../../code_snippet.rst:46 45 | msgid "FollowAll" 46 | msgstr "모든 팔로워 팔로우하기" 47 | 48 | #: ../../code_snippet.rst:48 49 | msgid "This snippet will follow every follower of the authenticated user." 50 | msgstr "아래 코드는 현재 인증된 사용자의 모든 팔로워를 팔로우 하도록 합니다." 51 | 52 | #: ../../code_snippet.rst:56 53 | msgid "Handling the rate limit using cursors" 54 | msgstr "커서 이용 한도의 처리" 55 | 56 | #: ../../code_snippet.rst:58 57 | msgid "" 58 | "Since cursors raise ``RateLimitError``\\ s in their ``next()`` method, " 59 | "handling them can be done by wrapping the cursor in an iterator." 60 | msgstr "" 61 | "커서는 커서 안의 ``next()`` 메소드 안에서 ``RateLimitError`` 를 일으킵니다. " 62 | "이 오류는 커서를 반복자로 감쌈으로써 처리할 수 있습니다." 63 | 64 | #: ../../code_snippet.rst:61 65 | msgid "" 66 | "Running this snippet will print all users you follow that themselves " 67 | "follow less than 300 people total - to exclude obvious spambots, for " 68 | "example - and will wait for 15 minutes each time it hits the rate limit." 69 | msgstr "" 70 | "이 코드를 실행하면 당신이 팔로우한 모든 유저 중 300명 이하를 팔로우하는 유저들을 출력하고, " 71 | "속도 제한에 도달할 때마다 15분간 기다릴 것입니다. 이 코드는 명백한 스팸봇을 제외하기 위한 예제입니다." 72 | -------------------------------------------------------------------------------- /docs/code_snippet.rst: -------------------------------------------------------------------------------- 1 | .. _code_snippet: 2 | 3 | 4 | ************* 5 | Code Snippets 6 | ************* 7 | 8 | Introduction 9 | ============ 10 | 11 | Here are some code snippets to help you out with using Tweepy. Feel 12 | free to contribute your own snippets or improve the ones here! 13 | 14 | OAuth 15 | ===== 16 | 17 | .. code-block :: python 18 | 19 | auth = tweepy.OAuthHandler("consumer_key", "consumer_secret") 20 | 21 | # Redirect user to Twitter to authorize 22 | redirect_user(auth.get_authorization_url()) 23 | 24 | # Get access token 25 | auth.get_access_token("verifier_value") 26 | 27 | # Construct the API instance 28 | api = tweepy.API(auth) 29 | 30 | Pagination 31 | ========== 32 | 33 | .. code-block :: python 34 | 35 | # Iterate through all of the authenticated user's friends 36 | for friend in tweepy.Cursor(api.friends).items(): 37 | # Process the friend here 38 | process_friend(friend) 39 | 40 | # Iterate through the first 200 statuses in the home timeline 41 | for status in tweepy.Cursor(api.home_timeline).items(200): 42 | # Process the status here 43 | process_status(status) 44 | 45 | FollowAll 46 | ========= 47 | 48 | This snippet will follow every follower of the authenticated user. 49 | 50 | .. code-block :: python 51 | 52 | for follower in tweepy.Cursor(api.followers).items(): 53 | follower.follow() 54 | 55 | Handling the rate limit using cursors 56 | ===================================== 57 | 58 | Since cursors raise ``RateLimitError``\ s while iterating, 59 | handling them can be done by wrapping the cursor in an iterator. 60 | 61 | Running this snippet will print all users you follow that themselves follow 62 | less than 300 people total - to exclude obvious spambots, for example - and 63 | will wait for 15 minutes each time it hits the rate limit. 64 | 65 | .. code-block :: python 66 | 67 | # In this example, the handler is time.sleep(15 * 60), 68 | # but you can of course handle it in any way you want. 69 | 70 | def limit_handled(cursor): 71 | while True: 72 | try: 73 | yield next(cursor) 74 | except tweepy.RateLimitError: 75 | time.sleep(15 * 60) 76 | 77 | for follower in limit_handled(tweepy.Cursor(api.followers).items()): 78 | if follower.friends_count < 300: 79 | print(follower.screen_name) 80 | -------------------------------------------------------------------------------- /cassettes/testfollowersids.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.25.1 13 | method: GET 14 | uri: https://api.twitter.com/1.1/followers/ids.json?screen_name=TweepyDev 15 | response: 16 | body: 17 | string: !!binary | 18 | H4sIAAAAAAAAAKpWykwpVrKKNjQ1NzeyNLfQMTQxNTY2szQzitVRykutKIlPLi0qzi9SsjJA4ccX 19 | lwDFlAyUdJQKilLLMvNLi5FVookhqS7JL0nMiU/OL80rUbLKK83JqQUAAAD//wMADfXWf4MAAAA= 20 | headers: 21 | cache-control: 22 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 23 | content-disposition: 24 | - attachment; filename=json.json 25 | content-encoding: 26 | - gzip 27 | content-length: 28 | - '113' 29 | content-type: 30 | - application/json;charset=utf-8 31 | date: 32 | - Fri, 12 Feb 2021 23:26:26 GMT 33 | expires: 34 | - Tue, 31 Mar 1981 05:00:00 GMT 35 | last-modified: 36 | - Fri, 12 Feb 2021 23:26:26 GMT 37 | pragma: 38 | - no-cache 39 | server: 40 | - tsa_b 41 | set-cookie: 42 | - personalization_id="v1_E4S85sbqqf18p/qFlDsitQ=="; Max-Age=63072000; Expires=Sun, 43 | 12 Feb 2023 23:26:26 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 44 | - lang=en; Path=/ 45 | - guest_id=v1%3A161317238617765137; Max-Age=63072000; Expires=Sun, 12 Feb 2023 46 | 23:26:26 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 47 | status: 48 | - 200 OK 49 | strict-transport-security: 50 | - max-age=631138519 51 | x-access-level: 52 | - read-write-directmessages 53 | x-connection-hash: 54 | - b4267ee0c139d8be7d3fb00e2f9cc49a 55 | x-content-type-options: 56 | - nosniff 57 | x-frame-options: 58 | - SAMEORIGIN 59 | x-rate-limit-limit: 60 | - '15' 61 | x-rate-limit-remaining: 62 | - '14' 63 | x-rate-limit-reset: 64 | - '1613173286' 65 | x-response-time: 66 | - '25' 67 | x-transaction: 68 | - 00514258005abbb3 69 | x-twitter-response-tags: 70 | - BouncerCompliant 71 | x-xss-protection: 72 | - '0' 73 | status: 74 | code: 200 75 | message: OK 76 | version: 1 77 | -------------------------------------------------------------------------------- /tweepy/errors.py: -------------------------------------------------------------------------------- 1 | # Tweepy 2 | # Copyright 2009-2021 Joshua Roesslein 3 | # See LICENSE for details. 4 | 5 | import json 6 | 7 | 8 | class TweepyException(Exception): 9 | """Base exception for Tweepy""" 10 | pass 11 | 12 | 13 | class HTTPException(TweepyException): 14 | """Exception raised when an HTTP request fails""" 15 | 16 | def __init__(self, response): 17 | self.response = response 18 | 19 | self.api_errors = [] 20 | self.api_codes = [] 21 | self.api_messages = [] 22 | 23 | try: 24 | response_json = response.json() 25 | except json.JSONDecodeError: 26 | super().__init__(f"{response.status_code} {response.reason}") 27 | else: 28 | error_text = [] 29 | # Use := when support for Python 3.7 is dropped 30 | if "errors" not in response_json: 31 | super().__init__(f"{response.status_code} {response.reason}") 32 | return 33 | for error in response_json["errors"]: 34 | self.api_errors.append(error) 35 | if "code" in error: 36 | self.api_codes.append(error["code"]) 37 | if "message" in error: 38 | self.api_messages.append(error["message"]) 39 | if "code" in error and "message" in error: 40 | error_text.append(f"{error['code']} - {error['message']}") 41 | elif "message" in error: 42 | error_text.append(error["message"]) 43 | error_text = '\n'.join(error_text) 44 | super().__init__( 45 | f"{response.status_code} {response.reason}\n{error_text}" 46 | ) 47 | 48 | 49 | class BadRequest(HTTPException): 50 | """Exception raised for a 400 HTTP status code""" 51 | pass 52 | 53 | 54 | class Unauthorized(HTTPException): 55 | """Exception raised for a 401 HTTP status code""" 56 | pass 57 | 58 | 59 | class Forbidden(HTTPException): 60 | """Exception raised for a 403 HTTP status code""" 61 | pass 62 | 63 | 64 | class NotFound(HTTPException): 65 | """Exception raised for a 404 HTTP status code""" 66 | pass 67 | 68 | 69 | class TooManyRequests(HTTPException): 70 | """Exception raised for a 429 HTTP status code""" 71 | pass 72 | 73 | 74 | class TwitterServerError(HTTPException): 75 | """Exception raised for a 5xx HTTP status code""" 76 | -------------------------------------------------------------------------------- /cassettes/testfriendsids.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.25.1 13 | method: GET 14 | uri: https://api.twitter.com/1.1/friends/ids.json?screen_name=TweepyDev 15 | response: 16 | body: 17 | string: !!binary | 18 | H4sIAAAAAAAAAFzKwQrCMAyA4XfJOYcmTZNmryIyZO4wGKusrQjDd9fj9Ph//Acs9wrDJSdTd1dn 19 | ja5CSmhZxQkpmbFbRpIUo7oyWgpROagIqzFxJlROkTMjs4i7uKQrwja/2jj1vZYdhvDTY21fgwAI 20 | j31+LqXX8/lnp7uVdlvHqfStwbD1dX1/AAAA//8DACleTB7DAAAA 21 | headers: 22 | cache-control: 23 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 24 | content-disposition: 25 | - attachment; filename=json.json 26 | content-encoding: 27 | - gzip 28 | content-length: 29 | - '153' 30 | content-type: 31 | - application/json;charset=utf-8 32 | date: 33 | - Fri, 12 Feb 2021 03:58:54 GMT 34 | expires: 35 | - Tue, 31 Mar 1981 05:00:00 GMT 36 | last-modified: 37 | - Fri, 12 Feb 2021 03:58:54 GMT 38 | pragma: 39 | - no-cache 40 | server: 41 | - tsa_b 42 | set-cookie: 43 | - personalization_id="v1_Ckle6m0DOIQ6zRa+jgLJUg=="; Max-Age=63072000; Expires=Sun, 44 | 12 Feb 2023 03:58:54 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 45 | - lang=en; Path=/ 46 | - guest_id=v1%3A161310233437496974; Max-Age=63072000; Expires=Sun, 12 Feb 2023 47 | 03:58:54 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 48 | status: 49 | - 200 OK 50 | strict-transport-security: 51 | - max-age=631138519 52 | x-access-level: 53 | - read-write-directmessages 54 | x-connection-hash: 55 | - c1e0c06b1a3689373d6a1ecdad8dbd97 56 | x-content-type-options: 57 | - nosniff 58 | x-frame-options: 59 | - SAMEORIGIN 60 | x-rate-limit-limit: 61 | - '15' 62 | x-rate-limit-remaining: 63 | - '13' 64 | x-rate-limit-reset: 65 | - '1613103206' 66 | x-response-time: 67 | - '22' 68 | x-transaction: 69 | - 004d8a93006dcf9b 70 | x-twitter-response-tags: 71 | - BouncerCompliant 72 | x-xss-protection: 73 | - '0' 74 | status: 75 | code: 200 76 | message: OK 77 | version: 1 78 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | from setuptools import find_packages, setup 5 | 6 | VERSION_FILE = "tweepy/__init__.py" 7 | with open(VERSION_FILE) as version_file: 8 | match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 9 | version_file.read(), re.MULTILINE) 10 | 11 | if match: 12 | version = match.group(1) 13 | else: 14 | raise RuntimeError(f"Unable to find version string in {VERSION_FILE}.") 15 | 16 | with open("README.md") as readme_file: 17 | long_description = readme_file.read() 18 | 19 | tests_require = [ 20 | "mock>=1.0.1", 21 | "nose>=1.3.3", 22 | "vcrpy>=1.10.3", 23 | ] 24 | 25 | setup( 26 | name="tweepy", 27 | version=version, 28 | description="Twitter library for Python", 29 | long_description=long_description, 30 | long_description_content_type="text/markdown", 31 | license="MIT", 32 | author="Joshua Roesslein", 33 | author_email="tweepy@googlegroups.com", 34 | url="https://www.tweepy.org/", 35 | project_urls={ 36 | "Documentation": "https://tweepy.readthedocs.io", 37 | "Issue Tracker": "https://github.com/tweepy/tweepy/issues", 38 | "Source Code": "https://github.com/tweepy/tweepy", 39 | }, 40 | download_url="https://pypi.org/project/tweepy/", 41 | packages=find_packages(exclude=["tests", "examples"]), 42 | install_requires=[ 43 | "requests>=2.11.1,<3", 44 | "requests_oauthlib>=1.0.0,<2", 45 | ], 46 | tests_require=tests_require, 47 | extras_require={ 48 | "dev": [ 49 | "coveralls>=2.1.0", 50 | "tox>=2.4.0", 51 | ], 52 | "socks": ["requests[socks]>=2.11.1,<3"], 53 | "test": tests_require, 54 | }, 55 | test_suite="nose.collector", 56 | keywords="twitter library", 57 | python_requires=">=3.6", 58 | classifiers=[ 59 | "Development Status :: 5 - Production/Stable", 60 | "Topic :: Software Development :: Libraries", 61 | "License :: OSI Approved :: MIT License", 62 | "Operating System :: OS Independent", 63 | "Programming Language :: Python", 64 | "Programming Language :: Python :: 3", 65 | "Programming Language :: Python :: 3.6", 66 | "Programming Language :: Python :: 3.7", 67 | "Programming Language :: Python :: 3.8", 68 | "Programming Language :: Python :: 3.9", 69 | "Programming Language :: Python :: 3 :: Only", 70 | ], 71 | zip_safe=True, 72 | ) 73 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/running_tests.po: -------------------------------------------------------------------------------- 1 | # Running Tests 2 | # Copyright (C) 2009-2020, Joshua Roesslein 3 | # This file is distributed under the same license as the Tweepy package. 4 | # 악동분홍토끼 , 2019. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Tweepy-ko\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-12-28 09:41+0900\n" 12 | "PO-Revision-Date: 2019-12-28 10:59+0900\n" 13 | "Last-Translator: 악동분홍토끼 \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.7.0\n" 19 | 20 | #: ../../running_tests.rst:5 21 | msgid "Running Tests" 22 | msgstr "테스트 하기" 23 | 24 | #: ../../running_tests.rst:7 25 | msgid "These steps outline how to run tests for Tweepy:" 26 | msgstr "다음은 Tweepy에서 어떻게 테스트를 진행할 수 있는가에 대한 대략적인 설명입니다." 27 | 28 | #: ../../running_tests.rst:9 29 | msgid "Download Tweepy's source code to a directory." 30 | msgstr "Tweepy 소스코드를 다운로드합니다." 31 | 32 | #: ../../running_tests.rst:11 33 | msgid "" 34 | "Install from the downloaded source with the ``test`` extra, e.g. ``pip " 35 | "install .[test]``. Optionally install the ``dev`` extra as well, for " 36 | "``tox`` and ``coverage``, e.g. ``pip install .[dev,test]``." 37 | msgstr "" 38 | "다운로드한 소스코드를 ``test`` 의 추가 정보를 사용, 설치하세요. " 39 | "(예시: ``pip install .[test]`` ) 추가적으로 ``tox`` 와 ``coverage`` 의 사용이 필요하다면 " 40 | "``dev`` 추가 정보와 같이 설치해주세요. (예시: ``pip install .[dev,test]`` )" 41 | 42 | #: ../../running_tests.rst:15 43 | msgid "" 44 | "Run ``python setup.py nosetests`` or simply ``nosetests`` in the source " 45 | "directory. With the ``dev`` extra, coverage will be shown, and ``tox`` " 46 | "can also be run to test different Python versions." 47 | msgstr "" 48 | "소스 디렉토리에서 ``python setup.py nonsetests`` 또는 간단하게 ``nonsetests`` 를 실행시키세요. " 49 | "``dev`` 추가 정보를 포함했다면 ``coverage`` 를 볼 수 있으며, " 50 | "``tox`` 를 이용해 다른 버전의 파이썬으로 실행할 수도 있습니다." 51 | 52 | #: ../../running_tests.rst:19 53 | msgid "To record new cassettes, the following environment variables can be used:" 54 | msgstr "새 카세트를 기록하기 위해서는, 아래 환경 변수들을 사용할 수 있어야 합니다:" 55 | 56 | #: ../../running_tests.rst:21 57 | msgid "" 58 | "``TWITTER_USERNAME`` ``CONSUMER_KEY`` ``CONSUMER_SECRET`` ``ACCESS_KEY`` " 59 | "``ACCESS_SECRET`` ``USE_REPLAY``" 60 | msgstr "" 61 | "``TWITTER_USERNAME`` ``CONSUMER_KEY`` ``CONSUMER_SECRET`` ``ACCESS_KEY`` " 62 | "``ACCESS_SECRET`` ``USE_REPLAY``" 63 | 64 | #: ../../running_tests.rst:28 65 | msgid "" 66 | "Simply set ``USE_REPLAY`` to ``False`` and provide the app and account " 67 | "credentials and username." 68 | msgstr "" 69 | "간단하게 ``USE_REPLAY`` 를 ``False`` 로 설정하고, 앱, 계정 자격 증명, 사용자 이름을 제공해도 됩니다." 70 | -------------------------------------------------------------------------------- /docs/locale/pl/LC_MESSAGES/code_snippet.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2020, Joshua Roesslein 2 | # This file is distributed under the same license as the Tweepy package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Tweepy-pl\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2020-01-14 14:12+0100\n" 8 | "PO-Revision-Date: 2020-01-19 09:28+0100\n" 9 | "Last-Translator: krzysztofturtle \n" 10 | "Language: pl\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " 13 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Generated-By: Babel 2.8.0\n" 18 | "X-Generator: POEditor.com\n" 19 | 20 | #: ../../code_snippet.rst:6 21 | msgid "Code Snippets" 22 | msgstr "Snippety" 23 | 24 | #: ../../code_snippet.rst:9 25 | msgid "Introduction" 26 | msgstr "Wprowadzenie" 27 | 28 | #: ../../code_snippet.rst:11 29 | msgid "Here are some code snippets to help you out with using Tweepy. Feel free to contribute your own snippets or improve the ones here!" 30 | msgstr "Tutaj znajdują się snippety, które mogą pomóc tobie w użytkowaniu Tweepy. Możesz także dodać swoje własne snippety lub usprawnić te, które się tutaj znajdują!" 31 | 32 | #: ../../code_snippet.rst:15 33 | msgid "OAuth" 34 | msgstr "OAuth" 35 | 36 | #: ../../code_snippet.rst:31 37 | msgid "Pagination" 38 | msgstr "Stronnicowanie" 39 | 40 | #: ../../code_snippet.rst:46 41 | msgid "FollowAll" 42 | msgstr "FollowAll" 43 | 44 | #: ../../code_snippet.rst:48 45 | msgid "This snippet will follow every follower of the authenticated user." 46 | msgstr "Ten snippet będzie obserwował każdego kto obserwuje uwierzytelnionego użytkownika." 47 | 48 | #: ../../code_snippet.rst:56 49 | msgid "Handling the rate limit using cursors" 50 | msgstr "Obsługiwanie limitu wartości używając kursorów" 51 | 52 | #: ../../code_snippet.rst:58 53 | msgid "Since cursors raise ``RateLimitError``\\ s in their ``next()`` method, handling them can be done by wrapping the cursor in an iterator." 54 | msgstr "Ponieważ kursory podnoszą ``RateLimitError``\\ w swoich metodach ``next()``, obsługiwanie ich może być wykonane poprzez zapakowanie kursora jako iterator." 55 | 56 | #: ../../code_snippet.rst:61 57 | msgid "Running this snippet will print all users you follow that themselves follow less than 300 people total - to exclude obvious spambots, for example - and will wait for 15 minutes each time it hits the rate limit." 58 | msgstr "Uruchomienie tego snippeta wyświetli listę wszystkich użytkowników których obserwujesz, a którzy sami obserwują mniej niż 300 osób - między innymi by wykluczyć oczywiste spamboty - dodatkowo snippet będzie czekał 15 minut za każdym razem gdy osiągnie limit wartości." 59 | 60 | -------------------------------------------------------------------------------- /docs/locale/pl/LC_MESSAGES/running_tests.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2020, Joshua Roesslein 2 | # This file is distributed under the same license as the Tweepy package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Tweepy-pl\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2020-01-14 14:12+0100\n" 8 | "PO-Revision-Date: 2020-01-19 09:28+0100\n" 9 | "Last-Translator: krzysztofturtle \n" 10 | "Language: pl\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " 13 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Generated-By: Babel 2.8.0\n" 18 | "X-Generator: POEditor.com\n" 19 | 20 | #: ../../running_tests.rst:5 21 | msgid "Running Tests" 22 | msgstr "Uruchamianie testów" 23 | 24 | #: ../../running_tests.rst:7 25 | msgid "These steps outline how to run tests for Tweepy:" 26 | msgstr "Te kroki wyjaśniają jak uruchamiać testy dla Tweepy:" 27 | 28 | #: ../../running_tests.rst:9 29 | msgid "Download Tweepy's source code to a directory." 30 | msgstr "Pobierz kod źródłowy Tweepy do katalogu." 31 | 32 | #: ../../running_tests.rst:11 33 | msgid "Install from the downloaded source with the ``test`` extra, e.g. ``pip install .[test]``. Optionally install the ``dev`` extra as well, for ``tox`` and ``coverage``, e.g. ``pip install .[dev,test]``." 34 | msgstr "Zainstaluj z pobranego źródła z ``test`` extra, np. ``pip install .[test]``. Opcjonalnie możesz zainstalować też ``dev`` extra, dla ``tox`` i ``coverage`` np. ``pip install .[dev,test]``." 35 | 36 | #: ../../running_tests.rst:15 37 | msgid "Run ``python setup.py nosetests`` or simply ``nosetests`` in the source directory. With the ``dev`` extra, coverage will be shown, and ``tox`` can also be run to test different Python versions." 38 | msgstr "Uruchom ``python setup.py nosetests`` lub po prostu ``nosetests`` w katalogu źródłowym. Z ``dev`` extra będzie wyświetlone sprawozdanie a ``tox`` może być także użyty do uruchomienia testu z inną wersją Python." 39 | 40 | #: ../../running_tests.rst:19 41 | msgid "To record new cassettes, the following environment variables can be used:" 42 | msgstr "By nagrywać nowe kasety możesz użyć niżej wymienionych zmiennych środowiskowych:" 43 | 44 | #: ../../running_tests.rst:21 45 | msgid "``TWITTER_USERNAME`` ``CONSUMER_KEY`` ``CONSUMER_SECRET`` ``ACCESS_KEY`` ``ACCESS_SECRET`` ``USE_REPLAY``" 46 | msgstr "``TWITTER_USERNAME`` ``CONSUMER_KEY`` ``CONSUMER_SECRET`` ``ACCESS_KEY`` ``ACCESS_SECRET`` ``USE_REPLAY``" 47 | 48 | #: ../../running_tests.rst:28 49 | msgid "Simply set ``USE_REPLAY`` to ``False`` and provide the app and account credentials and username." 50 | msgstr "Po prostu ustaw ``USE_REPLAY`` jako ``FALSE`` i dostarcz aplikacji dane logowania oraz nazwę użytkownika." 51 | 52 | -------------------------------------------------------------------------------- /tweepy/parsers.py: -------------------------------------------------------------------------------- 1 | # Tweepy 2 | # Copyright 2009-2021 Joshua Roesslein 3 | # See LICENSE for details. 4 | 5 | import json as json_lib 6 | 7 | from tweepy.errors import TweepyException 8 | from tweepy.models import ModelFactory 9 | 10 | 11 | class Parser: 12 | 13 | def parse(self, payload, *args, **kwargs): 14 | """ 15 | Parse the response payload and return the result. 16 | Returns a tuple that contains the result data and the cursors 17 | (or None if not present). 18 | """ 19 | raise NotImplementedError 20 | 21 | 22 | class RawParser(Parser): 23 | 24 | def __init__(self): 25 | pass 26 | 27 | def parse(self, payload, *args, **kwargs): 28 | return payload 29 | 30 | 31 | class JSONParser(Parser): 32 | 33 | payload_format = 'json' 34 | 35 | def parse(self, payload, *, return_cursors=False, **kwargs): 36 | try: 37 | json = json_lib.loads(payload) 38 | except Exception as e: 39 | raise TweepyException(f'Failed to parse JSON payload: {e}') 40 | 41 | if return_cursors and isinstance(json, dict): 42 | if 'next' in json: 43 | return json, json['next'] 44 | elif 'next_cursor' in json: 45 | if 'previous_cursor' in json: 46 | cursors = json['previous_cursor'], json['next_cursor'] 47 | return json, cursors 48 | else: 49 | return json, json['next_cursor'] 50 | return json 51 | 52 | 53 | class ModelParser(JSONParser): 54 | 55 | def __init__(self, model_factory=None): 56 | JSONParser.__init__(self) 57 | self.model_factory = model_factory or ModelFactory 58 | 59 | def parse(self, payload, *, api=None, payload_list=False, 60 | payload_type=None, return_cursors=False): 61 | try: 62 | if payload_type is None: 63 | return 64 | model = getattr(self.model_factory, payload_type) 65 | except AttributeError: 66 | raise TweepyException( 67 | f'No model for this payload type: {payload_type}' 68 | ) 69 | 70 | json = JSONParser.parse(self, payload, return_cursors=return_cursors) 71 | if isinstance(json, tuple): 72 | json, cursors = json 73 | else: 74 | cursors = None 75 | 76 | try: 77 | if payload_list: 78 | result = model.parse_list(api, json) 79 | else: 80 | result = model.parse(api, json) 81 | except KeyError: 82 | raise TweepyException( 83 | f"Unable to parse response payload: {json}" 84 | ) from None 85 | 86 | if cursors: 87 | return result, cursors 88 | else: 89 | return result 90 | -------------------------------------------------------------------------------- /docs/cursor_tutorial.rst: -------------------------------------------------------------------------------- 1 | .. _cursor_tutorial: 2 | 3 | *************** 4 | Cursor Tutorial 5 | *************** 6 | 7 | This tutorial describes details on pagination with Cursor objects. 8 | 9 | Introduction 10 | ============ 11 | 12 | We use pagination a lot in Twitter API development. Iterating through 13 | timelines, user lists, direct messages, etc. In order to perform 14 | pagination, we must supply a page/cursor parameter with each of our 15 | requests. The problem here is this requires a lot of boiler plate code 16 | just to manage the pagination loop. To help make pagination easier and 17 | require less code, Tweepy has the Cursor object. 18 | 19 | Old way vs Cursor way 20 | ===================== 21 | 22 | First let's demonstrate iterating the statuses in the authenticated 23 | user's timeline. Here is how we would do it the "old way" before the 24 | Cursor object was introduced:: 25 | 26 | page = 1 27 | while True: 28 | statuses = api.user_timeline(page=page) 29 | if statuses: 30 | for status in statuses: 31 | # process status here 32 | process_status(status) 33 | else: 34 | # All done 35 | break 36 | page += 1 # next page 37 | 38 | As you can see, we must manage the "page" parameter manually in our 39 | pagination loop. Now here is the version of the code using the Cursor 40 | object:: 41 | 42 | for status in tweepy.Cursor(api.user_timeline).items(): 43 | # process status here 44 | process_status(status) 45 | 46 | Now that looks much better! Cursor handles all the pagination work for 47 | us behind the scenes, so our code can now focus entirely on processing 48 | the results. 49 | 50 | Passing parameters into the API method 51 | ====================================== 52 | 53 | What if you need to pass in parameters to the API method? 54 | 55 | .. code-block :: python 56 | 57 | api.user_timeline(id="twitter") 58 | 59 | Since we pass Cursor the callable, we can not pass the parameters 60 | directly into the method. Instead we pass the parameters into the 61 | Cursor constructor method:: 62 | 63 | tweepy.Cursor(api.user_timeline, id="twitter") 64 | 65 | Now Cursor will pass the parameter into the method for us whenever it 66 | makes a request. 67 | 68 | Items or Pages 69 | ============== 70 | 71 | So far we have just demonstrated pagination iterating per 72 | item. What if instead you want to process per page of results? You 73 | would use the pages() method:: 74 | 75 | for page in tweepy.Cursor(api.user_timeline).pages(): 76 | # page is a list of statuses 77 | process_page(page) 78 | 79 | 80 | Limits 81 | ====== 82 | 83 | What if you only want n items or pages returned? You pass into the 84 | items() or pages() methods the limit you want to impose. 85 | 86 | .. code-block :: python 87 | 88 | # Only iterate through the first 200 statuses 89 | for status in tweepy.Cursor(api.user_timeline).items(200): 90 | process_status(status) 91 | 92 | # Only iterate through the first 3 pages 93 | for page in tweepy.Cursor(api.user_timeline).pages(3): 94 | process_page(page) 95 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/getting_started.po: -------------------------------------------------------------------------------- 1 | # Getting started 2 | # Copyright (C) 2009-2020, Joshua Roesslein 3 | # This file is distributed under the same license as the Tweepy package. 4 | # 악동분홍토끼 , 2019. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Tweepy-ko\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-12-28 09:41+0900\n" 12 | "PO-Revision-Date: 2019-12-28 11:09+0900\n" 13 | "Last-Translator: 악동분홍토끼 \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.7.0\n" 19 | 20 | #: ../../getting_started.rst:6 21 | msgid "Getting started" 22 | msgstr "Tweepy 시작하기" 23 | 24 | #: ../../getting_started.rst:9 25 | msgid "Introduction" 26 | msgstr "들어가며" 27 | 28 | #: ../../getting_started.rst:11 29 | msgid "" 30 | "If you are new to Tweepy, this is the place to begin. The goal of this " 31 | "tutorial is to get you set-up and rolling with Tweepy. We won't go into " 32 | "too much detail here, just some important basics." 33 | msgstr "" 34 | "Tweepy가 처음이라면, 이 문서를 참조하시는 것을 권장합니다. " 35 | "이 문서의 목표는 여러분이 Tweepy를 어떻게 설정하고 롤링하는지 " 36 | "알게 되는 것입니다. 여기서는 세부적인 언급은 피할 것이며, " 37 | "몇 가지 중요한 기초 사항들만 다룰 것입니다." 38 | 39 | #: ../../getting_started.rst:16 40 | msgid "Hello Tweepy" 41 | msgstr "Hello Tweepy" 42 | 43 | #: ../../getting_started.rst:31 44 | msgid "" 45 | "This example will download your home timeline tweets and print each one " 46 | "of their texts to the console. Twitter requires all requests to use OAuth" 47 | " for authentication. The :ref:`auth_tutorial` goes into more details " 48 | "about authentication." 49 | msgstr "" 50 | "위 예제는 내 타임라인의 트윗을 다운로드하여, 콘솔에 각 트윗을 텍스트로써 " 51 | "출력하는 예제입니다. 참고로, 트위터는 모든 요청에 OAuth 인증을 요구합니다. " 52 | "인증에 대한 보다 자세한 내용은 :ref:`auth_tutorial` 를 참고해주세요." 53 | 54 | #: ../../getting_started.rst:37 55 | msgid "API" 56 | msgstr "API" 57 | 58 | #: ../../getting_started.rst:39 59 | msgid "" 60 | "The API class provides access to the entire twitter RESTful API methods. " 61 | "Each method can accept various parameters and return responses. For more " 62 | "information about these methods please refer to :ref:`API Reference " 63 | "`." 64 | msgstr "" 65 | "API 클래스는 트위터의 모든 RESTful API 메소드에 대한 접근을 지원합니다. " 66 | "각 메소드는 다양한 매개변수를 전달받고 적절한 값을 반환할 수 있습니다. " 67 | "보다 자세한 내용은 :ref:`API Reference ` 를 참고해주세요." 68 | 69 | #: ../../getting_started.rst:45 70 | msgid "Models" 71 | msgstr "모델 (Models)" 72 | 73 | #: ../../getting_started.rst:47 74 | msgid "" 75 | "When we invoke an API method most of the time returned back to us will be" 76 | " a Tweepy model class instance. This will contain the data returned from " 77 | "Twitter which we can then use inside our application. For example the " 78 | "following code returns to us an User model::" 79 | msgstr "" 80 | "API 메소드를 호출할 때, 반환받는 것의 대부분은 Tweepy의 모델 클래스 인스턴스가 " 81 | "될 것입니다. 이는 애플리케이션에서 사용 가능한, " 82 | "트위터로부터 반환받은 데이터를 포함할 것입니다. " 83 | "예를 들어, 아래의 코드는 User 모델을 반환합니다:: " 84 | 85 | #: ../../getting_started.rst:55 86 | msgid "Models contain the data and some helper methods which we can then use::" 87 | msgstr "모델에는 다음과 같이, 사용 가능한 데이터 및 메소드가 포함되어 있습니다:: " 88 | 89 | #: ../../getting_started.rst:63 90 | msgid "For more information about models please see ModelsReference." 91 | msgstr "모델에 대한 보다 자세한 내용은 ModelsReference(Original Link Missing)를 참고해주세요." 92 | 93 | -------------------------------------------------------------------------------- /docs/locale/pl/LC_MESSAGES/getting_started.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2020, Joshua Roesslein 2 | # This file is distributed under the same license as the Tweepy package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Tweepy-pl\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2020-01-14 14:12+0100\n" 8 | "PO-Revision-Date: 2020-01-19 09:28+0100\n" 9 | "Last-Translator: krzysztofturtle \n" 10 | "Language: pl\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " 13 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Generated-By: Babel 2.8.0\n" 18 | "X-Generator: POEditor.com\n" 19 | 20 | #: ../../getting_started.rst:6 21 | msgid "Getting started" 22 | msgstr "Pierwsze kroki" 23 | 24 | #: ../../getting_started.rst:9 25 | msgid "Introduction" 26 | msgstr "Wprowadzenie" 27 | 28 | #: ../../getting_started.rst:11 29 | msgid "If you are new to Tweepy, this is the place to begin. The goal of this tutorial is to get you set-up and rolling with Tweepy. We won't go into too much detail here, just some important basics." 30 | msgstr "Jeżeli jesteś nowy to tutaj jest idealne miejsce do rozpoczęcia twojej przygody z Tweepy. Celem tego poradnika jest pomoc w ustawieniu i rozpoczęciu pracy z Tweepy. Nie znajdziesz tu zbyt szczegółowych opisów, głównie podstawowe informacje." 31 | 32 | #: ../../getting_started.rst:16 33 | msgid "Hello Tweepy" 34 | msgstr "Hello Tweepy" 35 | 36 | #: ../../getting_started.rst:31 37 | msgid "This example will download your home timeline tweets and print each one of their texts to the console. Twitter requires all requests to use OAuth for authentication. The :ref:`auth_tutorial` goes into more details about authentication." 38 | msgstr "Ten przykład pobierze tweety z twojej osi czasu i wyświetli tekst każdego z nich w konsoli. Twitter wymaga by wszystkie żądania używały OAuth do uwierzytelniania. W :ref:`auth_tutorial` znajdziesz więcej szczegółów na temat uwierzytelniania." 39 | 40 | #: ../../getting_started.rst:37 41 | msgid "API" 42 | msgstr "API" 43 | 44 | #: ../../getting_started.rst:39 45 | msgid "The API class provides access to the entire twitter RESTful API methods. Each method can accept various parameters and return responses. For more information about these methods please refer to :ref:`API Reference `." 46 | msgstr "Klasa API dostarcza dostęp do całości metod twitter RESTful API. Każda metoda może zaakceptować różne parametry i zwrócić odpowiedzi. Po więcej informacji zajrzyj do :ref:`API Reference `." 47 | 48 | #: ../../getting_started.rst:45 49 | msgid "Models" 50 | msgstr "Modele" 51 | 52 | #: ../../getting_started.rst:47 53 | msgid "When we invoke an API method most of the time returned back to us will be a Tweepy model class instance. This will contain the data returned from Twitter which we can then use inside our application. For example the following code returns to us an User model::" 54 | msgstr "Gdy wywołujesz metodę API, w większości przypadków zwrócony zostanie moduł klasy instancji. Będzie on zawierał dane zwrócone przez Twitter, które możesz później użyć w swojej aplikacji. Na przykład poniższy kod zwraca model User::" 55 | 56 | #: ../../getting_started.rst:55 57 | msgid "Models contain the data and some helper methods which we can then use::" 58 | msgstr "Modele zawierają dane i metody pomocnicze, których możesz później użyć::" 59 | 60 | #: ../../getting_started.rst:63 61 | msgid "For more information about models please see ModelsReference." 62 | msgstr "Po więcej informacji zajrzyj do ModelsReference." 63 | 64 | -------------------------------------------------------------------------------- /cassettes/testfavorites.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/favorites/list.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:39 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "5fda8a1a4713e6bdbbdd1d54e1671f1b" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "75" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "72" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:39 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_xGMPMYX1ceno4iWU1bMeVQ==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:39 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298485909372546; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:39 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "007ed7c400a5c288" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "2" 85 | ], 86 | "x-response-time": [ 87 | "16" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562985491" 91 | ] 92 | }, 93 | "body": { 94 | "string": "[]" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testretweetsofme.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/statuses/retweets_of_me.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:49 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "24d621c51432c35c07a135ece42510d3" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "75" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "70" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:49 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_AAuwgktA0Do7PKCnKUeifQ==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:49 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486992017647; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:49 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "00742ffd00fb4c86" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "2" 85 | ], 86 | "x-response-time": [ 87 | "15" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984897" 91 | ] 92 | }, 93 | "body": { 94 | "string": "[]" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testmentionstimeline.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/statuses/mentions_timeline.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:48 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "a0b763ea87c85da87247794ef637df3a" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "75" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "70" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:48 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_kfe0CyQMMPyKHY9Vw5Qw4g==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:48 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486800315510; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:48 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "000c93c700957e3c" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "2" 85 | ], 86 | "x-response-time": [ 87 | "16" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984896" 91 | ] 92 | }, 93 | "body": { 94 | "string": "[]" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testlistdirectmessages.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/direct_messages/events/list.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-length": [ 22 | "56" 23 | ], 24 | "x-frame-options": [ 25 | "SAMEORIGIN" 26 | ], 27 | "x-rate-limit-reset": [ 28 | "1562991666" 29 | ], 30 | "x-rate-limit-remaining": [ 31 | "14" 32 | ], 33 | "x-xss-protection": [ 34 | "1; mode=block; report=https://twitter.com/i/xss_report" 35 | ], 36 | "expires": [ 37 | "Tue, 31 Mar 1981 05:00:00 GMT" 38 | ], 39 | "x-connection-hash": [ 40 | "4d7817b7fe77dbb0e40785bd9f5dbe6b" 41 | ], 42 | "cache-control": [ 43 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 44 | ], 45 | "date": [ 46 | "Sat, 13 Jul 2019 04:06:06 GMT" 47 | ], 48 | "content-type": [ 49 | "application/json;charset=utf-8" 50 | ], 51 | "x-twitter-response-tags": [ 52 | "BouncerCompliant" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "strict-transport-security": [ 58 | "max-age=631138519" 59 | ], 60 | "server": [ 61 | "tsa_b" 62 | ], 63 | "last-modified": [ 64 | "Sat, 13 Jul 2019 04:06:06 GMT" 65 | ], 66 | "x-content-type-options": [ 67 | "nosniff" 68 | ], 69 | "x-transaction": [ 70 | "00daf5c0000082c2" 71 | ], 72 | "set-cookie": [ 73 | "personalization_id=\"v1_TLAth1AvGuvuQAXnlvPVZQ==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 04:06:06 GMT; Path=/; Domain=.twitter.com", 74 | "lang=en; Path=/", 75 | "guest_id=v1%3A156299076660481425; Max-Age=63072000; Expires=Mon, 12 Jul 2021 04:06:06 GMT; Path=/; Domain=.twitter.com" 76 | ], 77 | "content-disposition": [ 78 | "attachment; filename=json.json" 79 | ], 80 | "x-rate-limit-limit": [ 81 | "15" 82 | ], 83 | "x-response-time": [ 84 | "18" 85 | ], 86 | "status": [ 87 | "200 OK" 88 | ], 89 | "x-access-level": [ 90 | "read-write-directmessages" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"events\":[],\"next_cursor\":\"MTE0MjA5ODA3OTUyNjYwMDcwOA\"}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testlistsmemberships.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/lists/memberships.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:44 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "25037a788cfa5c2a5a6656add6da8d4d" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "75" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "70" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:44 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_BGYj/OZeMyqRo/ANrpX+KA==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:44 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486472902530; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:44 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "00bcf3d1008d708d" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "96" 85 | ], 86 | "x-response-time": [ 87 | "17" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984893" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"next_cursor\":0,\"next_cursor_str\":\"0\",\"previous_cursor\":0,\"previous_cursor_str\":\"0\",\"lists\":[]}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testblocks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/blocks/list.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:34 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "547e037d3886912085fabe18577553b9" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "15" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "10" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:34 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_YCbHI8sqow22BZssnYYepQ==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:34 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298485486413899; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:34 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "002148e700c5a164" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "115" 85 | ], 86 | "x-response-time": [ 87 | "13" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984883" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"users\":[],\"next_cursor\":0,\"next_cursor_str\":\"0\",\"previous_cursor\":0,\"previous_cursor_str\":\"0\",\"total_count\":null}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testblocksids.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/blocks/ids.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:35 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "ca1dd9934d27bb0ecc7cbe49190750b3" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "15" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "10" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:35 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_0nRD1pMdTBlvzUP/HTbl8A==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:35 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298485515073122; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:35 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "006ed2c100610a4d" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "113" 85 | ], 86 | "x-response-time": [ 87 | "13" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984884" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"ids\":[],\"next_cursor\":0,\"next_cursor_str\":\"0\",\"previous_cursor\":0,\"previous_cursor_str\":\"0\",\"total_count\":null}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testlistssubscriptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/lists/subscriptions.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:45 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "4d6f9df706626c4f81a07930e9370a99" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "15" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "10" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:45 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_TRsXhCwTCeRKlVQMqVexHw==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:45 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486504019521; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:45 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "0040ba9d00c1bcb4" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "96" 85 | ], 86 | "x-response-time": [ 87 | "16" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984893" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"next_cursor\":0,\"next_cursor_str\":\"0\",\"previous_cursor\":0,\"previous_cursor_str\":\"0\",\"lists\":[]}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /docs/parameters.rst: -------------------------------------------------------------------------------- 1 | .. API parameters: 2 | 3 | .. |additional_owners| replace:: A list of user IDs to set as additional owners allowed to use the returned ``media_id`` in Tweet or Cards. Up to 100 additional owners may be specified. 4 | .. |count| replace:: The number of results to try and retrieve per page. 5 | .. |cursor| replace:: Breaks the results into pages. Provide a value of -1 to begin paging. Provide values as returned to in the response body's next_cursor and previous_cursor attributes to page back and forth in the list. 6 | .. |date| replace:: Permits specifying a start date for the report. The date should be formatted YYYY-MM-DD. 7 | .. |exclude| replace:: Setting this equal to hashtags will remove all hashtags from the trends list. 8 | .. |exclude_replies| replace:: This parameter will prevent replies from appearing in the returned timeline. Using ``exclude_replies`` with the ``count`` parameter will mean you will receive up-to count Tweets — this is because the ``count`` parameter retrieves that many Tweets before filtering out retweets and replies. 9 | .. |file| replace:: A file object, which will be used instead of opening ``filename``. ``filename`` is still required, for MIME type detection and to use as a form field in the POST data. 10 | .. |filename| replace:: The filename of the image to upload. This will automatically be opened unless ``file`` is specified. 11 | .. |full_text| replace:: A boolean indicating whether or not the full text of a message should be returned. If False the message text returned will be truncated to 140 chars. Defaults to False. 12 | .. |include_card_uri| replace:: A boolean indicating if the retrieved Tweet should include a card_uri attribute when there is an ads card attached to the Tweet and when that card was attached using the card_uri value. 13 | .. |include_entities| replace:: The entities node will not be included when set to false. Defaults to true. 14 | .. |include_ext_alt_text| replace:: If alt text has been added to any attached media entities, this parameter will return an ext_alt_text value in the top-level key for the media entity. 15 | .. |include_user_entities| replace:: The user object entities node will not be included when set to false. Defaults to true. 16 | .. |list_id| replace:: The numerical id of the list. 17 | .. |list_mode| replace:: Whether your list is public or private. Values can be public or private. Lists are public by default if no mode is specified. 18 | .. |list_owner| replace:: the screen name of the owner of the list 19 | .. |media_category| replace:: The category that represents how the media will be used. This field is required when using the media with the Ads API. 20 | .. |max_id| replace:: Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. 21 | .. |owner_id| replace:: The user ID of the user who owns the list being requested by a slug. 22 | .. |owner_screen_name| replace:: The screen name of the user who owns the list being requested by a slug. 23 | .. |page| replace:: Specifies the page of results to retrieve. Note: there are pagination limits. 24 | .. |screen_name| replace:: Specifies the screen name of the user. Helpful for disambiguating when a valid screen name is also a user ID. 25 | .. |sid| replace:: The numerical ID of the status. 26 | .. |since_id| replace:: Returns only statuses with an ID greater than (that is, more recent than) the specified ID. 27 | .. |skip_status| replace:: A boolean indicating whether statuses will not be included in the returned user objects. Defaults to false. 28 | .. |slug| replace:: You can identify a list by its slug instead of its numerical id. If you decide to do so, note that you'll also have to specify the list owner using the owner_id or owner_screen_name parameters. 29 | .. |trim_user| replace:: A boolean indicating if user IDs should be provided, instead of complete user objects. Defaults to False. 30 | .. |uid| replace:: Specifies the ID or screen name of the user. 31 | .. |user_id| replace:: Specifies the ID of the user. Helpful for disambiguating when a valid user ID is also a valid screen name. 32 | 33 | -------------------------------------------------------------------------------- /cassettes/testgetoembed.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/statuses/oembed.json?url=https%3A%2F%2Ftwitter.com%2FTwitter%2Fstatus%2F266367358078169089", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "strict-transport-security": [ 22 | "max-age=631138519" 23 | ], 24 | "date": [ 25 | "Fri, 18 Dec 2020 13:28:43 GMT" 26 | ], 27 | "x-response-time": [ 28 | "22" 29 | ], 30 | "set-cookie": [ 31 | "personalization_id=\"v1_lNZrX8ERkRY3O6pDMRf0EQ==\"; Max-Age=63072000; Expires=Sun, 18 Dec 2022 13:28:43 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None", 32 | "guest_id=v1%3A160829812347056154; Max-Age=63072000; Expires=Sun, 18 Dec 2022 13:28:43 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None" 33 | ], 34 | "content-length": [ 35 | "983" 36 | ], 37 | "x-xss-protection": [ 38 | "0" 39 | ], 40 | "expires": [ 41 | "Sun, 24 Nov 2120 13:28:43 GMT" 42 | ], 43 | "content-disposition": [ 44 | "attachment; filename=json.json" 45 | ], 46 | "cache-control": [ 47 | "must-revalidate, max-age=3153600000" 48 | ], 49 | "last-modified": [ 50 | "Fri, 18 Dec 2020 13:28:43 GMT" 51 | ], 52 | "server": [ 53 | "tsa_b" 54 | ], 55 | "x-connection-hash": [ 56 | "535f712e275b4e0dcb0621cfe640c1e5" 57 | ], 58 | "x-content-type-options": [ 59 | "nosniff" 60 | ], 61 | "x-frame-options": [ 62 | "SAMEORIGIN" 63 | ], 64 | "x-rate-limit-remaining": [ 65 | "177" 66 | ], 67 | "x-rate-limit-limit": [ 68 | "180" 69 | ], 70 | "x-rate-limit-reset": [ 71 | "1608298530" 72 | ], 73 | "content-type": [ 74 | "application/json; charset=utf-8" 75 | ] 76 | }, 77 | "body": { 78 | "string": "{\"url\":\"https:\\/\\/twitter.com\\/Twitter\\/status\\/266367358078169089\",\"author_name\":\"Twitter\",\"author_url\":\"https:\\/\\/twitter.com\\/Twitter\",\"html\":\"\\u003Cblockquote class=\\\"twitter-tweet\\\"\\u003E\\u003Cp lang=\\\"en\\\" dir=\\\"ltr\\\"\\u003ERT \\u003Ca href=\\\"https:\\/\\/twitter.com\\/TwitterEng?ref_src=twsrc%5Etfw\\\"\\u003E@TwitterEng\\u003C\\/a\\u003E: Bolstering our infrastructure. "As usage patterns change, Twitter can remain resilient." \\u003Ca href=\\\"http:\\/\\/t.co\\/uML86B6s\\\"\\u003Ehttp:\\/\\/t.co\\/uML86B6s\\u003C\\/a\\u003E\\u003C\\/p\\u003E— Twitter (@Twitter) \\u003Ca href=\\\"https:\\/\\/twitter.com\\/Twitter\\/status\\/266367358078169089?ref_src=twsrc%5Etfw\\\"\\u003ENovember 8, 2012\\u003C\\/a\\u003E\\u003C\\/blockquote\\u003E\\n\\u003Cscript async src=\\\"https:\\/\\/platform.twitter.com\\/widgets.js\\\" charset=\\\"utf-8\\\"\\u003E\\u003C\\/script\\u003E\\n\",\"width\":550,\"height\":null,\"type\":\"rich\",\"cache_age\":\"3153600000\",\"provider_name\":\"Twitter\",\"provider_url\":\"https:\\/\\/twitter.com\",\"version\":\"1.0\"}" 79 | } 80 | } 81 | } 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /tests/test_streaming.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.case import skip 3 | 4 | from mock import MagicMock, patch 5 | 6 | from .config import create_auth 7 | from .test_utils import mock_tweet 8 | from tweepy.api import API 9 | from tweepy.auth import OAuthHandler 10 | from tweepy.models import Status 11 | from tweepy.streaming import Stream, StreamListener 12 | 13 | 14 | class MockStreamListener(StreamListener): 15 | def __init__(self, test_case): 16 | super().__init__() 17 | self.test_case = test_case 18 | self.status_count = 0 19 | self.status_stop_count = 0 20 | self.connect_cb = None 21 | 22 | def on_connect(self): 23 | if self.connect_cb: 24 | self.connect_cb() 25 | 26 | def on_timeout(self): 27 | self.test_case.fail('timeout') 28 | return False 29 | 30 | def on_error(self, code): 31 | print(f"response: {code}") 32 | return True 33 | 34 | def on_status(self, status): 35 | self.status_count += 1 36 | self.test_case.assertIsInstance(status, Status) 37 | if self.status_stop_count == self.status_count: 38 | return False 39 | 40 | 41 | class TweepyStreamTests(unittest.TestCase): 42 | def setUp(self): 43 | self.auth = create_auth() 44 | self.listener = MockStreamListener(self) 45 | self.stream = Stream(self.auth, self.listener, timeout=3.0) 46 | 47 | def tearDown(self): 48 | self.stream.disconnect() 49 | 50 | def on_connect(self): 51 | API(self.auth).update_status(mock_tweet()) 52 | 53 | def test_sample(self): 54 | self.listener.status_stop_count = 10 55 | self.stream.sample() 56 | self.assertEqual(self.listener.status_count, 57 | self.listener.status_stop_count) 58 | 59 | def test_filter_track(self): 60 | self.listener.status_stop_count = 5 61 | phrases = ['twitter'] 62 | self.stream.filter(track=phrases) 63 | self.assertEqual(self.listener.status_count, 64 | self.listener.status_stop_count) 65 | 66 | def test_track_encoding(self): 67 | s = Stream(None, None) 68 | s._start = lambda is_async: None 69 | s.filter(track=['Caf\xe9']) 70 | 71 | # Should be UTF-8 encoded 72 | self.assertEqual('Caf\xe9'.encode('utf8'), s.body['track']) 73 | 74 | def test_follow_encoding(self): 75 | s = Stream(None, None) 76 | s._start = lambda is_async: None 77 | s.filter(follow=['Caf\xe9']) 78 | 79 | # Should be UTF-8 encoded 80 | self.assertEqual('Caf\xe9'.encode('utf8'), s.body['follow']) 81 | 82 | 83 | class TweepyStreamBackoffTests(unittest.TestCase): 84 | def setUp(self): 85 | #bad auth causes twitter to return 401 errors 86 | self.auth = OAuthHandler("bad-key", "bad-secret") 87 | self.auth.set_access_token("bad-token", "bad-token-secret") 88 | self.listener = MockStreamListener(self) 89 | self.stream = Stream(self.auth, self.listener) 90 | 91 | def tearDown(self): 92 | self.stream.disconnect() 93 | 94 | def test_exp_backoff(self): 95 | self.stream = Stream(self.auth, self.listener, timeout=3.0, 96 | retry_count=1, retry_time=1.0, retry_time_cap=100.0) 97 | self.stream.sample() 98 | # 1 retry, should be 4x the retry_time 99 | self.assertEqual(self.stream.retry_time, 4.0) 100 | 101 | def test_exp_backoff_cap(self): 102 | self.stream = Stream(self.auth, self.listener, timeout=3.0, 103 | retry_count=1, retry_time=1.0, retry_time_cap=3.0) 104 | self.stream.sample() 105 | # 1 retry, but 4x the retry_time exceeds the cap, so should be capped 106 | self.assertEqual(self.stream.retry_time, 3.0) 107 | 108 | mock_resp = MagicMock() 109 | mock_resp.return_value.status_code = 420 110 | 111 | @patch('requests.Session.request', mock_resp) 112 | def test_420(self): 113 | self.stream = Stream(self.auth, self.listener, timeout=3.0, retry_count=0, 114 | retry_time=1.0, retry_420=1.5, retry_time_cap=20.0) 115 | self.stream.sample() 116 | # no retries, but error 420, should be double the retry_420, not double the retry_time 117 | self.assertEqual(self.stream.retry_time, 3.0) 118 | -------------------------------------------------------------------------------- /cassettes/testshowfriendship.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/friendships/show.json?target_screen_name=twitter", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:52 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "63aaf006928c79676ed193ad263d6460" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "180" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "175" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:52 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_QyFz3jvs80bGrB+ljuFFOg==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:52 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298487238422522; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:52 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "005f0f4900e76ce6" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "543" 85 | ], 86 | "x-response-time": [ 87 | "21" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984900" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"relationship\":{\"source\":{\"id\":1072250532645998596,\"id_str\":\"1072250532645998596\",\"screen_name\":\"TweepyDev\",\"following\":false,\"followed_by\":false,\"live_following\":false,\"following_received\":false,\"following_requested\":false,\"notifications_enabled\":false,\"can_dm\":false,\"blocking\":false,\"blocked_by\":false,\"muting\":false,\"want_retweets\":false,\"all_replies\":false,\"marked_spam\":false},\"target\":{\"id\":783214,\"id_str\":\"783214\",\"screen_name\":\"Twitter\",\"following\":false,\"followed_by\":false,\"following_received\":false,\"following_requested\":false}}}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testcursorcursoritems.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.25.1 13 | method: GET 14 | uri: https://api.twitter.com/1.1/friends/ids.json?cursor=-1 15 | response: 16 | body: 17 | string: !!binary | 18 | H4sIAAAAAAAAAFzKwQrCMAyA4XfJOYcmTZNmryIyZO4wGKusrQjDd9fj9Ph//Acs9wrDJSdTd1dn 19 | ja5CSmhZxQkpmbFbRpIUo7oyWgpROagIqzFxJlROkTMjs4i7uKQrwja/2jj1vZYdhvDTY21fgwAI 20 | j31+LqXX8/lnp7uVdlvHqfStwbD1dX1/AAAA//8DACleTB7DAAAA 21 | headers: 22 | cache-control: 23 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 24 | content-disposition: 25 | - attachment; filename=json.json 26 | content-encoding: 27 | - gzip 28 | content-length: 29 | - '153' 30 | content-type: 31 | - application/json;charset=utf-8 32 | date: 33 | - Fri, 12 Feb 2021 23:29:03 GMT 34 | expires: 35 | - Tue, 31 Mar 1981 05:00:00 GMT 36 | last-modified: 37 | - Fri, 12 Feb 2021 23:29:03 GMT 38 | pragma: 39 | - no-cache 40 | server: 41 | - tsa_b 42 | set-cookie: 43 | - personalization_id="v1_CKqJzwqWaovePh6gjmaeYQ=="; Max-Age=63072000; Expires=Sun, 44 | 12 Feb 2023 23:29:03 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 45 | - lang=en; Path=/ 46 | - guest_id=v1%3A161317254375268593; Max-Age=63072000; Expires=Sun, 12 Feb 2023 47 | 23:29:03 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 48 | status: 49 | - 200 OK 50 | strict-transport-security: 51 | - max-age=631138519 52 | x-access-level: 53 | - read-write-directmessages 54 | x-connection-hash: 55 | - 23ef2eff67bf3d377021ea3131d4cd9c 56 | x-content-type-options: 57 | - nosniff 58 | x-frame-options: 59 | - SAMEORIGIN 60 | x-rate-limit-limit: 61 | - '15' 62 | x-rate-limit-remaining: 63 | - '14' 64 | x-rate-limit-reset: 65 | - '1613173443' 66 | x-response-time: 67 | - '20' 68 | x-transaction: 69 | - 00a626be0047d878 70 | x-twitter-response-tags: 71 | - BouncerCompliant 72 | x-xss-protection: 73 | - '0' 74 | status: 75 | code: 200 76 | message: OK 77 | - request: 78 | body: null 79 | headers: 80 | Accept: 81 | - '*/*' 82 | Accept-Encoding: 83 | - gzip, deflate 84 | Connection: 85 | - keep-alive 86 | Cookie: 87 | - guest_id=v1%3A161317254375268593; personalization_id="v1_CKqJzwqWaovePh6gjmaeYQ=="; 88 | lang=en 89 | User-Agent: 90 | - python-requests/2.25.1 91 | method: GET 92 | uri: https://api.twitter.com/1.1/followers/ids.json?cursor=-1&screen_name=TweepyDev 93 | response: 94 | body: 95 | string: !!binary | 96 | H4sIAAAAAAAAAKpWykwpVrKKNjQ1NzeyNLfQMTQxNTY2szQzitVRykutKIlPLi0qzi9SsjJA4ccX 97 | lwDFlAyUdJQKilLLMvNLi5FVookhqS7JL0nMiU/OL80rUbLKK83JqQUAAAD//wMADfXWf4MAAAA= 98 | headers: 99 | cache-control: 100 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 101 | content-disposition: 102 | - attachment; filename=json.json 103 | content-encoding: 104 | - gzip 105 | content-length: 106 | - '113' 107 | content-type: 108 | - application/json;charset=utf-8 109 | date: 110 | - Fri, 12 Feb 2021 23:29:04 GMT 111 | expires: 112 | - Tue, 31 Mar 1981 05:00:00 GMT 113 | last-modified: 114 | - Fri, 12 Feb 2021 23:29:04 GMT 115 | pragma: 116 | - no-cache 117 | server: 118 | - tsa_b 119 | status: 120 | - 200 OK 121 | strict-transport-security: 122 | - max-age=631138519 123 | x-access-level: 124 | - read-write-directmessages 125 | x-connection-hash: 126 | - 5447ba711d0e8cb655f9b9ba810fe801 127 | x-content-type-options: 128 | - nosniff 129 | x-frame-options: 130 | - SAMEORIGIN 131 | x-rate-limit-limit: 132 | - '15' 133 | x-rate-limit-remaining: 134 | - '11' 135 | x-rate-limit-reset: 136 | - '1613173286' 137 | x-response-time: 138 | - '23' 139 | x-transaction: 140 | - 00ae59170027f80f 141 | x-twitter-response-tags: 142 | - BouncerCompliant 143 | x-xss-protection: 144 | - '0' 145 | status: 146 | code: 200 147 | message: OK 148 | version: 1 149 | -------------------------------------------------------------------------------- /cassettes/testcursorcursorpages.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.25.1 13 | method: GET 14 | uri: https://api.twitter.com/1.1/friends/ids.json?cursor=-1 15 | response: 16 | body: 17 | string: !!binary | 18 | H4sIAAAAAAAAAFzKwQrCMAyA4XfJOYcmTZNmryIyZO4wGKusrQjDd9fj9Ph//Acs9wrDJSdTd1dn 19 | ja5CSmhZxQkpmbFbRpIUo7oyWgpROagIqzFxJlROkTMjs4i7uKQrwja/2jj1vZYdhvDTY21fgwAI 20 | j31+LqXX8/lnp7uVdlvHqfStwbD1dX1/AAAA//8DACleTB7DAAAA 21 | headers: 22 | cache-control: 23 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 24 | content-disposition: 25 | - attachment; filename=json.json 26 | content-encoding: 27 | - gzip 28 | content-length: 29 | - '153' 30 | content-type: 31 | - application/json;charset=utf-8 32 | date: 33 | - Fri, 12 Feb 2021 23:29:04 GMT 34 | expires: 35 | - Tue, 31 Mar 1981 05:00:00 GMT 36 | last-modified: 37 | - Fri, 12 Feb 2021 23:29:04 GMT 38 | pragma: 39 | - no-cache 40 | server: 41 | - tsa_b 42 | set-cookie: 43 | - personalization_id="v1_FXShy2e47TeUuAHnSV6pFw=="; Max-Age=63072000; Expires=Sun, 44 | 12 Feb 2023 23:29:04 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 45 | - lang=en; Path=/ 46 | - guest_id=v1%3A161317254433441601; Max-Age=63072000; Expires=Sun, 12 Feb 2023 47 | 23:29:04 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 48 | status: 49 | - 200 OK 50 | strict-transport-security: 51 | - max-age=631138519 52 | x-access-level: 53 | - read-write-directmessages 54 | x-connection-hash: 55 | - 20dbf735aab4e501dfdb0e5a3ee667c8 56 | x-content-type-options: 57 | - nosniff 58 | x-frame-options: 59 | - SAMEORIGIN 60 | x-rate-limit-limit: 61 | - '15' 62 | x-rate-limit-remaining: 63 | - '13' 64 | x-rate-limit-reset: 65 | - '1613173443' 66 | x-response-time: 67 | - '16' 68 | x-transaction: 69 | - 00722e2a00c9bc1f 70 | x-twitter-response-tags: 71 | - BouncerCompliant 72 | x-xss-protection: 73 | - '0' 74 | status: 75 | code: 200 76 | message: OK 77 | - request: 78 | body: null 79 | headers: 80 | Accept: 81 | - '*/*' 82 | Accept-Encoding: 83 | - gzip, deflate 84 | Connection: 85 | - keep-alive 86 | Cookie: 87 | - guest_id=v1%3A161317254433441601; personalization_id="v1_FXShy2e47TeUuAHnSV6pFw=="; 88 | lang=en 89 | User-Agent: 90 | - python-requests/2.25.1 91 | method: GET 92 | uri: https://api.twitter.com/1.1/followers/ids.json?cursor=-1&screen_name=TweepyDev 93 | response: 94 | body: 95 | string: !!binary | 96 | H4sIAAAAAAAAAKpWykwpVrKKNjQ1NzeyNLfQMTQxNTY2szQzitVRykutKIlPLi0qzi9SsjJA4ccX 97 | lwDFlAyUdJQKilLLMvNLi5FVookhqS7JL0nMiU/OL80rUbLKK83JqQUAAAD//wMADfXWf4MAAAA= 98 | headers: 99 | cache-control: 100 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 101 | content-disposition: 102 | - attachment; filename=json.json 103 | content-encoding: 104 | - gzip 105 | content-length: 106 | - '113' 107 | content-type: 108 | - application/json;charset=utf-8 109 | date: 110 | - Fri, 12 Feb 2021 23:29:04 GMT 111 | expires: 112 | - Tue, 31 Mar 1981 05:00:00 GMT 113 | last-modified: 114 | - Fri, 12 Feb 2021 23:29:04 GMT 115 | pragma: 116 | - no-cache 117 | server: 118 | - tsa_b 119 | status: 120 | - 200 OK 121 | strict-transport-security: 122 | - max-age=631138519 123 | x-access-level: 124 | - read-write-directmessages 125 | x-connection-hash: 126 | - c8fbfddf5b3c4a55a3a077b4b79a6f26 127 | x-content-type-options: 128 | - nosniff 129 | x-frame-options: 130 | - SAMEORIGIN 131 | x-rate-limit-limit: 132 | - '15' 133 | x-rate-limit-remaining: 134 | - '10' 135 | x-rate-limit-reset: 136 | - '1613173286' 137 | x-response-time: 138 | - '22' 139 | x-transaction: 140 | - 00b656fd007b3ff5 141 | x-twitter-response-tags: 142 | - BouncerCompliant 143 | x-xss-protection: 144 | - '0' 145 | status: 146 | code: 200 147 | message: OK 148 | version: 1 149 | -------------------------------------------------------------------------------- /docs/locale/ko/LC_MESSAGES/cursor_tutorial.po: -------------------------------------------------------------------------------- 1 | # Cursor Tutorial 2 | # Copyright (C) 2009-2020, Joshua Roesslein 3 | # This file is distributed under the same license as the Tweepy package. 4 | # 악동분홍토끼 , 2019. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Tweepy-ko\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-12-28 09:41+0900\n" 12 | "PO-Revision-Date: 2019-12-28 11:26+0900\n" 13 | "Last-Translator: 악동분홍토끼 \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.7.0\n" 19 | 20 | #: ../../cursor_tutorial.rst:5 21 | msgid "Cursor Tutorial" 22 | msgstr "커서(Cursor) 지침" 23 | 24 | #: ../../cursor_tutorial.rst:7 25 | msgid "This tutorial describes details on pagination with Cursor objects." 26 | msgstr "이 지침은 커서 객체를 이용한 페이징에 대한 세부 사항을 설명합니다." 27 | 28 | #: ../../cursor_tutorial.rst:10 29 | msgid "Introduction" 30 | msgstr "들어가며" 31 | 32 | #: ../../cursor_tutorial.rst:12 33 | msgid "" 34 | "We use pagination a lot in Twitter API development. Iterating through " 35 | "timelines, user lists, direct messages, etc. In order to perform " 36 | "pagination, we must supply a page/cursor parameter with each of our " 37 | "requests. The problem here is this requires a lot of boiler plate code " 38 | "just to manage the pagination loop. To help make pagination easier and " 39 | "require less code, Tweepy has the Cursor object." 40 | msgstr "" 41 | "트위터 API를 이용하는 개발에서, 페이징은 타임라인 반복, 사용자 목록, 쪽지, 그 외 여러 곳에서 자주 사용됩니다. " 42 | "페이징을 수행하기 위해서는, 각 요청마다 페이지/커서 매개변수를 전달해야 합니다. " 43 | "문제는, 페이징 루프를 관리하기 위해서는 많은 양의 표준 코드가 필요하다는 점입니다. " 44 | "Tweepy는 이런 페이징을 더 쉽고 적은 코드로 돕기 위해 커서 객체를 가지고 있습니다." 45 | 46 | #: ../../cursor_tutorial.rst:20 47 | msgid "Old way vs Cursor way" 48 | msgstr "기존 방법 vs 커서 방법" 49 | 50 | #: ../../cursor_tutorial.rst:22 51 | msgid "" 52 | "First let's demonstrate iterating the statuses in the authenticated " 53 | "user's timeline. Here is how we would do it the \"old way\" before the " 54 | "Cursor object was introduced::" 55 | msgstr "" 56 | "먼저, 인증된 사용자의 타임라인 내에서 status를 반복하는 방법을 구현해봅시다. " 57 | "커서 객체가 도입되기 전에 사용하던 \"기존 방법\"은 다음과 같습니다:: " 58 | 59 | #: ../../cursor_tutorial.rst:38 60 | msgid "" 61 | "As you can see, we must manage the \"page\" parameter manually in our " 62 | "pagination loop. Now here is the version of the code using the Cursor " 63 | "object::" 64 | msgstr "" 65 | "위 코드에서도 볼 수 있듯, 기존 방법은 페이징 루프마다 "page" 매개변수를 수동으로 관리해야 합니다. " 66 | "다음은 Tweepy의 커서 객체를 사용하는 예시 코드입니다:: " 67 | 68 | #: ../../cursor_tutorial.rst:46 69 | msgid "" 70 | "Now that looks much better! Cursor handles all the pagination work for us" 71 | " behind the scenes, so our code can now focus entirely on processing the " 72 | "results." 73 | msgstr "" 74 | "훨씬 나아 보이는군요! " 75 | "커서가 보이지 않는 곳에서 모든 페이징 작업을 처리해 주므로, " 76 | "우리는 결과 처리를 위한 코드에만 집중 할 수 있겠습니다." 77 | 78 | #: ../../cursor_tutorial.rst:51 79 | msgid "Passing parameters into the API method" 80 | msgstr "API 메소드로 매개변수 전달하기" 81 | 82 | #: ../../cursor_tutorial.rst:53 83 | msgid "What if you need to pass in parameters to the API method?" 84 | msgstr "API 메소드로 매개변수를 전달해야 한다면 어떻게 하시겠습니까?" 85 | 86 | #: ../../cursor_tutorial.rst:59 87 | msgid "" 88 | "Since we pass Cursor the callable, we can not pass the parameters " 89 | "directly into the method. Instead we pass the parameters into the Cursor " 90 | "constructor method::" 91 | msgstr "" 92 | "커서를 호출 가능으로 전달했기 때문에, 메소드에 직접적으로 매개변수를 전달 할 수 없습니다." 93 | "대신, 다음과 같이 커서 생성자 메소드로 매개변수를 전달합니다::" 94 | 95 | #: ../../cursor_tutorial.rst:65 96 | msgid "" 97 | "Now Cursor will pass the parameter into the method for us whenever it " 98 | "makes a request." 99 | msgstr "" 100 | "이제 커서는 요청을 받으면 메소드에 매개변수를 전달해 줄 것입니다." 101 | 102 | #: ../../cursor_tutorial.rst:69 103 | msgid "Items or Pages" 104 | msgstr "항목과 페이지" 105 | 106 | #: ../../cursor_tutorial.rst:71 107 | msgid "" 108 | "So far we have just demonstrated pagination iterating per item. What if " 109 | "instead you want to process per page of results? You would use the " 110 | "pages() method::" 111 | msgstr "" 112 | "지금까지 항목당 페이징을 반복하는 방법을 구현해보았습니다. " 113 | "페이지별로 결과를 처리하려면 어떻게 해야 할까요? " 114 | "이럴 때는 pages() 메소드를 사용해보세요::" 115 | 116 | #: ../../cursor_tutorial.rst:81 117 | msgid "Limits" 118 | msgstr "제한" 119 | 120 | #: ../../cursor_tutorial.rst:83 121 | msgid "" 122 | "What if you only want n items or pages returned? You pass into the " 123 | "items() or pages() methods the limit you want to impose." 124 | msgstr "" 125 | "일정 갯수의 항목이나 페이지만 반환받기를 원한다면 어떻게 해야 할까요? " 126 | "아래와 같이, items()나 pages() 메소드에 값을 전달하는 것으로 이를 설정 할 수 있습니다." 127 | 128 | -------------------------------------------------------------------------------- /cassettes/testretweeters.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/statuses/retweeters/ids.json?id=266367358078169089", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:49 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "de4559ae15b878b96f109142a2b961c7" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "75" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "70" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:49 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_5/g17dMDiaNkyKDhXxW6RA==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:49 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486862027077; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:49 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "007ed7b8009bde71" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "842" 85 | ], 86 | "x-response-time": [ 87 | "510" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984896" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"ids\":[2390428970,2392623769,2298388902,2218822532,2294010576,2298665443,2295646148,586229030,1377248521,1360513801,1362034669,1458196202,1231476126,1183016636,188937623,1156915609,1206961129,396722289,755584388,838471987,1032012109,892834280,601235246,569558338,451776898,532092216,831618858,712469083,562549745,942200450,184662037,620862766,899643482,16482751,605168279,955312028,957010932,856105045,948683221,946377140,848197370,60412483,877388418,942234878,943352540,941760217,393226522,104938500,940243159,794327168,913965085,938792107,547911317,545004607,937135218,932267131,936550507,832543117,297861279,532505398,828583268,17916539,56933470,36912323,30592580,835617390,548741348,760957819,824311056,934584805,517135684,292837239],\"next_cursor\":0,\"next_cursor_str\":\"0\",\"previous_cursor\":0,\"previous_cursor_str\":\"0\",\"total_count\":null}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testfollowers.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.25.1 13 | method: GET 14 | uri: https://api.twitter.com/1.1/followers/list.json?screen_name=TweepyDev 15 | response: 16 | body: 17 | string: !!binary | 18 | H4sIAAAAAAAAAORXbW/bOAz+K4K/3IcLEr87NnDAsrbbbm8otgLDsAyGbCuOVlvKJDlZNvS/H2U7 19 | TpyXtts63HDXDWhNUhJFPSQffjMqSYQ0og/fDJoZkeUFgR0G4wF8xVIJIzI2ImNgMFwSkDwnUtIU 20 | o6dYSM5ALlNBCItb9adGnW+0BU+xovBnZDwWnF8XazZAr9+DJiOwki5a5TlZkoIviECTbKnXEPTo 21 | akWVImKILtdqzhlaCJ4LXJZahF5zRTIE8iqfK1QQnMFaMFqSHDMkGU6v5RC9wtdEorICnxCW6FXr 22 | /ER7NwQnKlHA4XOlFjKajqYjNUz5dBRwO/P55EWhb0CYoooSCNO3xrz+1UTtPsu/LDDLSBZvbWvT 23 | nKp5lYB9OR21Me1CmlG5KPC6XXKbJWUZTbVvH8yB7Xy8+XizF9jO2Y83oIIIKpJC4IxohgtJBsaM 24 | FwVfAQrilFdMGZHjhC6IBSUs64S2HzjwllTC0o3Msr2BAW+PtQyDwHgiKJpUOTLHyLQjy49sB/1p 25 | wg+yTVNDaIaXvBJUkW5jK7RDC55BpTGfzSQBEauKYmAoWpL4K2dkI8gJjwnDSaGdV6IC35dE0Bnd 26 | uYxUWAGku939sWWD25jlm10aCx2WnudXFUFPSILMENlWZFqRZ3ae25aOs04Pxwtt17fc0A18Mxyb 27 | nrObKYdaWKfIF73/I07nuPyK0XteoTleEiQ5oDKdI4ULAJiGItyJadxvr7OLvDmWc4Xz+iXhGusy 28 | 4UX7oZM4LrUxZw0s+ynZnr1N4WftZ32pwHdtzw3t3atsZHsAGwO+Bls8gRvwmqnecVqZpgN5NRdk 29 | 9td0JyFKntCCDFWbyRrDBhKkACPGG+xNm9WkzXb0Dt5hslg0W05HuNHWrsSCLIp1rHjcvGPcPYsd 30 | +o49DseOD/i1T9juvlR/wd7udUT3o3OoPxqunXNPvAIgeQPHlHORUQavLjciSPyUbPVMCZpUiovO 31 | gMr4cwVpHG+w3KJFELUiRG2wbzbpprOtS+2tKNtftiNp8gXQZ9z0PdjmX2sJriiBmSwwqI9JAZEH 32 | i6AGzQATcQIVOhfgma4ohV5vmPWPcdSGljgn+0UUJ1Jjq8yb6ljbSCjDc1J2v/3pKMmHOZ3dsW9c 33 | o7ZXzh9gewXfB1c/epdF/7CeLRwGqA0s1w58PwTEAubCcDqqllcJvZrImHFR4mL4aZEbR445crWH 34 | Py3BjEFa7LfEEwc11vqklmHov2zT8Vy3bhUbu4Ky6w4fTyaB61zsaCXNSIJFnEAawdkncbSxg4/i 35 | tJUu1qe1kPQHuOleFspzDKtJ3enbFZ0yIzNcFeou+d6WTXWkOhebdlfQJYl3pD07KDufKyJVLIlO 36 | 9VbHuIIO2TCwbaUoK7W7QQIc7fpAAPdI1p1om+axWi90PWO6N98MWuboeo7jh36vi2xku41HlEc4 37 | YyMOvHGfLx5wxJN8bXJ+dvnm6WW4VD/E1/rLD/laa7vTw0ady5dVUtD0gLPdYfzjtK2BwhHWdsDZ 38 | PN/bp2zm4JD2vMJrZI3hf+RYkeN1tMcyjxI217W/n661ILqTr9Wt+x507S1W6DmwfNNGlhlZFnDN 39 | Y3QNAGhZfmh5YzcY+67j9ujagXZL195coUfv/n5zcR6hl4SgZ3W08foPiWBAAeKNC6rWaAUDBbRH 40 | qUPMFJpxgTKSwhwipxXEz4Ur0QJRpQ3ZH2qIruYEJQJTJlFC5oABRBmAhBGFJEl1nNdoRkWJzgpe 41 | ZbMCC7CHHUu9n+3/So5Y33abqJvPNo5u6Fn92GlJD8fOIPwt2WHLmU7TwUODjv2dVJ1a2Ytplwc/ 42 | Q/Y6dhafSIV3MARPFgJZHrKcyAkg7bepYG6e0PZM1xqb8M8OgfQGXq9QH2q3qfBv4b+BPNov1fnL 43 | 19mzMlwT1s+Gpiz+YDJsYXuqQ/SOvV+HoNPRiiTTUfNuwG6ORvmWrnGwQVcFtllnWcHAcs2mgdyW 44 | b7V/q9VqKHlKcTGD97w13d7WZk+05H+Safccq3Tm7M9Vlhfeb7JacClpAjcAkiYBqUtyfOj6CV/M 45 | 333Gczw7qFH8kDOe92tnvDu2f7gZzwxtz7fNsR+MXds0A9OdjmafXpznj4Prh5/xvv+03jRmPQ69 46 | i2NT1t40BvXu3Ds2tfWmsTA8O3OcU9OYc+G6lnfnNNa0gePDWKP7D89i0MZYHbMKerSoS8HOd9vs 47 | m/ciS8qhMu9Y7sl2rBVXuNiUF10rb/4BAAD//wMA87DgKzgYAAA= 48 | headers: 49 | cache-control: 50 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 51 | content-disposition: 52 | - attachment; filename=json.json 53 | content-encoding: 54 | - gzip 55 | content-length: 56 | - '1691' 57 | content-type: 58 | - application/json;charset=utf-8 59 | date: 60 | - Fri, 12 Feb 2021 23:43:24 GMT 61 | expires: 62 | - Tue, 31 Mar 1981 05:00:00 GMT 63 | last-modified: 64 | - Fri, 12 Feb 2021 23:43:24 GMT 65 | pragma: 66 | - no-cache 67 | server: 68 | - tsa_b 69 | set-cookie: 70 | - personalization_id="v1_LvMjRAWaCJiezI8izgPZWw=="; Max-Age=63072000; Expires=Sun, 71 | 12 Feb 2023 23:43:24 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 72 | - lang=en; Path=/ 73 | - guest_id=v1%3A161317340462563794; Max-Age=63072000; Expires=Sun, 12 Feb 2023 74 | 23:43:24 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 75 | status: 76 | - 200 OK 77 | strict-transport-security: 78 | - max-age=631138519 79 | x-access-level: 80 | - read-write-directmessages 81 | x-connection-hash: 82 | - 966f4e140e26afab3d0e11f88e7d36e4 83 | x-content-type-options: 84 | - nosniff 85 | x-frame-options: 86 | - SAMEORIGIN 87 | x-rate-limit-limit: 88 | - '15' 89 | x-rate-limit-remaining: 90 | - '14' 91 | x-rate-limit-reset: 92 | - '1613174304' 93 | x-response-time: 94 | - '61' 95 | x-transaction: 96 | - 00c6f5e700d2b4ce 97 | x-twitter-response-tags: 98 | - BouncerCompliant 99 | x-xss-protection: 100 | - '0' 101 | status: 102 | code: 200 103 | message: OK 104 | version: 1 105 | -------------------------------------------------------------------------------- /docs/locale/pl/LC_MESSAGES/cursor_tutorial.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2020, Joshua Roesslein 2 | # This file is distributed under the same license as the Tweepy package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Tweepy-pl\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2020-01-14 14:12+0100\n" 8 | "PO-Revision-Date: 2020-01-19 09:28+0100\n" 9 | "Last-Translator: krzysztofturtle \n" 10 | "Language: pl\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " 13 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Generated-By: Babel 2.8.0\n" 18 | "X-Generator: POEditor.com\n" 19 | 20 | #: ../../cursor_tutorial.rst:5 21 | msgid "Cursor Tutorial" 22 | msgstr "Poradnik na temat kursorów" 23 | 24 | #: ../../cursor_tutorial.rst:7 25 | msgid "This tutorial describes details on pagination with Cursor objects." 26 | msgstr "Ten poradnik wyjaśnia szczegóły dotyczące stronnicowania używając obiektów kursora." 27 | 28 | #: ../../cursor_tutorial.rst:10 29 | msgid "Introduction" 30 | msgstr "Wprowadzenie" 31 | 32 | #: ../../cursor_tutorial.rst:12 33 | msgid "We use pagination a lot in Twitter API development. Iterating through timelines, user lists, direct messages, etc. In order to perform pagination, we must supply a page/cursor parameter with each of our requests. The problem here is this requires a lot of boiler plate code just to manage the pagination loop. To help make pagination easier and require less code, Tweepy has the Cursor object." 34 | msgstr "Stronnicowanie jest często używane w rozwoju Twitter API. Używa się go do iterowania osi czasu, listy użytkowników, bezpośrednich wiadomości itd. Aby wykonać stronnicowanie, musisz dostarczyć stronie lub kursorowi parametr z każdym ze swoich żądań. Problemem tego rozwiązania jest duża ilość boiler plate code wymaganego do zarządzania pętlą stronnicowania. Tweepy używa obiektu kursora by usprawnić stronnicowanie i zmniejszyć objętość kodu." 35 | 36 | #: ../../cursor_tutorial.rst:20 37 | msgid "Old way vs Cursor way" 38 | msgstr "Stary sposób vs sposób kursora" 39 | 40 | #: ../../cursor_tutorial.rst:22 41 | msgid "First let's demonstrate iterating the statuses in the authenticated user's timeline. Here is how we would do it the \"old way\" before the Cursor object was introduced::" 42 | msgstr "Na początku zademonstrujemy iterowanie statusów na osi czasu uwierzytelnionego użytkownika. W taki sposób robiło się to \"starą metodą\" zanim wprowadzony został obiekt kursora." 43 | 44 | #: ../../cursor_tutorial.rst:38 45 | msgid "As you can see, we must manage the \"page\" parameter manually in our pagination loop. Now here is the version of the code using the Cursor object::" 46 | msgstr "Jak widać musimy manualnie zarządzać parametrem \"page\" w naszej pętli stronnicowania. A teraz zaprezentujemy wersje kodu, która używa obiektu kursora:" 47 | 48 | #: ../../cursor_tutorial.rst:46 49 | msgid "Now that looks much better! Cursor handles all the pagination work for us behind the scenes, so our code can now focus entirely on processing the results." 50 | msgstr "Wygląda to dużo lepiej! Kursor załatwia za nas całą sprawę stronnicowania, co pozwala nam skupić się wyłącznie na przetwarzaniu rezultatów." 51 | 52 | #: ../../cursor_tutorial.rst:51 53 | msgid "Passing parameters into the API method" 54 | msgstr "Przekazywanie parametrów do metody API" 55 | 56 | #: ../../cursor_tutorial.rst:53 57 | msgid "What if you need to pass in parameters to the API method?" 58 | msgstr "Co zrobić jeżeli muszę przekazać parametry do metody API?" 59 | 60 | #: ../../cursor_tutorial.rst:59 61 | msgid "Since we pass Cursor the callable, we can not pass the parameters directly into the method. Instead we pass the parameters into the Cursor constructor method::" 62 | msgstr "Jako że przekazujesz kursorowi obiekt wywoływany, nie możesz przekazać parametrów prosto do metody. Zamiast tego parametry są przekazywane do metody konstruktora kursora::" 63 | 64 | #: ../../cursor_tutorial.rst:65 65 | msgid "Now Cursor will pass the parameter into the method for us whenever it makes a request." 66 | msgstr "Kursor przekaże parametry do metody gdy tylko stworzy żądanie." 67 | 68 | #: ../../cursor_tutorial.rst:69 69 | msgid "Items or Pages" 70 | msgstr "Obiekty czy strony" 71 | 72 | #: ../../cursor_tutorial.rst:71 73 | msgid "So far we have just demonstrated pagination iterating per item. What if instead you want to process per page of results? You would use the pages() method::" 74 | msgstr "Do tej pory zademonstrowaliśmy iteracje stronnicowania dla obiektu. Co jeżeli zamiast tego chcesz przetworzyć rezultaty dla strony? Użyj do tego metody pages()::" 75 | 76 | #: ../../cursor_tutorial.rst:81 77 | msgid "Limits" 78 | msgstr "Limity" 79 | 80 | #: ../../cursor_tutorial.rst:83 81 | msgid "What if you only want n items or pages returned? You pass into the items() or pages() methods the limit you want to impose." 82 | msgstr "Co jeżeli chcesz by zwrócone zostało n obiektów lub stron? Musisz wtedy przekazać do metod items() lub pages() limit, który chcesz nałożyć." 83 | 84 | -------------------------------------------------------------------------------- /tests/test_rate_limit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from .config import create_auth 5 | from tweepy import API 6 | from tweepy.errors import HTTPException 7 | 8 | testratelimit = 'TEST_RATE_LIMIT' in os.environ 9 | 10 | 11 | @unittest.skipIf(not testratelimit, "skipping rate limiting test since testratelimit is not specified") 12 | class TweepyRateLimitTests(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.api = API(create_auth()) 16 | self.api.retry_count = 2 17 | self.api.retry_delay = 5 18 | self.api.retry_errors = {401, 404, 503} 19 | self.api.wait_on_rate_limit = True 20 | 21 | def testratelimit(self): 22 | # should cause the api to sleep 23 | test_user_ids = [123796151, 263168076, 990027860, 901955678, 214630268, 18305040, 36126818, 312483939, 426975332, 469837158, 1104126054, 1342066705, 281632872, 608977002, 242901099, 846643308, 1166401645, 153886833, 95314037, 314458230, 149856382, 287916159, 472506496, 267180736, 251764866, 351035524, 997113991, 445915272, 57335947, 251043981, 95051918, 200761489, 48341139, 972660884, 422330517, 326429297, 864927896, 94183577, 95887514, 220807325, 194330782, 58796741, 1039212709, 1017192614, 625828008, 66539548, 320566383, 309829806, 571383983, 382694863, 439140530, 93977882, 277651636, 19984414, 502004733, 1093673143, 60014776, 469849460, 937107642, 155516395, 1272979644, 617433802, 102212981, 301228831, 805784562, 427799926, 322298054, 162197537, 554001783, 89252046, 536789199, 177807568, 805044434, 495541739, 392904916, 154656981, 291266775, 865454102, 475846642, 56910044, 55834550, 177389790, 339841061, 319614526, 954529597, 595960038, 501301480, 15679722, 938090731, 495829228, 325034224, 1041031410, 18882803, 161080540, 456245496, 636854521, 811974907, 222085372, 222306563, 422846724, 281616645, 223641862, 705786134, 1038901512, 174211339, 426795277, 370259272, 34759594, 366410456, 320577812, 757211413, 483238166, 222624369, 29425605, 456455726, 408723740, 1274608346, 295837985, 273490210, 232497444, 726843685, 465232166, 18850087, 22503721, 259629354, 414250375, 1259941938, 777167150, 1080552157, 1271036282, 1000551816, 109443357, 345781858, 45113654, 406536508, 253801866, 98836799, 395469120, 252920129, 604660035, 69124420, 283459909, 482261729, 377767308, 565240139, 191788429, 102048080, 330054371, 527868245, 177044049, 1250978114, 424042840, 15810905, 389030234, 69324415, 15638877, 159080798, 378708319, 549183840, 1034658145, 629924195, 969130340, 1143593845, 188129639, 535863656, 552452458, 1325277547, 756236624, 48421608, 178495858, 566206836, 378519925, 22678249, 377659768, 102326650, 76783997, 440716178, 49062271, 26296705, 1328036587, 289644932, 305767830, 437305735, 124821901, 591735533, 155140501, 1099612568, 631398810, 469295515, 131350941, 325804447, 529801632, 977197808, 232613818, 614777251, 229261732, 255533478, 256942503, 169583016, 237860252, 29257799, 276668845, 871571886, 398162507, 451954078, 526016951, 285655480, 1281827257, 340042172, 146653629, 61055423, 33407417, 95582321, 237420995, 310960580, 1222064886, 16490950, 60924360, 81928649, 374424010, 45703629, 817455571, 336077264, 400268024, 1203200467, 457105876, 232309205, 45838026, 91972056, 226927065, 82125276, 760131962, 1032274398, 562552291, 155155166, 146464315, 864864355, 128655844, 589747622, 293290470, 192004584, 19100402, 133931498, 19775979, 446374381, 1175241198, 20128240, 332395944, 74575955, 247407092, 427794934, 329823657, 405742072, 497475320, 997384698, 147718652, 757768705, 96757163, 289874437, 29892071, 568541704, 297039276, 356590090, 502055438, 291826323, 238944785, 71483924, 50031538, 863355416, 120273668, 224403994, 14880858, 1241506364, 848962080, 57898416, 599695908, 1222132262, 54045447, 907207212, 851412402, 454418991, 231844616, 618447410, 602997300, 447685173, 19681556, 22233657, 509901138, 184705596, 307624714, 553017923, 1249878596, 33727045, 419873350, 789307489, 287531592, 399163977, 1069425228, 920789582, 136891149, 134857296, 358558478, 436855382, 963011161, 195764827, 548872797, 1058980446, 442376799, 578216544, 527147110, 122077799, 1004773993, 420332138, 514994279, 61530732, 133462802, 19513966, 1286972018, 786121332, 265863798, 221258362, 42656382, 43631231, 198264256, 944382595, 37387030, 260948614, 314406408, 296512982, 92830743, 24519306, 21070476, 454107789, 331006606, 939713168, 256197265, 30065299, 74774188, 1332842606, 289424023, 526992024, 429933209, 116384410, 762143389, 308093598, 421208736, 454943394, 66026267, 158851748, 257550092, 70697073, 903627432, 290669225, 121168557, 92994330, 67642033, 635183794, 499303091, 421205146, 1252648171, 375268025, 16281866, 211960508, 267179466, 129016511, 157172416, 373370004, 167781059, 43624522] 24 | for user_id in test_user_ids: 25 | try: 26 | self.api.user_timeline(user_id=user_id, count=1, include_rts=True) 27 | except HTTPException as e: 28 | # continue if we're not autherized to access the user's timeline or user doesn't exist anymore 29 | if e.response.status_code in (401, 404): 30 | continue 31 | raise e 32 | 33 | 34 | if __name__ == '__main__': 35 | oauth_consumer_key = os.environ.get('CONSUMER_KEY', '') 36 | if testratelimit: 37 | unittest.TextTestRunner().run(unittest.loader.makeSuite(TweepyRateLimitTests)) 38 | else: 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /docs/streaming_how_to.rst: -------------------------------------------------------------------------------- 1 | .. _streaming_how_to: 2 | .. _Twitter Streaming API Documentation: https://developer.twitter.com/en/docs/tweets/filter-realtime/overview 3 | .. _Twitter Streaming API Connecting Documentation: https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data 4 | .. _Twitter Response Codes Documentation: https://dev.twitter.com/overview/api/response-codes 5 | 6 | ********************* 7 | Streaming With Tweepy 8 | ********************* 9 | Tweepy makes it easier to use the twitter streaming api by handling authentication, 10 | connection, creating and destroying the session, reading incoming messages, 11 | and partially routing messages. 12 | 13 | This page aims to help you get started using Twitter streams with Tweepy 14 | by offering a first walk through. Some features of Tweepy streaming are 15 | not covered here. See streaming.py in the Tweepy source code. 16 | 17 | API authorization is required to access Twitter streams. 18 | Follow the :ref:`auth_tutorial` if you need help with authentication. 19 | 20 | Summary 21 | ======= 22 | The Twitter streaming API is used to download twitter messages in real 23 | time. It is useful for obtaining a high volume of tweets, or for 24 | creating a live feed using a site stream or user stream. 25 | See the `Twitter Streaming API Documentation`_. 26 | 27 | The streaming api is quite different from the REST api because the 28 | REST api is used to *pull* data from twitter but the streaming api 29 | *pushes* messages to a persistent session. This allows the streaming 30 | api to download more data in real time than could be done using the 31 | REST API. 32 | 33 | In Tweepy, an instance of **tweepy.Stream** establishes a streaming 34 | session and routes messages to **StreamListener** instance. The 35 | **on_data** method of a stream listener receives all messages and 36 | calls functions according to the message type. The default 37 | **StreamListener** can classify most common twitter messages and 38 | routes them to appropriately named methods, but these methods are 39 | only stubs. 40 | 41 | Therefore using the streaming api has three steps. 42 | 43 | 1. Create a class inheriting from **StreamListener** 44 | 45 | 2. Using that class create a **Stream** object 46 | 47 | 3. Connect to the Twitter API using the **Stream**. 48 | 49 | 50 | Step 1: Creating a **StreamListener** 51 | ===================================== 52 | This simple stream listener prints status text. 53 | The **on_data** method of Tweepy's **StreamListener** conveniently passes 54 | data from statuses to the **on_status** method. 55 | Create class **MyStreamListener** inheriting from **StreamListener** 56 | and overriding **on_status**.:: 57 | 58 | import tweepy 59 | #override tweepy.StreamListener to add logic to on_status 60 | class MyStreamListener(tweepy.StreamListener): 61 | 62 | def on_status(self, status): 63 | print(status.text) 64 | 65 | Step 2: Creating a **Stream** 66 | ============================= 67 | We need an api to stream. See :ref:`auth_tutorial` to learn how to get an api object. 68 | Once we have an api and a status listener we can create our stream object.:: 69 | 70 | myStreamListener = MyStreamListener() 71 | myStream = tweepy.Stream(auth = api.auth, listener=myStreamListener) 72 | 73 | Step 3: Starting a Stream 74 | ========================= 75 | A number of twitter streams are available through Tweepy. Most cases 76 | will use filter. 77 | For more information on the capabilities and limitations of the different 78 | streams see `Twitter Streaming API Documentation`_. 79 | 80 | In this example we will use **filter** to stream all tweets containing 81 | the word *python*. The **track** parameter is an array of search terms to stream. :: 82 | 83 | myStream.filter(track=['python']) 84 | 85 | This example shows how to use **filter** to stream tweets by a specific user. The **follow** parameter is an array of IDs. :: 86 | 87 | myStream.filter(follow=["2211149702"]) 88 | 89 | An easy way to find a single ID is to use one of the many conversion websites: search for 'what is my twitter ID'. 90 | 91 | A Few More Pointers 92 | =================== 93 | 94 | Async Streaming 95 | --------------- 96 | Streams do not terminate unless the connection is closed, blocking the thread. 97 | Tweepy offers a convenient **is_async** parameter on **filter** so the stream will run on a new 98 | thread. For example :: 99 | 100 | myStream.filter(track=['python'], is_async=True) 101 | 102 | Handling Errors 103 | --------------- 104 | When using Twitter's streaming API one must be careful of the dangers of 105 | rate limiting. If clients exceed a limited number of attempts to connect to the streaming API 106 | in a window of time, they will receive error 420. The amount of time a client has to wait after receiving error 420 107 | will increase exponentially each time they make a failed attempt. 108 | 109 | Tweepy's **Stream Listener** passes error codes to an **on_error** stub. The 110 | default implementation returns **False** for all codes, but we can override it 111 | to allow Tweepy to reconnect for some or all codes, using the backoff 112 | strategies recommended in the `Twitter Streaming API Connecting 113 | Documentation`_. :: 114 | 115 | class MyStreamListener(tweepy.StreamListener): 116 | 117 | def on_error(self, status_code): 118 | if status_code == 420: 119 | #returning False in on_error disconnects the stream 120 | return False 121 | 122 | # returning non-False reconnects the stream, with backoff. 123 | 124 | For more information on error codes from the Twitter API see `Twitter Response Codes Documentation`_. 125 | 126 | -------------------------------------------------------------------------------- /docs/auth_tutorial.rst: -------------------------------------------------------------------------------- 1 | .. _auth_tutorial: 2 | 3 | 4 | *********************** 5 | Authentication Tutorial 6 | *********************** 7 | 8 | Introduction 9 | ============ 10 | 11 | Tweepy supports both OAuth 1a (application-user) and OAuth 2 12 | (application-only) authentication. Authentication is handled by the 13 | tweepy.AuthHandler class. 14 | 15 | OAuth 1a Authentication 16 | ======================= 17 | 18 | Tweepy tries to make OAuth 1a as painless as possible for you. To begin 19 | the process we need to register our client application with 20 | Twitter. Create a new application and once you 21 | are done you should have your consumer key and secret. Keep these 22 | two handy, you'll need them. 23 | 24 | The next step is creating an OAuthHandler instance. Into this we pass 25 | our consumer key and secret which was given to us in the previous 26 | paragraph:: 27 | 28 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret) 29 | 30 | If you have a web application and are using a callback URL that needs 31 | to be supplied dynamically you would pass it in like so:: 32 | 33 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret, 34 | callback_url) 35 | 36 | If the callback URL will not be changing, it is best to just configure 37 | it statically on twitter.com when setting up your application's 38 | profile. 39 | 40 | Unlike basic auth, we must do the OAuth 1a "dance" before we can start 41 | using the API. We must complete the following steps: 42 | 43 | #. Get a request token from twitter 44 | 45 | #. Redirect user to twitter.com to authorize our application 46 | 47 | #. If using a callback, twitter will redirect the user to 48 | us. Otherwise the user must manually supply us with the verifier 49 | code. 50 | 51 | #. Exchange the authorized request token for an access token. 52 | 53 | So let's fetch our request token to begin the dance:: 54 | 55 | try: 56 | redirect_url = auth.get_authorization_url() 57 | except tweepy.TweepError: 58 | print('Error! Failed to get request token.') 59 | 60 | This call requests the token from twitter and returns to us the 61 | authorization URL where the user must be redirect to authorize us. Now 62 | if this is a desktop application we can just hang onto our 63 | OAuthHandler instance until the user returns back. In a web 64 | application we will be using a callback request. So we must store the 65 | request token in the session since we will need it inside the callback 66 | URL request. Here is a pseudo example of storing the request token in 67 | a session:: 68 | 69 | session.set('request_token', auth.request_token['oauth_token']) 70 | 71 | So now we can redirect the user to the URL returned to us earlier from 72 | the get_authorization_url() method. 73 | 74 | If this is a desktop application (or any application not using 75 | callbacks) we must query the user for the "verifier code" that twitter 76 | will supply them after they authorize us. Inside a web application 77 | this verifier value will be supplied in the callback request from 78 | twitter as a GET query parameter in the URL. 79 | 80 | .. code-block :: python 81 | 82 | # Example using callback (web app) 83 | verifier = request.GET.get('oauth_verifier') 84 | 85 | # Example w/o callback (desktop) 86 | verifier = raw_input('Verifier:') 87 | 88 | The final step is exchanging the request token for an access 89 | token. The access token is the "key" for opening the Twitter API 90 | treasure box. To fetch this token we do the following:: 91 | 92 | # Let's say this is a web app, so we need to re-build the auth handler 93 | # first... 94 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret) 95 | token = session.get('request_token') 96 | session.delete('request_token') 97 | auth.request_token = { 'oauth_token' : token, 98 | 'oauth_token_secret' : verifier } 99 | 100 | try: 101 | auth.get_access_token(verifier) 102 | except tweepy.TweepError: 103 | print('Error! Failed to get access token.') 104 | 105 | It is a good idea to save the access token for later use. You do not 106 | need to re-fetch it each time. Twitter currently does not expire the 107 | tokens, so the only time it would ever go invalid is if the user 108 | revokes our application access. To store the access token depends on 109 | your application. Basically you need to store 2 string values: key and 110 | secret:: 111 | 112 | auth.access_token 113 | auth.access_token_secret 114 | 115 | You can throw these into a database, file, or where ever you store 116 | your data. To re-build an OAuthHandler from this stored access token 117 | you would do this:: 118 | 119 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret) 120 | auth.set_access_token(key, secret) 121 | 122 | So now that we have our OAuthHandler equipped with an access token, we 123 | are ready for business:: 124 | 125 | api = tweepy.API(auth) 126 | api.update_status('tweepy + oauth!') 127 | 128 | OAuth 2 Authentication 129 | ====================== 130 | 131 | Tweepy also supports OAuth 2 authentication. OAuth 2 is a method of 132 | authentication where an application makes API requests without the 133 | user context. Use this method if you just need read-only access to 134 | public information. 135 | 136 | Like OAuth 1a, we first register our client application and acquire 137 | a consumer key and secret. 138 | 139 | Then we create an AppAuthHandler instance, passing in our consumer 140 | key and secret:: 141 | 142 | auth = tweepy.AppAuthHandler(consumer_key, consumer_secret) 143 | 144 | With the bearer token received, we are now ready for business:: 145 | 146 | api = tweepy.API(auth) 147 | for tweet in tweepy.Cursor(api.search, q='tweepy').items(10): 148 | print(tweet.text) -------------------------------------------------------------------------------- /cassettes/testlistsall.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/lists/list.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:44 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "1eca66533a589f2a75ad8f31ca15ba50" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "15" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "10" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:44 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_apEu/nMCl/CP0/7i7tY1Iw==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:44 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486446386097; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:44 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "001d1efc00965cca" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "1961" 85 | ], 86 | "x-response-time": [ 87 | "38" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984892" 91 | ] 92 | }, 93 | "body": { 94 | "string": "[{\"id\":1141904301733875713,\"id_str\":\"1141904301733875713\",\"name\":\"test\",\"uri\":\"\\/TweepyDev\\/lists\\/test\",\"subscriber_count\":0,\"member_count\":0,\"mode\":\"private\",\"description\":\"testing update_list\",\"slug\":\"test\",\"full_name\":\"@TweepyDev\\/test\",\"created_at\":\"Fri Jun 21 03:02:55 +0000 2019\",\"following\":true,\"user\":{\"id\":1072250532645998596,\"id_str\":\"1072250532645998596\",\"name\":\"Tweepy Testing\",\"screen_name\":\"TweepyDev\",\"location\":\"\",\"description\":\"Account used to test Tweepy\",\"url\":\"https:\\/\\/t.co\\/XRfax6xExn\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\\/\\/t.co\\/XRfax6xExn\",\"expanded_url\":\"https:\\/\\/github.com\\/tweepy\\/tweepy\",\"display_url\":\"github.com\\/tweepy\\/tweepy\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":1,\"friends_count\":6,\"listed_count\":0,\"created_at\":\"Mon Dec 10 22:03:43 +0000 2018\",\"favourites_count\":0,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":false,\"verified\":false,\"statuses_count\":10,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"000000\",\"profile_background_image_url\":\"http:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_image_url_https\":\"https:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\/\\/abs.twimg.com\\/sticky\\/default_profile_images\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\/\\/abs.twimg.com\\/sticky\\/default_profile_images\\/default_profile_normal.png\",\"profile_banner_url\":\"https:\\/\\/pbs.twimg.com\\/profile_banners\\/1072250532645998596\\/1562984835\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"000000\",\"profile_sidebar_fill_color\":\"000000\",\"profile_text_color\":\"000000\",\"profile_use_background_image\":false,\"has_extended_profile\":false,\"default_profile\":false,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\"}}]" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testgetlist.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/lists/show.json?owner_screen_name=Twitter&slug=Official-Twitter-Accounts", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:41 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "5cf1755c77e883bc5a3a11c0898b98df" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "75" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "68" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:41 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_lZPB6YLPBIV7R4nA+12o/A==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:41 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486191963552; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:41 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "0081c0cd00f9e38f" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "1993" 85 | ], 86 | "x-response-time": [ 87 | "50" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984890" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"id\":84839422,\"id_str\":\"84839422\",\"name\":\"Official Twitter Accounts\",\"uri\":\"\\/Twitter\\/lists\\/official-twitter-accounts\",\"subscriber_count\":763,\"member_count\":130,\"mode\":\"public\",\"description\":\"Accounts managed by Twitter, Inc.\",\"slug\":\"official-twitter-accounts\",\"full_name\":\"@Twitter\\/official-twitter-accounts\",\"created_at\":\"Tue Feb 05 18:14:21 +0000 2013\",\"following\":false,\"user\":{\"id\":783214,\"id_str\":\"783214\",\"name\":\"Twitter\",\"screen_name\":\"Twitter\",\"location\":\"Everywhere\",\"description\":\"What\\u2019s happening?!\",\"url\":\"https:\\/\\/t.co\\/TAXQpsHa5X\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\\/\\/t.co\\/TAXQpsHa5X\",\"expanded_url\":\"https:\\/\\/about.twitter.com\\/\",\"display_url\":\"about.twitter.com\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":56335782,\"friends_count\":140,\"listed_count\":90877,\"created_at\":\"Tue Feb 20 14:35:54 +0000 2007\",\"favourites_count\":5965,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":true,\"statuses_count\":10829,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"ACDED6\",\"profile_background_image_url\":\"http:\\/\\/abs.twimg.com\\/images\\/themes\\/theme18\\/bg.gif\",\"profile_background_image_url_https\":\"https:\\/\\/abs.twimg.com\\/images\\/themes\\/theme18\\/bg.gif\",\"profile_background_tile\":true,\"profile_image_url\":\"http:\\/\\/pbs.twimg.com\\/profile_images\\/1111729635610382336\\/_65QFl7B_normal.png\",\"profile_image_url_https\":\"https:\\/\\/pbs.twimg.com\\/profile_images\\/1111729635610382336\\/_65QFl7B_normal.png\",\"profile_banner_url\":\"https:\\/\\/pbs.twimg.com\\/profile_banners\\/783214\\/1556918042\",\"profile_link_color\":\"1B95E0\",\"profile_sidebar_border_color\":\"FFFFFF\",\"profile_sidebar_fill_color\":\"F6F6F6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":true,\"default_profile\":false,\"default_profile_image\":false,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"regular\"}}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testlistsownerships.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/lists/ownerships.json", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "x-access-level": [ 22 | "read-write-directmessages" 23 | ], 24 | "cache-control": [ 25 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 26 | ], 27 | "last-modified": [ 28 | "Sun, 10 Jan 2021 03:21:30 GMT" 29 | ], 30 | "x-connection-hash": [ 31 | "275177888e7ea9e2d52761388f7a3b06" 32 | ], 33 | "content-type": [ 34 | "application/json;charset=utf-8" 35 | ], 36 | "date": [ 37 | "Sun, 10 Jan 2021 03:21:30 GMT" 38 | ], 39 | "set-cookie": [ 40 | "personalization_id=\"v1_Mp6BeeB23lbF6kKSYjpsDA==\"; Max-Age=63072000; Expires=Tue, 10 Jan 2023 03:21:30 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None", 41 | "lang=en; Path=/", 42 | "guest_id=v1%3A161024889090374126; Max-Age=63072000; Expires=Tue, 10 Jan 2023 03:21:30 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None" 43 | ], 44 | "pragma": [ 45 | "no-cache" 46 | ], 47 | "x-transaction": [ 48 | "0028e2fa00852759" 49 | ], 50 | "x-rate-limit-reset": [ 51 | "1610249790" 52 | ], 53 | "x-content-type-options": [ 54 | "nosniff" 55 | ], 56 | "x-xss-protection": [ 57 | "0" 58 | ], 59 | "x-frame-options": [ 60 | "SAMEORIGIN" 61 | ], 62 | "expires": [ 63 | "Tue, 31 Mar 1981 05:00:00 GMT" 64 | ], 65 | "x-rate-limit-remaining": [ 66 | "14" 67 | ], 68 | "strict-transport-security": [ 69 | "max-age=631138519" 70 | ], 71 | "x-rate-limit-limit": [ 72 | "15" 73 | ], 74 | "server": [ 75 | "tsa_b" 76 | ], 77 | "status": [ 78 | "200 OK" 79 | ], 80 | "content-length": [ 81 | "2054" 82 | ], 83 | "x-response-time": [ 84 | "46" 85 | ], 86 | "content-disposition": [ 87 | "attachment; filename=json.json" 88 | ], 89 | "x-twitter-response-tags": [ 90 | "BouncerCompliant" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"next_cursor\":0,\"next_cursor_str\":\"0\",\"previous_cursor\":0,\"previous_cursor_str\":\"0\",\"lists\":[{\"id\":1141904301733875713,\"id_str\":\"1141904301733875713\",\"name\":\"test\",\"uri\":\"\\/TweepyDev\\/lists\\/test\",\"subscriber_count\":0,\"member_count\":0,\"mode\":\"private\",\"description\":\"testing update_list\",\"slug\":\"test\",\"full_name\":\"@TweepyDev\\/test\",\"created_at\":\"Fri Jun 21 03:02:55 +0000 2019\",\"following\":true,\"user\":{\"id\":1072250532645998596,\"id_str\":\"1072250532645998596\",\"name\":\"Tweepy Testing\",\"screen_name\":\"TweepyDev\",\"location\":\"\",\"description\":\"Account used to test Tweepy\",\"url\":\"https:\\/\\/t.co\\/XRfax6xExn\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\\/\\/t.co\\/XRfax6xExn\",\"expanded_url\":\"https:\\/\\/github.com\\/tweepy\\/tweepy\",\"display_url\":\"github.com\\/tweepy\\/tweepy\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":2,\"friends_count\":6,\"listed_count\":0,\"created_at\":\"Mon Dec 10 22:03:43 +0000 2018\",\"favourites_count\":0,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":false,\"verified\":false,\"statuses_count\":5,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"000000\",\"profile_background_image_url\":\"http:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_image_url_https\":\"https:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\/\\/abs.twimg.com\\/sticky\\/default_profile_images\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\/\\/abs.twimg.com\\/sticky\\/default_profile_images\\/default_profile_normal.png\",\"profile_banner_url\":\"https:\\/\\/pbs.twimg.com\\/profile_banners\\/1072250532645998596\\/1608975647\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"000000\",\"profile_sidebar_fill_color\":\"000000\",\"profile_text_color\":\"000000\",\"profile_use_background_image\":false,\"has_extended_profile\":false,\"default_profile\":false,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\"}}]}" 95 | } 96 | } 97 | } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /docs/extended_tweets.rst: -------------------------------------------------------------------------------- 1 | .. _extended_tweets: 2 | .. _Twitter's Tweet updates documentation: https://developer.twitter.com/en/docs/tweets/tweet-updates 3 | 4 | *************** 5 | Extended Tweets 6 | *************** 7 | 8 | This supplements `Twitter's Tweet updates documentation`_. 9 | 10 | Introduction 11 | ============ 12 | 13 | On May 24, 2016, Twitter 14 | `announced `__ 15 | changes to the way that replies and URLs are handled and 16 | `published plans `__ 17 | around support for these changes in the Twitter API and initial technical 18 | documentation describing the updates to Tweet objects and API options.\ [#]_ 19 | On September 26, 2017, Twitter 20 | `started testing `__ 21 | 280 characters for certain languages,\ [#]_ and on November 7, 2017, 22 | `announced `__ 23 | that the character limit was being expanded for Tweets in languages where 24 | cramming was an issue.\ [#]_ 25 | 26 | Standard API methods 27 | ==================== 28 | 29 | Any ``tweepy.API`` method that returns a Status object accepts a new 30 | ``tweet_mode`` parameter. Valid values for this parameter are ``compat`` and 31 | ``extended``, which give compatibility mode and extended mode, respectively. 32 | The default mode (if no parameter is provided) is compatibility mode. 33 | 34 | Compatibility mode 35 | ------------------ 36 | 37 | By default, using compatibility mode, the ``text`` attribute of Status objects 38 | returned by ``tweepy.API`` methods is truncated to 140 characters, as needed. 39 | When this truncation occurs, the ``truncated`` attribute of the Status object 40 | will be ``True``, and only entities that are fully contained within the 41 | available 140 characters range will be included in the ``entities`` attribute. 42 | It will also be discernible that the ``text`` attribute of the Status object 43 | is truncated as it will be suffixed with an ellipsis character, a space, and a 44 | shortened self-permalink URL to the Tweet. 45 | 46 | Extended mode 47 | ------------- 48 | 49 | When using extended mode, the ``text`` attribute of Status objects returned by 50 | ``tweepy.API`` methods is replaced by a ``full_text`` attribute, which 51 | contains the entire untruncated text of the Tweet. The ``truncated`` attribute 52 | of the Status object will be ``False``, and the ``entities`` attribute will 53 | contain all entities. Additionally, the Status object will have a 54 | ``display_text_range`` attribute, an array of two Unicode code point indices, 55 | identifying the inclusive start and exclusive end of the displayable content 56 | of the Tweet. 57 | 58 | Streaming 59 | ========= 60 | 61 | By default, the Status objects from streams may contain an ``extended_tweet`` 62 | attribute representing the equivalent field in the raw data/payload for the 63 | Tweet. This attribute/field will only exist for extended Tweets, containing a 64 | dictionary of sub-fields. The ``full_text`` sub-field/key of this dictionary 65 | will contain the full, untruncated text of the Tweet, and the ``entities`` 66 | sub-field/key will contain the full set of entities. If there are extended 67 | entities, the ``extended_entities`` sub-field/key will contain the full set of 68 | those. Additionally, the ``display_text_range`` sub-field/key will contain an 69 | array of two Unicode code point indices, identifying the inclusive start and 70 | exclusive end of the displayable content of the Tweet. 71 | 72 | Handling Retweets 73 | ================= 74 | 75 | When using extended mode with a Retweet, the ``full_text`` attribute of the 76 | Status object may be truncated with an ellipsis character instead of 77 | containing the full text of the Retweet. However, since the 78 | ``retweeted_status`` attribute (of a Status object that is a Retweet) is 79 | itself a Status object, the ``full_text`` attribute of the Retweeted Status 80 | object can be used instead. 81 | 82 | This also applies similarly to Status objects/payloads that are Retweets from 83 | streams. The dictionary from the ``extended_tweet`` attribute/field contains a 84 | ``full_text`` sub-field/key that may be truncated with an ellipsis character. 85 | Instead, the ``extended_tweet`` attribute/field of the Retweeted Status (from 86 | the ``retweeted_status`` attribute/field) can be used. 87 | 88 | Examples 89 | ======== 90 | 91 | Given an existing ``tweepy.API`` object and ``id`` for a Tweet, the following 92 | can be used to print the full text of the Tweet, or if it's a Retweet, the 93 | full text of the Retweeted Tweet:: 94 | 95 | status = api.get_status(id, tweet_mode="extended") 96 | try: 97 | print(status.retweeted_status.full_text) 98 | except AttributeError: # Not a Retweet 99 | print(status.full_text) 100 | 101 | If ``status`` is a Retweet, ``status.full_text`` could be truncated. 102 | 103 | This Status event handler for a ``StreamListener`` prints the full text of the 104 | Tweet, or if it's a Retweet, the full text of the Retweeted Tweet:: 105 | 106 | def on_status(self, status): 107 | if hasattr(status, "retweeted_status"): # Check if Retweet 108 | try: 109 | print(status.retweeted_status.extended_tweet["full_text"]) 110 | except AttributeError: 111 | print(status.retweeted_status.text) 112 | else: 113 | try: 114 | print(status.extended_tweet["full_text"]) 115 | except AttributeError: 116 | print(status.text) 117 | 118 | If ``status`` is a Retweet, it will not have an ``extended_tweet`` attribute, 119 | and ``status.text`` could be truncated. 120 | 121 | .. rubric:: Footnotes 122 | 123 | .. [#] https://twittercommunity.com/t/upcoming-changes-to-simplify-replies-and-links-in-tweets/67497 124 | .. [#] https://twittercommunity.com/t/testing-280-characters-for-certain-languages/94126 125 | .. [#] https://twittercommunity.com/t/updating-the-character-limit-and-the-twitter-text-library/96425 126 | -------------------------------------------------------------------------------- /cassettes/testgetstatus.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/statuses/show.json?id=266367358078169089", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:42 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "6992d076c2012a88e19a14bea62775ab" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "900" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "895" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:42 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_0/ZZYd2pgIhybUfvV/gBrQ==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:42 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298486249475180; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:42 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "003afd79007e6a11" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "2760" 85 | ], 86 | "x-response-time": [ 87 | "29" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984891" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"created_at\":\"Thu Nov 08 02:31:41 +0000 2012\",\"id\":266367358078169089,\"id_str\":\"266367358078169089\",\"text\":\"RT @TwitterEng: Bolstering our infrastructure. \\\"As usage patterns change, Twitter can remain resilient.\\\" http:\\/\\/t.co\\/uML86B6s\",\"truncated\":false,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[{\"screen_name\":\"TwitterEng\",\"name\":\"Twitter Engineering\",\"id\":6844292,\"id_str\":\"6844292\",\"indices\":[3,14]}],\"urls\":[{\"url\":\"http:\\/\\/t.co\\/uML86B6s\",\"expanded_url\":\"https:\\/\\/engineering.twitter.com\\/2012\\/11\\/bolstering-our-infrastructure.html\",\"display_url\":\"engineering.twitter.com\\/2012\\/11\\/bolste\\u2026\",\"indices\":[106,126]}]},\"source\":\"\\u003ca href=\\\"http:\\/\\/twitter.com\\\" rel=\\\"nofollow\\\"\\u003eTwitter Web Client\\u003c\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"user\":{\"id\":783214,\"id_str\":\"783214\",\"name\":\"Twitter\",\"screen_name\":\"Twitter\",\"location\":\"Everywhere\",\"description\":\"What\\u2019s happening?!\",\"url\":\"https:\\/\\/t.co\\/TAXQpsHa5X\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\\/\\/t.co\\/TAXQpsHa5X\",\"expanded_url\":\"https:\\/\\/about.twitter.com\\/\",\"display_url\":\"about.twitter.com\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":56335782,\"friends_count\":140,\"listed_count\":90877,\"created_at\":\"Tue Feb 20 14:35:54 +0000 2007\",\"favourites_count\":5965,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":true,\"statuses_count\":10829,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"ACDED6\",\"profile_background_image_url\":\"http:\\/\\/abs.twimg.com\\/images\\/themes\\/theme18\\/bg.gif\",\"profile_background_image_url_https\":\"https:\\/\\/abs.twimg.com\\/images\\/themes\\/theme18\\/bg.gif\",\"profile_background_tile\":true,\"profile_image_url\":\"http:\\/\\/pbs.twimg.com\\/profile_images\\/1111729635610382336\\/_65QFl7B_normal.png\",\"profile_image_url_https\":\"https:\\/\\/pbs.twimg.com\\/profile_images\\/1111729635610382336\\/_65QFl7B_normal.png\",\"profile_banner_url\":\"https:\\/\\/pbs.twimg.com\\/profile_banners\\/783214\\/1556918042\",\"profile_link_color\":\"1B95E0\",\"profile_sidebar_border_color\":\"FFFFFF\",\"profile_sidebar_fill_color\":\"F6F6F6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":true,\"default_profile\":false,\"default_profile_image\":false,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"regular\"},\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":211,\"favorite_count\":144,\"favorited\":false,\"retweeted\":false,\"possibly_sensitive\":false,\"possibly_sensitive_appealable\":false,\"lang\":\"en\"}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testshowlistmember.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/lists/members/show.json?owner_screen_name=Twitter&slug=Official-Twitter-Accounts&screen_name=TwitterAPI", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:52 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "8f95361c55957f1cf9bc4c17e17f1628" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "15" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "6" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:52 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_0O3eqCClYdJ3NMsdFxz4uA==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:52 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298487264860627; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:52 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "00db753d00e30e8d" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "2785" 85 | ], 86 | "x-response-time": [ 87 | "37" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984900" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"id\":6253282,\"id_str\":\"6253282\",\"name\":\"Twitter API\",\"screen_name\":\"TwitterAPI\",\"location\":\"San Francisco, CA\",\"description\":\"The Real Twitter API. Tweets about API changes, service issues and our Developer Platform. Don't get an answer? It's on my website.\",\"url\":\"https:\\/\\/t.co\\/8IkCzCDr19\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\\/\\/t.co\\/8IkCzCDr19\",\"expanded_url\":\"https:\\/\\/developer.twitter.com\",\"display_url\":\"developer.twitter.com\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":6121857,\"friends_count\":12,\"listed_count\":12893,\"created_at\":\"Wed May 23 06:01:13 +0000 2007\",\"favourites_count\":31,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":false,\"verified\":true,\"statuses_count\":3666,\"lang\":null,\"status\":{\"created_at\":\"Mon Jun 24 17:50:46 +0000 2019\",\"id\":1143214899109277697,\"id_str\":\"1143214899109277697\",\"text\":\"We\\u2019ve spoken with all developers who\\u2019ve contacted us to discuss these new rate limits and elevations, and as of tod\\u2026 https:\\/\\/t.co\\/w8WoepBjeU\",\"truncated\":true,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[{\"url\":\"https:\\/\\/t.co\\/w8WoepBjeU\",\"expanded_url\":\"https:\\/\\/twitter.com\\/i\\/web\\/status\\/1143214899109277697\",\"display_url\":\"twitter.com\\/i\\/web\\/status\\/1\\u2026\",\"indices\":[117,140]}]},\"source\":\"\\u003ca href=\\\"http:\\/\\/twitter.com\\\" rel=\\\"nofollow\\\"\\u003eTwitter Web Client\\u003c\\/a\\u003e\",\"in_reply_to_status_id\":1141392777600806912,\"in_reply_to_status_id_str\":\"1141392777600806912\",\"in_reply_to_user_id\":6253282,\"in_reply_to_user_id_str\":\"6253282\",\"in_reply_to_screen_name\":\"TwitterAPI\",\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":24,\"favorite_count\":38,\"favorited\":false,\"retweeted\":false,\"possibly_sensitive\":false,\"lang\":\"en\"},\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_image_url_https\":\"https:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_tile\":true,\"profile_image_url\":\"http:\\/\\/pbs.twimg.com\\/profile_images\\/942858479592554497\\/BbazLO9L_normal.jpg\",\"profile_image_url_https\":\"https:\\/\\/pbs.twimg.com\\/profile_images\\/942858479592554497\\/BbazLO9L_normal.jpg\",\"profile_banner_url\":\"https:\\/\\/pbs.twimg.com\\/profile_banners\\/6253282\\/1497491515\",\"profile_link_color\":\"0084B4\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":true,\"default_profile\":false,\"default_profile_image\":false,\"following\":true,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"regular\"}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testshowlistsubscriber.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "interactions": [ 4 | { 5 | "request": { 6 | "method": "GET", 7 | "uri": "https://api.twitter.com/1.1/lists/subscribers/show.json?owner_screen_name=Twitter&slug=Official-Twitter-Accounts&screen_name=TwitterMktg", 8 | "body": null, 9 | "headers": { 10 | "Host": [ 11 | "api.twitter.com" 12 | ] 13 | } 14 | }, 15 | "response": { 16 | "status": { 17 | "code": 200, 18 | "message": "OK" 19 | }, 20 | "headers": { 21 | "content-type": [ 22 | "application/json;charset=utf-8" 23 | ], 24 | "x-xss-protection": [ 25 | "1; mode=block; report=https://twitter.com/i/xss_report" 26 | ], 27 | "x-content-type-options": [ 28 | "nosniff" 29 | ], 30 | "expires": [ 31 | "Tue, 31 Mar 1981 05:00:00 GMT" 32 | ], 33 | "last-modified": [ 34 | "Sat, 13 Jul 2019 02:27:53 GMT" 35 | ], 36 | "server": [ 37 | "tsa_b" 38 | ], 39 | "cache-control": [ 40 | "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" 41 | ], 42 | "x-connection-hash": [ 43 | "e5700753f222691845ba861a48433a84" 44 | ], 45 | "x-rate-limit-limit": [ 46 | "15" 47 | ], 48 | "x-frame-options": [ 49 | "SAMEORIGIN" 50 | ], 51 | "x-rate-limit-remaining": [ 52 | "8" 53 | ], 54 | "pragma": [ 55 | "no-cache" 56 | ], 57 | "date": [ 58 | "Sat, 13 Jul 2019 02:27:53 GMT" 59 | ], 60 | "status": [ 61 | "200 OK" 62 | ], 63 | "set-cookie": [ 64 | "personalization_id=\"v1_IS40yXVek0Y0GmC+SclmXA==\"; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:53 GMT; Path=/; Domain=.twitter.com", 65 | "lang=en; Path=/", 66 | "guest_id=v1%3A156298487301030996; Max-Age=63072000; Expires=Mon, 12 Jul 2021 02:27:53 GMT; Path=/; Domain=.twitter.com" 67 | ], 68 | "x-access-level": [ 69 | "read-write-directmessages" 70 | ], 71 | "x-twitter-response-tags": [ 72 | "BouncerCompliant" 73 | ], 74 | "x-transaction": [ 75 | "00161b2e007ae7c2" 76 | ], 77 | "strict-transport-security": [ 78 | "max-age=631138519" 79 | ], 80 | "content-disposition": [ 81 | "attachment; filename=json.json" 82 | ], 83 | "content-length": [ 84 | "2792" 85 | ], 86 | "x-response-time": [ 87 | "37" 88 | ], 89 | "x-rate-limit-reset": [ 90 | "1562984901" 91 | ] 92 | }, 93 | "body": { 94 | "string": "{\"id\":357750891,\"id_str\":\"357750891\",\"name\":\"Twitter Marketing\",\"screen_name\":\"TwitterMktg\",\"location\":\"Twitter HQ\",\"description\":\"Twitter connects you with the most valuable audiences, when they're the most receptive. #StartWithThem\",\"url\":\"https:\\/\\/t.co\\/Tfo4moo92y\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\\/\\/t.co\\/Tfo4moo92y\",\"expanded_url\":\"https:\\/\\/marketing.twitter.com\",\"display_url\":\"marketing.twitter.com\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":993300,\"friends_count\":713,\"listed_count\":4544,\"created_at\":\"Thu Aug 18 21:08:15 +0000 2011\",\"favourites_count\":2740,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":false,\"verified\":true,\"statuses_count\":7913,\"lang\":null,\"status\":{\"created_at\":\"Fri Jul 12 14:30:07 +0000 2019\",\"id\":1149687385497702405,\"id_str\":\"1149687385497702405\",\"text\":\"Stans on @Twitter influence the airwaves and shape what we listen to, making them the perfect audience for brands t\\u2026 https:\\/\\/t.co\\/Kg6JaIb4Nd\",\"truncated\":true,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[{\"screen_name\":\"Twitter\",\"name\":\"Twitter\",\"id\":783214,\"id_str\":\"783214\",\"indices\":[9,17]}],\"urls\":[{\"url\":\"https:\\/\\/t.co\\/Kg6JaIb4Nd\",\"expanded_url\":\"https:\\/\\/twitter.com\\/i\\/web\\/status\\/1149687385497702405\",\"display_url\":\"twitter.com\\/i\\/web\\/status\\/1\\u2026\",\"indices\":[117,140]}]},\"source\":\"\\u003ca href=\\\"http:\\/\\/twitter.com\\\" rel=\\\"nofollow\\\"\\u003eTwitter Web Client\\u003c\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":9,\"favorite_count\":6,\"favorited\":false,\"retweeted\":false,\"possibly_sensitive\":false,\"lang\":\"en\"},\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_image_url_https\":\"https:\\/\\/abs.twimg.com\\/images\\/themes\\/theme1\\/bg.png\",\"profile_background_tile\":true,\"profile_image_url\":\"http:\\/\\/pbs.twimg.com\\/profile_images\\/1008726624320118784\\/rKY9KsBd_normal.jpg\",\"profile_image_url_https\":\"https:\\/\\/pbs.twimg.com\\/profile_images\\/1008726624320118784\\/rKY9KsBd_normal.jpg\",\"profile_banner_url\":\"https:\\/\\/pbs.twimg.com\\/profile_banners\\/357750891\\/1560519558\",\"profile_link_color\":\"19CF86\",\"profile_sidebar_border_color\":\"FFFFFF\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":true,\"default_profile\":false,\"default_profile_image\":false,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\"}" 95 | } 96 | } 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /cassettes/testcreatedestroyfriendship.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Length: 12 | - '0' 13 | User-Agent: 14 | - python-requests/2.25.1 15 | method: POST 16 | uri: https://api.twitter.com/1.1/friendships/destroy.json?screen_name=Twitter 17 | response: 18 | body: 19 | string: !!binary | 20 | H4sIAAAAAAAAAKxW2a7bNhD9FVcvfahxLVqLZQNFmxV56UORAA0QXxCUNJLYUKRCUnGcC/97hlos 21 | eblogVYGLHPmzELOmaGfPJ57u00SrEm4xN/UWO3tvF7gLT3JasD1hwO3FjQKTKYBJL2RC5Uxy5VE 22 | IXwFfTxUoAHlOaAFbwbVXxWzP5tFxZoGJJflbz8hpNUCVZW1jdntV/uVfcjUfvXhxcc/G/OORR8R 23 | AtJyy8F4u6ce3r1w+enpX5l/a5jMIafXWJaq1j7YfhdoV+9XLmduGsGOA/oGgwguc565dD75y3Xw 24 | eHo8Xe30nN/jCVWNVhYyC3jUBRMGll6hhFAH0IZmqpXW20VbEpAo2aJKc5D5WeHj2XKDtqMg2RBX 25 | K6wDc1JmXR1aWLyFdLH2FyTcBdEuChe/+PigxN9gwgX7qlrNLZz9xkG8xrO3GVVFYQAlshVi6Vle 26 | A/2uJIyCEhQFyVLh0re6xeyxwLzg09pYZlsz+SahHweYN5Pl6KWHuIO5TLxqu8QJWZAEs96tp8TX 27 | xJ00BiFBtN0GSRJFCUm2ZLsN/RlX72jRzsI35//3P74jU7Wir94t9m2eBDl+A3HMxNRlxuZFmbOs 28 | YqayrOxKiNkf61SJYYE71bR2YCV7Cl42xRRyaqD3yKHqiCmsY8aGbcX+JtwE4UXjnWVXJCMESbac 29 | SIUpYT0z53rf+n6QsUWlofh1PyP34XB4MI3m8rPoue0tNAiESNXTb9/bwvsB1HvCrujFXQpUQyOO 30 | 1CraV5BOBYk3vp9EcZIQJO4z2IsaXRhcee8O9fpUbvV3j2kW9/lCII1HLmZK6ZxLrL0ZRdjvGUx6 31 | aTVPW6v0GcAN/dJiF9ORyANnNNgDgD13Fel7zbXaKFtHm2iS5temM0nfMF4rc+90mcbUgQMU87Ga 32 | SSMYqu9JkZw3RjiHCi6Apiz7XGrMzg0V4ey9F69ev3kde3cxvGYlzGbnMDqNG4p12Y/NDmNw+lZQ 33 | n98k2a/S8qHkxT84ph1rr0bzf/dvcT3OqFF9dzPNZbALLAZD+obhZhuHOGZwoIZhEuxXL60s6nDT 34 | UKl0zcTD303p3QlzZ2v/f7SUSYn9cX29PROoR2Ok/prHiDEh28iPAn/mU+BMONODvNxGb+Zaw3NI 35 | maYpthJGHnFvu+cODhdiQsXuM0O5aX3WBt0z02Lv37BmLCvOaYrG0F3vg8Goy6FgrbCTeGiDK/no 36 | 8OJq5q4TLyQ4Zb60YCw14Lp60Ell8Srs//tMU6Fu7dzB1KnUHhs3mjSUrWDaO/0AAAD//wMACDHM 37 | LYEJAAA= 38 | headers: 39 | cache-control: 40 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 41 | content-disposition: 42 | - attachment; filename=json.json 43 | content-encoding: 44 | - gzip 45 | content-length: 46 | - '974' 47 | content-type: 48 | - application/json;charset=utf-8 49 | date: 50 | - Fri, 12 Feb 2021 03:43:02 GMT 51 | expires: 52 | - Tue, 31 Mar 1981 05:00:00 GMT 53 | last-modified: 54 | - Fri, 12 Feb 2021 03:43:02 GMT 55 | pragma: 56 | - no-cache 57 | server: 58 | - tsa_b 59 | set-cookie: 60 | - personalization_id="v1_Pwtl6XAlWAXLFYqjPwyU6w=="; Max-Age=63072000; Expires=Sun, 61 | 12 Feb 2023 03:43:02 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 62 | - lang=en; Path=/ 63 | - guest_id=v1%3A161310138241727584; Max-Age=63072000; Expires=Sun, 12 Feb 2023 64 | 03:43:02 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 65 | status: 66 | - 200 OK 67 | strict-transport-security: 68 | - max-age=631138519 69 | x-access-level: 70 | - read-write-directmessages 71 | x-connection-hash: 72 | - af8d2e5828fda17622373fe27bb727c4 73 | x-content-type-options: 74 | - nosniff 75 | x-frame-options: 76 | - SAMEORIGIN 77 | x-response-time: 78 | - '109' 79 | x-transaction: 80 | - 00825ea50030540a 81 | x-twitter-response-tags: 82 | - BouncerCompliant 83 | x-xss-protection: 84 | - '0' 85 | status: 86 | code: 200 87 | message: OK 88 | - request: 89 | body: null 90 | headers: 91 | Accept: 92 | - '*/*' 93 | Accept-Encoding: 94 | - gzip, deflate 95 | Connection: 96 | - keep-alive 97 | Content-Length: 98 | - '0' 99 | Cookie: 100 | - guest_id=v1%3A161310138241727584; personalization_id="v1_Pwtl6XAlWAXLFYqjPwyU6w=="; 101 | lang=en 102 | User-Agent: 103 | - python-requests/2.25.1 104 | method: POST 105 | uri: https://api.twitter.com/1.1/friendships/create.json?screen_name=Twitter 106 | response: 107 | body: 108 | string: !!binary | 109 | H4sIAAAAAAAAAKxW247bNhD9FVcvfaixFq2LZQNFmyvy0ociARogXhCUNJLYUKRCUnGchf89Q10s 110 | +bJogVYG7OWZM8Mh58xonzyee7tNEqxJuMS/qbHa23k94C09yWrA9YcDtxY0AibTAJLe4EJlzHIl 111 | EYSvoI+HCjQgngN68GYw/VUx+7NZVKxpQHJZ/vYTUlot0FRZ25jdfrVf2YdM7VcfXnz8szHvWPQR 112 | KSAttxyMt3vq6d0PLj89/Sv3bw2TOeT0mstS1doH258C/er9yuXMTSPYcWDfcJDBZc4zl84nf7kO 113 | Hk+Pp6uTnvN7PKGp0cpCZgGvumDCwNIrlBDqANrQTLXSertoSwISbX00aQ4yPxsQEdyg7wgkG+Jq 114 | hXVgDmXW1aGFxVtIF2t/QcJdEO2icPGLjw8i/gYTLthX1Wpu4Rw3DuI13r3NqCoKA4jIVoilZ3kN 115 | 9LuSMAIlKAqSpcKlb3WL2WOBecGntbHMtmaKTUI/DjBvJssxSk9xF3OZeNV2iROyIAlmvVtPia+J 116 | u2nchATRdhskSRQlJNmS7Tb0Z1q9Y0U/C99c/N//+I5K1Yq+erfYt3kS5PgNxCkTU5cZmxdlrrKK 117 | mcqysishZn+sUyWGBZ5U09qRlewleNkU05ZTA71HDVVHTGEdMzYcK/Y34SYILxrvjF2JjBAU2XIS 118 | FaaE9cxc6H3r+0HGFpWG4tf9TNyHw+HBNJrLz6LXtrfQIJAiVS+/fe8L7wdSHwm7ooe7FKiGRhyp 119 | VbSvIJ0KEm98P4niJCEkSp7hXtTowuEqenep17dya797TbN9ny8EynjUYqaUzrnE2psRwn7PYLJL 120 | q3naWqXPBG7olxa7mI5CHjSjwR4A7LmrSN9rrtVGbB1tognNr11nSN8wXitz73SZxtSBAxXzsZpJ 121 | Ixia76EozhsnnEMFF0BTln0uNWbnhopw/t6LV6/fvI69uxxesxJms3MYncYNxbrsx2bHMTh9K6jP 122 | vyTZr9LyoeTFPwSmnWqvRvN/j29xPc6o0Xz3MM3lZhdc3AzlG4abbRzimMGBGoZJsF+9tLKow01D 123 | pdI1Ew9/N6V3Z5s7R/v/d0uZlNgf16+3Zzbq2bhT/5rHHWNCtpEfBf4spsCZcJYHebmN3sythueQ 124 | Mk1TbCXceeS97Z47PFyIiRW7z4zlpvXZGnTPzIq9f6Oasaw4pyk6Q/d6HxxGWw4Fa4Wd4KENrvAx 125 | 4MWrmbtOvEBwynxpwVhqwHX1YJPK4quw/99nmgp1a+cBpk6l9ti40aShbAXT3ukHAAAA//8DAObG 126 | aeaBCQAA 127 | headers: 128 | cache-control: 129 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 130 | content-disposition: 131 | - attachment; filename=json.json 132 | content-encoding: 133 | - gzip 134 | content-length: 135 | - '975' 136 | content-type: 137 | - application/json;charset=utf-8 138 | date: 139 | - Fri, 12 Feb 2021 03:43:02 GMT 140 | expires: 141 | - Tue, 31 Mar 1981 05:00:00 GMT 142 | last-modified: 143 | - Fri, 12 Feb 2021 03:43:02 GMT 144 | pragma: 145 | - no-cache 146 | server: 147 | - tsa_b 148 | status: 149 | - 200 OK 150 | strict-transport-security: 151 | - max-age=631138519 152 | x-access-level: 153 | - read-write-directmessages 154 | x-connection-hash: 155 | - a8250b1f3af12df69924ea9376ddcd99 156 | x-content-type-options: 157 | - nosniff 158 | x-frame-options: 159 | - SAMEORIGIN 160 | x-response-time: 161 | - '97' 162 | x-transaction: 163 | - 0048dcdc0065f257 164 | x-twitter-response-tags: 165 | - BouncerCompliant 166 | x-xss-protection: 167 | - '0' 168 | status: 169 | code: 200 170 | message: OK 171 | version: 1 172 | -------------------------------------------------------------------------------- /cassettes/testgetuser.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.25.1 13 | method: GET 14 | uri: https://api.twitter.com/1.1/users/show.json?screen_name=Twitter 15 | response: 16 | body: 17 | string: !!binary | 18 | H4sIAAAAAAAAAKxWW6+bRhD+K5SXPtSywYABS1WbNIn60ocqkRopPlotMMAmyy7ZXWK7R/7vHe74 19 | cpRKrc+R7J355rIz3ww82yyz92HkbV1/hb+JNsre273AXtmCVoDnD0dmDCgU6FQBCHIn5zKlhkmB 20 | QvgG6nwsQQHKayVzxoHMetFwvrIzQE+sHkz+Kqn5UVslrWsQTBS//ICmjeKoKo2p9f6wOWzMOpWH 21 | zYdXH/+s9e80+IgQEIYZBtreP/fw7guPn57/lfmppiKDjNxiaSIbszb97dCuOmwQnjFdc3oe0HcY 22 | RDCRsbRN55Oz2npPl6fLzU2n/J4ul644BlID2IKccg0rO5ecyyMoTVLZCGPvg9h1t7soQpViILJJ 23 | 4WDNmUbbURCFrhuubOwPbaXUtP1pwHoHibV1LNffe8E+8K2fHPygxAkx4Zx+k41iBia/O2/nYe1N 24 | SmSeazBjwwyrgPwtBYyCAiQBQRPepm9Ug9lj41nO5rM21DR69u36jh9g3lQUo5ce0hbmKvE/pOgS 25 | dyLLjfdBuHedKfGt21Yag7heEEW70I9R5ISe77Rlmjj8QIt2Bk6t/19PJ12ypGxOJ4sa6zMWQcAZ 26 | KYglblGqESldNmbJtJLq0tCiayPe4Fwlkg8HvK0iVQuWoqfh9cDMYefhet9Jpju5Qew4XhT5QRh7 27 | fuRf3+lOe8M610XWrWaWYX54t7SNc2jQMqVWqSD/+bBg+/F4XOtaMfGF92S3LQUcIUL2fDz0tvB+ 28 | APWecEx6cZcCUVDzMzGS9C0lc4dcP/biAP/DqOvQI+xV064Mbrx3FX65UvfI75RukcvLnUKuj4RN 29 | pVQZE0gOPYpwKaQw64VRLGmMVBOAafK1wVEnI9sHUikwRwAzjkfcz2M7jqMojHazMLs1XEj6mUKa 30 | 2pfrHOYZHZCYjFFUaE5R/UiK1L0zGtd4QtMvhcLc2rXDW3v71W9v3r7Z2Q8xrKIFLLbrsFx1uzar 31 | ol+sHUbjfi6hmr7d6LBJinXB8u84Jh2Nb5b3f/dv8DxusVH98DL1dbArLAZDPvt+GO985B2uXN+P 32 | vMPmtRF55Yc1EVJVlK8/14X9IMyDq/3/0RIqBI7J7QPwhUA9GiP1LwgYcee6ceAEnrN81uOSmOjh 33 | vo6Dt0utZhkkVJEE5wgjj7h33ecBDg98Ru3avwWq3eeT1us+Cy2ugDvWjG3FLU7QGLoXgMFg1GWQ 34 | 04abWTyMwY18dHj18GbtIF5JcMV8bUAboqGd6UEnpMGHZf9WNK+EeTSJOdftIlJQNJwq+/IPAAAA 35 | //8DANPV/2msCQAA 36 | headers: 37 | cache-control: 38 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 39 | content-disposition: 40 | - attachment; filename=json.json 41 | content-encoding: 42 | - gzip 43 | content-length: 44 | - '981' 45 | content-type: 46 | - application/json;charset=utf-8 47 | date: 48 | - Wed, 10 Feb 2021 04:37:44 GMT 49 | expires: 50 | - Tue, 31 Mar 1981 05:00:00 GMT 51 | last-modified: 52 | - Wed, 10 Feb 2021 04:37:44 GMT 53 | pragma: 54 | - no-cache 55 | server: 56 | - tsa_b 57 | set-cookie: 58 | - personalization_id="v1_5wyYbkwuwrWQsw4r71+yNw=="; Max-Age=63072000; Expires=Fri, 59 | 10 Feb 2023 04:37:44 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 60 | - lang=en; Path=/ 61 | - guest_id=v1%3A161293186489932288; Max-Age=63072000; Expires=Fri, 10 Feb 2023 62 | 04:37:44 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None 63 | status: 64 | - 200 OK 65 | strict-transport-security: 66 | - max-age=631138519 67 | x-access-level: 68 | - read-write-directmessages 69 | x-connection-hash: 70 | - d14cbc7f406df5a5f2ed61aeef5e3a71 71 | x-content-type-options: 72 | - nosniff 73 | x-frame-options: 74 | - SAMEORIGIN 75 | x-rate-limit-limit: 76 | - '900' 77 | x-rate-limit-remaining: 78 | - '889' 79 | x-rate-limit-reset: 80 | - '1612932333' 81 | x-response-time: 82 | - '41' 83 | x-transaction: 84 | - 00822d2600c6a963 85 | x-twitter-response-tags: 86 | - BouncerCompliant 87 | x-xss-protection: 88 | - '0' 89 | status: 90 | code: 200 91 | message: OK 92 | - request: 93 | body: null 94 | headers: 95 | Accept: 96 | - '*/*' 97 | Accept-Encoding: 98 | - gzip, deflate 99 | Connection: 100 | - keep-alive 101 | Cookie: 102 | - guest_id=v1%3A161293186489932288; personalization_id="v1_5wyYbkwuwrWQsw4r71+yNw=="; 103 | lang=en 104 | User-Agent: 105 | - python-requests/2.25.1 106 | method: GET 107 | uri: https://api.twitter.com/1.1/users/show.json?user_id=783214 108 | response: 109 | body: 110 | string: !!binary | 111 | H4sIAAAAAAAAAKxWW6+bRhD+K5SXPtSywYABS1WbNIn60ocqkRopPlotMMAmyy7ZXWK7R/7vHW4G 112 | bB+lUost2TvzzWV3vpnl2WaZvQ8jb+v6K/xPtFH23u4F9soWtAJcfzgyY0ChQKcKQJA7OZcpNUwK 113 | FMI3UOdjCQpQXiuZMw5k0ouG85WdAXpi9WDyV0nNj9oqaV2DYKL45Qc0bRRHVWlMrfeHzWFj1qk8 114 | bD68+vhnrX+nwUeEgDDMMND2/rmHdz+4/PT8r8xPNRUZZOQWSxPZmLXpd4d21WGD8IzpmtPzgL7D 115 | IIKJjKVtOp+c1dZ7ujxdbnZ6ze/pcukOx0BqAEuQU65hZeeSc3kEpUkqG2HsfRC77jbYuqhSDER2 116 | VTh45kyj7SiIQtf1VjbWh7ZSatr6NGC9g8TaOpbr771gH/jWTw4+KHFCTDin32SjmIGr3523Qy+N 117 | SYnMcw1mLJhhFZC/pYBRUIAkIGjC2/SNajB7LDzL2bTWhppGT75d3/EDzJuKYvTSQ9qDWST+hxRd 118 | 4k5kufE+CPeuc01867YnjUFcL4iiXejHKHJCz3eiaMbhB1q0M3Bq/f96OumSJWVzOlnUWJ/xEASc 119 | kYJ4xC1KNSKl88LMmVZSXRpadGXEHZyrRPJhgbtVpGrBUvQ0XDbMFHZqrved5LonN4gdx4siPwhj 120 | z4/85Z7utDesc11k3WpiGeaHe0vbOIcGLVNqlQrynw8zth+Px7WuFRNfeE9221LAESJkz8dDbwvv 121 | B1DvCdukF3cpEAU1PxMjSV9SMlXI9WMvDvAbRl2FHmEXRVsY3HjvTvjlk7pHfufoZrm8XCnk+kjY 122 | VEqVMYHk0KMIh0IKk14YxZLGSHUFME2+NtjqZGT7QCoF5ghgxvaI+35s23EUhdFuEma3hjNJ31NI 123 | U/uyzGHq0QGJyRhFheYU1Y+kSN07o3GMJzT9UijMrR07vLW3X/325u2bnf0QwypawGy6DsNVt2Oz 124 | KvrB2mE0zucSquuvGx02SbEuWP4dx6Sj8c3w/u/+Da7HKTaqH26mXgZbYDEY8tn3w3jnI+9w5Pp+ 125 | 5B02r43IKz+siZCqonz9uS7sB2EebO3/j5ZQIbBNbi/AFwL1aIzUvyBgxJ3rxoETeM78rschcaWH 126 | +zoO3s61mmWQUEUS7COMPOLedc8DHC74hNq1nxmqnedXrdc9My2OgDvWjGXFKU7QGLoXgMFg1GWQ 127 | 04abSTy0wY18dLi4vFnbiAsJjpivDWhDNLQ9PeiENHhZ9m9F00iYWpOYc90OIgVFw6myL/8AAAD/ 128 | /wMA7kcM66wJAAA= 129 | headers: 130 | cache-control: 131 | - no-cache, no-store, must-revalidate, pre-check=0, post-check=0 132 | content-disposition: 133 | - attachment; filename=json.json 134 | content-encoding: 135 | - gzip 136 | content-length: 137 | - '980' 138 | content-type: 139 | - application/json;charset=utf-8 140 | date: 141 | - Wed, 10 Feb 2021 04:37:45 GMT 142 | expires: 143 | - Tue, 31 Mar 1981 05:00:00 GMT 144 | last-modified: 145 | - Wed, 10 Feb 2021 04:37:45 GMT 146 | pragma: 147 | - no-cache 148 | server: 149 | - tsa_b 150 | status: 151 | - 200 OK 152 | strict-transport-security: 153 | - max-age=631138519 154 | x-access-level: 155 | - read-write-directmessages 156 | x-connection-hash: 157 | - 7b5e74fab79ce7c8bece646a8554d29b 158 | x-content-type-options: 159 | - nosniff 160 | x-frame-options: 161 | - SAMEORIGIN 162 | x-rate-limit-limit: 163 | - '900' 164 | x-rate-limit-remaining: 165 | - '888' 166 | x-rate-limit-reset: 167 | - '1612932333' 168 | x-response-time: 169 | - '99' 170 | x-transaction: 171 | - 00a903510055a6ec 172 | x-twitter-response-tags: 173 | - BouncerCompliant 174 | x-xss-protection: 175 | - '0' 176 | status: 177 | code: 200 178 | message: OK 179 | version: 1 180 | --------------------------------------------------------------------------------