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

allow for the ability for us to shut off gifs

+39 -9
+4 -1
assets/js/postHelper.js
··· 232 232 pushToast(`${file.name} duration could not be processed`, false); 233 233 deleteFileOnError(); 234 234 } else if (videoDuration > MAX_VIDEO_LENGTH) { 235 - pushToast(`${file.name} is too long for bsky by ${(videoDuration - MAX_VIDEO_LENGTH).toFixed(2)} seconds`, false); 235 + pushToast(`${file.name} is over the maximum video duration by ${(videoDuration - MAX_VIDEO_LENGTH).toFixed(2)} seconds`, false); 236 + deleteFileOnError(); 237 + } else if (videoDuration >= MAX_GIF_LENGTH) { 238 + pushToast(`${file.name} is over the maximum length for a gif by ${(videoDuration - MAX_GIF_LENGTH).toFixed(2)} seconds`, false); 236 239 deleteFileOnError(); 237 240 } else { 238 241 fileData.set(file.name, { content: response.data, type: 3, height: imageHeight, width: imageWidth, duration: videoDuration });
+7
package-lock.json
··· 18 18 "just-has": "^2.3.0", 19 19 "just-is-empty": "^3.4.1", 20 20 "just-random": "^3.2.0", 21 + "just-remove": "^3.2.0", 21 22 "just-safe-get": "^4.2.0", 22 23 "just-split": "^3.2.0", 23 24 "just-truncate": "^2.2.0", ··· 4005 4006 "version": "3.2.0", 4006 4007 "resolved": "https://registry.npmjs.org/just-random/-/just-random-3.2.0.tgz", 4007 4008 "integrity": "sha512-RMf8vbtCfLIbAEHvIPu2FwMkpB/JudGyk/VPfqPItcRgt7k8QnV+Aa7s7kRFPo+bavQkUi8Yg1x/ooW6Ttyb9A==", 4009 + "license": "MIT" 4010 + }, 4011 + "node_modules/just-remove": { 4012 + "version": "3.2.0", 4013 + "resolved": "https://registry.npmjs.org/just-remove/-/just-remove-3.2.0.tgz", 4014 + "integrity": "sha512-gR3EOFIkZapF3g0K4IRRqH4lM+czJEfaOFgrwZl5vNfVUeJqfke9/L67Mf3CgnT3j2HM46C+mdCRvqaCD77Nqg==", 4008 4015 "license": "MIT" 4009 4016 }, 4010 4017 "node_modules/just-safe-get": {
+1
package.json
··· 37 37 "just-has": "^2.3.0", 38 38 "just-is-empty": "^3.4.1", 39 39 "just-random": "^3.2.0", 40 + "just-remove": "^3.2.0", 40 41 "just-safe-get": "^4.2.0", 41 42 "just-split": "^3.2.0", 42 43 "just-truncate": "^2.2.0",
+11 -3
src/limits.d.ts
··· 1 + import remove from "just-remove"; 2 + 1 3 /** APPLICATION CONFIGURATIONS **/ 2 4 // minimum length of a post 3 5 export const MIN_LENGTH: number = 1; ··· 7 9 export const MAX_REPOST_DAYS: number = 10; 8 10 // max amount of days to hold a post after it's been posted and has no reposts before it's purged from the DB 9 11 export const MAX_HOLD_DAYS_BEFORE_PURGE: number = 7; 12 + // max length of an animated gif in minutes 13 + export const MAX_GIF_LENGTH: number = 1; 14 + // if gifs should be allowed to upload 15 + export const GIF_UPLOAD_ALLOWED: boolean = false; 10 16 11 17 // This is the length of how much we keep in the DB after a post has been made 12 18 export const MAX_POSTED_LENGTH: number = 50; ··· 95 101 ]; 96 102 97 103 // Used for human readable display 98 - export const BSKY_VIDEO_FILE_EXTS: string = [ 104 + export const BSKY_VIDEO_FILE_EXTS: string = remove([ 99 105 "mp4", 100 106 "m4v", 101 107 "mp4v", ··· 107 113 "mov", 108 114 "qt", 109 115 "webm", 110 - "animated gif" /* This is handled in a special case because bluesky */ 111 - ].join(", "); 116 + /* This is handled in a special case because bluesky */ 117 + (GIF_UPLOAD_ALLOWED ? "animated gif" : undefined) 118 + ], [undefined]).join(", "); 112 119 113 120 // Max size of files that can go to R2 without doing multipart uploads 114 121 export const R2_FILE_SIZE_LIMIT_IN_MB: number = 100; ··· 116 123 export const BSKY_IMG_SIZE_LIMIT: number = BSKY_IMG_SIZE_LIMIT_IN_MB * MB_TO_BYTES; 117 124 export const BSKY_VIDEO_SIZE_LIMIT: number = BSKY_VIDEO_MAX_SIZE_IN_MB * MB_TO_BYTES; 118 125 export const BSKY_VIDEO_LENGTH_LIMIT: number = BSKY_VIDEO_MAX_DURATION * TO_SEC; 126 + export const MAX_GIF_LENGTH_LIMIT: number = MAX_GIF_LENGTH * TO_SEC; 119 127 // Max size of Cloudflare Images files 120 128 export const CF_IMAGES_FILE_SIZE_LIMIT_IN_MB: number = 70; 121 129 export const CF_IMAGES_FILE_SIZE_LIMIT: number = CF_IMAGES_FILE_SIZE_LIMIT_IN_MB * MB_TO_BYTES;
+1 -1
src/utils/appScripts.ts
··· 1 1 // Change this value to break out of any caching that might be happening 2 2 // for the runtime scripts (ex: main.js & postHelper.js) 3 - export const CURRENT_SCRIPT_VERSION: string = "1.4.1"; 3 + export const CURRENT_SCRIPT_VERSION: string = "1.4.2"; 4 4 5 5 export const getAppScriptStr = (scriptName: string) => `/js/${scriptName}.min.js?v=${CURRENT_SCRIPT_VERSION}`; 6 6
+10 -3
src/utils/constScriptGen.ts
··· 6 6 BSKY_VIDEO_MIME_TYPES, 7 7 MAX_ALT_TEXT, 8 8 MAX_EMBEDS_PER_POST, 9 + MAX_GIF_LENGTH_LIMIT, 9 10 MAX_LENGTH, 10 11 MAX_THUMBNAIL_SIZE, 11 - R2_FILE_SIZE_LIMIT 12 + R2_FILE_SIZE_LIMIT, 13 + GIF_UPLOAD_ALLOWED 12 14 } from "../limits.d"; 13 15 import { PreloadRules } from "../types.d"; 14 16 import { postRecordURI } from "../validation/regexCases"; 15 17 16 - const CONST_SCRIPT_VERSION: number = 7; 18 + const CONST_SCRIPT_VERSION: number = 8; 17 19 18 20 const makeFileTypeStr = (typeMap: string[]) => { 19 21 return typeMap.map((type) => `"${type}"`).join() ··· 24 26 ]; 25 27 26 28 export function makeConstScript() { 27 - return `const fileTypesSupported = [${makeFileTypeStr([...BSKY_IMG_MIME_TYPES, ...BSKY_VIDEO_MIME_TYPES, ...BSKY_GIF_MIME_TYPES])}]; 29 + let fileTypeArray = [...BSKY_IMG_MIME_TYPES, ...BSKY_VIDEO_MIME_TYPES]; 30 + if (GIF_UPLOAD_ALLOWED) { 31 + fileTypeArray.push(...BSKY_GIF_MIME_TYPES); 32 + } 33 + return `const fileTypesSupported = [${makeFileTypeStr(fileTypeArray)}]; 28 34 const imageTypes = [${makeFileTypeStr(BSKY_IMG_MIME_TYPES)}]; 29 35 const videoTypes = [${makeFileTypeStr(BSKY_VIDEO_MIME_TYPES)}]; 30 36 const gifTypes = [${makeFileTypeStr(BSKY_GIF_MIME_TYPES)}]; 31 37 const MAX_LENGTH=${MAX_LENGTH}; 32 38 const MAX_ALT_LENGTH=${MAX_ALT_TEXT}; 33 39 const MAX_VIDEO_LENGTH=${BSKY_VIDEO_LENGTH_LIMIT}; /* in seconds */ 40 + const MAX_GIF_LENGTH=${MAX_GIF_LENGTH_LIMIT}; /* in seconds */ 34 41 const MAX_AUTO_COMPLETE_NAMES=${BSKY_NAME_LOOKUP_LIMIT}; 35 42 const MIN_CHAR_AUTO_COMPLETE_NAMES=${BSKY_NAME_TYPE_AHEAD_CHARS}; 36 43 const FILE_DROP_MAX_SIZE=${R2_FILE_SIZE_LIMIT};
+5 -1
src/utils/r2Query.ts
··· 225 225 const fileType: string = file.type.toLowerCase(); 226 226 if (BSKY_IMG_MIME_TYPES.includes(fileType)) { 227 227 return await uploadImageToR2(c, file, userId); 228 - } else if (BSKY_VIDEO_MIME_TYPES.includes(fileType) || BSKY_GIF_MIME_TYPES.includes(fileType)) { 228 + } else if (BSKY_VIDEO_MIME_TYPES.includes(fileType)) { 229 + return await uploadVideoToR2(c.env, file, userId); 230 + } else if (BSKY_GIF_MIME_TYPES.includes(fileType)) { 231 + // TODO: modify this in the future to transform the image to a webm 232 + // then push to uploadVideo 229 233 return await uploadVideoToR2(c.env, file, userId); 230 234 } 231 235 return {"success": false, "error": "unable to push to R2"};