Schedule posts to Bluesky with Cloudflare workers.
skyscheduler.work
cf
tool
bsky-tool
cloudflare
bluesky
schedule
bsky
service
social-media
cloudflare-workers
1import isEmpty from 'just-is-empty';
2import split from 'just-split';
3import type { AllContext } from '../../types';
4import { getAllPostedPosts, getAllPostedPostsOfUser } from '../db/data';
5import { getPostRecords } from './bskyApi';
6
7// This looks for a bunch of posts that are posted and determines if the posts
8// are still on the network or not. If they are not, then this prunes the posts from
9// the database. This call is quite expensive and should only be ran on a weekly
10// cron job.
11export const pruneBskyPosts = async (c: AllContext, userId?: string) => {
12 const allPostedPosts = (userId !== undefined) ? await getAllPostedPostsOfUser(c, userId) : await getAllPostedPosts(c);
13 let removePostIds: string[] = [];
14 let postedGroups = split(allPostedPosts, 25);
15 while (!isEmpty(postedGroups)) {
16 const currentGroup = postedGroups.pop();
17 // This technically shouldn't be possible because we do a check in the while loop beforehand.
18 if (currentGroup === undefined) {
19 console.error("current group was somehow undefined...");
20 break;
21 }
22
23 // Create a map and an array so we can do lookups for reconciliation later
24 // to figure out what posts can be removed.
25 let urisOnly: string[] = [];
26 const postMap: Map<string, string> = new Map();
27 currentGroup.forEach((itm) => {
28 if (itm.uri === null) {
29 console.log(`Skipping a "posted" post that does not have an uri: ${itm.id}`);
30 return;
31 }
32 postMap.set(itm.uri, itm.id);
33 urisOnly.push(itm.uri);
34 });
35
36 try {
37 // Records from the public bsky api service
38 const bskyLookupResults = await getPostRecords(urisOnly);
39 if (bskyLookupResults !== null) {
40 // There is a discrepancy, we need to find which record it is.
41 if (bskyLookupResults.length !== urisOnly.length) {
42 // Go through the entire lookup results
43 while (!isEmpty(bskyLookupResults)) {
44 // remove the object at the end so we can try to figure out what's left
45 const currentRecord = bskyLookupResults.pop();
46 if (currentRecord === undefined)
47 break;
48
49 // if this record exists, remove it from our post map
50 // we will reconcile outside of this loop to delete the posts.
51 postMap.delete(currentRecord.uri);
52 }
53 // Add all the deleted keys to the main array.
54 postMap.forEach((value, key) => {
55 removePostIds.push(value);
56 });
57 }
58 }
59 } catch(err) {
60 console.error(`encountered error trying to prune: ${err}`);
61 // Remove all the posts in the current post group.
62 continue;
63 }
64 }
65 return removePostIds;
66};