diff --git a/src/lib/image.ts b/src/lib/file.ts
similarity index 73%
rename from src/lib/image.ts
rename to src/lib/file.ts
index 80f3d5a..e814954 100644
--- a/src/lib/image.ts
+++ b/src/lib/file.ts
@@ -1,5 +1,5 @@
///
-import type { AppBskyEmbedImages } from "@atcute/bluesky"
+import type { AppBskyEmbedImages, AppBskyEmbedVideo } from "@atcute/bluesky"
import { type Did } from "@atcute/lexicons"
import { isLegacyBlob } from "@atcute/lexicons/interfaces"
import {
@@ -66,6 +66,54 @@ export const uploadImages = async (
return imageIds
}
+interface UploadVideoParams {
+ accessToken: string
+ did: Did
+ video: AppBskyEmbedVideo.Main
+ targetChannelId: string
+}
+
+export const uploadVideo = async (
+ { accessToken, did, video, targetChannelId }: UploadVideoParams,
+) => {
+ if (isLegacyBlob(video.video)) {
+ throw new Error("Legacy blobs are not supported")
+ }
+
+ const { data: downloadRes, ok } = await client.get(
+ "com.atproto.sync.getBlob",
+ {
+ as: "blob",
+ params: {
+ did,
+ cid: video.video.ref.$link,
+ },
+ },
+ )
+
+ if (!ok) {
+ throw new Error(
+ `Failed to download video: ${video.video.ref.$link}`,
+ )
+ }
+
+ const { data: uploadedFile } = await postFile({
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ body: {
+ channelId: targetChannelId,
+ file: downloadRes,
+ },
+ })
+
+ if (!uploadedFile) {
+ throw new Error(`Failed to upload video: ${video.video.ref.$link}`)
+ }
+
+ return uploadedFile.id
+}
+
const isUint8ArrayOfArrayBuffer = (
data: Uint8Array,
): data is Uint8Array => data.buffer instanceof ArrayBuffer
diff --git a/src/service/eventQueueWorker.ts b/src/service/eventQueueWorker.ts
index 67faa24..efc2809 100644
--- a/src/service/eventQueueWorker.ts
+++ b/src/service/eventQueueWorker.ts
@@ -1,12 +1,13 @@
import {
AppBskyEmbedImages,
AppBskyEmbedRecordWithMedia,
+ AppBskyEmbedVideo,
AppBskyFeedPost,
} from "@atcute/bluesky"
import { is } from "@atcute/lexicons/validations"
import { retry } from "@std/async/retry"
import { buildAtProtoUri } from "../lib/atProto.ts"
-import { uploadImages } from "../lib/image.ts"
+import { uploadImages, uploadVideo } from "../lib/file.ts"
import {
getTraqMessageIdByAtProtoUri,
savePostMetadata,
@@ -86,30 +87,51 @@ export class EventQueueWorker {
console.debug("Attempting to post message to traQ", atProtoUri)
let images: AppBskyEmbedImages.Image[] | undefined
- let imageIds: string[] | undefined
+ let video: AppBskyEmbedVideo.Main | undefined
+ let fileIds: string[] | undefined
if (is(AppBskyEmbedImages.mainSchema, record.embed)) {
images = record.embed.images
+ } else if (is(AppBskyEmbedVideo.mainSchema, record.embed)) {
+ video = record.embed
} else if (
is(
AppBskyEmbedRecordWithMedia.mainSchema,
record.embed,
- ) &&
- is(AppBskyEmbedImages.mainSchema, record.embed.media)
+ )
) {
- images = record.embed.media.images
+ if (is(AppBskyEmbedImages.mainSchema, record.embed.media)) {
+ images = record.embed.media.images
+ } else if (is(AppBskyEmbedVideo.mainSchema, record.embed.media)) {
+ video = record.embed.media
+ }
}
if (images?.length) {
- imageIds = await uploadImages({
+ const imageFileIds = await uploadImages({
accessToken,
did: event.did,
images,
targetChannelId: userSetting.targetChannelId,
})
+ fileIds = imageFileIds
+
+ console.debug(
+ `Uploaded images for post ${atProtoUri}: ${imageFileIds}`,
+ )
+ }
+
+ if (video) {
+ const videoFileId = await uploadVideo({
+ accessToken,
+ did: event.did,
+ video,
+ targetChannelId: userSetting.targetChannelId,
+ })
+ fileIds = fileIds ? [...fileIds, videoFileId] : [videoFileId]
console.debug(
- `Uploaded images for post ${atProtoUri}: ${imageIds}`,
+ `Uploaded video for post ${atProtoUri}: ${videoFileId}`,
)
}
@@ -126,7 +148,7 @@ export class EventQueueWorker {
},
body: {
content: await messageBuilder.build({
- imageIds,
+ fileIds,
post: record,
}),
},
diff --git a/src/service/jestream.ts b/src/service/jestream.ts
index 967abf6..ff8c8ef 100644
--- a/src/service/jestream.ts
+++ b/src/service/jestream.ts
@@ -1,4 +1,4 @@
-import { AppBskyEmbedVideo, AppBskyFeedPost } from "@atcute/bluesky"
+import { AppBskyFeedPost } from "@atcute/bluesky"
import { JetstreamSubscription } from "@atcute/jetstream"
import { type Did, is } from "@atcute/lexicons"
import { debounce } from "@std/async"
@@ -90,14 +90,13 @@ export class JetstreamService {
})
if (
- is(AppBskyEmbedVideo.mainSchema, event.commit.record.embed) ||
- !(await isSelfThread({
+ !await isSelfThread({
post: event.commit.record,
authorDid: event.did,
- }))
+ })
) {
console.warn(
- `Skipping post ${atProtoUri} because it has video or is not a self thread`,
+ `Skipping post ${atProtoUri} because it is not a self thread`,
)
this.updateCursor(event.time_us)
diff --git a/src/service/messageBuilder.ts b/src/service/messageBuilder.ts
index dc95787..332978d 100644
--- a/src/service/messageBuilder.ts
+++ b/src/service/messageBuilder.ts
@@ -19,7 +19,7 @@ interface MessageBuilderConstructorParams {
interface BuildMessageParams {
post: AppBskyFeedPost.Main
- imageIds?: string[]
+ fileIds?: string[]
}
const encoder = new TextEncoder()
@@ -36,7 +36,7 @@ export class MessageBuilder {
this.traqAccessToken = traqAccessToken
}
- async build({ post, imageIds }: BuildMessageParams) {
+ async build({ post, fileIds }: BuildMessageParams) {
let text = post.text
if (post.facets?.length) {
@@ -74,13 +74,13 @@ export class MessageBuilder {
text = decoder.decode(textBytes)
}
- if (imageIds?.length) {
- const imageLinks = imageIds.map((id) =>
+ if (fileIds?.length) {
+ const fileLinks = fileIds.map((id: string) =>
`${config.traqBaseUrl}/files/${id}`
)
.join("\n")
- text = text ? `${text}\n${imageLinks}` : imageLinks
+ text = text ? `${text}\n${fileLinks}` : fileLinks
}
if (post.reply) {