From 0e46dc943ee320adfeb5875d65db60552c7b1ecb Mon Sep 17 00:00:00 2001 From: dusk <y.bera003.06@protonmail.com> Date: Wed, 2 Apr 2025 05:13:38 +0300 Subject: [PATCH] feat: update texT --- src/lib/bluesky.ts | 84 ++++++++------- src/lib/lastfm.ts | 70 ++++++------ src/lib/steam.ts | 89 ++++++++------- src/lib/visits.ts | 202 +++++++++++++++++++---------------- src/lib/window.ts | 23 ++-- src/routes/+layout.server.ts | 34 +++--- src/routes/+layout.svelte | 57 ++++++---- src/routes/+page.svelte | 196 +++++++++++++++++---------------- 8 files changed, 417 insertions(+), 338 deletions(-) diff --git a/src/lib/bluesky.ts b/src/lib/bluesky.ts index 2451ad9..d37f783 100644 --- a/src/lib/bluesky.ts +++ b/src/lib/bluesky.ts @@ -1,48 +1,56 @@ -import { env } from '$env/dynamic/private' -import { Bot, type Post } from "@skyware/bot"; -import { get, writable } from 'svelte/store' +import { env } from '$env/dynamic/private'; +import { Bot, type Post } from '@skyware/bot'; +import { get, writable } from 'svelte/store'; -const bskyClient = writable<null | Bot>(null) +const bskyClient = writable<null | Bot>(null); export const getBskyClient = async () => { - let client = get(bskyClient) - if (client === null) { - client = await loginToBsky() - bskyClient.set(client) - } - return client -} + let client = get(bskyClient); + if (client === null) { + client = await loginToBsky(); + bskyClient.set(client); + } + return client; +}; const loginToBsky = async () => { - const bot = new Bot({ service: "https://gaze.systems" }) - await bot.login({ identifier: 'guestbook.gaze.systems', password: env.BSKY_PASSWORD ?? "" }) - return bot -} + const bot = new Bot({ service: 'https://gaze.systems' }); + await bot.login({ identifier: 'guestbook.gaze.systems', password: env.BSKY_PASSWORD ?? '' }); + return bot; +}; -export const getUserPosts = async (did: string, count: number = 10, cursor: string | null = null) => { - const client = await getBskyClient() - let feedCursor: string | null | undefined = cursor; - let posts: Post[] = [] - // fetch requested amount of posts - while (posts.length < count - 1 && (typeof feedCursor === "string" || feedCursor === null)) { - let feedData = await client.getUserPosts( - did, { limit: count, filter: 'posts_no_replies', cursor: feedCursor === null ? undefined : feedCursor } - ) - posts.push(...feedData.posts.filter((post) => post.author.did === did)) - feedCursor = feedData.cursor - } - return { posts, cursor: feedCursor === null ? undefined : feedCursor } -} +export const getUserPosts = async ( + did: string, + count: number = 10, + cursor: string | null = null +) => { + const client = await getBskyClient(); + let feedCursor: string | null | undefined = cursor; + const posts: Post[] = []; + // fetch requested amount of posts + while (posts.length < count - 1 && (typeof feedCursor === 'string' || feedCursor === null)) { + const feedData = await client.getUserPosts(did, { + limit: count, + filter: 'posts_no_replies', + cursor: feedCursor === null ? undefined : feedCursor + }); + posts.push(...feedData.posts.filter((post) => post.author.did === did)); + feedCursor = feedData.cursor; + } + return { posts, cursor: feedCursor === null ? undefined : feedCursor }; +}; -const lastPosts = writable<Post[]>([]) +const lastPosts = writable<Post[]>([]); export const updateLastPosts = async () => { - try { - const { posts } = await getUserPosts("did:plc:dfl62fgb7wtjj3fcbb72naae", 13) - lastPosts.set(posts) - } catch (err) { - console.log(`can't update last posts ${err}`) - } -} + try { + const { posts } = await getUserPosts('did:plc:dfl62fgb7wtjj3fcbb72naae', 13); + lastPosts.set(posts); + } catch (err) { + console.log(`can't update last posts ${err}`); + } +}; -export const getLastPosts = () => { return get(lastPosts) } \ No newline at end of file +export const getLastPosts = () => { + return get(lastPosts); +}; diff --git a/src/lib/lastfm.ts b/src/lib/lastfm.ts index 8bdff5a..18fcc7f 100644 --- a/src/lib/lastfm.ts +++ b/src/lib/lastfm.ts @@ -1,37 +1,45 @@ -import { get, writable } from "svelte/store" +import { get, writable } from 'svelte/store'; -const GET_RECENT_TRACKS_ENDPOINT = "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=yusdacra&api_key=da1911d405b5b37383e200b8f36ee9ec&format=json&limit=1" +const GET_RECENT_TRACKS_ENDPOINT = + 'https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=yusdacra&api_key=da1911d405b5b37383e200b8f36ee9ec&format=json&limit=1'; type LastTrack = { - name: string, - artist: string, - image: string | null, - link: string, - when: number, - playing: boolean, -} -const lastTrack = writable<LastTrack | null>(null) + name: string; + artist: string; + image: string | null; + link: string; + when: number; + playing: boolean; +}; +const lastTrack = writable<LastTrack | null>(null); export const lastFmUpdateNowPlaying = async () => { - try { - var resp = await (await fetch(GET_RECENT_TRACKS_ENDPOINT)).json() - var track = resp.recenttracks.track[0] ?? null - if (!((track['@attr'] ?? {}).nowplaying ?? null)) { - throw "no nowplaying track found" - } - var data = { - name: track.name, - artist: track.artist['#text'], - image: track.image[2]['#text'] ?? null, - link: track.url, - when: Date.now(), - playing: true, - } - lastTrack.set(data) - } catch(why) { - console.log("could not fetch last fm: ", why) - lastTrack.update((t) => { if (t !== null) { t.playing = false; } return t }) - } -} + try { + const resp = await (await fetch(GET_RECENT_TRACKS_ENDPOINT)).json(); + const track = resp.recenttracks.track[0] ?? null; + if (!((track['@attr'] ?? {}).nowplaying ?? null)) { + throw 'no nowplaying track found'; + } + const data = { + name: track.name, + artist: track.artist['#text'], + image: track.image[2]['#text'] ?? null, + link: track.url, + when: Date.now(), + playing: true + }; + lastTrack.set(data); + } catch (why) { + console.log('could not fetch last fm: ', why); + lastTrack.update((t) => { + if (t !== null) { + t.playing = false; + } + return t; + }); + } +}; -export const getNowPlaying = () => { return get(lastTrack) } \ No newline at end of file +export const getNowPlaying = () => { + return get(lastTrack); +}; diff --git a/src/lib/steam.ts b/src/lib/steam.ts index eaf5779..b9743f8 100644 --- a/src/lib/steam.ts +++ b/src/lib/steam.ts @@ -1,48 +1,55 @@ -import { env } from "$env/dynamic/private"; -import SGDB from "steamgriddb"; -import { get, writable } from "svelte/store"; +import { env } from '$env/dynamic/private'; +import SGDB from 'steamgriddb'; +import { get, writable } from 'svelte/store'; -const STEAM_ID = "76561198106829949" -const GET_PLAYER_SUMMARY_ENDPOINT = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${env.STEAM_API_KEY}&steamids=${STEAM_ID}&format=json` +const STEAM_ID = '76561198106829949'; +const GET_PLAYER_SUMMARY_ENDPOINT = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${env.STEAM_API_KEY}&steamids=${STEAM_ID}&format=json`; type LastGame = { - name: string, - link: string, - icon: string, - pfp: string, - when: number, - playing: boolean, -} + name: string; + link: string; + icon: string; + pfp: string; + when: number; + playing: boolean; +}; -const steamgriddbClient = writable<SGDB | null>(null) -const lastGame = writable<LastGame | null>(null) +const steamgriddbClient = writable<SGDB | null>(null); +const lastGame = writable<LastGame | null>(null); export const steamUpdateNowPlaying = async () => { - var griddbClient = get(steamgriddbClient) - if (griddbClient === null) { - griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY) - steamgriddbClient.set(griddbClient) - } - try { - var profile = (await (await fetch(GET_PLAYER_SUMMARY_ENDPOINT)).json()).response.players[0] - if (!profile.gameid) { - throw "no game is being played" - } - var icons = await griddbClient.getIconsBySteamAppId(profile.gameid, ['official', 'custom']) - //console.log(icons) - var game: LastGame = { - name: profile.gameextrainfo, - link: `https://store.steampowered.com/app/${profile.gameid}`, - icon: icons[0].thumb.toString(), - pfp: profile.avatarmedium, - when: Date.now(), - playing: true, - } - lastGame.set(game) - } catch(why) { - console.log("could not fetch steam: ", why) - lastGame.update((t) => { if (t !== null) { t.playing = false; } return t }) - } -} + let griddbClient = get(steamgriddbClient); + if (griddbClient === null) { + griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY); + steamgriddbClient.set(griddbClient); + } + try { + const profile = (await (await fetch(GET_PLAYER_SUMMARY_ENDPOINT)).json()).response.players[0]; + if (!profile.gameid) { + throw 'no game is being played'; + } + const icons = await griddbClient.getIconsBySteamAppId(profile.gameid, ['official', 'custom']); + //console.log(icons) + const game: LastGame = { + name: profile.gameextrainfo, + link: `https://store.steampowered.com/app/${profile.gameid}`, + icon: icons[0].thumb.toString(), + pfp: profile.avatarmedium, + when: Date.now(), + playing: true + }; + lastGame.set(game); + } catch (why) { + console.log('could not fetch steam: ', why); + lastGame.update((t) => { + if (t !== null) { + t.playing = false; + } + return t; + }); + } +}; -export const getLastGame = () => { return get(lastGame) } \ No newline at end of file +export const getLastGame = () => { + return get(lastGame); +}; diff --git a/src/lib/visits.ts b/src/lib/visits.ts index 8b15999..b0b85df 100644 --- a/src/lib/visits.ts +++ b/src/lib/visits.ts @@ -1,110 +1,122 @@ -import { env } from "$env/dynamic/private"; -import { scopeCookies } from "$lib"; -import type { Cookies } from "@sveltejs/kit"; -import { existsSync, readFileSync, writeFileSync } from "fs"; -import { nanoid } from "nanoid"; -import { get, writable } from "svelte/store"; +import { env } from '$env/dynamic/private'; +import { scopeCookies } from '$lib'; +import type { Cookies } from '@sveltejs/kit'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { nanoid } from 'nanoid'; +import { get, writable } from 'svelte/store'; -const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount` -const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0')) +const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount`; +const visitCount = writable( + parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0') +); -type Visitor = { visits: number[] } -const lastVisitors = writable<Map<string, Visitor>>(new Map()) -const VISITOR_EXPIRY_SECONDS = 60 * 60 // an hour seems reasonable +type Visitor = { visits: number[] }; +const lastVisitors = writable<Map<string, Visitor>>(new Map()); +const VISITOR_EXPIRY_SECONDS = 60 * 60; // an hour seems reasonable export const incrementVisitCount = (request: Request, cookies: Cookies) => { - let currentVisitCount = get(visitCount) - // 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 currentVisitCount } - const scopedCookies = scopeCookies(cookies, '/') - // parse the last visit timestamp from cookies if it exists - const visitedTimestamp = parseInt(scopedCookies.get('visitedTimestamp') || "0") - // get unix timestamp - const currentTime = Date.now() - const timeSinceVisit = currentTime - visitedTimestamp - // check if this is the first time a client is visiting or if an hour has passed since they last visited - if (visitedTimestamp === 0 || timeSinceVisit > 1000 * 60 * 60 * 24) { - // increment current and write to the store - currentVisitCount += 1; visitCount.set(currentVisitCount) - // update the cookie with the current timestamp - scopedCookies.set('visitedTimestamp', currentTime.toString()) - // write the visit count to a file so we can load it later again - writeFileSync(visitCountFile, currentVisitCount.toString()) - } - return currentVisitCount -} + let currentVisitCount = get(visitCount); + // 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 currentVisitCount; + } + const scopedCookies = scopeCookies(cookies, '/'); + // parse the last visit timestamp from cookies if it exists + const visitedTimestamp = parseInt(scopedCookies.get('visitedTimestamp') || '0'); + // get unix timestamp + const currentTime = Date.now(); + const timeSinceVisit = currentTime - visitedTimestamp; + // check if this is the first time a client is visiting or if an hour has passed since they last visited + if (visitedTimestamp === 0 || timeSinceVisit > 1000 * 60 * 60 * 24) { + // increment current and write to the store + currentVisitCount += 1; + visitCount.set(currentVisitCount); + // update the cookie with the current timestamp + scopedCookies.set('visitedTimestamp', currentTime.toString()); + // write the visit count to a file so we can load it later again + writeFileSync(visitCountFile, currentVisitCount.toString()); + } + return currentVisitCount; +}; export const addLastVisitor = (request: Request, cookies: Cookies) => { - let visitors = get(lastVisitors) - visitors = _addLastVisitor(visitors, request, cookies) - lastVisitors.set(visitors) - return visitors -} + let visitors = get(lastVisitors); + visitors = _addLastVisitor(visitors, request, cookies); + lastVisitors.set(visitors); + return visitors; +}; export const getVisitorId = (cookies: Cookies) => { - const scopedCookies = scopeCookies(cookies, '/') - // parse the last visit timestamp from cookies if it exists - return scopedCookies.get('visitorId') -} + const scopedCookies = scopeCookies(cookies, '/'); + // parse the last visit timestamp from cookies if it exists + return scopedCookies.get('visitorId'); +}; // 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() - // filter out old entries - visitors.forEach((visitor, id, map) => { - if (currentTime - visitor.visits[0] > 1000 * VISITOR_EXPIRY_SECONDS) - map.delete(id) - else { - visitor.visits = visitor.visits.filter((since) => { - return currentTime - since < 1000 * VISITOR_EXPIRY_SECONDS - }) - map.set(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) - if (isBot(request)) { return visitors } - const scopedCookies = scopeCookies(cookies, '/') - // parse the last visit timestamp from cookies if it exists - let visitorId = scopedCookies.get('visitorId') || "" - // if no such id exists, create one and assign it to the client - if (! visitors.has(visitorId)) { - visitorId = nanoid() - scopedCookies.set('visitorId', visitorId) - console.log(`new client visitor id ${visitorId}`) - } - // 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 -} + const currentTime = Date.now(); + // filter out old entries + visitors.forEach((visitor, id, map) => { + if (currentTime - visitor.visits[0] > 1000 * VISITOR_EXPIRY_SECONDS) map.delete(id); + else { + visitor.visits = visitor.visits.filter((since) => { + return currentTime - since < 1000 * VISITOR_EXPIRY_SECONDS; + }); + map.set(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) + if (isBot(request)) { + return visitors; + } + const scopedCookies = scopeCookies(cookies, '/'); + // parse the last visit timestamp from cookies if it exists + let visitorId = scopedCookies.get('visitorId') || ''; + // if no such id exists, create one and assign it to the client + if (!visitors.has(visitorId)) { + visitorId = nanoid(); + scopedCookies.set('visitorId', visitorId); + console.log(`new client visitor id ${visitorId}`); + } + // update the entry + const visitorEntry = visitors.get(visitorId) || { visits: [] }; + // put new visit in the front + visitorEntry.visits = [currentTime].concat(visitorEntry.visits); + visitors.set(visitorId, visitorEntry); + return visitors; +}; const isBot = (request: Request) => { - const ua = request.headers.get('user-agent') - return ua ? ua.toLowerCase().match(/(bot|crawl|spider|walk|fetch|scrap|proxy|image)/) !== null : true -} + const ua = request.headers.get('user-agent'); + return ua + ? ua.toLowerCase().match(/(bot|crawl|spider|walk|fetch|scrap|proxy|image)/) !== null + : true; +}; export const notifyDarkVisitors = (url: URL, request: Request) => { - fetch('https://api.darkvisitors.com/visits', { - method: 'POST', - headers: { - authorization: `Bearer ${env.DARK_VISITORS_TOKEN}`, - 'content-type': 'application/json', - }, - body: JSON.stringify({ - request_path: url.pathname, - request_method: request.method, - request_headers: request.headers, - }) - }).catch((why) => { - console.log("failed sending dark visitors analytics:", why) - return null - }).then(async (resp) => { - if (resp !== null) { - const msg = await resp.json() - const host = `(${request.headers.get('host')} ${request.headers.get('x-real-ip')})` - console.log(`sent visitor analytic to dark visitors: ${resp.statusText}; ${msg.message ?? ''}${host}`) - } - }) -} \ No newline at end of file + fetch('https://api.darkvisitors.com/visits', { + method: 'POST', + headers: { + authorization: `Bearer ${env.DARK_VISITORS_TOKEN}`, + 'content-type': 'application/json' + }, + body: JSON.stringify({ + request_path: url.pathname, + request_method: request.method, + request_headers: request.headers + }) + }) + .catch((why) => { + console.log('failed sending dark visitors analytics:', why); + return null; + }) + .then(async (resp) => { + if (resp !== null) { + const msg = await resp.json(); + const host = `(${request.headers.get('host')} ${request.headers.get('x-real-ip')})`; + console.log( + `sent visitor analytic to dark visitors: ${resp.statusText}; ${msg.message ?? ''}${host}` + ); + } + }); +}; diff --git a/src/lib/window.ts b/src/lib/window.ts index 2783b14..a8700ff 100644 --- a/src/lib/window.ts +++ b/src/lib/window.ts @@ -1,9 +1,20 @@ -import { writable } from "svelte/store"; +/* eslint-disable no-useless-escape */ +import { writable } from 'svelte/store'; -export const highestZIndex = writable(0) +export const highestZIndex = writable(0); export const isMobile = () => { - let check = false; - (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor); - return check; -} \ No newline at end of file + let check = false; + (function (a) { + if ( + /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( + a + ) || + /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( + a.slice(0, 4) + ) + ) + check = true; + })(navigator.userAgent || navigator.vendor); + return check; +}; diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index b6e943c..2c77762 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -5,26 +5,26 @@ import { error } from '@sveltejs/kit'; export const csr = true; export const ssr = true; export const prerender = false; -export const trailingSlash = 'always'; +export const trailingSlash = 'always'; export async function load({ request, cookies, url }) { - notifyDarkVisitors(url, request) // no await so it doesnt block load + notifyDarkVisitors(url, request); // no await so it doesnt block load - // block any requests if the user agent is disallowed by our robots txt - if (await testUa(url.toString(), request.headers.get('user-agent') ?? "") === false) { - throw error(403, "get a better user agent silly") - } + // block any requests if the user agent is disallowed by our robots txt + if ((await testUa(url.toString(), request.headers.get('user-agent') ?? '')) === false) { + throw error(403, 'get a better user agent silly'); + } - const lastVisitors = addLastVisitor(request, cookies) - let recentVisitCount = 0 - for (const [_, visitor] of lastVisitors) { - recentVisitCount += visitor.visits.length - } + const lastVisitors = addLastVisitor(request, cookies); + let recentVisitCount = 0; + for (const [, visitor] of lastVisitors) { + recentVisitCount += visitor.visits.length; + } - return { - route: url.pathname, - visitCount: incrementVisitCount(request, cookies), - lastVisitors, - recentVisitCount, - } + return { + route: url.pathname, + visitCount: incrementVisitCount(request, cookies), + lastVisitors, + recentVisitCount + }; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f9ef313..acc193c 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,6 +5,7 @@ import '../styles/app.css'; interface Props { + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any; children?: import('svelte').Snippet; } @@ -73,7 +74,7 @@ <filter id="squiggly-{index}"> <feTurbulence id="turbulence" - baseFrequency=0.03 + baseFrequency="0.03" numOctaves="3" result="noise" seed={index} @@ -134,7 +135,9 @@ </defs> </svg> -<div class="md:h-[96vh] pb-[8vh] lg:px-[1vw] 2xl:px-[2vw] lg:pb-[3vh] lg:pt-[1vh] overflow-x-hidden [scrollbar-gutter:stable]"> +<div + class="md:h-[96vh] pb-[8vh] lg:px-[1vw] 2xl:px-[2vw] lg:pb-[3vh] lg:pt-[1vh] overflow-x-hidden [scrollbar-gutter:stable]" +> {@render children?.()} </div> @@ -152,7 +155,12 @@ {@const highlight = isRoute(item.href)} <NavButton {highlight} {...item} /> {#if doAddPostItem && menuIdx == 1} - <NavButton highlight name={routeComponents[2]} href={data.route.slice(1)} iconUri='/icons/entry.webp'/> + <NavButton + highlight + name={routeComponents[2]} + href={data.route.slice(1)} + iconUri="/icons/entry.webp" + /> {/if} {/each} <div class="hidden md:block grow"></div> @@ -163,22 +171,33 @@ </div> <Tooltip> {#snippet tooltipContent()} - - <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> - - {/snippet} - <div class="navbox"><p><span class="text-ralsei-green-light text-shadow-green">{data.recentVisitCount}</span> recent clicks</p></div> + <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> + {/snippet} + <div class="navbox"> + <p> + <span class="text-ralsei-green-light text-shadow-green">{data.recentVisitCount}</span> recent + clicks + </p> + </div> </Tooltip> - {#if isRoute("entries") || isRoute("log")} - <div class="navbox !gap-1"> - rss: - <a class="align-middle hover:underline" href="/entries/_rss">posts</a> - / - <a class="align-middle hover:underline" href="/log/_rss">log</a> - </div> + {#if isRoute('entries') || isRoute('log')} + <div class="navbox !gap-1"> + rss: + <a class="align-middle hover:underline" href="/entries/_rss">posts</a> + / + <a class="align-middle hover:underline" href="/log/_rss">log</a> + </div> {/if} </div> </div> @@ -189,4 +208,4 @@ @apply flex gap-3 px-1.5 text-nowrap align-middle items-center text-center place-content-center border-ralsei-white border-4; border-style: groove; } -</style> \ No newline at end of file +</style> diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 67a4a51..e9c06d7 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -3,9 +3,10 @@ import Note from '../components/note.svelte'; import Window from '../components/window.svelte'; import LatestStuff from './lateststuff.md'; - import {renderDate, renderRelativeDate} from '$lib/dateFmt'; + import { renderDate, renderRelativeDate } from '$lib/dateFmt'; interface Props { + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any; } @@ -16,9 +17,7 @@ <div class="flex flex-col gap-y-2 lg:gap-y-0 mx-auto"> <Window title="readme?" iconUri="/icons/question.webp" removePadding> <div class="flex flex-col p-1.5 gap-1.5 prose prose-ralsei prose-img:m-0 leading-none"> - <div - class="flex flex-row gap-3 mx-auto bg-ralsei-black/20 overflow-hidden" - > + <div class="flex flex-row gap-3 mx-auto bg-ralsei-black/20 overflow-hidden"> {#each data.banners as bannerNo, index} {@const hideIfMobile = index === data.banners.length - 1} <img @@ -35,10 +34,13 @@ {/each} </div> <div class="flex flex-grow"> - <div - class="w-36 [padding:8px] place-content-center place-self-center bg-ralsei-black/20" - > - <img class="w-36 u-photo hover:invert transition-all [transition-duration:300ms]" src="/pfp-iojkqpwerojnasduijf.webp" alt="my character" title="hi ;)"/> + <div class="w-36 [padding:8px] place-content-center place-self-center bg-ralsei-black/20"> + <img + class="w-36 u-photo hover:invert transition-all [transition-duration:300ms]" + src="/pfp-iojkqpwerojnasduijf.webp" + alt="my angelsona" + title="that's me! my angelsona :3c" + /> </div> <div class="flex flex-row flex-grow place-content-center ml-1.5 [padding:8px] bg-ralsei-black/20" @@ -47,7 +49,9 @@ class="place-self-center m-0 mr-4 [padding-left:1em] sm:[padding-left:0.5em] leading-none marker:[content:'->'] [list-style-type:'->']" > <li class="[list-style-type:'->'] p-note">trying to do stuff</li> - <li class="[list-style-type:'->'] p-note">is a thing that exists</li> + <li class="[list-style-type:'->'] p-note" title="angelrobotdollpuppything"> + is a thing (it/they) + </li> <li class="[list-style-type:'->']"> <span class="p-category">software engineer</span>, <span class="p-category">indie game dev</span> @@ -95,7 +99,9 @@ </div> <div class="flex flex-col gap-y-2 lg:gap-y-0 mx-auto w-full md:w-fit place-items-end"> <Window title="links!" iconUri="/icons/contact.webp"> - <div class="prose prose-ralsei prose-ul:px-[0.9rem] prose-ul:leading-[1.1rem] prose-headings:leading-none"> + <div + class="prose prose-ralsei prose-ul:px-[0.9rem] prose-ul:leading-[1.1rem] prose-headings:leading-none" + > <ul> <li>discord: yusdacra</li> <li> @@ -135,102 +141,110 @@ </ul> <h4>88x31</h4> <div class="flex flex-row flex-wrap gap-1 prose-img:m-0"> - <img src="/88x31.gif" alt="88x31 banner" title="midnight AND sunrise! woaw"/> - <img src="/88x31_midnight.gif" alt="88x31 banner (midnight only)" title="it's midnight!"/> - <img src="/88x31_sunrise.gif" alt="88x31 banner (sunrise only)" title="it's sunrise!"/> + <img src="/88x31.gif" alt="88x31 banner" title="midnight AND sunrise! woaw" /> + <img + src="/88x31_midnight.gif" + alt="88x31 banner (midnight only)" + title="it's midnight!" + /> + <img src="/88x31_sunrise.gif" alt="88x31 banner (sunrise only)" title="it's sunrise!" /> </div> </div> </Window> <Window title="status" style="mt-auto" removePadding> {#if data.lastNote} - <div class="m-1.5 flex flex-col font-monospace text-sm"> - <p - class="prose prose-ralsei p-1 border-4 text-sm bg-ralsei-black" - style="border-style: double double none double;" - title={renderDate(data.lastNote.published)} - > - <a href="/entries">last log was…</a> - published {renderRelativeDate(data.lastNote.published)}! - </p> - <div class="mt-0 p-1.5 border-4 border-double bg-ralsei-black min-w-full max-w-[40ch]"> - <Note note={data.lastNote} onlyContent/> + <div class="m-1.5 flex flex-col font-monospace text-sm"> + <p + class="prose prose-ralsei p-1 border-4 text-sm bg-ralsei-black" + style="border-style: double double none double;" + title={renderDate(data.lastNote.published)} + > + <a href="/entries">last log was…</a> + published {renderRelativeDate(data.lastNote.published)}! + </p> + <div class="mt-0 p-1.5 border-4 border-double bg-ralsei-black min-w-full max-w-[40ch]"> + <Note note={data.lastNote} onlyContent /> + </div> </div> - </div> {/if} {#if data.lastTrack} - <div class="flex flex-row gap-0.5 m-1.5 border-4 border-double bg-ralsei-black"> - <!-- svelte-ignore a11y_missing_attribute --> - {#if data.lastTrack.image} + <div class="flex flex-row gap-0.5 m-1.5 border-4 border-double bg-ralsei-black"> + <!-- svelte-ignore a11y_missing_attribute --> + {#if data.lastTrack.image} + <img + class="border-4 w-[4.5rem] h-[4.5rem]" + style="border-style: none double none none;" + src={data.lastTrack.image} + /> + {:else} + <img + class="border-4 w-[4.5rem] h-[4.5rem] p-2" + style="border-style: none double none none; image-rendering: pixelated;" + src="/icons/cd_audio.webp" + /> + {/if} + <div class="flex flex-col max-w-[40ch] p-2"> + <p + class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" + > + <span class="text-sm text-shadow-white text-ralsei-white" + >{data.lastTrack.playing ? 'listening to' : 'listened to'}</span + > + <a + title={data.lastTrack.name} + href="https://www.last.fm/user/yusdacra" + class="hover:underline motion-safe:hover:animate-squiggle">{data.lastTrack.name}</a + > + </p> + <p + class="text-shadow-pink text-ralsei-pink-regular text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" + > + <span class="text-shadow-white text-ralsei-white">by</span> + <span title={data.lastTrack.artist}>{data.lastTrack.artist}</span> + </p> + <p + class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" + > + …{renderRelativeDate(data.lastTrack.when)} + </p> + </div> + </div> + {/if} + {#if data.lastGame} + <div class="flex flex-row m-1.5 border-4 border-double bg-ralsei-black"> + <!-- svelte-ignore a11y_missing_attribute --> <img class="border-4 w-[4.5rem] h-[4.5rem]" style="border-style: none double none none;" - src={data.lastTrack.image} + width="64" + height="64" + src={data.lastGame.icon} /> - {:else} - <img - class="border-4 w-[4.5rem] h-[4.5rem] p-2" - style="border-style: none double none none; image-rendering: pixelated;" - src="/icons/cd_audio.webp" - /> - {/if} - <div class="flex flex-col max-w-[40ch] p-2"> - <p - class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" - > - <span class="text-sm text-shadow-white text-ralsei-white">{data.lastTrack.playing ? "listening to" : "listened to"}</span> + <div class="flex flex-col max-w-[40ch] p-2 gap-0.5 overflow-hidden"> + <p + class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" + > + <span class="text-sm text-shadow-white text-ralsei-white" + >{data.lastGame.playing ? 'playing' : 'played'}</span + > + <a title={data.lastGame.name} class="hover:underline" href={data.lastGame.link} + >{data.lastGame.name}</a + > + </p> + <p + class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" + > + …{renderRelativeDate(data.lastGame.when)} + </p> + <!-- svelte-ignore a11y_missing_attribute --> <a - title={data.lastTrack.name} - href="https://www.last.fm/user/yusdacra" - class="hover:underline motion-safe:hover:animate-squiggle">{data.lastTrack.name}</a + href="https://steamcommunity.com/id/yusdacra" + class="text-xs hover:underline text-shadow-green text-ralsei-green-light" + ><img class="inline w-4" src={data.lastGame.pfp} /> + <span class="align-middle">steam profile</span></a > - </p> - <p - class="text-shadow-pink text-ralsei-pink-regular text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" - > - <span class="text-shadow-white text-ralsei-white">by</span> - <span title={data.lastTrack.artist}>{data.lastTrack.artist}</span> - </p> - <p - class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" - > - …{renderRelativeDate(data.lastTrack.when)} - </p> + </div> </div> - </div> - {/if} - {#if data.lastGame} - <div class="flex flex-row m-1.5 border-4 border-double bg-ralsei-black"> - <!-- svelte-ignore a11y_missing_attribute --> - <img - class="border-4 w-[4.5rem] h-[4.5rem]" - style="border-style: none double none none;" - width="64" - height="64" - src={data.lastGame.icon} - /> - <div class="flex flex-col max-w-[40ch] p-2 gap-0.5 overflow-hidden"> - <p - class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" - > - <span class="text-sm text-shadow-white text-ralsei-white">{data.lastGame.playing ? "playing" : "played"}</span> - <a title={data.lastGame.name} class="hover:underline" href={data.lastGame.link} - >{data.lastGame.name}</a - > - </p> - <p - class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" - > - …{renderRelativeDate(data.lastGame.when)} - </p> - <!-- svelte-ignore a11y_missing_attribute --> - <a - href="https://steamcommunity.com/id/yusdacra" - class="text-xs hover:underline text-shadow-green text-ralsei-green-light" - ><img class="inline w-4" src={data.lastGame.pfp} /> - <span class="align-middle">steam profile</span></a - > - </div> - </div> {/if} </Window> </div>