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

remove unused imports and reorder

woof

+213 -138
+4 -4
src/auth/index.ts
··· 1 - import { betterAuth, Session, User } from "better-auth"; 2 - import { username } from "better-auth/plugins"; 1 + import { betterAuth, Session } from "better-auth"; 3 2 import { withCloudflare } from "better-auth-cloudflare"; 4 3 import { drizzleAdapter } from "better-auth/adapters/drizzle"; 4 + import { username } from "better-auth/plugins"; 5 5 import { drizzle } from "drizzle-orm/d1"; 6 6 import { schema } from "../db"; 7 + import { BSKY_MAX_USERNAME_LENGTH, BSKY_MIN_USERNAME_LENGTH } from "../limits.d"; 7 8 import { Bindings } from "../types"; 8 - import { BSKY_MAX_USERNAME_LENGTH, BSKY_MIN_USERNAME_LENGTH } from "../limits.d"; 9 9 import { lookupBskyHandle } from "../utils/bskyApi"; 10 10 import { createDMWithUser } from "../utils/bskyMsg"; 11 11 ··· 149 149 }; 150 150 151 151 // Export for runtime usage 152 - export { createAuth, ContextVariables }; 152 + export { ContextVariables, createAuth };
+1 -1
src/db/app.schema.ts
··· 1 1 import { sql } from "drizzle-orm"; 2 - import { users } from "./auth.schema"; 3 2 import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 4 3 import { EmbedData, PostLabel } from '../types.d'; 4 + import { users } from "./auth.schema"; 5 5 6 6 export const posts = sqliteTable('posts', { 7 7 uuid: text('uuid', {mode: 'text'}).primaryKey(),
+13 -10
src/endpoints/account.tsx
··· 1 1 import { Context, Hono } from "hono"; 2 2 import { secureHeaders } from "hono/secure-headers"; 3 + import isEmpty from "just-is-empty"; 3 4 import { ContextVariables } from "../auth"; 4 - import { Bindings, LooseObj } from "../types"; 5 + import { ViolationNoticeBar } from "../layout/violationsBar"; 5 6 import { authMiddleware, pullAuthData } from "../middleware/auth"; 6 7 import { corsHelperMiddleware } from "../middleware/corsHelper"; 7 8 import { verifyTurnstile } from "../middleware/turnstile"; 9 + import { Bindings, LooseObj } from "../types"; 10 + import { doesHandleExist } from "../utils/bskyApi"; 11 + import { 12 + doesUserExist, getAllMediaOfUser, getUserEmailForHandle, 13 + getUsernameForUser, updateUserData 14 + } from "../utils/dbQuery"; 15 + import { consumeInviteKey, doesInviteKeyHaveValues } from "../utils/inviteKeys"; 16 + import { deleteFromR2 } from "../utils/r2Query"; 17 + import { AccountDeleteSchema, AccountForgotSchema } from "../validation/accountForgotDeleteSchema"; 8 18 import { AccountResetSchema } from "../validation/accountResetSchema"; 9 - import { SignupSchema } from "../validation/signupSchema"; 10 - import { LoginSchema } from "../validation/loginSchema"; 11 19 import { AccountUpdateSchema } from "../validation/accountUpdateSchema"; 12 - import { AccountDeleteSchema, AccountForgotSchema } from "../validation/accountForgotDeleteSchema"; 13 - import { doesUserExist, getAllMediaOfUser, getUserEmailForHandle, getUsernameForUser, updateUserData } from "../utils/dbQuery"; 14 - import { doesInviteKeyHaveValues, consumeInviteKey } from "../utils/inviteKeys"; 15 - import { doesHandleExist } from "../utils/bskyApi"; 16 - import { deleteFromR2 } from "../utils/r2Query"; 17 - import isEmpty from "just-is-empty"; 18 - import { ViolationNoticeBar } from "../layout/violationsBar"; 20 + import { LoginSchema } from "../validation/loginSchema"; 21 + import { SignupSchema } from "../validation/signupSchema"; 19 22 20 23 export const account = new Hono<{ Bindings: Bindings, Variables: ContextVariables }>(); 21 24
+12 -10
src/endpoints/post.tsx
··· 1 1 import { Context, Hono } from "hono"; 2 2 import { secureHeaders } from "hono/secure-headers"; 3 - import { validate as isValid } from 'uuid'; 4 - import { Bindings, CreatePostQueryResponse, EmbedDataType, LooseObj, Post } from "../types.d"; 3 + import isEmpty from "just-is-empty"; 4 + import { validate as isValid } from 'uuid'; 5 5 import { ContextVariables } from "../auth"; 6 + import { PostEdit } from "../layout/editPost"; 7 + import { ScheduledPost, ScheduledPostList } from "../layout/postList"; 6 8 import { authMiddleware } from "../middleware/auth"; 7 9 import { corsHelperMiddleware } from "../middleware/corsHelper"; 8 - import { FileDeleteSchema } from "../validation/mediaSchema"; 9 - import { EditSchema } from "../validation/postSchema"; 10 + import { Bindings, CreatePostQueryResponse, EmbedDataType, LooseObj, Post } from "../types.d"; 10 11 import { makePost } from "../utils/bskyApi"; 11 - import { deleteFromR2, uploadFileR2 } from "../utils/r2Query"; 12 - import { createPost, deletePost, getPostById, getUsernameForUser, 13 - setPostNowOffForPost, updatePostForUser } from "../utils/dbQuery"; 14 - import { ScheduledPost, ScheduledPostList } from "../layout/postList"; 15 - import { PostEdit } from "../layout/editPost"; 16 - import isEmpty from "just-is-empty"; 12 + import { 13 + createPost, deletePost, getPostById, getUsernameForUser, 14 + updatePostForUser 15 + } from "../utils/dbQuery"; 17 16 import { enqueuePost, isQueueEnabled, shouldPostNowQueue } from "../utils/queuePublisher"; 17 + import { deleteFromR2, uploadFileR2 } from "../utils/r2Query"; 18 + import { FileDeleteSchema } from "../validation/mediaSchema"; 19 + import { EditSchema } from "../validation/postSchema"; 18 20 19 21 export const post = new Hono<{ Bindings: Bindings, Variables: ContextVariables }>(); 20 22
+3 -3
src/endpoints/preview.tsx
··· 1 1 import { Context, Hono } from "hono"; 2 2 import { secureHeaders } from "hono/secure-headers"; 3 - import { Bindings } from "../types.d"; 3 + import isEmpty from "just-is-empty"; 4 4 import { ContextVariables } from "../auth"; 5 + import { BSKY_IMG_MIME_TYPES } from "../limits.d"; 5 6 import { authMiddleware } from "../middleware/auth"; 6 7 import { corsHelperMiddleware } from "../middleware/corsHelper"; 7 - import { BSKY_IMG_MIME_TYPES } from "../limits.d"; 8 + import { Bindings } from "../types.d"; 8 9 import { FileContentSchema } from "../validation/mediaSchema"; 9 - import isEmpty from "just-is-empty"; 10 10 11 11 export const preview = new Hono<{ Bindings: Bindings, Variables: ContextVariables }>(); 12 12
+16 -16
src/index.tsx
··· 1 1 import { Env, Hono } from "hono"; 2 - import { createAuth, ContextVariables } from "./auth"; 3 - import { Bindings, QueueTaskData, QueueTaskType, ScheduledContext } from "./types.d"; 4 - import Home from "./pages/homepage"; 5 - import Signup from "./pages/signup"; 2 + import { ContextVariables, createAuth } from "./auth"; 3 + import { account } from "./endpoints/account"; 4 + import { post } from "./endpoints/post"; 5 + import { preview } from "./endpoints/preview"; 6 + import { authAdminOnlyMiddleware } from "./middleware/adminOnly"; 7 + import { authMiddleware } from "./middleware/auth"; 8 + import { corsHelperMiddleware } from "./middleware/corsHelper"; 9 + import { redirectToDashIfLogin } from "./middleware/redirectDash"; 6 10 import Dashboard from "./pages/dashboard"; 11 + import ForgotPassword from "./pages/forgot"; 12 + import Home from "./pages/homepage"; 7 13 import Login from "./pages/login"; 14 + import PrivacyPolicy from "./pages/privacy"; 8 15 import ResetPassword from "./pages/reset"; 9 - import ForgotPassword from "./pages/forgot"; 16 + import Signup from "./pages/signup"; 10 17 import TermsOfService from "./pages/tos"; 11 - import PrivacyPolicy from "./pages/privacy"; 12 - import { cleanUpPostsTask, handlePostTask, handleRepostTask, schedulePostTask } from "./utils/scheduler"; 18 + import { Bindings, QueueTaskData, QueueTaskType, ScheduledContext } from "./types.d"; 19 + import { makeConstScript } from "./utils/constScriptGen"; 13 20 import { runMaintenanceUpdates } from "./utils/dbQuery"; 14 - import { setupAccounts } from "./utils/setup"; 15 21 import { makeInviteKey } from "./utils/inviteKeys"; 16 - import { makeConstScript } from "./utils/constScriptGen"; 17 - import { authMiddleware } from "./middleware/auth"; 18 - import { authAdminOnlyMiddleware } from "./middleware/adminOnly"; 19 - import { corsHelperMiddleware } from "./middleware/corsHelper"; 20 - import { redirectToDashIfLogin } from "./middleware/redirectDash"; 21 - import { account } from "./endpoints/account"; 22 - import { post } from "./endpoints/post"; 23 - import { preview } from "./endpoints/preview"; 22 + import { cleanUpPostsTask, handlePostTask, handleRepostTask, schedulePostTask } from "./utils/scheduler"; 23 + import { setupAccounts } from "./utils/setup"; 24 24 25 25 const app = new Hono<{ Bindings: Bindings, Variables: ContextVariables }>(); 26 26
+1 -1
src/layout/editPost.tsx
··· 1 1 import { html } from "hono/html"; 2 - import { MAX_LENGTH } from "../limits.d" 2 + import { MAX_LENGTH } from "../limits.d"; 3 3 import { EmbedDataType, Post } from "../types.d"; 4 4 import { PostContentObject } from "./postList"; 5 5
+2 -2
src/layout/main.tsx
··· 1 1 import { html } from 'hono/html'; 2 2 import { Child } from 'hono/jsx'; 3 - import MetaTags from './metaTags'; 4 - import { PreloadRules } from '../types.d'; 5 3 import { CURRENT_SCRIPT_VERSION } from '../limits.d'; 4 + import { PreloadRules } from '../types.d'; 6 5 import { PreloadDependencyTags } from './depTags'; 6 + import MetaTags from './metaTags'; 7 7 8 8 type BaseLayoutProps = { 9 9 children: Child;
+14 -6
src/layout/makePost.tsx
··· 1 1 import { html } from "hono/html"; 2 - import { MAX_LENGTH, CF_IMAGES_MAX_DIMENSION, CF_IMAGES_FILE_SIZE_LIMIT_IN_MB, 3 - MAX_REPOST_INTERVAL_LIMIT, MAX_REPOST_IN_HOURS, BSKY_VIDEO_MAX_DURATION, 4 - BSKY_IMG_FILE_EXTS, BSKY_VIDEO_FILE_EXTS, BSKY_IMG_SIZE_LIMIT_IN_MB, 5 - R2_FILE_SIZE_LIMIT_IN_MB, MAX_THUMBNAIL_SIZE, 6 - CURRENT_SCRIPT_VERSION } from "../limits.d" 7 - import { ConstScriptPreload } from "../utils/constScriptGen"; 2 + import { 3 + BSKY_IMG_FILE_EXTS, 4 + BSKY_IMG_SIZE_LIMIT_IN_MB, 5 + BSKY_VIDEO_FILE_EXTS, 6 + BSKY_VIDEO_MAX_DURATION, 7 + CF_IMAGES_FILE_SIZE_LIMIT_IN_MB, 8 + CF_IMAGES_MAX_DIMENSION, 9 + CURRENT_SCRIPT_VERSION, 10 + MAX_LENGTH, 11 + MAX_REPOST_INTERVAL_LIMIT, MAX_REPOST_IN_HOURS, 12 + MAX_THUMBNAIL_SIZE, 13 + R2_FILE_SIZE_LIMIT_IN_MB 14 + } from "../limits.d"; 8 15 import { PreloadRules } from "../types.d"; 16 + import { ConstScriptPreload } from "../utils/constScriptGen"; 9 17 import { DependencyTags } from "./depTags"; 10 18 11 19 export const PreloadPostCreation: PreloadRules[] = [
-1
src/layout/metaTags.tsx
··· 1 - 2 1 export default function MetaTags() { 3 2 return ( 4 3 <>
+14 -7
src/layout/postList.tsx
··· 1 1 import { Context } from "hono"; 2 2 import { html, raw } from "hono/html"; 3 3 import { getPostsForUser, getUsernameForUser } from "../utils/dbQuery"; 4 - import { createPostObject } from "../utils/helpers"; 5 4 import { Post } from "../types.d"; 6 5 import isEmpty from "just-is-empty"; 6 + 7 + function TrashCanIcon() { 8 + return (<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" width="20px" height="20px"> 9 + <path strokeLinecap="round" strokeLinejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> 10 + </svg>); 11 + } 7 12 8 13 type PostContentObjectProps = { 9 14 text: string; ··· 32 37 return html` 33 38 <article id="postBase${content.postid}" ${oobSwapStr}> 34 39 <div id="post${content.postid}"> 35 - ${hasBeenPosted ? '' : raw(`<button type="submit" hx-delete="/post/delete/${content.postid}" hx-confirm="Are you sure you want to delete this post?" hx-target="#postBase${content.postid}" hx-swap="outerHTML" hx-trigger="click" class="btn-sm btn-error outline btn-delete"> 36 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" width="20px" height="20px"> 37 - <path strokeLinecap="round" strokeLinejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> 38 - </svg> 40 + ${hasBeenPosted ? '' : raw(`<button type="submit" hx-delete="/post/delete/${content.postid}" 41 + hx-confirm="Are you sure you want to delete this post?" hx-target="#postBase${content.postid}" 42 + hx-swap="outerHTML" hx-trigger="click" class="btn-sm btn-error outline btn-delete"> 43 + ${<TrashCanIcon />} 39 44 </button>`)} 40 - <div ${hasBeenPosted ? '' : raw(`title="Click to edit post content" hx-get="/post/edit/${content.postid}" hx-trigger="click once" hx-target="#post${content.postid}" hx-swap="innerHTML show:#editPost${content.postid}:top"`)}> 45 + <div ${hasBeenPosted ? '' : raw(`title="Click to edit post content" hx-get="/post/edit/${content.postid}" 46 + hx-trigger="click once" hx-target="#post${content.postid}" hx-swap="innerHTML show:#editPost${content.postid}:top"`)}> 41 47 ${<PostContentObject text={content.text}/>} 42 48 </div> 43 49 </div> 44 50 <footer> 45 51 <small> 46 52 ${hasBeenPosted ? 47 - raw(`<a class="secondary" data-uri="${content.uri}" href="https://bsky.app/profile/${username}/post${postURIID}" target="_blank" title="link to post">Posted on</a>:`) : 53 + raw(`<a class="secondary" data-uri="${content.uri}" href="https://bsky.app/profile/${username}/post${postURIID}" 54 + target="_blank" title="link to post">Posted on</a>:`) : 48 55 'Scheduled for:' } 49 56 <span class="timestamp">${content.scheduledDate}</span> 50 57 ${!isEmpty(content.embeds) ? ' | Embeds: ' + content.embeds?.length : ''}
+2 -2
src/layout/settings.tsx
··· 1 1 import { html } from "hono/html"; 2 2 import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits.d"; 3 - import { UsernameField } from "./usernameField"; 4 - import { BSkyAppPasswordField, DashboardPasswordField } from "./passwordFields"; 5 3 import { PWAutoCompleteSettings } from "../types.d"; 4 + import { BSkyAppPasswordField, DashboardPasswordField } from "./passwordFields"; 5 + import { UsernameField } from "./usernameField"; 6 6 7 7 export function Settings() { 8 8 return (
+1 -1
src/layout/usernameField.tsx
··· 1 - import { html, raw } from "hono/html"; 1 + import { raw } from "hono/html"; 2 2 import { BSKY_MIN_USERNAME_LENGTH } from "../limits.d"; 3 3 4 4 type UsernameFieldProps = {
+6 -4
src/layout/violationsBar.tsx
··· 1 1 import { Context } from "hono"; 2 - import { getViolationsForCurrentUser } from "../utils/dbQuery"; 3 - import { Violation } from "../types.d"; 4 2 import isEmpty from "just-is-empty"; 3 + import { Violation } from "../types.d"; 4 + import { getViolationsForCurrentUser } from "../utils/dbQuery"; 5 5 6 6 export async function ViolationNoticeBar(props: any) { 7 7 const ctx: Context = props.ctx; ··· 21 21 errorStr = "You currently have media that's too large for Bluesky (like a video), please delete those posts"; 22 22 } 23 23 return ( 24 - <div id="violationBar" class="warning-box" hx-trigger="accountViolations from:body" hx-swap="outerHTML" hx-get="/account/violations" hx-target="this"> 24 + <div id="violationBar" class="warning-box" hx-trigger="accountViolations from:body" 25 + hx-swap="outerHTML" hx-get="/account/violations" hx-target="this"> 25 26 <span class="warning"><b>WARNING</b>: Account error found! {errorStr}</span> 26 27 </div> 27 28 ); 28 29 } 29 - return (<div hx-trigger="accountViolations from:body" hidden id="hiddenViolations" hx-get="/account/violations" hx-swap="outerHTML" hx-target="this"></div>); 30 + return (<div hx-trigger="accountViolations from:body" hidden id="hiddenViolations" 31 + hx-get="/account/violations" hx-swap="outerHTML" hx-target="this"></div>); 30 32 };
+13 -6
src/pages/dashboard.tsx
··· 1 1 import { Context } from "hono"; 2 - import { PostCreation, PreloadPostCreation } from "../layout/makePost"; 2 + import { AltTextDialog } from "../layout/altTextModal"; 3 + import { DependencyTags } from "../layout/depTags"; 4 + import FooterCopyright from "../layout/footer"; 3 5 import { BaseLayout } from "../layout/main"; 6 + import { PostCreation, PreloadPostCreation } from "../layout/makePost"; 4 7 import { ScheduledPostList } from "../layout/postList"; 5 8 import { Settings, SettingsButton } from "../layout/settings"; 6 9 import { ViolationNoticeBar } from "../layout/violationsBar"; 7 - import FooterCopyright from "../layout/footer"; 8 10 import { PreloadRules } from "../types.d"; 9 - import { AltTextDialog } from "../layout/altTextModal"; 10 - import { DependencyTags } from "../layout/depTags"; 11 + 12 + function RefreshPostIcon() { 13 + return ( 14 + <svg width="20px" height="20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 3V8M3 8H8M3 8L6 5.29168C7.59227 3.86656 9.69494 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.71683 21 4.13247 18.008 3.22302 14" stroke="#ffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg> 15 + ); 16 + } 11 17 12 18 export default function Dashboard(props:any) { 13 19 const ctx: Context = props.c; ··· 28 34 <h4>SkyScheduler Dashboard</h4> 29 35 <div class="sidebar-block"> 30 36 <small><i>Schedule Bluesky posts effortlessly</i>.</small><br /> 31 - <small>Account: <b class="truncate" id="currentUser" hx-get="/account/username" hx-trigger="accountUpdated from:body, load once" hx-target="this"></b></small> 37 + <small>Account: <b class="truncate" id="currentUser" hx-get="/account/username" 38 + hx-trigger="accountUpdated from:body, load once" hx-target="this"></b></small> 32 39 </div> 33 40 <center class="controls"> 34 41 <button id="refresh-posts" hx-get="/post/all" hx-target="#posts" hx-trigger="click throttle:3s" 35 42 hx-on-htmx-before-request="this.classList.add('svgAnim');" 36 43 hx-on-htmx-after-request="setTimeout(() => {this.classList.remove('svgAnim')}, 3000)"> 37 44 <span>Refresh Posts</span> 38 - <svg width="20px" height="20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 3V8M3 8H8M3 8L6 5.29168C7.59227 3.86656 9.69494 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.71683 21 4.13247 18.008 3.22302 14" stroke="#ffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg> 45 + <RefreshPostIcon /> 39 46 </button> 40 47 <SettingsButton /> 41 48 </center>
+3 -3
src/pages/forgot.tsx
··· 1 1 import { Context } from "hono"; 2 + import AccountHandler from "../layout/account"; 3 + import FooterCopyright from "../layout/footer"; 2 4 import { BaseLayout } from "../layout/main"; 3 5 import NavTags from "../layout/navTags"; 4 - import AccountHandler from "../layout/account"; 5 - import { UsernameField } from "../layout/usernameField"; 6 6 import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/turnstile"; 7 - import FooterCopyright from "../layout/footer"; 7 + import { UsernameField } from "../layout/usernameField"; 8 8 9 9 export default function ForgotPassword(props:any) { 10 10 const ctx: Context = props.c;
+1 -1
src/pages/homepage.tsx
··· 1 1 import FooterCopyright from "../layout/footer"; 2 2 import { BaseLayout } from "../layout/main"; 3 3 import NavTags from "../layout/navTags"; 4 - import { MAX_REPOST_IN_HOURS, MAX_REPOST_INTERVAL, MAX_REPOST_DAYS, R2_FILE_SIZE_LIMIT_IN_MB } from "../limits.d"; 4 + import { MAX_REPOST_DAYS, MAX_REPOST_IN_HOURS, MAX_REPOST_INTERVAL, R2_FILE_SIZE_LIMIT_IN_MB } from "../limits.d"; 5 5 6 6 export default function Home() { 7 7 return (
+2 -2
src/pages/login.tsx
··· 1 + import AccountHandler from "../layout/account"; 1 2 import { BaseLayout } from "../layout/main"; 2 3 import NavTags from "../layout/navTags"; 3 - import AccountHandler from "../layout/account"; 4 - import { UsernameField } from "../layout/usernameField"; 5 4 import { DashboardPasswordField } from "../layout/passwordFields"; 5 + import { UsernameField } from "../layout/usernameField"; 6 6 import { PWAutoCompleteSettings } from "../types.d"; 7 7 8 8 export default function Login() {
+1 -1
src/pages/reset.tsx
··· 1 1 import { html } from "hono/html"; 2 + import AccountHandler from "../layout/account"; 2 3 import { BaseLayout } from "../layout/main"; 3 4 import NavTags from "../layout/navTags"; 4 5 import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits.d"; 5 - import AccountHandler from "../layout/account"; 6 6 7 7 export default function ResetPassword() { 8 8 const links = [{title: "Forgot Password", url: "/forgot"}];
+6 -6
src/pages/signup.tsx
··· 1 1 import { Context } from "hono"; 2 - import { BaseLayout } from "../layout/main"; 3 - import { getInviteThread, isUsingInviteKeys } from "../utils/inviteKeys"; 4 - import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits.d"; 5 - import NavTags from "../layout/navTags"; 6 2 import AccountHandler from "../layout/account"; 7 - import { UsernameField } from "../layout/usernameField"; 8 - import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/turnstile"; 9 3 import FooterCopyright from "../layout/footer"; 4 + import { BaseLayout } from "../layout/main"; 5 + import NavTags from "../layout/navTags"; 10 6 import { BSkyAppPasswordField, DashboardPasswordField } from "../layout/passwordFields"; 7 + import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/turnstile"; 8 + import { UsernameField } from "../layout/usernameField"; 9 + import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits.d"; 11 10 import { PWAutoCompleteSettings } from "../types.d"; 11 + import { getInviteThread, isUsingInviteKeys } from "../utils/inviteKeys"; 12 12 13 13 export default function Signup(props:any) { 14 14 const ctx: Context = props.c;
+15 -8
src/utils/bskyApi.ts
··· 1 - import { Context } from 'hono'; 2 1 import { type AppBskyFeedPost, AtpAgent, RichText } from '@atproto/api'; 3 - import { Bindings, Post, Repost, PostLabel, EmbedData, PostResponseObject, LooseObj, PlatformLoginResponse, EmbedDataType, ScheduledContext, BskyEmbedWrapper, BskyRecordWrapper } from '../types.d'; 2 + import { Context } from 'hono'; 3 + import { imageDimensionsFromStream } from 'image-dimensions'; 4 + import has from 'just-has'; 5 + import isEmpty from "just-is-empty"; 6 + import truncate from "just-truncate"; 4 7 import { MAX_ALT_TEXT, MAX_EMBEDS_PER_POST, MAX_POSTED_LENGTH } from '../limits.d'; 5 - import { updatePostData, getBskyUserPassForId, createViolationForUser, isPostAlreadyPosted, setPostNowOffForPost } from './dbQuery'; 8 + import { 9 + Bindings, BskyEmbedWrapper, BskyRecordWrapper, EmbedData, EmbedDataType, 10 + LooseObj, PlatformLoginResponse, Post, PostLabel, 11 + PostResponseObject, Repost, ScheduledContext 12 + } from '../types.d'; 13 + import { postRecordURI } from '../validation/regexCases'; 14 + import { 15 + createViolationForUser, getBskyUserPassForId, 16 + isPostAlreadyPosted, setPostNowOffForPost, updatePostData 17 + } from './dbQuery'; 6 18 import { deleteEmbedsFromR2 } from './r2Query'; 7 - import { imageDimensionsFromStream } from 'image-dimensions'; 8 - import { postRecordURI } from '../validation/regexCases'; 9 - import truncate from "just-truncate"; 10 - import isEmpty from "just-is-empty"; 11 - import has from 'just-has'; 12 19 13 20 export const doesHandleExist = async (user: string) => { 14 21 try {
+1 -1
src/utils/bskyMsg.ts
··· 1 1 import { AtpAgent, RichText } from '@atproto/api'; 2 - import { loginToBsky } from './bskyApi'; 3 2 import { Bindings, PlatformLoginResponse } from '../types.d'; 3 + import { loginToBsky } from './bskyApi'; 4 4 5 5 export const createDMWithUser = async (env: Bindings, user: string, msg: string) => { 6 6 const agent = new AtpAgent({
+2 -2
src/utils/bskyPrune.ts
··· 1 + import isEmpty from 'just-is-empty'; 2 + import split from 'just-split'; 1 3 import { Bindings } from '../types.d'; 2 4 import { getPostRecords } from './bskyApi'; 3 5 import { getAllPostedPosts, getAllPostedPostsOfUser } from './dbQuery'; 4 - import split from 'just-split'; 5 - import isEmpty from 'just-is-empty'; 6 6 7 7 // This looks for a bunch of posts that are posted and determines if the posts 8 8 // are still on the network or not. If they are not, then this prunes the posts from
+12 -4
src/utils/constScriptGen.ts
··· 1 - import {BSKY_NAME_LOOKUP_LIMIT, BSKY_NAME_TYPE_AHEAD_CHARS, BSKY_GIF_MIME_TYPES, 2 - BSKY_IMG_MIME_TYPES, BSKY_VIDEO_MIME_TYPES, BSKY_VIDEO_LENGTH_LIMIT, 3 - MAX_LENGTH, MAX_ALT_TEXT, R2_FILE_SIZE_LIMIT, MAX_THUMBNAIL_SIZE, 4 - MAX_EMBEDS_PER_POST} from "../limits.d"; 1 + import { 2 + BSKY_GIF_MIME_TYPES, 3 + BSKY_IMG_MIME_TYPES, 4 + BSKY_NAME_LOOKUP_LIMIT, BSKY_NAME_TYPE_AHEAD_CHARS, 5 + BSKY_VIDEO_LENGTH_LIMIT, 6 + BSKY_VIDEO_MIME_TYPES, 7 + MAX_ALT_TEXT, 8 + MAX_EMBEDS_PER_POST, 9 + MAX_LENGTH, 10 + MAX_THUMBNAIL_SIZE, 11 + R2_FILE_SIZE_LIMIT 12 + } from "../limits.d"; 5 13 import { PreloadRules } from "../types.d"; 6 14 7 15 const CONST_SCRIPT_VERSION: number = 6;
+23 -14
src/utils/dbQuery.ts
··· 1 + import { addHours, isAfter } from "date-fns"; 2 + import { 3 + and, count, desc, eq, getTableColumns, gt, inArray, 4 + isNull, lte, ne, notInArray, sql 5 + } from "drizzle-orm"; 6 + import { BatchItem } from "drizzle-orm/batch"; 7 + import { drizzle, DrizzleD1Database } from "drizzle-orm/d1"; 1 8 import { Context } from "hono"; 2 - import { DrizzleD1Database, drizzle } from "drizzle-orm/d1"; 3 - import { sql, and, gt, eq, lte, inArray, desc, count, getTableColumns, notInArray, ne, isNull } from "drizzle-orm"; 4 - import { BatchItem } from "drizzle-orm/batch"; 9 + import flatten from "just-flatten-it"; 10 + import has from "just-has"; 11 + import isEmpty from "just-is-empty"; 12 + import truncate from "just-truncate"; 13 + import unique from "just-unique"; 14 + import { v4 as uuidv4, validate as uuidValid } from 'uuid'; 5 15 import { posts, reposts, violations } from "../db/app.schema"; 6 16 import { accounts, users } from "../db/auth.schema"; 17 + import { MAX_HOLD_DAYS_BEFORE_PURGE, MAX_POSTED_LENGTH } from "../limits.d"; 18 + import { 19 + Bindings, BskyAPILoginCreds, CreatePostQueryResponse, 20 + GetAllPostedBatch, LooseObj, PlatformLoginResponse, 21 + Post, PostLabel, Repost, Violation 22 + } from "../types.d"; 7 23 import { PostSchema } from "../validation/postSchema"; 8 - import { Bindings, BskyAPILoginCreds, CreatePostQueryResponse, GetAllPostedBatch, LooseObj, 9 - PlatformLoginResponse, Post, PostLabel, Repost, Violation } from "../types.d"; 10 - import { MAX_HOLD_DAYS_BEFORE_PURGE, MAX_POSTED_LENGTH } from "../limits.d"; 11 - import { createLoginCredsObj, createPostObject, createRepostObject, floorCurrentTime, floorGivenTime } from "./helpers"; 24 + import { 25 + createLoginCredsObj, createPostObject, createRepostObject, 26 + floorCurrentTime, floorGivenTime 27 + } from "./helpers"; 12 28 import { deleteEmbedsFromR2 } from "./r2Query"; 13 - import { isAfter, addHours } from "date-fns"; 14 - import { v4 as uuidv4, validate as uuidValid } from 'uuid'; 15 - import has from "just-has"; 16 - import isEmpty from "just-is-empty"; 17 - import flatten from "just-flatten-it"; 18 - import truncate from "just-truncate"; 19 - import unique from "just-unique"; 20 29 21 30 type BatchQuery = [BatchItem<'sqlite'>, ...BatchItem<'sqlite'>[]]; 22 31
+1 -1
src/utils/helpers.ts
··· 1 + import { startOfHour } from "date-fns"; 1 2 import isEmpty from "just-is-empty"; 2 3 import { BskyAPILoginCreds, Post, Repost } from "../types.d"; 3 - import { startOfHour } from "date-fns"; 4 4 5 5 export function createPostObject(data: any) { 6 6 const postData: Post = (new Object() as Post);
+1 -1
src/utils/queuePublisher.ts
··· 1 - import { Bindings, Post, QueueTaskData, QueueTaskType, Repost } from "../types.d"; 2 1 import isEmpty from 'just-is-empty'; 3 2 import random from 'just-random'; 4 3 import get from 'just-safe-get'; 4 + import { Bindings, Post, QueueTaskData, QueueTaskType, Repost } from "../types.d"; 5 5 6 6 const queueContentType = 'json'; 7 7
+15 -6
src/utils/r2Query.ts
··· 1 - import { Bindings, ScheduledContext, EmbedData, EmbedDataType } from '../types.d'; 2 - import { CF_IMAGES_MAX_DIMENSION, BSKY_IMG_SIZE_LIMIT, CF_IMAGES_FILE_SIZE_LIMIT_IN_MB, 3 - CF_IMAGES_FILE_SIZE_LIMIT, R2_FILE_SIZE_LIMIT, 4 - MB_TO_BYTES, BSKY_VIDEO_MIME_TYPES, R2_FILE_SIZE_LIMIT_IN_MB, 5 - BSKY_IMG_MIME_TYPES, BSKY_VIDEO_SIZE_LIMIT, BSKY_GIF_MIME_TYPES } from "../limits.d"; 6 - import { v4 as uuidv4 } from 'uuid'; 7 1 import { Context } from 'hono'; 8 2 import { imageDimensionsFromStream } from 'image-dimensions'; 3 + import { v4 as uuidv4 } from 'uuid'; 4 + import { 5 + BSKY_GIF_MIME_TYPES, 6 + BSKY_IMG_MIME_TYPES, 7 + BSKY_IMG_SIZE_LIMIT, 8 + BSKY_VIDEO_MIME_TYPES, 9 + BSKY_VIDEO_SIZE_LIMIT, 10 + CF_IMAGES_FILE_SIZE_LIMIT, 11 + CF_IMAGES_FILE_SIZE_LIMIT_IN_MB, 12 + CF_IMAGES_MAX_DIMENSION, 13 + MB_TO_BYTES, 14 + R2_FILE_SIZE_LIMIT, 15 + R2_FILE_SIZE_LIMIT_IN_MB 16 + } from "../limits.d"; 17 + import { Bindings, EmbedData, EmbedDataType, ScheduledContext } from '../types.d'; 9 18 10 19 type FileMetaData = { 11 20 name: string,
+9 -4
src/utils/scheduler.ts
··· 1 - import { Bindings, ScheduledContext, Post, Repost } from '../types.d'; 1 + import isEmpty from 'just-is-empty'; 2 + import { Bindings, Post, Repost, ScheduledContext } from '../types.d'; 2 3 import { makePost, makeRepost } from './bskyApi'; 3 4 import { pruneBskyPosts } from './bskyPrune'; 4 - import { getAllPostsForCurrentTime, deleteAllRepostsBeforeCurrentTime, getAllRepostsForCurrentTime, 5 - deletePosts, purgePostedPosts } from './dbQuery'; 5 + import { 6 + deleteAllRepostsBeforeCurrentTime, 7 + deletePosts, 8 + getAllPostsForCurrentTime, 9 + getAllRepostsForCurrentTime, 10 + purgePostedPosts 11 + } from './dbQuery'; 6 12 import { enqueuePost, enqueueRepost, isQueueEnabled } from './queuePublisher'; 7 - import isEmpty from 'just-is-empty'; 8 13 9 14 export const handlePostTask = async(runtime: ScheduledContext, postData: Post, isQueued: boolean = false) => { 10 15 const madePost = await makePost(runtime, postData, isQueued);
+1 -1
src/validation/accountResetSchema.ts
··· 1 - import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits.d"; 2 1 import * as z from "zod/v4"; 2 + import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits.d"; 3 3 4 4 export const AccountResetSchema = z.object({ 5 5 resetToken: z.string().nonempty("reset token is missing!"),
+4 -1
src/validation/accountUpdateSchema.ts
··· 1 1 import * as z from "zod/v4"; 2 + import { 3 + BSKY_MAX_APP_PASSWORD_LENGTH, BSKY_MIN_USERNAME_LENGTH, 4 + MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS 5 + } from "../limits.d"; 2 6 import { appPasswordRegex } from "./regexCases"; 3 - import { BSKY_MAX_APP_PASSWORD_LENGTH, BSKY_MIN_USERNAME_LENGTH, MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits.d"; 4 7 5 8 export const AccountUpdateSchema = z.object({ 6 9 username: z.string().trim().toLowerCase()
+3 -3
src/validation/embedSchema.ts
··· 1 + import isEmpty from "just-is-empty"; 2 + import * as z from "zod/v4"; 1 3 import { BSKY_VIDEO_LENGTH_LIMIT } from "../limits.d"; 2 4 import { EmbedDataType } from "../types.d"; 3 - import { AltTextSchema } from "./sharedValidations"; 4 5 import { FileContentSchema } from "./mediaSchema"; 5 6 import { postRecordURI } from "./regexCases"; 6 - import * as z from "zod/v4"; 7 - import isEmpty from "just-is-empty"; 7 + import { AltTextSchema } from "./sharedValidations"; 8 8 9 9 export const ImageEmbedSchema = z.object({ 10 10 ...FileContentSchema.shape,
+1 -1
src/validation/loginSchema.ts
··· 1 - import { PasswordSchema, UsernameSchema } from "./sharedValidations"; 2 1 import * as z from "zod/v4"; 2 + import { PasswordSchema, UsernameSchema } from "./sharedValidations"; 3 3 4 4 // Schema for login validation 5 5 export const LoginSchema = z.object({
+3 -3
src/validation/postSchema.ts
··· 1 - import { MIN_LENGTH, MAX_REPOST_INTERVAL_LIMIT, MAX_REPOST_IN_HOURS, MAX_LENGTH } from "../limits.d"; 1 + import * as z from "zod/v4"; 2 + import { MAX_LENGTH, MAX_REPOST_INTERVAL_LIMIT, MAX_REPOST_IN_HOURS, MIN_LENGTH } from "../limits.d"; 3 + import { EmbedDataType, PostLabel } from "../types.d"; 2 4 import { ImageEmbedSchema, LinkEmbedSchema, PostRecordSchema, VideoEmbedSchema } from "./embedSchema"; 3 5 import { FileContentSchema } from "./mediaSchema"; 4 - import { EmbedDataType, PostLabel } from "../types.d"; 5 - import * as z from "zod/v4"; 6 6 import { AltTextSchema } from "./sharedValidations"; 7 7 8 8 const TextContent = z.object({
+7 -1
src/validation/sharedValidations.ts
··· 1 1 import * as z from "zod/v4"; 2 + import { 3 + BSKY_MAX_APP_PASSWORD_LENGTH, 4 + BSKY_MAX_USERNAME_LENGTH, 5 + BSKY_MIN_USERNAME_LENGTH, 6 + MAX_ALT_TEXT, 7 + MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS 8 + } from "../limits.d"; 2 9 import { appPasswordRegex } from "./regexCases"; 3 - import { BSKY_MAX_APP_PASSWORD_LENGTH, BSKY_MIN_USERNAME_LENGTH, BSKY_MAX_USERNAME_LENGTH, MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS, MAX_ALT_TEXT } from "../limits.d"; 4 10 5 11 export const UsernameSchema = z.object({ 6 12 username: z.string().trim().toLowerCase()