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

Configure Feed

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

refactor: don't fetch steam / lastfm / bsky posts on user request, instead peridocially do it in the background every minute

dusk a90bf505 4b81de80

+57 -38
bun.lockb

This is a binary file and will not be displayed.

+2
package.json
··· 43 43 "@neodrag/svelte": "^2.3.0", 44 44 "@skyware/bot": "^0.3.8", 45 45 "@std/toml": "npm:@jsr/std__toml", 46 + "@types/node-schedule": "^2.1.7", 46 47 "base64url": "^3.0.1", 47 48 "nanoid": "^5.0.9", 49 + "node-schedule": "^2.1.1", 48 50 "rehype-autolink-headings": "^7.1.0", 49 51 "rehype-slug": "^6.0.0", 50 52 "robots-parser": "^3.0.1",
+17
src/hooks.server.ts
··· 1 + import { updateLastPosts } from '$lib/bluesky'; 2 + import { lastFmUpdateNowPlaying } from '$lib/lastfm'; 3 + import { steamUpdateNowPlaying } from '$lib/steam' 4 + import { cancelJob, scheduleJob, scheduledJobs } from 'node-schedule' 5 + 6 + const UPDATE_LAST_JOB_NAME = "update steam game, lastfm track, bsky posts" 7 + 8 + if (UPDATE_LAST_JOB_NAME in scheduledJobs) { 9 + console.log(`${UPDATE_LAST_JOB_NAME} is already running, cancelling so we can start a new one`) 10 + cancelJob(UPDATE_LAST_JOB_NAME) 11 + } 12 + 13 + console.log(`starting ${UPDATE_LAST_JOB_NAME} job...`); 14 + scheduleJob(UPDATE_LAST_JOB_NAME, "*/1 * * * *", async () => { 15 + console.log(`running ${UPDATE_LAST_JOB_NAME} job...`) 16 + await Promise.all([steamUpdateNowPlaying(), lastFmUpdateNowPlaying(), updateLastPosts()]) 17 + }).invoke() // invoke once immediately
+10 -1
src/lib/bluesky.ts
··· 32 32 feedCursor = feedData.cursor 33 33 } 34 34 return posts 35 - } 35 + } 36 + 37 + const lastPosts = writable<Post[]>([]) 38 + 39 + export const updateLastPosts = async () => { 40 + const posts = await getUserPosts("did:plc:dfl62fgb7wtjj3fcbb72naae", false, 13) 41 + lastPosts.set(posts) 42 + } 43 + 44 + export const getLastPosts = () => { return get(lastPosts) }
+7 -13
src/lib/lastfm.ts
··· 1 1 import { get, writable } from "svelte/store" 2 2 3 3 const GET_RECENT_TRACKS_ENDPOINT = "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=yusdacra&api_key=da1911d405b5b37383e200b8f36ee9ec&format=json&limit=1" 4 - const CACHE_EXPIRY_SECONDS = 10 5 4 6 5 type LastTrack = {name: string, artist: string, image: string | null, link: string} 7 - type CachedLastTrack = {track: LastTrack | null, since: number} 8 - const cachedLastTrack = writable<CachedLastTrack>({track: null, since: 0}) 6 + const lastTrack = writable<LastTrack | null>(null) 9 7 10 - export const lastFmGetNowPlaying: () => Promise<LastTrack | null> = async () => { 11 - var cached = get(cachedLastTrack) 12 - if (Date.now() - cached.since < CACHE_EXPIRY_SECONDS * 1000) { 13 - return cached.track 14 - } 8 + export const lastFmUpdateNowPlaying = async () => { 15 9 try { 16 10 var resp = await (await fetch(GET_RECENT_TRACKS_ENDPOINT)).json() 17 11 var track = resp.recenttracks.track[0] ?? null ··· 24 18 image: track.image[2]['#text'] ?? null, 25 19 link: track.url, 26 20 } 27 - cachedLastTrack.set({track: data, since: Date.now()}) 28 - return data 21 + lastTrack.set(data) 29 22 } catch(why) { 30 23 console.log("could not fetch last fm: ", why) 31 - cachedLastTrack.set({track: null, since: Date.now()}) 32 - return null 24 + lastTrack.set(null) 33 25 } 34 - } 26 + } 27 + 28 + export const getNowPlaying = () => { return get(lastTrack) }
+10 -16
src/lib/steam.ts
··· 4 4 5 5 const STEAM_ID = "76561198106829949" 6 6 const GET_PLAYER_SUMMARY_ENDPOINT = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${env.STEAM_API_KEY}&steamids=${STEAM_ID}&format=json` 7 - const CACHE_EXPIRY_SECONDS = 10 8 7 9 8 type LastGame = {name: string, link: string, icon: string, pfp: string} 10 - type CachedLastGame = {game: LastGame | null, since: number} 11 9 12 - const steamgriddbClient = writable<SGDB | null>(null); 13 - const cachedLastGame = writable<CachedLastGame>({game: null, since: 0}) 10 + const steamgriddbClient = writable<SGDB | null>(null) 11 + const lastGame = writable<LastGame | null>(null) 14 12 15 - export const steamGetNowPlaying: () => Promise<LastGame | null> = async () => { 13 + export const steamUpdateNowPlaying = async () => { 16 14 var griddbClient = get(steamgriddbClient) 17 15 if (griddbClient === null) { 18 16 griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY) 19 17 steamgriddbClient.set(griddbClient) 20 18 } 21 - var cached = get(cachedLastGame) 22 - if (Date.now() - cached.since < CACHE_EXPIRY_SECONDS * 1000) { 23 - return cached.game 24 - } 25 19 try { 26 20 var profile = (await (await fetch(GET_PLAYER_SUMMARY_ENDPOINT)).json()).response.players[0] 27 21 if (!profile.gameid) { 28 22 throw "no game is being played" 29 23 } 30 24 var icons = await griddbClient.getIconsBySteamAppId(profile.gameid, ['official']) 31 - console.log(icons) 32 - var game = { 25 + //console.log(icons) 26 + var game: LastGame = { 33 27 name: profile.gameextrainfo, 34 28 link: `https://store.steampowered.com/app/${profile.gameid}`, 35 29 icon: icons[0].thumb.toString(), 36 30 pfp: profile.avatarmedium, 37 31 } 38 - cachedLastGame.set({game, since: Date.now()}) 39 - return game 32 + lastGame.set(game) 40 33 } catch(why) { 41 34 console.log("could not fetch steam: ", why) 42 - cachedLastGame.set({game: null, since: Date.now()}) 43 - return null 35 + lastGame.set(null) 44 36 } 45 - } 37 + } 38 + 39 + export const getLastGame = () => { return get(lastGame) }
+7 -6
src/routes/+page.server.ts
··· 1 - import { getUserPosts } from "$lib/bluesky.js" 2 - import { lastFmGetNowPlaying } from "$lib/lastfm" 3 - import { steamGetNowPlaying } from "$lib/steam" 1 + import { getLastPosts } from "$lib/bluesky.js" 2 + import { getNowPlaying } from "$lib/lastfm" 3 + import { getLastGame } from "$lib/steam" 4 4 import { noteFromBskyPost } from "../components/note.svelte" 5 5 6 6 export const load = async ({}) => { 7 - const lastTrack = await lastFmGetNowPlaying() 8 - const lastGame = await steamGetNowPlaying() 9 - const lastNote = noteFromBskyPost((await getUserPosts("did:plc:dfl62fgb7wtjj3fcbb72naae", false, 1))[0]) 7 + const lastTrack = getNowPlaying() 8 + const lastGame = getLastGame() 9 + const lastPosts = getLastPosts() 10 + const lastNote = lastPosts.length > 0 ? noteFromBskyPost(lastPosts[0]) : null 10 11 let banners: number[] = [] 11 12 while (banners.length < 3) { 12 13 const no = getBannerNo(banners)
+2
src/routes/+page.svelte
··· 146 146 </div> 147 147 </Window> 148 148 <Window title="status" style="mt-auto" removePadding> 149 + {#if data.lastNote} 149 150 <div class="m-1.5 flex flex-col font-monospace"> 150 151 <div 151 152 class="prose prose-ralsei items-center p-1 border-4 text-sm font-bold bg-ralsei-black" ··· 158 159 <Note note={data.lastNote} onlyContent/> 159 160 </div> 160 161 </div> 162 + {/if} 161 163 {#if data.lastTrack} 162 164 <div class="flex flex-row m-1.5 border-4 border-double bg-ralsei-black"> 163 165 <!-- svelte-ignore a11y-missing-attribute -->
+2 -2
src/routes/log/+page.server.ts
··· 1 - import { getUserPosts } from '$lib/bluesky.js'; 1 + import { getLastPosts, getUserPosts } from '$lib/bluesky.js'; 2 2 import { noteFromBskyPost } from '../../components/note.svelte'; 3 3 4 4 export const load = async ({ }) => { ··· 7 7 8 8 export const _load = async () => { 9 9 return { 10 - feedPosts: (await getUserPosts("did:plc:dfl62fgb7wtjj3fcbb72naae", false, 13)).map(noteFromBskyPost), 10 + feedPosts: getLastPosts().map(noteFromBskyPost), 11 11 } 12 12 }