diff --git a/logs.py b/logs.py index c73bf7d..fb56edd 100644 --- a/logs.py +++ b/logs.py @@ -29,8 +29,9 @@ class ResponsesHandler(TemplateHandler): responses = Response.query().order(-Response.updated).fetch(20) for r in responses: - r.source, r.target = [util.pretty_link(url) - for url in r.key.id().split(' ')] + r.source_link = util.pretty_link(r.source()) + r.target_link = util.pretty_link(r.target()) + # TODO: support inbound too if r.direction == 'out' and r.updated >= VERSION_1_DEPLOYED: r.log_url_path = '/log?' + urllib.urlencode({ 'key': r.key.id(), diff --git a/models.py b/models.py index 9e0ff49..bd4a6c2 100644 --- a/models.py +++ b/models.py @@ -92,6 +92,12 @@ class Response(StringIdModel): def get_or_create(cls, source=None, target=None, **kwargs): return cls.get_or_insert(cls._id(source, target), **kwargs) + def source(self): + return self.key.id().split()[0] + + def target(self): + return self.key.id().split()[1] + def proxy_url(self): """Returns the Bridgy Fed proxy URL to render this response as HTML.""" if self.source_mf2 or self.source_as2 or self.source_atom: diff --git a/templates/responses.html b/templates/responses.html index c0414aa..2882810 100644 --- a/templates/responses.html +++ b/templates/responses.html @@ -18,12 +18,11 @@ Source Target Protocol Status Time (click for log) {% for r in responses %} - {{ r.source|safe }} - {{ r.target|safe }} + {{ r.source_link|safe }} + {{ r.target_link|safe }} {{ r.protocol }} {{ r.status }} - {# TODO: support inbound too #} {% if r.log_url_path %}{% endif %} {{ r.updated.replace(microsecond=0) }} {% if r.log_url_path %}{% endif %} diff --git a/webmention.py b/webmention.py index efe89e9..97fa6d5 100644 --- a/webmention.py +++ b/webmention.py @@ -34,15 +34,13 @@ class WebmentionHandler(webapp2.RequestHandler): """Handles inbound webmention, converts to ActivityPub or Salmon. Instance attributes: - response: Response + resp: Response """ def post(self): - logging.info('Params: %s', self.request.params.items()) - source = util.get_required_param(self, 'source') - target = util.get_required_param(self, 'target') - logging.info('source target: %s %s', source, target) + logging.info('(Params: %s )', self.request.params.items()) + source = util.get_required_param(self, 'source') try: msg = 'Bridgy Fed: new webmention from %s' % source mail.send_mail( @@ -52,6 +50,22 @@ class WebmentionHandler(webapp2.RequestHandler): except BaseException: logging.warning('Error sending email', exc_info=True) + self.resp = None + try: + self.try_activitypub() + if self.resp: + self.resp.status = 'complete' + except: + if self.resp: + self.resp.status = 'error' + raise + finally: + if self.resp: + self.resp.put() + + def try_activitypub(self): + source = util.get_required_param(self, 'source') + # fetch source page, convert to ActivityStreams source_resp = common.requests_get(source) source_url = source_resp.url or source @@ -71,18 +85,22 @@ class WebmentionHandler(webapp2.RequestHandler): common.error(self, 'No u-in-reply-to, u-like-of, or u-repost-of ' 'found in %s' % source_url) + logging.info('source target: %s %s', source, target) try: target_resp = common.get_as2(target) except (requests.HTTPError, exc.HTTPBadGateway) as e: if (e.response.status_code // 100 == 2 and common.content_type(e.response).startswith('text/html')): - return self.send_salmon(source_obj, source_mf2, target_resp=e.response) + self.resp = Response.get_or_create( + source=source_url, target=e.response.url or target, + direction='out', source_mf2=json.dumps(source_mf2)) + return self.send_salmon(source_obj, target_resp=e.response) raise target_url = target_resp.url or target - stored_response = Response.get_or_create( + self.resp = Response.get_or_create( source=source_url, target=target_url, direction='out', - source_mf2=json.dumps(source_mf2)) + protocol='activitypub', source_mf2=json.dumps(source_mf2)) # find actor's inbox target_obj = target_resp.json() @@ -106,7 +124,7 @@ class WebmentionHandler(webapp2.RequestHandler): # TODO: probably need a way to save errors like this so that we can # return them if ostatus fails too. # common.error(self, 'Target actor has no inbox') - return self.send_salmon(source_obj, source_mf2, target_resp=target_resp) + return self.send_salmon(source_obj, target_resp=target_resp) # convert to AS2 source_domain = urlparse.urlparse(source_url).netloc @@ -114,7 +132,7 @@ class WebmentionHandler(webapp2.RequestHandler): source_activity = common.postprocess_as2( as2.from_as1(source_obj), target=target_obj, key=key) - if stored_response.status == 'complete': + if self.resp.status == 'complete': source_activity['type'] = 'Update' # prepare HTTP Signature (required by Mastodon) @@ -141,23 +159,17 @@ class WebmentionHandler(webapp2.RequestHandler): 'signature verification probably failed. :(\n') self.response.write(resp.text) - stored_response.status = 'complete' - stored_response.protocol = 'activitypub' - stored_response.put() + def send_salmon(self, source_obj, target_resp=None): + self.resp.protocol = 'ostatus' - def send_salmon(self, source_obj, source_mf2, target_url=None, target_resp=None): # fetch target HTML page, extract Atom rel-alternate link - if target_url: - assert not target_resp - target_resp = common.requests_get(target_url) - else: - assert target_resp - target_url = target_resp.url + if not target_resp: + target_resp = common.requests_get(self.resp.target()) parsed = BeautifulSoup(target_resp.content, from_encoding=target_resp.encoding) atom_url = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM) if not atom_url or not atom_url.get('href'): - common.error(self, 'Target post %s has no Atom link' % target_url, + common.error(self, 'Target post %s has no Atom link' % self.resp.target(), status=400) # fetch Atom target post, extract and inject id into source object @@ -190,7 +202,7 @@ class WebmentionHandler(webapp2.RequestHandler): if not endpoint: # try webfinger - parsed = urlparse.urlparse(target_url) + parsed = urlparse.urlparse(self.resp.target()) # TODO: test missing email email = entry.author_detail.get('email') or '@'.join( (entry.author_detail.name, parsed.netloc)) @@ -208,7 +220,7 @@ class WebmentionHandler(webapp2.RequestHandler): logging.info('Discovered Salmon endpoint %s', endpoint) # construct reply Atom object - source_url = self.request.get('source') + source_url = self.resp.source() activity = (source_obj if source_obj.get('verb') in source.VERBS_WITH_OBJECT else {'object': source_obj}) entry = atom.activity_to_atom(activity, xml_base=source_url) @@ -226,10 +238,6 @@ class WebmentionHandler(webapp2.RequestHandler): endpoint, data=common.XML_UTF8 + magic_envelope, headers={'Content-Type': common.CONTENT_TYPE_MAGIC_ENVELOPE}) - Response(source=source_url, target=target_url, direction='out', - protocol = 'ostatus', status = 'complete', - source_mf2=json.dumps(source_mf2)).put() - app = webapp2.WSGIApplication([ ('/webmention', WebmentionHandler),