data endpoint for entity 90008 (aka. a website)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at svelte 147 lines 4.8 kB view raw
1import { updateLastPosts } from './lib/bluesky.ts'; 2import { updateNowPlayingTrack } from '$lib/lastfm.ts'; 3import { steamUpdateNowPlaying } from '$lib/steam.ts'; 4import { updateCommits } from '$lib/activity.ts'; 5import { ToadScheduler, SimpleIntervalJob, AsyncTask } from 'toad-scheduler'; 6import { 7 incrementFakeVisitCount, 8 incrementLegitVisitCount, 9 pushMetric, 10 sendAllMetrics 11} from '$lib/metrics.ts'; 12import { addLastVisitor, notifyDarkVisitors, removeLastVisitor } from '$lib/visits.ts'; 13import { testUa } from '$lib/robots.ts'; 14import { error, type Handle } from '@sveltejs/kit'; 15import { _fetchEntries } from './routes/(site)/guestbook/+page.server.ts'; 16import { sequence } from '@sveltejs/kit/hooks'; 17import { initConstellation, renderConstellation } from '$lib/constellation.ts'; 18 19// Init constellation on startup (non-blocking) 20initConstellation(); 21 22const updateNowPlaying = async () => { 23 try { 24 await Promise.all([steamUpdateNowPlaying(), updateNowPlayingTrack()]); 25 } catch (err) { 26 console.log(`error while updating: ${err}`); 27 } 28}; 29const refreshContent = async () => { 30 try { 31 await Promise.all([updateLastPosts(), _fetchEntries(), updateCommits(), sendAllMetrics()]); 32 } catch (err) { 33 console.log(`error while updating: ${err}`); 34 } 35}; 36 37await Promise.all([updateNowPlaying(), refreshContent()]); 38 39const scheduler = new ToadScheduler(); 40scheduler.addSimpleIntervalJob( 41 new SimpleIntervalJob( 42 { seconds: 5 }, 43 new AsyncTask('updateNowPlaying task', updateNowPlaying, (err) => 44 console.log(`error while updateNowPlaying: ${err}`) 45 ) 46 ) 47); 48scheduler.addSimpleIntervalJob( 49 new SimpleIntervalJob( 50 { seconds: 30 }, 51 new AsyncTask('refreshContent task', refreshContent, (err) => 52 console.log(`error while refreshContent: ${err}`) 53 ) 54 ) 55); 56scheduler.addSimpleIntervalJob( 57 new SimpleIntervalJob( 58 { minutes: 1 }, 59 new AsyncTask('rotateConstellation task', renderConstellation, (err) => 60 console.log(`error while rotateConstellation: ${err}`) 61 ) 62 ) 63); 64scheduler.addSimpleIntervalJob( 65 new SimpleIntervalJob( 66 { days: 1 }, 67 new AsyncTask('initConstellation task', initConstellation, (err) => 68 console.log(`error while initConstellation: ${err}`) 69 ) 70 ) 71); 72 73const corsHandler = (allowedOrigins = ['*']) => { 74 return async ({ event, resolve }: Parameters<Handle>[0]) => { 75 const origin = event.request.headers.get('origin'); 76 77 const corsHeaders: Record<string, string> = { 78 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 79 'Access-Control-Allow-Headers': 'Content-Type, Authorization' 80 }; 81 82 if (allowedOrigins.includes('*')) corsHeaders['Access-Control-Allow-Origin'] = '*'; 83 else if (origin && allowedOrigins.includes(origin)) { 84 corsHeaders['Access-Control-Allow-Origin'] = origin; 85 corsHeaders['Access-Control-Allow-Credentials'] = 'true'; 86 } 87 88 if (event.request.method === 'OPTIONS') return new Response(null, { headers: corsHeaders }); 89 90 const response = await resolve(event); 91 92 Object.entries(corsHeaders).forEach(([key, value]) => { 93 response.headers.set(key, value); 94 }); 95 96 return response; 97 }; 98}; 99 100const handler = async ({ event, resolve }: Parameters<Handle>[0]) => { 101 notifyDarkVisitors(event.url, event.request); // no await so it doesnt block 102 103 const isPrefetch = () => { 104 return ( 105 event.request.headers.get('Sec-Purpose')?.includes('prefetch') || 106 event.request.headers.get('Purpose')?.includes('prefetch') || 107 event.request.headers.get('x-purpose')?.includes('preview') || 108 event.request.headers.get('x-moz')?.includes('prefetch') 109 ); 110 }; 111 const isApi = () => { 112 return event.url.pathname.startsWith('/_api'); 113 }; 114 const isRss = () => { 115 return event.url.pathname.endsWith('/_rss'); 116 }; 117 118 // block any requests if the user agent is disallowed by our robots txt 119 const isFakeVisit = 120 (await testUa(event.url.toString(), event.request.headers.get('user-agent') ?? '')) === false; 121 if (isFakeVisit) { 122 pushMetric({ gazesys_visit_fake_total: await incrementFakeVisitCount() }); 123 throw error(403, 'get a better user agent silly'); 124 } 125 126 // only push metric if legit page visit (still want rss to count here though) 127 const isPageVisit = !isApi() && !isPrefetch(); 128 if (isPageVisit) pushMetric({ gazesys_visit_real_total: await incrementLegitVisitCount() }); 129 130 // only add visitors if its a "legit" page visit 131 let id = null; 132 if (isPageVisit && !isRss()) { 133 id = addLastVisitor(event.request, event.cookies); 134 } 135 136 // actually resolve event 137 const resp = await resolve(event); 138 // remove visitors if it was a 404 139 if (resp.status === 404) { 140 if (id !== null) removeLastVisitor(id); 141 } 142 143 return resp; 144}; 145 146const allowedOrigins = ['https://gaze.systems', 'https://ptr.pet', 'https://poor.dog']; 147export const handle = sequence(corsHandler(allowedOrigins), handler);