Example #1
0
def render():
    """Fetches a stored Response and renders it as HTML."""
    source = flask_util.get_required_param('source')
    target = flask_util.get_required_param('target')

    id = f'{source} {target}'
    resp = Response.get_by_id(id)
    if not resp:
        error(f'No stored response for {id}', status=404)

    if resp.source_mf2:
        as1 = microformats2.json_to_object(json_loads(resp.source_mf2))
    elif resp.source_as2:
        as1 = as2.to_as1(json_loads(resp.source_as2))
    elif resp.source_atom:
        as1 = atom.atom_to_activity(resp.source_atom)
    else:
        error(f'Stored response for {id} has no data', status=404)

    # add HTML meta redirect to source page. should trigger for end users in
    # browsers but not for webmention receivers (hopefully).
    html = microformats2.activities_to_html([as1])
    utf8 = '<meta charset="utf-8">'
    refresh = f'<meta http-equiv="refresh" content="0;url={source}">'
    return html.replace(utf8, utf8 + '\n' + refresh)
Example #2
0
    def test_activities_to_html_like(self):
        self.assert_multiline_equals(
            """\
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<article class="h-entry">
  <span class="p-uid">http://localhost:3000/users/ryan#likes/7</span>
  <span class="p-author h-card">
    <a class="u-url" href="http://localhost:3000/users/ryan">http://localhost:3000/users/ryan</a>
  </span>
  <div class="e-content p-name">
  <a href="http://localhost/2017-10-01_mastodon-dev-6">likes this.</a>
  </div>
<a class="u-like-of" href="http://localhost/2017-10-01_mastodon-dev-6"></a>
</article>
</body>
</html>
""",
            microformats2.activities_to_html([{
                'id': 'http://localhost:3000/users/ryan#likes/7',
                'objectType': 'activity',
                'verb': 'like',
                'object': {
                    'url': 'http://localhost/2017-10-01_mastodon-dev-6'
                },
                'actor': {
                    'url': 'http://localhost:3000/users/ryan'
                },
            }]),
            ignore_blanks=True)
Example #3
0
def render(activities, actor=None):
    # Pass images and videos through caching proxy to cache them
    for a in activities:
        microformats2.prefix_image_urls(a, IMAGE_PROXY_URL_BASE)
        microformats2.prefix_video_urls(a, VIDEO_PROXY_URL_BASE)

    # Generate output
    format = request.args.get('format') or 'atom'
    if format == 'atom':
        title = 'instagram-atom feed for %s' % source.Source.actor_name(actor)
        return atom.activities_to_atom(
            activities,
            actor,
            title=title,
            host_url=request.host_url,
            request_url=request.url,
            xml_base='https://www.instagram.com/',
        ), {
            'Content-Type': 'application/atom+xml'
        }

    elif format == 'html':
        return microformats2.activities_to_html(activities)
    else:
        flask_util.error(f'format must be either atom or html; got {format}')
Example #4
0
  def test_activities_to_html_like(self):
    self.assert_multiline_equals("""\
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<article class="h-entry">
  <span class="p-uid">http://localhost:3000/users/ryan#likes/7</span>
  <span class="p-author h-card">
    <a class="u-url" href="http://localhost:3000/users/ryan">http://localhost:3000/users/ryan</a>
  </span>
  <div class="e-content p-name">
  <a href="http://localhost/2017-10-01_mastodon-dev-6">likes this.</a>
  </div>
<a class="u-like-of" href="http://localhost/2017-10-01_mastodon-dev-6"></a>
</article>
</body>
</html>
""", microformats2.activities_to_html([{
  'id': 'http://localhost:3000/users/ryan#likes/7',
  'objectType': 'activity',
  'verb': 'like',
  'object': {'url': 'http://localhost/2017-10-01_mastodon-dev-6'},
  'actor': {'url': 'http://localhost:3000/users/ryan'},
}]), ignore_blanks=True)
Example #5
0
  def test_share_activity_to_json_html(self):
    """Should translate the full activity, not just the object."""
    share = {
      'verb': 'share',
      'actor': {'displayName': 'sharer'},
      'object': {
        'content': 'original',
        'actor': {'displayName': 'author'},
      },
    }

    self.assert_equals({
      'type': ['h-entry'],
      'properties': {
        'author': [{
          'type': ['h-card'],
          'properties': {'name': ['sharer']},
        }],
        'repost-of': [{
          'type': ['h-cite'],
          'properties': {
            'content': [{'html': 'original', 'value': 'original'}],
            'author': [{
              'type': ['h-card'],
              'properties': {'name': ['author']},
            }],
          }
        }],
      },
    }, microformats2.activity_to_json(share, synthesize_content=False))

    self.assert_multiline_in("""\
Shared <a href="#">a post</a> by   <span class="h-card">
<span class="p-name">author</span>
""", microformats2.activities_to_html([share]), ignore_blanks=True)
Example #6
0
    def get(self):
        source = util.get_required_param(self, 'source')
        target = util.get_required_param(self, 'target')

        id = '%s %s' % (source, target)
        resp = Response.get_by_id(id)
        if not resp:
            self.abort(404, 'No stored response for %s' % id)

        if resp.source_mf2:
            as1 = microformats2.json_to_object(json.loads(resp.source_mf2))
        elif resp.source_as2:
            as1 = as2.to_as1(json.loads(resp.source_as2))
        elif resp.source_atom:
            as1 = atom.atom_to_activity(resp.source_atom)
        else:
            self.abort(404, 'Stored response for %s has no data' % id)

        # add HTML meta redirect to source page. should trigger for end users in
        # browsers but not for webmention receivers (hopefully).
        html = microformats2.activities_to_html([as1])
        utf8 = '<meta charset="utf-8">'
        refresh = '<meta http-equiv="refresh" content="0;url=%s">' % source
        html = html.replace(utf8, utf8 + '\n' + refresh)

        self.response.write(html)
Example #7
0
  def write_response(self, response, actor=None, url=None, title=None):
    """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom output.
      url: the input URL
      title: string, Used in Atom output
    """
    expected_formats = ('activitystreams', 'json', 'atom', 'xml', 'html', 'json-mf2')
    format = self.request.get('format') or self.request.get('output') or 'json'
    if format not in expected_formats:
      raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                               (format, expected_formats))

    activities = response['items']

    self.response.headers.update({
      'Access-Control-Allow-Origin': '*',
      'Strict-Transport-Security':
          'max-age=16070400; includeSubDomains; preload',  # 6 months
    })

    if format in ('json', 'activitystreams'):
      # list of official MIME types:
      # https://www.iana.org/assignments/media-types/media-types.xhtml
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    elif format == 'atom':
      self.response.headers['Content-Type'] = 'application/atom+xml'
      hub = self.request.get('hub')
      self.response.out.write(atom.activities_to_atom(
        activities, actor,
        host_url=url or self.request.host_url + '/',
        request_url=self.request.url,
        xml_base=util.base_url(url),
        title=title,
        rels={'hub': hub} if hub else None))
      self.response.headers.add('Link', str('<%s>; rel="self"' % self.request.url))
      if hub:
        self.response.headers.add('Link', str('<%s>; rel="hub"' % hub))
    elif format == 'xml':
      self.response.headers['Content-Type'] = 'application/xml'
      self.response.out.write(XML_TEMPLATE % util.to_xml(response))
    elif format == 'html':
      self.response.headers['Content-Type'] = 'text/html'
      self.response.out.write(microformats2.activities_to_html(activities))
    elif format == 'json-mf2':
      self.response.headers['Content-Type'] = 'application/json'
      items = [microformats2.object_to_json(a) for a in activities]
      self.response.out.write(json.dumps({'items': items}, indent=2))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'
Example #8
0
  def write_response(self, response, actor=None, url=None, title=None):
    """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom output.
      url: the input URL
      title: string, Used in Atom output
    """
    expected_formats = ('activitystreams', 'json', 'atom', 'xml', 'html', 'json-mf2')
    format = self.request.get('format') or self.request.get('output') or 'json'
    if format not in expected_formats:
      raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                               (format, expected_formats))

    activities = response['items']

    self.response.headers.update({
      'Access-Control-Allow-Origin': '*',
      'Strict-Transport-Security':
          'max-age=16070400; includeSubDomains; preload',  # 6 months
    })

    if format in ('json', 'activitystreams'):
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    elif format == 'atom':
      self.response.headers['Content-Type'] = 'text/xml'
      hub = self.request.get('hub')
      self.response.out.write(atom.activities_to_atom(
        activities, actor,
        host_url=url or self.request.host_url + '/',
        request_url=self.request.url,
        xml_base=util.base_url(url),
        title=title,
        rels={'hub': hub} if hub else None))
      self.response.headers.add('Link', str('<%s>; rel="self"' % self.request.url))
      if hub:
        self.response.headers.add('Link', str('<%s>; rel="hub"' % hub))
    elif format == 'xml':
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write(XML_TEMPLATE % util.to_xml(response))
    elif format == 'html':
      self.response.headers['Content-Type'] = 'text/html'
      self.response.out.write(microformats2.activities_to_html(activities))
    elif format == 'json-mf2':
      self.response.headers['Content-Type'] = 'application/json'
      items = [microformats2.object_to_json(a) for a in activities]
      self.response.out.write(json.dumps({'items': items}, indent=2))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'
Example #9
0
    def test_share_activity_to_json_html(self):
        """Should translate the full activity, not just the object."""
        share = {
            'verb': 'share',
            'actor': {
                'displayName': 'sharer'
            },
            'object': {
                'content': 'original',
                'actor': {
                    'displayName': 'author'
                },
            },
        }

        self.assert_equals(
            {
                'type': ['h-entry'],
                'properties': {
                    'author': [{
                        'type': ['h-card'],
                        'properties': {
                            'name': ['sharer']
                        },
                    }],
                    'repost-of': [{
                        'type': ['h-cite'],
                        'properties': {
                            'content': [{
                                'html': 'original',
                                'value': 'original'
                            }],
                            'author': [{
                                'type': ['h-card'],
                                'properties': {
                                    'name': ['author']
                                },
                            }],
                        }
                    }],
                },
            }, microformats2.activity_to_json(share, synthesize_content=False))

        self.assert_multiline_in("""\
Shared <a href="#">a post</a> by   <span class="h-card">
<span class="p-name">author</span>
""",
                                 microformats2.activities_to_html([share]),
                                 ignore_blanks=True)
Example #10
0
    def get(self):
        source = util.get_required_param(self, 'source')
        target = util.get_required_param(self, 'target')

        id = '%s %s' % (source, target)
        resp = Response.get_by_id(id)
        if not resp:
            self.abort(404, 'No stored response for %s' % id)

        if resp.source_mf2:
            as1 = microformats2.json_to_object(json.loads(resp.source_mf2))
        elif resp.source_as2:
            as1 = as2.to_as1(json.loads(resp.source_as2))
        elif resp.source_atom:
            as1 = atom.atom_to_activity(resp.source_atom)
        else:
            self.abort(404, 'Stored response for %s has no data' % id)

        self.response.write(microformats2.activities_to_html([as1]))
Example #11
0
  def write_response(self, response, actor=None):
    """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom output.
    """
    expected_formats = ('activitystreams', 'json', 'atom', 'xml', 'html', 'json-mf2')
    format = self.request.get('format') or self.request.get('output') or 'json'
    if format not in expected_formats:
      raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                               (format, expected_formats))

    activities = response['items']

    self.response.headers['Access-Control-Allow-Origin'] = '*'
    if format in ('json', 'activitystreams'):
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    elif format == 'atom':
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write(atom.activities_to_atom(
          activities, actor, host_url=self.request.host_url + '/',
          request_url=self.request.path_url))
    elif format == 'xml':
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write(XML_TEMPLATE % util.to_xml(response))
    elif format == 'html':
      self.response.headers['Content-Type'] = 'text/html'
      self.response.out.write(microformats2.activities_to_html(activities))
    elif format == 'json-mf2':
      self.response.headers['Content-Type'] = 'application/json'
      items = [microformats2.object_to_json(a['object'], a.get('context', {}))
               for a in activities]
      self.response.out.write(json.dumps({'items': items}, indent=2))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'
Example #12
0
    def write_response(self,
                       response,
                       actor=None,
                       url=None,
                       title=None,
                       hfeed=None):
        """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom and JSON Feed output.
      url: the input URL
      title: string, used in feed output (Atom, JSON Feed, RSS)
      hfeed: dict, parsed mf2 h-feed, if available
    """
        format = self.request.get('format') or self.request.get(
            'output') or 'json'
        if format not in FORMATS:
            raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                                     (format, FORMATS))

        if 'plaintext' in self.request.params:
            # override content type
            self.response.headers['Content-Type'] = 'text/plain'
        else:
            content_type = FORMATS.get(format)
            if content_type:
                self.response.headers['Content-Type'] = content_type

        if self.request.method == 'HEAD':
            return

        activities = response['items']
        try:
            if format in ('as1', 'json', 'activitystreams'):
                self.response.out.write(json_dumps(response, indent=2))
            elif format == 'as2':
                response.update({
                    'items': [as2.from_as1(a) for a in activities],
                    'totalItems':
                    response.pop('totalResults', None),
                    'updated':
                    response.pop('updatedSince', None),
                    'filtered':
                    None,
                    'sorted':
                    None,
                })
                self.response.out.write(
                    json_dumps(util.trim_nulls(response), indent=2))
            elif format == 'atom':
                hub = self.request.get('hub')
                reader = self.request.get('reader', 'true').lower()
                if reader not in ('true', 'false'):
                    self.abort(400,
                               'reader param must be either true or false')
                if not actor and hfeed:
                    actor = microformats2.json_to_object({
                        'properties':
                        hfeed.get('properties', {}),
                    })
                self.response.out.write(
                    atom.activities_to_atom(activities,
                                            actor,
                                            host_url=url
                                            or self.request.host_url + '/',
                                            request_url=self.request.url,
                                            xml_base=util.base_url(url),
                                            title=title,
                                            rels={'hub': hub} if hub else None,
                                            reader=(reader == 'true')))
                self.response.headers.add(
                    'Link', str('<%s>; rel="self"' % self.request.url))
                if hub:
                    self.response.headers.add('Link',
                                              str('<%s>; rel="hub"' % hub))
            elif format == 'rss':
                if not title:
                    title = 'Feed for %s' % url
                self.response.out.write(
                    rss.from_activities(activities,
                                        actor,
                                        title=title,
                                        feed_url=self.request.url,
                                        hfeed=hfeed,
                                        home_page_url=util.base_url(url)))
            elif format in ('as1-xml', 'xml'):
                self.response.out.write(XML_TEMPLATE % util.to_xml(response))
            elif format == 'html':
                self.response.out.write(
                    microformats2.activities_to_html(activities))
            elif format in ('mf2-json', 'json-mf2'):
                items = [microformats2.activity_to_json(a) for a in activities]
                self.response.out.write(json_dumps({'items': items}, indent=2))
            elif format == 'jsonfeed':
                try:
                    jf = jsonfeed.activities_to_jsonfeed(
                        activities,
                        actor=actor,
                        title=title,
                        feed_url=self.request.url)
                except TypeError as e:
                    raise exc.HTTPBadRequest('Unsupported input data: %s' % e)
                self.response.out.write(json_dumps(jf, indent=2))
        except ValueError as e:
            logging.warning('converting to output format failed',
                            stack_info=True)
            self.abort(400, 'Could not convert to %s: %s' % (format, str(e)))
Example #13
0
    def write_response(self, response, actor=None, url=None, title=None):
        """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom and JSON Feed output.
      url: the input URL
      title: string, Used in Atom and JSON Feed output
    """
        expected_formats = ('activitystreams', 'json', 'atom', 'xml', 'html',
                            'json-mf2', 'jsonfeed')
        format = self.request.get('format') or self.request.get(
            'output') or 'json'
        if format not in expected_formats:
            raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                                     (format, expected_formats))

        activities = response['items']

        if format in ('json', 'activitystreams'):
            # list of official MIME types:
            # https://www.iana.org/assignments/media-types/media-types.xhtml
            self.response.headers['Content-Type'] = 'application/json'
            self.response.out.write(json.dumps(response, indent=2))
        elif format == 'atom':
            self.response.headers['Content-Type'] = 'application/atom+xml'
            hub = self.request.get('hub')
            reader = self.request.get('reader', 'true').lower()
            if reader not in ('true', 'false'):
                self.abort(400, 'reader param must be either true or false')
            self.response.out.write(
                atom.activities_to_atom(activities,
                                        actor,
                                        host_url=url
                                        or self.request.host_url + '/',
                                        request_url=self.request.url,
                                        xml_base=util.base_url(url),
                                        title=title,
                                        rels={'hub': hub} if hub else None,
                                        reader=(reader == 'true')))
            self.response.headers.add(
                'Link', str('<%s>; rel="self"' % self.request.url))
            if hub:
                self.response.headers.add('Link', str('<%s>; rel="hub"' % hub))
        elif format == 'xml':
            self.response.headers['Content-Type'] = 'application/xml'
            self.response.out.write(XML_TEMPLATE % util.to_xml(response))
        elif format == 'html':
            self.response.headers['Content-Type'] = 'text/html'
            self.response.out.write(
                microformats2.activities_to_html(activities))
        elif format == 'json-mf2':
            self.response.headers['Content-Type'] = 'application/json'
            items = [microformats2.activity_to_json(a) for a in activities]
            self.response.out.write(json.dumps({'items': items}, indent=2))
        elif format == 'jsonfeed':
            self.response.headers['Content-Type'] = 'application/json'
            try:
                jf = jsonfeed.activities_to_jsonfeed(activities,
                                                     actor=actor,
                                                     title=title,
                                                     feed_url=self.request.url)
            except TypeError as e:
                raise exc.HTTPBadRequest('Unsupported input data: %s' % e)
            self.response.out.write(json.dumps(jf, indent=2))

        if 'plaintext' in self.request.params:
            # override response content type
            self.response.headers['Content-Type'] = 'text/plain'
Example #14
0
  def write_response(self, response, actor=None, url=None, title=None,
                     hfeed=None):
    """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom and JSON Feed output.
      url: the input URL
      title: string, used in feed output (Atom, JSON Feed, RSS)
      hfeed: dict, parsed mf2 h-feed, if available
    """
    format = self.request.get('format') or self.request.get('output') or 'json'
    if format not in FORMATS:
      raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                               (format, FORMATS))

    activities = response['items']

    try:
      if format in ('as1', 'json', 'activitystreams'):
        # list of official MIME types:
        # https://www.iana.org/assignments/media-types/media-types.xhtml
        self.response.headers['Content-Type'] = \
          'application/json' if format == 'json' else 'application/stream+json'
        self.response.out.write(json.dumps(response, indent=2))
      elif format == 'as2':
        self.response.headers['Content-Type'] = 'application/activity+json'
        response.update({
          'items': [as2.from_as1(a) for a in activities],
          'totalItems': response.pop('totalResults', None),
          'updated': response.pop('updatedSince', None),
          'filtered': None,
          'sorted': None,
        })
        self.response.out.write(json.dumps(util.trim_nulls(response), indent=2))
      elif format == 'atom':
        self.response.headers['Content-Type'] = 'application/atom+xml'
        hub = self.request.get('hub')
        reader = self.request.get('reader', 'true').lower()
        if reader not in ('true', 'false'):
          self.abort(400, 'reader param must be either true or false')
        self.response.out.write(atom.activities_to_atom(
          activities, actor,
          host_url=url or self.request.host_url + '/',
          request_url=self.request.url,
          xml_base=util.base_url(url),
          title=title,
          rels={'hub': hub} if hub else None,
          reader=(reader == 'true')))
        self.response.headers.add('Link', str('<%s>; rel="self"' % self.request.url))
        if hub:
          self.response.headers.add('Link', str('<%s>; rel="hub"' % hub))
      elif format == 'rss':
        self.response.headers['Content-Type'] = 'application/rss+xml'
        if not title:
          title = 'Feed for %s' % url
        self.response.out.write(rss.from_activities(
          activities, actor, title=title,
          feed_url=self.request.url, hfeed=hfeed,
          home_page_url=util.base_url(url)))
      elif format in ('as1-xml', 'xml'):
        self.response.headers['Content-Type'] = 'application/xml'
        self.response.out.write(XML_TEMPLATE % util.to_xml(response))
      elif format == 'html':
        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write(microformats2.activities_to_html(activities))
      elif format in ('mf2-json', 'json-mf2'):
        self.response.headers['Content-Type'] = 'application/json'
        items = [microformats2.activity_to_json(a) for a in activities]
        self.response.out.write(json.dumps({'items': items}, indent=2))
      elif format == 'jsonfeed':
        self.response.headers['Content-Type'] = 'application/json'
        try:
          jf = jsonfeed.activities_to_jsonfeed(activities, actor=actor, title=title,
                                               feed_url=self.request.url)
        except TypeError as e:
          raise exc.HTTPBadRequest('Unsupported input data: %s' % e)
        self.response.out.write(json.dumps(jf, indent=2))
    except ValueError as e:
      logging.warning('converting to output format failed', exc_info=True)
      self.abort(400, 'Could not convert to %s: %s' % (format, str(e)))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'
Example #15
0
    def get(self):
        cookie = 'sessionid=%s' % urllib.parse.quote(
            util.get_required_param(self, 'sessionid').encode('utf-8'))
        logging.info('Fetching with Cookie: %s', cookie)

        host_url = self.request.host_url + '/'
        ig = instagram.Instagram()
        try:
            resp = ig.get_activities_response(group_id=source.FRIENDS,
                                              scrape=True,
                                              cookie=cookie)
        except Exception as e:
            status, text = util.interpret_http_exception(e)
            if status in ('403', ):
                self.response.headers['Content-Type'] = 'application/atom+xml'
                self.response.out.write(
                    atom.activities_to_atom([{
                        'object': {
                            'url':
                            self.request.url,
                            'content':
                            'Your instagram-atom cookie isn\'t working. <a href="%s">Click here to regenerate your feed!</a>'
                            % host_url,
                        },
                    }], {},
                                            title='instagram-atom',
                                            host_url=host_url,
                                            request_url=self.request.path_url))
                return
            elif status == '401':
                # IG returns 401 sometimes as a form of rate limiting or bot detection
                self.response.status = '429'
            elif status:
                self.response.status = status
            else:
                logging.exception('oops!')
                self.response.status = 500

            self.response.text = text or 'Unknown error.'
            return

        actor = resp.get('actor')
        if actor:
            logging.info('Logged in as %s (%s)', actor.get('username'),
                         actor.get('displayName'))
        else:
            logging.warning("Couldn't determine Instagram user!")

        activities = resp.get('items', [])
        format = self.request.get('format', 'atom')
        if format == 'atom':
            title = 'instagram-atom feed for %s' % ig.actor_name(actor)
            self.response.headers['Content-Type'] = 'application/atom+xml'
            self.response.out.write(
                atom.activities_to_atom(activities,
                                        actor,
                                        title=title,
                                        host_url=host_url,
                                        request_url=self.request.path_url,
                                        xml_base='https://www.instagram.com/'))
        elif format == 'html':
            self.response.headers['Content-Type'] = 'text/html'
            self.response.out.write(
                microformats2.activities_to_html(activities))
        else:
            self.abort(400,
                       'format must be either atom or html; got %s' % format)