示例#1
0
  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
示例#2
0
  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
示例#3
0
  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
示例#4
0
    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)
示例#5
0
文件: twitter.py 项目: kylewm/granary
  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)
示例#6
0
  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
示例#7
0
  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)