mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 07:46:07 -07:00
Update python-twitter to 3.4.1
This commit is contained in:
parent
8e4aba7ed4
commit
f743a817ba
8 changed files with 1520 additions and 993 deletions
|
@ -23,7 +23,7 @@ __author__ = 'The Python-Twitter Developers'
|
|||
__email__ = 'python-twitter@googlegroups.com'
|
||||
__copyright__ = 'Copyright (c) 2007-2016 The Python-Twitter Developers'
|
||||
__license__ = 'Apache License 2.0'
|
||||
__version__ = '3.0rc1'
|
||||
__version__ = '3.4.1'
|
||||
__url__ = 'https://github.com/bear/python-twitter'
|
||||
__download_url__ = 'https://pypi.python.org/pypi/python-twitter'
|
||||
__description__ = 'A Python wrapper around the Twitter API'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
import errno
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
from hashlib import md5
|
||||
|
@ -47,7 +46,7 @@ class _FileCache(object):
|
|||
path = self._GetPath(key)
|
||||
if not path.startswith(self._root_directory):
|
||||
raise _FileCacheError('%s does not appear to live under %s' %
|
||||
(path, self._root_directory ))
|
||||
(path, self._root_directory))
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
|
@ -101,61 +100,3 @@ class _FileCache(object):
|
|||
|
||||
def _GetPrefix(self, hashed_key):
|
||||
return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])
|
||||
|
||||
|
||||
class ParseTweet(object):
|
||||
# compile once on import
|
||||
regexp = {"RT": "^RT", "MT": r"^MT", "ALNUM": r"(@[a-zA-Z0-9_]+)",
|
||||
"HASHTAG": r"(#[\w\d]+)", "URL": r"([http://]?[a-zA-Z\d\/]+[\.]+[a-zA-Z\d\/\.]+)"}
|
||||
regexp = dict((key, re.compile(value)) for key, value in list(regexp.items()))
|
||||
|
||||
def __init__(self, timeline_owner, tweet):
|
||||
""" timeline_owner : twitter handle of user account. tweet - 140 chars from feed; object does all computation on construction
|
||||
properties:
|
||||
RT, MT - boolean
|
||||
URLs - list of URL
|
||||
Hashtags - list of tags
|
||||
"""
|
||||
self.Owner = timeline_owner
|
||||
self.tweet = tweet
|
||||
self.UserHandles = ParseTweet.getUserHandles(tweet)
|
||||
self.Hashtags = ParseTweet.getHashtags(tweet)
|
||||
self.URLs = ParseTweet.getURLs(tweet)
|
||||
self.RT = ParseTweet.getAttributeRT(tweet)
|
||||
self.MT = ParseTweet.getAttributeMT(tweet)
|
||||
|
||||
# additional intelligence
|
||||
if ( self.RT and len(self.UserHandles) > 0 ): # change the owner of tweet?
|
||||
self.Owner = self.UserHandles[0]
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
""" for display method """
|
||||
return "owner %s, urls: %d, hashtags %d, user_handles %d, len_tweet %d, RT = %s, MT = %s" % (
|
||||
self.Owner, len(self.URLs), len(self.Hashtags), len(self.UserHandles),
|
||||
len(self.tweet), self.RT, self.MT)
|
||||
|
||||
@staticmethod
|
||||
def getAttributeRT(tweet):
|
||||
""" see if tweet is a RT """
|
||||
return re.search(ParseTweet.regexp["RT"], tweet.strip()) is not None
|
||||
|
||||
@staticmethod
|
||||
def getAttributeMT(tweet):
|
||||
""" see if tweet is a MT """
|
||||
return re.search(ParseTweet.regexp["MT"], tweet.strip()) is not None
|
||||
|
||||
@staticmethod
|
||||
def getUserHandles(tweet):
|
||||
""" given a tweet we try and extract all user handles in order of occurrence"""
|
||||
return re.findall(ParseTweet.regexp["ALNUM"], tweet)
|
||||
|
||||
@staticmethod
|
||||
def getHashtags(tweet):
|
||||
""" return all hashtags"""
|
||||
return re.findall(ParseTweet.regexp["HASHTAG"], tweet)
|
||||
|
||||
@staticmethod
|
||||
def getURLs(tweet):
|
||||
""" URL : [http://]?[\w\.?/]+"""
|
||||
return re.findall(ParseTweet.regexp["URL"], tweet)
|
||||
|
|
2061
lib/twitter/api.py
2061
lib/twitter/api.py
File diff suppressed because it is too large
Load diff
|
@ -8,3 +8,18 @@ class TwitterError(Exception):
|
|||
def message(self):
|
||||
'''Returns the first argument used to construct this error.'''
|
||||
return self.args[0]
|
||||
|
||||
|
||||
class PythonTwitterDeprecationWarning(DeprecationWarning):
|
||||
"""Base class for python-twitter deprecation warnings"""
|
||||
pass
|
||||
|
||||
|
||||
class PythonTwitterDeprecationWarning330(PythonTwitterDeprecationWarning):
|
||||
"""Warning for features to be removed in version 3.3.0"""
|
||||
pass
|
||||
|
||||
|
||||
class PythonTwitterDeprecationWarning340(PythonTwitterDeprecationWarning):
|
||||
"""Warning for features to be removed in version 3.4.0"""
|
||||
pass
|
||||
|
|
|
@ -28,6 +28,13 @@ class TwitterModel(object):
|
|||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
if hasattr(self, 'id'):
|
||||
return hash(self.id)
|
||||
else:
|
||||
raise TypeError('unhashable type: {} (no id attribute)'
|
||||
.format(type(self)))
|
||||
|
||||
def AsJsonString(self):
|
||||
""" Returns the TwitterModel as a JSON string based on key/value
|
||||
pairs returned from the AsDict() method. """
|
||||
|
@ -78,11 +85,14 @@ class TwitterModel(object):
|
|||
|
||||
"""
|
||||
|
||||
json_data = data.copy()
|
||||
if kwargs:
|
||||
for key, val in kwargs.items():
|
||||
data[key] = val
|
||||
json_data[key] = val
|
||||
|
||||
return cls(**data)
|
||||
c = cls(**json_data)
|
||||
c._json = data
|
||||
return c
|
||||
|
||||
|
||||
class Media(TwitterModel):
|
||||
|
@ -93,11 +103,14 @@ class Media(TwitterModel):
|
|||
self.param_defaults = {
|
||||
'display_url': None,
|
||||
'expanded_url': None,
|
||||
'ext_alt_text': None,
|
||||
'id': None,
|
||||
'media_url': None,
|
||||
'media_url_https': None,
|
||||
'sizes': None,
|
||||
'type': None,
|
||||
'url': None,
|
||||
'video_info': None,
|
||||
}
|
||||
|
||||
for (param, default) in self.param_defaults.items():
|
||||
|
@ -172,8 +185,10 @@ class DirectMessage(TwitterModel):
|
|||
self.param_defaults = {
|
||||
'created_at': None,
|
||||
'id': None,
|
||||
'recipient': None,
|
||||
'recipient_id': None,
|
||||
'recipient_screen_name': None,
|
||||
'sender': None,
|
||||
'sender_id': None,
|
||||
'sender_screen_name': None,
|
||||
'text': None,
|
||||
|
@ -181,6 +196,10 @@ class DirectMessage(TwitterModel):
|
|||
|
||||
for (param, default) in self.param_defaults.items():
|
||||
setattr(self, param, kwargs.get(param, default))
|
||||
if 'sender' in kwargs:
|
||||
self.sender = User.NewFromJsonDict(kwargs.get('sender', None))
|
||||
if 'recipient' in kwargs:
|
||||
self.recipient = User.NewFromJsonDict(kwargs.get('recipient', None))
|
||||
|
||||
def __repr__(self):
|
||||
if self.text and len(self.text) > 140:
|
||||
|
@ -206,7 +225,7 @@ class Trend(TwitterModel):
|
|||
'query': None,
|
||||
'timestamp': None,
|
||||
'url': None,
|
||||
'volume': None,
|
||||
'tweet_volume': None,
|
||||
}
|
||||
|
||||
for (param, default) in self.param_defaults.items():
|
||||
|
@ -218,6 +237,10 @@ class Trend(TwitterModel):
|
|||
self.timestamp,
|
||||
self.url)
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
return self.tweet_volume
|
||||
|
||||
|
||||
class Hashtag(TwitterModel):
|
||||
|
||||
|
@ -259,7 +282,7 @@ class UserStatus(TwitterModel):
|
|||
""" A class representing the UserStatus structure. This is an abbreviated
|
||||
form of the twitter.User object. """
|
||||
|
||||
connections = {'following': False,
|
||||
_connections = {'following': False,
|
||||
'followed_by': False,
|
||||
'following_received': False,
|
||||
'following_requested': False,
|
||||
|
@ -284,10 +307,19 @@ class UserStatus(TwitterModel):
|
|||
setattr(self, param, kwargs.get(param, default))
|
||||
|
||||
if 'connections' in kwargs:
|
||||
for param in self.connections:
|
||||
for param in self._connections:
|
||||
if param in kwargs['connections']:
|
||||
setattr(self, param, True)
|
||||
|
||||
@property
|
||||
def connections(self):
|
||||
return {'following': self.following,
|
||||
'followed_by': self.followed_by,
|
||||
'following_received': self.following_received,
|
||||
'following_requested': self.following_requested,
|
||||
'blocking': self.blocking,
|
||||
'muting': self.muting}
|
||||
|
||||
def __repr__(self):
|
||||
connections = [param for param in self.connections if getattr(self, param)]
|
||||
return "UserStatus(ID={uid}, ScreenName={sn}, Connections=[{conn}])".format(
|
||||
|
@ -307,11 +339,14 @@ class User(TwitterModel):
|
|||
'default_profile': None,
|
||||
'default_profile_image': None,
|
||||
'description': None,
|
||||
'email': None,
|
||||
'favourites_count': None,
|
||||
'followers_count': None,
|
||||
'following': None,
|
||||
'friends_count': None,
|
||||
'geo_enabled': None,
|
||||
'id': None,
|
||||
'id_str': None,
|
||||
'lang': None,
|
||||
'listed_count': None,
|
||||
'location': None,
|
||||
|
@ -319,12 +354,16 @@ class User(TwitterModel):
|
|||
'notifications': None,
|
||||
'profile_background_color': None,
|
||||
'profile_background_image_url': None,
|
||||
'profile_background_image_url_https': None,
|
||||
'profile_background_tile': None,
|
||||
'profile_banner_url': None,
|
||||
'profile_image_url': None,
|
||||
'profile_image_url_https': None,
|
||||
'profile_link_color': None,
|
||||
'profile_sidebar_border_color': None,
|
||||
'profile_sidebar_fill_color': None,
|
||||
'profile_text_color': None,
|
||||
'profile_use_background_image': None,
|
||||
'protected': None,
|
||||
'screen_name': None,
|
||||
'status': None,
|
||||
|
@ -333,6 +372,8 @@ class User(TwitterModel):
|
|||
'url': None,
|
||||
'utc_offset': None,
|
||||
'verified': None,
|
||||
'withheld_in_countries': None,
|
||||
'withheld_scope': None,
|
||||
}
|
||||
|
||||
for (param, default) in self.param_defaults.items():
|
||||
|
@ -365,6 +406,7 @@ class Status(TwitterModel):
|
|||
'current_user_retweet': None,
|
||||
'favorite_count': None,
|
||||
'favorited': None,
|
||||
'full_text': None,
|
||||
'geo': None,
|
||||
'hashtags': None,
|
||||
'id': None,
|
||||
|
@ -377,6 +419,9 @@ class Status(TwitterModel):
|
|||
'media': None,
|
||||
'place': None,
|
||||
'possibly_sensitive': None,
|
||||
'quoted_status': None,
|
||||
'quoted_status_id': None,
|
||||
'quoted_status_id_str': None,
|
||||
'retweet_count': None,
|
||||
'retweeted': None,
|
||||
'retweeted_status': None,
|
||||
|
@ -395,6 +440,11 @@ class Status(TwitterModel):
|
|||
for (param, default) in self.param_defaults.items():
|
||||
setattr(self, param, kwargs.get(param, default))
|
||||
|
||||
if kwargs.get('full_text', None):
|
||||
self.tweet_mode = 'extended'
|
||||
else:
|
||||
self.tweet_mode = 'compatibility'
|
||||
|
||||
@property
|
||||
def created_at_in_seconds(self):
|
||||
""" Get the time this status message was posted, in seconds since
|
||||
|
@ -414,17 +464,21 @@ class Status(TwitterModel):
|
|||
string: A string representation of this twitter.Status instance with
|
||||
the ID of status, username and datetime.
|
||||
"""
|
||||
if self.tweet_mode == 'extended':
|
||||
text = self.full_text
|
||||
else:
|
||||
text = self.text
|
||||
if self.user:
|
||||
return "Status(ID={0}, ScreenName={1}, Created={2}, Text={3!r})".format(
|
||||
self.id,
|
||||
self.user.screen_name,
|
||||
self.created_at,
|
||||
self.text)
|
||||
text)
|
||||
else:
|
||||
return u"Status(ID={0}, Created={1}, Text={2!r})".format(
|
||||
self.id,
|
||||
self.created_at,
|
||||
self.text)
|
||||
text)
|
||||
|
||||
@classmethod
|
||||
def NewFromJsonDict(cls, data, **kwargs):
|
||||
|
@ -439,17 +493,25 @@ class Status(TwitterModel):
|
|||
current_user_retweet = None
|
||||
hashtags = None
|
||||
media = None
|
||||
quoted_status = None
|
||||
retweeted_status = None
|
||||
urls = None
|
||||
user = None
|
||||
user_mentions = None
|
||||
|
||||
# for loading extended tweets from the streaming API.
|
||||
if 'extended_tweet' in data:
|
||||
for k, v in data['extended_tweet'].items():
|
||||
data[k] = v
|
||||
|
||||
if 'user' in data:
|
||||
user = User.NewFromJsonDict(data['user'])
|
||||
if 'retweeted_status' in data:
|
||||
retweeted_status = Status.NewFromJsonDict(data['retweeted_status'])
|
||||
if 'current_user_retweet' in data:
|
||||
current_user_retweet = data['current_user_retweet']['id']
|
||||
if 'quoted_status' in data:
|
||||
quoted_status = Status.NewFromJsonDict(data.get('quoted_status'))
|
||||
|
||||
if 'entities' in data:
|
||||
if 'urls' in data['entities']:
|
||||
|
@ -470,6 +532,7 @@ class Status(TwitterModel):
|
|||
current_user_retweet=current_user_retweet,
|
||||
hashtags=hashtags,
|
||||
media=media,
|
||||
quoted_status=quoted_status,
|
||||
retweeted_status=retweeted_status,
|
||||
urls=urls,
|
||||
user=user,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import re
|
||||
|
||||
|
||||
class Emoticons:
|
||||
POSITIVE = ["*O", "*-*", "*O*", "*o*", "* *",
|
||||
":P", ":D", ":d", ":p",
|
||||
|
@ -27,6 +28,7 @@ class Emoticons:
|
|||
"[:", ";]"
|
||||
]
|
||||
|
||||
|
||||
class ParseTweet(object):
|
||||
# compile once on import
|
||||
regexp = {"RT": "^RT", "MT": r"^MT", "ALNUM": r"(@[a-zA-Z0-9_]+)",
|
||||
|
@ -51,7 +53,7 @@ class ParseTweet(object):
|
|||
self.Emoticon = ParseTweet.getAttributeEmoticon(tweet)
|
||||
|
||||
# additional intelligence
|
||||
if ( self.RT and len(self.UserHandles) > 0 ): # change the owner of tweet?
|
||||
if (self.RT and len(self.UserHandles) > 0): # change the owner of tweet?
|
||||
self.Owner = self.UserHandles[0]
|
||||
return
|
||||
|
||||
|
@ -66,10 +68,10 @@ class ParseTweet(object):
|
|||
emoji = list()
|
||||
for tok in re.split(ParseTweet.regexp["SPACES"], tweet.strip()):
|
||||
if tok in Emoticons.POSITIVE:
|
||||
emoji.append( tok )
|
||||
emoji.append(tok)
|
||||
continue
|
||||
if tok in Emoticons.NEGATIVE:
|
||||
emoji.append( tok )
|
||||
emoji.append(tok)
|
||||
return emoji
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -97,6 +97,7 @@ class RateLimit(object):
|
|||
and a dictionary of limit, remaining, and reset will be returned.
|
||||
|
||||
"""
|
||||
self.__dict__['resources'] = {}
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
@staticmethod
|
||||
|
@ -117,10 +118,12 @@ class RateLimit(object):
|
|||
for non_std_endpoint in NON_STANDARD_ENDPOINTS:
|
||||
if re.match(non_std_endpoint.regex, resource):
|
||||
return non_std_endpoint.resource
|
||||
else:
|
||||
return resource
|
||||
|
||||
def set_unknown_limit(self, url, limit, remaining, reset):
|
||||
return self.set_limit(url, limit, remaining, reset)
|
||||
|
||||
def set_limit(self, url, limit, remaining, reset):
|
||||
""" If a resource family is unknown, add it to the object's
|
||||
dictionary. This is to deal with new endpoints being added to
|
||||
the API, but not necessarily to the information returned by
|
||||
|
@ -146,13 +149,18 @@ class RateLimit(object):
|
|||
"""
|
||||
endpoint = self.url_to_resource(url)
|
||||
resource_family = endpoint.split('/')[1]
|
||||
self.__dict__['resources'].update(
|
||||
{resource_family: {
|
||||
endpoint: {
|
||||
"limit": limit,
|
||||
"remaining": remaining,
|
||||
"reset": reset
|
||||
}}})
|
||||
new_endpoint = {endpoint: {
|
||||
"limit": enf_type('limit', int, limit),
|
||||
"remaining": enf_type('remaining', int, remaining),
|
||||
"reset": enf_type('reset', int, reset)
|
||||
}}
|
||||
|
||||
if not self.resources.get(resource_family, None):
|
||||
self.resources[resource_family] = {}
|
||||
|
||||
self.__dict__['resources'][resource_family].update(new_endpoint)
|
||||
|
||||
return self.get_limit(url)
|
||||
|
||||
def get_limit(self, url):
|
||||
""" Gets a EndpointRateLimit object for the given url.
|
||||
|
@ -181,35 +189,3 @@ class RateLimit(object):
|
|||
return EndpointRateLimit(family_rates['limit'],
|
||||
family_rates['remaining'],
|
||||
family_rates['reset'])
|
||||
|
||||
def set_limit(self, url, limit, remaining, reset):
|
||||
""" Set an endpoint's rate limits. The data used for each of the
|
||||
args should come from Twitter's ``x-rate-limit`` headers.
|
||||
|
||||
Args:
|
||||
url (str):
|
||||
URL of the endpoint being fetched.
|
||||
limit (int):
|
||||
Max number of times a user or app can hit the endpoint
|
||||
before being rate limited.
|
||||
remaining (int):
|
||||
Number of times a user or app can access the endpoint
|
||||
before being rate limited.
|
||||
reset (int):
|
||||
Epoch time at which the rate limit window will reset.
|
||||
"""
|
||||
endpoint = self.url_to_resource(url)
|
||||
resource_family = endpoint.split('/')[1]
|
||||
|
||||
try:
|
||||
family_rates = self.resources.get(resource_family).get(endpoint)
|
||||
except AttributeError:
|
||||
self.set_unknown_limit(url, limit, remaining, reset)
|
||||
family_rates = self.resources.get(resource_family).get(endpoint)
|
||||
family_rates['limit'] = enf_type('limit', int, limit)
|
||||
family_rates['remaining'] = enf_type('remaining', int, remaining)
|
||||
family_rates['reset'] = enf_type('reset', int, reset)
|
||||
|
||||
return EndpointRateLimit(family_rates['limit'],
|
||||
family_rates['remaining'],
|
||||
family_rates['reset'])
|
||||
|
|
|
@ -1,13 +1,33 @@
|
|||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from tempfile import NamedTemporaryFile
|
||||
from unicodedata import normalize
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
import requests
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from twitter import TwitterError
|
||||
import twitter
|
||||
|
||||
if sys.version_info < (3,):
|
||||
range = xrange
|
||||
|
||||
if sys.version_info > (3,):
|
||||
unicode = str
|
||||
|
||||
CHAR_RANGES = [
|
||||
range(0, 4351),
|
||||
range(8192, 8205),
|
||||
range(8208, 8223),
|
||||
range(8242, 8247)]
|
||||
|
||||
TLDS = [
|
||||
"ac", "ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar",
|
||||
|
@ -138,7 +158,14 @@ TLDS = [
|
|||
"淡马锡", "游戏", "点看", "移动", "组织机构", "网址", "网店", "网络", "谷歌", "集团",
|
||||
"飞利浦", "餐厅", "닷넷", "닷컴", "삼성", "onion"]
|
||||
|
||||
URL_REGEXP = re.compile(r'(?i)((?:https?://|www\\.)*(?:[\w+-_]+[.])(?:' + r'\b|'.join(TLDS) + r'\b|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))+(?:[:\w+\/]?[a-z0-9!\*\'\(\);:&=\+\$/%#\[\]\-_\.,~?])*)', re.UNICODE)
|
||||
URL_REGEXP = re.compile((
|
||||
r'('
|
||||
r'^(?!(https?://|www\.)?\.|ftps?://|([0-9]+\.){{1,3}}\d+)' # exclude urls that start with "."
|
||||
r'(?:https?://|www\.)*^(?!.*@)(?:[\w+-_]+[.])' # beginning of url
|
||||
r'(?:{0}\b' # all tlds
|
||||
r'(?:[:0-9]))' # port numbers & close off TLDs
|
||||
r'(?:[\w+\/]?[a-z0-9!\*\'\(\);:&=\+\$/%#\[\]\-_\.,~?])*' # path/query params
|
||||
r')').format(r'\b|'.join(TLDS)), re.U | re.I | re.X)
|
||||
|
||||
|
||||
def calc_expected_status_length(status, short_url_length=23):
|
||||
|
@ -153,12 +180,19 @@ def calc_expected_status_length(status, short_url_length=23):
|
|||
Expected length of the status message as an integer.
|
||||
|
||||
"""
|
||||
replaced_chars = 0
|
||||
status_length = len(status)
|
||||
match = re.findall(URL_REGEXP, status)
|
||||
if len(match) >= 1:
|
||||
replaced_chars = len(''.join(match))
|
||||
status_length = status_length - replaced_chars + (short_url_length * len(match))
|
||||
status_length = 0
|
||||
if isinstance(status, bytes):
|
||||
status = unicode(status)
|
||||
for word in re.split(r'\s', status):
|
||||
if is_url(word):
|
||||
status_length += short_url_length
|
||||
else:
|
||||
for character in word:
|
||||
if any([ord(normalize("NFC", character)) in char_range for char_range in CHAR_RANGES]):
|
||||
status_length += 1
|
||||
else:
|
||||
status_length += 2
|
||||
status_length += len(re.findall(r'\s', status))
|
||||
return status_length
|
||||
|
||||
|
||||
|
@ -171,16 +205,14 @@ def is_url(text):
|
|||
Returns:
|
||||
Boolean of whether the text should be treated as a URL or not.
|
||||
"""
|
||||
if re.findall(URL_REGEXP, text):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return bool(re.findall(URL_REGEXP, text))
|
||||
|
||||
|
||||
def http_to_file(http):
|
||||
data_file = NamedTemporaryFile()
|
||||
req = requests.get(http, stream=True)
|
||||
data_file.write(req.raw.data)
|
||||
for chunk in req.iter_content(chunk_size=1024 * 1024):
|
||||
data_file.write(chunk)
|
||||
return data_file
|
||||
|
||||
|
||||
|
@ -200,7 +232,8 @@ def parse_media_file(passed_media):
|
|||
'image/gif',
|
||||
'image/bmp',
|
||||
'image/webp']
|
||||
video_formats = ['video/mp4']
|
||||
video_formats = ['video/mp4',
|
||||
'video/quicktime']
|
||||
|
||||
# If passed_media is a string, check if it points to a URL, otherwise,
|
||||
# it should point to local file. Create a reference to a file obj for
|
||||
|
@ -208,7 +241,7 @@ def parse_media_file(passed_media):
|
|||
if not hasattr(passed_media, 'read'):
|
||||
if passed_media.startswith('http'):
|
||||
data_file = http_to_file(passed_media)
|
||||
filename = os.path.basename(passed_media)
|
||||
filename = os.path.basename(urlparse(passed_media).path)
|
||||
else:
|
||||
data_file = open(os.path.realpath(passed_media), 'rb')
|
||||
filename = os.path.basename(passed_media)
|
||||
|
@ -216,8 +249,8 @@ def parse_media_file(passed_media):
|
|||
# Otherwise, if a file object was passed in the first place,
|
||||
# create the standard reference to media_file (i.e., rename it to fp).
|
||||
else:
|
||||
if passed_media.mode != 'rb':
|
||||
raise TwitterError({'message': 'File mode must be "rb".'})
|
||||
if passed_media.mode not in ['rb', 'rb+', 'w+b']:
|
||||
raise TwitterError('File mode must be "rb" or "rb+"')
|
||||
filename = os.path.basename(passed_media.name)
|
||||
data_file = passed_media
|
||||
|
||||
|
@ -226,10 +259,11 @@ def parse_media_file(passed_media):
|
|||
|
||||
try:
|
||||
data_file.seek(0)
|
||||
except:
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
media_type = mimetypes.guess_type(os.path.basename(filename))[0]
|
||||
if media_type is not None:
|
||||
if media_type in img_formats and file_size > 5 * 1048576:
|
||||
raise TwitterError({'message': 'Images must be less than 5MB.'})
|
||||
elif media_type in video_formats and file_size > 15 * 1048576:
|
||||
|
@ -263,3 +297,18 @@ def enf_type(field, _type, val):
|
|||
raise TwitterError({
|
||||
'message': '"{0}" must be type {1}'.format(field, _type.__name__)
|
||||
})
|
||||
|
||||
|
||||
def parse_arg_list(args, attr):
|
||||
out = []
|
||||
if isinstance(args, (str, unicode)):
|
||||
out.append(args)
|
||||
elif isinstance(args, twitter.User):
|
||||
out.append(getattr(args, attr))
|
||||
elif isinstance(args, (list, tuple)):
|
||||
for item in args:
|
||||
if isinstance(item, (str, unicode)):
|
||||
out.append(item)
|
||||
elif isinstance(item, twitter.User):
|
||||
out.append(getattr(item, attr))
|
||||
return ",".join([str(item) for item in out])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue