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

Configure Feed

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

feat: use last visitors to show recent clicks

dusk dc04c39b da8485f2

+47 -23
+1 -1
src/components/window.svelte
··· 59 59 class=" 60 60 window-titlebar p-1 border-ralsei-white border-8 61 61 bg-gradient-to-l from-ralsei-pink-neon to-ralsei-black to-75% 62 - {!isOnMobile ? "cursor-move" : ""} uppercase 62 + {!isOnMobile ? "cursor-move" : ""} 63 63 " 64 64 style="border-style: hidden hidden ridge hidden;" 65 65 >
+31 -17
src/lib/visits.ts
··· 2 2 import { scopeCookies } from "$lib"; 3 3 import type { Cookies } from "@sveltejs/kit"; 4 4 import { existsSync, readFileSync, writeFileSync } from "fs"; 5 + import { nanoid } from "nanoid"; 5 6 import { get, writable } from "svelte/store"; 6 7 7 8 const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount` 8 - const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0')); 9 + const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0')) 9 10 10 - type Visitor = { since: number } 11 - const lastVisitors = writable<Visitor[]>([]); 12 - const MAX_VISITORS = 10 13 - const VISITOR_EXPIRY_SECONDS = 60 * 30 // half an hour is reasonable 11 + type Visitor = { count: number, since: number } 12 + const lastVisitors = writable<Map<string, Visitor>>(new Map()) 13 + const VISITOR_EXPIRY_SECONDS = 60 * 60 * 1 14 14 15 15 export const incrementVisitCount = (request: Request, cookies: Cookies) => { 16 16 let currentVisitCount = get(visitCount) ··· 35 35 } 36 36 37 37 export const addLastVisitor = (request: Request, cookies: Cookies) => { 38 + let visitors = get(lastVisitors) 39 + console.log(visitors) 40 + visitors = _addLastVisitor(visitors, request, cookies) 41 + lastVisitors.set(visitors) 42 + return visitors 43 + } 44 + 45 + // why not use this for incrementVisitCount? cuz i wanna have separate visit counts (one per hour and one per day, per hour being recent visitors) 46 + const _addLastVisitor = (visitors: Map<string, Visitor>, request: Request, cookies: Cookies) => { 38 47 const currentTime = Date.now() 39 - let visitors = get(lastVisitors).filter( 40 - (value) => { return currentTime - value.since > 1000 * VISITOR_EXPIRY_SECONDS } 48 + // filter out old entries 49 + visitors = new Map( 50 + visitors.entries().filter( 51 + ([_, value]) => 52 + { return currentTime - value.since < 1000 * VISITOR_EXPIRY_SECONDS } 53 + ) 41 54 ) 42 55 // check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots) 43 56 if (isBot(request)) { return visitors } 44 57 const scopedCookies = scopeCookies(cookies, '/') 45 58 // parse the last visit timestamp from cookies if it exists 46 - const visitorTimestamp = parseInt(scopedCookies.get('visitorTimestamp') || "0") 47 - // get unix timestamp 48 - const timeSinceVisit = currentTime - visitorTimestamp 49 - // check if this is the first time a client is visiting or if an hour has passed since they last visited 50 - if (visitorTimestamp === 0 || timeSinceVisit > 1000 * VISITOR_EXPIRY_SECONDS) { 51 - visitors.push({ since: currentTime }) 52 - if (visitors.length > MAX_VISITORS) { visitors.shift() } 53 - // update the cookie with the current timestamp 54 - scopedCookies.set('visitorTimestamp', currentTime.toString()) 59 + let visitorId = scopedCookies.get('visitorId') || "" 60 + // if no such id exists, create one and assign it to the client 61 + if (! visitors.has(visitorId)) { 62 + visitorId = nanoid() 63 + scopedCookies.set('visitorId', visitorId) 64 + console.log(`new client id ${visitorId}`) 55 65 } 66 + // update the entry 67 + let visitorEntry = visitors.get(visitorId) || {count: 0, since: 0} 68 + visitorEntry.count += 1 69 + visitorEntry.since = currentTime 70 + visitors.set(visitorId, visitorEntry); 56 71 return visitors 57 72 } 58 73 59 74 const isBot = (request: Request) => { 60 75 const ua = request.headers.get('user-agent') 61 76 return ua ? ua.toLowerCase().match(/(bot|crawl|spider|walk|fetch|scrap|proxy|image)/) !== null : true 62 - 63 77 } 64 78 65 79 export const notifyDarkVisitors = (url: URL, request: Request) => {
+2 -1
src/routes/+layout.server.ts
··· 1 1 import { testUa } from '$lib/robots.js'; 2 - import { incrementVisitCount, notifyDarkVisitors } from '$lib/visits.js'; 2 + import { addLastVisitor, incrementVisitCount, notifyDarkVisitors } from '$lib/visits.js'; 3 3 import { error } from '@sveltejs/kit'; 4 4 5 5 export const csr = true; ··· 18 18 return { 19 19 route: url.pathname, 20 20 visitCount: incrementVisitCount(request, cookies), 21 + lastVisitors: addLastVisitor(request, cookies), 21 22 } 22 23 }
+12 -4
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import { browser } from '$app/environment'; 3 3 import getTitle from '$lib/getTitle'; 4 + import type { Visitor } from 'svelte/types/compiler/interfaces'; 4 5 import NavButton from '../components/navButton.svelte'; 5 6 import Tooltip from '../components/tooltip.svelte'; 6 7 import Window from '../components/window.svelte'; ··· 40 41 }; 41 42 42 43 $: title = getTitle(data.route); 44 + 45 + $: recentVisitCount = data.lastVisitors.values().reduce( 46 + (total, visitor) => { return total + visitor.count; }, 0 47 + ) 43 48 44 49 const svgSquiggles = [[2], [3], [2], [3], [1]]; 45 50 </script> ··· 140 145 <slot /> 141 146 </div> 142 147 143 - <nav class="w-full min-h-[5vh] max-h-[6vh] fixed bottom-0 z-[999] bg-ralsei-black overflow-visible uppercase"> 148 + <nav class="w-full min-h-[5vh] max-h-[6vh] fixed bottom-0 z-[999] bg-ralsei-black overflow-visible"> 144 149 <div 145 150 class=" 146 151 max-w-full max-h-fit p-1 z-[999] ··· 160 165 <div class="hidden md:block grow" /> 161 166 <div class="navbox"> 162 167 <a title="previous site" class="hover:underline" href="https://xn--sr8hvo.ws/previous">⮜</a> 163 - <a class="hover:underline" href="https://xn--sr8hvo.ws">IndieWeb 🕸💍</a> 168 + <a class="hover:underline" href="https://xn--sr8hvo.ws">indieweb 🕸💍</a> 164 169 <a title="next site" class="hover:underline" href="https://xn--sr8hvo.ws/next">⮞</a> 165 170 </div> 166 171 <Tooltip> 167 172 <svelte:fragment slot="tooltipContent"> 168 - <img class="min-w-64" style="image-rendering: crisp-edges pixelated;" alt="visits" src="https://count.getloli.com/@yusdacrawebsite?name=yusdacrawebsitetest&theme=booru-lewd&padding=5&offset=0&align=center&scale=1&pixelated=1&darkmode=0&num={data.visitCount}"/> 173 + <p class="font-monospace"> 174 + <nobr>total visits = <span class="text-ralsei-green-light text-shadow-green">{data.visitCount.toString().padStart(10, "0")}</span></nobr> 175 + <nobr>unique recent visits = <span class="text-ralsei-green-light text-shadow-green">{data.lastVisitors.size.toString().padStart(2, "0")}</span></nobr> 176 + </p> 169 177 </svelte:fragment> 170 - <div class="navbox"><p><span class="text-ralsei-green-light text-shadow-green">{data.visitCount}</span> visit(s)</p></div> 178 + <div class="navbox"><p><span class="text-ralsei-green-light text-shadow-green">{recentVisitCount}</span> recent clicks</p></div> 171 179 </Tooltip> 172 180 {#if isRoute("entries") || isRoute("log")} 173 181 <div class="navbox !gap-1">
+1
src/routes/+page.svelte
··· 19 19 <img 20 20 width="150" 21 21 height="20" 22 + title="banners from https://blinkies.cafe/" 22 23 alt="banner" 23 24 class=" 24 25 {hideIfMobile ? 'hidden' : ''} sm:inline w-[150px] [height:20px]