Schedule posts to Bluesky with Cloudflare workers. skyscheduler.work
cf tool bsky-tool cloudflare bluesky schedule bsky service social-media cloudflare-workers

Support for #83

+38 -36
+38 -36
src/utils/bskyApi.ts
··· 12 PostResponseObject, Repost, ScheduledContext 13 } from '../types.d'; 14 import { atpRecordURI } from '../validation/regexCases'; 15 - import { getBskyUserPassForId } from './db/userinfo'; 16 import { createViolationForUser } from './db/violations'; 17 import { deleteEmbedsFromR2 } from './r2Query'; 18 import { isPostAlreadyPosted, setPostNowOffForPost, updatePostData } from './db/data'; ··· 104 return AccountStatus.UnhandledError; 105 } 106 107 - export const makePost = async (c: Context|ScheduledContext, content: Post|null, isQueued: boolean=false) => { 108 if (content === null) 109 return false; 110 ··· 114 console.log(`Dropped handling make post for post ${content.postid}, already posted.`) 115 return true; 116 } 117 - const newPost: PostResponseObject|null = await makePostRaw(env, content); 118 if (newPost !== null) { 119 // update post data in the d1 120 const postDataUpdate: Promise<boolean> = updatePostData(env, content.postid, { posted: true, uri: newPost.uri, cid: newPost.cid, ··· 139 return false; 140 } 141 142 - export const makeRepost = async (c: Context|ScheduledContext, content: Repost) => { 143 const env = c.env; 144 let bWasSuccess = true; 145 - const loginCreds = await getBskyUserPassForId(env, content.userId); 146 - if (loginCreds.valid === false) { 147 - console.error(`bsky credentials for repost ${content.uri} were invalid`); 148 - return false; 149 - } 150 - 151 - const {username, password, pds} = loginCreds 152 - const agent = new AtpAgent({ 153 - service: new URL(pds), 154 - }); 155 - 156 - const loginResponse: AccountStatus = await loginToBsky(agent, username, password); 157 - if (loginResponse != AccountStatus.Ok) { 158 - const addViolation:boolean = await createViolationForUser(env, content.userId, loginResponse); 159 - if (addViolation) 160 - console.error(`Unable to login to make repost from user ${content.userId} with violation ${loginResponse}`); 161 return false; 162 } 163 ··· 179 return bWasSuccess; 180 }; 181 182 - export const makePostRaw = async (env: Bindings, content: Post) => { 183 - const loginCreds = await getBskyUserPassForId(env, content.user); 184 - if (loginCreds.valid === false) { 185 - console.error(`credentials for post ${content.postid} were invalid`); 186 - return null; 187 - } 188 - 189 - const {username, password, pds} = loginCreds; 190 - // Login to bsky 191 - const agent = new AtpAgent({ service: new URL(pds) }); 192 - 193 - const loginResponse: AccountStatus = await loginToBsky(agent, username, password); 194 - if (loginResponse != AccountStatus.Ok) { 195 - const addViolation: boolean = await createViolationForUser(env, content.user, loginResponse); 196 - if (addViolation) 197 - console.error(`Unable to login to make post ${content.user} with violation ${loginResponse}`); 198 return null; 199 } 200
··· 12 PostResponseObject, Repost, ScheduledContext 13 } from '../types.d'; 14 import { atpRecordURI } from '../validation/regexCases'; 15 + import { getBskyUserPassForId, getUsernameForUserId } from './db/userinfo'; 16 import { createViolationForUser } from './db/violations'; 17 import { deleteEmbedsFromR2 } from './r2Query'; 18 import { isPostAlreadyPosted, setPostNowOffForPost, updatePostData } from './db/data'; ··· 104 return AccountStatus.UnhandledError; 105 } 106 107 + export const makeAgentForUser = async (env: Bindings, userId: string) => { 108 + const loginCreds = await getBskyUserPassForId(env, userId); 109 + if (loginCreds.valid === false) { 110 + console.error(`credentials for user ${userId} were invalid`); 111 + return null; 112 + } 113 + const {username, password, pds} = loginCreds; 114 + // Login to bsky 115 + const agent = new AtpAgent({ service: new URL(pds) }); 116 + 117 + const loginResponse: AccountStatus = await loginToBsky(agent, username, password); 118 + if (loginResponse != AccountStatus.Ok) { 119 + const addViolation: boolean = await createViolationForUser(env, userId, loginResponse); 120 + if (addViolation) 121 + console.error(`Unable to login to ${userId} with violation ${loginResponse}`); 122 + return null; 123 + } 124 + return agent; 125 + } 126 + 127 + export const makePost = async (c: Context|ScheduledContext, content: Post|null, isQueued: boolean=false, usingAgent: AtpAgent|null=null) => { 128 if (content === null) 129 return false; 130 ··· 134 console.log(`Dropped handling make post for post ${content.postid}, already posted.`) 135 return true; 136 } 137 + 138 + const agent: AtpAgent|null = (usingAgent === null) ? await makeAgentForUser(env, content.user) : usingAgent; 139 + if (agent === null) { 140 + console.warn(`could not make agent for post ${content.postid}`); 141 + return false; 142 + } 143 + const newPost: PostResponseObject|null = await makePostRaw(env, content, agent); 144 if (newPost !== null) { 145 // update post data in the d1 146 const postDataUpdate: Promise<boolean> = updatePostData(env, content.postid, { posted: true, uri: newPost.uri, cid: newPost.cid, ··· 165 return false; 166 } 167 168 + export const makeRepost = async (c: Context|ScheduledContext, content: Repost, usingAgent: AtpAgent|null=null) => { 169 const env = c.env; 170 let bWasSuccess = true; 171 + const agent: AtpAgent|null = (usingAgent === null) ? await makeAgentForUser(env, content.userId) : usingAgent; 172 + if (agent === null) { 173 + console.warn(`could not make agent for repost ${content.postid}`); 174 return false; 175 } 176 ··· 192 return bWasSuccess; 193 }; 194 195 + export const makePostRaw = async (env: Bindings, content: Post, agent: AtpAgent) => { 196 + const username = await getUsernameForUserId(env, content.user); 197 + // incredibly unlikely but we'll handle it 198 + if (username === null) { 199 + console.warn(`username for post ${content.postid} was invalid`); 200 return null; 201 } 202