Compare commits
7 Commits
e88feaa80f
...
48727b60e7
Author | SHA1 | Date | |
---|---|---|---|
48727b60e7 | |||
2b30a0fd08 | |||
78d7752f81 | |||
8bc6d13650 | |||
0dc606643a | |||
dc04c39bd3 | |||
da8485f20f |
@ -59,7 +59,7 @@
|
|||||||
class="
|
class="
|
||||||
window-titlebar p-1 border-ralsei-white border-8
|
window-titlebar p-1 border-ralsei-white border-8
|
||||||
bg-gradient-to-l from-ralsei-pink-neon to-ralsei-black to-75%
|
bg-gradient-to-l from-ralsei-pink-neon to-ralsei-black to-75%
|
||||||
{!isOnMobile ? "cursor-move" : ""} uppercase
|
{!isOnMobile ? "cursor-move" : ""}
|
||||||
"
|
"
|
||||||
style="border-style: hidden hidden ridge hidden;"
|
style="border-style: hidden hidden ridge hidden;"
|
||||||
>
|
>
|
||||||
|
@ -2,15 +2,15 @@ import { env } from "$env/dynamic/private";
|
|||||||
import { scopeCookies } from "$lib";
|
import { scopeCookies } from "$lib";
|
||||||
import type { Cookies } from "@sveltejs/kit";
|
import type { Cookies } from "@sveltejs/kit";
|
||||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import { get, writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
|
|
||||||
const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount`
|
const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount`
|
||||||
const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0'));
|
const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0'))
|
||||||
|
|
||||||
type Visitor = { since: number }
|
type Visitor = { visits: number[] }
|
||||||
const lastVisitors = writable<Visitor[]>([]);
|
const lastVisitors = writable<Map<string, Visitor>>(new Map())
|
||||||
const MAX_VISITORS = 10
|
const VISITOR_EXPIRY_SECONDS = 60 * 60 * 1
|
||||||
const VISITOR_EXPIRY_SECONDS = 60 * 30 // half an hour is reasonable
|
|
||||||
|
|
||||||
export const incrementVisitCount = (request: Request, cookies: Cookies) => {
|
export const incrementVisitCount = (request: Request, cookies: Cookies) => {
|
||||||
let currentVisitCount = get(visitCount)
|
let currentVisitCount = get(visitCount)
|
||||||
@ -35,31 +35,51 @@ export const incrementVisitCount = (request: Request, cookies: Cookies) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const addLastVisitor = (request: Request, cookies: Cookies) => {
|
export const addLastVisitor = (request: Request, cookies: Cookies) => {
|
||||||
|
let visitors = get(lastVisitors)
|
||||||
|
visitors = _addLastVisitor(visitors, request, cookies)
|
||||||
|
lastVisitors.set(visitors)
|
||||||
|
return visitors
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
const _addLastVisitor = (visitors: Map<string, Visitor>, request: Request, cookies: Cookies) => {
|
||||||
const currentTime = Date.now()
|
const currentTime = Date.now()
|
||||||
let visitors = get(lastVisitors).filter(
|
// filter out old entries
|
||||||
(value) => { return currentTime - value.since > 1000 * VISITOR_EXPIRY_SECONDS }
|
visitors = new Map(
|
||||||
|
visitors.entries().filter(
|
||||||
|
([_, visitor]) =>
|
||||||
|
{ return currentTime - visitor.visits[0] < 1000 * VISITOR_EXPIRY_SECONDS }
|
||||||
|
).map(
|
||||||
|
([id, visitor]) => {
|
||||||
|
visitor.visits = visitor.visits.filter((since) => {
|
||||||
|
return currentTime - since < 1000 * VISITOR_EXPIRY_SECONDS
|
||||||
|
})
|
||||||
|
return [id, visitor]
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
// check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots)
|
// check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots)
|
||||||
if (isBot(request)) { return visitors }
|
if (isBot(request)) { return visitors }
|
||||||
const scopedCookies = scopeCookies(cookies, '/')
|
const scopedCookies = scopeCookies(cookies, '/')
|
||||||
// parse the last visit timestamp from cookies if it exists
|
// parse the last visit timestamp from cookies if it exists
|
||||||
const visitorTimestamp = parseInt(scopedCookies.get('visitorTimestamp') || "0")
|
let visitorId = scopedCookies.get('visitorId') || ""
|
||||||
// get unix timestamp
|
// if no such id exists, create one and assign it to the client
|
||||||
const timeSinceVisit = currentTime - visitorTimestamp
|
if (! visitors.has(visitorId)) {
|
||||||
// check if this is the first time a client is visiting or if an hour has passed since they last visited
|
visitorId = nanoid()
|
||||||
if (visitorTimestamp === 0 || timeSinceVisit > 1000 * VISITOR_EXPIRY_SECONDS) {
|
scopedCookies.set('visitorId', visitorId)
|
||||||
visitors.push({ since: currentTime })
|
console.log(`new client visitor id ${visitorId}`)
|
||||||
if (visitors.length > MAX_VISITORS) { visitors.shift() }
|
|
||||||
// update the cookie with the current timestamp
|
|
||||||
scopedCookies.set('visitorTimestamp', currentTime.toString())
|
|
||||||
}
|
}
|
||||||
|
// update the entry
|
||||||
|
let visitorEntry = visitors.get(visitorId) || {visits: []}
|
||||||
|
// put new visit in the front
|
||||||
|
visitorEntry.visits = [currentTime].concat(visitorEntry.visits)
|
||||||
|
visitors.set(visitorId, visitorEntry);
|
||||||
return visitors
|
return visitors
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBot = (request: Request) => {
|
const isBot = (request: Request) => {
|
||||||
const ua = request.headers.get('user-agent')
|
const ua = request.headers.get('user-agent')
|
||||||
return ua ? ua.toLowerCase().match(/(bot|crawl|spider|walk|fetch|scrap|proxy|image)/) !== null : true
|
return ua ? ua.toLowerCase().match(/(bot|crawl|spider|walk|fetch|scrap|proxy|image)/) !== null : true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const notifyDarkVisitors = (url: URL, request: Request) => {
|
export const notifyDarkVisitors = (url: URL, request: Request) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { testUa } from '$lib/robots.js';
|
import { testUa } from '$lib/robots.js';
|
||||||
import { incrementVisitCount, notifyDarkVisitors } from '$lib/visits.js';
|
import { addLastVisitor, incrementVisitCount, notifyDarkVisitors } from '$lib/visits.js';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const csr = true;
|
export const csr = true;
|
||||||
@ -18,5 +18,6 @@ export async function load({ request, cookies, url }) {
|
|||||||
return {
|
return {
|
||||||
route: url.pathname,
|
route: url.pathname,
|
||||||
visitCount: incrementVisitCount(request, cookies),
|
visitCount: incrementVisitCount(request, cookies),
|
||||||
|
lastVisitors: addLastVisitor(request, cookies),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import getTitle from '$lib/getTitle';
|
import getTitle from '$lib/getTitle';
|
||||||
|
import type { Visitor } from 'svelte/types/compiler/interfaces';
|
||||||
import NavButton from '../components/navButton.svelte';
|
import NavButton from '../components/navButton.svelte';
|
||||||
import Tooltip from '../components/tooltip.svelte';
|
import Tooltip from '../components/tooltip.svelte';
|
||||||
import Window from '../components/window.svelte';
|
import Window from '../components/window.svelte';
|
||||||
@ -41,6 +42,10 @@
|
|||||||
|
|
||||||
$: title = getTitle(data.route);
|
$: title = getTitle(data.route);
|
||||||
|
|
||||||
|
$: recentVisitCount = data.lastVisitors.values().reduce(
|
||||||
|
(total, visitor) => { return total + visitor.visits.length; }, 0
|
||||||
|
)
|
||||||
|
|
||||||
const svgSquiggles = [[2], [3], [2], [3], [1]];
|
const svgSquiggles = [[2], [3], [2], [3], [1]];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -140,7 +145,7 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="w-full min-h-[5vh] max-h-[6vh] fixed bottom-0 z-[999] bg-ralsei-black overflow-visible uppercase">
|
<nav class="w-full min-h-[5vh] max-h-[6vh] fixed bottom-0 z-[999] bg-ralsei-black overflow-visible">
|
||||||
<div
|
<div
|
||||||
class="
|
class="
|
||||||
max-w-full max-h-fit p-1 z-[999]
|
max-w-full max-h-fit p-1 z-[999]
|
||||||
@ -160,14 +165,17 @@
|
|||||||
<div class="hidden md:block grow" />
|
<div class="hidden md:block grow" />
|
||||||
<div class="navbox">
|
<div class="navbox">
|
||||||
<a title="previous site" class="hover:underline" href="https://xn--sr8hvo.ws/previous">⮜</a>
|
<a title="previous site" class="hover:underline" href="https://xn--sr8hvo.ws/previous">⮜</a>
|
||||||
<a class="hover:underline" href="https://xn--sr8hvo.ws">IndieWeb 🕸💍</a>
|
<a class="hover:underline" href="https://xn--sr8hvo.ws">indieweb 🕸💍</a>
|
||||||
<a title="next site" class="hover:underline" href="https://xn--sr8hvo.ws/next">⮞</a>
|
<a title="next site" class="hover:underline" href="https://xn--sr8hvo.ws/next">⮞</a>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<svelte:fragment slot="tooltipContent">
|
<svelte:fragment slot="tooltipContent">
|
||||||
<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}"/>
|
<p class="font-monospace">
|
||||||
|
<nobr>total visits = <span class="text-ralsei-green-light text-shadow-green">{data.visitCount.toString().padStart(9, ".")}</span></nobr>
|
||||||
|
<nobr>uniq recent visits = <span class="text-ralsei-green-light text-shadow-green">{data.lastVisitors.size.toString().padStart(3, ".")}</span></nobr>
|
||||||
|
</p>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<div class="navbox"><p><span class="text-ralsei-green-light text-shadow-green">{data.visitCount}</span> visit(s)</p></div>
|
<div class="navbox"><p><span class="text-ralsei-green-light text-shadow-green">{recentVisitCount}</span> recent clicks</p></div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{#if isRoute("entries") || isRoute("log")}
|
{#if isRoute("entries") || isRoute("log")}
|
||||||
<div class="navbox !gap-1">
|
<div class="navbox !gap-1">
|
||||||
|
@ -4,10 +4,18 @@ import { steamGetNowPlaying } from "$lib/steam"
|
|||||||
export const load = async ({}) => {
|
export const load = async ({}) => {
|
||||||
const lastTrack = await lastFmGetNowPlaying()
|
const lastTrack = await lastFmGetNowPlaying()
|
||||||
const lastGame = await steamGetNowPlaying()
|
const lastGame = await steamGetNowPlaying()
|
||||||
const banners = [getBannerNo(), getBannerNo(), getBannerNo()]
|
let banners: number[] = []
|
||||||
|
while (banners.length < 3) {
|
||||||
|
const no = getBannerNo(banners)
|
||||||
|
banners.push(no)
|
||||||
|
}
|
||||||
return {banners, lastTrack, lastGame}
|
return {banners, lastTrack, lastGame}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBannerNo = () => {
|
const getBannerNo = (others: number[]) => {
|
||||||
return Math.floor(Math.random() * 18) + 1;
|
const no = Math.floor(Math.random() * 20) + 1
|
||||||
|
if (others.includes(no)) {
|
||||||
|
return ((no + (Math.floor(Math.random() * 20))) % 20) + 1
|
||||||
|
}
|
||||||
|
return no
|
||||||
};
|
};
|
@ -19,6 +19,7 @@
|
|||||||
<img
|
<img
|
||||||
width="150"
|
width="150"
|
||||||
height="20"
|
height="20"
|
||||||
|
title="banners from https://blinkies.cafe/"
|
||||||
alt="banner"
|
alt="banner"
|
||||||
class="
|
class="
|
||||||
{hideIfMobile ? 'hidden' : ''} sm:inline w-[150px] [height:20px]
|
{hideIfMobile ? 'hidden' : ''} sm:inline w-[150px] [height:20px]
|
||||||
|
@ -3,10 +3,11 @@ title = "itches"
|
|||||||
layout = "simple"
|
layout = "simple"
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
*last updated on: 16-01-2025*
|
||||||
|
|
||||||
- website / social presence todos on [the indieweb wiki](https://indieweb.org/User:Gaze.systems)
|
- website / social presence todos on [the indieweb wiki](https://indieweb.org/User:Gaze.systems)
|
||||||
- want to start reading chaos;head and chaos;child
|
|
||||||
- start playing killer7 and the silver case etc.
|
|
||||||
- participate in a game jam every month
|
- participate in a game jam every month
|
||||||
- stream game dev stuff (...if i can stop being anxiety x1000)
|
- stream game dev stuff (...if i can stop being anxiety x1000)
|
||||||
- mess around with kinect devkit, try to reimplement skeleton estimation?
|
- mess around with kinect devkit, try to reimplement skeleton estimation?
|
||||||
- fly more in VTOL VR with people
|
- want to start reading chaos;head and chaos;child
|
||||||
|
- start playing killer7 and the silver case etc.
|
@ -20,11 +20,12 @@ import Window from '../../components/window.svelte';
|
|||||||
<div class="flex flex-col gap-4 2xl:w-[60ch] leading-6">
|
<div class="flex flex-col gap-4 2xl:w-[60ch] leading-6">
|
||||||
<p>
|
<p>
|
||||||
hia, here is the guestbook if you wanna post anything :)
|
hia, here is the guestbook if you wanna post anything :)
|
||||||
<br />
|
|
||||||
just fill the post in and click on your preferred auth method to post
|
|
||||||
</p>
|
</p>
|
||||||
<p>rules: be a good human bean pretty please</p>
|
<p>
|
||||||
<p>don't be shy!!!</p>
|
just fill the post in and click on your preferred auth method to post
|
||||||
|
(auth is there because i don't want to deal with any bots, sorry -.-)
|
||||||
|
</p>
|
||||||
|
<p>rules: be a good human bean pretty please (and don't be shy!!!)</p>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="entry entryflex">
|
<div class="entry entryflex">
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
|
BIN
static/banners/19.gif
Normal file
BIN
static/banners/19.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
static/banners/20.gif
Normal file
BIN
static/banners/20.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Loading…
x
Reference in New Issue
Block a user