···
1
-
from atproto import client_utils, Client, AtUri, IdResolver
1
+
from atproto import client_utils, IdResolver
from atproto_client import models
3
+
from atproto2 import Client2
···
# only for lexicon reference
SERVICE = 'https://bsky.app'
14
-
ADULT_LABEL = ["sexual content", "nsfw"]
15
-
PORN_LABEL = ["porn", "yiff"]
16
+
# TODO this is terrible and stupid
17
+
ADULT_PATTERN = re.compile(r"\b(sexual content|nsfw|erotic|adult only|18\+)\b", re.IGNORECASE)
18
+
PORN_PATTERN = re.compile(r"\b(porn|yiff|hentai|pornographic|fetish)\b", re.IGNORECASE)
class BlueskyOutput(cross.Output):
def __init__(self, input: cross.Input, settings: dict, db: DataBaseWorker) -> None:
super().__init__(input, settings, db)
20
-
self.options = util.safe_get(settings, 'options', {})
23
+
self.options = settings.get('options') or {}
22
-
if not util.get_or_envvar(settings, 'app-password'):
25
+
if not util.as_envvar(settings.get('app-password')):
raise Exception("Account app password not provided!")
26
-
did: str | None = util.get_or_envvar(settings, 'did')
29
+
did: str | None = util.as_envvar(settings.get('did'))
28
-
if not util.get_or_envvar(settings, 'handle'):
31
+
handle = util.as_envvar(settings.get('handle'))
raise Exception("ATP handle not specified!")
30
-
LOGGER.info("Resolving ATP identity for %s...", util.get_or_envvar(settings, 'handle'))
31
-
did = resolver.handle.resolve(util.get_or_envvar(settings, 'handle'))
34
+
LOGGER.info("Resolving ATP identity for %s...", handle)
35
+
did = resolver.handle.resolve(handle)
raise Exception("Failed to resolve DID!")
35
-
pds: str | None = util.get_or_envvar(settings, 'pds')
39
+
pds: str | None = util.as_envvar(settings.get('pds'))
LOGGER.info("Resolving PDS from DID document...")
did_doc = resolver.did.resolve(did)
···
raise Exception("Failed to resolve PDS!")
45
-
self.client = Client(pds)
46
-
self.client.login(did, util.get_or_envvar(settings, 'app-password'))
47
-
self.bsky = Bluesky(self.client)
49
+
self.bsky = Client2(pds)
50
+
self.bsky.login(did, util.as_envvar(settings.get('app-password')))
def _find_parent(self, parent_id: str):
50
-
login = self.client.me
53
+
login = self.bsky.me
raise Exception("Client not logged in!")
···
def accept_post(self, post: cross.Post):
143
-
login = self.client.me
146
+
login = self.bsky.me
raise Exception("Client not logged in!")
···
tokens.insert(0, cross.TextToken("CW: " + cw + "\n\n"))
unique_labels.add('graphic-media')
169
-
if any(tag in cw for tag in ADULT_LABEL):
172
+
if ADULT_PATTERN.search(cw):
unique_labels.add('sexual')
172
-
if any(tag in cw for tag in PORN_LABEL):
175
+
if PORN_PATTERN.search(cw):
unique_labels.add('porn')
···
database.insert_mapping(self.db, db_post['id'], new_parent_id)
def delete_post(self, identifier: str):
347
-
login = self.client.me
350
+
login = self.bsky.me
raise Exception("Client not logged in!")
···
mappings = database.find_mappings(self.db, post['id'], SERVICE, login.did)
for mapping in mappings[::-1]:
357
-
self.client.delete_post(json.loads(mapping[0])['uri'])
360
+
self.bsky.delete_post(json.loads(mapping[0])['uri'])
database.delete_post(self.db, mapping[0], SERVICE, login.did)
362
-
def __init__(self, client: Client) -> None:
363
-
self.client = client
367
-
text: str | client_utils.TextBuilder,
369
-
video_alt: str | None = None,
370
-
video_aspect_ratio: models.AppBskyEmbedDefs.AspectRatio | None = None,
371
-
reply_to: models.AppBskyFeedPost.ReplyRef | None = None,
372
-
langs: list[str] | None = None,
373
-
facets: list[models.AppBskyRichtextFacet.Main] | None = None,
374
-
labels: models.ComAtprotoLabelDefs.SelfLabels | None = None
375
-
) -> models.AppBskyFeedPost.CreateRecordResponse:
377
-
if video_alt is None:
380
-
upload = self.client.upload_blob(video)
382
-
return self.send_post(
385
-
embed=models.AppBskyEmbedVideo.Main(video=upload.blob, alt=video_alt, aspect_ratio=video_aspect_ratio),
393
-
text: str | client_utils.TextBuilder,
394
-
images: list[bytes],
395
-
image_alts: list[str] | None = None,
396
-
image_aspect_ratios: list[models.AppBskyEmbedDefs.AspectRatio] | None = None,
397
-
reply_to: models.AppBskyFeedPost.ReplyRef | None = None,
398
-
langs: list[str] | None = None,
399
-
facets: list[models.AppBskyRichtextFacet.Main] | None = None,
400
-
labels: models.ComAtprotoLabelDefs.SelfLabels | None = None
401
-
) -> models.AppBskyFeedPost.CreateRecordResponse:
403
-
if image_alts is None:
404
-
image_alts = [''] * len(images)
406
-
diff = len(images) - len(image_alts)
407
-
image_alts = image_alts + [''] * diff
409
-
if image_aspect_ratios is None:
410
-
aligned_image_aspect_ratios = [None] * len(images)
412
-
diff = len(images) - len(image_aspect_ratios)
413
-
aligned_image_aspect_ratios = image_aspect_ratios + [None] * diff
415
-
uploads = [self.client.upload_blob(image) for image in images]
418
-
models.AppBskyEmbedImages.Image(alt=alt, image=upload.blob, aspect_ratio=aspect_ratio)
419
-
for alt, upload, aspect_ratio in zip(image_alts, uploads, aligned_image_aspect_ratios)
422
-
return self.send_post(
425
-
embed=models.AppBskyEmbedImages.Main(images=embed_images),
433
-
text: str | client_utils.TextBuilder,
434
-
reply_to: models.AppBskyFeedPost.ReplyRef | None = None,
437
-
models.AppBskyEmbedImages.Main |
438
-
models.AppBskyEmbedExternal.Main |
439
-
models.AppBskyEmbedRecord.Main |
440
-
models.AppBskyEmbedRecordWithMedia.Main |
441
-
models.AppBskyEmbedVideo.Main = None,
442
-
langs: list[str] | None = None,
443
-
facets: list[models.AppBskyRichtextFacet.Main] | None = None,
444
-
labels: models.ComAtprotoLabelDefs.SelfLabels | None = None
445
-
) -> models.AppBskyFeedPost.CreateRecordResponse:
447
-
if isinstance(text, client_utils.TextBuilder):
448
-
facets = text.build_facets()
449
-
text = text.build_text()
451
-
repo = self.client.me and self.client.me.did
453
-
raise Exception("Client not logged in!")
458
-
record = models.AppBskyFeedPost.Record(
459
-
created_at=self.client.get_current_time_iso(),
467
-
return self.client.app.bsky.feed.post.create(repo, record)
469
-
def create_gates(self, options: dict, post_uri: str):
470
-
account = self.client.me
472
-
raise Exception("Client not logged in!")
474
-
rkey = AtUri.from_str(post_uri).rkey
475
-
time = self.client.get_current_time_iso()
477
-
thread_gate_opts = options.get('thread_gate', [])
478
-
if 'everybody' not in thread_gate_opts:
480
-
if thread_gate_opts:
481
-
if 'following' in thread_gate_opts:
482
-
allow.append(models.AppBskyFeedThreadgate.FollowingRule())
483
-
if 'followers' in thread_gate_opts:
484
-
allow.append(models.AppBskyFeedThreadgate.FollowerRule())
485
-
if 'mentioned' in thread_gate_opts:
486
-
allow.append(models.AppBskyFeedThreadgate.MentionRule())
488
-
thread_gate = models.AppBskyFeedThreadgate.Record(
494
-
self.client.app.bsky.feed.threadgate.create(account.did, thread_gate, rkey)
496
-
if options.get('quote_gate', False):
497
-
post_gate = models.AppBskyFeedPostgate.Record(
501
-
models.AppBskyFeedPostgate.DisableRule()
505
-
self.client.app.bsky.feed.postgate.create(account.did, post_gate, rkey)
def tokens_to_richtext(tokens: list[cross.Token]) -> client_utils.TextBuilder | None: