Schedule posts to Bluesky with Cloudflare workers.
skyscheduler.work
cf
tool
bsky-tool
cloudflare
bluesky
schedule
bsky
service
social-media
cloudflare-workers
1import has from "just-has";
2import isEmpty from "just-is-empty";
3import { EmbedData, PostLabel } from "../types";
4import { RepostInfo } from "./repost";
5import { MAX_REPOST_RULES_PER_POST } from "../limits";
6
7// Basically a copy of the schema
8export class Post {
9 // guid for post
10 postid: string;
11 // SkyScheduler User Id
12 user: string;
13 // post data
14 text: string;
15 embeds?: EmbedData[];
16 label: PostLabel;
17 // post flags
18 postNow: boolean;
19 posted?: boolean;
20 isRepost?: boolean;
21 // repost metadata
22 repostInfo?: RepostInfo[];
23 scheduledDate?: string;
24 repostCount?: number;
25 // atproto data
26 cid?: string;
27 uri?: string;
28 // thread data
29 threadOrder: number;
30 rootPost?: string;
31 parentPost?: string;
32
33 constructor(data: any) {
34 if (has(data, "userId"))
35 this.user = data.userId;
36 else
37 this.user = data.user;
38
39 if (has(data, "uuid"))
40 this.postid = data.uuid;
41 else
42 this.postid = data.postid;
43
44 if (has(data, "embedContent"))
45 this.embeds = data.embedContent;
46 else if (has(data, "embeds"))
47 this.embeds = data.embeds;
48
49 if (has(data, "contentLabel"))
50 this.label = data.contentLabel;
51 else
52 this.label = data.label;
53
54 if (has(data, "content"))
55 this.text = data.content;
56 else
57 this.text = data.text;
58
59 this.postNow = data.postNow;
60 this.threadOrder = data.threadOrder;
61
62 if (has(data, "repostCount"))
63 this.repostCount = data.repostCount;
64
65 if (data.scheduledDate)
66 this.scheduledDate = data.scheduledDate;
67
68 if (data.repostInfo)
69 this.repostInfo = data.repostInfo;
70
71 if (data.rootPost)
72 this.rootPost = data.rootPost;
73
74 if (data.parentPost)
75 this.parentPost = data.parentPost;
76
77 // ATProto data
78 if (data.uri)
79 this.uri = data.uri;
80 if (data.cid)
81 this.cid = data.cid;
82
83 if (has(data, "isRepost"))
84 this.isRepost = data.isRepost;
85
86 if (has(data, "posted"))
87 this.posted = data.posted;
88
89 // if a cid flag appears for the object and it's a thread root, then the post (if marked not posted) is posted.
90 if (this.posted == false && !isEmpty(data.cid) && this.isThreadRoot)
91 this.posted = true;
92 }
93 getURI(): string|null {
94 return this.uri ? "https://bsky.app/profile/" + this.uri.replace("at://","").replace("app.bsky.feed.","") : null;
95 }
96 getUser(): string {
97 return this.user;
98 }
99 canAddMoreRepostRules(): boolean {
100 if (!this.posted)
101 return false;
102
103 return !this.isChildPost && (this.repostInfo === undefined || this.repostInfo.length < MAX_REPOST_RULES_PER_POST);
104 }
105 hasEmbeds(): boolean {
106 return this.embeds !== undefined && this.embeds.length > 0;
107 }
108 get isThreadRoot() { return this.threadOrder == 0; }
109 get isChildPost() { return this.parentPost !== undefined; }
110};