Bluesky client in Google Sheets
1function RESOLVEHANDLE(handle) {
2 url = "https://api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=" + handle;
3 const result = UrlFetchApp.fetch(url).getContentText();
4 const response = JSON.parse(result);
5 did = response.did;
6 return did;
7}
8
9function GETPOST(did, postid) {
10 url = `https://api.bsky.app/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.feed.post&rkey=${postid}`
11 const result = UrlFetchApp.fetch(url).getContentText();
12 const response = JSON.parse(result);
13 const content = response.value.text;
14 return content
15}
16
17function postToBluesky() {
18 const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
19 const statusText = sheet.getRange("A2").getValue();
20
21 const BSKY_HANDLE = sheet.getRange("B27").getValue();
22 const BSKY_PASSWORD = sheet.getRange("A27").getValue();
23 const PDS_URL = sheet.getRange("C27").getValue();
24
25 if (!statusText) {
26 SpreadsheetApp.getUi().alert("Cell A2 is empty!");
27 return;
28 }
29
30 try {
31 SpreadsheetApp.getUi().alert("Logging in");
32 const sessionResponse = UrlFetchApp.fetch(`https://${PDS_URL}/xrpc/com.atproto.server.createSession`, {
33 method: "POST",
34 contentType: "application/json",
35 payload: JSON.stringify({
36 identifier: BSKY_HANDLE,
37 password: BSKY_PASSWORD
38 }),
39 muteHttpExceptions: true
40 });
41
42 const sessionText = sessionResponse.getContentText();
43 if (!sessionText) throw new Error("Empty response from Bluesky during login.");
44
45 const sessionData = JSON.parse(sessionText);
46 if (sessionResponse.getResponseCode() !== 200) {
47 throw new Error("Login failed: " + (sessionData.message || "Unknown error"));
48 }
49
50 const token = sessionData.accessJwt;
51 const did = sessionData.did;
52
53 SpreadsheetApp.getUi().alert("Posting");
54 const postRecord = {
55 repo: did,
56 collection: "app.bsky.feed.post",
57 record: {
58 text: statusText,
59 createdAt: new Date().toISOString(),
60 $type: "app.bsky.feed.post"
61 }
62 };
63
64 const postResponse = UrlFetchApp.fetch(`https://${PDS_URL}/xrpc/com.atproto.repo.createRecord`, {
65 method: "POST",
66 contentType: "application/json",
67 headers: { "Authorization": `Bearer ${token}` },
68 payload: JSON.stringify(postRecord),
69 muteHttpExceptions: true
70 });
71
72 const postText = postResponse.getContentText();
73 if (postResponse.getResponseCode() === 200) {
74 SpreadsheetApp.getUi().alert("Post successful!");
75 } else {
76 const errorData = postText ? JSON.parse(postText) : { message: "No response body" };
77 throw new Error(errorData.message || "Post failed");
78 }
79
80 } catch (e) {
81 Logger.log(e.toString());
82 SpreadsheetApp.getUi().alert("Error: " + e.message);
83 }
84}