def upload_images(self, urls): """Uploads one or more images from web URLs. https://dev.twitter.com/rest/reference/post/media/upload Args: urls: sequence of string URLs of images Returns: list of string media ids """ ids = [] for url in urls: image_resp = util.urlopen(url) bad_type = self._check_mime_type(url, image_resp, IMAGE_MIME_TYPES, 'JPG, PNG, GIF, and WEBP images') if bad_type: return bad_type headers = twitter_auth.auth_header( API_UPLOAD_MEDIA, self.access_token_key, self.access_token_secret, 'POST') resp = util.requests_post(API_UPLOAD_MEDIA, files={'media': image_resp}, headers=headers) resp.raise_for_status() logging.info('Got: %s', resp.text) try: ids.append(json.loads(resp.text)['media_id_string']) except ValueError, KeyError: logging.exception("Couldn't parse response: %s", resp.text) raise
def upload_images(self, urls): """Uploads one or more images from web URLs. https://dev.twitter.com/rest/reference/post/media/upload Args: urls: sequence of string URLs of images Returns: list of string media ids """ ids = [] for url in urls: image_resp = util.urlopen(url) bad_type = self._check_mime_type(url, image_resp, IMAGE_MIME_TYPES, 'JPG, PNG, GIF, and WEBP images') if bad_type: return bad_type headers = twitter_auth.auth_header( API_UPLOAD_MEDIA, self.access_token_key, self.access_token_secret, 'POST') resp = util.requests_post(API_UPLOAD_MEDIA, files={'media': image_resp}, headers=headers) resp.raise_for_status() logging.info('Got: %s', resp.text) ids.append(source.load_json(resp.text, API_UPLOAD_MEDIA)['media_id_string']) return ids
def upload_images(self, urls): """Uploads one or more images from web URLs. https://dev.twitter.com/rest/reference/post/media/upload Args: urls: sequence of string URLs of images Returns: list of string media ids """ ids = [] for url in urls: headers = twitter_auth.auth_header( API_UPLOAD_MEDIA, self.access_token_key, self.access_token_secret, 'POST') resp = util.requests_post(API_UPLOAD_MEDIA, files={'media': util.urlopen(url)}, headers=headers) resp.raise_for_status() logging.info('Got: %s', resp.text) try: ids.append(json.loads(resp.text)['media_id_string']) except ValueError, KeyError: logging.exception("Couldn't parse response: %s", resp.text) raise
def _create(self, obj, preview=None, include_link=False): """Creates or previews creating a tweet, reply tweet, retweet, or favorite. https://dev.twitter.com/docs/api/1.1/post/statuses/update https://dev.twitter.com/docs/api/1.1/post/statuses/retweet/:id https://dev.twitter.com/docs/api/1.1/post/favorites/create Args: obj: ActivityStreams object preview: boolean include_link: boolean Returns: a CreationResult If preview is True, the content will be a unicode string HTML snippet. If False, it will be a dict with 'id' and 'url' keys for the newly created Twitter object. """ assert preview in (False, True) type = obj.get('objectType') verb = obj.get('verb') base_obj = self.base_object(obj) base_id = base_obj.get('id') base_url = base_obj.get('url') is_reply = type == 'comment' or 'inReplyTo' in obj has_picture = obj.get('image') and (type in ('note', 'article') or is_reply) content = self._content_for_create(obj) if not content: if type == 'activity': content = verb elif has_picture: content = '' else: return source.creation_result( abort=False, # keep looking for things to publish, error_plain='No content text found.', error_html='No content text found.') if is_reply and base_url: # extract username from in-reply-to URL so we can @-mention it, if it's # not already @-mentioned, since Twitter requires that to make our new # tweet a reply. # https://dev.twitter.com/docs/api/1.1/post/statuses/update#api-param-in_reply_to_status_id # TODO: this doesn't handle an in-reply-to username that's a prefix of # another username already mentioned, e.g. in reply to @foo when content # includes @foobar. parsed = urlparse.urlparse(base_url) parts = parsed.path.split('/') if len(parts) < 2 or not parts[1]: raise ValueError( 'Could not determine author of in-reply-to URL %s' % base_url) mention = '@' + parts[1] if mention.lower() not in content.lower(): content = mention + ' ' + content # the embed URL in the preview can't start with mobile. or www., so just # hard-code it to twitter.com. index #1 is netloc. parsed = list(parsed) parsed[1] = self.DOMAIN base_url = urlparse.urlunparse(parsed) # need a base_url with the tweet id for the embed HTML below. do this # *after* checking the real base_url for in-reply-to author username. if base_id and not base_url: base_url = 'https://twitter.com/-/statuses/' + base_id if is_reply and not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to reply to.', error_html= 'Could not find a tweet to <a href="http://indiewebcamp.com/reply">reply to</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/comment">in-reply-to</a> ' 'link a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.' ) # truncate and ellipsize content if it's over the character # count. URLs will be t.co-wrapped, so include that when counting. include_url = obj.get('url') if include_link else None content = self._truncate(content, include_url, has_picture) # linkify defaults to Twitter's link shortening behavior preview_content = util.linkify(content, pretty=True, skip_bare_cc_tlds=True) if has_picture: image_url = obj.get('image').get('url') if preview: if is_reply: desc = ( '<span class="verb">@-reply</span> to <a href="%s">this tweet' '</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: desc = '<span class="verb">tweet</span>:' if preview_content: preview_content += '<br /><br />' return source.creation_result(content='%s<img src="%s" />' % (preview_content, image_url), description=desc) else: content = unicode(content).encode('utf-8') data = {'status': content} if is_reply: data['in_reply_to_status_id'] = base_id files = {'media[]': urllib2.urlopen(image_url)} headers = twitter_auth.auth_header(API_POST_MEDIA_URL, self.access_token_key, self.access_token_secret, 'POST') resp = requests.post(API_POST_MEDIA_URL, data=data, files=files, headers=headers, timeout=HTTP_TIMEOUT) resp.raise_for_status() resp = json.loads(resp.text) resp['type'] = 'comment' if is_reply else 'post' elif is_reply: if preview: return source.creation_result( content=preview_content, description= '<span class="verb">@-reply</span> to <a href="%s">this tweet' '</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: content = unicode(content).encode('utf-8') data = urllib.urlencode({ 'status': content, 'in_reply_to_status_id': base_id }) resp = self.urlopen(API_POST_TWEET_URL, data=data) resp['type'] = 'comment' elif type == 'activity' and verb == 'like': if not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to like.', error_html= 'Could not find a tweet to <a href="http://indiewebcamp.com/favorite">favorite</a>. ' 'Check that your post has a like-of link to a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.' ) if preview: return source.creation_result( description= '<span class="verb">favorite</span> <a href="%s">' 'this tweet</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: data = urllib.urlencode({'id': base_id}) self.urlopen(API_POST_FAVORITE_URL, data=data) resp = {'type': 'like'} elif type == 'activity' and verb == 'share': if not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to retweet.', error_html= 'Could not find a tweet to <a href="http://indiewebcamp.com/repost">retweet</a>. ' 'Check that your post has a repost-of link to a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.' ) if preview: return source.creation_result( description='<span class="verb">retweet</span> <a href="%s">' 'this tweet</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: data = urllib.urlencode({'id': base_id}) resp = self.urlopen(API_POST_RETWEET_URL % base_id, data=data) resp['type'] = 'repost' elif type in ('note', 'article'): if preview: return source.creation_result( content=preview_content, description='<span class="verb">tweet</span>:') else: content = unicode(content).encode('utf-8') data = urllib.urlencode({'status': content}) resp = self.urlopen(API_POST_TWEET_URL, data=data) resp['type'] = 'post' elif (verb and verb.startswith('rsvp-')) or verb == 'invite': return source.creation_result( abort=True, error_plain='Cannot publish RSVPs to Twitter.', error_html= 'This looks like an <a href="http://indiewebcamp.com/rsvp">RSVP</a>. ' 'Publishing events or RSVPs to Twitter is not supported.') else: return source.creation_result( abort=False, error_plain='Cannot publish type=%s, verb=%s to Twitter' % (type, verb), error_html='Cannot publish type=%s, verb=%s to Twitter' % (type, verb)) id_str = resp.get('id_str') if id_str: resp.update({'id': id_str, 'url': self.tweet_url(resp)}) elif 'url' not in resp: resp['url'] = base_url return source.creation_result(resp)
def _create(self, obj, preview=None, include_link=False, ignore_formatting=False): """Creates or previews creating a tweet, reply tweet, retweet, or favorite. https://dev.twitter.com/docs/api/1.1/post/statuses/update https://dev.twitter.com/docs/api/1.1/post/statuses/retweet/:id https://dev.twitter.com/docs/api/1.1/post/favorites/create Args: obj: ActivityStreams object preview: boolean include_link: boolean Returns: a CreationResult If preview is True, the content will be a unicode string HTML snippet. If False, it will be a dict with 'id' and 'url' keys for the newly created Twitter object. """ assert preview in (False, True) type = obj.get('objectType') verb = obj.get('verb') base_obj = self.base_object(obj) base_id = base_obj.get('id') base_url = base_obj.get('url') is_reply = type == 'comment' or 'inReplyTo' in obj has_picture = obj.get('image') and (type in ('note', 'article') or is_reply) content = self._content_for_create(obj, ignore_formatting=ignore_formatting) if not content: if type == 'activity': content = verb elif has_picture: content = '' else: return source.creation_result( abort=False, # keep looking for things to publish, error_plain='No content text found.', error_html='No content text found.') if is_reply and base_url: # extract username from in-reply-to URL so we can @-mention it, if it's # not already @-mentioned, since Twitter requires that to make our new # tweet a reply. # https://dev.twitter.com/docs/api/1.1/post/statuses/update#api-param-in_reply_to_status_id # TODO: this doesn't handle an in-reply-to username that's a prefix of # another username already mentioned, e.g. in reply to @foo when content # includes @foobar. parsed = urlparse.urlparse(base_url) parts = parsed.path.split('/') if len(parts) < 2 or not parts[1]: raise ValueError('Could not determine author of in-reply-to URL %s' % base_url) mention = '@' + parts[1] if mention.lower() not in content.lower(): content = mention + ' ' + content # the embed URL in the preview can't start with mobile. or www., so just # hard-code it to twitter.com. index #1 is netloc. parsed = list(parsed) parsed[1] = self.DOMAIN base_url = urlparse.urlunparse(parsed) # need a base_url with the tweet id for the embed HTML below. do this # *after* checking the real base_url for in-reply-to author username. if base_id and not base_url: base_url = 'https://twitter.com/-/statuses/' + base_id if is_reply and not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to reply to.', error_html='Could not find a tweet to <a href="http://indiewebcamp.com/reply">reply to</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/comment">in-reply-to</a> ' 'link a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.') # truncate and ellipsize content if it's over the character # count. URLs will be t.co-wrapped, so include that when counting. include_url = obj.get('url') if include_link else None content = self._truncate(content, include_url, has_picture) # linkify defaults to Twitter's link shortening behavior preview_content = util.linkify(content, pretty=True, skip_bare_cc_tlds=True) if has_picture: image_url = obj.get('image').get('url') if preview: if is_reply: desc = ('<span class="verb">@-reply</span> to <a href="%s">this tweet' '</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: desc = '<span class="verb">tweet</span>:' if preview_content: preview_content += '<br /><br />' return source.creation_result( content='%s<img src="%s" />' % (preview_content, image_url), description=desc) else: content = unicode(content).encode('utf-8') data = {'status': content} if is_reply: data['in_reply_to_status_id'] = base_id files = {'media[]': urllib2.urlopen(image_url)} url = API_BASE + API_POST_MEDIA_URL headers = twitter_auth.auth_header(url, self.access_token_key, self.access_token_secret, 'POST') resp = requests.post(url, data=data, files=files, headers=headers, timeout=HTTP_TIMEOUT) resp.raise_for_status() resp = json.loads(resp.text) resp['type'] = 'comment' if is_reply else 'post' elif is_reply: if preview: return source.creation_result( content=preview_content, description='<span class="verb">@-reply</span> to <a href="%s">this tweet' '</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: content = unicode(content).encode('utf-8') data = urllib.urlencode({'status': content, 'in_reply_to_status_id': base_id}) resp = self.urlopen(API_POST_TWEET_URL, data=data) resp['type'] = 'comment' elif type == 'activity' and verb == 'like': if not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to like.', error_html='Could not find a tweet to <a href="http://indiewebcamp.com/favorite">favorite</a>. ' 'Check that your post has a like-of link to a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.') if preview: return source.creation_result( description='<span class="verb">favorite</span> <a href="%s">' 'this tweet</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: data = urllib.urlencode({'id': base_id}) self.urlopen(API_POST_FAVORITE_URL, data=data) resp = {'type': 'like'} elif type == 'activity' and verb == 'share': if not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to retweet.', error_html='Could not find a tweet to <a href="http://indiewebcamp.com/repost">retweet</a>. ' 'Check that your post has a repost-of link to a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.') if preview: return source.creation_result( description='<span class="verb">retweet</span> <a href="%s">' 'this tweet</a>:\n%s' % (base_url, self.embed_post(base_obj))) else: data = urllib.urlencode({'id': base_id}) resp = self.urlopen(API_POST_RETWEET_URL % base_id, data=data) resp['type'] = 'repost' elif type in ('note', 'article'): if preview: return source.creation_result(content=preview_content, description='<span class="verb">tweet</span>:') else: content = unicode(content).encode('utf-8') data = urllib.urlencode({'status': content}) resp = self.urlopen(API_POST_TWEET_URL, data=data) resp['type'] = 'post' elif (verb and verb.startswith('rsvp-')) or verb == 'invite': return source.creation_result( abort=True, error_plain='Cannot publish RSVPs to Twitter.', error_html='This looks like an <a href="http://indiewebcamp.com/rsvp">RSVP</a>. ' 'Publishing events or RSVPs to Twitter is not supported.') else: return source.creation_result( abort=False, error_plain='Cannot publish type=%s, verb=%s to Twitter' % (type, verb), error_html='Cannot publish type=%s, verb=%s to Twitter' % (type, verb)) id_str = resp.get('id_str') if id_str: resp.update({'id': id_str, 'url': self.tweet_url(resp)}) elif 'url' not in resp: resp['url'] = base_url return source.creation_result(resp)
def upload_video(self, url): """Uploads a video from web URLs using the chunked upload process. Chunked upload consists of multiple API calls: * command=INIT, which allocates the media id * command=APPEND for each 5MB block, up to 15MB total * command=FINALIZE https://dev.twitter.com/rest/reference/post/media/upload-chunked https://dev.twitter.com/rest/public/uploading-media#chunkedupload Args: url: string URL of images Returns: string media id or CreationResult on error """ video_resp = util.urlopen(url) # check format and size type = video_resp.headers.get('Content-Type') if not type: type, _ = mimetypes.guess_type(url) if type and type not in VIDEO_MIME_TYPES: msg = 'Twitter only supports MP4 videos; yours looks like a %s.' % type return source.creation_result(abort=True, error_plain=msg, error_html=msg) length = video_resp.headers.get('Content-Length') if not util.is_int(length): msg = "Couldn't determine your video's size." return source.creation_result(abort=True, error_plain=msg, error_html=msg) length = int(length) if int(length) > MAX_VIDEO_SIZE: msg = "Your %sMB video is larger than Twitter's %dMB limit." % ( length // MB, MAX_VIDEO_SIZE // MB) return source.creation_result(abort=True, error_plain=msg, error_html=msg) # INIT media_id = self.urlopen(API_UPLOAD_MEDIA, data=urllib.urlencode({ 'command': 'INIT', 'media_type': 'video/mp4', 'total_bytes': length, }))['media_id_string'] # APPEND headers = twitter_auth.auth_header( API_UPLOAD_MEDIA, self.access_token_key, self.access_token_secret, 'POST') i = 0 while True: chunk = util.FileLimiter(video_resp, UPLOAD_CHUNK_SIZE) data = { 'command': 'APPEND', 'media_id': media_id, 'segment_index': i, } resp = util.requests_post(API_UPLOAD_MEDIA, data=data, files={'media': chunk}, headers=headers) resp.raise_for_status() if chunk.ateof: break i += 1 # FINALIZE self.urlopen(API_UPLOAD_MEDIA, data=urllib.urlencode({ 'command': 'FINALIZE', 'media_id': media_id, })) return media_id
def upload_video(self, url): """Uploads a video from web URLs using the chunked upload process. Chunked upload consists of multiple API calls: * command=INIT, which allocates the media id * command=APPEND for each 5MB block, up to 15MB total * command=FINALIZE https://dev.twitter.com/rest/reference/post/media/upload-chunked https://dev.twitter.com/rest/public/uploading-media#chunkedupload Args: url: string URL of images Returns: string media id or CreationResult on error """ video_resp = util.urlopen(url) bad_type = self._check_mime_type(url, video_resp, VIDEO_MIME_TYPES, 'MP4 videos') if bad_type: return bad_type length = video_resp.headers.get('Content-Length') if not util.is_int(length): msg = "Couldn't determine your video's size." return source.creation_result(abort=True, error_plain=msg, error_html=msg) length = int(length) if int(length) > MAX_VIDEO_SIZE: msg = "Your %sMB video is larger than Twitter's %dMB limit." % ( length // MB, MAX_VIDEO_SIZE // MB) return source.creation_result(abort=True, error_plain=msg, error_html=msg) # INIT media_id = self.urlopen(API_UPLOAD_MEDIA, data=urllib.urlencode({ 'command': 'INIT', 'media_type': 'video/mp4', 'total_bytes': length, }))['media_id_string'] # APPEND headers = twitter_auth.auth_header( API_UPLOAD_MEDIA, self.access_token_key, self.access_token_secret, 'POST') i = 0 while True: chunk = util.FileLimiter(video_resp, UPLOAD_CHUNK_SIZE) data = { 'command': 'APPEND', 'media_id': media_id, 'segment_index': i, } resp = util.requests_post(API_UPLOAD_MEDIA, data=data, files={'media': chunk}, headers=headers) resp.raise_for_status() if chunk.ateof: break i += 1 # FINALIZE self.urlopen(API_UPLOAD_MEDIA, data=urllib.urlencode({ 'command': 'FINALIZE', 'media_id': media_id, })) return media_id
def _create(self, obj, preview=None, include_link=False): """Creates or previews creating a tweet, reply tweet, retweet, or favorite. https://dev.twitter.com/docs/api/1.1/post/statuses/update https://dev.twitter.com/docs/api/1.1/post/statuses/retweet/:id https://dev.twitter.com/docs/api/1.1/post/favorites/create Args: obj: ActivityStreams object preview: boolean include_link: boolean Returns: a CreationResult If preview is True, the content will be a unicode string HTML snippet. If False, it will be a dict with 'id' and 'url' keys for the newly created Twitter object. """ # TODO: validation, error handling assert preview in (False, True) type = obj.get('objectType') verb = obj.get('verb') base_id, base_url = self.base_object(obj) content = self._content_for_create(obj) if not content: if type == 'activity': content = verb else: return source.creation_result( abort=False, # keep looking for things to publish, error_plain='No content text found.', error_html='No content text found.') is_reply = type == 'comment' or 'inReplyTo' in obj if is_reply and base_url: # extract username from in-reply-to URL so we can @-mention it, if it's # not already @-mentioned, since Twitter requires that to make our new # tweet a reply. # https://dev.twitter.com/docs/api/1.1/post/statuses/update#api-param-in_reply_to_status_id # TODO: this doesn't handle an in-reply-to username that's a prefix of # another username already mentioned, e.g. in reply to @foo when content # includes @foobar. parsed = urlparse.urlparse(base_url) parts = parsed.path.split('/') if len(parts) < 2 or not parts[1]: raise ValueError('Could not determine author of in-reply-to URL %s' % base_url) mention = '@' + parts[1] if mention not in content: content = mention + ' ' + content # the embed URL in the preview can't start with mobile. or www., so just # hard-code it to twitter.com. index #1 is netloc. parsed = list(parsed) parsed[1] = self.DOMAIN base_url = urlparse.urlunparse(parsed) # need a base_url with the tweet id for the embed HTML below. do this # *after* checking the real base_url for in-reply-to author username. if base_id and not base_url: base_url = 'https://twitter.com/-/statuses/' + base_id # truncate and ellipsize content if it's over the character count. URLs will # be t.co-wrapped, so include that when counting. links = set(util.extract_links(content)) max = MAX_TWEET_LENGTH include_url = obj.get('url') if include_link else None if include_url: max -= TCO_LENGTH + 3 length = 0 tokens = content.split() for i, token in enumerate(tokens): # extract_links() strips trailing slashes from URLs, so do the same here # so we can compare. as_url = token[:-1] if token.endswith('/') else token length += (TCO_LENGTH if as_url in links else len(token)) if i > 0: length += 1 # space between tokens if length > max: break else: i = len(tokens) # normalize whitespace # TODO: user opt in to preserve original whitespace (newlines, etc) content = ' '.join(tokens[:i]) if i < len(tokens): content += u'…' if include_url: content += ' (%s)' % include_url # linkify defaults to Twitter's link shortening behavior preview_content = util.linkify(content, pretty=True) if is_reply: if not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to reply to.', error_html='Could not find a tweet to <a href="http://indiewebcamp.com/reply">reply to</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/comment">in-reply-to</a> ' 'link a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.') if preview: return source.creation_result( 'will <span class="verb">@-reply</span>:<br /><br />\n<em>%s</em>\n' '<br /><br />...to <a href="%s">this tweet</a>:\n%s' % (preview_content, base_url, EMBED_TWEET % base_url)) else: content = unicode(content).encode('utf-8') data = urllib.urlencode({'status': content, 'in_reply_to_status_id': base_id}) resp = json.loads(self.urlopen(API_POST_TWEET_URL, data=data).read()) resp['type'] = 'comment' elif type == 'activity' and verb == 'like': if not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to like.', error_html='Could not find a tweet to <a href="http://indiewebcamp.com/favorite">favorite</a>. ' 'Check that your post has a like-of link to a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.') if preview: return source.creation_result( 'will <span class="verb">favorite</span> <a href="%s">this tweet</a>:\n%s' % (base_url, EMBED_TWEET % base_url)) else: data = urllib.urlencode({'id': base_id}) self.urlopen(API_POST_FAVORITE_URL, data=data).read() resp = {'type': 'like'} elif type == 'activity' and verb == 'share': if not base_url: return source.creation_result( abort=True, error_plain='Could not find a tweet to retweet.', error_html='Could not find a tweet to <a href="http://indiewebcamp.com/repost">retweet</a>. ' 'Check that your post has a repost-of link to a Twitter URL or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Twitter.') if preview: return source.creation_result( 'will <span class="verb">retweet</span> <a href="%s">this tweet</a>:\n%s' % (base_url, EMBED_TWEET % base_url)) else: data = urllib.urlencode({'id': base_id}) resp = json.loads(self.urlopen(API_POST_RETWEET_URL % base_id, data=data).read()) resp['type'] = 'repost' elif type in ('note', 'article') and obj.get('image'): image_url = obj.get('image').get('url') if preview: return source.creation_result( 'will <span class="verb">tweet</span> with photo:<br /><br />' '<em>%s</em><br /><img src="%s"/><br />' % (preview_content, image_url)) else: content = unicode(content).encode('utf-8') data = {'status': content} files = {'media[]': urllib2.urlopen(image_url)} headers = twitter_auth.auth_header(API_POST_MEDIA_URL, self.access_token_key, self.access_token_secret, 'POST') resp = json.loads(requests.post(API_POST_MEDIA_URL, data=data, files=files, headers=headers, timeout=HTTP_TIMEOUT).text) resp['type'] = 'post' elif type in ('note', 'article'): if preview: return source.creation_result( 'will <span class="verb">tweet</span>:<br /><br />' '<em>%s</em><br />' % preview_content) else: content = unicode(content).encode('utf-8') data = urllib.urlencode({'status': content}) resp = json.loads(self.urlopen(API_POST_TWEET_URL, data=data).read()) resp['type'] = 'post' elif (verb and verb.startswith('rsvp-')) or verb == 'invite': return source.creation_result( abort=True, error_plain='Cannot publish RSVPs to Twitter.', error_html='This looks like an <a href="http://indiewebcamp.com/rsvp">RSVP</a>. ' 'Publishing events or RSVPs to Twitter is not supported.') else: return source.creation_result( abort=False, error_plain='Cannot publish type=%s, verb=%s to Twitter' % (type, verb), error_html='Cannot publish type=%s, verb=%s to Twitter' % (type, verb)) id_str = resp.get('id_str') if id_str: resp.update({'id': id_str, 'url': self.tweet_url(resp)}) elif 'url' not in resp: resp['url'] = base_url return source.creation_result(resp)