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

up some values yeah

+15 -7
+3 -3
src/endpoints/account.tsx
··· 44 44 } 45 45 46 46 // wrapper to login 47 - account.post("/login", async (c) => { 47 + account.post("/login", rateLimit({limiter: "ACCOUNT_LIMITER"}), async (c) => { 48 48 const body = await c.req.json(); 49 49 const auth = c.get("auth"); 50 50 const validation = LoginSchema.safeParse(body); ··· 137 137 return c.text(""); 138 138 }); 139 139 140 - account.post("/signup", verifyTurnstile, async (c: Context) => { 140 + account.post("/signup", verifyTurnstile, rateLimit({limiter: "ACCOUNT_LIMITER"}), async (c: Context) => { 141 141 const body = await c.req.json(); 142 142 const validation = SignupSchema.safeParse(body); 143 143 if (!validation.success) { ··· 253 253 return c.json({ok: true, msg: "request processed"}); 254 254 }); 255 255 256 - account.post("/reset", async (c: Context) => { 256 + account.post("/reset", rateLimit({limiter: "ACCOUNT_LIMITER"}), async (c: Context) => { 257 257 const body = await c.req.json(); 258 258 259 259 const validation = AccountResetSchema.safeParse(body);
+4 -2
src/middleware/rateLimit.ts
··· 7 7 type RateLimitProps = { 8 8 limiter: string; 9 9 html?: boolean; 10 + message?: string; 10 11 }; 11 12 12 13 export const rateLimit = (prop: RateLimitProps) => { ··· 22 23 if (success) { 23 24 await next(); 24 25 } else { 26 + const str: string = prop.message || "You are currently rate limited, try again later"; 25 27 if (prop.html) { 26 - return c.html(html`<b class="btn-error">You are currently rate limited, try again later</b>`); 28 + return c.html(html`<b class="btn-error">${str}</b>`); 27 29 } else { 28 - return c.json({ok: false, msg: "You are currently rate limited, try again later"}, 429); 30 + return c.json({ok: false, msg: str}, 429); 29 31 } 30 32 } 31 33 });
+1
src/types.ts
··· 65 65 POST_LIMITER: RateLimit; 66 66 REPOST_LIMITER: RateLimit; 67 67 UPDATE_LIMITER: RateLimit; 68 + ACCOUNT_LIMITER: RateLimit; 68 69 DEFAULT_ADMIN_USER: string; 69 70 DEFAULT_ADMIN_PASS: string; 70 71 DEFAULT_ADMIN_BSKY_PASS: string;
+2 -1
src/wrangler.d.ts
··· 1 1 /* eslint-disable */ 2 - // Generated by Wrangler by running `wrangler types src/wrangler.d.ts` (hash: ede590ad561b49f51ae6fcdf7fe7f2a3) 2 + // Generated by Wrangler by running `wrangler types src/wrangler.d.ts` (hash: e2b1e0c3605cf1bbdd0ecf0cdf57036b) 3 3 // Runtime types generated with workerd@1.20260305.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 { ··· 16 16 REPOST_LIMITER: RateLimit; 17 17 POST_LIMITER: RateLimit; 18 18 UPDATE_LIMITER: RateLimit; 19 + ACCOUNT_LIMITER: RateLimit; 19 20 IMAGES: ImagesBinding; 20 21 ASSETS: Fetcher; 21 22 IMAGE_SETTINGS: {"enabled":true,"steps":[95,85,75],"bucket_url":"https://resize.skyscheduler.work/","max_width":3000};
+5 -1
wrangler.toml
··· 46 46 [[ratelimits]] 47 47 name = "POST_LIMITER" 48 48 namespace_id = "1002" 49 - simple = { limit = 3, period = 10 } 49 + simple = { limit = 5, period = 10 } 50 50 [[ratelimits]] 51 51 name = "UPDATE_LIMITER" 52 52 namespace_id = "1003" 53 53 simple = { limit = 7, period = 60 } 54 + [[ratelimits]] 55 + name = "ACCOUNT_LIMITER" 56 + namespace_id = "1004" 57 + simple = { limit = 10, period = 60 } 54 58 55 59 [triggers] 56 60 # Schedule cron triggers at the start of every hour and ~5:30pm on sunday for big cleanups: