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 12 PostResponseObject, Repost, ScheduledContext 13 13 } from '../types.d'; 14 14 import { atpRecordURI } from '../validation/regexCases'; 15 - import { getBskyUserPassForId } from './db/userinfo'; 15 + import { getBskyUserPassForId, getUsernameForUserId } from './db/userinfo'; 16 16 import { createViolationForUser } from './db/violations'; 17 17 import { deleteEmbedsFromR2 } from './r2Query'; 18 18 import { isPostAlreadyPosted, setPostNowOffForPost, updatePostData } from './db/data'; ··· 104 104 return AccountStatus.UnhandledError; 105 105 } 106 106 107 - export const makePost = async (c: Context|ScheduledContext, content: Post|null, isQueued: boolean=false) => { 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) => { 108 128 if (content === null) 109 129 return false; 110 130 ··· 114 134 console.log(`Dropped handling make post for post ${content.postid}, already posted.`) 115 135 return true; 116 136 } 117 - const newPost: PostResponseObject|null = await makePostRaw(env, content); 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); 118 144 if (newPost !== null) { 119 145 // update post data in the d1 120 146 const postDataUpdate: Promise<boolean> = updatePostData(env, content.postid, { posted: true, uri: newPost.uri, cid: newPost.cid, ··· 139 165 return false; 140 166 } 141 167 142 - export const makeRepost = async (c: Context|ScheduledContext, content: Repost) => { 168 + export const makeRepost = async (c: Context|ScheduledContext, content: Repost, usingAgent: AtpAgent|null=null) => { 143 169 const env = c.env; 144 170 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}`); 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}`); 161 174 return false; 162 175 } 163 176 ··· 179 192 return bWasSuccess; 180 193 }; 181 194 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}`); 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`); 198 200 return null; 199 201 } 200 202