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

update sitemap, site data

+59 -38
+1 -2
.dev.vars.example
··· 7 7 TURNSTILE_SECRET_KEY=cf-secret-keycode 8 8 RESET_BOT_USERNAME=skyscheduler.work 9 9 RESET_BOT_APP_PASS=bsky-app-password-for-user-with-dm-access 10 - RESIZE_SECRET_HEADER=somecoollongstring 11 - IN_DEV=true 10 + RESIZE_SECRET_HEADER=somecoollongstring
+14 -8
README.md
··· 55 55 - `TURNSTILE_SECRET_KEY` - the turnstile secret key for captcha 56 56 - `RESIZE_SECRET_HEADER` - a header value that will be included on requests while trying to resize images. Protects the resize bucket while still making it accessible to CF Images. 57 57 58 - **Note**: When deploying, these variables should also be configured as secrets in your Cloudflare worker dashboard. You can also do this via `npx wrangler secret put <NAME_OF_SECRET>`. _Alternatively_, make a file like `.env.prod` and use `npx wrangler secret bulk FILENAME` to upload all the settings at once. 58 + **Note**: When deploying, these variables should also be configured as secrets in your Cloudflare worker dashboard. You can also do this via `npx wrangler secret put <NAME_OF_SECRET>`. 59 + 60 + _Alternatively_, make a file like `.env.prod` and use `npx wrangler secret bulk FILENAME` to upload all the settings at once. 59 61 60 62 4. Update your `wrangler.toml` with changes that reflect your account. 61 63 - You'll need to update the values for the kv, r2, queues, d1 to reflect the bindings on your account. ··· 104 106 105 107 ### Application Variables 106 108 107 - Most of the application can be modified either through the `wrangler.toml` vars section or via `src/limits.ts`. These are usually heavily commented to explain what the options control. 109 + Most of the application can be modified with `wrangler.toml`'s vars section or via `src/limits.ts`. Both files are heavily commented to explain what the options control. 110 + 111 + ### Site Variables 112 + 113 + Modifying key values such as meta tag data, the application name and any descriptions is fully controlled via `src/siteinfo.ts`. Changing these fields will modify the rest of the application's web output. 108 114 109 115 ### Minimization 110 116 ··· 166 172 - pico - styling, tabs, modals 167 173 - countable - dynamic input counter 168 174 169 - ## Contributing 170 - 171 - We welcome contributions! 175 + ## Contributions 172 176 173 - ### Ways to Contribute 177 + We welcome contributions! Here's some ways to contribute: 174 178 175 179 - Report bugs 176 180 - Suggest enhancements 177 - - Submit pull requests 178 181 - [Sponsor](https://ko-fi.com/socksthewolf/tip) 179 182 180 183 ## License 181 184 182 - This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 185 + This project's source code is licensed under the MIT License 186 + Name, Logos and Branding are copyright SocksTheWolf, all rights reserved. 187 + 188 + See the [LICENSE](LICENSE) file for details. 183 189 184 190 --- 185 191 _Source hosted on [Github](https://github.com/SocksTheWolf/SkyScheduler), mirrored on [tangled](https://tangled.org/socksthewolf.com/skyscheduler)_
+1 -1
assets/robots.txt
··· 1 - User-Agent: * 1 + User-agent: * 2 2 Disallow: *.js$ 3 3 Disallow: *.css$ 4 4 Disallow: /admin
+5 -5
assets/sitemap.xml
··· 2 2 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> 3 3 <url> 4 4 <loc>https://skyscheduler.work/</loc> 5 - <lastmod>2026-02-23T01:42:15.807Z</lastmod> 5 + <lastmod>2026-02-24T00:01:03.732Z</lastmod> 6 6 </url> 7 7 <url> 8 8 <loc>https://skyscheduler.work/login</loc> 9 - <lastmod>2026-02-23T01:49:53.299Z</lastmod> 9 + <lastmod>2026-02-23T02:20:36.042Z</lastmod> 10 10 </url> 11 11 <url> 12 12 <loc>https://skyscheduler.work/privacy</loc> 13 - <lastmod>2026-02-23T01:51:40.422Z</lastmod> 13 + <lastmod>2026-02-23T02:20:36.042Z</lastmod> 14 14 </url> 15 15 <url> 16 16 <loc>https://skyscheduler.work/signup</loc> 17 - <lastmod>2026-02-23T01:53:39.083Z</lastmod> 17 + <lastmod>2026-02-23T02:20:36.042Z</lastmod> 18 18 </url> 19 19 <url> 20 20 <loc>https://skyscheduler.work/tos</loc> 21 - <lastmod>2026-02-23T01:47:34.846Z</lastmod> 21 + <lastmod>2026-02-23T02:20:36.042Z</lastmod> 22 22 </url> 23 23 </urlset>
+4 -4
src/layout/helpers/footer.tsx
··· 1 - import { APP_NAME, PROGRESS_MADE, PROGRESS_TOTAL } from "../../siteinfo"; 1 + import { APP_NAME, APP_REPO, PROGRESS_MADE, PROGRESS_TOTAL, PROJECT_AUTHOR, PROJECT_AUTHOR_SITE } from "../../siteinfo"; 2 2 3 3 // Helper footer for various pages 4 4 type FooterCopyrightProps = { ··· 9 9 10 10 export default function FooterCopyright(props: FooterCopyrightProps) { 11 11 const newWinAttr = props.inNewWindow ? {"target": '_blank'} : {}; 12 - const projectURL = (<a class="secondary" target="_blank" title="Project source on GitHub" 13 - href="https://github.com/SocksTheWolf/SkyScheduler">{APP_NAME}</a>); 12 + const projectURL = (<a class="secondary" target="_blank" title="Project source repository" 13 + href={APP_REPO}>{APP_NAME}</a>); 14 14 const homepageURL = (<a class="secondary" title="Homepage" href="/">{APP_NAME}</a>); 15 15 const progressBarTooltip = `$${PROGRESS_MADE}/$${PROGRESS_TOTAL} for this month`; 16 16 return ( ··· 19 19 <progress value={PROGRESS_MADE} max={PROGRESS_TOTAL} /></div> : null} 20 20 {props.showHomepage ? homepageURL : projectURL} &copy; {new Date().getFullYear()} 21 21 <span class="credits"> 22 - <a rel="author" target="_blank" title="Project author" href="https://socksthewolf.com">SocksTheWolf</a><br /> 22 + <a rel="author" target="_blank" title="Project author" href={PROJECT_AUTHOR_SITE}>{PROJECT_AUTHOR}</a><br /> 23 23 <small> 24 24 <a class="secondary" target="_blank" 25 25 data-tooltip="Tips are not required, the service is free, but if you like this service they are appreciated <3"
+2 -2
src/layout/helpers/logo.tsx
··· 1 - import { APP_NAME } from "../../siteinfo"; 1 + import { APP_NAME, LOGO_ENABLED } from "../../siteinfo"; 2 2 3 3 type LogoImageProps = { 4 4 enabled?: boolean; ··· 7 7 }; 8 8 9 9 export function LogoImage(props: LogoImageProps) { 10 - if (props.enabled == false) 10 + if (LOGO_ENABLED == false) 11 11 return null; 12 12 13 13 let width: number = props.width || 32;
+5 -6
src/layout/helpers/metaTags.tsx
··· 1 1 import { raw } from "hono/html"; 2 - import { APP_NAME, SITE_DESCRIPTION, SITE_URL } from "../../siteinfo"; 2 + import { APP_NAME, PROJECT_AUTHOR, PROJECT_AUTHOR_SITE, SITE_DESCRIPTION, SITE_URL, SOCIAL_CARD_IMAGE } from "../../siteinfo"; 3 3 4 4 export default function MetaTags() { 5 - const SocialImage: string = `${SITE_URL}/social-card.png`; 6 - 7 5 return ( 8 6 <> 9 7 <meta name="title" content={APP_NAME} /> ··· 12 10 <meta property="og:url" content={SITE_URL} /> 13 11 <meta property="og:title" content={APP_NAME} /> 14 12 <meta property="og:description" content={SITE_DESCRIPTION} /> 15 - <meta property="og:image" content={SocialImage} /> 13 + <meta property="og:image" content={SOCIAL_CARD_IMAGE} /> 16 14 <meta property="twitter:card" content="summary_large_image" /> 17 15 <meta property="twitter:url" content={SITE_URL} /> 18 16 <meta property="twitter:title" content={APP_NAME} /> 19 17 <meta property="twitter:description" content={SITE_DESCRIPTION} /> 20 - <meta property="twitter:image" content={SocialImage} /> 18 + <meta property="twitter:image" content={SOCIAL_CARD_IMAGE} /> 21 19 <script type="application/ld+json"> 22 20 {raw(`{ 23 21 "@context": "https://schema.org", 24 22 "@type": "WebSite", 23 + "creator": { "@type": "Person", "name": "${PROJECT_AUTHOR}", "url": "${PROJECT_AUTHOR_SITE}" }, 24 + "copyrightYear": ${new Date().getFullYear()}, 25 25 "name": "${APP_NAME}", 26 - "headline": "${APP_NAME}", 27 26 "url": "${SITE_URL}" 28 27 }`)} 29 28 </script>
+2 -2
src/pages/dashboard.tsx
··· 9 9 import { ScheduledPostList } from "../layout/postList"; 10 10 import { Settings, SettingsButton } from "../layout/settings"; 11 11 import { ViolationNoticeBar } from "../layout/violationsBar"; 12 - import { APP_NAME, SHOW_SUPPORT_PROGRESS_BAR } from "../siteinfo"; 12 + import { APP_NAME, DASHBOARD_TAG_LINE, SHOW_SUPPORT_PROGRESS_BAR } from "../siteinfo"; 13 13 import { PreloadRules } from "../types"; 14 14 import { 15 15 dashboardScriptStr, ··· 44 44 <h4>{APP_NAME} Dashboard</h4> 45 45 </div> 46 46 <div class="sidebar-block"> 47 - <small><i>Schedule Bluesky posts effortlessly</i>.</small><br /> 47 + <small><i>{DASHBOARD_TAG_LINE}</i>.</small><br /> 48 48 <small>Account: <b id="currentUser" hx-get="/account/username" 49 49 hx-trigger="accountUpdated from:body, load once" hx-target="this"></b></small> 50 50 </div>
+2 -2
src/pages/homepage.tsx
··· 5 5 MAX_POSTS_PER_THREAD, MAX_REPOST_DAYS, MAX_REPOST_IN_HOURS, 6 6 MAX_REPOST_INTERVAL, R2_FILE_SIZE_LIMIT_IN_MB 7 7 } from "../limits"; 8 - import { APP_NAME } from "../siteinfo"; 8 + import { APP_NAME, APP_REPO } from "../siteinfo"; 9 9 10 10 export default function Home() { 11 11 return ( ··· 16 16 <noscript><header>Javascript is required to use this website!</header></noscript> 17 17 <p> 18 18 <strong>{APP_NAME}</strong> is a 19 - free, <a href="https://github.com/socksthewolf/skyscheduler" rel="nofollow" target="_blank">open source</a> service 19 + free, <a href={APP_REPO} rel="nofollow" target="_blank">open source</a> service 20 20 that allows you to schedule and automatically repost your content on Bluesky!<br /> 21 21 Boost engagement and reach more people no matter what time of day!<br /> 22 22 <center>
+3 -3
src/pages/privacy.tsx
··· 1 1 import FooterCopyright from "../layout/helpers/footer"; 2 2 import NavTags from "../layout/helpers/navTags"; 3 3 import { BaseLayout } from "../layout/main"; 4 - import { APP_NAME } from "../siteinfo"; 4 + import { APP_NAME, APP_REPO } from "../siteinfo"; 5 5 6 6 export default function PrivacyPolicy() { 7 7 return ( ··· 28 28 <li>Images are sent to Cloudflare Images compressor to optimize the file size in order to upload onto BlueSky</li> 29 29 <li>CF's Turnstile captcha service is used during signup and password recovery to prevent botted behaviors</li> 30 30 <li>Data is stored in Cloudflare's D1/R2/KV storage containers</li> 31 - <li>Media may be scanned by Cloudflare's 31 + <li>Media may be scanned by Cloudflare's 32 32 &nbsp;<a rel="noopener nofollow noindex" href="https://developers.cloudflare.com/cache/reference/csam-scanning/" class="secondary">illicit material detection service</a>.<br /> 33 33 &nbsp;Said media is not allowed on this service and violators will be banned.</li> 34 34 </ul> ··· 49 49 <li>Data is not accessible to the maintainers of {APP_NAME}</li> 50 50 <li>{APP_NAME} does not sell your data to any third party</li> 51 51 <li>No data is used for genAI purposes nor for training any models</li> 52 - <li>You can verify this by just looking at <a href="https://github.com/socksthewolf/skyscheduler" class="secondary" ref="noopener nofollow">the source code</a></li> 52 + <li>You can verify this by just looking at <a href={APP_REPO} class="secondary" ref="noopener nofollow">the source code</a></li> 53 53 </ul> 54 54 </div> 55 55 </p>
+17
src/siteinfo.ts
··· 2 2 // Basically hard coded site info without the need for CF bindings passed around 3 3 // or having to figure out the domain by parsing request urls. 4 4 5 + // Name of the application 5 6 export const APP_NAME: string = "SkyScheduler"; 7 + // Site URL, used in places where we won't have the CF env bindings (most static rendered assets) 6 8 export const SITE_URL: string = "https://skyscheduler.work"; 9 + // Description of the website, used for meta tags and social cards. 7 10 export const SITE_DESCRIPTION: string = "Schedule and automatically repost on Bluesky! Boost engagement and reach more people no matter what time of day!"; 11 + // Link to the image to display on the social card. 12 + export const SOCIAL_CARD_IMAGE: string = `${SITE_URL}/social-card.png`; 8 13 14 + // The public repository that this application can be found on 15 + export const APP_REPO: string = "https://github.com/SocksTheWolf/skyscheduler"; 16 + 17 + // Author information, used for JSON-LD and footers 18 + export const PROJECT_AUTHOR: string = "SocksTheWolf"; 19 + export const PROJECT_AUTHOR_SITE: string = "https://socksthewolf.com"; 20 + 21 + // This line shows up on the dashboard when the user logs in, located under the logo. 22 + export const DASHBOARD_TAG_LINE: string = "Schedule Bluesky posts effortlessly"; 23 + 24 + // If the logo image should be rendered on the site. 25 + export const LOGO_ENABLED: boolean = true; 9 26 10 27 // if the support bar should be shown or not. Currently is only visible on the dashboard page 11 28 export const SHOW_SUPPORT_PROGRESS_BAR: boolean = false;
+3 -3
src/wrangler.d.ts
··· 1 1 /* eslint-disable */ 2 - // Generated by Wrangler by running `wrangler types src/wrangler.d.ts` (hash: b2ae2f1009d7b9bb3d9bfd77101d2f5b) 3 - // Runtime types generated with workerd@1.20260217.0 2025-11-18 disable_ctx_exports,disable_nodejs_http_server_modules,nodejs_compat,nodejs_compat_do_not_populate_process_env 2 + // Generated by Wrangler by running `wrangler types src/wrangler.d.ts` (hash: dce2dba190f574b7f369a14b8358ae53) 3 + // Runtime types generated with workerd@1.20260219.0 2025-11-18 disable_ctx_exports,disable_nodejs_http_server_modules,nodejs_compat,nodejs_compat_do_not_populate_process_env 4 4 declare namespace Cloudflare { 5 5 interface GlobalProps { 6 6 mainModule: typeof import("./index"); ··· 44 44 R2: R2Bucket; 45 45 DB: D1Database; 46 46 IMAGES: ImagesBinding; 47 - IMAGE_SETTINGS: {"enabled":false} | {"enabled":true,"steps":[95,85,75],"bucket_url":"https://resize.skyscheduler.work/"}; 47 + IMAGE_SETTINGS: {"enabled":false} | {"enabled":true,"steps":[95,85,75],"bucket_url":"https://resize.skyscheduler.work/","max_width":3000}; 48 48 SIGNUP_SETTINGS: {"use_captcha":false,"invite_only":false,"invite_thread":"","invite_uses":10} | {"use_captcha":true,"invite_only":false,"invite_thread":"","invite_uses":10}; 49 49 QUEUE_SETTINGS: {"enabled":false,"repostsEnabled":false,"postNowEnabled":false,"threadEnabled":true,"delay_val":100,"post_queues":["POST_QUEUE"],"repost_queues":[]} | {"enabled":true,"repostsEnabled":true,"postNowEnabled":false,"threadEnabled":true,"delay_val":100,"post_queues":["POST_QUEUE"],"repost_queues":["REPOST_QUEUE"]}; 50 50 REDIRECTS: {"contact":"https://bsky.app/profile/skyscheduler.work","tip":"https://ko-fi.com/socksthewolf/tip"};