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

more customization go!

+75 -53
+2 -1
.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 10 + RESIZE_SECRET_HEADER=somecoollongstring 11 + IN_DEV=true
+1
.gitignore
··· 17 17 18 18 # env 19 19 .env 20 + .env.prod 20 21 .env.production 21 22 .dev.vars 22 23
+7 -5
README.md
··· 28 28 29 29 - Node.js (v24.x or later) 30 30 - Package Manager 31 - - Cloudflare Pro Workers account (for CPU) 31 + - Cloudflare Pro Workers account (you will hit CPU limits otherwise) 32 32 33 33 ### Installation 34 34 ··· 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>`. 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. 59 59 60 60 4. Update your `wrangler.toml` with changes that reflect your account. 61 - - You'll need to update the values for the kv, r2, d1 to reflect the bindings on your account. 61 + - You'll need to update the values for the kv, r2, queues, d1 to reflect the bindings on your account. 62 62 - Also make sure you update the `BETTER_AUTH_URL` to your working url as well. 63 63 - Do remember to remove the domain bindings! 64 64 ··· 86 86 npm run migrate:all 87 87 ``` 88 88 89 - 9. Modify the metatags located in the `metaTags.tsx` (these are currently set up for the website attached to this project) 89 + 9. Modify the site specific information located in `limits.ts`, `metaTags.tsx`, `robots.txt`, `sitemap.xml` (these are currently set up for the website attached to this project) 90 90 91 91 10. Run your application and go to `/setup`. This will create the admin account. 92 92 ··· 102 102 103 103 ### Minimization 104 104 105 - The application by default is configured to use the minified versions of the scripts in `assets/js`. By default these rebuild whenever any typescript file is changed or the application is deployed/ran. 105 + The application by default is configured to use the minified versions of the scripts in `assets/js`. By default all client JS files will rebuild whenever any typescript file is changed or the application is deployed/ran. 106 106 107 107 ## Project Structure 108 108 ··· 121 121 ├── migrations/ 122 122 ├── .dev.vars 123 123 ├── .node-version 124 + ├── .markdownlint.json 124 125 ├── .minify.json 125 126 ├── drizzle.config.ts 126 127 ├── package.json ··· 160 161 - Report bugs 161 162 - Suggest enhancements 162 163 - Submit pull requests 164 + - [Sponsor](https://ko-fi.com/socksthewolf/tip) 163 165 164 166 ## License 165 167
+3 -3
src/auth/index.ts
··· 4 4 import { username } from "better-auth/plugins"; 5 5 import { drizzle, DrizzleD1Database } from "drizzle-orm/d1"; 6 6 import { schema } from "../db"; 7 - import { BSKY_MAX_USERNAME_LENGTH, BSKY_MIN_USERNAME_LENGTH } from "../limits"; 7 + import { APP_NAME, BSKY_MAX_USERNAME_LENGTH, BSKY_MIN_USERNAME_LENGTH } from "../limits"; 8 8 import { Bindings } from "../types.d"; 9 9 import { lookupBskyHandle } from "../utils/bskyApi"; 10 10 import { createDMWithUser } from "../utils/bskyMsg"; 11 11 12 12 function createPasswordResetMessage(url: string) { 13 - return `Your SkyScheduler password reset url is: 13 + return `Your ${APP_NAME} password reset url is: 14 14 ${url} 15 15 16 16 This URL will expire in about an hour. ··· 107 107 }, 108 108 } 109 109 ), 110 - appName: "SkyScheduler", 110 + appName: APP_NAME, 111 111 secret: env?.BETTER_AUTH_SECRET, 112 112 baseURL: (env?.BETTER_AUTH_URL === "*") ? undefined : env?.BETTER_AUTH_URL, 113 113 user: {
+3 -2
src/layout/helpers/footer.tsx
··· 1 + import { APP_NAME } from "../../limits"; 1 2 import { PROGRESS_MADE, PROGRESS_TOTAL } from "../../progress"; 2 3 3 4 // Helper footer for various pages ··· 10 11 export default function FooterCopyright(props: FooterCopyrightProps) { 11 12 const newWinAttr = props.inNewWindow ? {"target": '_blank'} : {}; 12 13 const projectURL = (<a class="secondary" target="_blank" title="Project source on GitHub" 13 - href="https://github.com/SocksTheWolf/SkyScheduler">SkyScheduler</a>); 14 - const homepageURL = (<a class="secondary" title="Homepage" href="/">SkyScheduler</a>); 14 + href="https://github.com/SocksTheWolf/SkyScheduler">{APP_NAME}</a>); 15 + const homepageURL = (<a class="secondary" title="Homepage" href="/">{APP_NAME}</a>); 15 16 const progressBarTooltip = `$${PROGRESS_MADE}/$${PROGRESS_TOTAL} for this month`; 16 17 return ( 17 18 <center><small>
+6 -6
src/layout/helpers/metaTags.tsx
··· 1 1 import { raw } from "hono/html"; 2 + import { APP_NAME } from "../../limits"; 2 3 3 4 export default function MetaTags() { 4 5 /* Modify these to change meta information on all pages. */ 5 - const Title: string = "SkyScheduler"; 6 6 const URL: string = "https://skyscheduler.work"; 7 7 const Description: string = "Schedule and automatically repost on Bluesky! Boost engagement and reach more people no matter what time of day!"; 8 8 const SocialImage: string = "https://skyscheduler.work/dashboard.png"; 9 9 10 10 return ( 11 11 <> 12 - <meta name="title" content={Title} /> 12 + <meta name="title" content={APP_NAME} /> 13 13 <meta name="description" content={Description} /> 14 14 <meta property="og:type" content="website" /> 15 15 <meta property="og:url" content={URL} /> 16 - <meta property="og:title" content={Title} /> 16 + <meta property="og:title" content={APP_NAME} /> 17 17 <meta property="og:description" content={Description} /> 18 18 <meta property="og:image" content={SocialImage} /> 19 19 <meta property="twitter:card" content="summary_large_image" /> 20 20 <meta property="twitter:url" content={URL} /> 21 - <meta property="twitter:title" content={Title} /> 21 + <meta property="twitter:title" content={APP_NAME} /> 22 22 <meta property="twitter:description" content={Description} /> 23 23 <meta property="twitter:image" content={SocialImage} /> 24 24 <script type="application/ld+json"> 25 25 {raw(`{ 26 26 "@context": "https://schema.org", 27 27 "@type": "WebSite", 28 - "name": "${Title}", 29 - "headline": "${Title}", 28 + "name": "${APP_NAME}", 29 + "headline": "${APP_NAME}", 30 30 "url": "${URL}" 31 31 }`)} 32 32 </script>
+3 -1
src/layout/helpers/navTags.tsx
··· 1 + import { APP_NAME } from "../../limits"; 2 + 1 3 export default function NavTags() { 2 4 return ( 3 5 <div class="container"> 4 6 <nav> 5 7 <ul> 6 - <li><h2><a class="contrast" href="/">SkyScheduler</a></h2></li> 8 + <li><h2><a class="contrast" href="/">{APP_NAME}</a></h2></li> 7 9 </ul> 8 10 <ul> 9 11 <li><a href="/signup">Sign Up</a></li>
+4 -3
src/layout/main.tsx
··· 1 1 import { html } from 'hono/html'; 2 2 import { Child } from 'hono/jsx'; 3 + import { APP_NAME } from '../limits'; 3 4 import { PreloadRules } from '../types.d'; 4 5 import { mainScriptStr } from '../utils/appScripts'; 5 6 import { PreloadDependencyTags } from './helpers/includesTags'; ··· 7 8 8 9 type BaseLayoutProps = { 9 10 children: Child; 10 - title?: string; 11 + title: string; 11 12 mainClass?: string; 12 13 preloads?: PreloadRules[] 13 14 }; 14 15 15 16 export const BaseLayout = ({ 16 17 children, 17 - title = "SkyScheduler", 18 + title, 18 19 mainClass = "", 19 20 preloads = [] 20 21 }: BaseLayoutProps) => { ··· 22 23 <html data-theme="dark" lang="en"> 23 24 <head> 24 25 <meta charset="UTF-8" /> 25 - <title>{title}</title> 26 + <title>{APP_NAME} - {title}</title> 26 27 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 27 28 <link rel="preload" href="/dep/htmx.min.js" as="script" /> 28 29 <link rel="preload" href="/dep/toastify.js" as="script" />
+2 -1
src/layout/makePost.tsx
··· 1 1 import { 2 + APP_NAME, 2 3 BSKY_IMG_FILE_EXTS, 3 4 BSKY_IMG_SIZE_LIMIT_IN_MB, 4 5 BSKY_VIDEO_FILE_EXTS, ··· 62 63 <li><span data-tooltip={BSKY_IMG_FILE_EXTS}>Images</span>: 63 64 <ul> 64 65 <li>must be less than {CF_IMAGES_MAX_DIMENSION}x{CF_IMAGES_MAX_DIMENSION} pixels</li> 65 - <li>must have a file size smaller than {CF_IMAGES_FILE_SIZE_LIMIT_IN_MB}MB (SkyScheduler will attempt to compress images to fit <span data-tooltip={bskyImageLimits}>BlueSky's requirements</span>)</li> 66 + <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>)</li> 66 67 <li>thumbnails will only be shown here for images that are smaller than {MAX_THUMBNAIL_SIZE}MB</li> 67 68 <li>don't upload and fail, it's recommended to use a lower resolution file instead</li> 68 69 </ul></li>
+4 -4
src/layout/settings.tsx
··· 1 - import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits"; 1 + import { APP_NAME, MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits"; 2 2 import { PWAutoCompleteSettings } from "../types.d"; 3 3 import { settingsScriptStr } from "../utils/appScripts"; 4 4 import { BSkyAppPasswordField, DashboardPasswordField } from "./passwordFields"; ··· 32 32 <label> 33 33 Dashboard Pass: 34 34 <DashboardPasswordField autocomplete={PWAutoCompleteSettings.CurrentPass} /> 35 - <small>The password to access the SkyScheduler Dashboard</small> 35 + <small>The password to access the {APP_NAME} Dashboard</small> 36 36 </label> 37 37 <label> 38 38 BSky App Password: ··· 60 60 <dialog id="deleteAccount"> 61 61 <article> 62 62 <header>Delete Account</header> 63 - <p>To delete your SkyScheduler account, please type your password below.<br /> 63 + <p>To delete your {APP_NAME} account, please type your password below.<br /> 64 64 All pending, scheduled posts + all unposted media will be deleted from this service. 65 65 66 66 <center><strong>NOTE</strong>: THIS ACTION IS <u>PERMANENT</u>.</center> ··· 71 71 <label> 72 72 Dashboard Pass: <input id="deleteAccountPass" type="password" name="password" 73 73 minlength={MIN_DASHBOARD_PASS} maxlength={MAX_DASHBOARD_PASS} /> 74 - <small>The password to access the SkyScheduler Dashboard</small> 74 + <small>The password to access the {APP_NAME} Dashboard</small> 75 75 </label> 76 76 </form> 77 77 <progress id="delSpinner" class="htmx-indicator" />
+2 -1
src/layout/violationsBar.tsx
··· 1 1 import { Context } from "hono"; 2 + import { APP_NAME } from "../limits"; 2 3 import { getViolationsForCurrentUser } from "../utils/db/violations"; 3 4 4 5 type ViolationNoticeProps = { ··· 11 12 if (violationData !== null) { 12 13 let errorStr = ""; 13 14 if (violationData.tosViolation) { 14 - errorStr = "Your account is in violation of SkyScheduler usage."; 15 + errorStr = `Your account is in violation of ${APP_NAME} usage.`; 15 16 } else if(violationData.userPassInvalid) { 16 17 errorStr = "Your Bluesky handle or application password is invalid. Please update these in the settings."; 17 18 } else if (violationData.accountSuspended) {
+1
src/limits.ts
··· 1 1 import remove from "just-remove"; 2 2 3 3 /** APPLICATION CONFIGURATIONS **/ 4 + export const APP_NAME: string = "SkyScheduler"; 4 5 // minimum length of a post 5 6 export const MIN_LENGTH: number = 1; 6 7 // max amount of times something can be reposted
+3 -2
src/pages/dashboard.tsx
··· 8 8 import { ScheduledPostList } from "../layout/postList"; 9 9 import { Settings, SettingsButton } from "../layout/settings"; 10 10 import { ViolationNoticeBar } from "../layout/violationsBar"; 11 + import { APP_NAME } from "../limits"; 11 12 import { SHOW_PROGRESS_BAR } from "../progress"; 12 13 import { PreloadRules } from "../types.d"; 13 14 import { ··· 37 38 return {href: itm, type: "script"}; 38 39 }); 39 40 return ( 40 - <BaseLayout title="SkyScheduler - Dashboard" mainClass="dashboard" 41 + <BaseLayout title="Dashboard" mainClass="dashboard" 41 42 preloads={[...PreloadPostCreation, ...defaultDashboardPreloads, ...dashboardScripts]}> 42 43 <IncludeDependencyTags scripts={defaultDashboardPreloads} /> 43 44 <div class="row-fluid"> 44 45 <section class="col-3"> 45 46 <article> 46 47 <header> 47 - <h4>SkyScheduler Dashboard</h4> 48 + <h4>{APP_NAME} Dashboard</h4> 48 49 <div class="sidebar-block"> 49 50 <small><i>Schedule Bluesky posts effortlessly</i>.</small><br /> 50 51 <small>Account: <b class="truncate" id="currentUser" hx-get="/account/username"
+5 -4
src/pages/forgot.tsx
··· 1 1 import { Context } from "hono"; 2 2 import AccountHandler from "../layout/account"; 3 3 import FooterCopyright from "../layout/helpers/footer"; 4 - import { BaseLayout } from "../layout/main"; 5 4 import NavTags from "../layout/helpers/navTags"; 6 5 import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/helpers/turnstile"; 6 + import { BaseLayout } from "../layout/main"; 7 7 import { UsernameField } from "../layout/usernameField"; 8 + import { APP_NAME } from "../limits"; 8 9 9 10 export default function ForgotPassword(props:any) { 10 11 const ctx: Context = props.c; 11 - const botAccountURL:string = `https://bsky.app/profile/${ctx.env.RESET_BOT_USERNAME}`; 12 + const botAccountURL: string = `https://bsky.app/profile/${ctx.env.RESET_BOT_USERNAME}`; 12 13 return ( 13 - <BaseLayout title="SkyScheduler - Forgot Password" 14 + <BaseLayout title="Forgot Password" 14 15 preloads={[...TurnstileCaptchaPreloads(ctx)]}> 15 16 <NavTags /> 16 17 <AccountHandler title="Forgot Password Reset" ··· 26 27 If you encounter errors, your <a href="https://bsky.app/messages/settings" class="secondary" rel="nofollow" target="_blank">Direct Communication settings</a> might be set to forbid 27 28 Direct Messages from accounts you don't follow.<br /><br /> 28 29 It is <u>heavily recommended</u> to <a href={botAccountURL} target="_blank">follow the service account</a>.<br /><br /> 29 - <small><b>NOTE</b>: SkyScheduler sends DMs via an one-way delivery method. No one (other than you) can see the account password reset URL.</small></p> 30 + <small><b>NOTE</b>: {APP_NAME} sends DMs via an one-way delivery method. No one (other than you) can see the account password reset URL.</small></p> 30 31 </center> 31 32 32 33 <UsernameField />
+5 -4
src/pages/homepage.tsx
··· 2 2 import { BaseLayout } from "../layout/main"; 3 3 import NavTags from "../layout/helpers/navTags"; 4 4 import { 5 + APP_NAME, 5 6 MAX_POSTS_PER_THREAD, MAX_REPOST_DAYS, MAX_REPOST_IN_HOURS, 6 7 MAX_REPOST_INTERVAL, R2_FILE_SIZE_LIMIT_IN_MB 7 8 } from "../limits"; 8 9 9 10 export default function Home() { 10 11 return ( 11 - <BaseLayout title="SkyScheduler - Home" mainClass="homepage"> 12 + <BaseLayout title="Home" mainClass="homepage"> 12 13 <NavTags /> 13 14 <section class="container"> 14 15 <article> 15 16 <noscript><header>Javascript is required to use this website</header></noscript> 16 17 <p> 17 - <strong>SkyScheduler</strong> is a 18 + <strong>{APP_NAME}</strong> is a 18 19 free, <a href="https://github.com/socksthewolf/skyscheduler" rel="nofollow" target="_blank">open source</a> service 19 20 that lets you schedule and automatically repost your content on Bluesky!<br /> 20 21 Boost engagement and reach more people no matter what time of day!<br /> ··· 23 24 <img 24 25 src="/dashboard.png" 25 26 fetchpriority="high" 26 - alt="Picture of SkyScheduler Dashboard" 27 + alt={`Picture of ${APP_NAME} Dashboard`} 27 28 height="618px" 28 29 width="1200px" 29 30 /> 30 31 <figcaption> 31 - An amazing picture of SkyScheduler's Dashboard, wow! 32 + An amazing picture of {APP_NAME}'s Dashboard, wow! 32 33 </figcaption> 33 34 </figure> 34 35 </center>
+1 -1
src/pages/login.tsx
··· 8 8 export default function Login() { 9 9 const links = [{title: "Sign Up", url: "/signup"}, {title: "Forgot Password", url: "/forgot"}]; 10 10 return ( 11 - <BaseLayout title="SkyScheduler - Login"> 11 + <BaseLayout title="Login"> 12 12 <NavTags /> 13 13 <AccountHandler title="Login" 14 14 loadingText="Logging in..."
+1 -1
src/pages/privacy.tsx
··· 4 4 5 5 export default function PrivacyPolicy() { 6 6 return ( 7 - <BaseLayout title="SkyScheduler - Privacy Policy" mainClass="homepage"> 7 + <BaseLayout title="Privacy Policy" mainClass="homepage"> 8 8 <NavTags /> 9 9 <section class="container"> 10 10 <article>
+1 -1
src/pages/reset.tsx
··· 6 6 export default function ResetPassword() { 7 7 const links = [{title: "Forgot Password", url: "/forgot"}]; 8 8 return ( 9 - <BaseLayout title="SkyScheduler - Reset Password"> 9 + <BaseLayout title="Reset Password"> 10 10 <NavTags /> 11 11 <AccountHandler title="Reset Password" 12 12 loadingText="Resetting Password..."
+1 -1
src/pages/signup.tsx
··· 17 17 "You can ask for the maintainer for it"; 18 18 19 19 return ( 20 - <BaseLayout title="SkyScheduler - Signup" 20 + <BaseLayout title="Signup" 21 21 preloads={[...TurnstileCaptchaPreloads(ctx)]}> 22 22 <NavTags /> 23 23 <AccountHandler title="Create an Account"
+8 -7
src/pages/tos.tsx
··· 1 1 import FooterCopyright from "../layout/helpers/footer"; 2 2 import { BaseLayout } from "../layout/main"; 3 3 import NavTags from "../layout/helpers/navTags"; 4 + import { APP_NAME } from "../limits"; 4 5 5 6 export default function TermsOfService() { 6 7 return ( 7 - <BaseLayout title="SkyScheduler - TOS" mainClass="homepage"> 8 + <BaseLayout title="TOS" mainClass="homepage"> 8 9 <NavTags /> 9 10 <section class="container"> 10 11 <article> 11 12 <header><h3>Terms of Service</h3></header> 12 13 <h4>Terms</h4> 13 14 <p> 14 - By signing up and using the services provided by SkyScheduler ("software") you agree to the terms set forth below.<br /> 15 + By signing up and using the services provided by {APP_NAME} ("software") you agree to the terms set forth below.<br /> 15 16 If you do not agree to said terms please delete your account at your earliest convenience. Said deletion will be treated 16 17 as a separation of you to this agreement. 17 18 </p> 18 19 <h4>Usage</h4> 19 - <p>By using SkyScheduler you agree to: 20 + <p>By using this software you agree to: 20 21 <ol> 21 22 <li>Not use the service to scam, spam or to otherwise violate the terms of the <a class="secondary" href="https://bsky.social/about/support/tos" rel="nofollow noindex noopener" target="_blank">Bluesky Terms of Service</a></li> 22 23 <li>Not upload material that is illegal, illicit or stolen</li> 23 24 <li>Not attempt to reverse engineer the software to cause damage or otherwise harm others</li> 24 - <li>Not hold SkyScheduler at fault for any damages, neither perceived nor tangible</li> 25 - <li>Grant SkyScheduler a temporary, non-exclusive, royalty-free license to the content that you schedule for the sole purpose of transmitting it on your behalf via the ATProtocol to the PDS of your choosing (default: Bluesky).</li> 25 + <li>Not hold the software nor its developers at fault for any damages, neither perceived nor tangible</li> 26 + <li>Grant {APP_NAME} a temporary, non-exclusive, royalty-free license to the content that you schedule for the sole purpose of transmitting it on your behalf via the ATProtocol to the PDS of your choosing (default: Bluesky).</li> 26 27 <ul> 27 28 <li>Upon successful transmission, content will be deleted from our temporary holding storage.</li> 28 29 </ul> 29 30 </ol> 30 31 <hr /> 31 - Violations of these agreements will allow SkyScheduler to terminate your access to the website. Upon account deletion/termination, all temporarily stored content will be deleted.<br /> 32 + Violations of these agreements will allow {APP_NAME} to terminate your access to the website. Upon account deletion/termination, all temporarily stored content will be deleted.<br /> 32 33 Deletions may take up to 30 days to fully cycle out of backups. 33 34 </p> 34 35 <h4>Disclaimer/Limitations</h4> 35 - <p>SkyScheduler IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 36 + <p>{APP_NAME} IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 36 37 IN NO EVENT SHALL THE AUTHORS, HOSTS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 37 38 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p> 38 39 <h4>Ammendum</h4>
+3 -3
src/utils/dbQuery.ts
··· 7 7 import { v4 as uuidv4, validate as uuidValid } from 'uuid'; 8 8 import { mediaFiles, posts, repostCounts, reposts } from "../db/app.schema"; 9 9 import { accounts, users } from "../db/auth.schema"; 10 - import { MAX_POSTS_PER_THREAD, MAX_REPOST_POSTS, MAX_REPOST_RULES_PER_POST } from "../limits"; 10 + import { APP_NAME, MAX_POSTS_PER_THREAD, MAX_REPOST_POSTS, MAX_REPOST_RULES_PER_POST } from "../limits"; 11 11 import { 12 12 AccountStatus, 13 13 AllContext, ··· 196 196 const violationData = await getViolationsForUser(db, userId); 197 197 if (violationData != null) { 198 198 if (violationData.tosViolation) { 199 - return {ok: false, msg: "This account is unable to use SkyScheduler services at this time"}; 199 + return {ok: false, msg: `This account is unable to use ${APP_NAME} services at this time`}; 200 200 } else if (violationData.userPassInvalid) { 201 201 return {ok: false, msg: "The BSky account credentials is invalid, please update these in the settings"}; 202 202 } ··· 355 355 const violationData = await getViolationsForUser(db, userId); 356 356 if (violationData != null) { 357 357 if (violationData.tosViolation) { 358 - return {ok: false, msg: "This account is unable to use SkyScheduler services at this time"}; 358 + return {ok: false, msg: `This account is unable to use ${APP_NAME} services at this time`}; 359 359 } else if (violationData.userPassInvalid) { 360 360 return {ok: false, msg: "The BSky account credentials is invalid, please update these in the settings"}; 361 361 }
+9 -2
src/utils/setup.ts
··· 6 6 if (await doesAdminExist(c)) 7 7 return c.html("already created", 501); 8 8 9 - if (!has(c.env, "DEFAULT_ADMIN_USER") || !has(c.env, "DEFAULT_ADMIN_PASS") || !has(c.env, "DEFAULT_ADMIN_BSKY_PASS")) 10 - return c.html("invalid configuration, missing configs"); 9 + const settingsToCheck:string[] = 10 + ["DEFAULT_ADMIN_USER", "DEFAULT_ADMIN_PASS", "DEFAULT_ADMIN_BSKY_PASS"]; 11 + 12 + // Loop through and check all of the settings that are easy to miss 13 + for (const setting of settingsToCheck) { 14 + if (!has(c.env, setting)) { 15 + return c.text(`missing ${setting} setting!`); 16 + } 17 + } 11 18 12 19 const data = await c.get("auth").api.signUpEmail({ 13 20 body: {