···4040 if not did:
4141 if not handle:
4242 raise KeyError("No did: or atproto handle provided!")
4343- self.log.info("Resolving ATP identity for %s...", handle)
4343+ self.log.info("Resolving ATP identity for '%s'...", handle)
4444 identity = resolve_identity(handle, self._store)
4545 self.did = identity.did
46464747 if not pds:
4848- self.log.info("Resolving PDS for %s...", self.did)
4848+ self.log.info("Resolving PDS for '%s'...", self.did)
4949 identity = resolve_identity(self.did, self._store)
5050 self.pds = identity.pds
5151
+24-26
bluesky/input.py
···5252 post_uri = cast(str, record["$xpost.strongRef"]["uri"])
5353 post_cid = cast(str, record["$xpost.strongRef"]["cid"])
54545555- self.log.info("Processing new post: %s", post_uri)
5656-5755 if self._is_post_crossposted(self.url, self.did, post_uri):
5856 self.log.info(
5959- "Skipping %s, already crossposted",
5757+ "Skipping '%s': already crossposted",
6058 post_uri,
6159 )
6260 return
···6664 )
6765 parent = None
6866 if parent_uri:
6767+ did, _, _ = AtUri.record_uri(parent_uri)
6868+ if did != self.did:
6969+ self.log.info("Skipping '%s': reply to other user..", post_uri)
7070+ return
7171+6972 parent = self._get_post(self.url, self.did, parent_uri)
7073 if not parent:
7174 self.log.info(
7272- "Skipping %s, parent %s not found in db", post_uri, parent_uri
7575+ "Skipping '%s': parent '%s' not found in db", post_uri, parent_uri
7376 )
7477 return
7578···101104 )
102105 did, collection, _ = AtUri.record_uri(rcrd["uri"])
103106 if collection != "app.bsky.feed.post":
104104- return f"Unhandled record collection {collection}"
107107+ return f"unhandled record collection '{collection}'"
105108 if did != self.did:
106109 return ""
107110108111 rquote = self._get_post(self.url, did, rcrd["uri"])
109112 if not rquote:
110110- return f"Quote {rcrd['uri']} not found in the db"
113113+ return f"quote '{rcrd['uri']}' not found in db"
111114 post.attachments.put(
112115 QuoteAttachment(quoted_id=rcrd["uri"], quoted_user=did)
113116 )
···124127 url = f"{self.pds}/xrpc/com.atproto.sync.getBlob?did={self.did}&cid={blob_cid}"
125128 blob_urls.append((url, blob_cid, embed.get("alt")))
126129 case _:
127127- self.log.warning(f"Unhandled embed type {embed['$type']}")
130130+ self.log.warning(f"unhandled embed type '{embed['$type']}'")
128131 return None
129132130133 if embed:
131134 fexit = handle_embeds(embed)
132135 if fexit is not None:
133133- self.log.info("Skipping %s! %s", post_uri, fexit)
136136+ self.log.info("Skipping '%s': %s", post_uri, fexit)
134137 return
135138136139 if blob_urls:
137140 blobs: list[Blob] = []
138141 for url, cid, alt in blob_urls:
139139- self.log.info("Downloading %s...", cid)
142142+ self.log.info("Downloading '%s'...", cid)
140143 blob: Blob | None = download_blob(url, alt, client=self.http)
141144 if not blob:
142145 self.log.error(
143143- "Skipping %s! Failed to download blob %s.", post_uri, cid
146146+ "Skipping '%s': failed to download blob '%s'", post_uri, cid
144147 )
145148 return
146149 blobs.append(blob)
···178181 }
179182 )
180183181181- self.log.info("Post stored in DB: %s", post_uri)
182182-184184+ self.log.info("Crossposting: '%s'", post_uri)
183185 for out in self.outputs:
184186 self.submitter(lambda: out.accept_post(post))
185187···187189 post_uri = cast(str, record["$xpost.strongRef"]["uri"])
188190 post_cid = cast(str, record["$xpost.strongRef"]["cid"])
189191190190- self.log.info("Processing repost: %s", post_uri)
191191-192192 reposted_uri = cast(str, record["subject"]["uri"])
193193 reposted = self._get_post(self.url, self.did, reposted_uri)
194194 if not reposted:
195195 self.log.info(
196196- "Skipping repost '%s' as reposted post '%s' was not found in the db.",
196196+ "Skipping repost '%s': reposted post '%s' not found in db",
197197 post_uri,
198198 reposted_uri,
199199 )
···209209 }
210210 )
211211212212- self.log.info("Repost stored in DB: %s", post_uri)
213213-214212 repost_ref = PostRef(id=post_uri, author=self.did, service=self.url)
215213 reposted_ref = PostRef(id=reposted_uri, author=self.did, service=self.url)
214214+215215+ self.log.info("Crossposting: '%s'", post_uri)
216216 for out in self.outputs:
217217 self.submitter(lambda: out.accept_repost(repost_ref, reposted_ref))
218218219219 def _on_delete_post(self, post_id: str, repost: bool):
220220- self.log.info("Processing delete for %s (repost: %s)...", post_id, repost)
221220 post = self._get_post(self.url, self.did, post_id)
222221 if not post:
223223- self.log.warning("Post not found in DB: %s", post_id)
222222+ self.log.warning("Skipping delete '%s': post not found in db", post_id)
224223 return
225224226225 post_ref = PostRef(id=post_id, author=self.did, service=self.url)
227226 if repost:
228228- self.log.info("Deleting repost: %s", post_id)
227227+ self.log.info("Deleting repost: '%s'", post_id)
229228 for output in self.outputs:
230229 self.submitter(lambda: output.delete_repost(post_ref))
231230 else:
232232- self.log.info("Deleting post: %s", post_id)
231231+ self.log.info("Deleting post: '%s'", post_id)
233232 for output in self.outputs:
234233 self.submitter(lambda: output.delete_post(post_ref))
235234 self.submitter(lambda: self._delete_post_by_id(post["id"]))
236236- self.log.info("Delete successful for %s", post_id)
237235238236239237class BlueskyJetstreamInputService(BlueskyBaseInputService):
···304302 close_timeout=5,
305303 ):
306304 try:
307307- self.log.info("Listening to %s...", env.JETSTREAM_URL)
305305+ self.log.info("Listening to '%s'...", env.JETSTREAM_URL)
308306309307 async def listen_for_messages():
310308 async for msg in ws:
···315313 _ = await asyncio.gather(listen)
316314 except websockets.ConnectionClosedError as e:
317315 self.log.error(e, stack_info=True, exc_info=True)
318318- self.log.info("Reconnecting to %s...", env.JETSTREAM_URL)
316316+ self.log.info("Reconnecting to '%s'...", env.JETSTREAM_URL)
319317 continue
320318 except TimeoutError as e:
321321- self.log.error("Connection timeout: %s", e)
322322- self.log.info("Reconnecting to %s...", env.JETSTREAM_URL)
319319+ self.log.error("Connection timeout: '%s'", e)
320320+ self.log.info("Reconnecting to '%s'...", env.JETSTREAM_URL)
323321 continue
+33-68
bluesky/output.py
···5656 validate_and_transform(data)
57575858 if "password" not in data:
5959- raise KeyError("password is required for bluesky")
5959+ raise KeyError("'password' is required for bluesky")
60606161 if "quote_gate" in data:
6262 data["quote_gate"] = bool(data["quote_gate"])
···9696 self.options.password,
9797 )
9898 self.options.password = ""
9999- self.log.info("Logged in as %s", self.did)
9999+ self.log.info("Logged in as '%s'", self.did)
100100101101 @override
102102 def get_identity_options(self) -> tuple[str | None, str | None, str | None]:
···184184185185 @override
186186 def accept_post(self, post: Post):
187187- self.log.info(
188188- "Accepting post %s (author: %s, service: %s)...",
189189- post.id,
190190- post.author,
191191- post.service,
192192- )
187187+ db_post = self._get_post(post.service, post.author, post.id)
188188+ if not db_post:
189189+ self.log.error("Skipping '%s': post not found in db")
190190+ return
191191+193192 reply_to: ReplyRef | None = None
194193 new_root_id: int | None = None
195194 new_parent_id: int | None = None
···197196 if post.parent_id:
198197 parent = self._get_post(post.service, post.author, post.parent_id)
199198 if not parent:
200200- self.log.error("Parent post not found in DB: %s", post.parent_id)
199199+ self.log.error(
200200+ "Skipping '%s': parent post not found in db", post.parent_id
201201+ )
201202 return
202203203204 thread = self._find_mapped_thread(
···209210 )
210211 if not thread:
211212 self.log.error(
212212- "Failed to find thread tuple in the database for parent: %s",
213213+ "Skipping '%s': parent thread tuple not found in db",
213214 post.parent_id,
214215 )
215216 return
···220221 reply_post = self._get_post(self.url, self.did, reply_uri)
221222222223 if not root_post or not reply_post:
223223- self.log.error("Failed to fetch parent posts from database!")
224224+ self.log.error("Skipping '%s': failed to fetch parent posts from db")
224225 return
225226226227 try:
···233234 json.loads(reply_cid_data).get("cid", "") if reply_cid_data else ""
234235 )
235236 except (json.JSONDecodeError, AttributeError, KeyError):
236236- self.log.error("Failed to parse CID from database!")
237237+ self.log.error("Skipping '%s': failed to parse CID from db")
237238 return
238239239240 root_ref = StrongRef(uri=root_uri, cid=root_cid)
···309310 quoted_uri: str | None = None
310311 if quote_attachment:
311312 if quote_attachment.quoted_user != post.author:
312312- self.log.info("Quoted other user, skipping quote!")
313313+ self.log.info("Skipping '%s': quoted other user")
313314 return
314315315316 quoted_post = self._get_post(
316317 post.service, post.author, quote_attachment.quoted_id
317318 )
318319 if not quoted_post:
319319- self.log.error("Failed to find quoted post in the database!")
320320+ self.log.error("Skipping '%s': quoted post not found in db!")
320321 else:
321322 quoted_mappings = self._get_mappings(
322323 quoted_post["id"], self.url, self.did
323324 )
324325 if not quoted_mappings:
325325- self.log.error("Failed to find mappings for quoted post!")
326326+ self.log.error(
327327+ "Skipping '%s': failed to find mappings for quoted post"
328328+ )
326329 else:
327330 bluesky_quoted_post = self._get_post(
328331 self.url, self.did, quoted_mappings[0]["identifier"]
329332 )
330333 if not bluesky_quoted_post:
331331- self.log.error("Failed to find Bluesky quoted post!")
334334+ self.log.error(
335335+ "Skipping '%s': Failed to find Bluesky quoted post!"
336336+ )
332337 else:
333338 quoted_cid_data = bluesky_quoted_post["extra_data"]
334339 quoted_cid = (
···342347 token_blocks = splitter.split(tokens)
343348344349 if token_blocks is None:
345345- self.log.error(
346346- "Skipping '%s' as it contains links/tags that are too long!", post.id
347347- )
350350+ self.log.error("Skipping '%s': links/tags are too long", post.id)
348351 return
349352350353 for blob in supported_media:
351354 if blob.mime.startswith("image/") and len(blob.io) > 2_000_000:
352355 self.log.error(
353353- "Skipping post '%s', image too large!",
356356+ "Skipping '%s': image too large",
354357 post.id,
355358 )
356359 return
357360 if blob.mime.startswith("video/"):
358361 if blob.mime != "video/mp4" and not self.options.encode_videos:
359362 self.log.info(
360360- "Video is not mp4, but encoding is disabled. Skipping '%s'...",
363363+ "Skipping '%s': video is not mp4, but encoding is disabled",
361364 post.id,
362365 )
363366 return
364367 if len(blob.io) > 100_000_000:
365368 self.log.error(
366366- "Skipping post '%s', video too large!",
369369+ "Skipping '%s': video too large",
367370 post.id,
368371 )
369372 return
···378381 result = tokens_to_richtext(block)
379382 if result is None:
380383 self.log.error(
381381- "Skipping '%s' as it contains invalid rich text types!",
384384+ "Skipping '%s': invalid rich text types",
382385 post.id,
383386 )
384387 return
···479482480483 if duration and duration > 180:
481484 self.log.info(
482482- "Skipping post '%s', video too long (%.1f > 180s)!",
485485+ "Skipping '%s': video too long (%.1f > 180s)",
483486 post.id,
484487 duration,
485488 )
···509512 self.options.quote_gate,
510513 )
511514512512- db_post = self._get_post(post.service, post.author, post.id)
513513- if not db_post:
514514- self.log.error("Post not found in database!")
515515- return
516516-517515 if new_root_id is None or new_parent_id is None:
518516 self._insert_post(
519517 {
···562560563561 @override
564562 def delete_post(self, post: PostRef):
565565- self.log.info(
566566- "Deleting post %s (author: %s, service: %s)...",
567567- post.id,
568568- post.author,
569569- post.service,
570570- )
571563 db_post = self._get_post(post.service, post.author, post.id)
572564 if not db_post:
573573- self.log.warning(
574574- "Post not found in DB: %s (author: %s, service: %s)",
575575- post.id,
576576- post.author,
577577- post.service,
578578- )
565565+ self.log.warning("Skipping delete '%s': post not found in db", post.id)
579566 return
580567581568 mappings = self._get_mappings(db_post["id"], self.url, self.did)
582569 for mapping in mappings[::-1]:
583583- self.log.info("Deleting '%s'...", mapping["identifier"])
584570 self._client.delete_post(mapping["identifier"])
585571 self._delete_post_by_id(mapping["id"])
586586- self.log.info("Post deleted successfully: %s", post.id)
572572+ self.log.info("Deleted '%s'", post.id)
587573588574 @override
589575 def accept_repost(self, repost: PostRef, reposted: PostRef):
590590- self.log.info(
591591- "Accepting repost %s of %s (author: %s, service: %s)...",
592592- repost.id,
593593- reposted.id,
594594- repost.author,
595595- repost.service,
596596- )
597576 db_repost = self._get_post(repost.service, repost.author, repost.id)
598577 db_reposted = self._get_post(reposted.service, reposted.author, reposted.id)
599578 if not db_repost or not db_reposted:
600600- self.log.info("Post not found in db, skipping repost..")
579579+ self.log.info("Skipping repost '%s': post not found in db")
601580 return
602581603582 mappings = self._get_mappings(db_reposted["id"], self.url, self.did)
···607586 try:
608587 cid = json.loads(mappings[0]["extra_data"])["cid"]
609588 except (json.JSONDecodeError, AttributeError, KeyError):
610610- self.log.exception("Failed to parse CID from extra_data!")
589589+ self.log.exception("Skipping '%s': failed to parse CID from extra_data")
611590 return
612591613592 response = self._client.repost(mappings[0]["identifier"], cid)
···632611633612 @override
634613 def delete_repost(self, repost: PostRef):
635635- self.log.info(
636636- "Deleting repost %s (author: %s, service: %s)...",
637637- repost.id,
638638- repost.author,
639639- repost.service,
640640- )
641614 db_repost = self._get_post(repost.service, repost.author, repost.id)
642615 if not db_repost:
643643- self.log.warning(
644644- "Repost not found in DB: %s (author: %s, service: %s)",
645645- repost.id,
646646- repost.author,
647647- repost.service,
648648- )
616616+ self.log.warning("Skipping delete '%s': repost not found in db", repost.id)
649617 return
650618651619 mappings = self._get_mappings(db_repost["id"], self.url, self.did)
652620 if mappings:
653653- self.log.info("Deleting '%s'...", mappings[0]["identifier"])
654621 self._client.delete_repost(mappings[0]["identifier"])
655622 self._delete_post_by_id(mappings[0]["id"])
656656- self.log.info("Repost deleted successfully: %s", repost.id)
657657- else:
658658- self.log.error([mappings])
623623+ self.log.info("Deleted %s", repost.id)
+3-3
main.py
···101101 read_env(settings)
102102103103 if "services" not in settings:
104104- raise KeyError("No `services` specified in settings!")
104104+ raise KeyError("No 'services' specified in settings!")
105105106106 service_pairs: list[tuple[Any, list[Any]]] = []
107107 for svc in settings["services"]:
108108 if "input" not in svc:
109109- raise KeyError("Each service must have an `input` field!")
109109+ raise KeyError("Each service must have an 'input' field!")
110110 if "outputs" not in svc:
111111- raise KeyError("Each service must have an `outputs` field!")
111111+ raise KeyError("Each service must have an 'outputs' field!")
112112113113 inp = create_input_service(db_pool, http_client, svc["input"])
114114 outs = [
+22-26
mastodon/input.py
···5656 super().__init__(options.instance, db, http)
5757 self.options: MastodonInputOptions = options
58585959- self.log.info("Verifying %s credentails...", self.url)
5959+ self.log.info("Verifying '%s' credentails...", self.url)
6060 response = self.verify_credentials()
6161 self.user_id: str = response["id"]
62626363- self.log.info("Getting %s configuration...", self.url)
6363+ self.log.info("Getting '%s' configuration...", self.url)
6464 response = self.fetch_instance_info()
6565 self.streaming_url: str = response["urls"]["streaming_api"]
6666···6969 return self.options.token
70707171 def _on_create_post(self, status: dict[str, Any]):
7272- self.log.info("Processing new post: %s", status["id"])
7373-7472 if status["account"]["id"] != self.user_id:
7573 return
76747775 if status["visibility"] not in self.options.allowed_visibility:
7876 self.log.info(
7979- "Skipping post with disallowed visibility: %s (%s)",
7777+ "Skipping '%s': disallowed visibility (%s)",
8078 status["id"],
8179 status["visibility"],
8280 )
···84828583 if self._is_post_crossposted(self.url, self.user_id, status["id"]):
8684 self.log.info(
8787- "Skipping %s, already crossposted",
8585+ "Skipping '%s': already crossposted",
8886 status["id"],
8987 )
9088 return
···9795 return
98969997 if status.get("poll"):
100100- self.log.info("Skipping '%s'! Contains a poll..", status["id"])
9898+ self.log.info("Skipping '%s': polls not supported", status["id"])
10199 return
102100103101 quote: dict[str, Any] | None = status.get("quote")
···109107 rquote = self._get_post(self.url, self.user_id, quote["id"])
110108 if not rquote:
111109 self.log.info(
112112- "Skipping %s, parent %s not found in db", status["id"], quote["id"]
110110+ "Skipping '%s': quoted post '%s' not found in db",
111111+ status["id"],
112112+ quote["id"],
113113 )
114114 return
115115···123123 parent = self._get_post(self.url, self.user_id, in_reply)
124124 if not parent:
125125 self.log.info(
126126- "Skipping %s, parent %s not found in db", status["id"], in_reply
126126+ "Skipping '%s': parent '%s' not found in db", status["id"], in_reply
127127 )
128128 return
129129 parser = StatusParser(status)
···154154155155 blobs: list[Blob] = []
156156 for media in status.get("media_attachments", []):
157157- self.log.info("Downloading %s...", media["url"])
157157+ self.log.info("Downloading '%s'...", media["url"])
158158 blob: Blob | None = download_blob(
159159 media["url"], media.get("alt"), client=self.http
160160 )
161161 if not blob:
162162 self.log.error(
163163- "Skipping %s! Failed to download media %s.",
163163+ "Skipping '%s': failed to download attachment '%s'",
164164 status["id"],
165165 media["url"],
166166 )
···189189 }
190190 )
191191192192- self.log.info("Post stored in DB: %s", status["id"])
193193-192192+ self.log.info("Crossposting: '%s'", status["id"])
194193 for out in self.outputs:
195194 self.submitter(lambda: out.accept_post(post))
196195197196 def _on_reblog(self, status: dict[str, Any], reblog: dict[str, Any]):
198198- self.log.info("Processing reblog: %s", status["id"])
199197 reposted = self._get_post(self.url, self.user_id, reblog["id"])
200198 if not reposted:
201199 self.log.info(
202202- "Skipping repost '%s' as reposted post '%s' was not found in the db.",
200200+ "Skipping repost '%s': reposted post '%s' not found in db",
203201 status["id"],
204202 reblog["id"],
205203 )
···213211 "reposted": reposted["id"],
214212 }
215213 )
216216-217217- self.log.info("Reblog stored in DB: %s", status["id"])
218214219215 repost_ref = PostRef(id=status["id"], author=self.user_id, service=self.url)
220216 reposted_ref = PostRef(id=reblog["id"], author=self.user_id, service=self.url)
217217+218218+ self.log.info("Crossposting: '%s'", status["id"])
221219 for out in self.outputs:
222220 self.submitter(lambda: out.accept_repost(repost_ref, reposted_ref))
223221224222 def _on_delete_post(self, status_id: str):
225225- self.log.info("Processing delete for %s...", status_id)
226223 post = self._get_post(self.url, self.user_id, status_id)
227224 if not post:
228228- self.log.warning("Post not found in DB: %s", status_id)
225225+ self.log.warning("Skipping delete '%s': post not found in db", status_id)
229226 return
230227231228 post_ref = PostRef(id=status_id, author=self.user_id, service=self.url)
232229 if post["reposted"]:
233233- self.log.info("Deleting repost: %s", status_id)
230230+ self.log.info("Deleting repost: '%s'", status_id)
234231 for output in self.outputs:
235232 self.submitter(lambda: output.delete_repost(post_ref))
236233 else:
237237- self.log.info("Deleting post: %s", status_id)
234234+ self.log.info("Deleting post: '%s'", status_id)
238235 for output in self.outputs:
239236 self.submitter(lambda: output.delete_post(post_ref))
240237 self.submitter(lambda: self._delete_post_by_id(post["id"]))
241241- self.log.info("Delete processed successfully for %s", status_id)
242238243239 def _accept_msg(self, msg: websockets.Data) -> None:
244240 data: dict[str, Any] = cast(dict[str, Any], json.loads(msg))
···262258 close_timeout=5,
263259 ):
264260 try:
265265- self.log.info("Listening to %s...", self.streaming_url)
261261+ self.log.info("Listening to '%s'...", self.streaming_url)
266262267263 async def listen_for_messages():
268264 async for msg in ws:
···273269 _ = await asyncio.gather(listen)
274270 except websockets.ConnectionClosedError as e:
275271 self.log.error(e, stack_info=True, exc_info=True)
276276- self.log.info("Reconnecting to %s...", self.streaming_url)
272272+ self.log.info("Reconnecting to '%s'...", self.streaming_url)
277273 continue
278274 except TimeoutError as e:
279279- self.log.error("Connection timeout: %s", e)
280280- self.log.info("Reconnecting to %s...", self.streaming_url)
275275+ self.log.error("Connection timeout: '%s'", e)
276276+ self.log.info("Reconnecting to '%s'...", self.streaming_url)
281277 continue
+37-93
mastodon/output.py
···3939 "visibility" in data
4040 and data["visibility"] not in ALLOWED_POSTING_VISIBILITY
4141 ):
4242- raise ValueError(f"Invalid visibility option {data['visibility']}!")
4242+ raise ValueError(f"Invalid visibility option '{data['visibility']}'!")
43434444 return MastodonOutputOptions(**data)
4545···5757 super().__init__(options.instance, db, http)
5858 self.options: MastodonOutputOptions = options
59596060- self.log.info("Verifying %s credentails...", self.url)
6060+ self.log.info("Verifying '%s' credentails...", self.url)
6161 response = self.verify_credentials()
6262 self.user_id: str = response["id"]
63636464- self.log.info("Getting %s configuration...", self.url)
6464+ self.log.info("Getting '%s' configuration...", self.url)
6565 response = self.fetch_instance_info()
6666 self.instance_info: InstanceInfo = InstanceInfo.from_api(response)
6767···209209210210 if response.status_code == 200:
211211 self.log.info(
212212- "Uploaded %s! (%s)", blob.name or "unknown", response.json()["id"]
212212+ "Uploaded '%s'! (%s)", blob.name or "unknown", response.json()["id"]
213213 )
214214 uploads.append(
215215 MediaUploadResult(id=response.json()["id"], processed=True)
216216 )
217217 elif response.status_code == 202:
218218- self.log.info("Waiting for %s to process!", blob.name or "unknown")
218218+ self.log.info("Waiting for '%s' to process...", blob.name or "unknown")
219219 uploads.append(
220220 MediaUploadResult(id=response.json()["id"], processed=False)
221221 )
222222 else:
223223 self.log.error(
224224- "Failed to upload %s! %s",
224224+ "Failed to upload '%s'! %s",
225225 blob.name or "unknown",
226226 response.text,
227227 )
···248248249249 @override
250250 def accept_post(self, post: Post):
251251- self.log.info(
252252- "Accepting post %s (author: %s, service: %s)...",
253253- post.id,
254254- post.author,
255255- post.service,
256256- )
251251+ db_post = self._get_post(post.service, post.author, post.id)
252252+ if not db_post:
253253+ self.log.error("Skipping '%s': post not found in db")
254254+ return
255255+257256 new_root_id: int | None = None
258257 new_parent_id: int | None = None
259258···263262 post.parent_id, post.service, post.author, self.url, self.user_id
264263 )
265264 if not thread:
266266- self.log.error("Failed to find thread tuple in the database!")
265265+ self.log.error("Skipping '%s': parent thread tuple not found in db")
267266 return
268267 _, reply_ref, new_root_id, new_parent_id = thread
269268···271270 quote = post.attachments.get(QuoteAttachment)
272271 if quote:
273272 if quote.quoted_user != post.author:
274274- self.log.info("Quoted other user, skipping!")
273273+ self.log.info("Skipping '%s': quote of other user")
275274 return
276275277276 quoted_post = self._get_post(post.service, post.author, quote.quoted_id)
278277 if not quoted_post:
279279- self.log.error("Failed to find quoted post in the database!")
278278+ self.log.error("Skipping '%s': quoted post not found in db")
280279 return
281280282281 quoted_mappings = self._get_mappings(
283282 quoted_post["id"], self.url, self.user_id
284283 )
285284 if not quoted_mappings:
286286- self.log.error("Failed to find mappings for quoted post!")
285285+ self.log.error(
286286+ "Skipping '%s': mappings for quoted post not found in db"
287287+ )
287288 return
288289289290 quoted_status_id = quoted_mappings[-1]["identifier"]
···315316316317 raw_statuses = self._split_tokens_and_media(post_tokens, media_blobs)
317318 if not raw_statuses:
318318- self.log.error("Failed to split post into statuses!")
319319+ self.log.error("Skipping '%s': couldn't split post into statuses")
319320 return
320321321322 baked_statuses: list[tuple[str, list[str] | None]] = []
···324325 if raw_media:
325326 media_ids = self._upload_media(raw_media)
326327 if not media_ids:
327327- self.log.error("Failed to upload attachments!")
328328+ self.log.error("Skipping '%s': failed to upload attachments")
328329 return
329330 baked_statuses.append((status_text, media_ids))
330331···360361 },
361362 json=payload,
362363 )
363363-364364- if response.status_code != 200:
365365- self.log.error(
366366- "Failed to post status! %s - %s",
367367- response.status_code,
368368- response.text,
369369- )
370370- response.raise_for_status()
364364+ response.raise_for_status()
371365372366 status_id = response.json()["id"]
373373- self.log.info("Created new status %s!", status_id)
374367 created_statuses.append(status_id)
375368376369 if i == 0:
377370 reply_ref = status_id
378371379379- db_post = self._get_post(post.service, post.author, post.id)
380380- if not db_post:
381381- self.log.error("Post not found in database!")
382382- return
383383-384372 if new_root_id is None or new_parent_id is None:
385373 self._insert_post(
386374 {
···425413426414 @override
427415 def delete_post(self, post: PostRef):
428428- self.log.info(
429429- "Deleting post %s (author: %s, service: %s)...",
430430- post.id,
431431- post.author,
432432- post.service,
433433- )
434416 db_post = self._get_post(post.service, post.author, post.id)
435417 if not db_post:
436436- self.log.warning(
437437- "Post not found in DB: %s (author: %s, service: %s)",
438438- post.id,
439439- post.author,
440440- post.service,
441441- )
418418+ self.log.warning("Skipping delete '%s': post not found in db: %s", post.id)
442419 return
443420444421 mappings = self._get_mappings(db_post["id"], self.url, self.user_id)
445422446423 for mapping in mappings[::-1]:
447447- self.log.info("Deleting '%s'...", mapping["identifier"])
448448- self.http.delete(
424424+ response = self.http.delete(
449425 f"{self.url}/api/v1/statuses/{mapping['identifier']}",
450426 headers={"Authorization": f"Bearer {self._get_token()}"},
451427 )
428428+ response.raise_for_status()
429429+452430 self._delete_post_by_id(mapping["id"])
453453- self.log.info("Post deleted successfully: %s", post.id)
431431+ self.log.info("Deleted '%s'", mapping["identifier"])
454432455433 @override
456434 def accept_repost(self, repost: PostRef, reposted: PostRef):
457457- self.log.info(
458458- "Accepting repost %s of %s (author: %s, service: %s)...",
459459- repost.id,
460460- reposted.id,
461461- repost.author,
462462- repost.service,
463463- )
464435 original = self._get_post(reposted.service, reposted.author, reposted.id)
465436 if not original:
466466- self.log.info("Post not found in db, skipping repost..")
437437+ self.log.info("Skipping repost '%s': reposted post not found in db")
467438 return
468439469440 mappings = self._get_mappings(original["id"], self.url, self.user_id)
470441 if not mappings:
471471- self.log.error("No mappings found for reposted post!")
442442+ self.log.error("Skipping repost '%s': no mappings found for reposted post")
472443 return
473444474445 response = self.http.post(
475446 f"{self.url}/api/v1/statuses/{mappings[0]['identifier']}/reblog",
476447 headers={"Authorization": f"Bearer {self._get_token()}"},
477448 )
478478-479479- if response.status_code != 200:
480480- self.log.error(
481481- "Failed to boost status! status_code: %s, msg: %s",
482482- response.status_code,
483483- response.content,
484484- )
485485- return
449449+ response.raise_for_status()
486450487451 self._insert_post(
488452 {
···502466503467 original_repost = self._get_post(repost.service, repost.author, repost.id)
504468 if not original_repost:
505505- self.log.error("original repost not found in DB: %s", repost.id)
469469+ self.log.error(
470470+ "Skipping repost '%s': repost not found in db: %s", repost.id
471471+ )
506472 return
507473508474 self._insert_post_mapping(original_repost["id"], inserted["id"])
···510476511477 @override
512478 def delete_repost(self, repost: PostRef):
513513- self.log.info(
514514- "Deleting repost %s (author: %s, service: %s)...",
515515- repost.id,
516516- repost.author,
517517- repost.service,
518518- )
519479 db_repost = self._get_post(repost.service, repost.author, repost.id)
520480 if not db_repost:
521521- self.log.warning(
522522- "Repost not found in DB: %s (author: %s, service: %s)",
523523- repost.id,
524524- repost.author,
525525- repost.service,
526526- )
481481+ self.log.warning("Skipping delete '%s': repost not found in db", repost.id)
527482 return
528483529484 mappings = self._get_mappings(db_repost["id"], self.url, self.user_id)
530485 rmappings = self._get_mappings(db_repost["reposted"], self.url, self.user_id)
531486532487 if not mappings:
533533- self.log.warning("No mappings found for repost %s", repost.id)
488488+ self.log.warning(
489489+ "Skipping delete '%s': no mappings found for repost", repost.id
490490+ )
534491 return
535492 if not rmappings:
536493 self.log.warning(
537537- "No mappings found for original post %s (reposted_id=%s)",
494494+ "Skipping delete '%s': no mappings found for post",
538495 repost.id,
539496 db_repost["reposted"],
540497 )
541498 return
542499543543- self.log.info(
544544- "Removing '%s' Repost of '%s'...",
545545- mappings[0]["identifier"],
546546- rmappings[0]["identifier"],
547547- )
548548-549500 response = self.http.post(
550501 f"{self.url}/api/v1/statuses/{rmappings[0]['identifier']}/unreblog",
551502 headers={"Authorization": f"Bearer {self._get_token()}"},
552503 )
553553-554554- if response.status_code != 200:
555555- self.log.error(
556556- "Failed to unreblog! status_code: %s, msg: %s",
557557- response.status_code,
558558- response.text,
559559- )
560560- return
504504+ response.raise_for_status()
561505562506 self._delete_post_by_id(mappings[0]["id"])
563507 self.log.info("Repost deleted successfully: %s", repost.id)
+21-21
misskey/input.py
···5858 super().__init__(options.instance, db, http)
5959 self.options: MisskeyInputOptions = options
60606161- self.log.info("Verifying %s credentails...", self.url)
6161+ self.log.info("Verifying '%s' credentails...", self.url)
6262 response = self.verify_credentials()
6363 self.user_id: str = response["id"]
6464···6767 return self.options.token
68686969 def _on_note(self, note: dict[str, Any]):
7070- self.log.info("Processing new note: %s", note["id"])
7171-7270 if note["userId"] != self.user_id:
7371 return
74727573 if note["visibility"] not in self.options.allowed_visibility:
7674 self.log.info(
7777- "Skipping note with disallowed visibility: %s (%s)",
7575+ "Skipping '%s': disallowed visibility (%s)",
7876 note["id"],
7977 note["visibility"],
8078 )
···82808381 if self._is_post_crossposted(self.url, self.user_id, note["id"]):
8482 self.log.info(
8585- "Skipping %s, already crossposted",
8383+ "Skipping '%s': already crossposted",
8684 note["id"],
8785 )
8886 return
89879088 if note.get("poll"):
9191- self.log.info("Skipping '%s'! Contains a poll..", note["id"])
8989+ self.log.info("Skipping '%s': polls not supported", note["id"])
9290 return
93919492 renote: dict[str, Any] | None = note.get("renote")
···103101 rrenote = self._get_post(self.url, self.user_id, renote["id"])
104102 if not rrenote:
105103 self.log.info(
106106- "Skipping %s, quote %s not found in db", note["id"], renote["id"]
104104+ "Skipping '%s': quoted post '%s' not found in db",
105105+ note["id"],
106106+ renote["id"],
107107 )
108108 return
109109110110 reply: dict[str, Any] | None = note.get("reply")
111111 if reply and reply.get("userId") != self.user_id:
112112- self.log.info("Skipping '%s'! Reply to other user..", note["id"])
112112+ self.log.info("Skipping '%s': Reply to other user..", note["id"])
113113 return
114114115115 parent = None
···117117 parent = self._get_post(self.url, self.user_id, reply["id"])
118118 if not parent:
119119 self.log.info(
120120- "Skipping %s, parent %s not found in db", note["id"], reply["id"]
120120+ "Skipping '%s': parent '%s' not found in db",
121121+ note["id"],
122122+ reply["id"],
121123 )
122124 return
123125···151153152154 blobs: list[Blob] = []
153155 for media in note.get("files", []):
154154- self.log.info("Downloading %s...", media["url"])
156156+ self.log.info("Downloading '%s'...", media["url"])
155157 blob: Blob | None = download_blob(
156158 media["url"], media.get("comment", ""), client=self.http
157159 )
158160 if not blob:
159161 self.log.error(
160160- "Skipping %s! Failed to download media %s.",
162162+ "Skipping '%s': failed to download media '%s'.",
161163 note["id"],
162164 media["url"],
163165 )
···186188 }
187189 )
188190189189- self.log.info("Note stored in DB: %s", note["id"])
190190-191191+ self.log.info("Crossposting: '%s'", note["id"])
191192 for out in self.outputs:
192193 self.submitter(lambda: out.accept_post(post))
193194194195 def _on_renote(self, note: dict[str, Any], renote: dict[str, Any]):
195195- self.log.info("Processing renote: %s", note["id"])
196196 reposted = self._get_post(self.url, self.user_id, renote["id"])
197197 if not reposted:
198198 self.log.info(
199199- "Skipping repost '%s' as reposted post '%s' was not found in the db.",
199199+ "Skipping repost '%s': reposted post '%s' not found in db",
200200 note["id"],
201201 renote["id"],
202202 )
···210210 "reposted": reposted["id"],
211211 }
212212 )
213213-214214- self.log.info("Renote stored in DB: %s", note["id"])
215213216214 repost_ref = PostRef(id=note["id"], author=self.user_id, service=self.url)
217215 reposted_ref = PostRef(id=renote["id"], author=self.user_id, service=self.url)
216216+217217+ self.log.info("Crossposting: '%s'", note["id"])
218218 for out in self.outputs:
219219 self.submitter(lambda: out.accept_repost(repost_ref, reposted_ref))
220220···250250 close_timeout=5,
251251 ):
252252 try:
253253- self.log.info("Listening to %s...", streaming)
253253+ self.log.info("Listening to '%s'...", streaming)
254254 await self._subscribe_to_home(ws)
255255256256 async def listen_for_messages():
···262262 _ = await asyncio.gather(listen)
263263 except websockets.ConnectionClosedError as e:
264264 self.log.error(e, stack_info=True, exc_info=True)
265265- self.log.info("Reconnecting to %s...", streaming)
265265+ self.log.info("Reconnecting to '%s'...", streaming)
266266 continue
267267 except TimeoutError as e:
268268- self.log.error("Connection timeout: %s", e)
269269- self.log.info("Reconnecting to %s...", streaming)
268268+ self.log.error("Connection timeout: '%s'", e)
269269+ self.log.info("Reconnecting to '%s'...", streaming)
270270 continue
+2-2
registry.py
···1919 db: DatabasePool, http: httpx.Client, data: dict[str, Any]
2020) -> InputService:
2121 if "type" not in data:
2222- raise ValueError("No `type` field in input data!")
2222+ raise ValueError("No 'type' field in input data!")
2323 type: str = str(data["type"])
2424 del data["type"]
2525···3333 db: DatabasePool, http: httpx.Client, data: dict[str, Any]
3434) -> OutputService:
3535 if "type" not in data:
3636- raise ValueError("No `type` field in input data!")
3636+ raise ValueError("No 'type' field in input data!")
3737 type: str = str(data["type"])
3838 del data["type"]
3939
+2-1
util/util.py
···17171818def normalize_service_url(url: str) -> str:
1919 if not url.startswith("https://") and not url.startswith("http://"):
2020- raise ValueError(f"Invalid service url {url}! Only http/https are supported.")
2020+ raise ValueError(f"Invalid service url {url}! Must start with http/https!")
21212222 return url[:-1] if url.endswith("/") else url
2323+23242425def _read_env(data: Any) -> None:
2526 match data: