Making twython work (again?) in Python 3
- 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
- Added ``compat.py`` for compatability with Python 2.6 and greater
- Added some ascii art, moved description of Twython and ``__author__`` to ``__init__.py``
- Added ``version.py`` to store the current Twython version, instead of repeating it twice -- it also had to go into it's own file because of dependencies of ``requests`` and ``requests-oauthlib``, install would fail because those libraries weren't installed yet (on fresh install of Twython)
- Removed ``find_packages()`` from ``setup.py``, only one package -- we can
just define it
- added quick publish method for Ryan and I: ``python setup.py publish`` is faster to type and easier to remember than ``python setup.py sdist upload``
- Removed ``base_url`` from ``endpoints.py`` because we're just repeating it in
``Twython.__init__``
- ``Twython.get_authentication_tokens()`` now takes ``callback_url`` argument rather than passing the ``callback_url`` through ``Twython.__init__``, ``callback_url`` is only used in the ``get_authentication_tokens`` method and nowhere else (kept in init though for backwards compatability)
- Updated README to better reflect current Twython codebase
- Added ``warnings.simplefilter('default')`` line in ``twython.py`` for Python 2.7 and greater to display Deprecation Warnings in console
- Added Deprecation Warnings for usage of ``twitter_token``, ``twitter_secret`` and ``callback_url`` in ``Twython.__init__``
- Headers now always include the User-Agent as Twython vXX unless User-Agent is overwritten
- Removed senseless TwythonError thrown if method is not GET or POST, who cares -- if the user passes something other than GET or POST just let Twitter return the error that they messed up
- Removed conversion to unicode of (int, bool) params passed to a requests. ``requests`` isn't greedy about variables that can't be converted to unicode anymore
This commit is contained in:
parent
8ecc55b5ad
commit
bb019d3a57
16 changed files with 306 additions and 1000 deletions
39
.gitignore
vendored
39
.gitignore
vendored
|
|
@ -1,5 +1,36 @@
|
||||||
*.pyc
|
*.py[cod]
|
||||||
build
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Packages
|
||||||
|
*.egg
|
||||||
|
*.egg-info
|
||||||
dist
|
dist
|
||||||
twython.egg-info
|
build
|
||||||
*.swp
|
eggs
|
||||||
|
parts
|
||||||
|
bin
|
||||||
|
var
|
||||||
|
sdist
|
||||||
|
develop-eggs
|
||||||
|
.installed.cfg
|
||||||
|
lib
|
||||||
|
lib64
|
||||||
|
__pycache__
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
.coverage
|
||||||
|
.tox
|
||||||
|
nosetests.xml
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
# Mr Developer
|
||||||
|
.mr.developer.cfg
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|
@ -13,7 +13,7 @@ Development Lead
|
||||||
Patches and Suggestions
|
Patches and Suggestions
|
||||||
````````````````````````
|
````````````````````````
|
||||||
|
|
||||||
- `Mike Helmick <https://github.com/michaelhelmick>`_, multiple fixes and proper ``requests`` integration. Too much to list here.
|
- `Mike Helmick <https://github.com/michaelhelmick>`_, multiple fixes and proper ``requests`` integration, Python 3 compatibility, too much to list here.
|
||||||
- `kracekumar <https://github.com/kracekumar>`_, early ``requests`` work and various fixes.
|
- `kracekumar <https://github.com/kracekumar>`_, early ``requests`` work and various fixes.
|
||||||
- `Erik Scheffers <https://github.com/eriks5>`_, various fixes regarding OAuth callback URLs.
|
- `Erik Scheffers <https://github.com/eriks5>`_, various fixes regarding OAuth callback URLs.
|
||||||
- `Jordan Bouvier <https://github.com/jbouvier>`_, various fixes regarding OAuth callback URLs.
|
- `Jordan Bouvier <https://github.com/jbouvier>`_, various fixes regarding OAuth callback URLs.
|
||||||
|
|
|
||||||
58
HISTORY.rst
Normal file
58
HISTORY.rst
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
History
|
||||||
|
-------
|
||||||
|
|
||||||
|
2.8.0 (2013-xx-xx)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
- 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
|
||||||
|
- Added ``compat.py`` for compatability with Python 2.6 and greater
|
||||||
|
- Added some ascii art, moved description of Twython and ``__author__`` to ``__init__.py``
|
||||||
|
- Added ``version.py`` to store the current Twython version, instead of repeating it twice -- it also had to go into it's own file because of dependencies of ``requests`` and ``requests-oauthlib``, install would fail because those libraries weren't installed yet (on fresh install of Twython)
|
||||||
|
- Removed ``find_packages()`` from ``setup.py``, only one package -- we can
|
||||||
|
just define it
|
||||||
|
- added quick publish method for Ryan and I: ``python setup.py publish`` is faster to type and easier to remember than ``python setup.py sdist upload``
|
||||||
|
- Removed ``base_url`` from ``endpoints.py`` because we're just repeating it in
|
||||||
|
``Twython.__init__``
|
||||||
|
- ``Twython.get_authentication_tokens()`` now takes ``callback_url`` argument rather than passing the ``callback_url`` through ``Twython.__init__``, ``callback_url`` is only used in the ``get_authentication_tokens`` method and nowhere else (kept in init though for backwards compatability)
|
||||||
|
- Updated README to better reflect current Twython codebase
|
||||||
|
- Added ``warnings.simplefilter('default')`` line in ``twython.py`` for Python 2.7 and greater to display Deprecation Warnings in console
|
||||||
|
- Added Deprecation Warnings for usage of ``twitter_token``, ``twitter_secret`` and ``callback_url`` in ``Twython.__init__``
|
||||||
|
- Headers now always include the User-Agent as Twython vXX unless User-Agent is overwritten
|
||||||
|
- Removed senseless TwythonError thrown if method is not GET or POST, who cares -- if the user passes something other than GET or POST just let Twitter return the error that they messed up
|
||||||
|
- Removed conversion to unicode of (int, bool) params passed to a requests. ``requests`` isn't greedy about variables that can't be converted to unicode anymore
|
||||||
|
|
||||||
|
2.7.3 (2013-04-12)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
- Fixed issue where Twython Exceptions were not being logged correctly
|
||||||
|
|
||||||
|
2.7.2 (2013-04-08)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
- Fixed ``AttributeError`` when trying to decode the JSON response via ``Response.json()``
|
||||||
|
|
||||||
|
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)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
- New ``showOwnedLists`` method
|
||||||
|
|
||||||
|
2.7.0 (2013-03-31)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
- Added missing slash to ``getMentionsTimeline`` in ``twitter_endpoints.py``
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
include LICENSE README.md README.rst
|
include LICENSE README.md README.rst HISTORY.rst
|
||||||
recursive-include examples *
|
recursive-include examples *
|
||||||
recursive-exclude examples *.pyc
|
recursive-exclude examples *.pyc
|
||||||
|
|
|
||||||
64
README.md
64
README.md
|
|
@ -10,12 +10,13 @@ Features
|
||||||
- User information
|
- User information
|
||||||
- Twitter lists
|
- Twitter lists
|
||||||
- Timelines
|
- Timelines
|
||||||
- User avatar URL
|
- Direct Messages
|
||||||
- and anything found in [the docs](https://dev.twitter.com/docs/api/1.1)
|
- and anything found in [the docs](https://dev.twitter.com/docs/api/1.1)
|
||||||
* Image Uploading!
|
* Image Uploading!
|
||||||
- **Update user status with an image**
|
- **Update user status with an image**
|
||||||
- Change user avatar
|
- Change user avatar
|
||||||
- Change user background image
|
- Change user background image
|
||||||
|
- Change user banner image
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
@ -36,11 +37,9 @@ Usage
|
||||||
```python
|
```python
|
||||||
from twython import Twython
|
from twython import Twython
|
||||||
|
|
||||||
t = Twython(app_key=app_key,
|
t = Twython(app_key, app_secret)
|
||||||
app_secret=app_secret,
|
|
||||||
callback_url='http://google.com/')
|
|
||||||
|
|
||||||
auth_props = t.get_authentication_tokens()
|
auth_props = t.get_authentication_tokens(callback_url='http://google.com')
|
||||||
|
|
||||||
oauth_token = auth_props['oauth_token']
|
oauth_token = auth_props['oauth_token']
|
||||||
oauth_token_secret = auth_props['oauth_token_secret']
|
oauth_token_secret = auth_props['oauth_token_secret']
|
||||||
|
|
@ -55,41 +54,53 @@ Be sure you have a URL set up to handle the callback after the user has allowed
|
||||||
```python
|
```python
|
||||||
from twython import Twython
|
from twython import Twython
|
||||||
|
|
||||||
'''
|
# oauth_token_secret comes from the previous step
|
||||||
oauth_token and oauth_token_secret come from the previous step
|
# if needed, store that in a session variable or something.
|
||||||
if needed, store those in a session variable or something. oauth_verifier from the previous call is now required to pass to get_authorized_tokens
|
# oauth_verifier and oauth_token from the previous call is now REQUIRED # to pass to get_authorized_tokens
|
||||||
'''
|
|
||||||
|
|
||||||
t = Twython(app_key=app_key,
|
# In Django, to get the oauth_verifier and oauth_token from the callback
|
||||||
app_secret=app_secret,
|
# url querystring, you might do something like this:
|
||||||
oauth_token=oauth_token,
|
# oauth_token = request.GET.get('oauth_token')
|
||||||
oauth_token_secret=oauth_token_secret)
|
# oauth_verifier = request.GET.get('oauth_verifier')
|
||||||
|
|
||||||
|
t = Twython(app_key, app_secret,
|
||||||
|
oauth_token, oauth_token_secret)
|
||||||
|
|
||||||
auth_tokens = t.get_authorized_tokens(oauth_verifier)
|
auth_tokens = t.get_authorized_tokens(oauth_verifier)
|
||||||
print auth_tokens
|
print auth_tokens
|
||||||
```
|
```
|
||||||
|
|
||||||
*Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/twitter_endpoints.py*
|
*Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/endpoints.py*
|
||||||
|
|
||||||
###### Getting a user home timeline
|
###### Getting a user home timeline
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from twython import Twython
|
from twython import Twython
|
||||||
|
|
||||||
'''
|
# oauth_token and oauth_token_secret are the final tokens produced
|
||||||
oauth_token and oauth_token_secret are the final tokens produced
|
# from the 'Handling the callback' step
|
||||||
from the `Handling the callback` step
|
|
||||||
'''
|
|
||||||
|
|
||||||
t = Twython(app_key=app_key,
|
t = Twython(app_key, app_secret,
|
||||||
app_secret=app_secret,
|
oauth_token, oauth_token_secret)
|
||||||
oauth_token=oauth_token,
|
|
||||||
oauth_token_secret=oauth_token_secret)
|
|
||||||
|
|
||||||
# Returns an dict of the user home timeline
|
# Returns an dict of the user home timeline
|
||||||
print t.getHomeTimeline()
|
print t.getHomeTimeline()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
###### Catching exceptions
|
||||||
|
> Twython offers three Exceptions currently: TwythonError, TwythonAuthError and TwythonRateLimitError
|
||||||
|
```python
|
||||||
|
from twython import Twython, TwythonAuthError
|
||||||
|
|
||||||
|
t = Twython(MY_WRONG_APP_KEY, MY_WRONG_APP_SECRET,
|
||||||
|
BAD_OAUTH_TOKEN, BAD_OAUTH_TOKEN_SECRET)
|
||||||
|
|
||||||
|
try:
|
||||||
|
t.verifyCredentials()
|
||||||
|
except TwythonAuthError as e:
|
||||||
|
print e
|
||||||
|
```
|
||||||
|
|
||||||
###### Streaming API
|
###### Streaming API
|
||||||
*Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access)
|
*Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access)
|
||||||
streams.*
|
streams.*
|
||||||
|
|
@ -136,12 +147,7 @@ from you using them by this library.
|
||||||
|
|
||||||
Twython 3k
|
Twython 3k
|
||||||
----------
|
----------
|
||||||
There's an experimental version of Twython that's made for Python 3k. This is currently not guaranteed to
|
Full compatiabilty with Python 3 is now available seamlessly in the main Twython package. The Twython 3k package has been removed as of Twython 2.8.0
|
||||||
work in all situations, but it's provided so that others can grab it and hack on it.
|
|
||||||
If you choose to try it out, be aware of this.
|
|
||||||
|
|
||||||
**OAuth is now working thanks to updates from [Hades](https://github.com/hades). You'll need to grab
|
|
||||||
his [Python 3 branch for python-oauth2](https://github.com/hades/python-oauth2/tree/python3) to have it work, though.**
|
|
||||||
|
|
||||||
Questions, Comments, etc?
|
Questions, Comments, etc?
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
@ -150,8 +156,6 @@ at ryan@venodesigns.net.
|
||||||
|
|
||||||
You can also follow me on Twitter - **[@ryanmcgrath](http://twitter.com/ryanmcgrath)**.
|
You can also follow me on Twitter - **[@ryanmcgrath](http://twitter.com/ryanmcgrath)**.
|
||||||
|
|
||||||
Twython is released under an MIT License - see the LICENSE file for more information.
|
|
||||||
|
|
||||||
Want to help?
|
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!
|
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!
|
||||||
|
|
|
||||||
71
README.rst
71
README.rst
|
|
@ -9,12 +9,13 @@ Features
|
||||||
- User information
|
- User information
|
||||||
- Twitter lists
|
- Twitter lists
|
||||||
- Timelines
|
- Timelines
|
||||||
- User avatar URL
|
- Direct Messages
|
||||||
- and anything found in `the docs <https://dev.twitter.com/docs/api/1.1>`_
|
- and anything found in `the docs <https://dev.twitter.com/docs/api/1.1>`_
|
||||||
* Image Uploading!
|
* Image Uploading!
|
||||||
- **Update user status with an image**
|
- **Update user status with an image**
|
||||||
- Change user avatar
|
- Change user avatar
|
||||||
- Change user background image
|
- Change user background image
|
||||||
|
- Change user banner image
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
@ -37,13 +38,12 @@ Usage
|
||||||
Authorization URL
|
Authorization URL
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
::
|
::
|
||||||
|
|
||||||
from twython import Twython
|
from twython import Twython
|
||||||
|
|
||||||
t = Twython(app_key=app_key,
|
t = Twython(app_key, app_secret)
|
||||||
app_secret=app_secret,
|
|
||||||
callback_url='http://google.com/')
|
|
||||||
|
|
||||||
auth_props = t.get_authentication_tokens()
|
auth_props = t.get_authentication_tokens(callback_url='http://google.com')
|
||||||
|
|
||||||
oauth_token = auth_props['oauth_token']
|
oauth_token = auth_props['oauth_token']
|
||||||
oauth_token_secret = auth_props['oauth_token_secret']
|
oauth_token_secret = auth_props['oauth_token_secret']
|
||||||
|
|
@ -56,41 +56,59 @@ Handling the callback
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
::
|
::
|
||||||
|
|
||||||
'''
|
|
||||||
oauth_token and oauth_token_secret come from the previous step
|
|
||||||
if needed, store those in a session variable or something. oauth_verifier from the previous call is now required to pass to get_authorized_tokens
|
|
||||||
'''
|
|
||||||
from twython import Twython
|
from twython import Twython
|
||||||
|
|
||||||
t = Twython(app_key=app_key,
|
# oauth_token_secret comes from the previous step
|
||||||
app_secret=app_secret,
|
# if needed, store that in a session variable or something.
|
||||||
oauth_token=oauth_token,
|
# oauth_verifier and oauth_token from the previous call is now REQUIRED # to pass to get_authorized_tokens
|
||||||
oauth_token_secret=oauth_token_secret)
|
|
||||||
|
# In Django, to get the oauth_verifier and oauth_token from the callback
|
||||||
|
# url querystring, you might do something like this:
|
||||||
|
# oauth_token = request.GET.get('oauth_token')
|
||||||
|
# oauth_verifier = request.GET.get('oauth_verifier')
|
||||||
|
|
||||||
|
t = Twython(app_key, app_secret,
|
||||||
|
oauth_token, oauth_token_secret)
|
||||||
|
|
||||||
auth_tokens = t.get_authorized_tokens(oauth_verifier)
|
auth_tokens = t.get_authorized_tokens(oauth_verifier)
|
||||||
print auth_tokens
|
print auth_tokens
|
||||||
|
|
||||||
*Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/twitter_endpoints.py*
|
*Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/endpoints.py*
|
||||||
|
|
||||||
Getting a user home timeline
|
Getting a user home timeline
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
::
|
::
|
||||||
|
|
||||||
'''
|
|
||||||
oauth_token and oauth_token_secret are the final tokens produced
|
|
||||||
from the `Handling the callback` step
|
|
||||||
'''
|
|
||||||
from twython import Twython
|
from twython import Twython
|
||||||
|
|
||||||
t = Twython(app_key=app_key,
|
# oauth_token and oauth_token_secret are the final tokens produced
|
||||||
app_secret=app_secret,
|
# from the 'Handling the callback' step
|
||||||
oauth_token=oauth_token,
|
|
||||||
oauth_token_secret=oauth_token_secret)
|
t = Twython(app_key, app_secret,
|
||||||
|
oauth_token, oauth_token_secret)
|
||||||
|
|
||||||
# Returns an dict of the user home timeline
|
# Returns an dict of the user home timeline
|
||||||
print t.getHomeTimeline()
|
print t.getHomeTimeline()
|
||||||
|
|
||||||
|
|
||||||
|
Catching exceptions
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Twython offers three Exceptions currently: ``TwythonError``, ``TwythonAuthError`` and ``TwythonRateLimitError``
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from twython import Twython, TwythonAuthError
|
||||||
|
|
||||||
|
t = Twython(MY_WRONG_APP_KEY, MY_WRONG_APP_SECRET,
|
||||||
|
BAD_OAUTH_TOKEN, BAD_OAUTH_TOKEN_SECRET)
|
||||||
|
|
||||||
|
try:
|
||||||
|
t.verifyCredentials()
|
||||||
|
except TwythonAuthError as e:
|
||||||
|
print e
|
||||||
|
|
||||||
|
|
||||||
Streaming API
|
Streaming API
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
*Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access)
|
*Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access)
|
||||||
|
|
@ -143,12 +161,7 @@ from you using them by this library.
|
||||||
|
|
||||||
Twython 3k
|
Twython 3k
|
||||||
----------
|
----------
|
||||||
There's an experimental version of Twython that's made for Python 3k. This is currently not guaranteed to
|
Full compatiabilty with Python 3 is now available seamlessly in the main Twython package. The Twython 3k package has been removed as of Twython 2.8.0
|
||||||
work in all situations, but it's provided so that others can grab it and hack on it.
|
|
||||||
If you choose to try it out, be aware of this.
|
|
||||||
|
|
||||||
**OAuth is now working thanks to updates from [Hades](https://github.com/hades). You'll need to grab
|
|
||||||
his [Python 3 branch for python-oauth2](https://github.com/hades/python-oauth2/tree/python3) to have it work, though.**
|
|
||||||
|
|
||||||
Questions, Comments, etc?
|
Questions, Comments, etc?
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
@ -156,8 +169,6 @@ My hope is that Twython is so simple that you'd never *have* to ask any question
|
||||||
|
|
||||||
You can also follow me on Twitter - `@ryanmcgrath <https://twitter.com/ryanmcgrath>`_
|
You can also follow me on Twitter - `@ryanmcgrath <https://twitter.com/ryanmcgrath>`_
|
||||||
|
|
||||||
*Twython is released under an MIT License - see the LICENSE file for more information.*
|
|
||||||
|
|
||||||
Want to help?
|
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!
|
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!
|
||||||
|
|
|
||||||
17
setup.py
17
setup.py
|
|
@ -1,14 +1,25 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from twython.version import __version__
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
from setuptools import find_packages
|
|
||||||
|
|
||||||
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
|
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
|
||||||
__version__ = '2.7.3'
|
|
||||||
|
packages = [
|
||||||
|
'twython'
|
||||||
|
]
|
||||||
|
|
||||||
|
if sys.argv[-1] == 'publish':
|
||||||
|
os.system('python setup.py sdist upload')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
# Basic package information.
|
# Basic package information.
|
||||||
name='twython',
|
name='twython',
|
||||||
version=__version__,
|
version=__version__,
|
||||||
packages=find_packages(),
|
packages=packages,
|
||||||
|
|
||||||
# Packaging options.
|
# Packaging options.
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,23 @@
|
||||||
from twython import Twython
|
# ______ __ __
|
||||||
|
# /_ __/_ __ __ __ / /_ / /_ ____ ____
|
||||||
|
# / / | | /| / // / / // __// __ \ / __ \ / __ \
|
||||||
|
# / / | |/ |/ // /_/ // /_ / / / // /_/ // / / /
|
||||||
|
# /_/ |__/|__/ \__, / \__//_/ /_/ \____//_/ /_/
|
||||||
|
# /____/
|
||||||
|
|
||||||
|
"""
|
||||||
|
Twython
|
||||||
|
-------
|
||||||
|
|
||||||
|
Twython is a library for Python that wraps the Twitter API.
|
||||||
|
|
||||||
|
It aims to abstract away all the API endpoints, so that additions to the library
|
||||||
|
and/or the Twitter API won't cause any overall problems.
|
||||||
|
|
||||||
|
Questions, comments? ryan@venodesigns.net
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
|
||||||
|
|
||||||
|
from .twython import Twython
|
||||||
from .exceptions import TwythonError, TwythonRateLimitError, TwythonAuthError
|
from .exceptions import TwythonError, TwythonRateLimitError, TwythonAuthError
|
||||||
|
|
|
||||||
38
twython/compat.py
Normal file
38
twython/compat.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_ver = sys.version_info
|
||||||
|
|
||||||
|
#: Python 2.x?
|
||||||
|
is_py2 = (_ver[0] == 2)
|
||||||
|
|
||||||
|
#: Python 3.x?
|
||||||
|
is_py3 = (_ver[0] == 3)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import simplejson as json
|
||||||
|
except ImportError:
|
||||||
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urlparse import parse_qsl
|
||||||
|
except ImportError:
|
||||||
|
from cgi import parse_qsl
|
||||||
|
|
||||||
|
if is_py2:
|
||||||
|
from urllib import urlencode, quote_plus
|
||||||
|
|
||||||
|
builtin_str = str
|
||||||
|
bytes = str
|
||||||
|
str = unicode
|
||||||
|
basestring = basestring
|
||||||
|
numeric_types = (int, long, float)
|
||||||
|
|
||||||
|
|
||||||
|
elif is_py3:
|
||||||
|
from urllib.parse import urlencode, quote_plus
|
||||||
|
|
||||||
|
builtin_str = str
|
||||||
|
str = str
|
||||||
|
bytes = bytes
|
||||||
|
basestring = (str, bytes)
|
||||||
|
numeric_types = (int, float)
|
||||||
|
|
@ -15,9 +15,6 @@
|
||||||
https://dev.twitter.com/docs/api/1.1
|
https://dev.twitter.com/docs/api/1.1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Base Twitter API url, no need to repeat this junk...
|
|
||||||
base_url = 'http://api.twitter.com/{{version}}'
|
|
||||||
|
|
||||||
api_table = {
|
api_table = {
|
||||||
# Timelines
|
# Timelines
|
||||||
'getMentionsTimeline': {
|
'getMentionsTimeline': {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from twitter_endpoints import twitter_http_status_codes
|
from .endpoints import twitter_http_status_codes
|
||||||
|
|
||||||
|
|
||||||
class TwythonError(Exception):
|
class TwythonError(Exception):
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,19 @@
|
||||||
"""
|
|
||||||
Twython is a library for Python that wraps the Twitter API.
|
|
||||||
It aims to abstract away all the API endpoints, so that additions to the library
|
|
||||||
and/or the Twitter API won't cause any overall problems.
|
|
||||||
|
|
||||||
Questions, comments? ryan@venodesigns.net
|
|
||||||
"""
|
|
||||||
|
|
||||||
__author__ = "Ryan McGrath <ryan@venodesigns.net>"
|
|
||||||
__version__ = "2.7.3"
|
|
||||||
|
|
||||||
import urllib
|
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
warnings.simplefilter('default') # For Python 2.7 >
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests_oauthlib import OAuth1
|
from requests_oauthlib import OAuth1
|
||||||
|
|
||||||
try:
|
from .compat import json, urlencode, parse_qsl, quote_plus
|
||||||
from urlparse import parse_qsl
|
from .endpoints import api_table
|
||||||
except ImportError:
|
|
||||||
from cgi import parse_qsl
|
|
||||||
|
|
||||||
# Twython maps keyword based arguments to Twitter API endpoints. The endpoints
|
|
||||||
# table is a file with a dictionary of every API endpoint that Twython supports.
|
|
||||||
from twitter_endpoints import base_url, api_table
|
|
||||||
from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError
|
from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError
|
||||||
|
from .version import __version__
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class Twython(object):
|
class Twython(object):
|
||||||
def __init__(self, app_key=None, app_secret=None, oauth_token=None, oauth_token_secret=None,
|
def __init__(self, app_key=None, app_secret=None, oauth_token=None, oauth_token_secret=None,
|
||||||
headers=None, callback_url=None, twitter_token=None, twitter_secret=None, proxies=None, version='1.1'):
|
headers=None, proxies=None, version='1.1', callback_url=None, twitter_token=None, twitter_secret=None):
|
||||||
"""Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below).
|
"""Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below).
|
||||||
|
|
||||||
:param app_key: (optional) Your applications key
|
:param app_key: (optional) Your applications key
|
||||||
|
|
@ -46,46 +25,55 @@ class Twython(object):
|
||||||
:param proxies: (optional) A dictionary of proxies, for example {"http":"proxy.example.org:8080", "https":"proxy.example.org:8081"}.
|
:param proxies: (optional) A dictionary of proxies, for example {"http":"proxy.example.org:8080", "https":"proxy.example.org:8081"}.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Needed for hitting that there API.
|
# API urls, OAuth urls and API version; needed for hitting that there API.
|
||||||
self.api_version = version
|
self.api_version = version
|
||||||
self.api_url = 'https://api.twitter.com/%s'
|
self.api_url = 'https://api.twitter.com/%s'
|
||||||
self.request_token_url = self.api_url % 'oauth/request_token'
|
self.request_token_url = self.api_url % 'oauth/request_token'
|
||||||
self.access_token_url = self.api_url % 'oauth/access_token'
|
self.access_token_url = self.api_url % 'oauth/access_token'
|
||||||
self.authenticate_url = self.api_url % 'oauth/authenticate'
|
self.authenticate_url = self.api_url % 'oauth/authenticate'
|
||||||
|
|
||||||
# Enforce unicode on keys and secrets
|
self.app_key = app_key or twitter_token
|
||||||
self.app_key = app_key and unicode(app_key) or twitter_token and unicode(twitter_token)
|
self.app_secret = app_secret or twitter_secret
|
||||||
self.app_secret = app_key and unicode(app_secret) or twitter_secret and unicode(twitter_secret)
|
self.oauth_token = oauth_token
|
||||||
|
self.oauth_token_secret = oauth_token_secret
|
||||||
self.oauth_token = oauth_token and u'%s' % oauth_token
|
|
||||||
self.oauth_token_secret = oauth_token_secret and u'%s' % oauth_token_secret
|
|
||||||
|
|
||||||
self.callback_url = callback_url
|
self.callback_url = callback_url
|
||||||
|
|
||||||
# If there's headers, set them, otherwise be an embarassing parent for their own good.
|
if twitter_token or twitter_secret:
|
||||||
self.headers = headers or {'User-Agent': 'Twython v' + __version__}
|
warnings.warn(
|
||||||
|
'Instead of twitter_token or twitter_secret, please use app_key or app_secret (respectively).',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
|
||||||
# Allow for unauthenticated requests
|
if callback_url:
|
||||||
self.client = requests.Session()
|
warnings.warn(
|
||||||
self.client.proxies = proxies
|
'Please pass callback_url to the get_authentication_tokens method rather than Twython.__init__',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
|
||||||
|
self.headers = {'User-Agent': 'Twython v' + __version__}
|
||||||
|
if headers:
|
||||||
|
self.headers.update(headers)
|
||||||
|
|
||||||
|
# Generate OAuth authentication object for the request
|
||||||
|
# If no keys/tokens are passed to __init__, self.auth=None allows for
|
||||||
|
# unauthenticated requests, although I think all v1.1 requests need auth
|
||||||
self.auth = None
|
self.auth = None
|
||||||
|
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:
|
||||||
|
self.auth = OAuth1(self.app_key, self.app_secret)
|
||||||
|
|
||||||
if self.app_key is not None and self.app_secret is not None and \
|
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:
|
self.oauth_token is not None and self.oauth_token_secret is not None:
|
||||||
self.auth = OAuth1(self.app_key, self.app_secret,
|
self.auth = OAuth1(self.app_key, self.app_secret,
|
||||||
signature_type='auth_header')
|
self.oauth_token, self.oauth_token_secret)
|
||||||
|
|
||||||
if self.app_key is not None and self.app_secret is not None and \
|
self.client = requests.Session()
|
||||||
self.oauth_token is not None and self.oauth_token_secret is not None:
|
self.client.headers = self.headers
|
||||||
self.auth = OAuth1(self.app_key, self.app_secret,
|
self.client.proxies = proxies
|
||||||
self.oauth_token, self.oauth_token_secret,
|
self.client.auth = self.auth
|
||||||
signature_type='auth_header')
|
|
||||||
|
|
||||||
if self.auth is not None:
|
|
||||||
self.client = requests.Session()
|
|
||||||
self.client.headers = self.headers
|
|
||||||
self.client.auth = self.auth
|
|
||||||
self.client.proxies = proxies
|
|
||||||
|
|
||||||
# register available funcs to allow listing name when debugging.
|
# register available funcs to allow listing name when debugging.
|
||||||
def setFunc(key):
|
def setFunc(key):
|
||||||
|
|
@ -101,8 +89,8 @@ class Twython(object):
|
||||||
fn = api_table[api_call]
|
fn = api_table[api_call]
|
||||||
url = re.sub(
|
url = re.sub(
|
||||||
'\{\{(?P<m>[a-zA-Z_]+)\}\}',
|
'\{\{(?P<m>[a-zA-Z_]+)\}\}',
|
||||||
lambda m: "%s" % kwargs.get(m.group(1), self.api_version),
|
lambda m: "%s" % kwargs.get(m.group(1)),
|
||||||
base_url + fn['url']
|
self.api_url % self.api_version + fn['url']
|
||||||
)
|
)
|
||||||
|
|
||||||
content = self._request(url, method=fn['method'], params=kwargs)
|
content = self._request(url, method=fn['method'], params=kwargs)
|
||||||
|
|
@ -114,15 +102,7 @@ class Twython(object):
|
||||||
code twice, right? ;)
|
code twice, right? ;)
|
||||||
'''
|
'''
|
||||||
method = method.lower()
|
method = method.lower()
|
||||||
if not method in ('get', 'post'):
|
|
||||||
raise TwythonError('Method must be of GET or POST')
|
|
||||||
|
|
||||||
params = params or {}
|
params = params or {}
|
||||||
# requests doesn't like items that can't be converted to unicode,
|
|
||||||
# so let's be nice and do that for the user
|
|
||||||
for k, v in params.items():
|
|
||||||
if isinstance(v, (int, bool)):
|
|
||||||
params[k] = u'%s' % v
|
|
||||||
|
|
||||||
func = getattr(self.client, method)
|
func = getattr(self.client, method)
|
||||||
if method == 'get':
|
if method == 'get':
|
||||||
|
|
@ -176,7 +156,7 @@ class Twython(object):
|
||||||
error_code=response.status_code,
|
error_code=response.status_code,
|
||||||
retry_after=response.headers.get('retry-after'))
|
retry_after=response.headers.get('retry-after'))
|
||||||
|
|
||||||
# if we have a json error here, then it's not an official TwitterAPI error
|
# if we have a json error here, then it's not an official Twitter API error
|
||||||
if json_error and not response.status_code in (200, 201, 202):
|
if json_error and not response.status_code in (200, 201, 202):
|
||||||
raise TwythonError('Response was not valid JSON, unable to decode.')
|
raise TwythonError('Response was not valid JSON, unable to decode.')
|
||||||
|
|
||||||
|
|
@ -228,23 +208,23 @@ class Twython(object):
|
||||||
return self._last_call['headers'][header]
|
return self._last_call['headers'][header]
|
||||||
return self._last_call
|
return self._last_call
|
||||||
|
|
||||||
def get_authentication_tokens(self, force_login=False, screen_name=''):
|
def get_authentication_tokens(self, callback_url=None, force_login=False, screen_name=''):
|
||||||
"""Returns an authorization URL for a user to hit.
|
"""Returns a dict including an authorization URL (auth_url) to direct a user to
|
||||||
|
|
||||||
|
:param callback_url: (optional.. for now) Url the user is returned to after they authorize your app
|
||||||
:param force_login: (optional) Forces the user to enter their credentials to ensure the correct users account is authorized.
|
:param force_login: (optional) Forces the user to enter their credentials to ensure the correct users account is authorized.
|
||||||
:param app_secret: (optional) If forced_login is set OR user is not currently logged in, Prefills the username input box of the OAuth login screen with the given value
|
:param app_secret: (optional) If forced_login is set OR user is not currently logged in, Prefills the username input box of the OAuth login screen with the given value
|
||||||
"""
|
"""
|
||||||
request_args = {}
|
callback_url = callback_url or self.callback_url
|
||||||
if self.callback_url:
|
request_args = {'oauth_callback': callback_url}
|
||||||
request_args['oauth_callback'] = self.callback_url
|
|
||||||
|
|
||||||
response = self.client.get(self.request_token_url, params=request_args)
|
response = self.client.get(self.request_token_url, params=request_args)
|
||||||
|
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
raise TwythonAuthError(response.content, error_code=response.status_code)
|
raise TwythonAuthError(response.content, error_code=response.status_code)
|
||||||
elif response.status_code != 200:
|
elif response.status_code != 200:
|
||||||
raise TwythonError(response.content, error_code=response.status_code)
|
raise TwythonError(response.content, error_code=response.status_code)
|
||||||
|
|
||||||
request_tokens = dict(parse_qsl(response.content))
|
request_tokens = dict(parse_qsl(response.content.decode('utf-8')))
|
||||||
if not request_tokens:
|
if not request_tokens:
|
||||||
raise TwythonError('Unable to decode request tokens.')
|
raise TwythonError('Unable to decode request tokens.')
|
||||||
|
|
||||||
|
|
@ -261,18 +241,20 @@ class Twython(object):
|
||||||
})
|
})
|
||||||
|
|
||||||
# Use old-style callback argument if server didn't accept new-style
|
# Use old-style callback argument if server didn't accept new-style
|
||||||
if self.callback_url and not oauth_callback_confirmed:
|
if callback_url and not oauth_callback_confirmed:
|
||||||
auth_url_params['oauth_callback'] = self.callback_url
|
auth_url_params['oauth_callback'] = self.callback_url
|
||||||
|
|
||||||
request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params)
|
request_tokens['auth_url'] = self.authenticate_url + '?' + urlencode(auth_url_params)
|
||||||
|
|
||||||
return request_tokens
|
return request_tokens
|
||||||
|
|
||||||
def get_authorized_tokens(self, oauth_verifier):
|
def get_authorized_tokens(self, oauth_verifier):
|
||||||
"""Returns authorized tokens after they go through the auth_url phase.
|
"""Returns authorized tokens after they go through the auth_url phase.
|
||||||
|
|
||||||
|
:param oauth_verifier: (required) The oauth_verifier retrieved from the callback url querystring
|
||||||
"""
|
"""
|
||||||
response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier})
|
response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier})
|
||||||
authorized_tokens = dict(parse_qsl(response.content))
|
authorized_tokens = dict(parse_qsl(response.content.decode('utf-8')))
|
||||||
if not authorized_tokens:
|
if not authorized_tokens:
|
||||||
raise TwythonError('Unable to decode authorized tokens.')
|
raise TwythonError('Unable to decode authorized tokens.')
|
||||||
|
|
||||||
|
|
@ -308,7 +290,7 @@ class Twython(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def constructApiURL(base_url, params):
|
def constructApiURL(base_url, params):
|
||||||
return base_url + "?" + "&".join(["%s=%s" % (Twython.unicode2utf8(key), urllib.quote_plus(Twython.unicode2utf8(value))) for (key, value) in params.iteritems()])
|
return base_url + "?" + "&".join(["%s=%s" % (Twython.unicode2utf8(key), quote_plus(Twython.unicode2utf8(value))) for (key, value) in params.iteritems()])
|
||||||
|
|
||||||
def searchGen(self, search_query, **kwargs):
|
def searchGen(self, search_query, **kwargs):
|
||||||
""" Returns a generator of tweets that match a specified query.
|
""" Returns a generator of tweets that match a specified query.
|
||||||
|
|
@ -331,7 +313,7 @@ class Twython(object):
|
||||||
yield tweet
|
yield tweet
|
||||||
|
|
||||||
if 'page' not in kwargs:
|
if 'page' not in kwargs:
|
||||||
kwargs['page'] = '2'
|
kwargs['page'] = 2
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
kwargs['page'] = int(kwargs['page'])
|
kwargs['page'] = int(kwargs['page'])
|
||||||
|
|
@ -416,7 +398,7 @@ class Twython(object):
|
||||||
only API version for Twitter that supports this call
|
only API version for Twitter that supports this call
|
||||||
|
|
||||||
**params - You may pass items that are taken in this doc
|
**params - You may pass items that are taken in this doc
|
||||||
(https://dev.twitter.com/docs/api/1/post/account/update_profile_banner)
|
(https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner)
|
||||||
"""
|
"""
|
||||||
url = 'https://api.twitter.com/%s/account/update_profile_banner.json' % version
|
url = 'https://api.twitter.com/%s/account/update_profile_banner.json' % version
|
||||||
return self._media_update(url,
|
return self._media_update(url,
|
||||||
|
|
|
||||||
1
twython/version.py
Normal file
1
twython/version.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
__version__ = '2.7.3'
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from .twython import Twython
|
|
||||||
|
|
@ -1,334 +0,0 @@
|
||||||
"""
|
|
||||||
A huge map of every Twitter API endpoint to a function definition in Twython.
|
|
||||||
|
|
||||||
Parameters that need to be embedded in the URL are treated with mustaches, e.g:
|
|
||||||
|
|
||||||
{{version}}, etc
|
|
||||||
|
|
||||||
When creating new endpoint definitions, keep in mind that the name of the mustache
|
|
||||||
will be replaced with the keyword that gets passed in to the function at call time.
|
|
||||||
|
|
||||||
i.e, in this case, if I pass version = 47 to any function, {{version}} will be replaced
|
|
||||||
with 47, instead of defaulting to 1 (said defaulting takes place at conversion time).
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Base Twitter API url, no need to repeat this junk...
|
|
||||||
base_url = 'http://api.twitter.com/{{version}}'
|
|
||||||
|
|
||||||
api_table = {
|
|
||||||
'getRateLimitStatus': {
|
|
||||||
'url': '/account/rate_limit_status.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
'verifyCredentials': {
|
|
||||||
'url': '/account/verify_credentials.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
'endSession' : {
|
|
||||||
'url': '/account/end_session.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Timeline methods
|
|
||||||
'getPublicTimeline': {
|
|
||||||
'url': '/statuses/public_timeline.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getHomeTimeline': {
|
|
||||||
'url': '/statuses/home_timeline.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getUserTimeline': {
|
|
||||||
'url': '/statuses/user_timeline.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getFriendsTimeline': {
|
|
||||||
'url': '/statuses/friends_timeline.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Interfacing with friends/followers
|
|
||||||
'getUserMentions': {
|
|
||||||
'url': '/statuses/mentions.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getFriendsStatus': {
|
|
||||||
'url': '/statuses/friends.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getFollowersStatus': {
|
|
||||||
'url': '/statuses/followers.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'createFriendship': {
|
|
||||||
'url': '/friendships/create.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'destroyFriendship': {
|
|
||||||
'url': '/friendships/destroy.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'getFriendsIDs': {
|
|
||||||
'url': '/friends/ids.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getFollowersIDs': {
|
|
||||||
'url': '/followers/ids.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getIncomingFriendshipIDs': {
|
|
||||||
'url': '/friendships/incoming.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getOutgoingFriendshipIDs': {
|
|
||||||
'url': '/friendships/outgoing.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Retweets
|
|
||||||
'reTweet': {
|
|
||||||
'url': '/statuses/retweet/{{id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'getRetweets': {
|
|
||||||
'url': '/statuses/retweets/{{id}}.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'retweetedOfMe': {
|
|
||||||
'url': '/statuses/retweets_of_me.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'retweetedByMe': {
|
|
||||||
'url': '/statuses/retweeted_by_me.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'retweetedToMe': {
|
|
||||||
'url': '/statuses/retweeted_to_me.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# User methods
|
|
||||||
'showUser': {
|
|
||||||
'url': '/users/show.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'searchUsers': {
|
|
||||||
'url': '/users/search.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
'lookupUser': {
|
|
||||||
'url': '/users/lookup.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Status methods - showing, updating, destroying, etc.
|
|
||||||
'showStatus': {
|
|
||||||
'url': '/statuses/show/{{id}}.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'updateStatus': {
|
|
||||||
'url': '/statuses/update.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'destroyStatus': {
|
|
||||||
'url': '/statuses/destroy/{{id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Direct Messages - getting, sending, effing, etc.
|
|
||||||
'getDirectMessages': {
|
|
||||||
'url': '/direct_messages.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getSentMessages': {
|
|
||||||
'url': '/direct_messages/sent.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'sendDirectMessage': {
|
|
||||||
'url': '/direct_messages/new.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'destroyDirectMessage': {
|
|
||||||
'url': '/direct_messages/destroy/{{id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Friendship methods
|
|
||||||
'checkIfFriendshipExists': {
|
|
||||||
'url': '/friendships/exists.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'showFriendship': {
|
|
||||||
'url': '/friendships/show.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Profile methods
|
|
||||||
'updateProfile': {
|
|
||||||
'url': '/account/update_profile.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'updateProfileColors': {
|
|
||||||
'url': '/account/update_profile_colors.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'myTotals': {
|
|
||||||
'url' : '/account/totals.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Favorites methods
|
|
||||||
'getFavorites': {
|
|
||||||
'url': '/favorites.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'createFavorite': {
|
|
||||||
'url': '/favorites/create/{{id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'destroyFavorite': {
|
|
||||||
'url': '/favorites/destroy/{{id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Blocking methods
|
|
||||||
'createBlock': {
|
|
||||||
'url': '/blocks/create/{{id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'destroyBlock': {
|
|
||||||
'url': '/blocks/destroy/{{id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'getBlocking': {
|
|
||||||
'url': '/blocks/blocking.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getBlockedIDs': {
|
|
||||||
'url': '/blocks/blocking/ids.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'checkIfBlockExists': {
|
|
||||||
'url': '/blocks/exists.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Trending methods
|
|
||||||
'getCurrentTrends': {
|
|
||||||
'url': '/trends/current.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getDailyTrends': {
|
|
||||||
'url': '/trends/daily.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getWeeklyTrends': {
|
|
||||||
'url': '/trends/weekly.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'availableTrends': {
|
|
||||||
'url': '/trends/available.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'trendsByLocation': {
|
|
||||||
'url': '/trends/{{woeid}}.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# Saved Searches
|
|
||||||
'getSavedSearches': {
|
|
||||||
'url': '/saved_searches.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'showSavedSearch': {
|
|
||||||
'url': '/saved_searches/show/{{id}}.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'createSavedSearch': {
|
|
||||||
'url': '/saved_searches/create.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'destroySavedSearch': {
|
|
||||||
'url': '/saved_searches/destroy/{{id}}.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
|
|
||||||
# List API methods/endpoints. Fairly exhaustive and annoying in general. ;P
|
|
||||||
'createList': {
|
|
||||||
'url': '/{{username}}/lists.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'updateList': {
|
|
||||||
'url': '/{{username}}/lists/{{list_id}}.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'showLists': {
|
|
||||||
'url': '/{{username}}/lists.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getListMemberships': {
|
|
||||||
'url': '/{{username}}/lists/memberships.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getListSubscriptions': {
|
|
||||||
'url': '/{{username}}/lists/subscriptions.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'deleteList': {
|
|
||||||
'url': '/{{username}}/lists/{{list_id}}.json',
|
|
||||||
'method': 'DELETE',
|
|
||||||
},
|
|
||||||
'getListTimeline': {
|
|
||||||
'url': '/{{username}}/lists/{{list_id}}/statuses.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'getSpecificList': {
|
|
||||||
'url': '/{{username}}/lists/{{list_id}}/statuses.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'addListMember': {
|
|
||||||
'url': '/{{username}}/{{list_id}}/members.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'getListMembers': {
|
|
||||||
'url': '/{{username}}/{{list_id}}/members.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'deleteListMember': {
|
|
||||||
'url': '/{{username}}/{{list_id}}/members.json',
|
|
||||||
'method': 'DELETE',
|
|
||||||
},
|
|
||||||
'getListSubscribers': {
|
|
||||||
'url': '/{{username}}/{{list_id}}/subscribers.json',
|
|
||||||
'method': 'GET',
|
|
||||||
},
|
|
||||||
'subscribeToList': {
|
|
||||||
'url': '/{{username}}/{{list_id}}/subscribers.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'unsubscribeFromList': {
|
|
||||||
'url': '/{{username}}/{{list_id}}/subscribers.json',
|
|
||||||
'method': 'DELETE',
|
|
||||||
},
|
|
||||||
|
|
||||||
# The one-offs
|
|
||||||
'notificationFollow': {
|
|
||||||
'url': '/notifications/follow/follow.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'notificationLeave': {
|
|
||||||
'url': '/notifications/leave/leave.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'updateDeliveryService': {
|
|
||||||
'url': '/account/update_delivery_device.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
'reportSpam': {
|
|
||||||
'url': '/report_spam.json',
|
|
||||||
'method': 'POST',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,513 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Twython is a library for Python that wraps the Twitter API.
|
|
||||||
It aims to abstract away all the API endpoints, so that additions to the library
|
|
||||||
and/or the Twitter API won't cause any overall problems.
|
|
||||||
|
|
||||||
Questions, comments? ryan@venodesigns.net
|
|
||||||
"""
|
|
||||||
|
|
||||||
__author__ = "Ryan McGrath <ryan@venodesigns.net>"
|
|
||||||
__version__ = "1.4.7"
|
|
||||||
|
|
||||||
import cgi
|
|
||||||
import urllib.request, urllib.parse, urllib.error
|
|
||||||
import urllib.request, urllib.error, urllib.parse
|
|
||||||
import urllib.parse
|
|
||||||
import http.client
|
|
||||||
import httplib2
|
|
||||||
import mimetypes
|
|
||||||
import re
|
|
||||||
import inspect
|
|
||||||
import email.generator
|
|
||||||
|
|
||||||
import oauth2 as oauth
|
|
||||||
|
|
||||||
# Twython maps keyword based arguments to Twitter API endpoints. The endpoints
|
|
||||||
# table is a file with a dictionary of every API endpoint that Twython supports.
|
|
||||||
from twitter_endpoints import base_url, api_table
|
|
||||||
|
|
||||||
from urllib.error import HTTPError
|
|
||||||
|
|
||||||
# There are some special setups (like, oh, a Django application) where
|
|
||||||
# simplejson exists behind the scenes anyway. Past Python 2.6, this should
|
|
||||||
# never really cause any problems to begin with.
|
|
||||||
try:
|
|
||||||
# Python 2.6 and up
|
|
||||||
import json as simplejson
|
|
||||||
except ImportError:
|
|
||||||
# Seriously wtf is wrong with you if you get this Exception.
|
|
||||||
raise Exception("Twython3k requires a json library to work. http://www.undefined.org/python/")
|
|
||||||
|
|
||||||
# Try and gauge the old OAuth2 library spec. Versions 1.5 and greater no longer have the callback
|
|
||||||
# url as part of the request object; older versions we need to patch for Python 2.5... ugh. ;P
|
|
||||||
OAUTH_CALLBACK_IN_URL = False
|
|
||||||
OAUTH_LIB_SUPPORTS_CALLBACK = False
|
|
||||||
if not hasattr(oauth, '_version') or float(oauth._version.manual_verstr) <= 1.4:
|
|
||||||
OAUTH_CLIENT_INSPECTION = inspect.getargspec(oauth.Client.request)
|
|
||||||
try:
|
|
||||||
OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION.args
|
|
||||||
except AttributeError:
|
|
||||||
# Python 2.5 doesn't return named tuples, so don't look for an args section specifically.
|
|
||||||
OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION
|
|
||||||
else:
|
|
||||||
OAUTH_CALLBACK_IN_URL = True
|
|
||||||
|
|
||||||
class TwythonError(AttributeError):
|
|
||||||
"""
|
|
||||||
Generic error class, catch-all for most Twython issues.
|
|
||||||
Special cases are handled by APILimit and AuthError.
|
|
||||||
|
|
||||||
Note: To use these, the syntax has changed as of Twython 1.3. To catch these,
|
|
||||||
you need to explicitly import them into your code, e.g:
|
|
||||||
|
|
||||||
from twython import TwythonError, APILimit, AuthError
|
|
||||||
"""
|
|
||||||
def __init__(self, msg, error_code=None):
|
|
||||||
self.msg = msg
|
|
||||||
if error_code == 400:
|
|
||||||
raise APILimit(msg)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return repr(self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
class TwythonAPILimit(TwythonError):
|
|
||||||
"""
|
|
||||||
Raised when you've hit an API limit. Try to avoid these, read the API
|
|
||||||
docs if you're running into issues here, Twython does not concern itself with
|
|
||||||
this matter beyond telling you that you've done goofed.
|
|
||||||
"""
|
|
||||||
def __init__(self, msg):
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return repr(self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
class APILimit(TwythonError):
|
|
||||||
"""
|
|
||||||
Raised when you've hit an API limit. Try to avoid these, read the API
|
|
||||||
docs if you're running into issues here, Twython does not concern itself with
|
|
||||||
this matter beyond telling you that you've done goofed.
|
|
||||||
|
|
||||||
DEPRECATED, you should be importing TwythonAPILimit instead. :)
|
|
||||||
"""
|
|
||||||
def __init__(self, msg):
|
|
||||||
self.msg = '%s\n Notice: APILimit is deprecated and soon to be removed, catch TwythonAPILimit instead!' % msg
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return repr(self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
class TwythonAuthError(TwythonError):
|
|
||||||
"""
|
|
||||||
Raised when you try to access a protected resource and it fails due to some issue with
|
|
||||||
your authentication.
|
|
||||||
"""
|
|
||||||
def __init__(self, msg):
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return repr(self.msg)
|
|
||||||
|
|
||||||
class AuthError(TwythonError):
|
|
||||||
"""
|
|
||||||
Raised when you try to access a protected resource and it fails due to some issue with
|
|
||||||
your authentication.
|
|
||||||
|
|
||||||
DEPRECATED, you should be importing TwythonAuthError instead.
|
|
||||||
"""
|
|
||||||
def __init__(self, msg):
|
|
||||||
self.msg = '%s\n Notice: AuthLimit is deprecated and soon to be removed, catch TwythonAPILimit instead!' % msg
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return repr(self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
class Twython(object):
|
|
||||||
def __init__(self, twitter_token = None, twitter_secret = None, oauth_token = None, oauth_token_secret = None, headers=None, callback_url=None, client_args={}):
|
|
||||||
"""setup(self, oauth_token = None, headers = None)
|
|
||||||
|
|
||||||
Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below).
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
twitter_token - Given to you when you register your application with Twitter.
|
|
||||||
twitter_secret - Given to you when you register your application with Twitter.
|
|
||||||
oauth_token - If you've gone through the authentication process and have a token for this user,
|
|
||||||
pass it in and it'll be used for all requests going forward.
|
|
||||||
oauth_token_secret - see oauth_token; it's the other half.
|
|
||||||
headers - User agent header, dictionary style ala {'User-Agent': 'Bert'}
|
|
||||||
client_args - additional arguments for HTTP client (see httplib2.Http.__init__), e.g. {'timeout': 10.0}
|
|
||||||
|
|
||||||
** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported.
|
|
||||||
"""
|
|
||||||
# Needed for hitting that there API.
|
|
||||||
self.request_token_url = 'https://twitter.com/oauth/request_token'
|
|
||||||
self.access_token_url = 'https://twitter.com/oauth/access_token'
|
|
||||||
self.authorize_url = 'https://twitter.com/oauth/authorize'
|
|
||||||
self.authenticate_url = 'https://twitter.com/oauth/authenticate'
|
|
||||||
self.twitter_token = twitter_token
|
|
||||||
self.twitter_secret = twitter_secret
|
|
||||||
self.oauth_token = oauth_token
|
|
||||||
self.oauth_secret = oauth_token_secret
|
|
||||||
self.callback_url = callback_url
|
|
||||||
|
|
||||||
# If there's headers, set them, otherwise be an embarassing parent for their own good.
|
|
||||||
self.headers = headers
|
|
||||||
if self.headers is None:
|
|
||||||
self.headers = {'User-agent': 'Twython Python Twitter Library v1.3'}
|
|
||||||
|
|
||||||
consumer = None
|
|
||||||
token = None
|
|
||||||
|
|
||||||
if self.twitter_token is not None and self.twitter_secret is not None:
|
|
||||||
consumer = oauth.Consumer(self.twitter_token, self.twitter_secret)
|
|
||||||
|
|
||||||
if self.oauth_token is not None and self.oauth_secret is not None:
|
|
||||||
token = oauth.Token(oauth_token, oauth_token_secret)
|
|
||||||
|
|
||||||
# Filter down through the possibilities here - if they have a token, if they're first stage, etc.
|
|
||||||
if consumer is not None and token is not None:
|
|
||||||
self.client = oauth.Client(consumer, token, **client_args)
|
|
||||||
elif consumer is not None:
|
|
||||||
self.client = oauth.Client(consumer, **client_args)
|
|
||||||
else:
|
|
||||||
# If they don't do authentication, but still want to request unprotected resources, we need an opener.
|
|
||||||
self.client = httplib2.Http(**client_args)
|
|
||||||
def setFunc(key):
|
|
||||||
return lambda **kwargs: self._constructFunc(key, **kwargs)
|
|
||||||
# register available funcs to allow listing name when debugging.
|
|
||||||
for key in api_table.keys():
|
|
||||||
self.__dict__[key] = setFunc(key)
|
|
||||||
|
|
||||||
def _constructFunc(self, api_call, **kwargs):
|
|
||||||
# Go through and replace any mustaches that are in our API url.
|
|
||||||
fn = api_table[api_call]
|
|
||||||
base = re.sub(
|
|
||||||
'\{\{(?P<m>[a-zA-Z_]+)\}\}',
|
|
||||||
lambda m: "%s" % kwargs.get(m.group(1), '1'), # The '1' here catches the API version. Slightly hilarious.
|
|
||||||
base_url + fn['url']
|
|
||||||
)
|
|
||||||
|
|
||||||
# Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication
|
|
||||||
if fn['method'] == 'POST':
|
|
||||||
resp, content = self.client.request(base, fn['method'], urllib.parse.urlencode(dict([k, Twython.encode(v)] for k, v in list(kwargs.items()))), headers = self.headers)
|
|
||||||
else:
|
|
||||||
url = base + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in list(kwargs.items())])
|
|
||||||
resp, content = self.client.request(url, fn['method'], headers = self.headers)
|
|
||||||
|
|
||||||
return simplejson.loads(content.decode('utf-8'))
|
|
||||||
|
|
||||||
def get_authentication_tokens(self):
|
|
||||||
"""
|
|
||||||
get_auth_url(self)
|
|
||||||
|
|
||||||
Returns an authorization URL for a user to hit.
|
|
||||||
"""
|
|
||||||
callback_url = self.callback_url or 'oob'
|
|
||||||
|
|
||||||
request_args = {}
|
|
||||||
method = 'GET'
|
|
||||||
if OAUTH_LIB_SUPPORTS_CALLBACK:
|
|
||||||
request_args['callback_url'] = callback_url
|
|
||||||
else:
|
|
||||||
# This is a hack for versions of oauth that don't support the callback URL. This is also
|
|
||||||
# done differently than the Python2 version of Twython, which uses Requests internally (as opposed to httplib2).
|
|
||||||
request_args['body'] = urllib.urlencode({'oauth_callback': callback_url})
|
|
||||||
method = 'POST'
|
|
||||||
|
|
||||||
resp, content = self.client.request(self.request_token_url, method, **request_args)
|
|
||||||
|
|
||||||
if resp['status'] != '200':
|
|
||||||
raise AuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (resp['status'], content))
|
|
||||||
|
|
||||||
try:
|
|
||||||
request_tokens = dict(urllib.parse.parse_qsl(content))
|
|
||||||
except:
|
|
||||||
request_tokens = dict(cgi.parse_qsl(content))
|
|
||||||
|
|
||||||
oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed')=='true'
|
|
||||||
|
|
||||||
if not OAUTH_LIB_SUPPORTS_CALLBACK and callback_url != 'oob' and oauth_callback_confirmed:
|
|
||||||
import warnings
|
|
||||||
warnings.warn("oauth2 library doesn't support OAuth 1.0a type callback, but remote requires it")
|
|
||||||
oauth_callback_confirmed = False
|
|
||||||
|
|
||||||
auth_url_params = {
|
|
||||||
'oauth_token' : request_tokens['oauth_token'],
|
|
||||||
}
|
|
||||||
|
|
||||||
if OAUTH_CALLBACK_IN_URL or (callback_url!='oob' and not oauth_callback_confirmed):
|
|
||||||
auth_url_params['oauth_callback'] = callback_url
|
|
||||||
|
|
||||||
request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.parse.urlencode(auth_url_params)
|
|
||||||
|
|
||||||
return request_tokens
|
|
||||||
|
|
||||||
def get_authorized_tokens(self):
|
|
||||||
"""
|
|
||||||
get_authorized_tokens
|
|
||||||
|
|
||||||
Returns authorized tokens after they go through the auth_url phase.
|
|
||||||
"""
|
|
||||||
resp, content = self.client.request(self.access_token_url, "GET")
|
|
||||||
try:
|
|
||||||
return dict(urllib.parse.parse_qsl(content))
|
|
||||||
except:
|
|
||||||
return dict(cgi.parse_qsl(content))
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------------------------------------------------
|
|
||||||
# The following methods are all different in some manner or require special attention with regards to the Twitter API.
|
|
||||||
# Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able,
|
|
||||||
# but it's not high on the priority list at the moment.
|
|
||||||
# ------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def constructApiURL(base_url, params):
|
|
||||||
return base_url + "?" + "&".join(["%s=%s" % (key, urllib.parse.quote_plus(Twython.unicode2utf8(value))) for (key, value) in list(params.items())])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"):
|
|
||||||
"""shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl")
|
|
||||||
|
|
||||||
Shortens url specified by url_to_shorten.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
url_to_shorten - URL to shorten.
|
|
||||||
shortener - In case you want to use a url shortening service other than is.gd.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
content = urllib.request.urlopen(shortener + "?" + urllib.parse.urlencode({query: Twython.unicode2utf8(url_to_shorten)})).read()
|
|
||||||
return content
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("shortenURL() failed with a %s error code." % repr(e.code))
|
|
||||||
|
|
||||||
def bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs):
|
|
||||||
""" bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs)
|
|
||||||
|
|
||||||
A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that
|
|
||||||
contain their respective data sets.
|
|
||||||
|
|
||||||
Statuses for the users in question will be returned inline if they exist. Requires authentication!
|
|
||||||
"""
|
|
||||||
if ids:
|
|
||||||
kwargs['user_id'] = ','.join(map(str, ids))
|
|
||||||
if screen_names:
|
|
||||||
kwargs['screen_name'] = ','.join(screen_names)
|
|
||||||
|
|
||||||
lookupURL = Twython.constructApiURL("https://api.twitter.com/%d/users/lookup.json" % version, kwargs)
|
|
||||||
try:
|
|
||||||
resp, content = self.client.request(lookupURL, "POST", headers = self.headers)
|
|
||||||
return simplejson.loads(content.decode('utf-8'))
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("bulkUserLookup() failed with a %s error code." % repr(e.code), e.code)
|
|
||||||
|
|
||||||
def search(self, **kwargs):
|
|
||||||
"""search(search_query, **kwargs)
|
|
||||||
|
|
||||||
Returns tweets that match a specified query.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters.
|
|
||||||
|
|
||||||
e.g x.search(q = "jjndf", page = '2')
|
|
||||||
"""
|
|
||||||
searchURL = Twython.constructApiURL("https://search.twitter.com/search.json", kwargs)
|
|
||||||
try:
|
|
||||||
resp, content = self.client.request(searchURL, "GET", headers = self.headers)
|
|
||||||
return simplejson.loads(content.decode('utf-8'))
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("getSearchTimeline() failed with a %s error code." % repr(e.code), e.code)
|
|
||||||
|
|
||||||
def searchTwitter(self, **kwargs):
|
|
||||||
"""use search() ,this is a fall back method to support searchTwitter()
|
|
||||||
"""
|
|
||||||
return self.search(**kwargs)
|
|
||||||
|
|
||||||
def searchGen(self, search_query, **kwargs):
|
|
||||||
"""searchGen(search_query, **kwargs)
|
|
||||||
|
|
||||||
Returns a generator of tweets that match a specified query.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters.
|
|
||||||
|
|
||||||
e.g x.searchGen("python", page="2") or
|
|
||||||
x.searchGen(search_query = "python", page = "2")
|
|
||||||
"""
|
|
||||||
searchURL = Twython.constructApiURL("https://search.twitter.com/search.json?q=%s" % Twython.unicode2utf8(search_query), kwargs)
|
|
||||||
try:
|
|
||||||
resp, content = self.client.request(searchURL, "GET", headers = self.headers)
|
|
||||||
data = simplejson.loads(content.decode('utf-8'))
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("searchGen() failed with a %s error code." % repr(e.code), e.code)
|
|
||||||
|
|
||||||
if not data['results']:
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
for tweet in data['results']:
|
|
||||||
yield tweet
|
|
||||||
|
|
||||||
if 'page' not in kwargs:
|
|
||||||
kwargs['page'] = '2'
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
kwargs['page'] = int(kwargs['page'])
|
|
||||||
kwargs['page'] += 1
|
|
||||||
kwargs['page'] = str(kwargs['page'])
|
|
||||||
except TypeError:
|
|
||||||
raise TwythonError("searchGen() exited because page takes str")
|
|
||||||
except e:
|
|
||||||
raise TwythonError("searchGen() failed with %s error code" %\
|
|
||||||
repr(e.code), e.code)
|
|
||||||
|
|
||||||
for tweet in self.searchGen(search_query, **kwargs):
|
|
||||||
yield tweet
|
|
||||||
|
|
||||||
def searchTwitterGen(self, search_query, **kwargs):
|
|
||||||
"""use searchGen(), this is a fallback method to support
|
|
||||||
searchTwitterGen()"""
|
|
||||||
return self.searchGen(search_query, **kwargs)
|
|
||||||
|
|
||||||
def isListMember(self, list_id, id, username, version = 1):
|
|
||||||
""" isListMember(self, list_id, id, version)
|
|
||||||
|
|
||||||
Check if a specified user (id) is a member of the list in question (list_id).
|
|
||||||
|
|
||||||
**Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
list_id - Required. The slug of the list to check against.
|
|
||||||
id - Required. The ID of the user being checked in the list.
|
|
||||||
username - User who owns the list you're checking against (username)
|
|
||||||
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
resp, content = self.client.request("https://api.twitter.com/%d/%s/%s/members/%s.json" % (version, username, list_id, repr(id)), headers = self.headers)
|
|
||||||
return simplejson.loads(content.decode('utf-8'))
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code)
|
|
||||||
|
|
||||||
def isListSubscriber(self, username, list_id, id, version = 1):
|
|
||||||
""" isListSubscriber(self, list_id, id, version)
|
|
||||||
|
|
||||||
Check if a specified user (id) is a subscriber of the list in question (list_id).
|
|
||||||
|
|
||||||
**Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
list_id - Required. The slug of the list to check against.
|
|
||||||
id - Required. The ID of the user being checked in the list.
|
|
||||||
username - Required. The username of the owner of the list that you're seeing if someone is subscribed to.
|
|
||||||
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
resp, content = self.client.request("https://api.twitter.com/%d/%s/%s/following/%s.json" % (version, username, list_id, repr(id)), headers = self.headers)
|
|
||||||
return simplejson.loads(content.decode('utf-8'))
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code)
|
|
||||||
|
|
||||||
# The following methods are apart from the other Account methods, because they rely on a whole multipart-data posting function set.
|
|
||||||
def updateProfileBackgroundImage(self, filename, tile="true", version = 1):
|
|
||||||
""" updateProfileBackgroundImage(filename, tile="true")
|
|
||||||
|
|
||||||
Updates the authenticating user's profile background image.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down.
|
|
||||||
tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise.
|
|
||||||
** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false"
|
|
||||||
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
files = [("image", filename, open(filename, 'rb').read())]
|
|
||||||
fields = []
|
|
||||||
content_type, body = Twython.encode_multipart_formdata(fields, files)
|
|
||||||
headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
|
|
||||||
r = urllib.request.Request("https://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers)
|
|
||||||
return urllib.request.urlopen(r).read()
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code)
|
|
||||||
|
|
||||||
def updateProfileImage(self, filename, version = 1):
|
|
||||||
""" updateProfileImage(filename)
|
|
||||||
|
|
||||||
Updates the authenticating user's profile image (avatar).
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down.
|
|
||||||
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
files = [("image", filename, open(filename, 'rb').read())]
|
|
||||||
fields = []
|
|
||||||
content_type, body = Twython.encode_multipart_formdata(fields, files)
|
|
||||||
headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
|
|
||||||
r = urllib.request.Request("https://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers)
|
|
||||||
return urllib.request.urlopen(r).read()
|
|
||||||
except HTTPError as e:
|
|
||||||
raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code)
|
|
||||||
|
|
||||||
def getProfileImageUrl(self, username, size=None, version=1):
|
|
||||||
""" getProfileImageUrl(username)
|
|
||||||
|
|
||||||
Gets the URL for the user's profile image.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
username - Required. User name of the user you want the image url of.
|
|
||||||
size - Optional. Image size. Valid options include 'normal', 'mini' and 'bigger'. Defaults to 'normal' if not given.
|
|
||||||
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
|
|
||||||
"""
|
|
||||||
url = "http://api.twitter.com/%s/users/profile_image/%s.json" % (version, username)
|
|
||||||
if size:
|
|
||||||
url = self.constructApiURL(url, {'size':size})
|
|
||||||
|
|
||||||
client = httplib2.Http()
|
|
||||||
client.follow_redirects = False
|
|
||||||
resp, content = client.request(url, 'GET')
|
|
||||||
|
|
||||||
if resp.status in (301,302,303,307):
|
|
||||||
return resp['location']
|
|
||||||
elif resp.status == 200:
|
|
||||||
return simplejson.loads(content.decode('utf-8'))
|
|
||||||
|
|
||||||
raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encode_multipart_formdata(fields, files):
|
|
||||||
BOUNDARY = email.generator._make_boundary()
|
|
||||||
CRLF = '\r\n'
|
|
||||||
L = []
|
|
||||||
for (key, value) in fields:
|
|
||||||
L.append('--' + BOUNDARY)
|
|
||||||
L.append('Content-Disposition: form-data; name="%s"' % key)
|
|
||||||
L.append('')
|
|
||||||
L.append(value)
|
|
||||||
for (key, filename, value) in files:
|
|
||||||
L.append('--' + BOUNDARY)
|
|
||||||
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
|
|
||||||
L.append('Content-Type: %s' % mimetypes.guess_type(filename)[0] or 'application/octet-stream')
|
|
||||||
L.append('')
|
|
||||||
L.append(value)
|
|
||||||
L.append('--' + BOUNDARY + '--')
|
|
||||||
L.append('')
|
|
||||||
body = CRLF.join(L)
|
|
||||||
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
|
|
||||||
return content_type, body
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unicode2utf8(text):
|
|
||||||
try:
|
|
||||||
if isinstance(text, str):
|
|
||||||
text = text.encode('utf-8')
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return text
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encode(text):
|
|
||||||
if isinstance(text, str):
|
|
||||||
return Twython.unicode2utf8(text)
|
|
||||||
return str(text)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue