diff --git a/deno.jsonc b/deno.jsonc index 1717ed9..66f6764 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -8,6 +8,7 @@ "semiColons": false }, "imports": { + "@atcute/bluesky": "npm:@atcute/bluesky@^3.3.3", "@badgateway/oauth2-client": "npm:@badgateway/oauth2-client@^3.3.1", "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.1.2", "@hey-api/openapi-ts": "npm:@hey-api/openapi-ts@^0.97.0", diff --git a/deno.lock b/deno.lock index 36a4678..41def26 100644 --- a/deno.lock +++ b/deno.lock @@ -35,6 +35,7 @@ "jsr:@std/semver@^1.0.6": "1.0.8", "jsr:@std/streams@^1.1.0": "1.1.0", "jsr:@std/uuid@^1.0.9": "1.1.1", + "npm:@atcute/bluesky@^3.3.3": "3.3.3", "npm:@babel/core@^7.28.0": "7.29.0", "npm:@babel/preset-react@^7.27.1": "7.28.5_@babel+core@7.29.0", "npm:@badgateway/oauth2-client@^3.3.1": "3.3.1", @@ -1705,6 +1706,7 @@ "jsr:@kysely/kysely@~0.28.16", "jsr:@std/encoding@^1.0.10", "jsr:@std/http@^1.1.0", + "npm:@atcute/bluesky@^3.3.3", "npm:@badgateway/oauth2-client@^3.3.1", "npm:@hey-api/openapi-ts@0.97", "npm:@hey-api/vite-plugin@~0.3.1", diff --git a/lib/buildMessageContent.ts b/lib/buildMessageContent.ts new file mode 100644 index 0000000..8a2188f --- /dev/null +++ b/lib/buildMessageContent.ts @@ -0,0 +1,47 @@ +import { AppBskyFeedPost } from "@atcute/bluesky" + +interface BuildMessageParams { + post: AppBskyFeedPost.Main +} + +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +export const buildMessageContent = ({ post }: BuildMessageParams) => { + if (!post.facets?.length) { + return post.text + } + + let textBytes = encoder.encode(post.text) + // Sort facets in reverse order to avoid affecting the byte offsets of subsequent facets + const facets = post.facets.sort((a, b) => + b.index.byteStart - a.index.byteStart + ) + + for (const facet of facets) { + const linkFeature = facet.features.find((f) => + f.$type === "app.bsky.richtext.facet#link" + ) + + if (!linkFeature) { + continue + } + + const uriBytes = encoder.encode(linkFeature.uri) + const newBytes = new Uint8Array( + facet.index.byteStart + uriBytes.length + + (textBytes.length - facet.index.byteEnd), + ) + + newBytes.set(textBytes.subarray(0, facet.index.byteStart), 0) + newBytes.set(uriBytes, facet.index.byteStart) + newBytes.set( + textBytes.subarray(facet.index.byteEnd), + facet.index.byteStart + uriBytes.length, + ) + + textBytes = newBytes + } + + return decoder.decode(textBytes) +} diff --git a/service/jestream.ts b/service/jestream.ts index 68d2e62..1232c8c 100644 --- a/service/jestream.ts +++ b/service/jestream.ts @@ -1,5 +1,6 @@ import { Jetstream } from "@skyware/jetstream" import { buildAtProtoUri } from "../lib/atProto.ts" +import { buildMessageContent } from "../lib/buildMessageContent.ts" import { config } from "../lib/config.ts" import { getTraqMessageIdByAtProtoUri, @@ -27,8 +28,12 @@ export class JetstreamService { cursor: opts.cursor, }) this.jetstream.onCreate("app.bsky.feed.post", async (event) => { - if (event.commit.record.embed || event.commit.record.reply?.parent) { - // Ignore replies posts with embed for now + const hasNonExternalEmbed = event.commit.record.embed && + event.commit.record.embed.$type !== "app.bsky.embed.external" + const isReply = !!event.commit.record.reply?.parent + + if (hasNonExternalEmbed || isReply) { + // Ignore replies and posts with embeds other than app.bsky.embed.external for now return } @@ -53,7 +58,9 @@ export class JetstreamService { channelId: userSetting.targetChannelId, }, body: { - content: event.commit.record.text, + content: buildMessageContent({ + post: event.commit.record, + }), }, })