diff --git a/.travis.yml b/.travis.yml index 231b15c..91ebf1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,8 @@ language: python python: - - 3.5 - - 3.6 -# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true + - 2.6 + - 2.7 + - 3.3 env: global: - secure: USjLDneiXlVvEjkUVqTt+LBi0XJ4QhkRcJzqVXA9gEau1NTjAkNTPmHjUbOygp0dkfoV0uWrZKCw6fL1g+HJgWl0vHeHzcNl4mUkA+OwkGFHgaeIhvUfnyyJA8P3Zm21XHC+ehzMpEFN5fVNNhREjnRj+CXMc0FgA6knwBRobu4= @@ -22,11 +17,15 @@ env: - TEST_LIST_SLUG=team - TEST_LIST_OWNER_SCREEN_NAME=twitterapi - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= -install: +install: - pip install -r requirements.txt + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: email: false +branches: + only: + - master after_success: - coveralls before_script: diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b7d14e..739e11f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -44,7 +44,7 @@ Patches and Suggestions - `Oleg Anashkin `_, streaming ``handlers`` functionality - `Luis Alberto Santana `_, Fixed issue where Twython was unnecessarily disabling compression -- `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError +- `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError - `Natan L `_, Fixed typo in documentation - `Cash Costello `_, Moved tests to use `responsoes`, fixed typos in documentation - `Joe Cabrera `_, PEP 8 contributions diff --git a/HISTORY.md b/HISTORY.rst similarity index 89% rename from HISTORY.md rename to HISTORY.rst index fd2b8ee..05f1f71 100644 --- a/HISTORY.md +++ b/HISTORY.rst @@ -1,39 +1,17 @@ -# History +.. :changelog: -## 3.8.0 (2020-04-02) -- Bump release with latest patches from GitHub. -- Fix Direct Messages with patches from @manuelcortez. +History +------- -## 3.7.0 (2018-07-05) -- Fixes for cursoring API endpoints -- Improve `html_for_tweet()` parsing -- Documentation cleanup -- Documentation for cursor's `return_pages` keyword argument -- Update links to Twitter API in documentation -- Added `create_metadata` endpoint -- Raise error for when cursor is not provided a callable - -## 3.6.0 (2017-23-08) -- Improve replacing of entities with links in `html_for_tweet()` -- Update classifiers for PyPI - -## 3.5.0 (2017-06-06) -- Added support for "symbols" in `Twython.html_for_tweet()` -- Added support for extended tweets in `Twython.html_for_tweet()` -- You can now check progress of video uploads to Twitter when using `Twython.upload_video()` - -## 3.4.0 (2016-30-04) -- Added `upload_video` endpoint -- Fix quoted status checks in `html_for_tweet` -- Fix `html_for_tweet` method response when hashtag/mention is a substring of another - -## 3.3.0 (2015-18-07) +3.3.0 (2015-18-07) +++++++++++++++++++ - Added support for muting users - Fix typos in documentation - Updated documentation examples - Added dynamic filtering to streamer -## 3.2.0 (2014-10-30) +3.2.0 (2014-10-30) +++++++++++++++++++ - PEP8'd some code - Added `lookup_status` function to `endpoints.py` - Added keyword argument to `cursor` to return full pages rather than individual results @@ -46,16 +24,23 @@ - Deprecating `update_with_media` per Twitter API 1.1 (https://dev.twitter.com/rest/reference/post/statuses/update_with_media) - Unpin `requests` and `requests-oauthlib` in `requirements.txt` -## 3.1.2 (2013-12-05) + +3.1.2 (2013-12-05) +++++++++++++++++++ + - Fixed Changelog (HISTORY.rst) -## 3.1.1 (2013-12-05) +3.1.1 (2013-12-05) +++++++++++++++++++ + - Update `requests` version to 2.1.0. - Fixed: Streaming issue where `Exceptions` in handlers or `on_success` which subclass `ValueError` would previously be caught and reported as a JSON decoding problem, and `on_error()` would be called (with status_code=200) - Fixed issue where XML was returned when bad tokens were passed to `get_authorized_tokens` - Fixed import for `setup` causing installation to fail on some devices (eg. Nokia N9/MeeGo) -## 3.1.0 (2013-09-25) +3.1.0 (2013-09-25) +++++++++++++++++++ + - Added ``html_for_tweet`` static method. This method accepts a tweet object returned from a Twitter API call and will return a string with urls, mentions and hashtags in the tweet replaced with HTML. - Pass ``client_args`` to the streaming ``__init__``, much like in core Twython (you can pass headers, timeout, hooks, proxies, etc.). - Streamer has new parameter ``handlers`` which accepts a list of strings related to functions that are apart of the Streaming class and start with "on\_". i.e. ['delete'] is passed, when 'delete' is received from a stream response; ``on_delete`` will be called. @@ -67,7 +52,9 @@ - Fixed streaming issue where results wouldn't be returned for streams that weren't so active (See https://github.com/ryanmcgrath/twython/issues/202#issuecomment-19915708) - Streaming API now uses ``_transparent_params`` so when passed ``True`` or ``False`` or an array, etc. Twython formats it to meet Twitter parameter standards (i.e. ['ryanmcgrath', 'mikehelmick', 'twitterapi'] would convert to string 'ryanmcgrath,mikehelmick,twitterapi') -## 3.0.0 (2013-06-18) +3.0.0 (2013-06-18) +++++++++++++++++++ + - Changed ``twython/twython.py`` to ``twython/api.py`` in attempt to make structure look a little neater - Removed all camelCase function access (anything like ``getHomeTimeline`` is now ``get_home_timeline``) - Removed ``shorten_url``. With the ``requests`` library, shortening a URL on your own is simple enough @@ -86,7 +73,9 @@ - Added ``invalidate_token`` API method which allows registed apps to revoke an access token presenting its client credentials - ``get_lastfunction_header`` now accepts a ``default_return_value`` parameter. This means that if you pass a second value (ex. ``Twython.get_lastfunction_header('x-rate-limit-remaining', 0)``) and the value is not found, it returns your default value -## 2.10.1 (2013-05-29) +2.10.1 (2013-05-29) +++++++++++++++++++ + - More test coverage! - Fix ``search_gen`` - Fixed ``get_lastfunction_header`` to actually do what its docstring says, returns ``None`` if header is not found @@ -96,7 +85,9 @@ - No longer raise ``TwythonStreamError`` when stream line can't be decoded. Instead, sends signal to ``TwythonStreamer.on_error`` - Allow for (int, long, float) params to be passed to Twython Twitter API functions in Python 2, and (int, float) in Python 3 -## 2.10.0 (2013-05-21) +2.10.0 (2013-05-21) +++++++++++++++++++ + - Added ``get_retweeters_ids`` method - Fixed ``TwythonDeprecationWarning`` on camelCase functions if the camelCase was the same as the PEP8 function (i.e. ``Twython.retweet`` did not change) - Fixed error message bubbling when error message returned from Twitter was not an array (i.e. if you try to retweet something twice, the error is not found at index 0) @@ -107,14 +98,20 @@ - Cleaned up ``Twython.construct_api_url``, uses "transparent" parameters (see 4th bullet in this version for explaination) - Update ``requests`` and ``requests-oauthlib`` requirements, fixing posting files AND post data together, making authenticated requests in general in Python 3.3 -## 2.9.1 (2013-05-04) +2.9.1 (2013-05-04) +++++++++++++++++++ + - "PEP8" all the functions. Switch functions from camelCase() to underscore_funcs(). (i.e. ``updateStatus()`` is now ``update_status()``) -## 2.9.0 (2013-05-04) +2.9.0 (2013-05-04) +++++++++++++++++++ + - Fixed streaming issue #144, added ``TwythonStreamer`` to aid users in a friendly streaming experience (streaming examples in ``examples`` and README's have been updated as well) - ``Twython`` now requires ``requests-oauthlib`` 0.3.1, fixes #154 (unable to upload media when sending POST data with the file) -## 2.8.0 (2013-04-29) +2.8.0 (2013-04-29) +++++++++++++++++++ + - Added a ``HISTORY.rst`` to start tracking history of changes - Updated ``twitter_endpoints.py`` to ``endpoints.py`` for cleanliness - Removed twython3k directory, no longer needed @@ -137,24 +134,36 @@ - Twython now takes ``ssl_verify`` parameter, defaults True. Set False if you're having development server issues - Removed internal ``_media_update`` function, we could have always just used ``self.post`` -## 2.7.3 (2013-04-12) +2.7.3 (2013-04-12) +++++++++++++++++++ + - Fixed issue where Twython Exceptions were not being logged correctly -## 2.7.2 (2013-04-08) +2.7.2 (2013-04-08) +++++++++++++++++++ + - Fixed ``AttributeError`` when trying to decode the JSON response via ``Response.json()`` -## 2.7.1 (2013-04-08) +2.7.1 (2013-04-08) +++++++++++++++++++ + - Removed ``simplejson`` dependency - Fixed ``destroyDirectMessage``, ``createBlock``, ``destroyBlock`` endpoints in ``twitter_endpoints.py`` - Added ``getProfileBannerSizes`` method to ``twitter_endpoints.py`` - Made oauth_verifier argument required in ``get_authorized_tokens`` - Update ``updateProfileBannerImage`` to use v1.1 endpoint -## 2.7.0 (2013-04-04) +2.7.0 (2013-04-04) +++++++++++++++++++ + - New ``showOwnedLists`` method -## 2.7.0 (2013-03-31) +2.7.0 (2013-03-31) +++++++++++++++++++ + - Added missing slash to ``getMentionsTimeline`` in ``twitter_endpoints.py`` -## 2.6.0 (2013-03-29) +2.6.0 (2013-03-29) +++++++++++++++++++ + - Updated ``twitter_endpoints.py`` to better reflect order of API endpoints on the Twitter API v1.1 docs site diff --git a/MANIFEST.in b/MANIFEST.in index 79f570a..eae175a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,9 +1 @@ -include README.md HISTORY.md LICENSE requirements.txt - -recursive-include docs * -prune docs/_build - -recursive-include examples *.py - -recursive-include tests *.py -recursive-include tests/tweets *.json +include README.rst HISTORY.rst LICENSE requirements.txt diff --git a/README.md b/README.md deleted file mode 100644 index 853136e..0000000 --- a/README.md +++ /dev/null @@ -1,169 +0,0 @@ -# Twython - - - - - - -`Twython` is a Python library providing an easy way to access Twitter data. Supports Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! - -**Note**: As of Twython 3.7.0, there's a general call for maintainers put out. If you find the project useful and want to help out, reach out to Ryan with the info from the bottom of this README. Great open source project to get your feet wet with! - -## Features -- Query data for: - - User information - - Twitter lists - - Timelines - - Direct Messages - - and anything found in [the docs](https://developer.twitter.com/en/docs) -- Image Uploading: - - Update user status with an image - - Change user avatar - - Change user background image - - Change user banner image -- OAuth 2 Application Only (read-only) Support -- Support for Twitter's Streaming API -- Seamless Python 3 support! - -## Installation -Install Twython via pip: - -```bash -$ pip install twython -``` - -If you're on a legacy project that needs Python 2.7 support, you can install the last version of Twython that supported 2.7: - -``` -pip install twython==3.7.0` -``` - -Or, if you want the code that is currently on GitHub: - -```bash -git clone git://github.com/ryanmcgrath/twython.git -cd twython -python setup.py install -``` - -## Documentation -Documentation is available at https://twython.readthedocs.io/en/latest/ - -## Starting Out -First, you'll want to head over to https://apps.twitter.com and register an application! - -After you register, grab your applications `Consumer Key` and `Consumer Secret` from the application details tab. - -The most common type of authentication is Twitter user authentication using OAuth 1. If you're a web app planning to have users sign up with their Twitter account and interact with their timelines, updating their status, and stuff like that this **is** the authentication for you! - -First, you'll want to import Twython - -```python -from twython import Twython -``` - -## Obtain Authorization URL -Now, you'll want to create a Twython instance with your `Consumer Key` and `Consumer Secret`: - -- Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application -- Desktop and Mobile Applications **do not** require a callback_url - -```python -APP_KEY = 'YOUR_APP_KEY' -APP_SECRET = 'YOUR_APP_SECRET' - -twitter = Twython(APP_KEY, APP_SECRET) - -auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') -``` - -From the `auth` variable, save the `oauth_token` and `oauth_token_secret` for later use (these are not the final auth tokens). In Django or other web frameworks, you might want to store it to a session variable - -```python -OAUTH_TOKEN = auth['oauth_token'] -OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] -``` - -Send the user to the authentication url, you can obtain it by accessing - -```python -auth['auth_url'] -``` - -## Handling the Callback -If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code - -After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in `get_authentication_tokens`. - -You'll want to extract the `oauth_verifier` from the url. - -Django example: - -```python -oauth_verifier = request.GET['oauth_verifier'] -``` - -Now that you have the `oauth_verifier` stored to a variable, you'll want to create a new instance of Twython and grab the final user tokens - -```python -twitter = Twython( - APP_KEY, APP_SECRET, - OAUTH_TOKEN, OAUTH_TOKEN_SECRET -) - -final_step = twitter.get_authorized_tokens(oauth_verifier) -``` - -Once you have the final user tokens, store them in a database for later use:: - -```python - OAUTH_TOKEN = final_step['oauth_token'] - OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] -``` - -For OAuth 2 (Application Only, read-only) authentication, see [our documentation](https://twython.readthedocs.io/en/latest/usage/starting_out.html#oauth-2-application-authentication). - -## Dynamic Function Arguments -Keyword arguments to functions are mapped to the functions available for each endpoint in the Twitter API docs. Doing this allows us to be incredibly flexible in querying the Twitter API, so changes to the API aren't held up from you using them by this library. - -Basic Usage ------------ - -**Function definitions (i.e. get_home_timeline()) can be found by reading over twython/endpoints.py** - -Create a Twython instance with your application keys and the users OAuth tokens - -```python -from twython import Twython -twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) -``` - -## Authenticated Users Home Timeline -```python -twitter.get_home_timeline() -``` - -## Updating Status -This method makes use of dynamic arguments, [read more about them](https://twython.readthedocs.io/en/latest/usage/starting_out.html#dynamic-function-arguments). - -```python -twitter.update_status(status='See how easy using Twython is!') -``` - -## Advanced Usage -- [Advanced Twython Usage](https://twython.readthedocs.io/en/latest/usage/advanced_usage.html) -- [Streaming with Twython](https://twython.readthedocs.io/en/latest/usage/streaming_api.html) - - -## Questions, Comments, etc? -My hope is that Twython is so simple that you'd never *have* to ask any questions, but if you feel the need to contact me for this (or other) reasons, you can hit me up at ryan@venodesigns.net. - -Or if I'm to busy to answer, feel free to ping mikeh@ydekproductions.com as well. - -Follow us on Twitter: - -- [@ryanmcgrath](https://twitter.com/ryanmcgrath) -- [@mikehelmick](https://twitter.com/mikehelmick) - -## Want to help? -Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..6f12862 --- /dev/null +++ b/README.rst @@ -0,0 +1,222 @@ +Twython +======= + + +.. image:: https://img.shields.io/pypi/v/twython.svg?style=flat-square + :target: https://pypi.python.org/pypi/twython + +.. image:: https://img.shields.io/pypi/dw/twython.svg?style=flat-square + :target: https://pypi.python.org/pypi/twython + +.. image:: https://img.shields.io/travis/ryanmcgrath/twython.svg?style=flat-square + :target: https://travis-ci.org/ryanmcgrath/twython + +.. image:: https://img.shields.io/coveralls/ryanmcgrath/twython/master.svg?style=flat-square + :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master + +``Twython`` is the premier Python library providing an easy (and up-to-date) way to access Twitter data. Actively maintained and featuring support for Python 2.6+ and Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! + +Features +-------- + +- Query data for: + - User information + - Twitter lists + - Timelines + - Direct Messages + - and anything found in `the docs `_ +- Image Uploading: + - Update user status with an image + - Change user avatar + - Change user background image + - Change user banner image +- OAuth 2 Application Only (read-only) Support +- Support for Twitter's Streaming API +- Seamless Python 3 support! + +Installation +------------ + +Install Twython via `pip `_ + +.. code-block:: bash + + $ pip install twython + +or, with `easy_install `_ + +.. code-block:: bash + + $ easy_install twython + +But, hey... `that's up to you `_. + +Or, if you want the code that is currently on GitHub + +.. code-block:: bash + + git clone git://github.com/ryanmcgrath/twython.git + cd twython + python setup.py install + +Documentation +------------- + +Documentation is available at https://twython.readthedocs.org/en/latest/ + +Starting Out +------------ + +First, you'll want to head over to https://dev.twitter.com/apps and register an application! + +After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. + +The most common type of authentication is Twitter user authentication using OAuth 1. If you're a web app planning to have users sign up with their Twitter account and interact with their timelines, updating their status, and stuff like that this **is** the authentication for you! + +First, you'll want to import Twython + +.. code-block:: python + + from twython import Twython + +Authentication +~~~~~~~~~~~~~~ + +Obtain Authorization URL +^^^^^^^^^^^^^^^^^^^^^^^^ + +Now, you'll want to create a Twython instance with your ``Consumer Key`` and ``Consumer Secret`` + + Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application + + Desktop and Mobile Applications **do not** require a callback_url + +.. code-block:: python + + APP_KEY = 'YOUR_APP_KEY' + APP_SECRET = 'YOUR_APP_SECRET' + + twitter = Twython(APP_KEY, APP_SECRET) + + auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') + +From the ``auth`` variable, save the ``oauth_token`` and ``oauth_token_secret`` for later use (these are not the final auth tokens). In Django or other web frameworks, you might want to store it to a session variable + +.. code-block:: python + + OAUTH_TOKEN = auth['oauth_token'] + OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] + +Send the user to the authentication url, you can obtain it by accessing + +.. code-block:: python + + auth['auth_url'] + +Handling the Callback +^^^^^^^^^^^^^^^^^^^^^ + + If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code + +After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in ``get_authentication_tokens`` + +You'll want to extract the ``oauth_verifier`` from the url. + +Django example: + +.. code-block:: python + + oauth_verifier = request.GET['oauth_verifier'] + +Now that you have the ``oauth_verifier`` stored to a variable, you'll want to create a new instance of Twython and grab the final user tokens + +.. code-block:: python + + twitter = Twython(APP_KEY, APP_SECRET, + OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + + final_step = twitter.get_authorized_tokens(oauth_verifier) + +Once you have the final user tokens, store them in a database for later use:: + + OAUTH_TOKEN = final_step['oauth_token'] + OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] + +For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ + +Dynamic Function Arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Keyword arguments to functions are mapped to the functions available for each endpoint in the Twitter API docs. Doing this allows us to be incredibly flexible in querying the Twitter API, so changes to the API aren't held up from you using them by this library. + +Basic Usage +----------- + +**Function definitions (i.e. get_home_timeline()) can be found by reading over twython/endpoints.py** + +Create a Twython instance with your application keys and the users OAuth tokens + +.. code-block:: python + + from twython import Twython + twitter = Twython(APP_KEY, APP_SECRET, + OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + +Authenticated Users Home Timeline +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline + +.. code-block:: python + + twitter.get_home_timeline() + +Updating Status +~~~~~~~~~~~~~~~ + +This method makes use of dynamic arguments, `read more about them `_ + +Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update + +.. code-block:: python + + twitter.update_status(status='See how easy using Twython is!') + +Searching +~~~~~~~~~ + + https://dev.twitter.com/docs/api/1.1/get/search/tweets says it takes "q" and "result_type" amongst other arguments + +.. code-block:: python + + twitter.search(q='twitter') + twitter.search(q='twitter', result_type='popular') + +Advanced Usage +-------------- + +- `Advanced Twython Usage `_ +- `Streaming with Twython `_ + + +Notes +----- + +- Twython 3.0.0 has been injected with 1000mgs of pure awesomeness! OAuth 2 application authentication is now supported. And a *whole lot* more! See the `CHANGELOG `_ for more details! + +Questions, Comments, etc? +------------------------- + +My hope is that Twython is so simple that you'd never *have* to ask any questions, but if you feel the need to contact me for this (or other) reasons, you can hit me up at ryan@venodesigns.net. + +Or if I'm to busy to answer, feel free to ping mikeh@ydekproductions.com as well. + +Follow us on Twitter: + +- `@ryanmcgrath `_ +- `@mikehelmick `_ + +Want to help? +------------- + +Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! diff --git a/docs/_themes/basicstrap/customsidebar.html b/docs/_themes/basicstrap/customsidebar.html index dbeab99..ac8c583 100644 --- a/docs/_themes/basicstrap/customsidebar.html +++ b/docs/_themes/basicstrap/customsidebar.html @@ -1,5 +1,18 @@ +

{{ _('Donate') }}

+ +

+Find Twython useful? Consider supporting the author on Gittip: +

+ +

+ +

+

{{ _('Links') }}

+ \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 3ffa5b5..480a355 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2013, Ryan McGrath' # built documents. # # The short X.Y version. -version = '3.8.0' +version = '3.3.0' # The full version, including alpha/beta/rc tags. -release = '3.8.0' +release = '3.3.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/index.rst b/docs/index.rst index 05d2838..1aa13e0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ Features - Twitter lists - Timelines - Direct Messages - - and anything found in `the Twitter API docs `_. + - and anything found in `the Twitter API docs `_. - Image Uploading: - Update user status with an image - Change user avatar diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index df3bc48..d72b79f 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -29,27 +29,11 @@ This uploads an image as a media object and associates it with a status update. photo = open('/path/to/file/image.jpg', 'rb') response = twitter.upload_media(media=photo) twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) - + Documentation: -* https://developer.twitter.com/en/docs/api-reference-index -* https://developer.twitter.com/en/docs/media/upload-media/overview - -Updating Status with Video --------------------------- - -This uploads a video as a media object and associates it with a status update. - -.. code-block:: python - - video = open('/path/to/file/video.mp4', 'rb') - response = twitter.upload_video(media=video, media_type='video/mp4') - twitter.update_status(status='Checkout this cool video!', media_ids=[response['media_id']]) - -Documentation: - -* https://developer.twitter.com/en/docs/api-reference-index -* https://developer.twitter.com/en/docs/media/upload-media/overview +* https://dev.twitter.com/rest/reference/post/statuses/update +* https://dev.twitter.com/rest/reference/post/media/upload Posting a Status with an Editing Image -------------------------------------- @@ -59,10 +43,10 @@ with a status update. .. code-block:: python - # Assuming that you are working with a JPEG + # Assume you are working with a JPEG from PIL import Image - from io import BytesIO + from StringIO import StringIO photo = Image.open('/path/to/file/image.jpg') @@ -71,13 +55,14 @@ with a status update. height = int((float(photo.size[1]) * float(wpercent))) photo = photo.resize((basewidth, height), Image.ANTIALIAS) - image_io = BytesIO() + image_io = StringIO.StringIO() photo.save(image_io, format='JPEG') # If you do not seek(0), the image will be at the end of the file and # unable to be read image_io.seek(0) + response = twitter.upload_media(media=image_io) twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) @@ -97,7 +82,7 @@ That being said, Twython offers a generator for search results and can be access results = twitter.cursor(twitter.search, q='python') for result in results: - print(result) + print result Manipulate the Request (headers, proxies, etc.) ----------------------------------------------- diff --git a/docs/usage/basic_usage.rst b/docs/usage/basic_usage.rst index 8fee807..84ffb08 100644 --- a/docs/usage/basic_usage.rst +++ b/docs/usage/basic_usage.rst @@ -28,7 +28,7 @@ Create a Twython instance with your application keys and the users OAuth tokens User Information ^^^^^^^^^^^^^^^^ -Documentation: https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials +Documentation: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials .. code-block:: python @@ -37,7 +37,7 @@ Documentation: https://developer.twitter.com/en/docs/accounts-and-users/manage-a Authenticated Users Home Timeline ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline +Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline .. code-block:: python @@ -48,7 +48,7 @@ Updating Status This method makes use of dynamic arguments, :ref:`read more about them ` -Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update +Documentation: https://dev.twitter.com/docs/api/1.1/post/statuses/update .. code-block:: python @@ -72,7 +72,7 @@ Searching .. note:: Searching can be done whether you're authenticated via OAuth 1 or OAuth 2 -Documentation: https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets +Documentation: https://dev.twitter.com/docs/api/1.1/get/search/tweets .. code-block:: python @@ -80,7 +80,7 @@ Documentation: https://developer.twitter.com/en/docs/tweets/search/api-reference .. _dynamicargexplaination: -.. important:: To help explain :ref:`dynamic function arguments ` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation `_ that the search call accepts the parameter "q". You can pass mutiple keyword arguments. The search documentation also specifies that the call accepts the parameter "result_type" +.. important:: To help explain :ref:`dynamic function arguments ` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation `_ that the search call accepts the parameter "q". You can pass mutiple keyword arguments. The search documentation also specifies that the call accepts the parameter "result_type" .. code-block:: python diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index 301abd3..c89da38 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -25,7 +25,7 @@ The Old Way results = twitter.search(q='twitter') if results.get('statuses'): for result in results['statuses']: - print(result['id_str']) + print result['id_str'] The New Way ^^^^^^^^^^^ @@ -39,28 +39,15 @@ The New Way results = twitter.cursor(twitter.search, q='twitter') for result in results: - print(result['id_str']) + print result['id_str'] Another example: .. code-block:: python - results = twitter.cursor(twitter.get_mentions_timeline) + results = twitter.cursor(t.get_mentions_timeline) for result in results: - print(result['id_str']) - -Items vs Pages -^^^^^^^^^^^^^^ - -By default, the cursor yields one item at a time. If instead you prefer to work with entire pages of results, specify ``return_pages=True`` as a keyword argument. - -.. code-block:: python - - results = twitter.cursor(twitter.get_mentions_timeline, return_pages=True) - # page is a list - for page in results: - for result in page: - print(result['id_str']) + print result['id_str'] HTML for Tweet @@ -79,17 +66,17 @@ This function takes a tweet object received from the Twitter API and returns an include_rts=True) for tweet in user_tweets: tweet['text'] = Twython.html_for_tweet(tweet) - print(tweet['text']) + print tweet['text'] The above code takes all the tweets from a specific users timeline, loops over them and replaces the value of ``tweet['text']`` with HTML. So: - http://t.co/FCmXyI6VHd is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 + http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 will be replaced with: - google.com is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM github.com pic.twitter.com/N6InAO4B71 + google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71 .. note:: When converting the string to HTML we add a class to each HTML tag so that you can maninpulate the DOM later on. @@ -97,7 +84,6 @@ will be replaced with: - For media urls that are replaced we add ``class="twython-media"`` to the anchor tag - For user mentions that are replaced we add ``class="twython-mention"`` to the anchor tag - For hashtags that are replaced we add ``class="twython-hashtag"`` to the anchor tag -- For symbols that are replaced we add ``class="twython-symbol"`` to the anchor tag This function accepts two parameters: ``use_display_url`` and ``use_expanded_url`` By default, ``use_display_url`` is ``True``. Meaning the link displayed in the tweet text will appear as (ex. google.com, github.com) diff --git a/docs/usage/starting_out.rst b/docs/usage/starting_out.rst index 7bb3d94..0a681ac 100644 --- a/docs/usage/starting_out.rst +++ b/docs/usage/starting_out.rst @@ -10,7 +10,7 @@ This section is going to help you understand creating a Twitter Application, aut Beginning --------- -First, you'll want to head over to https://apps.twitter.com/ and register an application! +First, you'll want to head over to https://dev.twitter.com/apps and register an application! After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. diff --git a/docs/usage/streaming_api.rst b/docs/usage/streaming_api.rst index 97ef6ea..8b21c70 100644 --- a/docs/usage/streaming_api.rst +++ b/docs/usage/streaming_api.rst @@ -5,7 +5,7 @@ Streaming API This section will cover how to use Twython and interact with the Twitter Streaming API. -Streaming Documentation: https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types +Streaming Documentation: https://dev.twitter.com/docs/streaming-apis .. important:: The Streaming API requires that you have OAuth 1 authentication credentials. If you don't have credentials, head over to the :ref:`authentication section ` and find out how! @@ -27,10 +27,10 @@ Now set up how you want to handle the signals. class MyStreamer(TwythonStreamer): def on_success(self, data): if 'text' in data: - print(data['text']) + print data['text'].encode('utf-8') def on_error(self, status_code, data): - print(status_code) + print status_code # Want to stop trying to get data because of the error? # Uncomment the next line! diff --git a/examples/get_direct_messages.py b/examples/get_direct_messages.py deleted file mode 100644 index fa80865..0000000 --- a/examples/get_direct_messages.py +++ /dev/null @@ -1,5 +0,0 @@ -from twython import Twython, TwythonError -twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - -get_list = twitter.get_direct_messages() -print(get_list) diff --git a/examples/get_user_timeline.py b/examples/get_user_timeline.py index 55ab427..0a1f4df 100644 --- a/examples/get_user_timeline.py +++ b/examples/get_user_timeline.py @@ -7,4 +7,4 @@ try: except TwythonError as e: print e -print(user_timeline) +print user_timeline diff --git a/setup.py b/setup.py index a3600de..6d05baf 100755 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ try: except ImportError: from distutils.core import setup -__author__ = 'Ryan McGrath ' -__version__ = '3.9.1' +__author__ = 'Ryan McGrath ' +__version__ = '3.3.0' packages = [ 'twython', @@ -24,15 +24,15 @@ setup( name='twython', version=__version__, install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], - python_requires='>=3.5', author='Ryan McGrath', - author_email='ryan@rymc.io', - license='MIT', + author_email='ryan@venodesigns.net', + license=open('LICENSE').read(), url='https://github.com/ryanmcgrath/twython/tree/master', keywords='twitter search api tweet twython stream', - description='Actively maintained, pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs', - long_description=open('README.md', encoding='utf-8').read() + '\n\n' +open('HISTORY.md', encoding='utf-8').read(), - long_description_content_type='text/markdown', + description='Actively maintained, pure Python wrapper for the \ + Twitter API. Supports both normal and streaming Twitter APIs', + long_description=open('README.rst').read() + '\n\n' + + open('HISTORY.rst').read(), include_package_data=True, packages=packages, classifiers=[ @@ -41,11 +41,6 @@ setup( 'License :: OSI Approved :: MIT License', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Communications :: Chat', - 'Topic :: Internet', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', + 'Topic :: Internet' ] ) diff --git a/tests/config.py b/tests/config.py index 10ab3ea..21baa69 100644 --- a/tests/config.py +++ b/tests/config.py @@ -1,8 +1,10 @@ -# -*- coding: utf-8 -*- import os import sys -import unittest +if sys.version_info[0] == 2 and sys.version_info[1] == 6: + import unittest2 as unittest +else: + import unittest app_key = os.environ.get('APP_KEY') app_secret = os.environ.get('APP_SECRET') @@ -24,3 +26,5 @@ test_list_slug = os.environ.get('TEST_LIST_SLUG', 'team') test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 'twitterapi') +test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' diff --git a/tests/test_core.py b/tests/test_core.py index 4c215d4..1aa0eea 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError -from .config import unittest +from .config import ( + test_tweet_object, test_tweet_html, unittest +) import responses import requests @@ -298,15 +299,21 @@ class TwythonAPITestCase(unittest.TestCase): """Test encoding UTF-8 works""" self.api.encode('Twython is awesome!') - def test_cursor_requires_twython_function(self): - """Test that cursor() raises when called without a Twython function""" - def init_and_iterate_cursor(*args, **kwargs): - cursor = self.api.cursor(*args, **kwargs) - return next(cursor) + def test_html_for_tweet(self): + """Test HTML for Tweet returns what we want""" + tweet_text = self.api.html_for_tweet(test_tweet_object) + self.assertEqual(test_tweet_html, tweet_text) - non_function = object() - non_twython_function = lambda x: x - - self.assertRaises(TypeError, init_and_iterate_cursor, non_function) - self.assertRaises(TwythonError, init_and_iterate_cursor, non_twython_function) + def test_html_for_tweet_expanded_url(self): + """Test using expanded url in HTML for Tweet displays full urls""" + tweet_text = self.api.html_for_tweet(test_tweet_object, + use_expanded_url=True) + # Make sure full url is in HTML + self.assertTrue('http://google.com' in tweet_text) + def test_html_for_tweet_short_url(self): + """Test using expanded url in HTML for Tweet displays full urls""" + tweet_text = self.api.html_for_tweet(test_tweet_object, False) + # Make sure HTML doesn't contain the display OR expanded url + self.assertTrue('http://google.com' not in tweet_text) + self.assertTrue('google.com' not in tweet_text) diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index ecdd553..aa79998 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -4,7 +4,7 @@ from .config import ( app_key, app_secret, oauth_token, oauth_token_secret, protected_twitter_1, protected_twitter_2, screen_name, test_tweet_id, test_list_slug, test_list_owner_screen_name, - access_token, unittest + access_token, test_tweet_object, test_tweet_html, unittest ) import time diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py deleted file mode 100644 index 921dfee..0000000 --- a/tests/test_html_for_tweet.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -import json -import os - -from twython import Twython, TwythonError - -from .config import unittest - - -class TestHtmlForTweetTestCase(unittest.TestCase): - def setUp(self): - self.api = Twython('', '', '', '') - - def load_tweet(self, name): - f = open(os.path.join( - os.path.dirname(__file__), - 'tweets', - '%s.json' % name - )) - tweet = json.load(f) - f.close() - return tweet - - def test_basic(self): - """Test HTML for Tweet returns what we want""" - tweet_object = self.load_tweet('basic') - tweet_text = self.api.html_for_tweet(tweet_object) - self.assertEqual(tweet_text, - 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71') - - def test_reply(self): - """Test HTML for Tweet links the replied-to username.""" - tweet_object = self.load_tweet('reply') - tweet_text = self.api.html_for_tweet(tweet_object) - self.assertEqual(tweet_text, - u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') - - def test_expanded_url(self): - """Test using expanded url in HTML for Tweet displays full urls""" - tweet_object = self.load_tweet('basic') - tweet_text = self.api.html_for_tweet(tweet_object, - use_expanded_url=True) - # Make sure full url is in HTML - self.assertTrue('http://google.com' in tweet_text) - - def test_short_url(self): - """Test using expanded url in HTML for Tweet displays full urls""" - tweet_object = self.load_tweet('basic') - tweet_text = self.api.html_for_tweet(tweet_object, False) - # Make sure HTML doesn't contain the display OR expanded url - self.assertTrue('http://google.com' not in tweet_text) - self.assertTrue('google.com' not in tweet_text) - - def test_identical_urls(self): - """If the 'url's for different url entities are identical, they should link correctly.""" - tweet_object = self.load_tweet('identical_urls') - tweet_text = self.api.html_for_tweet(tweet_object) - self.assertEqual(tweet_text, - u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') - - def test_symbols(self): - tweet_object = self.load_tweet('symbols') - tweet_text = self.api.html_for_tweet(tweet_object) - # Should only link symbols listed in entities: - self.assertTrue('$AAPL' in tweet_text) - self.assertTrue('$ANOTHER' not in tweet_text) - - def test_no_symbols(self): - """Should still work if tweet object has no symbols list""" - tweet = self.load_tweet('symbols') - # Save a copy: - symbols = tweet['entities']['symbols'] - del tweet['entities']['symbols'] - tweet_text = self.api.html_for_tweet(tweet) - self.assertTrue('symbols: $AAPL and' in tweet_text) - self.assertTrue('and $ANOTHER and $A.' in tweet_text) - - def test_compatmode(self): - tweet_object = self.load_tweet('compat') - tweet_text = self.api.html_for_tweet(tweet_object) - # link to compat web status link - self.assertTrue( - u'twitter.com/i/web/status/7…' in tweet_text) - - def test_extendedmode(self): - tweet_object = self.load_tweet('extended') - tweet_text = self.api.html_for_tweet(tweet_object) - # full tweet rendered with suffix - self.assertEqual(tweet_text, - 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC') - - def test_entities_with_prefix(self): - """ - If there is a username mention at the start of a tweet it's in the - "prefix" and so isn't part of the main tweet display text. - But its length is still counted in the indices of any subsequent - mentions, urls, hashtags, etc. - """ - self.maxDiff = 2200 - tweet_object = self.load_tweet('entities_with_prefix') - tweet_text = self.api.html_for_tweet(tweet_object) - self.assertEqual(tweet_text, - u'@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and X for good measure AND that is longer than 140 characters. example.com') - - def test_media(self): - tweet_object = self.load_tweet('media') - tweet_text = self.api.html_for_tweet(tweet_object) - - self.assertEqual( - u"""I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2\u2026 pic.twitter.com/OwNc6uJklg""", - tweet_text) - - def test_quoted(self): - "With expand_quoted_status=True it should include a quoted tweet." - tweet_object = self.load_tweet('quoted') - tweet_text = self.api.html_for_tweet(tweet_object, - expand_quoted_status=True) - self.assertEqual( - u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", - tweet_text) - - def test_retweet(self): - "With expand_quoted_status=True it should include a quoted tweet." - tweet_object = self.load_tweet('retweet') - tweet_text = self.api.html_for_tweet(tweet_object) - - self.assertEqual( - u"""My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.""", - tweet_text) - diff --git a/tests/tweets/basic.json b/tests/tweets/basic.json deleted file mode 100644 index 7243cd8..0000000 --- a/tests/tweets/basic.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "contributors":null, - "truncated":false, - "text":"http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71", - "in_reply_to_status_id":null, - "id":349683012054683648, - "favorite_count":0, - "source":"Twitter Web Client", - "retweeted":false, - "coordinates":null, - "entities":{ - "symbols":[ - - ], - "user_mentions":[ - { - "id":29251354, - "indices":[ - 45, - 57 - ], - "id_str":"29251354", - "screen_name":"mikehelmick", - "name":"Mike Helmick" - }, - { - "id":1431865928, - "indices":[ - 81, - 93 - ], - "id_str":"1431865928", - "screen_name":"__twython__", - "name":"Twython" - } - ], - "hashtags":[ - { - "indices":[ - 28, - 33 - ], - "text":"cool" - }, - { - "indices":[ - 62, - 73 - ], - "text":"checkitout" - } - ], - "urls":[ - { - "url":"http://t.co/FCmXyI6VHd", - "indices":[ - 0, - 22 - ], - "expanded_url":"http://google.com", - "display_url":"google.com" - }, - { - "url":"https://t.co/67pwRvY6z9", - "indices":[ - 94, - 117 - ], - "expanded_url":"https://github.com", - "display_url":"github.com" - } - ], - "media":[ - { - "id":537884378513162240, - "id_str":"537884378513162240", - "indices":[ - 118, - 140 - ], - "media_url":"http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", - "media_url_https":"https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", - "url":"http://t.co/N6InAO4B71", - "display_url":"pic.twitter.com/N6InAO4B71", - "expanded_url":"http://twitter.com/pingofglitch/status/537884380060844032/photo/1", - "type":"photo", - "sizes":{ - "large":{ - "w":1024, - "h":640, - "resize":"fit" - }, - "thumb":{ - "w":150, - "h":150, - "resize":"crop" - }, - "medium":{ - "w":600, - "h":375, - "resize":"fit" - }, - "small":{ - "w":340, - "h":212, - "resize":"fit" - } - } - } - ] - }, - "in_reply_to_screen_name":null, - "id_str":"349683012054683648", - "retweet_count":0, - "in_reply_to_user_id":null, - "favorited":false, - "user":{ - "follow_request_sent":false, - "profile_use_background_image":true, - "default_profile_image":true, - "id":1431865928, - "verified":false, - "profile_text_color":"333333", - "profile_image_url_https":"https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", - "profile_sidebar_fill_color":"DDEEF6", - "entities":{ - "description":{ - "urls":[ - - ] - } - }, - "followers_count":1, - "profile_sidebar_border_color":"C0DEED", - "id_str":"1431865928", - "profile_background_color":"3D3D3D", - "listed_count":0, - "profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme1/bg.png", - "utc_offset":null, - "statuses_count":2, - "description":"", - "friends_count":1, - "location":"", - "profile_link_color":"0084B4", - "profile_image_url":"http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", - "following":false, - "geo_enabled":false, - "profile_background_image_url":"http://a0.twimg.com/images/themes/theme1/bg.png", - "screen_name":"__twython__", - "lang":"en", - "profile_background_tile":false, - "favourites_count":0, - "name":"Twython", - "notifications":false, - "url":null, - "created_at":"Thu May 16 01:11:09 +0000 2013", - "contributors_enabled":false, - "time_zone":null, - "protected":false, - "default_profile":false, - "is_translator":false - }, - "geo":null, - "in_reply_to_user_id_str":null, - "possibly_sensitive":false, - "lang":"en", - "created_at":"Wed Jun 26 00:18:21 +0000 2013", - "in_reply_to_status_id_str":null, - "place":null -} diff --git a/tests/tweets/compat.json b/tests/tweets/compat.json deleted file mode 100644 index b01b358..0000000 --- a/tests/tweets/compat.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "contributors":null, - "truncated":true, - "text":"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", - "is_quote_status":false, - "in_reply_to_status_id":null, - "id":777915304261193728, - "favorite_count":13856, - "source":"Twitter Web Client", - "retweeted":false, - "coordinates":null, - "entities":{ - "symbols":[ - - ], - "user_mentions":[ - - ], - "hashtags":[ - - ], - "urls":[ - { - "url":"https://t.co/SRmsuks2ru", - "indices":[ - 117, - 140 - ], - "expanded_url":"https://twitter.com/i/web/status/777915304261193728", - "display_url":"twitter.com/i/web/status/7\u2026" - } - ] - }, - "in_reply_to_screen_name":null, - "id_str":"777915304261193728", - "retweet_count":14767, - "in_reply_to_user_id":null, - "favorited":false, - "user":{ - "name":"Twitter", - "screen_name":"twitter" - }, - "geo":null, - "in_reply_to_user_id_str":null, - "possibly_sensitive":false, - "possibly_sensitive_appealable":false, - "lang":"en", - "created_at":"Mon Sep 19 17:00:36 +0000 2016", - "in_reply_to_status_id_str":null, - "place":null -} diff --git a/tests/tweets/entities_with_prefix.json b/tests/tweets/entities_with_prefix.json deleted file mode 100644 index 8ba8975..0000000 --- a/tests/tweets/entities_with_prefix.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "created_at":"Sat Jan 06 18:56:35 +0000 2018", - "id":949716340755091458, - "id_str":"949716340755091458", - "full_text":"@philgyford This is a test for @visionphil that includes a link https://t.co/sKw4J3A8SZ and #hashtag and X for good measure AND that is longer than 140 characters. https://t.co/jnQdy7Zg7u", - "truncated":false, - "display_text_range":[ 12, 187 ], - "entities":{ - "hashtags":[ - { - "text":"hashtag", - "indices":[ 92, 100 ] - } - ], - "symbols":[ ], - "user_mentions":[ - { - "screen_name":"philgyford", - "name":"Phil Gyford", - "id":12552, - "id_str":"12552", - "indices":[ 0, 11 ] - }, - { - "screen_name":"visionphil", - "name":"Vision Phil", - "id":104456050, - "id_str":"104456050", - "indices":[ 31, 42 ] - } - ], - "urls":[ - { - "url":"https://t.co/sKw4J3A8SZ", - "expanded_url":"http://example.org", - "display_url":"example.org", - "indices":[ 64, 87 ] - }, - { - "url":"https://t.co/jnQdy7Zg7u", - "expanded_url":"http://example.com", - "display_url":"example.com", - "indices":[ 164, 187 ] - } - ] - }, - "source":"Tweetbot for Mac", - "in_reply_to_status_id":948561036889722880, - "in_reply_to_status_id_str":"948561036889722880", - "in_reply_to_user_id":12552, - "in_reply_to_user_id_str":"12552", - "in_reply_to_screen_name":"philgyford", - "user":{ - "id":2030131, - "id_str":"2030131" - }, - "geo":null, - "coordinates":null, - "place":null, - "contributors":null, - "is_quote_status":false, - "retweet_count":0, - "favorite_count":0, - "favorited":false, - "retweeted":false, - "possibly_sensitive":false, - "lang":"en" -} diff --git a/tests/tweets/extended.json b/tests/tweets/extended.json deleted file mode 100644 index 105700a..0000000 --- a/tests/tweets/extended.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "full_text":"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", - "truncated":false, - "is_quote_status":false, - "in_reply_to_status_id":null, - "id":777915304261193728, - "favorite_count":13856, - "contributors":null, - "source":"Twitter Web Client", - "retweeted":false, - "coordinates":null, - "entities":{ - "symbols":[ - - ], - "user_mentions":[ - - ], - "hashtags":[ - - ], - "urls":[ - - ], - "media":[ - { - "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", - "sizes":{ - "small":{ - "h":340, - "w":340, - "resize":"fit" - }, - "large":{ - "h":700, - "w":700, - "resize":"fit" - }, - "medium":{ - "h":600, - "w":600, - "resize":"fit" - }, - "thumb":{ - "h":150, - "w":150, - "resize":"crop" - } - }, - "url":"https://t.co/I9pUC0NdZC", - "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", - "id_str":"777914712382058496", - "indices":[ - 140, - 163 - ], - "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", - "type":"photo", - "id":777914712382058496, - "display_url":"pic.twitter.com/I9pUC0NdZC" - } - ] - }, - "in_reply_to_screen_name":null, - "id_str":"777915304261193728", - "display_text_range":[ - 0, - 139 - ], - "retweet_count":14767, - "in_reply_to_user_id":null, - "favorited":false, - "user":{ - "follow_request_sent":false, - "has_extended_profile":false, - "profile_use_background_image":true, - "id":783214, - "verified":true, - "profile_text_color":"333333", - "profile_image_url_https":"https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", - "profile_sidebar_fill_color":"F6F6F6", - "is_translator":false, - "geo_enabled":true, - "entities":{ - "url":{ - "urls":[ - { - "url":"http://t.co/5iRhy7wTgu", - "indices":[ - 0, - 22 - ], - "expanded_url":"http://blog.twitter.com/", - "display_url":"blog.twitter.com" - } - ] - }, - "description":{ - "urls":[ - { - "url":"https://t.co/qq1HEzvnrA", - "indices":[ - 84, - 107 - ], - "expanded_url":"http://support.twitter.com", - "display_url":"support.twitter.com" - } - ] - } - }, - "followers_count":56827498, - "protected":false, - "location":"San Francisco, CA", - "default_profile_image":false, - "id_str":"783214", - "lang":"en", - "utc_offset":-25200, - "statuses_count":3161, - "description":"Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.", - "friends_count":145, - "profile_link_color":"226699", - "profile_image_url":"http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", - "notifications":false, - "profile_background_image_url_https":"https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", - "profile_background_color":"ACDED6", - "profile_banner_url":"https://pbs.twimg.com/profile_banners/783214/1471929200", - "profile_background_image_url":"http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", - "name":"Twitter", - "is_translation_enabled":false, - "profile_background_tile":true, - "favourites_count":2332, - "screen_name":"twitter", - "url":"http://t.co/5iRhy7wTgu", - "created_at":"Tue Feb 20 14:35:54 +0000 2007", - "contributors_enabled":false, - "time_zone":"Pacific Time (US & Canada)", - "profile_sidebar_border_color":"FFFFFF", - "default_profile":false, - "following":false, - "listed_count":90445 - }, - "geo":null, - "in_reply_to_user_id_str":null, - "possibly_sensitive":false, - "possibly_sensitive_appealable":false, - "lang":"en", - "created_at":"Mon Sep 19 17:00:36 +0000 2016", - "in_reply_to_status_id_str":null, - "place":null, - "extended_entities":{ - "media":[ - { - "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", - "display_url":"pic.twitter.com/I9pUC0NdZC", - "url":"https://t.co/I9pUC0NdZC", - "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", - "video_info":{ - "aspect_ratio":[ - 1, - 1 - ], - "variants":[ - { - "url":"https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4", - "bitrate":0, - "content_type":"video/mp4" - } - ] - }, - "id_str":"777914712382058496", - "sizes":{ - "small":{ - "h":340, - "w":340, - "resize":"fit" - }, - "large":{ - "h":700, - "w":700, - "resize":"fit" - }, - "medium":{ - "h":600, - "w":600, - "resize":"fit" - }, - "thumb":{ - "h":150, - "w":150, - "resize":"crop" - } - }, - "indices":[ - 140, - 163 - ], - "type":"animated_gif", - "id":777914712382058496, - "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg" - } - ] - } -} diff --git a/tests/tweets/identical_urls.json b/tests/tweets/identical_urls.json deleted file mode 100644 index 69840f9..0000000 --- a/tests/tweets/identical_urls.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "entities":{ - "hashtags":[ - - ], - "user_mentions":[ - - ], - "symbols":[ - - ], - "urls":[ - { - "display_url":"buff.ly/2sEhrgO", - "expanded_url":"http://buff.ly/2sEhrgO", - "indices":[ - 42, - 65 - ], - "url":"https://t.co/W0uArTMk9N" - }, - { - "display_url":"buff.ly/2sEhrgO", - "expanded_url":"http://buff.ly/2sEhrgO", - "indices":[ - 101, - 124 - ], - "url":"https://t.co/W0uArTMk9N" - } - ] - }, - "full_text":"Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N" -} diff --git a/tests/tweets/media.json b/tests/tweets/media.json deleted file mode 100644 index 4366794..0000000 --- a/tests/tweets/media.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "contributors":null, - "truncated":false, - "is_quote_status":false, - "in_reply_to_status_id":null, - "id":905105588279013377, - "favorite_count":10, - "full_text":"I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg", - "source":"Twitter Web Client", - "retweeted":false, - "coordinates":null, - "entities":{ - "symbols":[ - - ], - "user_mentions":[ - - ], - "hashtags":[ - - ], - "urls":[ - { - "url":"https://t.co/2yUmmn3TOc", - "indices":[ - 107, - 130 - ], - "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", - "display_url":"gyford.com/phil/writing/2\u2026" - } - ], - "media":[ - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "display_url":"pic.twitter.com/OwNc6uJklg", - "url":"https://t.co/OwNc6uJklg", - "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", - "id_str":"905105571765944320", - "sizes":{ - "small":{ - "h":256, - "resize":"fit", - "w":680 - }, - "large":{ - "h":376, - "resize":"fit", - "w":1000 - }, - "medium":{ - "h":376, - "resize":"fit", - "w":1000 - }, - "thumb":{ - "h":150, - "resize":"crop", - "w":150 - } - }, - "indices":[ - 131, - 154 - ], - "type":"photo", - "id":905105571765944320, - "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" - } - ] - }, - "in_reply_to_screen_name":null, - "in_reply_to_user_id":null, - "display_text_range":[ - 0, - 130 - ], - "retweet_count":1, - "id_str":"905105588279013377", - "favorited":false, - "user":{ - "screen_name":"philgyford", - "name":"Phil Gyford" - }, - "geo":null, - "in_reply_to_user_id_str":null, - "possibly_sensitive":false, - "possibly_sensitive_appealable":false, - "lang":"en", - "created_at":"Tue Sep 05 16:29:22 +0000 2017", - "in_reply_to_status_id_str":null, - "place":null, - "extended_entities":{ - "media":[ - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "display_url":"pic.twitter.com/OwNc6uJklg", - "url":"https://t.co/OwNc6uJklg", - "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", - "id_str":"905105571765944320", - "sizes":{ - "small":{ - "h":256, - "resize":"fit", - "w":680 - }, - "large":{ - "h":376, - "resize":"fit", - "w":1000 - }, - "medium":{ - "h":376, - "resize":"fit", - "w":1000 - }, - "thumb":{ - "h":150, - "resize":"crop", - "w":150 - } - }, - "indices":[ - 131, - 154 - ], - "type":"photo", - "id":905105571765944320, - "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" - }, - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "display_url":"pic.twitter.com/OwNc6uJklg", - "url":"https://t.co/OwNc6uJklg", - "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", - "id_str":"905105572529393668", - "sizes":{ - "large":{ - "h":399, - "resize":"fit", - "w":1000 - }, - "small":{ - "h":271, - "resize":"fit", - "w":680 - }, - "medium":{ - "h":399, - "resize":"fit", - "w":1000 - }, - "thumb":{ - "h":150, - "resize":"crop", - "w":150 - } - }, - "indices":[ - 131, - 154 - ], - "type":"photo", - "id":905105572529393668, - "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg" - }, - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "display_url":"pic.twitter.com/OwNc6uJklg", - "url":"https://t.co/OwNc6uJklg", - "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", - "id_str":"905105573255016448", - "sizes":{ - "small":{ - "h":195, - "resize":"fit", - "w":680 - }, - "large":{ - "h":287, - "resize":"fit", - "w":1000 - }, - "medium":{ - "h":287, - "resize":"fit", - "w":1000 - }, - "thumb":{ - "h":150, - "resize":"crop", - "w":150 - } - }, - "indices":[ - 131, - 154 - ], - "type":"photo", - "id":905105573255016448, - "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg" - } - ] - } -} diff --git a/tests/tweets/quoted.json b/tests/tweets/quoted.json deleted file mode 100644 index c8e372b..0000000 --- a/tests/tweets/quoted.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "contributors":null, - "truncated":false, - "text":"Here\u2019s a quoted tweet. https://t.co/3neKzof0gT", - "is_quote_status":true, - "in_reply_to_status_id":null, - "id":917706313785905157, - "favorite_count":0, - "source":"Twitter Web Client", - "quoted_status_id":917699069916729344, - "retweeted":false, - "coordinates":null, - "quoted_status":{ - "contributors":null, - "truncated":false, - "text":"The quoted tweet text.", - "is_quote_status":false, - "in_reply_to_status_id":null, - "id":917699069916729344, - "favorite_count":1, - "source":"Twitter Web Client", - "retweeted":false, - "coordinates":null, - "entities":{ - "symbols":[ - - ], - "user_mentions":[ - - ], - "hashtags":[ - - ], - "urls":[ - - ] - }, - "in_reply_to_screen_name":null, - "in_reply_to_user_id":null, - "retweet_count":0, - "id_str":"917699069916729344", - "favorited":false, - "user":{ - "screen_name":"philgyford", - "name":"Phil Gyford" - }, - "geo":null, - "in_reply_to_user_id_str":null, - "lang":"ht", - "created_at":"Tue Oct 10 10:31:22 +0000 2017", - "in_reply_to_status_id_str":null, - "place":null - }, - "entities":{ - "symbols":[ - - ], - "user_mentions":[ - - ], - "hashtags":[ - - ], - "urls":[ - { - "url":"https://t.co/3neKzof0gT", - "indices":[ - 23, - 46 - ], - "expanded_url":"https://twitter.com/philgyford/status/917699069916729344", - "display_url":"twitter.com/philgyford/sta\u2026" - } - ] - }, - "in_reply_to_screen_name":null, - "in_reply_to_user_id":null, - "retweet_count":0, - "id_str":"917706313785905157", - "favorited":false, - "user":{ - "screen_name":"philgyfordtest", - "name":"Phil Gyford Test" - }, - "geo":null, - "in_reply_to_user_id_str":null, - "possibly_sensitive":false, - "possibly_sensitive_appealable":false, - "lang":"en", - "created_at":"Tue Oct 10 11:00:10 +0000 2017", - "quoted_status_id_str":"917699069916729344", - "in_reply_to_status_id_str":null, - "place":null -} diff --git a/tests/tweets/reply.json b/tests/tweets/reply.json deleted file mode 100644 index 2a22712..0000000 --- a/tests/tweets/reply.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "display_text_range":[ - 12, - 114 - ], - "in_reply_to_status_id_str":"742374355531923456", - "source":"Twitter Web Client", - "geo":null, - "full_text":"@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr", - "extended_entities":{ - "media":[ - - ] - }, - "id_str":"300", - "in_reply_to_status_id":742374355531923456, - "id":300, - "in_reply_to_screen_name":"philgyford", - "retweet_count":0, - "user":{ - - }, - "created_at":"Mon Jun 13 15:48:06 +0000 2016", - "lang":"en", - "favorite_count":0, - "coordinates":null, - "place":null, - "contributors":null, - "in_reply_to_user_id":12552, - "in_reply_to_user_id_str":"12552", - "retweeted":false, - "favorited":false, - "truncated":false, - "entities":{ - "user_mentions":[ - { - "id_str":"12552", - "id":12552, - "screen_name":"philgyford", - "name":"Phil Gyford", - "indices":[ - 0, - 11 - ] - } - ], - "media":[ - - ], - "hashtags":[ - - ], - "symbols":[ - - ], - "urls":[ - - ] - }, - "is_quote_status":false, - "possibly_sensitive":false -} diff --git a/tests/tweets/retweet.json b/tests/tweets/retweet.json deleted file mode 100644 index 046bb64..0000000 --- a/tests/tweets/retweet.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "coordinates":null, - "source":"web", - "in_reply_to_user_id_str":null, - "truncated":false, - "in_reply_to_user_id":null, - "favorite_count":0, - "user":{ - "name":"Phil Gyford Test", - "screen_name":"philgyfordtest" - }, - "favorited":false, - "retweeted_status":{ - "coordinates":null, - "source":"Twitter Web Client", - "in_reply_to_user_id_str":null, - "truncated":false, - "in_reply_to_user_id":null, - "favorite_count":21, - "user":{ - "name":"Samuel Pepys", - "screen_name":"samuelpepys" - }, - "favorited":false, - "id":917459832885653506, - "contributors":null, - "in_reply_to_screen_name":null, - "geo":null, - "in_reply_to_status_id_str":null, - "id_str":"917459832885653506", - "entities":{ - "hashtags":[ - - ], - "symbols":[ - - ], - "user_mentions":[ - - ], - "urls":[ - - ] - }, - "in_reply_to_status_id":null, - "text":"My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.", - "retweet_count":3, - "place":null, - "lang":"en", - "created_at":"Mon Oct 09 18:40:44 +0000 2017", - "is_quote_status":false, - "retweeted":false - }, - "id":917712989649801216, - "contributors":null, - "in_reply_to_screen_name":null, - "geo":null, - "in_reply_to_status_id_str":null, - "id_str":"917712989649801216", - "entities":{ - "hashtags":[ - - ], - "symbols":[ - - ], - "user_mentions":[ - { - "id_str":"14475268", - "indices":[ - 3, - 15 - ], - "name":"Samuel Pepys", - "id":14475268, - "screen_name":"samuelpepys" - } - ], - "urls":[ - - ] - }, - "in_reply_to_status_id":null, - "text":"RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.", - "retweet_count":3, - "place":null, - "lang":"en", - "created_at":"Tue Oct 10 11:26:41 +0000 2017", - "is_quote_status":false, - "retweeted":false -} diff --git a/tests/tweets/symbols.json b/tests/tweets/symbols.json deleted file mode 100644 index 7519a08..0000000 --- a/tests/tweets/symbols.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "text":"Some symbols: $AAPL and $PEP and $ANOTHER and $A.", - "contributors":null, - "geo":null, - "favorited":true, - "in_reply_to_user_id_str":null, - "user":{ - "screen_name":"philgyfordtest", - "name":"Phil Gyford Test" - }, - "in_reply_to_user_id":null, - "retweeted":false, - "coordinates":null, - "place":null, - "in_reply_to_status_id":null, - "lang":"en", - "in_reply_to_status_id_str":null, - "truncated":false, - "retweet_count":0, - "is_quote_status":false, - "id":662694880657989632, - "id_str":"662694880657989632", - "in_reply_to_screen_name":null, - "favorite_count":1, - "entities":{ - "hashtags":[ - - ], - "user_mentions":[ - - ], - "symbols":[ - { - "indices":[ - 14, - 19 - ], - "text":"AAPL" - }, - { - "indices":[ - 24, - 28 - ], - "text":"PEP" - }, - { - "indices":[ - 46, - 48 - ], - "text":"A" - } - ], - "urls":[ - - ] - }, - "created_at":"Fri Nov 06 18:15:46 +0000 2015", - "source":"Twitter Web Client" -} diff --git a/twython/__init__.py b/twython/__init__.py index 7d25ef3..3525f3c 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.9.1' +__version__ = '3.3.0' from .api import Twython from .streaming import TwythonStreamer diff --git a/twython/advisory.py b/twython/advisory.py index 9a0863a..31657ee 100644 --- a/twython/advisory.py +++ b/twython/advisory.py @@ -17,6 +17,6 @@ only TwythonDeprecationWarnings. class TwythonDeprecationWarning(DeprecationWarning): """Custom DeprecationWarning to be raised when methods/variables are being deprecated in Twython. Python 2.7 > ignores DeprecationWarning - so we want to specifically bubble up ONLY Twython Deprecation Warnings + so we want to specifcally bubble up ONLY Twython Deprecation Warnings """ pass diff --git a/twython/api.py b/twython/api.py index 1e786ad..321cf63 100644 --- a/twython/api.py +++ b/twython/api.py @@ -8,10 +8,8 @@ This module contains functionality for access to core Twitter API calls, Twitter Authentication, and miscellaneous methods that are useful when dealing with the Twitter API """ - -from __future__ import generator_stop import warnings -import re +import logging import requests from requests.auth import HTTPBasicAuth @@ -27,7 +25,6 @@ from .helpers import _transparent_params warnings.simplefilter('always', TwythonDeprecationWarning) # For Python 2.7 > - class Twython(EndpointsMixin, object): def __init__(self, app_key=None, app_secret=None, oauth_token=None, oauth_token_secret=None, access_token=None, @@ -105,10 +102,15 @@ class Twython(EndpointsMixin, object): auth = None if oauth_version == 1: # User Authentication is through OAuth 1 - if self.app_key is not None and self.app_secret is not None: - auth = OAuth1(self.app_key, self.app_secret, - self.oauth_token, self.oauth_token_secret) + if self.app_key is not None and self.app_secret is not None and \ + self.oauth_token is None and self.oauth_token_secret is None: + auth = OAuth1(self.app_key, self.app_secret) + if self.app_key is not None and self.app_secret is not None and \ + self.oauth_token is not None and self.oauth_token_secret is \ + not None: + auth = OAuth1(self.app_key, self.app_secret, + self.oauth_token, self.oauth_token_secret) elif oauth_version == 2 and self.access_token: # Application Authentication is through OAuth 2 token = {'token_type': token_type, @@ -136,17 +138,13 @@ class Twython(EndpointsMixin, object): def __repr__(self): return '' % (self.app_key) - def _request(self, url, method='GET', params=None, api_call=None, json_encoded=False): + def _request(self, url, method='GET', params=None, api_call=None): """Internal request method""" method = method.lower() params = params or {} func = getattr(self.client, method) - if isinstance(params, dict) and json_encoded is False: - params, files = _transparent_params(params) - else: - params = params - files = list() + params, files = _transparent_params(params) requests_args = {} for k, v in self.client_args.items(): @@ -154,22 +152,33 @@ class Twython(EndpointsMixin, object): if k in ('timeout', 'allow_redirects', 'stream', 'verify'): requests_args[k] = v - if method == 'get' or method == 'delete': + if method == 'get': requests_args['params'] = params else: - # Check for json_encoded so we will sent params as "data" or "json" - if json_encoded: - data_key = 'json' - else: - data_key = 'data' requests_args.update({ - data_key: params, + 'data': params, 'files': files, }) try: response = func(url, **requests_args) except requests.RequestException as e: - raise TwythonError(str(e)) + logging.exception('RequestException encountered - refreshing...') + # Rebuild Session and retry + s2 = requests.Session() + s2.auth = self.client.auth + s2.headers = self.client.headers + s2.cert = self.client.cert + s2.hooks = self.client.hooks + s2.proxies = self.client.proxies + s2.max_redirects = self.client.max_redirects + self.client.close() + + self.client = s2 + try: + response = func(url, **requests_args) + except requests.RequestException as e: + logging.exception('RequestException encountered after refresh!') + raise TwythonError(str(e)) # create stash for last function intel self._last_call = { @@ -203,16 +212,12 @@ class Twython(EndpointsMixin, object): error_message, error_code=response.status_code, retry_after=response.headers.get('X-Rate-Limit-Reset')) - content = '' + try: - if response.status_code == 204: - content = response.content - else: - content = response.json() + content = response.json() except ValueError: - if response.content != '': - raise TwythonError('Response was not valid JSON. \ - Unable to decode.') + raise TwythonError('Response was not valid JSON. \ + Unable to decode.') return content @@ -236,14 +241,14 @@ class Twython(EndpointsMixin, object): return error_message - def request(self, endpoint, method='GET', params=None, version='1.1', json_encoded=False): + def request(self, endpoint, method='GET', params=None, version='1.1'): """Return dict of response received from Twitter's API :param endpoint: (required) Full url or Twitter API endpoint (e.g. search/tweets) :type endpoint: string :param method: (optional) Method of accessing data, either - GET, POST or DELETE. (default GET) + GET or POST. (default GET) :type method: string :param params: (optional) Dict of parameters (if any) accepted the by Twitter API endpoint you are trying to @@ -252,9 +257,6 @@ class Twython(EndpointsMixin, object): :param version: (optional) Twitter API version to access (default 1.1) :type version: string - :param json_encoded: (optional) Flag to indicate if this method should send data encoded as json - (default False) - :type json_encoded: bool :rtype: dict """ @@ -270,7 +272,7 @@ class Twython(EndpointsMixin, object): url = '%s/%s.json' % (self.api_url % version, endpoint) content = self._request(url, method=method, params=params, - api_call=url, json_encoded=json_encoded) + api_call=url) return content @@ -278,13 +280,9 @@ class Twython(EndpointsMixin, object): """Shortcut for GET requests via :class:`request`""" return self.request(endpoint, params=params, version=version) - def post(self, endpoint, params=None, version='1.1', json_encoded=False): + def post(self, endpoint, params=None, version='1.1'): """Shortcut for POST requests via :class:`request`""" - return self.request(endpoint, 'POST', params=params, version=version, json_encoded=json_encoded) - - def delete(self, endpoint, params=None, version='1.1', json_encoded=False): - """Shortcut for delete requests via :class:`request`""" - return self.request(endpoint, 'DELETE', params=params, version=version, json_encoded=json_encoded) + return self.request(endpoint, 'POST', params=params, version=version) def get_lastfunction_header(self, header, default_return_value=None): """Returns a specific header from the last API call @@ -431,7 +429,7 @@ class Twython(EndpointsMixin, object): @staticmethod def construct_api_url(api_url, **params): - r"""Construct a Twitter API url, encoded, with parameters + """Construct a Twitter API url, encoded, with parameters :param api_url: URL of the Twitter API endpoint you are attempting to construct @@ -470,7 +468,7 @@ class Twython(EndpointsMixin, object): return self.cursor(self.search, q=search_query, **params) def cursor(self, function, return_pages=False, **params): - r"""Returns a generator for results that match a specified query. + """Returns a generator for results that match a specified query. :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) @@ -489,11 +487,6 @@ class Twython(EndpointsMixin, object): >>> print result """ - if not callable(function): - raise TypeError('.cursor() takes a Twython function as its first \ - argument. Did you provide the result of a \ - function call?') - if not hasattr(function, 'iter_mode'): raise TwythonError('Unable to create generator for Twython \ method "%s"' % function.__name__) @@ -502,7 +495,7 @@ class Twython(EndpointsMixin, object): content = function(**params) if not content: - return + raise StopIteration if hasattr(function, 'iter_key'): results = content.get(function.iter_key) @@ -517,7 +510,7 @@ class Twython(EndpointsMixin, object): if function.iter_mode == 'cursor' and \ content['next_cursor_str'] == '0': - return + raise StopIteration try: if function.iter_mode == 'id': @@ -530,7 +523,7 @@ class Twython(EndpointsMixin, object): params = dict(parse_qsl(next_results.query)) else: # No more results - return + raise StopIteration else: # Twitter gives tweets in reverse chronological order: params['max_id'] = str(int(content[-1]['id_str']) - 1) @@ -559,8 +552,8 @@ class Twython(EndpointsMixin, object): return str(text) @staticmethod - def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=False): - """Return HTML for a tweet (urls, mentions, hashtags, symbols replaced with links) + def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): + """Return HTML for a tweet (urls, mentions, hashtags replaced with links) :param tweet: Tweet object from received from Twitter API :param use_display_url: Use display URL to represent link @@ -576,134 +569,54 @@ class Twython(EndpointsMixin, object): if 'retweeted_status' in tweet: tweet = tweet['retweeted_status'] - if 'extended_tweet' in tweet: - tweet = tweet['extended_tweet'] - - orig_tweet_text = tweet.get('full_text') or tweet['text'] - - display_text_range = tweet.get('display_text_range') or [0, len(orig_tweet_text)] - display_text_start, display_text_end = display_text_range[0], display_text_range[1] - display_text = orig_tweet_text[display_text_start:display_text_end] - prefix_text = orig_tweet_text[0:display_text_start] - suffix_text = orig_tweet_text[display_text_end:len(orig_tweet_text)] - if 'entities' in tweet: - # We'll put all the bits of replacement HTML and their starts/ends - # in this list: - entities = [] + text = tweet['text'] + entities = tweet['entities'] # Mentions - if 'user_mentions' in tweet['entities']: - for entity in tweet['entities']['user_mentions']: - temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + for entity in entities['user_mentions']: + start, end = entity['indices'][0], entity['indices'][1] - mention_html = '@%(screen_name)s' % {'screen_name': entity['screen_name']} - - if display_text_start <= temp['start'] <= display_text_end: - temp['replacement'] = mention_html - temp['start'] -= display_text_start - temp['end'] -= display_text_start - entities.append(temp) - else: - # Make the '@username' at the start, before - # display_text, into a link: - sub_expr = r'(?)' + orig_tweet_text[temp['start']:temp['end']] + '(?!)' - prefix_text = re.sub(sub_expr, mention_html, prefix_text) + mention_html = '@%(screen_name)s' + text = text.replace(tweet['text'][start:end], + mention_html % {'screen_name': entity['screen_name']}) # Hashtags - if 'hashtags' in tweet['entities']: - for entity in tweet['entities']['hashtags']: - temp = {} - temp['start'] = entity['indices'][0] - display_text_start - temp['end'] = entity['indices'][1] - display_text_start + for entity in entities['hashtags']: + start, end = entity['indices'][0], entity['indices'][1] - url_html = '#%(hashtag)s' % {'hashtag': entity['text']} + hashtag_html = '#%(hashtag)s' + text = text.replace(tweet['text'][start:end], hashtag_html % {'hashtag': entity['text']}) - temp['replacement'] = url_html - entities.append(temp) - - # Symbols - if 'symbols' in tweet['entities']: - for entity in tweet['entities']['symbols']: - temp = {} - temp['start'] = entity['indices'][0] - display_text_start - temp['end'] = entity['indices'][1] - display_text_start - - url_html = '$%(symbol)s' % {'symbol': entity['text']} - - temp['replacement'] = url_html - entities.append(temp) - - # URLs - if 'urls' in tweet['entities']: - for entity in tweet['entities']['urls']: - temp = {} - temp['start'] = entity['indices'][0] - display_text_start - temp['end'] = entity['indices'][1] - display_text_start - - if use_display_url and entity.get('display_url') and not use_expanded_url: - shown_url = entity['display_url'] - elif use_expanded_url and entity.get('expanded_url'): - shown_url = entity['expanded_url'] - else: - shown_url = entity['url'] - - url_html = '%s' % (entity['url'], shown_url) - - if display_text_start <= temp['start'] <= display_text_end: - temp['replacement'] = url_html - entities.append(temp) - else: - suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) - - if 'media' in tweet['entities'] and len(tweet['entities']['media']) > 0: - # We just link to the overall URL for the tweet's media, - # rather than to each individual item. - # So, we get the URL from the first media item: - entity = tweet['entities']['media'][0] - - temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] - - if use_display_url and entity.get('display_url') and not use_expanded_url: + # Urls + for entity in entities['urls']: + start, end = entity['indices'][0], entity['indices'][1] + if use_display_url and entity.get('display_url') \ + and not use_expanded_url: shown_url = entity['display_url'] elif use_expanded_url and entity.get('expanded_url'): shown_url = entity['expanded_url'] else: shown_url = entity['url'] - url_html = '%s' % (entity['url'], shown_url) + url_html = '%s' + text = text.replace(tweet['text'][start:end], + url_html % (entity['url'], shown_url)) - if display_text_start <= temp['start'] <= display_text_end: - temp['replacement'] = url_html - entities.append(temp) - else: - suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) + # Media + if 'media' in entities: + for entity in entities['media']: + start, end = entity['indices'][0], entity['indices'][1] + if use_display_url and entity.get('display_url') \ + and not use_expanded_url: + shown_url = entity['display_url'] + elif use_expanded_url and entity.get('expanded_url'): + shown_url = entity['expanded_url'] + else: + shown_url = entity['url'] - # Now do all the replacements, starting from the end, so that the - # start/end indices still work: - for entity in sorted(entities, key=lambda e: e['start'], reverse=True): - display_text = display_text[0:entity['start']] + entity['replacement'] + display_text[entity['end']:] + url_html = '%s' + text = text.replace(tweet['text'][start:end], + url_html % (entity['url'], shown_url)) - quote_text = '' - if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'): - quoted_status = tweet['quoted_status'] - quote_text += '
%(quote)s' \ - '%(quote_user_name)s' \ - '@%(quote_user_screen_name)s' \ - '
' % \ - {'quote': Twython.html_for_tweet(quoted_status, use_display_url, use_expanded_url, False), - 'quote_tweet_link': 'https://twitter.com/%s/status/%s' % - (quoted_status['user']['screen_name'], quoted_status['id_str']), - 'quote_user_name': quoted_status['user']['name'], - 'quote_user_screen_name': quoted_status['user']['screen_name']} - - return '%(prefix)s%(display)s%(suffix)s%(quote)s' % { - 'prefix': '%s' % prefix_text if prefix_text else '', - 'display': display_text, - 'suffix': '%s' % suffix_text if suffix_text else '', - 'quote': quote_text - } + return text diff --git a/twython/endpoints.py b/twython/endpoints.py index e35088d..356cd8a 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -10,19 +10,11 @@ as a keyword argument. e.g. Twython.retweet(id=12345) -Official documentation for Twitter API endpoints can be found at: -https://developer.twitter.com/en/docs/api-reference-index +This map is organized the order functions are documented at: +https://dev.twitter.com/docs/api/1.1 """ -import json -import os import warnings -from io import BytesIO -from time import sleep -#try: - #from StringIO import StringIO -#except ImportError: - #from io import StringIO from .advisory import TwythonDeprecationWarning @@ -34,7 +26,7 @@ class EndpointsMixin(object): @screen_name) for the authenticating user. Docs: - https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-mentions_timeline + https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline """ return self.get('statuses/mentions_timeline', params=params) @@ -42,10 +34,9 @@ class EndpointsMixin(object): def get_user_timeline(self, **params): """Returns a collection of the most recent Tweets posted by the user - indicated by the ``screen_name`` or ``user_id`` parameters. + indicated by the screen_name or user_id parameters. - Docs: - https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline """ return self.get('statuses/user_timeline', params=params) @@ -55,8 +46,7 @@ class EndpointsMixin(object): """Returns a collection of the most recent Tweets and retweets posted by the authenticating user and the users they follow. - Docs: - https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline """ return self.get('statuses/home_timeline', params=params) @@ -66,8 +56,7 @@ class EndpointsMixin(object): """Returns the most recent tweets authored by the authenticating user that have been retweeted by others. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweets_of_me + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets_of_me """ return self.get('statuses/retweets_of_me', params=params) @@ -77,38 +66,34 @@ class EndpointsMixin(object): def get_retweets(self, **params): """Returns up to 100 of the first retweets of a given tweet. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid """ return self.get('statuses/retweets/%s' % params.get('id'), params=params) def show_status(self, **params): - """Returns a single Tweet, specified by the ``id`` parameter + """Returns a single Tweet, specified by the id parameter - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-show-id + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/show/%3Aid """ return self.get('statuses/show/%s' % params.get('id'), params=params) def lookup_status(self, **params): """Returns fully-hydrated tweet objects for up to 100 tweets per - request, as specified by comma-separated values passed to the ``id`` + request, as specified by comma-separated values passed to the id parameter. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-lookup + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/lookup """ return self.post('statuses/lookup', params=params) def destroy_status(self, **params): - """Destroys the status specified by the required ``id`` parameter + """Destroys the status specified by the required ID parameter - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-destroy-id + Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/destroy/%3Aid """ return self.post('statuses/destroy/%s' % params.get('id')) @@ -116,17 +101,15 @@ class EndpointsMixin(object): def update_status(self, **params): """Updates the authenticating user's current status, also known as tweeting - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update + Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/update """ return self.post('statuses/update', params=params) def retweet(self, **params): - """Retweets a tweet specified by the ``id`` parameter + """Retweets a tweet specified by the id parameter - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id + Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/retweet/%3Aid """ return self.post('statuses/retweet/%s' % params.get('id')) @@ -136,7 +119,7 @@ class EndpointsMixin(object): for upload. In other words, it creates a Tweet with a picture attached. Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update_with_media + https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media """ warnings.warn( @@ -149,133 +132,27 @@ class EndpointsMixin(object): def upload_media(self, **params): """Uploads media file to Twitter servers. The file will be available to be attached to a status for 60 minutes. To attach to a update, pass a list of returned media ids - to the :meth:`update_status` method using the ``media_ids`` param. + to the 'update_status' method using the 'media_ids' param. Docs: - https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload - + https://dev.twitter.com/rest/reference/post/media/upload """ - # https://developer.twitter.com/en/docs/media/upload-media/api-reference/get-media-upload-status - if params and params.get('command', '') == 'STATUS': - return self.get('https://upload.twitter.com/1.1/media/upload.json', params=params) - return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) - def create_metadata(self, **params): - """ Adds metadata to a media element, such as image descriptions for visually impaired. - - Docs: - https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create - - """ - params = json.dumps(params) - return self.post("https://upload.twitter.com/1.1/media/metadata/create.json", params=params) - - def upload_video(self, media, media_type, media_category=None, size=None, check_progress=False): - """Uploads video file to Twitter servers in chunks. The file will be available to be attached - to a status for 60 minutes. To attach to a update, pass a list of returned media ids - to the :meth:`update_status` method using the ``media_ids`` param. - - Upload happens in 3 stages: - - INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error. - - APPEND calls each with media chunk. This returns a 204(No Content) if chunk is received. - - FINALIZE call to complete media upload. This returns media_id to be used with status update. - - Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each. - - Docs: - https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload - - """ - upload_url = 'https://upload.twitter.com/1.1/media/upload.json' - if not size: - media.seek(0, os.SEEK_END) - size = media.tell() - media.seek(0) - - # Stage 1: INIT call - params = { - 'command': 'INIT', - 'media_type': media_type, - 'total_bytes': size, - 'media_category': media_category - } - response_init = self.post(upload_url, params=params) - media_id = response_init['media_id'] - - # Stage 2: APPEND calls with 1mb chunks - segment_index = 0 - while True: - data = media.read(1*1024*1024) - if not data: - break - media_chunk = BytesIO() - media_chunk.write(data) - media_chunk.seek(0) - - params = { - 'command': 'APPEND', - 'media_id': media_id, - 'segment_index': segment_index, - 'media': media_chunk, - } - self.post(upload_url, params=params) - segment_index += 1 - - # Stage 3: FINALIZE call to complete upload - params = { - 'command': 'FINALIZE', - 'media_id': media_id - } - - response = self.post(upload_url, params=params) - - # Only get the status if explicity asked to - # Default to False - if check_progress: - - # Stage 4: STATUS call if still processing - params = { - 'command': 'STATUS', - 'media_id': media_id - } - - # added code to handle if media_category is NOT set and check_progress=True - # the API will return a NoneType object in this case - try: - processing_state = response.get('processing_info').get('state') - except AttributeError: - return response - - if processing_state: - while (processing_state == 'pending' or processing_state == 'in_progress') : - # get the secs to wait - check_after_secs = response.get('processing_info').get('check_after_secs') - - if check_after_secs: - sleep(check_after_secs) - response = self.get(upload_url, params=params) - # get new state after waiting - processing_state = response.get('processing_info').get('state') - - return response - def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded representation of a Tweet on third party sites. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-oembed + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/oembed """ - return self.get('oembed', params=params) + return self.get('statuses/oembed', params=params) def get_retweeters_ids(self, **params): """Returns a collection of up to 100 user IDs belonging to users who - have retweeted the tweet specified by the ``id`` parameter. + have retweeted the tweet specified by the id parameter. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweeters-ids + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweeters/ids """ return self.get('statuses/retweeters/ids', params=params) @@ -286,8 +163,7 @@ class EndpointsMixin(object): def search(self, **params): """Returns a collection of relevant Tweets matching a specified query. - Docs: - https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets + Docs: https://dev.twitter.com/docs/api/1.1/get/search/tweets """ return self.get('search/tweets', params=params) @@ -299,50 +175,45 @@ class EndpointsMixin(object): def get_direct_messages(self, **params): """Returns the 20 most recent direct messages sent to the authenticating user. - Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/list-events + Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages """ - return self.get('direct_messages/events/list', params=params) + return self.get('direct_messages', params=params) get_direct_messages.iter_mode = 'id' def get_sent_messages(self, **params): """Returns the 20 most recent direct messages sent by the authenticating user. - Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-sent-message + Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages/sent """ return self.get('direct_messages/sent', params=params) get_sent_messages.iter_mode = 'id' def get_direct_message(self, **params): - """Returns a single direct message, specified by an ``id`` parameter. + """Returns a single direct message, specified by an id parameter. - Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-event + Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages/show """ - return self.get('direct_messages/events/show', params=params) + return self.get('direct_messages/show', params=params) def destroy_direct_message(self, **params): - """Destroys the direct message specified in the required ``id`` parameter + """Destroys the direct message specified in the required id parameter - Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message-event + Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/destroy """ - return self.delete('direct_messages/events/destroy', params=params) + return self.post('direct_messages/destroy', params=params) def send_direct_message(self, **params): """Sends a new direct message to the specified user from the authenticating user. - Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-event + Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/new """ - return self.post('direct_messages/events/new', params=params, json_encoded=True) + return self.post('direct_messages/new', params=params) # Friends & Followers def get_user_ids_of_blocked_retweets(self, **params): @@ -350,7 +221,7 @@ class EndpointsMixin(object): user does not want to receive retweets from. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-no_retweets-ids + https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids """ return self.get('friendships/no_retweets/ids', params=params) @@ -359,8 +230,7 @@ class EndpointsMixin(object): """Returns a cursored collection of user IDs for every user the specified user is following (otherwise known as their "friends"). - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids + Docs: https://dev.twitter.com/docs/api/1.1/get/friends/ids """ return self.get('friends/ids', params=params) @@ -371,8 +241,7 @@ class EndpointsMixin(object): """Returns a cursored collection of user IDs for every user following the specified user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids + Docs: https://dev.twitter.com/docs/api/1.1/get/followers/ids """ return self.get('followers/ids', params=params) @@ -381,10 +250,9 @@ class EndpointsMixin(object): def lookup_friendships(self, **params): """Returns the relationships of the authenticating user to the - comma-separated list of up to 100 ``screen_names`` or ``user_ids`` provided. + comma-separated list of up to 100 screen_names or user_ids provided. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup + Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/lookup """ return self.get('friendships/lookup', params=params) @@ -393,8 +261,7 @@ class EndpointsMixin(object): """Returns a collection of numeric IDs for every user who has a pending request to follow the authenticating user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming + Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/incoming """ return self.get('friendships/incoming', params=params) @@ -405,8 +272,7 @@ class EndpointsMixin(object): """Returns a collection of numeric IDs for every protected user for whom the authenticating user has a pending follow request. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-outgoing + Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/outgoing """ return self.get('friendships/outgoing', params=params) @@ -415,20 +281,18 @@ class EndpointsMixin(object): def create_friendship(self, **params): """Allows the authenticating users to follow the user specified - in the ``id`` parameter. + in the ID parameter. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-create + Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/create """ return self.post('friendships/create', params=params) def destroy_friendship(self, **params): """Allows the authenticating user to unfollow the user specified - in the ``id`` parameter. + in the ID parameter. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy + Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/destroy """ return self.post('friendships/destroy', params=params) @@ -437,8 +301,7 @@ class EndpointsMixin(object): """Allows one to enable or disable retweets and device notifications from the specified user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-update + Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/update """ return self.post('friendships/update', params=params) @@ -447,8 +310,7 @@ class EndpointsMixin(object): """Returns detailed information about the relationship between two arbitrary users. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-show + Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/show """ return self.get('friendships/show', params=params) @@ -457,8 +319,7 @@ class EndpointsMixin(object): """Returns a cursored collection of user objects for every user the specified user is following (otherwise known as their "friends"). - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list + Docs: https://dev.twitter.com/docs/api/1.1/get/friends/list """ return self.get('friends/list', params=params) @@ -469,8 +330,7 @@ class EndpointsMixin(object): """Returns a cursored collection of user objects for users following the specified user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list + Docs: https://dev.twitter.com/docs/api/1.1/get/followers/list """ return self.get('followers/list', params=params) @@ -482,8 +342,7 @@ class EndpointsMixin(object): """Returns settings (including current trend, geo and sleep time information) for the authenticating user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-settings + Docs: https://dev.twitter.com/docs/api/1.1/get/account/settings """ return self.get('account/settings', params=params) @@ -494,7 +353,7 @@ class EndpointsMixin(object): code and an error message if not. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials + https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials """ return self.get('account/verify_credentials', params=params) @@ -502,8 +361,7 @@ class EndpointsMixin(object): def update_account_settings(self, **params): """Updates the authenticating user's settings. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-settings + Docs: https://dev.twitter.com/docs/api/1.1/post/account/settings """ return self.post('account/settings', params=params) @@ -521,8 +379,7 @@ class EndpointsMixin(object): """Sets values that users are able to set under the "Account" tab of their settings page. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile + Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile """ return self.post('account/update_profile', params=params) @@ -531,35 +388,26 @@ class EndpointsMixin(object): """Updates the authenticating user's profile background image. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_background_image + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image """ return self.post('account/update_profile_banner', params=params) - def update_profile_colors(self, **params): # pragma: no cover + def update_profile_colors(self, **params): """Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com. - This method is deprecated, replaced by the ``profile_link_color`` - parameter to :meth:`update_profile`. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors """ - warnings.warn( - 'This method is deprecated. You should use the' - ' profile_link_color parameter in Twython.update_profile instead.', - TwythonDeprecationWarning, - stacklevel=2 - ) return self.post('account/update_profile_colors', params=params) def update_profile_image(self, **params): # pragma: no cover """Updates the authenticating user's profile image. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_image + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image """ return self.post('account/update_profile_image', params=params) @@ -568,8 +416,7 @@ class EndpointsMixin(object): """Returns a collection of user objects that the authenticating user is blocking. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-list + Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/list """ return self.get('blocks/list', params=params) @@ -579,8 +426,7 @@ class EndpointsMixin(object): def list_block_ids(self, **params): """Returns an array of numeric user ids the authenticating user is blocking. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-ids + Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/ids """ return self.get('blocks/ids', params=params) @@ -590,39 +436,35 @@ class EndpointsMixin(object): def create_block(self, **params): """Blocks the specified user from following the authenticating user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-blocks-create + Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/create """ return self.post('blocks/create', params=params) def destroy_block(self, **params): - """Un-blocks the user specified in the ``id`` parameter for the + """Un-blocks the user specified in the ID parameter for the authenticating user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-blocks-destroy + Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/destroy """ return self.post('blocks/destroy', params=params) def lookup_user(self, **params): """Returns fully-hydrated user objects for up to 100 users per request, - as specified by comma-separated values passed to the ``user_id`` and/or - ``screen_name`` parameters. + as specified by comma-separated values passed to the user_id and/or + screen_name parameters. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup + Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup """ return self.get('users/lookup', params=params) def show_user(self, **params): """Returns a variety of information about the user specified by the - required ``user_id`` or ``screen_name`` parameter. + required user_id or screen_name parameter. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-show + Docs: https://dev.twitter.com/docs/api/1.1/get/users/show """ return self.get('users/show', params=params) @@ -631,8 +473,7 @@ class EndpointsMixin(object): """Provides a simple, relevance-based search interface to public user accounts on Twitter. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-search + Docs: https://dev.twitter.com/docs/api/1.1/get/users/search """ return self.get('users/search', params=params) @@ -658,7 +499,7 @@ class EndpointsMixin(object): Returns HTTP 200 upon success. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-remove_profile_banner + https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner """ return self.post('account/remove_profile_banner', params=params) @@ -667,7 +508,7 @@ class EndpointsMixin(object): """Uploads a profile banner on behalf of the authenticating user. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_banner + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner """ return self.post('account/update_profile_background_image', @@ -677,8 +518,7 @@ class EndpointsMixin(object): """Returns a map of the available size variations of the specified user's profile banner. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-users-profile_banner + Docs: https://dev.twitter.com/docs/api/1.1/get/users/profile_banner """ return self.get('users/profile_banner', params=params) @@ -687,8 +527,7 @@ class EndpointsMixin(object): """Returns a collection of user objects that the authenticating user is muting. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-mutes-users-list + Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/list """ return self.get('mutes/users/list', params=params) @@ -699,8 +538,7 @@ class EndpointsMixin(object): """Returns an array of numeric user ids the authenticating user is muting. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-mutes-users-ids + Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/ids """ return self.get('mutes/users/ids', params=params) @@ -708,21 +546,19 @@ class EndpointsMixin(object): list_mute_ids.iter_key = 'ids' def create_mute(self, **params): - """Mutes the specified user, preventing their tweets appearing + """Mutes the specified user, preventing their tweets appearing in the authenticating user's timeline. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-create + Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create """ return self.post('mutes/users/create', params=params) def destroy_mute(self, **params): - """Un-mutes the user specified in the user or ``id`` parameter for + """Un-mutes the user specified in the user or ID parameter for the authenticating user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-destroy + Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy """ return self.post('mutes/users/destroy', params=params) @@ -732,7 +568,7 @@ class EndpointsMixin(object): """Access the users in a given category of the Twitter suggested user list. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions-slug + https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug """ return self.get('users/suggestions/%s' % params.get('slug'), @@ -741,8 +577,7 @@ class EndpointsMixin(object): def get_user_suggestions(self, **params): """Access to Twitter's suggested user list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions + Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions """ return self.get('users/suggestions', params=params) @@ -753,7 +588,7 @@ class EndpointsMixin(object): user. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions-slug-members + https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members """ return self.get('users/suggestions/%s/members' % params.get('slug'), @@ -764,29 +599,26 @@ class EndpointsMixin(object): """Returns the 20 most recent Tweets favorited by the authenticating or specified user. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-favorites-list + Docs: https://dev.twitter.com/docs/api/1.1/get/favorites/list """ return self.get('favorites/list', params=params) get_favorites.iter_mode = 'id' def destroy_favorite(self, **params): - """Un-favorites the status specified in the ``id`` parameter as the + """Un-favorites the status specified in the ID parameter as the authenticating user. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-destroy + Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/destroy """ return self.post('favorites/destroy', params=params) def create_favorite(self, **params): - """Favorites the status specified in the ``id`` parameter as the + """Favorites the status specified in the ID parameter as the authenticating user. - Docs: - https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-create + Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/create """ return self.post('favorites/create', params=params) @@ -796,8 +628,7 @@ class EndpointsMixin(object): """Returns all lists the authenticating or specified user subscribes to, including their own. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-list + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/list """ return self.get('lists/list', params=params) @@ -805,8 +636,7 @@ class EndpointsMixin(object): def get_list_statuses(self, **params): """Returns a timeline of tweets authored by members of the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-statuses + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/statuses """ return self.get('lists/statuses', params=params) @@ -815,8 +645,7 @@ class EndpointsMixin(object): def delete_list_member(self, **params): """Removes the specified member from the list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-destroy + Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy """ return self.post('lists/members/destroy', params=params) @@ -824,8 +653,7 @@ class EndpointsMixin(object): def get_list_memberships(self, **params): """Returns the lists the specified user has been added to. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-memberships + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/memberships """ return self.get('lists/memberships', params=params) @@ -835,8 +663,7 @@ class EndpointsMixin(object): def get_list_subscribers(self, **params): """Returns the subscribers of the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscribers + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers """ return self.get('lists/subscribers', params=params) @@ -847,7 +674,7 @@ class EndpointsMixin(object): """Subscribes the authenticated user to the specified list. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-create + https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create """ return self.post('lists/subscribers/create', params=params) @@ -855,8 +682,7 @@ class EndpointsMixin(object): def is_list_subscriber(self, **params): """Check if the specified user is a subscriber of the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscribers-show + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers/show """ return self.get('lists/subscribers/show', params=params) @@ -865,7 +691,7 @@ class EndpointsMixin(object): """Unsubscribes the authenticated user from the specified list. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-destroy + https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy """ return self.post('lists/subscribers/destroy', params=params) @@ -875,7 +701,7 @@ class EndpointsMixin(object): list of member ids or screen names. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-create_all + https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all """ return self.post('lists/members/create_all', params=params) @@ -883,8 +709,7 @@ class EndpointsMixin(object): def is_list_member(self, **params): """Check if the specified user is a member of the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-members-show + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/members/show """ return self.get('lists/members/show', params=params) @@ -892,8 +717,7 @@ class EndpointsMixin(object): def get_list_members(self, **params): """Returns the members of the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-members + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/members """ return self.get('lists/members', params=params) @@ -903,8 +727,7 @@ class EndpointsMixin(object): def add_list_member(self, **params): """Add a member to a list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-create + Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/create """ return self.post('lists/members/create', params=params) @@ -912,8 +735,7 @@ class EndpointsMixin(object): def delete_list(self, **params): """Deletes the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-destroy + Docs: https://dev.twitter.com/docs/api/1.1/post/lists/destroy """ return self.post('lists/destroy', params=params) @@ -921,8 +743,7 @@ class EndpointsMixin(object): def update_list(self, **params): """Updates the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-update + Docs: https://dev.twitter.com/docs/api/1.1/post/lists/update """ return self.post('lists/update', params=params) @@ -930,8 +751,7 @@ class EndpointsMixin(object): def create_list(self, **params): """Creates a new list for the authenticated user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-create + Docs: https://dev.twitter.com/docs/api/1.1/post/lists/create """ return self.post('lists/create', params=params) @@ -939,8 +759,7 @@ class EndpointsMixin(object): def get_specific_list(self, **params): """Returns the specified list. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-show + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/show """ return self.get('lists/show', params=params) @@ -948,8 +767,7 @@ class EndpointsMixin(object): def get_list_subscriptions(self, **params): """Obtain a collection of the lists the specified user is subscribed to. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscriptions + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscriptions """ return self.get('lists/subscriptions', params=params) @@ -961,7 +779,7 @@ class EndpointsMixin(object): comma-separated list of member ids or screen names. Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-destroy_all + https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all """ return self.post('lists/members/destroy_all', params=params) @@ -969,8 +787,7 @@ class EndpointsMixin(object): def show_owned_lists(self, **params): """Returns the lists owned by the specified Twitter user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-ownerships + Docs: https://dev.twitter.com/docs/api/1.1/get/lists/ownerships """ return self.get('lists/ownerships', params=params) @@ -981,17 +798,16 @@ class EndpointsMixin(object): def get_saved_searches(self, **params): """Returns the authenticated user's saved search queries. - Docs: - https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-list + Docs: https://dev.twitter.com/docs/api/1.1/get/saved_searches/list """ return self.get('saved_searches/list', params=params) def show_saved_search(self, **params): - """Retrieve the information for the saved search represented by the given ``id``. + """Retrieve the information for the saved search represented by the given id. Docs: - https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-show-id + https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid """ return self.get('saved_searches/show/%s' % params.get('id'), @@ -1000,8 +816,7 @@ class EndpointsMixin(object): def create_saved_search(self, **params): """Create a new saved search for the authenticated user. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-create + Docs: https://dev.twitter.com/docs/api/1.1/post/saved_searches/create """ return self.post('saved_searches/create', params=params) @@ -1010,7 +825,7 @@ class EndpointsMixin(object): """Destroys a saved search for the authenticating user. Docs: - https://developer.twitter.com/en/docs/tweets/search/api-reference/post-saved_searches-destroy-id + https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid """ return self.post('saved_searches/destroy/%s' % params.get('id'), @@ -1020,8 +835,7 @@ class EndpointsMixin(object): def get_geo_info(self, **params): """Returns all the information about a known place. - Docs: - https://developer.twitter.com/en/docs/geo/place-information/api-reference/get-geo-id-place_id + Docs: https://dev.twitter.com/docs/api/1.1/get/geo/id/%3Aplace_id """ return self.get('geo/id/%s' % params.get('place_id'), params=params) @@ -1030,8 +844,7 @@ class EndpointsMixin(object): """Given a latitude and a longitude, searches for up to 20 places that can be used as a place_id when updating a status. - Docs: - https://developer.twitter.com/en/docs/geo/places-near-location/api-reference/get-geo-reverse_geocode + Docs: https://dev.twitter.com/docs/api/1.1/get/geo/reverse_geocode """ return self.get('geo/reverse_geocode', params=params) @@ -1039,8 +852,7 @@ class EndpointsMixin(object): def search_geo(self, **params): """Search for places that can be attached to a statuses/update. - Docs: - https://developer.twitter.com/en/docs/geo/places-near-location/api-reference/get-geo-search + Docs: https://dev.twitter.com/docs/api/1.1/get/geo/search """ return self.get('geo/search', params=params) @@ -1066,8 +878,7 @@ class EndpointsMixin(object): """Returns the top 10 trending topics for a specific WOEID, if trending information is available for it. - Docs: - https://developer.twitter.com/en/docs/trends/trends-for-location/api-reference/get-trends-place + Docs: https://dev.twitter.com/docs/api/1.1/get/trends/place """ return self.get('trends/place', params=params) @@ -1075,8 +886,7 @@ class EndpointsMixin(object): def get_available_trends(self, **params): """Returns the locations that Twitter has trending topic information for. - Docs: - https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-available + Docs: https://dev.twitter.com/docs/api/1.1/get/trends/available """ return self.get('trends/available', params=params) @@ -1085,8 +895,7 @@ class EndpointsMixin(object): """Returns the locations that Twitter has trending topic information for, closest to a specified location. - Docs: - https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-closest + Docs: https://dev.twitter.com/docs/api/1.1/get/trends/closest """ return self.get('trends/closest', params=params) @@ -1095,8 +904,7 @@ class EndpointsMixin(object): def report_spam(self, **params): # pragma: no cover """Report the specified user as a spam account to Twitter. - Docs: - https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-users-report_spam + Docs: https://dev.twitter.com/docs/api/1.1/post/users/report_spam """ return self.post('users/report_spam', params=params) @@ -1106,8 +914,7 @@ class EndpointsMixin(object): """Allows a registered application to revoke an issued OAuth 2 Bearer Token by presenting its client credentials. - Docs: - https://developer.twitter.com/en/docs/basics/authentication/api-reference/invalidate_token + Docs: https://dev.twitter.com/docs/api/1.1/post/oauth2/invalidate_token """ return self.post('oauth2/invalidate_token', params=params) @@ -1116,8 +923,7 @@ class EndpointsMixin(object): def get_twitter_configuration(self, **params): """Returns the current configuration used by Twitter - Docs: - https://developer.twitter.com/en/docs/developer-utilities/configuration/api-reference/get-help-configuration + Docs: https://dev.twitter.com/docs/api/1.1/get/help/configuration """ return self.get('help/configuration', params=params) @@ -1126,8 +932,7 @@ class EndpointsMixin(object): """Returns the list of languages supported by Twitter along with their ISO 639-1 code. - Docs: - https://developer.twitter.com/en/docs/developer-utilities/supported-languages/api-reference/get-help-languages + Docs: https://dev.twitter.com/docs/api/1.1/get/help/languages """ return self.get('help/languages', params=params) @@ -1135,8 +940,7 @@ class EndpointsMixin(object): def get_privacy_policy(self, **params): """Returns Twitter's Privacy Policy - Docs: - https://developer.twitter.com/en/docs/developer-utilities/privacy-policy/api-reference/get-help-privacy + Docs: https://dev.twitter.com/docs/api/1.1/get/help/privacy """ return self.get('help/privacy', params=params) @@ -1144,8 +948,7 @@ class EndpointsMixin(object): def get_tos(self, **params): """Return the Twitter Terms of Service - Docs: - https://developer.twitter.com/en/docs/developer-utilities/terms-of-service/api-reference/get-help-tos + Docs: https://dev.twitter.com/docs/api/1.1/get/help/tos """ return self.get('help/tos', params=params) @@ -1155,13 +958,13 @@ class EndpointsMixin(object): specified resource families. Docs: - https://developer.twitter.com/en/docs/developer-utilities/rate-limit-status/api-reference/get-application-rate_limit_status + https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status """ return self.get('application/rate_limit_status', params=params) -# from https://developer.twitter.com/en/docs/ads/general/guides/response-codes +# from https://dev.twitter.com/docs/error-codes-responses TWITTER_HTTP_STATUS_CODE = { 200: ('OK', 'Success!'), 304: ('Not Modified', 'There was no new data to return.'), diff --git a/twython/streaming/api.py b/twython/streaming/api.py index 6073abb..47678e4 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -86,6 +86,8 @@ class TwythonStreamer(object): # Set up type methods StreamTypes = TwythonStreamerTypes(self) self.statuses = StreamTypes.statuses + self.user = StreamTypes.user + self.site = StreamTypes.site self.connected = False @@ -123,7 +125,7 @@ class TwythonStreamer(object): self.on_timeout() else: if response.status_code != 200: - self.on_error(response.status_code, response.content, response.headers) + self.on_error(response.status_code, response.content) if self.retry_count and \ (self.retry_count - retry_counter) > 0: @@ -167,16 +169,16 @@ class TwythonStreamer(object): Returns True if other handlers for this message should be invoked. Feel free to override this to handle your streaming data how you - want it handled. See - https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types - for messages sent along in stream responses. + want it handled. + See https://dev.twitter.com/docs/streaming-apis/messages for messages + sent along in stream responses. :param data: data recieved from the stream :type data: dict """ return True - def on_error(self, status_code, data, headers=None): # pragma: no cover + def on_error(self, status_code, data): # pragma: no cover """Called when stream returns non-200 status code Feel free to override this to handle your streaming data how you @@ -187,9 +189,6 @@ class TwythonStreamer(object): :param data: Error message sent from stream :type data: dict - - :param headers: Response headers sent from the stream (i.e. Retry-After) - :type headers: dict """ return diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 5042d29..aa6b9ad 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -20,14 +20,33 @@ class TwythonStreamerTypes(object): self.streamer = streamer self.statuses = TwythonStreamerTypesStatuses(streamer) + def user(self, **params): + """Stream user + + Accepted params found at: + https://dev.twitter.com/docs/api/1.1/get/user + """ + url = 'https://userstream.twitter.com/%s/user.json' \ + % self.streamer.api_version + self.streamer._request(url, params=params) + + def site(self, **params): + """Stream site + + Accepted params found at: + https://dev.twitter.com/docs/api/1.1/get/site + """ + url = 'https://sitestream.twitter.com/%s/site.json' \ + % self.streamer.api_version + self.streamer._request(url, params=params) + class TwythonStreamerTypesStatuses(object): """Class for different statuses endpoints - Available so :meth:`TwythonStreamer.statuses.filter()` is available. - Just a bit cleaner than :meth:`TwythonStreamer.statuses_filter()`, - :meth:`statuses_sample()`, etc. all being single methods in - :class:`TwythonStreamer`. + Available so TwythonStreamer.statuses.filter() is available. + Just a bit cleaner than TwythonStreamer.statuses_filter(), + statuses_sample(), etc. all being single methods in TwythonStreamer """ def __init__(self, streamer): @@ -35,31 +54,31 @@ class TwythonStreamerTypesStatuses(object): self.params = None def filter(self, **params): - r"""Stream statuses/filter + """Stream statuses/filter :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter + https://dev.twitter.com/docs/api/1.1/post/statuses/filter """ url = 'https://stream.twitter.com/%s/statuses/filter.json' \ % self.streamer.api_version self.streamer._request(url, 'POST', params=params) def sample(self, **params): - r"""Stream statuses/sample + """Stream statuses/sample :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://developer.twitter.com/en/docs/tweets/sample-realtime/api-reference/get-statuses-sample + https://dev.twitter.com/docs/api/1.1/get/statuses/sample """ url = 'https://stream.twitter.com/%s/statuses/sample.json' \ % self.streamer.api_version self.streamer._request(url, params=params) def firehose(self, **params): - r"""Stream statuses/firehose + """Stream statuses/firehose :param \*\*params: Parameters to send with your stream request @@ -71,12 +90,12 @@ class TwythonStreamerTypesStatuses(object): self.streamer._request(url, params=params) def set_dynamic_filter(self, **params): - r"""Set/update statuses/filter + """Set/update statuses/filter :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter + https://dev.twitter.com/docs/api/1.1/post/statuses/filter """ self.params = params @@ -85,4 +104,4 @@ class TwythonStreamerTypesStatuses(object): url = 'https://stream.twitter.com/%s/statuses/filter.json' \ % self.streamer.api_version - self.streamer._request(url, 'POST', params=self.params) + self.streamer._request(url, 'POST', params=self.params)