def updated_or_published(item): props = microformats2.first_props(item.get('properties')) return props.get('updated') or props.get('published') or ''
def attempt_single_item(self, item): """Attempts to preview or publish a single mf2 item. Args: item: mf2 item dict from mf2py Returns: CreationResult """ self.maybe_inject_silo_content(item) obj = microformats2.json_to_object(item) ignore_formatting = self.ignore_formatting(item) if ignore_formatting: prop = microformats2.first_props(item.get('properties', {})) content = microformats2.get_text(prop.get('content')) if content: obj['content'] = content.strip() # which original post URL to include? in order of preference: # 1. rel-shortlink (background: https://github.com/snarfed/bridgy/issues/173) # 2. original user-provided URL if it redirected # 3. u-url if available # 4. actual final fetched URL if self.shortlink: obj['url'] = self.shortlink elif self.source_url() != self.fetched.url: obj['url'] = self.source_url() elif 'url' not in obj: obj['url'] = self.fetched.url logging.debug('Converted to ActivityStreams object: %s', json_dumps(obj, indent=2)) # posts and comments need content obj_type = obj.get('objectType') if obj_type in ('note', 'article', 'comment'): if (not obj.get('content') and not obj.get('summary') and not obj.get('displayName')): return gr_source.creation_result( abort=False, error_plain='Could not find content in %s' % self.fetched.url, error_html='Could not find <a href="http://microformats.org/">content</a> in %s' % self.fetched.url) self.preprocess(obj) include_link = self.include_link(item) if not self.authorize(): return gr_source.creation_result(abort=True) if self.PREVIEW: result = self.source.gr_source.preview_create( obj, include_link=include_link, ignore_formatting=ignore_formatting) previewed = result.content or result.description if self.entity.type == 'preview': self.entity.published = previewed if not previewed: return result # there was an error return self._render_preview(result, include_link=include_link) else: result = self.source.gr_source.create( obj, include_link=include_link, ignore_formatting=ignore_formatting) self.entity.published = result.content if not result.content: return result # there was an error if 'url' not in self.entity.published: self.entity.published['url'] = obj.get('url') self.entity.type = self.entity.published.get('type') or models.get_type(obj) self.response.headers['Content-Type'] = 'application/json' logging.info('Returning %s', json_dumps(self.entity.published, indent=2)) self.response.headers['Location'] = self.entity.published['url'] self.response.status = 201 return gr_source.creation_result( json_dumps(self.entity.published, indent=2))
def attempt_single_item(self, item): """Attempts to preview or publish a single mf2 item. Args: item: mf2 item dict from mf2py Returns: CreationResult """ self.maybe_inject_silo_content(item) obj = microformats2.json_to_object(item) ignore_formatting = self.ignore_formatting(item) if ignore_formatting: prop = microformats2.first_props(item.get('properties', {})) content = microformats2.get_text(prop.get('content')) if content: obj['content'] = content.strip() # which original post URL to include? in order of preference: # 1. rel-shortlink (background: https://github.com/snarfed/bridgy/issues/173) # 2. original user-provided URL if it redirected # 3. u-url if available # 4. actual final fetched URL if self.shortlink: obj['url'] = self.shortlink elif self.source_url() != self.fetched.url: obj['url'] = self.source_url() elif 'url' not in obj: obj['url'] = self.fetched.url logging.debug('Converted to ActivityStreams object: %s', json.dumps(obj, indent=2)) # posts and comments need content obj_type = obj.get('objectType') if obj_type in ('note', 'article', 'comment'): if (not obj.get('content') and not obj.get('summary') and not obj.get('displayName')): return gr_source.creation_result( abort=False, error_plain='Could not find content in %s' % self.fetched.url, error_html= 'Could not find <a href="http://microformats.org/">content</a> in %s' % self.fetched.url) self.preprocess(obj) include_link = self.include_link(item) if not self.authorize(): return gr_source.creation_result(abort=True) # RIP Facebook comments/likes. https://github.com/snarfed/bridgy/issues/350 if (isinstance(self.source, FacebookPage) and (obj_type == 'comment' or obj.get('verb') == 'like')): return gr_source.creation_result( abort=True, error_plain= 'Facebook comments and likes are no longer supported. :(', error_html= '<a href="https://github.com/snarfed/bridgy/issues/350">' 'Facebook comments and likes are no longer supported.</a> :(') if self.PREVIEW: result = self.source.gr_source.preview_create( obj, include_link=include_link, ignore_formatting=ignore_formatting) self.entity.published = result.content or result.description if not self.entity.published: return result # there was an error state = { 'source_key': self.source.key.urlsafe(), 'source_url': self.source_url(), 'target_url': self.target_url(), 'include_link': include_link, } vars = { 'source': self.preprocess_source(self.source), 'preview': result.content, 'description': result.description, 'webmention_endpoint': self.request.host_url + '/publish/webmention', 'state': self.encode_state_parameter(state), } vars.update(state) logging.info('Rendering preview with template vars %s', pprint.pformat(vars)) return gr_source.creation_result( template.render('templates/preview.html', vars)) else: result = self.source.gr_source.create( obj, include_link=include_link, ignore_formatting=ignore_formatting) self.entity.published = result.content if not result.content: return result # there was an error if 'url' not in self.entity.published: self.entity.published['url'] = obj.get('url') self.entity.type = self.entity.published.get( 'type') or models.get_type(obj) self.entity.type_label = self.source.TYPE_LABELS.get( self.entity.type) self.response.headers['Content-Type'] = 'application/json' logging.info('Returning %s', json.dumps(self.entity.published, indent=2)) self.response.headers['Location'] = self.entity.published[ 'url'].encode('utf-8') self.response.status = 201 return gr_source.creation_result( json.dumps(self.entity.published, indent=2))
def content(mf2): items = mf2.get('items') if items: return microformats2.first_props( items[0].get('properties')).get('content')
def get(self, type, source_short_name, string_id, *ids): source_cls = models.sources.get(source_short_name) if not source_cls: self.abort( 400, "Source type '%s' not found. Known sources: %s" % (source_short_name, filter(None, models.sources.keys()))) self.source = source_cls.get_by_id(string_id) if not self.source: self.abort( 400, 'Source %s %s not found' % (source_short_name, string_id)) elif (self.source.status == 'disabled' or ('listen' not in self.source.features and 'email' not in self.source.features)): self.abort( 400, 'Source %s is disabled for backfeed' % self.source.bridgy_path()) format = self.request.get('format', 'html') if format not in ('html', 'json'): self.abort(400, 'Invalid format %s, expected html or json' % format) for id in ids: if not self.VALID_ID.match(id): self.abort(404, 'Invalid id %s' % id) label = '%s:%s %s %s' % (source_short_name, string_id, type, ids) cache_key = 'H ' + label obj = memcache.get(cache_key) if obj and not appengine_config.DEBUG: logging.info('Using cached object for %s', label) else: logging.info('Fetching %s', label) try: obj = self.get_item(*ids) except models.DisableSource as e: self.abort( 401, "Bridgy's access to your account has expired. Please visit https://brid.gy/ to refresh it!" ) except ValueError as e: self.abort(400, '%s error:\n%s' % (self.source.GR_CLASS.NAME, e)) except Exception as e: # pass through all API HTTP errors if we can identify them code, body = util.interpret_http_exception(e) # temporary, trying to debug a flaky test failure # eg https://circleci.com/gh/snarfed/bridgy/769 if code: self.response.status_int = int(code) self.response.headers['Content-Type'] = 'text/plain' self.response.write('%s error:\n%s' % (self.source.GR_CLASS.NAME, body)) return else: raise memcache.set(cache_key, obj, time=CACHE_TIME) if not obj: self.abort(404, label) if self.source.is_blocked(obj): self.abort(410, 'That user is currently blocked') # use https for profile pictures so we don't cause SSL mixed mode errors # when serving over https. author = obj.get('author', {}) image = author.get('image', {}) url = image.get('url') if url: image['url'] = util.update_scheme(url, self) mf2_json = microformats2.object_to_json(obj, synthesize_content=False) # try to include the author's silo profile url author = first_props(mf2_json.get('properties', {})).get('author', {}) author_uid = first_props(author.get('properties', {})).get('uid', '') if author_uid: parsed = util.parse_tag_uri(author_uid) if parsed: silo_url = self.source.gr_source.user_url(parsed[1]) urls = author.get('properties', {}).setdefault('url', []) if silo_url not in microformats2.get_string_urls(urls): urls.append(silo_url) # write the response! self.response.headers['Access-Control-Allow-Origin'] = '*' if format == 'html': self.response.headers['Content-Type'] = 'text/html; charset=utf-8' url = obj.get('url', '') self.response.out.write( TEMPLATE.substitute({ 'refresh': (('<meta http-equiv="refresh" content="0;url=%s">' % url) if url else ''), 'url': url, 'body': microformats2.json_to_html(mf2_json), 'title': self.get_title(obj), })) elif format == 'json': self.response.headers[ 'Content-Type'] = 'application/json; charset=utf-8' self.response.out.write(json.dumps(mf2_json, indent=2))
def updated_or_published(item): props = microformats2.first_props(item.get('properties')) return props.get('updated') or props.get('published')
def get(self, type, source_short_name, string_id, *ids): source_cls = models.sources.get(source_short_name) if not source_cls: self.abort(400, "Source type '%s' not found. Known sources: %s" % (source_short_name, filter(None, models.sources.keys()))) self.source = source_cls.get_by_id(string_id) if not self.source: self.abort(400, 'Source %s %s not found' % (source_short_name, string_id)) elif self.source.status == 'disabled' or 'listen' not in self.source.features: self.abort(400, 'Source %s is disabled for backfeed' % self.source.bridgy_path()) format = self.request.get('format', 'html') if format not in ('html', 'json'): self.abort(400, 'Invalid format %s, expected html or json' % format) for id in ids: if not self.VALID_ID.match(id): self.abort(404, 'Invalid id %s' % id) label = '%s:%s %s %s' % (source_short_name, string_id, type, ids) cache_key = 'H ' + label obj = memcache.get(cache_key) if obj: logging.info('Using cached object for %s', label) else: logging.info('Fetching %s', label) try: obj = self.get_item(*ids) except models.DisableSource as e: self.abort(401, "Bridgy's access to your account has expired. Please visit https://brid.gy/ to refresh it!") except Exception as e: # pass through all API HTTP errors if we can identify them code, body = util.interpret_http_exception(e) if not code and util.is_connection_failure(e): code = 503 body = str(e) if code: self.response.status_int = int(code) self.response.headers['Content-Type'] = 'text/plain' self.response.write('%s error:\n%s' % (self.source.GR_CLASS.NAME, body)) return else: raise memcache.set(cache_key, obj, time=CACHE_TIME) if not obj: self.abort(404, label) # use https for profile pictures so we don't cause SSL mixed mode errors # when serving over https. author = obj.get('author', {}) image = author.get('image', {}) url = image.get('url') if url: image['url'] = util.update_scheme(url, self) mf2_json = microformats2.object_to_json(obj, synthesize_content=False) # try to include the author's silo profile url author = first_props(mf2_json.get('properties', {})).get('author', {}) author_uid = first_props(author.get('properties', {})).get('uid', '') if author_uid: parsed = util.parse_tag_uri(author_uid) if parsed: silo_url = self.source.gr_source.user_url(parsed[1]) urls = author.get('properties', {}).setdefault('url', []) if silo_url not in microformats2.get_string_urls(urls): urls.append(silo_url) # write the response! self.response.headers['Access-Control-Allow-Origin'] = '*' if format == 'html': self.response.headers['Content-Type'] = 'text/html; charset=utf-8' self.response.out.write(TEMPLATE.substitute({ 'url': obj.get('url', ''), 'body': microformats2.json_to_html(mf2_json), 'title': self.get_title(obj), })) elif format == 'json': self.response.headers['Content-Type'] = 'application/json; charset=utf-8' self.response.out.write(json.dumps(mf2_json, indent=2))
class ItemHandler(webapp2.RequestHandler): """Fetches a post, repost, like, or comment and serves it as mf2 HTML or JSON. """ handle_exception = handlers.handle_exception source = None VALID_ID = re.compile(r'^[\w.+:@-]+$') def head(self, *args): """Return an empty 200 with no caching directives.""" def get_item(self, id): """Fetches and returns an object from the given source. To be implemented by subclasses. Args: source: bridgy.Source subclass id: string Returns: ActivityStreams object dict """ raise NotImplementedError() def get_title(self, obj): """Returns the string to be used in the <title> tag. Args: obj: ActivityStreams object """ return obj.get('title') or obj.get('content') or 'Bridgy Response' def get_post(self, id, **kwargs): """Fetch a post. Args: id: string, site-specific post id is_event: bool kwargs: passed through to get_activities Returns: ActivityStreams object dict """ try: posts = self.source.get_activities(activity_id=id, user_id=self.source.key.id(), **kwargs) if posts: return posts[0] logging.warning('Source post %s not found', id) except Exception as e: util.interpret_http_exception(e) def get(self, type, source_short_name, string_id, *ids): source_cls = models.sources.get(source_short_name) if not source_cls: self.abort( 400, "Source type '%s' not found. Known sources: %s" % (source_short_name, filter(None, models.sources.keys()))) self.source = source_cls.get_by_id(string_id) if not self.source: self.abort( 400, 'Source %s %s not found' % (source_short_name, string_id)) format = self.request.get('format', 'html') if format not in ('html', 'json'): self.abort(400, 'Invalid format %s, expected html or json' % format) for id in ids: if not self.VALID_ID.match(id): self.abort(404, 'Invalid id %s' % id) label = '%s:%s %s %s' % (source_short_name, string_id, type, ids) cache_key = 'H ' + label obj = memcache.get(cache_key) if obj: logging.info('Using cached object for %s', label) else: logging.info('Fetching %s', label) try: obj = self.get_item(*ids) except Exception, e: # pass through all API HTTP errors if we can identify them code, body = util.interpret_http_exception(e) if not code and util.is_connection_failure(e): code = 503 body = str(e) if code: self.response.status_int = int(code) self.response.headers['Content-Type'] = 'text/plain' self.response.write('%s error:\n%s' % (self.source.GR_CLASS.NAME, body)) return else: raise memcache.set(cache_key, obj, time=CACHE_TIME) if not obj: self.abort(404, label) # use https for profile pictures so we don't cause SSL mixed mode errors # when serving over https. author = obj.get('author', {}) image = author.get('image', {}) url = image.get('url') if url: image['url'] = util.update_scheme(url, self) mf2_json = microformats2.object_to_json(obj, synthesize_content=False) # try to include the author's silo profile url author = first_props(mf2_json.get('properties', {})).get('author', {}) author_uid = first_props(author.get('properties', {})).get('uid', '') if author_uid: parsed = util.parse_tag_uri(author_uid) if parsed: silo_url = self.source.gr_source.user_url(parsed[1]) urls = author.get('properties', {}).setdefault('url', []) if silo_url not in microformats2.get_string_urls(urls): urls.append(silo_url) # write the response! self.response.headers['Access-Control-Allow-Origin'] = '*' if format == 'html': self.response.headers['Content-Type'] = 'text/html; charset=utf-8' self.response.out.write( TEMPLATE.substitute({ 'url': obj.get('url', ''), 'body': microformats2.json_to_html(mf2_json), 'title': self.get_title(obj), })) elif format == 'json': self.response.headers[ 'Content-Type'] = 'application/json; charset=utf-8' self.response.out.write(json.dumps(mf2_json, indent=2))
def attempt_single_item(self, item): """Attempts to preview or publish a single mf2 item. Args: item: mf2 item dict from mf2py Returns: CreationResult """ self.maybe_inject_silo_content(item) obj = microformats2.json_to_object(item) ignore_formatting = self.ignore_formatting(item) if ignore_formatting: prop = microformats2.first_props(item.get('properties', {})) content = microformats2.get_text(prop.get('content')) if content: obj['content'] = content.strip() # which original post URL to include? in order of preference: # 1. rel-shortlink (background: https://github.com/snarfed/bridgy/issues/173) # 2. original user-provided URL if it redirected # 3. u-url if available # 4. actual final fetched URL if self.shortlink: obj['url'] = self.shortlink elif self.source_url() != self.fetched.url: obj['url'] = self.source_url() elif 'url' not in obj: obj['url'] = self.fetched.url logging.debug('Converted to ActivityStreams object: %s', json.dumps(obj, indent=2)) # posts and comments need content obj_type = obj.get('objectType') if obj_type in ('note', 'article', 'comment'): if (not obj.get('content') and not obj.get('summary') and not obj.get('displayName')): return gr_source.creation_result( abort=False, error_plain='Could not find content in %s' % self.fetched.url, error_html='Could not find <a href="http://microformats.org/">content</a> in %s' % self.fetched.url) self.preprocess(obj) omit_link = self.omit_link(item) if not self.authorize(): return gr_source.creation_result(abort=True) # RIP Facebook comments/likes. https://github.com/snarfed/bridgy/issues/350 if (isinstance(self.source, FacebookPage) and (obj_type == 'comment' or obj.get('verb') == 'like')): return gr_source.creation_result( abort=True, error_plain='Facebook comments and likes are no longer supported. :(', error_html='<a href="https://github.com/snarfed/bridgy/issues/350">' 'Facebook comments and likes are no longer supported.</a> :(') if self.PREVIEW: result = self.source.gr_source.preview_create( obj, include_link=not omit_link, ignore_formatting=ignore_formatting) self.entity.published = result.content or result.description if not self.entity.published: return result # there was an error state = { 'source_key': self.source.key.urlsafe(), 'source_url': self.source_url(), 'target_url': self.target_url(), 'bridgy_omit_link': omit_link, } vars = {'source': self.preprocess_source(self.source), 'preview': result.content, 'description': result.description, 'webmention_endpoint': self.request.host_url + '/publish/webmention', 'state': self.encode_state_parameter(state), } vars.update(state) logging.info('Rendering preview with template vars %s', pprint.pformat(vars)) return gr_source.creation_result( template.render('templates/preview.html', vars)) else: result = self.source.gr_source.create(obj, include_link=not omit_link, ignore_formatting=ignore_formatting) self.entity.published = result.content if not result.content: return result # there was an error if 'url' not in self.entity.published: self.entity.published['url'] = obj.get('url') self.entity.type = self.entity.published.get('type') or models.get_type(obj) self.entity.type_label = self.source.TYPE_LABELS.get(self.entity.type) self.response.headers['Content-Type'] = 'application/json' logging.info('Returning %s', json.dumps(self.entity.published, indent=2)) self.response.headers['Location'] = self.entity.published['url'].encode('utf-8') self.response.status = 201 return gr_source.creation_result( json.dumps(self.entity.published, indent=2))
if not obj: self.abort(404, label) # use https for profile pictures so we don't cause SSL mixed mode errors # when serving over https. author = obj.get('author', {}) image = author.get('image', {}) url = image.get('url') if url: image['url'] = util.update_scheme(url, self) mf2_json = microformats2.object_to_json(obj) # try to include the author's silo profile url author = first_props(mf2_json.get('properties', {})).get('author', {}) author_uid = first_props(author.get('properties', {})).get('uid', '') if author_uid: parsed = util.parse_tag_uri(author_uid) if parsed: silo_url = self.source.gr_source.user_url(parsed[1]) urls = author.get('properties', {}).setdefault('url', []) if silo_url not in microformats2.get_string_urls(urls): urls.append(silo_url) # write the response! self.response.headers['Access-Control-Allow-Origin'] = '*' if format == 'html': self.response.headers['Content-Type'] = 'text/html; charset=utf-8' self.response.out.write(TEMPLATE.substitute({ 'url': obj.get('url', ''),
def dispatch_request(self, site, key_id, **kwargs): """Handle HTTP request.""" source_cls = models.sources.get(site) if not source_cls: error( f"Source type '{site}' not found. Known sources: {[s for s in models.sources.keys() if s]}" ) self.source = source_cls.get_by_id(key_id) if not self.source: error(f'Source {site} {key_id} not found') elif (self.source.status == 'disabled' or 'listen' not in self.source.features): error( f'Source {self.source.bridgy_path()} is disabled for backfeed') format = request.values.get('format', 'html') if format not in ('html', 'json'): error(f'Invalid format {format}, expected html or json') for id in kwargs.values(): if not self.VALID_ID.match(id): error(f'Invalid id {id}', 404) try: obj = self.get_item(**kwargs) except models.DisableSource: error( "Bridgy's access to your account has expired. Please visit https://brid.gy/ to refresh it!", 401) except ValueError as e: error(f'{self.source.GR_CLASS.NAME} error: {e}') if not obj: error(f'Not found: {site}:{key_id} {kwargs}', 404) if self.source.is_blocked(obj): error('That user is currently blocked', 410) # use https for profile pictures so we don't cause SSL mixed mode errors # when serving over https. author = obj.get('author', {}) image = author.get('image', {}) url = image.get('url') if url: image['url'] = util.update_scheme(url, request) mf2_json = microformats2.object_to_json(obj, synthesize_content=False) # try to include the author's silo profile url author = first_props(mf2_json.get('properties', {})).get('author', {}) author_uid = first_props(author.get('properties', {})).get('uid', '') if author_uid: parsed = util.parse_tag_uri(author_uid) if parsed: urls = author.get('properties', {}).setdefault('url', []) try: silo_url = self.source.gr_source.user_url(parsed[1]) if silo_url not in microformats2.get_string_urls(urls): urls.append(silo_url) except NotImplementedError: # from gr_source.user_url() pass # write the response! if format == 'html': url = obj.get('url', '') return TEMPLATE.substitute({ 'refresh': (f'<meta http-equiv="refresh" content="0;url={url}">' if url else ''), 'url': url, 'body': microformats2.json_to_html(mf2_json), 'title': obj.get('title') or obj.get('content') or 'Bridgy Response', }) elif format == 'json': return mf2_json
def attempt_single_item(self, item): """Attempts to preview or publish a single mf2 item. Args: item: mf2 item dict from mf2py Returns: a CreationResult object, where content is the string HTTP response or None if the source cannot publish this item type. """ props = item.get('properties', {}) ignore_formatting = self.ignore_formatting() if ignore_formatting is None: ignore_formatting = 'bridgy-ignore-formatting' in props obj = microformats2.json_to_object(item) if ignore_formatting: prop = microformats2.first_props(props) obj['content'] = prop.get('content', {}).get('value').strip() # which original post URL to include? if the source URL redirected, use the # (pre-redirect) source URL, since it might be a short URL. otherwise, use # u-url if it's set. finally, fall back to the actual fetched URL if self.source_url() != self.fetched.url: obj['url'] = self.source_url() elif 'url' not in obj: obj['url'] = self.fetched.url logging.debug('Converted to ActivityStreams object: %s', json.dumps(obj, indent=2)) # posts and comments need content obj_type = obj.get('objectType') if obj_type in ('note', 'article', 'comment'): if (not obj.get('content') and not obj.get('summary') and not obj.get('displayName')): return gr_source.creation_result( abort=False, error_plain='Could not find content in %s' % self.fetched.url, error_html='Could not find <a href="http://microformats.org/">content</a> in %s' % self.fetched.url) self.preprocess_activity(obj, ignore_formatting=ignore_formatting) omit_link = self.omit_link() if omit_link is None: omit_link = 'bridgy-omit-link' in props if not self.authorize(): return gr_source.creation_result(abort=True) # RIP Facebook comments/likes. https://github.com/snarfed/bridgy/issues/350 if (isinstance(self.source, FacebookPage) and (obj_type == 'comment' or obj.get('verb') == 'like')): return gr_source.creation_result( abort=True, error_plain='Facebook comments and likes are no longer supported. :(', error_html='<a href="https://github.com/snarfed/bridgy/issues/350">' 'Facebook comments and likes are no longer supported.</a> :(') if self.PREVIEW: result = self.source.gr_source.preview_create( obj, include_link=not omit_link) self.entity.published = result.content or result.description if not self.entity.published: return result # there was an error state = { 'source_key': self.source.key.urlsafe(), 'source_url': self.source_url(), 'target_url': self.target_url(), 'bridgy_omit_link': omit_link, } vars = {'source': self.preprocess_source(self.source), 'preview': result.content, 'description': result.description, 'webmention_endpoint': self.request.host_url + '/publish/webmention', 'state': self.encode_state_parameter(state), } vars.update(state) logging.info('Rendering preview with template vars %s', pprint.pformat(vars)) return gr_source.creation_result( template.render('templates/preview.html', vars)) else: result = self.source.gr_source.create(obj, include_link=not omit_link) self.entity.published = result.content if not result.content: return result # there was an error if 'url' not in self.entity.published: self.entity.published['url'] = obj.get('url') self.entity.type = self.entity.published.get('type') or models.get_type(obj) self.entity.type_label = self.source.TYPE_LABELS.get(self.entity.type) self.response.headers['Content-Type'] = 'application/json' logging.info('Returning %s', json.dumps(self.entity.published, indent=2)) return gr_source.creation_result( json.dumps(self.entity.published, indent=2))
if not obj: self.abort(404, label) # use https for profile pictures so we don't cause SSL mixed mode errors # when serving over https. author = obj.get("author", {}) image = author.get("image", {}) url = image.get("url") if url: image["url"] = util.update_scheme(url, self) mf2_json = microformats2.object_to_json(obj, synthesize_content=False) # try to include the author's silo profile url author = first_props(mf2_json.get("properties", {})).get("author", {}) author_uid = first_props(author.get("properties", {})).get("uid", "") if author_uid: parsed = util.parse_tag_uri(author_uid) if parsed: silo_url = self.source.gr_source.user_url(parsed[1]) urls = author.get("properties", {}).setdefault("url", []) if silo_url not in microformats2.get_string_urls(urls): urls.append(silo_url) # write the response! self.response.headers["Access-Control-Allow-Origin"] = "*" if format == "html": self.response.headers["Content-Type"] = "text/html; charset=utf-8" self.response.out.write( TEMPLATE.substitute(
def attempt_single_item(self, item): """Attempts to preview or publish a single mf2 item. Args: item: mf2 item dict from mf2py Returns: CreationResult """ self.maybe_inject_silo_content(item) obj = microformats2.json_to_object(item) ignore_formatting = self.ignore_formatting(item) if ignore_formatting: prop = microformats2.first_props(item.get('properties', {})) content = microformats2.get_text(prop.get('content')) if content: obj['content'] = content.strip() # which original post URL to include? in order of preference: # 1. rel-shortlink (background: https://github.com/snarfed/bridgy/issues/173) # 2. original user-provided URL if it redirected # 3. u-url if available # 4. actual final fetched URL if self.shortlink: obj['url'] = self.shortlink elif self.source_url() != self.fetched.url: obj['url'] = self.source_url() elif 'url' not in obj: obj['url'] = self.fetched.url logging.debug('Converted to ActivityStreams object: %s', json.dumps(obj, indent=2)) # posts and comments need content obj_type = obj.get('objectType') if obj_type in ('note', 'article', 'comment'): if (not obj.get('content') and not obj.get('summary') and not obj.get('displayName')): return gr_source.creation_result( abort=False, error_plain='Could not find content in %s' % self.fetched.url, error_html='Could not find <a href="http://microformats.org/">content</a> in %s' % self.fetched.url) self.preprocess(obj) include_link = self.include_link(item) if not self.authorize(): return gr_source.creation_result(abort=True) # RIP Facebook. # https://github.com/snarfed/bridgy/issues/817 # https://github.com/snarfed/bridgy/issues/350 verb = obj.get('verb') if isinstance(self.source, FacebookPage): return gr_source.creation_result( abort=True, error_plain='Facebook is no longer supported. So long, and thanks for all the fish!', error_html='<a href="https://brid.gy/about#rip-facebook">Facebook is no longer supported. So long, and thanks for all the fish!</a>') if self.PREVIEW: result = self.source.gr_source.preview_create( obj, include_link=include_link, ignore_formatting=ignore_formatting) self.entity.published = result.content or result.description if not self.entity.published: return result # there was an error return self._render_preview(result, include_link=include_link) else: result = self.source.gr_source.create( obj, include_link=include_link, ignore_formatting=ignore_formatting) self.entity.published = result.content if not result.content: return result # there was an error if 'url' not in self.entity.published: self.entity.published['url'] = obj.get('url') self.entity.type = self.entity.published.get('type') or models.get_type(obj) self.response.headers['Content-Type'] = 'application/json' logging.info('Returning %s', json.dumps(self.entity.published, indent=2)) self.response.headers['Location'] = self.entity.published['url'].encode('utf-8') self.response.status = 201 return gr_source.creation_result( json.dumps(self.entity.published, indent=2))