Schedule posts to Bluesky with Cloudflare workers.
skyscheduler.work
cf
tool
bsky-tool
cloudflare
bluesky
schedule
bsky
service
social-media
cloudflare-workers
1import {
2 BSKY_IMG_FILE_EXTS, BSKY_IMG_SIZE_LIMIT_IN_MB,
3 BSKY_VIDEO_FILE_EXTS, BSKY_VIDEO_MAX_DURATION,
4 CF_IMAGES_FILE_SIZE_LIMIT_IN_MB,
5 CF_IMAGES_MAX_DIMENSION,
6 MAX_LENGTH, MAX_THUMBNAIL_SIZE,
7 R2_FILE_SIZE_LIMIT_IN_MB
8} from "../limits";
9import { APP_NAME } from "../siteinfo";
10import { ConstScriptStr } from "../utils/constScriptGen";
11import { IncludeDependencyTags, PreloadRules } from "./helpers/includesTags";
12import ContentLabelOptions from "./options/contentLabelOptions";
13import RetweetOptions from "./options/retweetOptions";
14import ScheduleOptions from "./options/scheduleOptions";
15
16export const PreloadPostCreation: PreloadRules[] = [
17 {type: "script", href: ConstScriptStr },
18 {type: "script", href: "/dep/dropzone.min.js"},
19 {type: "style", href: "/dep/dropzone.min.css"},
20 {type: "style", href: "/css/dropzoneMods.css"},
21 {type: "style", href: "/dep/tribute.css"},
22 {type: "script", href: "/dep/tribute.min.js"}
23];
24
25export function PostCreation({ctx}: any) {
26 const maxWidth: number|undefined = ctx.env.IMAGE_SETTINGS.max_width;
27 const bskyImageLimits = `Max file size of ${BSKY_IMG_SIZE_LIMIT_IN_MB}MB`;
28 return (<section>
29 <IncludeDependencyTags scripts={PreloadPostCreation} />
30 <article>
31 <form id="postForm" novalidate>
32 <header>
33 <h4 id="postFormTitle"></h4>
34 <small class="thread-cancel hidden" data-placement="left" data-tooltip="Cancel adding post to highlighted thread">
35 <a id="cancelThreadPost" tabindex={0} class="contrast" role="button">Cancel Thread Post</a>
36 </small>
37 </header>
38 <div>
39 <article>
40 <section role="form">
41 <textarea id="content" rows={8} placeholder="Post Content" required aria-labelledby="post-content-label"></textarea>
42 <label id="post-content-label" for="content">Post Content</label>
43 <small class="smallLabel">Character Count: <span id="count">0/{MAX_LENGTH}</span></small>
44 </section>
45 </article>
46
47 <details>
48 <summary role="button" title="click to toggle section" class="secondary outline">Attach Media/Link</summary>
49 <section id="section-imageAttachment">
50 <article>
51 <header>Files</header>
52 <div>
53 <div id="fileUploads" class="dropzone">
54 <center class="dz-message">Drag or click here to upload files</center>
55 </div>
56 </div>
57 <footer>
58 <div class="uploadGuidelines"><small><b>Note</b>: <ul>
59 <li><span data-tooltip={BSKY_IMG_FILE_EXTS}>Images</span>:
60 <ul>
61 <li>must be less than {CF_IMAGES_MAX_DIMENSION}x{CF_IMAGES_MAX_DIMENSION} pixels</li>
62 <li>must have a file size smaller than {CF_IMAGES_FILE_SIZE_LIMIT_IN_MB}MB ({APP_NAME} will attempt to compress images to fit <span data-tooltip={bskyImageLimits}>BlueSky's requirements</span>)
63 {maxWidth ?
64 <ol>
65 <li>images over {BSKY_IMG_SIZE_LIMIT_IN_MB}MB with a width greater than <b>{maxWidth}px</b> will also <u data-tooltip="will preserve aspect ratio">be resized</u> in addition to being compressed</li>
66 </ol> : null}
67 </li>
68 <li>thumbnails will only be shown here for images that are smaller than {MAX_THUMBNAIL_SIZE}MB</li>
69 <li>if an image fails to upload, you'll need to manually adjust the file to fit it properly</li>
70 </ul></li>
71 <li><span data-tooltip={BSKY_VIDEO_FILE_EXTS}>Videos</span>:
72 <ul>
73 <li>must be shorter than {BSKY_VIDEO_MAX_DURATION} minutes</li>
74 <li>must be smaller than {R2_FILE_SIZE_LIMIT_IN_MB}MB</li>
75 <li>will be processed on your PDS after they're posted. This may show a temporary <i>"Video not Found"</i> message for a bit after posting.</li>
76 </ul></li>
77 </ul></small></div>
78 </footer>
79 </article>
80 </section>
81 <section id="section-weblink">
82 <article>
83 <header><label for="urlCard">Link Embed</label></header>
84 <input type="text" id="urlCard" placeholder="https://" value="" />
85 <small>Add a social embed card for a link to your post. This link will not count against the {MAX_LENGTH} character limit.<br />
86 Thumbnails may get automatically resized down to fit 1280x720.</small>
87 <footer><div class="uploadGuidelines"><small><b>NOTE</b>: File uploads will <b>always supersede</b> any link embeds.</small></div></footer>
88 </article>
89 </section>
90 <section id="content-label-selector" class="hidden">
91 <ContentLabelOptions id="contentLabels" />
92 </section>
93 </details>
94 <details>
95 <summary title="click to toggle section" role="button" class="outline secondary">Add Record (Quote Post)</summary>
96 <section>
97 <article>
98 <header><label for="recordBox">Insert Post/Feed/List Link</label></header>
99 <input id="recordBox" placeholder="https://" title="Must be a link to a ATProto based record" />
100 <small>Posts must be quotable and all record types must be resolvable (exist) upon the scheduled time. If it does not exist, it will not be attached to your post.</small>
101 </article>
102 </section>
103 </details>
104 <details id="section-postSchedule" open>
105 <summary title="click to toggle section" role="button" class="outline secondary">Post Scheduling</summary>
106 <ScheduleOptions allowNow={true} timeID="scheduledDate" checkboxID="postNow" type="post" />
107 </details>
108
109 <details id="section-retweet">
110 <summary role="button" title="click to toggle section" class="secondary outline">Auto-Retweet</summary>
111 <RetweetOptions id="makeReposts" contentType="post" />
112 </details>
113 <input type="hidden" id="threadInfo" />
114 </div>
115 <footer>
116 <button id="makingPostRequest" type="submit" class="w-full primary">
117 Schedule Post
118 </button>
119 </footer>
120 </form>
121 </article>
122 </section>);
123}