+79
-4
src/lib/at.ts
+79
-4
src/lib/at.ts
···
25
if (!didDoc.ok) throw didDoc.data.error;
26
return {
27
client: rpc,
28
did: res.data.did,
29
handle: res.data.handle,
30
pds: didDoc.data.pds,
···
38
altText?: string,
39
) => {
40
const login = await getSessionClient(did);
41
-
const upload = await login.client.post("com.atproto.repo.uploadBlob", {
42
-
input: blob,
43
});
44
-
if (!upload.ok) throw `failed to upload blob: ${upload.data.error}`;
45
const record: AppBskyFeedPost.Main = {
46
$type: "app.bsky.feed.post",
47
text: postContent,
48
embed: {
49
$type: "app.bsky.embed.video",
50
-
video: upload.data.blob,
51
alt: altText,
52
},
53
createdAt: new Date().toISOString(),
54
};
55
const result = await login.client.post("com.atproto.repo.createRecord", {
56
input: {
57
collection: "app.bsky.feed.post",
···
59
repo: did,
60
},
61
});
62
if (!result.ok) throw `failed to upload post: ${result.data.error}`;
63
return result.data;
64
};
···
25
if (!didDoc.ok) throw didDoc.data.error;
26
return {
27
client: rpc,
28
+
agent,
29
did: res.data.did,
30
handle: res.data.handle,
31
pds: didDoc.data.pds,
···
39
altText?: string,
40
) => {
41
const login = await getSessionClient(did);
42
+
43
+
const serviceAuthUrl = new URL(
44
+
`${login.pds}/xrpc/com.atproto.server.getServiceAuth`,
45
+
);
46
+
serviceAuthUrl.searchParams.append(
47
+
"aud",
48
+
login.pds!.replace("https://", "did:web:"),
49
+
);
50
+
serviceAuthUrl.searchParams.append("lxm", "com.atproto.repo.uploadBlob");
51
+
serviceAuthUrl.searchParams.append(
52
+
"exp",
53
+
(Math.floor(Date.now() / 1000) + 60 * 30).toString(),
54
+
); // 30 minutes
55
+
56
+
const serviceAuthResponse = await login.agent.handle(
57
+
`${serviceAuthUrl.pathname}${serviceAuthUrl.search}`,
58
+
{
59
+
method: "GET",
60
+
},
61
+
);
62
+
63
+
if (!serviceAuthResponse.ok) {
64
+
const error = await serviceAuthResponse.text();
65
+
throw `failed to get service auth: ${error}`;
66
+
}
67
+
68
+
const serviceAuth = await serviceAuthResponse.json();
69
+
const token = serviceAuth.token;
70
+
71
+
const uploadUrl = new URL(
72
+
"https://video.bsky.app/xrpc/app.bsky.video.uploadVideo",
73
+
);
74
+
uploadUrl.searchParams.append("did", did);
75
+
uploadUrl.searchParams.append("name", "video.mp4");
76
+
77
+
const uploadResponse = await fetch(uploadUrl.toString(), {
78
+
method: "POST",
79
+
headers: {
80
+
Authorization: `Bearer ${token}`,
81
+
"Content-Type": "video/mp4",
82
+
},
83
+
body: blob,
84
});
85
+
86
+
if (!uploadResponse.ok) {
87
+
const error = await uploadResponse.text();
88
+
throw `failed to upload video: ${error}`;
89
+
}
90
+
91
+
const jobStatus = await uploadResponse.json();
92
+
let videoBlobRef = jobStatus.blob;
93
+
94
+
while (!videoBlobRef) {
95
+
await new Promise((resolve) => setTimeout(resolve, 1000));
96
+
97
+
const statusResponse = await fetch(
98
+
`https://video.bsky.app/xrpc/app.bsky.video.getJobStatus?jobId=${jobStatus.jobId}`,
99
+
);
100
+
101
+
if (!statusResponse.ok) {
102
+
const error = await statusResponse.json();
103
+
if (error.error === "already_exists" && error.blob) {
104
+
videoBlobRef = error.blob;
105
+
break;
106
+
}
107
+
throw `failed to get job status: ${error.message || error.error}`;
108
+
}
109
+
110
+
const status = await statusResponse.json();
111
+
if (status.jobStatus.blob) {
112
+
videoBlobRef = status.jobStatus.blob;
113
+
} else if (status.jobStatus.state === "JOB_STATE_FAILED") {
114
+
throw `video processing failed: ${status.jobStatus.error || "unknown error"}`;
115
+
}
116
+
}
117
+
118
const record: AppBskyFeedPost.Main = {
119
$type: "app.bsky.feed.post",
120
text: postContent,
121
embed: {
122
$type: "app.bsky.embed.video",
123
+
video: videoBlobRef,
124
alt: altText,
125
},
126
createdAt: new Date().toISOString(),
127
};
128
+
129
const result = await login.client.post("com.atproto.repo.createRecord", {
130
input: {
131
collection: "app.bsky.feed.post",
···
133
repo: did,
134
},
135
});
136
+
137
if (!result.ok) throw `failed to upload post: ${result.data.error}`;
138
return result.data;
139
};
+1
-1
src/lib/oauthMetadata.json
+1
-1
src/lib/oauthMetadata.json
···
4
"client_uri": "http://localhost:3000",
5
"logo_uri": "http://localhost:3000/favicon.png",
6
"redirect_uris": ["http://127.0.0.1:3000/"],
7
-
"scope": "atproto repo:app.bsky.feed.post?action=create blob:video/*",
8
"grant_types": ["authorization_code", "refresh_token"],
9
"response_types": ["code"],
10
"token_endpoint_auth_method": "none",
···
4
"client_uri": "http://localhost:3000",
5
"logo_uri": "http://localhost:3000/favicon.png",
6
"redirect_uris": ["http://127.0.0.1:3000/"],
7
+
"scope": "atproto repo:app.bsky.feed.post?action=create rpc:com.atproto.repo.uploadBlob?aud=* blob:video/*",
8
"grant_types": ["authorization_code", "refresh_token"],
9
"response_types": ["code"],
10
"token_endpoint_auth_method": "none",