Skip to content

Commit

Permalink
ATProto.send: fetch blobs for attachment images
Browse files Browse the repository at this point in the history
for #1411
  • Loading branch information
snarfed committed Nov 30, 2024
1 parent 676dae3 commit 547e063
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 9 deletions.
31 changes: 22 additions & 9 deletions atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,14 @@ def _convert(cls, obj, fetch_blobs=False, from_user=None):
if not obj.as1:
return {}

obj_as1 = obj.as1

# generate link preview attachment for first link in content, if any
Source.postprocess_object(
(as1.get_object(obj_as1) if obj_as1.get('objectType') == 'activity'
else obj_as1),
first_link_to_attachment=True)

blobs = {} # maps str URL to dict blob object
if fetch_blobs:
def fetch_blob(url, blob_field, name, check_size=True, check_type=True):
Expand All @@ -836,41 +844,46 @@ def fetch_blob(url, blob_field, name, check_size=True, check_type=True):
except (RequestException, ValidationError) as e:
logger.info(f'failed, skipping {url} : {e}')

for o in obj.as1, as1.get_object(obj.as1):
for o in obj_as1, as1.get_object(obj_as1):
for url in util.get_urls(o, 'image'):
# TODO: maybe eventually check size and type? the current
# 1MB limit feels too small though, and the AppView doesn't
# seem to validate, it's happily allowing bigger image blobs
# and different types as of 9/29/2024:
# https://github.com/snarfed/bridgy-fed/issues/1348#issuecomment-2381056468
fetch_blob(url, appview.defs['app.bsky.embed.images#image']['properties'],
name='image', check_size=False, check_type=False)
props = appview.defs['app.bsky.embed.images#image']['properties']
fetch_blob(url, props, name='image', check_size=False,
check_type=False)

for att in util.get_list(o, 'attachments'):
if isinstance(att, dict):
fetch_blob(att.get('stream', {}).get('url'),
appview.defs['app.bsky.embed.video']['properties'],
props = appview.defs['app.bsky.embed.video']['properties']
fetch_blob(att.get('stream', {}).get('url'), props,
name='video', check_size=True, check_type=True)
for url in util.get_urls(att, 'image'):
props = appview.defs['app.bsky.embed.external#external']['properties']
fetch_blob(url, props, name='thumb',
check_size=False, check_type=False)

inner_obj = as1.get_object(obj.as1) or obj.as1
inner_obj = as1.get_object(obj_as1) or obj_as1
orig_url = as1.get_url(inner_obj) or inner_obj.get('id')

# convert! using our records in the datastore and fetching code instead
# of granary's
client = DatastoreClient(f'https://{os.environ["APPVIEW_HOST"]}')
as_embed = obj.atom or obj.rss
try:
ret = bluesky.from_as1(cls.translate_ids(obj.as1), blobs=blobs,
ret = bluesky.from_as1(cls.translate_ids(obj_as1), blobs=blobs,
client=client, original_fields_prefix='bridgy',
as_embed=as_embed, first_link_embed=True)
as_embed=as_embed)
except (ValueError, RequestException):
logger.info(f"Couldn't convert to ATProto", exc_info=True)
return {}

if from_proto != ATProto:
if ret['$type'] == 'app.bsky.actor.profile':
# populated by Protocol.convert
if orig_summary := obj.as1.get('bridgyOriginalSummary'):
if orig_summary := obj_as1.get('bridgyOriginalSummary'):
ret['bridgyOriginalDescription'] = orig_summary
else:
# don't use granary's since it will include source links
Expand Down
54 changes: 54 additions & 0 deletions tests/test_atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -1475,6 +1475,60 @@ def test_send_note_existing_repo(self, mock_create_task):

mock_create_task.assert_called() # atproto-commit

@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
@patch('requests.get', side_effect=[
requests_response(f"""\
<html>
<head>
<title>A poast</title>
<meta property="og:image" content="http://pi/c" />
<meta property="og:title" content="Titull" />
<meta property="og:description" content="Descrypshun" />
</head>
</html>""", url='http://orig/inal'),
requests_response('blob contents', content_type='image/png'),
])
def test_send_note_first_link_to_attachment(self, _, __):
user = self.make_user_and_repo()

obj = Object(id='fake:post', source_protocol='fake', our_as1={
**NOTE_AS,
'content': 'My <a href="http://orig/inal">original</a> post',
})
self.assertTrue(ATProto.send(obj, 'https://bsky.brid.gy'))

# check repo, record
did = user.key.get().get_copy(ATProto)
repo = self.storage.load_repo(did)
last_tid = arroba.util.int_to_tid(arroba.util._tid_ts_last)
self.assertEqual({
**NOTE_BSKY,
'bridgyOriginalText': 'My <a href="http://orig/inal">original</a> post',
'embed': {
'$type': 'app.bsky.embed.external',
'external': {
'$type': 'app.bsky.embed.external#external',
'description': 'Descrypshun',
'title': 'Titull',
'uri': 'http://orig/inal',
'thumb': {
'$type': 'blob',
'mimeType': 'image/png',
'ref': BLOB_CID,
'size': 13,
},
},
},
'facets': [{
'$type': 'app.bsky.richtext.facet',
'index': {'byteStart': 3, 'byteEnd': 11},
'features': [{
'$type': 'app.bsky.richtext.facet#link',
'uri': 'http://orig/inal',
}],
}],
}, repo.get_record('app.bsky.feed.post', last_tid))

@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
def test_send_update_note(self, mock_create_task):
self.test_send_note_existing_repo()
Expand Down

0 comments on commit 547e063

Please sign in to comment.