···
self.client.login(did, util.get_or_envvar(settings, 'app-password'))
self.bsky = Bluesky(self.client)
49
+
def _find_parent(self, parent_id: str):
50
+
login = self.client.me
52
+
raise Exception("Client not logged in!")
54
+
reply_data = database.find_post(self.db, parent_id, self.input.user_id, self.input.service)
55
+
assert reply_data, "reply_data requested, but doesn't exist in db (should've been skipped bt firehose)"
57
+
reply_mappings = [json.loads(data[0]) for data in database.find_mappings(self.db, reply_data['id'], SERVICE, login.did)]
58
+
if not reply_mappings:
59
+
LOGGER.error("Failed to find mappings for a post in the db!")
62
+
reply_record = models.AppBskyFeedPost.CreateRecordResponse(uri=str(reply_mappings[-1]['uri']), cid=str(reply_mappings[-1]['cid']))
63
+
root_record = models.AppBskyFeedPost.CreateRecordResponse(uri=str(reply_mappings[0]['uri']), cid=str(reply_mappings[0]['cid']))
64
+
if reply_data['root_id']:
65
+
root_data = database.find_post_by_id(self.db, reply_data['root_id'])
66
+
assert root_data, "root_data requested but doesn't exist in db"
68
+
root_mappings = [json.loads(data[0]) for data in database.find_mappings(self.db, reply_data['root_id'], SERVICE, login.did)]
69
+
if not root_mappings:
70
+
LOGGER.error("Failed to find mappings for a post in the db!")
72
+
root_record = models.AppBskyFeedPost.CreateRecordResponse(uri=str(root_mappings[0]['uri']), cid=str(root_mappings[0]['cid']))
75
+
models.create_strong_ref(root_record),
76
+
models.create_strong_ref(reply_record),
77
+
reply_data['root_id'],
81
+
def _split_attachments(self, attachments: list[cross.MediaAttachment]):
82
+
sup_media: list[cross.MediaAttachment] = []
83
+
unsup_media: list[cross.MediaAttachment] = []
85
+
for attachment in attachments:
86
+
attachment_type = attachment.get_type()
87
+
if not attachment_type:
90
+
if attachment_type in {'video', 'image'}: # TODO convert gifs to videos
91
+
sup_media.append(attachment)
93
+
unsup_media.append(attachment)
95
+
return (sup_media, unsup_media)
97
+
def _split_media_per_post(
99
+
tokens: list[client_utils.TextBuilder],
100
+
media: list[cross.MediaAttachment]):
102
+
posts: list[dict] = [{"tokens": tokens, "attachments": []} for tokens in tokens]
103
+
available_indices: list[int] = list(range(len(posts)))
105
+
current_image_post_idx: int | None = None
107
+
def make_blank_post() -> dict:
109
+
"tokens": [client_utils.TextBuilder().text('')],
113
+
def pop_next_empty_index() -> int:
114
+
if available_indices:
115
+
return available_indices.pop(0)
117
+
new_idx = len(posts)
118
+
posts.append(make_blank_post())
122
+
if att.get_type() == 'video':
123
+
current_image_post_idx = None
124
+
idx = pop_next_empty_index()
125
+
posts[idx]["attachments"].append(att)
126
+
elif att.get_type() == 'image':
128
+
current_image_post_idx is not None
129
+
and len(posts[current_image_post_idx]["attachments"]) < 4
131
+
posts[current_image_post_idx]["attachments"].append(att)
133
+
idx = pop_next_empty_index()
134
+
posts[idx]["attachments"].append(att)
135
+
current_image_post_idx = idx
137
+
result: list[tuple[client_utils.TextBuilder, list[cross.MediaAttachment]]] = []
139
+
result.append((p["tokens"], p["attachments"]))
def accept_post(self, post: cross.Post):
···
63
-
# parentless posts are skipped by the input
64
-
reply_data = database.find_post(self.db, parent_id, self.input.user_id, self.input.service)
65
-
assert reply_data, "reply_data requested, but doesn't exist in db (should've been skipped bt firehose)"
67
-
reply_mappings = [json.loads(data[0]) for data in database.find_mappings(self.db, reply_data['id'], SERVICE, login.did)]
68
-
if not reply_mappings:
69
-
LOGGER.error("Failed to find mappings for a post in the db!")
156
+
parents = self._find_parent(parent_id)
72
-
reply_record = models.AppBskyFeedPost.CreateRecordResponse(uri=str(reply_mappings[-1]['uri']), cid=str(reply_mappings[-1]['cid']))
73
-
root_record = models.AppBskyFeedPost.CreateRecordResponse(uri=str(reply_mappings[0]['uri']), cid=str(reply_mappings[0]['cid']))
74
-
if reply_data['root_id']:
75
-
root_data = database.find_post_by_id(self.db, reply_data['root_id'])
76
-
assert root_data, "root_data requested but doesn't exist in db"
78
-
root_mappings = [json.loads(data[0]) for data in database.find_mappings(self.db, reply_data['root_id'], SERVICE, login.did)]
79
-
if not root_mappings:
80
-
LOGGER.error("Failed to find mappings for a post in the db!")
82
-
root_record = models.AppBskyFeedPost.CreateRecordResponse(uri=str(root_mappings[0]['uri']), cid=str(root_mappings[0]['cid']))
84
-
new_root_id = reply_data['root_id']
85
-
new_parent_id = reply_data['id']
87
-
root_ref = models.create_strong_ref(root_record)
88
-
reply_ref = models.create_strong_ref(reply_record)
159
+
root_ref, reply_ref, new_root_id, new_parent_id = parents
tokens = post.get_tokens()
···
unique_labels.add('graphic-media')
labels = models.ComAtprotoLabelDefs.SelfLabels(values=[models.ComAtprotoLabelDefs.SelfLabel(val=label) for label in unique_labels])
180
+
sup_media, unsup_media = self._split_attachments(post.get_attachments())
184
+
tokens.append(cross.TextToken('\n'))
185
+
for i, attachment in enumerate(unsup_media):
186
+
tokens.append(cross.LinkToken(
187
+
attachment.get_url(),
188
+
f"[{media_util.get_filename_from_url(attachment.get_url())}]"
190
+
tokens.append(cross.TextToken(' '))
split_tokens: list[list[cross.Token]] = util.split_tokens(post.get_tokens(), 300)
post_text: list[client_utils.TextBuilder] = []
···
post_text = [client_utils.TextBuilder().text('')]
208
+
# download media first. increased RAM usage, but more reliable
209
+
for m in sup_media:
211
+
if m.get_type() == 'image':
212
+
image_bytes = media_util.download_blob(m.get_url(), max_bytes=2_000_000)
213
+
if not image_bytes:
214
+
LOGGER.error("Skipping post_id '%s', failed to download attachment! File too large?", post.get_id())
216
+
m.bytes = image_bytes
217
+
elif m.get_type() == 'video':
218
+
video_bytes = media_util.download_blob(m.get_url(), max_bytes=100_000_000)
219
+
if not video_bytes:
220
+
LOGGER.error("Skipping post_id '%s', failed to download attachment! File too large?", post.get_id())
222
+
m.bytes = video_bytes
created_records: list[models.AppBskyFeedPost.CreateRecordResponse] = []
125
-
attachments = post.get_attachments()
126
-
if not attachments:
127
-
for text in post_text:
225
+
baked_media = self._split_media_per_post(post_text, sup_media)
227
+
for text, attachments in baked_media:
228
+
if not attachments:
if reply_ref and root_ref:
new_post = self.bsky.send_post(text, reply_to=models.AppBskyFeedPost.ReplyRef(
···
self.bsky.create_gates(self.options, new_post.uri)
reply_ref = models.create_strong_ref(new_post)
created_records.append(new_post)
140
-
elif len(attachments) <= 4:
141
-
if len(attachments) == 1 and attachments[0].get_type() == 'video':
142
-
video_data = attachments[0]
144
-
video_io = media_util.download_blob(video_data.get_url(), max_bytes=100_000_000)
146
-
LOGGER.error("Skipping post_id '%s', failed to download attachment! File too large?", post.get_id())
149
-
metadata = video_data.create_meta(video_io)
150
-
if metadata.get_duration() > 180:
151
-
LOGGER.info("Skipping post_id '%s', video attachment too long!", post.get_id())
154
-
aspect_ratio = models.AppBskyEmbedDefs.AspectRatio(
155
-
width=metadata.get_width(),
156
-
height=metadata.get_height()
159
-
new_post = self.bsky.send_video(
162
-
video_aspect_ratio=aspect_ratio,
163
-
video_alt=video_data.get_alt(),
164
-
reply_to= models.AppBskyFeedPost.ReplyRef(
167
-
) if root_ref and reply_ref else None,
171
-
root_ref = models.create_strong_ref(new_post)
173
-
self.bsky.create_gates(self.options, new_post.uri)
174
-
reply_ref = models.create_strong_ref(new_post)
176
-
for attachment in attachments:
177
-
if attachment.get_type() != 'image':
178
-
LOGGER.info("Skipping post_id '%s'. Attachment type mismatch. got: '%s' expected: 'image'", post.get_id(), attachment.get_type())
181
-
images: list[bytes] = []
182
-
image_alts: list[str] = []
183
-
image_aspect_ratios: list[models.AppBskyEmbedDefs.AspectRatio] = []
184
-
for attachment in attachments:
185
-
image_io = media_util.download_blob(attachment.get_url(), max_bytes=2_000_000)
242
+
# if a single post is an image - everything else is an image
243
+
if attachments[0].get_type() == 'image':
244
+
images: list[bytes] = []
245
+
image_alts: list[str] = []
246
+
image_aspect_ratios: list[models.AppBskyEmbedDefs.AspectRatio] = []
248
+
for attachment in attachments:
249
+
assert attachment.bytes
250
+
image_io = media_util.compress_image(attachment.bytes, quality=100)
251
+
metadata = attachment.create_meta(image_io)
253
+
if len(image_io) > 1_000_000:
254
+
LOGGER.info("Compressing %s...", attachment.get_url())
256
+
images.append(image_io)
257
+
image_alts.append(attachment.get_alt())
258
+
image_aspect_ratios.append(models.AppBskyEmbedDefs.AspectRatio(
259
+
width=metadata.get_width(),
260
+
height=metadata.get_height()
263
+
new_post = self.bsky.send_images(
266
+
image_alts=image_alts,
267
+
image_aspect_ratios=image_aspect_ratios,
268
+
reply_to= models.AppBskyFeedPost.ReplyRef(
271
+
) if root_ref and reply_ref else None,
275
+
root_ref = models.create_strong_ref(new_post)
277
+
self.bsky.create_gates(self.options, new_post.uri)
278
+
reply_ref = models.create_strong_ref(new_post)
279
+
created_records.append(new_post)
280
+
else: # video is guarantedd to be one
281
+
video_data = attachments[0]
282
+
assert attachment.bytes
284
+
video_io = attachment.bytes
LOGGER.error("Skipping post_id '%s', failed to download attachment! File too large?", post.get_id())
189
-
LOGGER.info("Converting %s to .webp...", attachment.get_url())
190
-
image_io = media_util.compress_image(image_io, quality=100)
191
-
metadata = attachment.create_meta(image_io)
193
-
if len(image_io) > 1_000_000:
194
-
LOGGER.info("Compressing %s...", attachment.get_url())
196
-
images.append(image_io)
197
-
image_alts.append(attachment.get_alt())
198
-
image_aspect_ratios.append(models.AppBskyEmbedDefs.AspectRatio(
289
+
metadata = video_data.create_meta(video_io)
290
+
if metadata.get_duration() > 180:
291
+
LOGGER.info("Skipping post_id '%s', video attachment too long!", post.get_id())
294
+
aspect_ratio = models.AppBskyEmbedDefs.AspectRatio(
width=metadata.get_width(),
height=metadata.get_height()
203
-
new_post = self.bsky.send_images(
206
-
image_alts=image_alts,
207
-
image_aspect_ratios=image_aspect_ratios,
208
-
reply_to= models.AppBskyFeedPost.ReplyRef(
211
-
) if root_ref and reply_ref else None,
215
-
root_ref = models.create_strong_ref(new_post)
299
+
new_post = self.bsky.send_video(
302
+
video_aspect_ratio=aspect_ratio,
303
+
video_alt=video_data.get_alt(),
304
+
reply_to= models.AppBskyFeedPost.ReplyRef(
307
+
) if root_ref and reply_ref else None,
311
+
root_ref = models.create_strong_ref(new_post)
217
-
self.bsky.create_gates(self.options, new_post.uri)
218
-
reply_ref = models.create_strong_ref(new_post)
220
-
created_records.append(new_post)
221
-
for text in post_text[1:]:
222
-
new_post = self.bsky.send_post(text, reply_to=models.AppBskyFeedPost.ReplyRef(
226
-
self.bsky.create_gates(self.options, new_post.uri)
228
-
reply_ref = models.create_strong_ref(new_post)
229
-
created_records.append(new_post)
231
-
LOGGER.info("Skipping post_id '%s', too many attachments!", post.get_id())
234
-
if not created_records:
235
-
LOGGER.info("Skipped post_id '%s', for some reason...")
313
+
self.bsky.create_gates(self.options, new_post.uri)
314
+
reply_ref = models.create_strong_ref(new_post)
315
+
created_records.append(new_post)
db_post = database.find_post(self.db, post.get_id(), self.input.user_id, self.input.service)
assert db_post, "ghghghhhhh"