def create(self, obj, include_link=False, ignore_formatting=False): verb = obj.get('verb') type = obj.get('objectType') if verb == 'like': return gr_source.creation_result( abort=True, error_plain='Cannot publish likes', error_html='Cannot publish likes') if 'content' not in obj: return gr_source.creation_result( abort=False, error_plain='No content', error_html='No content') if type == 'comment': base_url = self.base_object(obj).get('url') if not base_url: return gr_source.creation_result( abort=True, error_plain='no %s url to reply to' % self.DOMAIN, error_html='no %s url to reply to' % self.DOMAIN) content = self._content_for_create(obj, ignore_formatting=ignore_formatting) if include_link: content += ' - %s' % obj['url'] ret = {'id': 'fake id', 'url': 'http://fake/url', 'content': content} if verb == 'rsvp-yes': ret['type'] = 'post' return gr_source.creation_result(ret)
def preview_create(self, obj, include_link=False): if obj.get('verb') == 'like': return gr_source.creation_result( abort=True, error_plain='Cannot publish likes', error_html='Cannot publish likes') content = 'preview of ' + obj['content'] + (' - %s' % obj['url'] if include_link else '') return gr_source.creation_result(description=content)
def preview_create(self, obj, include_link=False, ignore_formatting=False): if obj.get('verb') == 'like': return gr_source.creation_result( abort=True, error_plain='Cannot publish likes', error_html='Cannot publish likes') content = self._content_for_create(obj, ignore_formatting=ignore_formatting) if include_link: content += ' - %s' % obj['url'] content = 'preview of ' + content return gr_source.creation_result(description=content)
def _render_preview(self, result, include_link=False): """Renders a preview CreationResult as HTML. Args: result: CreationResult include_link: boolean Returns: CreationResult with the rendered HTML in content """ state = { 'source_key': self.source.key.urlsafe().decode(), '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': util.host_url(self) + '/publish/webmention', 'state': util.encode_oauth_state(state), } vars.update(state) logging.info('Rendering preview with template vars %s', pprint.pformat(vars)) return gr_source.creation_result( JINJA_ENV.get_template('preview.html').render(**vars))
def _render_preview(self, result, include_link=False): """Renders a preview CreationResult as HTML. Args: result: CreationResult include_link: boolean Returns: CreationResult with the rendered HTML in content """ 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': util.host_url(self) + '/publish/webmention', 'state': util.encode_oauth_state(state), } vars.update(state) logging.info('Rendering preview with template vars %s', pprint.pformat(vars)) return gr_source.creation_result( JINJA_ENV.get_template('preview.html').render(**vars))
def test_expand_target_urls_blacklisted_target(self): """RSVP to a domain in the webmention blacklist should not trigger a fetch. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get( 'http://foo.com/bar', """ <article class="h-entry"> <div class="e-content"> <span class="p-rsvp" value="yes">yes</span> <a class="u-in-reply-to" href="http://fa.ke/homebrew-website-club"></a> </div> <a class="u-url" href="http://foo.com/bar"></a> </article> """) self.source.gr_source.create({ 'url': 'http://foo.com/bar', 'verb': 'rsvp-yes', 'displayName': 'yes', 'object': [{'url': 'http://fa.ke/homebrew-website-club'}], 'objectType': 'activity', 'content': ' <span class="p-rsvp" value="yes">yes</span> <a class="u-in-reply-to" href="http://fa.ke/homebrew-website-club"></a> ', }, include_link=gr_source.INCLUDE_LINK, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'RSVPd yes', })) self.mox.ReplayAll() self.assert_created('')
def test_create_like(self): self.expect_urlopen( 'https://api.instagram.com/v1/media/shortcode/ABC123', json.dumps({'data': MEDIA})) self.expect_urlopen('https://api.instagram.com/v1/media/123_456/likes', '{"meta":{"status":200}}', data='access_token=None') self.expect_urlopen( 'https://api.instagram.com/v1/users/self', json.dumps({ 'data': { 'id': '8', 'username': '******', 'full_name': 'Alice', 'profile_picture': 'http://alice/picture', } })) # like obj doesn't have url or id prior to publishing to_publish = copy.deepcopy(LIKE_OBJS[0]) del to_publish['id'] del to_publish['url'] self.mox.ReplayAll() self.assert_equals(source.creation_result(LIKE_OBJS[0]), self.instagram.create(to_publish))
def test_create_video_too_big(self): self.expect_urlopen('http://foo/xyz.mp4', 'video response') self.expect_requests_post( 'https://up.flickr.com/services/upload', data=[('description', 'foo')] + IGNORED_OAUTH_PARAMS, files={ 'photo': 'video response' } ).AndRaise( requests.exceptions.ConnectionError( socket.timeout( 'Request exceeds 10 MiB limit for URL: https://up.flickr.com/services/upload' ))) self.mox.ReplayAll() err = 'Sorry, photos and videos must be under 10MB.' self.assertEqual( source.creation_result(error_plain=err, error_html=err), self.flickr.create({ 'objectType': 'note', 'stream': { 'url': 'http://foo/xyz.mp4' }, 'url': 'http://foo/xyz', 'content': 'foo', }))
def test_create_like(self): self.expect_urlopen("https://api.instagram.com/v1/media/shortcode/ABC123", json.dumps({"data": MEDIA})) self.expect_urlopen( "https://api.instagram.com/v1/media/123_456/likes", '{"meta":{"status":200}}', data="access_token=None" ) self.expect_urlopen( "https://api.instagram.com/v1/users/self", json.dumps( { "data": { "id": "8", "username": "******", "full_name": "Alice", "profile_picture": "http://alice/picture", } } ), ) # like obj doesn't have url or id prior to publishing to_publish = copy.deepcopy(LIKE_OBJS[0]) del to_publish["id"] del to_publish["url"] self.mox.ReplayAll() self.assert_equals(source.creation_result(LIKE_OBJS[0]), self.instagram.create(to_publish))
def test_create_like(self): self.expect_urlopen( 'https://api.instagram.com/v1/media/shortcode/ABC123', json.dumps({'data': MEDIA})) self.expect_urlopen( 'https://api.instagram.com/v1/media/123_456/likes', '{"meta":{"status":200}}', data='access_token=None') self.expect_urlopen( 'https://api.instagram.com/v1/users/self', json.dumps({'data': { 'id': '8', 'username': '******', 'full_name': 'Alice', 'profile_picture': 'http://alice/picture', }})) # like obj doesn't have url or id prior to publishing to_publish = copy.deepcopy(LIKE_OBJS[0]) del to_publish['id'] del to_publish['url'] self.mox.ReplayAll() self.assert_equals(source.creation_result(LIKE_OBJS[0]), self.instagram.create(to_publish))
def test_expand_target_urls_fetch_failure(self): """Fetching the in-reply-to URL fails, but that shouldn't prevent us from publishing the post itself. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get('http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-in-reply-to" href="http://orig.domain/baz">In reply to</a> </article> """) self.expect_requests_get('http://orig.domain/baz', '', status_code=404) self.source.gr_source.create({ 'inReplyTo': [{'url': 'http://orig.domain/baz'}], 'displayName': 'In reply to', 'url': 'http://foo.com/bar', 'objectType': 'comment', }, include_link=True, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'This is a reply', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_u_syndication(self): """Comment on a post with a u-syndication value """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get('http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-in-reply-to" href="http://orig.domain/baz">In reply to</a> </article> """) self.expect_requests_get('http://orig.domain/baz', """ <article class="h-entry"> <span class="p-name e-content">Original post</span> <a class="u-syndication" href="https://fa.ke/a/b">syndicated</a> </article> """) self.source.gr_source.create({ 'inReplyTo': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}], 'displayName': 'In reply to', 'url': 'http://foo.com/bar', 'objectType': 'comment', }, include_link=True, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'This is a reply', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_no_microformats(self): """Publishing a like of a post that has no microformats; should have no problems posting the like anyway. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get('http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-like-of" href="http://orig.domain/baz">liked this</a> </article> """) self.expect_requests_get('http://orig.domain/baz', """ <article> A fantastically well-written article </article> """) self.source.gr_source.create({ 'verb': 'like', 'displayName': 'liked this', 'url': 'http://foo.com/bar', 'object': [{'url': 'http://orig.domain/baz'}], 'objectType': 'activity', }, include_link=True, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'liked this', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_blacklisted_target(self): """RSVP to a domain in the webmention blacklist should not trigger a fetch. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get('http://foo.com/bar', """ <article class="h-entry h-as-rsvp"> <div class="e-content"> <span class="p-rsvp" value="yes">yes</span> <a class="u-in-reply-to" href="http://fa.ke/homebrew-website-club"></a> </div> <a class="u-url" href="http://foo.com/bar"></a> </article> """) self.source.gr_source.create({ 'url': 'http://foo.com/bar', 'verb': 'rsvp-yes', 'displayName': 'yes', 'object': [{'url': 'http://fa.ke/homebrew-website-club'}], 'objectType': 'activity', 'content': '\n <span class="p-rsvp" value="yes">yes</span>\n <a class="u-in-reply-to" href="http://fa.ke/homebrew-website-club"></a>\n ', }, include_link=True, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'RSVPd yes', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_fetch_failure(self): """Fetching the in-reply-to URL fails, but that shouldn't prevent us from publishing the post itself. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get( 'http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-in-reply-to" href="http://orig.domain/baz">In reply to</a> </article> """) self.expect_requests_get('http://orig.domain/baz', '', status_code=404) self.source.gr_source.create({ 'inReplyTo': [{'url': 'http://orig.domain/baz'}], 'displayName': 'In reply to', 'url': 'http://foo.com/bar', 'objectType': 'comment', }, include_link=gr_source.INCLUDE_LINK, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'This is a reply', })) self.mox.ReplayAll() self.assert_created('')
def preview_create(self, obj, include_link=gr_source.OMIT_LINK, ignore_formatting=False): if obj.get('verb') == 'like': return gr_source.creation_result( abort=True, error_plain='Cannot publish likes', error_html='Cannot publish likes') content = self._content_for_create(obj, ignore_formatting=ignore_formatting) if include_link == gr_source.INCLUDE_LINK: content += ' - %s' % obj['url'] content = 'preview of ' + content return gr_source.creation_result(description=content)
def preview_create(self, obj, include_link=gr_source.OMIT_LINK, ignore_formatting=False): if obj.get('verb') == 'like': return gr_source.creation_result( abort=True, error_plain='Cannot publish likes', error_html='Cannot publish likes') content = self._content_for_create(obj, ignore_formatting=ignore_formatting) if include_link == gr_source.INCLUDE_LINK: content += ' - %s' % obj['url'] content = 'preview of ' + content images = self._images(obj) if images: content += ' with images %s' % ','.join(images) return gr_source.creation_result(description=content)
def create(self, obj, include_link=gr_source.OMIT_LINK, ignore_formatting=False): verb = obj.get('verb') type = obj.get('objectType') if verb == 'like': return gr_source.creation_result( abort=True, error_plain='Cannot publish likes', error_html='Cannot publish likes') if 'content' not in obj: return gr_source.creation_result(abort=False, error_plain='No content', error_html='No content') if type == 'comment': base_url = self.base_object(obj).get('url') if not base_url: return gr_source.creation_result( abort=True, error_plain='no %s url to reply to' % self.DOMAIN, error_html='no %s url to reply to' % self.DOMAIN) content = self._content_for_create(obj, ignore_formatting=ignore_formatting) if include_link == gr_source.INCLUDE_LINK: content += ' - %s' % obj['url'] ret = { 'id': 'fake id', 'url': 'http://fake/url', 'content': content, 'granary_message': 'granary message', } if verb == 'rsvp-yes': ret['type'] = 'post' images = self._images(obj) if images: ret['images'] = images return gr_source.creation_result(ret)
def test_facebook_publish_person_tags(self): self.auth_entity = oauth_facebook.FacebookAuth( id='auth entity', user_json=json.dumps({'id': '1'}), access_token_str='') self.auth_entity.put() self.source.key.delete() self.source = facebook.FacebookPage.new( self.handler, auth_entity=self.auth_entity, features=['publish'], domains=['foo.com']) self.source.put() self.oauth_state['source_key'] = self.source.key.urlsafe() input_urls, expected_urls = test_facebook.FacebookPageTest.prepare_person_tags() names = [url.strip('/').split('/')[-1].capitalize() for url in input_urls] obj = { 'objectType': 'note', 'url': 'http://foo.com/bar', 'content': 'my message', 'displayName': 'my message\n\nUnknown,444,Username,Inferred,Unknown,My.domain', 'tags': [{ 'objectType': 'person', 'url': url, 'displayName': name, } for url, name in zip(expected_urls, names)], } post_html = """ <article class="h-entry"> <p class="e-content"> my message </p> %s <a href="http://localhost/publish/facebook"></a> </article> """ % ','.join('<a class="h-card u-category" href="%s">%s</a>' % (url, name) for url, name in zip(input_urls, names)) for i in range(2): self.expect_requests_get('http://foo.com/bar', post_html) self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) result = gr_source.creation_result(content={'content': 'my message'}) self.source.gr_source.create(obj, include_link=True).AndReturn(result) self.mox.StubOutWithMock(self.source.gr_source, 'preview_create', use_mock_anything=True) self.source.gr_source.preview_create(obj, include_link=False).AndReturn(result) self.mox.ReplayAll() self.assert_created('my message', interactive=False, target='https://brid.gy/publish/facebook') self.assert_success('my message', preview=True, target='https://brid.gy/publish/facebook')
def test_create_video_too_big(self): self.expect_urlopen('http://foo/xyz.mp4', 'video response') self.expect_requests_post( 'https://up.flickr.com/services/upload', data=[('description', 'foo')] + IGNORED_OAUTH_PARAMS, files={'photo': 'video response'} ).AndRaise(requests.exceptions.ConnectionError(socket.error( 'Request exceeds 10 MiB limit for URL: https://up.flickr.com/services/upload'))) self.mox.ReplayAll() err = 'Sorry, photos and videos must be under 10MB.' self.assert_equals( source.creation_result(error_plain=err, error_html=err), self.flickr.create({ 'objectType': 'note', 'stream': {'url': 'http://foo/xyz.mp4'}, 'url': 'http://foo/xyz', 'content': 'foo', }))
def test_expand_target_urls_h_event_in_h_feed(self): """RSVP to an event is a single element inside an h-feed; we should handle it just like a normal post permalink page. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get( 'http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-in-reply-to" href="http://orig.domain/baz"></a> <span class="p-rsvp">yes</span> </article> """) self.expect_requests_get( 'http://orig.domain/baz', """ <html class="h-feed"> <article class="h-event"> <span class="p-name e-content">Original post</span> <a class="u-syndication" href="https://fa.ke/a/b">On Fa.ke</a> </article> </html> """) self.source.gr_source.create({ 'url': 'http://foo.com/bar', 'verb': 'rsvp-yes', 'displayName': 'yes', 'object': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}], 'objectType': 'activity', }, include_link=gr_source.INCLUDE_LINK, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'RSVPd yes', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_rel_syndication(self): """Publishing a like of a post with two rel=syndication values """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get( 'http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-like-of" href="http://orig.domain/baz">liked this</a> </article> """) self.expect_requests_get( 'http://orig.domain/baz', """ <link rel="syndication" href="https://fa.ke/a/b"> <link rel="syndication" href="https://flic.kr/c/d"> <article class="h-entry"> <span class="p-name e-content">Original post</span> </article> """) self.source.gr_source.create({ 'verb': 'like', 'displayName': 'liked this', 'url': 'http://foo.com/bar', 'object': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}, {'url': 'https://flic.kr/c/d'}], 'objectType': 'activity', }, include_link=gr_source.INCLUDE_LINK, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'liked this', })) self.mox.ReplayAll() self.assert_created('')
def test_create_bridgy_omit_link_maybe_mf2(self): """Test that bridgy-omit-link=maybe is parsed properly from mf2 """ content = '<data class="p-bridgy-omit-link" value="maybe">foo</data>' self.expect_requests_get('http://foo.com/bar', self.post_html % content) self.mox.StubOutWithMock( self.source.gr_source, 'create', use_mock_anything=True) self.source.gr_source.create( mox.IgnoreArg(), include_link=gr_source.INCLUDE_IF_TRUNCATED, ignore_formatting=False ).AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'foo', })) self.mox.ReplayAll() self.assert_created('foo')
def test_create_bridgy_omit_link_maybe_query_param(self): """Test that ?bridgy_omit_link=maybe query parameter is interpreted properly. """ self.expect_requests_get('http://foo.com/bar', self.post_html % 'foo') self.mox.StubOutWithMock( self.source.gr_source, 'create', use_mock_anything=True) self.source.gr_source.create( mox.IgnoreArg(), include_link=gr_source.INCLUDE_IF_TRUNCATED, ignore_formatting=False ).AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'foo', })) self.mox.ReplayAll() self.assert_created('foo', params={'bridgy_omit_link': 'maybe'})
def test_expand_target_urls_h_event_in_h_feed(self): """RSVP to an event is a single element inside an h-feed; we should handle it just like a normal post permalink page. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get('http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-in-reply-to" href="http://orig.domain/baz"></a> <span class="p-rsvp">yes</span> </article> """) self.expect_requests_get('http://orig.domain/baz', """ <html class="h-feed"> <article class="h-event"> <span class="p-name e-content">Original post</span> <a class="u-syndication" href="https://fa.ke/a/b">On Fa.ke</a> </article> </html> """) self.source.gr_source.create({ 'url': 'http://foo.com/bar', 'verb': 'rsvp-yes', 'displayName': 'yes', 'object': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}], 'objectType': 'activity', }, include_link=True, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'RSVPd yes', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_h_cite(self): """Repost a post with a p-syndication h-cite value (syndication property is a dict rather than a string) """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get( 'http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-repost-of" href="http://orig.domain/baz">reposted this</a> </article> """) self.expect_requests_get( 'http://orig.domain/baz', """ <article class="h-entry"> <span class="p-name e-content">Original post</span> <a class="p-syndication h-cite" href="https://fa.ke/a/b">On Fa.ke</a> </article> """) self.source.gr_source.create({ 'verb': 'share', 'displayName': 'reposted this', 'url': 'http://foo.com/bar', 'object': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}], 'objectType': 'activity', }, include_link=gr_source.INCLUDE_LINK, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'reposted this', })) self.mox.ReplayAll() self.assert_created('')
def test_create_bridgy_omit_link_maybe_query_param(self): """Test that ?bridgy_omit_link=maybe query parameter is interpreted properly. """ self.expect_requests_get('http://foo.com/bar', self.post_html % 'foo') self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.source.gr_source.create( mox.IgnoreArg(), include_link=gr_source.INCLUDE_IF_TRUNCATED, ignore_formatting=False).AndReturn( gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'foo', })) self.mox.ReplayAll() self.assert_created('foo', params={'bridgy_omit_link': 'maybe'})
def test_expand_target_urls_rel_syndication(self): """Publishing a like of a post with two rel=syndication values """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get('http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-like-of" href="http://orig.domain/baz">liked this</a> </article> """) self.expect_requests_get('http://orig.domain/baz', """ <link rel="syndication" href="https://fa.ke/a/b"> <link rel="syndication" href="https://flic.kr/c/d"> <article class="h-entry"> <span class="p-name e-content">Original post</span> </article> """) self.source.gr_source.create({ 'verb': 'like', 'displayName': 'liked this', 'url': 'http://foo.com/bar', 'object': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}, {'url': 'https://flic.kr/c/d'}], 'objectType': 'activity', }, include_link=True, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'liked this', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_u_syndication(self): """Comment on a post with a u-syndication value """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get( 'http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-in-reply-to" href="http://orig.domain/baz">In reply to</a> </article> """) self.expect_requests_get( 'http://orig.domain/baz', """ <article class="h-entry"> <span class="p-name e-content">Original post</span> <a class="u-syndication" href="https://fa.ke/a/b">syndicated</a> </article> """) self.source.gr_source.create({ 'inReplyTo': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}], 'displayName': 'In reply to', 'url': 'http://foo.com/bar', 'objectType': 'comment', }, include_link=gr_source.INCLUDE_LINK, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'This is a reply', })) self.mox.ReplayAll() self.assert_created('')
def test_expand_target_urls_no_microformats(self): """Publishing a like of a post that has no microformats; should have no problems posting the like anyway. """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get( 'http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-like-of" href="http://orig.domain/baz">liked this</a> </article> """) self.expect_requests_get( 'http://orig.domain/baz', """ <article> A fantastically well-written article </article> """) self.source.gr_source.create({ 'verb': 'like', 'displayName': 'liked this', 'url': 'http://foo.com/bar', 'object': [{'url': 'http://orig.domain/baz'}], 'objectType': 'activity', }, include_link=gr_source.INCLUDE_LINK, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'liked this', })) self.mox.ReplayAll() self.assert_created('')
def test_create_bridgy_omit_link_maybe_mf2(self): """Test that bridgy-omit-link=maybe is parsed properly from mf2 """ content = '<data class="p-bridgy-omit-link" value="maybe">foo</data>' self.expect_requests_get('http://foo.com/bar', self.post_html % content) self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.source.gr_source.create( mox.IgnoreArg(), include_link=gr_source.INCLUDE_IF_TRUNCATED, ignore_formatting=False).AndReturn( gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'foo', })) self.mox.ReplayAll() self.assert_created('foo')
def test_expand_target_urls_h_cite(self): """Repost a post with a p-syndication h-cite value (syndication property is a dict rather than a string) """ self.mox.StubOutWithMock(self.source.gr_source, 'create', use_mock_anything=True) self.expect_requests_get('http://foo.com/bar', """ <article class="h-entry"> <a class="u-url" href="http://foo.com/bar"></a> <a class="u-repost-of" href="http://orig.domain/baz">reposted this</a> </article> """) self.expect_requests_get('http://orig.domain/baz', """ <article class="h-entry"> <span class="p-name e-content">Original post</span> <a class="p-syndication h-cite" href="https://fa.ke/a/b">On Fa.ke</a> </article> """) self.source.gr_source.create({ 'verb': 'share', 'displayName': 'reposted this', 'url': 'http://foo.com/bar', 'object': [{'url': 'http://orig.domain/baz'}, {'url': 'https://fa.ke/a/b'}], 'objectType': 'activity', }, include_link=True, ignore_formatting=False). \ AndReturn(gr_source.creation_result({ 'url': 'http://fake/url', 'id': 'http://fake/url', 'content': 'reposted this', })) self.mox.ReplayAll() self.assert_created('')
def preview_delete(self, id): return gr_source.creation_result(description='delete %s' % id)
def delete(self, id): return gr_source.creation_result({ 'url': 'http://fake/url', 'msg': 'delete %s' % id, })
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))
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 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) 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))
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))