···3232 let src = $derived(closed ? `/eyes/closed.webp` : `/eyes/${kind}_${look}.webp`);
33333434 // generate dollcode based on time, but mod by 3 hours
3535- const timeDollcode = genDollcode((visits[0] / 1000) % (60 * 60 * 24));
3636- const visitsDollcode = genDollcode(visits.length);
3535+ const timeDollcode = $derived(genDollcode((visits[0] / 1000) % (60 * 60 * 24)));
3636+ const visitsDollcode = $derived(genDollcode(visits.length));
37373838 randomizeLook();
3939</script>
+32-47
eunomia/src/hooks.server.ts
···11-import { updateLastPosts } from '$lib/bluesky';
22-import { getLastTrack, updateNowPlayingTrack } from '$lib/lastfm';
33-import { steamReadLastGame, steamUpdateNowPlaying } from '$lib/steam';
44-import { updateCommits } from '$lib/activity';
55-import { ToadScheduler, SimpleIntervalJob, Task, AsyncTask } from 'toad-scheduler';
11+import { updateLastPosts } from './lib/bluesky.ts';
22+import { updateNowPlayingTrack } from '$lib/lastfm.ts';
33+import { steamUpdateNowPlaying } from '$lib/steam.ts';
44+import { updateCommits } from '$lib/activity.ts';
55+import { ToadScheduler, SimpleIntervalJob, AsyncTask } from 'toad-scheduler';
66import {
77 incrementFakeVisitCount,
88 incrementLegitVisitCount,
99 pushMetric,
1010 sendAllMetrics
1111-} from '$lib/metrics';
1212-import {
1313- addLastVisitor,
1414- decrementVisitCount,
1515- incrementVisitCount,
1616- notifyDarkVisitors,
1717- removeLastVisitor
1818-} from '$lib/visits';
1919-import { testUa } from '$lib/robots';
1111+} from '$lib/metrics.ts';
1212+import { addLastVisitor, notifyDarkVisitors, removeLastVisitor } from '$lib/visits.ts';
1313+import { testUa } from '$lib/robots.ts';
2014import { error, type Handle } from '@sveltejs/kit';
2121-import { _fetchEntries } from './routes/(site)/guestbook/+page.server';
1515+import { _fetchEntries } from './routes/(site)/guestbook/+page.server.ts';
2216import { sequence } from '@sveltejs/kit/hooks';
23172418const updateNowPlaying = async () => {
···5751);
58525953const corsHandler = (allowedOrigins = ['*']) => {
6060- return async ({ event, resolve }: Parameters<Handle>[0]) => {
6161- const origin = event.request.headers.get('origin');
6262-6363- const corsHeaders: Record<string, string> = {
6464- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
6565- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
6666- };
5454+ return async ({ event, resolve }: Parameters<Handle>[0]) => {
5555+ const origin = event.request.headers.get('origin');
5656+5757+ const corsHeaders: Record<string, string> = {
5858+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
5959+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
6060+ };
6161+6262+ if (allowedOrigins.includes('*')) corsHeaders['Access-Control-Allow-Origin'] = '*';
6363+ else if (origin && allowedOrigins.includes(origin)) {
6464+ corsHeaders['Access-Control-Allow-Origin'] = origin;
6565+ corsHeaders['Access-Control-Allow-Credentials'] = 'true';
6666+ }
6767+6868+ if (event.request.method === 'OPTIONS') return new Response(null, { headers: corsHeaders });
67696868- if (allowedOrigins.includes('*'))
6969- corsHeaders['Access-Control-Allow-Origin'] = '*';
7070- else if (origin && allowedOrigins.includes(origin)) {
7171- corsHeaders['Access-Control-Allow-Origin'] = origin;
7272- corsHeaders['Access-Control-Allow-Credentials'] = 'true';
7373- }
7070+ const response = await resolve(event);
74717575- if (event.request.method === 'OPTIONS')
7676- return new Response(null, { headers: corsHeaders });
7777-7878- const response = await resolve(event);
7979-8080- Object.entries(corsHeaders).forEach(([key, value]) => {
8181- response.headers.set(key, value);
8282- });
7272+ Object.entries(corsHeaders).forEach(([key, value]) => {
7373+ response.headers.set(key, value);
7474+ });
83758484- return response;
8585- };
8686-}
7676+ return response;
7777+ };
7878+};
87798880const handler = async ({ event, resolve }: Parameters<Handle>[0]) => {
8981 notifyDarkVisitors(event.url, event.request); // no await so it doesnt block
···117109118110 // only add visitors if its a "legit" page visit
119111 let id = null;
120120- let valid = false;
121112 if (isPageVisit && !isRss()) {
122113 id = addLastVisitor(event.request, event.cookies);
123123- valid = await incrementVisitCount(event.request, event.cookies);
124114 }
125115126116 // actually resolve event
···128118 // remove visitors if it was a 404
129119 if (resp.status === 404) {
130120 if (id !== null) removeLastVisitor(id);
131131- if (valid) decrementVisitCount();
132121 }
133122134123 return resp;
135124};
136125137137-const allowedOrigins = [
138138- "https://gaze.systems",
139139- "https://ptr.pet",
140140- "https://poor.dog",
141141-];
126126+const allowedOrigins = ['https://gaze.systems', 'https://ptr.pet', 'https://poor.dog'];
142127export const handle = sequence(corsHandler(allowedOrigins), handler);
+3-1
eunomia/src/lib/counter.ts
···1010 let countRaw: string | null = null;
1111 try {
1212 countRaw = await Deno.readTextFile(filePath);
1313- } catch {}
1313+ } catch {
1414+ // we use initial value if not found
1515+ }
14161517 const counter = writable(parseInt(countRaw ?? initialValue.toString()));
1618
+2-41
eunomia/src/lib/visits.ts
···11-import { env } from '$env/dynamic/private';
22-import { scopeCookies } from '$lib';
33-import { DarkVisitors } from '@darkvisitors/sdk';
11+import { scopeCookies } from '$lib/index.ts';
42import type { Cookies } from '@sveltejs/kit';
53import { nanoid } from 'nanoid';
64import { get, writable } from 'svelte/store';
77-import { darkVisitors } from './darkvisitors';
88-99-const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount`;
1010-const readVisitCount = async () => {
1111- try {
1212- return parseInt(await Deno.readTextFile(visitCountFile));
1313- } catch {
1414- return 0;
1515- }
1616-};
1717-export const visitCount = writable(await readVisitCount());
55+import { darkVisitors } from './darkvisitors.ts';
186197export type Visitor = { visits: number[] };
208export const lastVisitors = writable<Map<string, Visitor>>(new Map());
219const VISITOR_EXPIRY_SECONDS = 60 * 60; // an hour seems reasonable
2222-2323-export const decrementVisitCount = () => {
2424- visitCount.set(get(visitCount) - 1);
2525-};
2626-2727-export const incrementVisitCount = async (request: Request, cookies: Cookies) => {
2828- let currentVisitCount = get(visitCount);
2929- // check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots)
3030- if (isBot(request)) return false;
3131- const scopedCookies = scopeCookies(cookies, '/');
3232- // parse the last visit timestamp from cookies if it exists
3333- const visitedTimestamp = parseInt(scopedCookies.get('visitedTimestamp') || '0');
3434- // get unix timestamp
3535- const currentTime = Date.now();
3636- const timeSinceVisit = currentTime - visitedTimestamp;
3737- // check if this is the first time a client is visiting or if an hour has passed since they last visited
3838- if (visitedTimestamp === 0 || timeSinceVisit > 1000 * 60 * 60 * 24) {
3939- // increment current and write to the store
4040- currentVisitCount += 1;
4141- visitCount.set(currentVisitCount);
4242- // update the cookie with the current timestamp
4343- scopedCookies.set('visitedTimestamp', currentTime.toString());
4444- // write the visit count to a file so we can load it later again
4545- await Deno.writeTextFile(visitCountFile, currentVisitCount.toString());
4646- }
4747- return true;
4848-};
49105011export const removeLastVisitor = (id: string) => {
5112 const visitors = get(lastVisitors);
+1-2
eunomia/src/routes/(site)/+layout.server.ts
···11import { getRequestEvent } from '$app/server';
22import { newToken as getApiToken } from '$lib/apiToken.js';
33import { bounceCount, distanceTravelled } from '$lib/metrics.js';
44-import { lastVisitors, visitCount } from '$lib/visits.js';
44+import { lastVisitors } from '$lib/visits.js';
55import { isIPv6 } from 'node:net';
66import { get } from 'svelte/store';
77···6262 route: url.pathname,
6363 petTotalBounce: bounceCount.get(),
6464 petTotalDistance: distanceTravelled.get(),
6565- visitCount: get(visitCount),
6665 lastVisitors: visitors,
6766 recentVisitCount,
6867 eyePositions,
···55 "checkJs": true,
66 "esModuleInterop": true,
77 "forceConsistentCasingInFileNames": true,
88+ "allowImportingTsExtensions": true,
89 "resolveJsonModule": true,
910 "skipLibCheck": true,
1011 "sourceMap": true,
···1617 }
1718 ]
1819 }
1919- // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
2020- // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
2121- //
2222- // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
2323- // from the referenced tsconfig.json - TypeScript does not merge them in
2420}