data endpoint for entity 90008 (aka. a website)

feat: add a git activity section to status [skip ci]

ptr.pet 41b181ce 954f2960

verified
bun.lockb

This is a binary file and will not be displayed.

+1
package.json
···
"type": "module",
"dependencies": {
"@neodrag/svelte": "^2.3.1",
+
"@rowanmanning/feed-parser": "^2.0.2",
"@skyware/bot": "^0.3.8",
"@std/toml": "npm:@jsr/std__toml",
"@types/node-schedule": "^2.1.7",
+19 -13
src/hooks.server.ts
···
import { updateLastPosts } from '$lib/bluesky';
import { lastFmUpdateNowPlaying } from '$lib/lastfm';
-
import { steamUpdateNowPlaying } from '$lib/steam'
-
import { cancelJob, scheduleJob, scheduledJobs } from 'node-schedule'
+
import { steamUpdateNowPlaying } from '$lib/steam';
+
import { updateCommits } from '$lib/activity';
+
import { cancelJob, scheduleJob, scheduledJobs } from 'node-schedule';
-
const UPDATE_LAST_JOB_NAME = "update steam game, lastfm track, bsky posts"
+
const UPDATE_LAST_JOB_NAME = 'update steam game, lastfm track, bsky posts, git activity';
if (UPDATE_LAST_JOB_NAME in scheduledJobs) {
-
console.log(`${UPDATE_LAST_JOB_NAME} is already running, cancelling so we can start a new one`)
-
cancelJob(UPDATE_LAST_JOB_NAME)
+
console.log(`${UPDATE_LAST_JOB_NAME} is already running, cancelling so we can start a new one`);
+
cancelJob(UPDATE_LAST_JOB_NAME);
}
console.log(`starting ${UPDATE_LAST_JOB_NAME} job...`);
-
scheduleJob(UPDATE_LAST_JOB_NAME, "*/1 * * * *", async () => {
-
console.log(`running ${UPDATE_LAST_JOB_NAME} job...`)
-
try {
-
await Promise.all([steamUpdateNowPlaying(), lastFmUpdateNowPlaying(), updateLastPosts()])
-
} catch (err) {
-
console.log(`error while running ${UPDATE_LAST_JOB_NAME} job: ${err}`)
-
}
-
}).invoke() // invoke once immediately
+
scheduleJob(UPDATE_LAST_JOB_NAME, '*/1 * * * *', async () => {
+
console.log(`running ${UPDATE_LAST_JOB_NAME} job...`);
+
try {
+
await Promise.all([
+
steamUpdateNowPlaying(),
+
lastFmUpdateNowPlaying(),
+
updateLastPosts(),
+
updateCommits()
+
]);
+
} catch (err) {
+
console.log(`error while running ${UPDATE_LAST_JOB_NAME} job: ${err}`);
+
}
+
}).invoke(); // invoke once immediately
+55
src/lib/activity.ts
···
+
import { get, writable } from 'svelte/store';
+
import { parseFeed } from '@rowanmanning/feed-parser';
+
+
const lastCommits = writable<Activity[]>([]);
+
+
export const updateCommits = async () => {
+
try {
+
const forgejoFeed = await parseFeedToActivity('https://git.gaze.systems/90008.rss');
+
const githubFeed = await parseFeedToActivity('https://github.com/yusdacra.atom');
+
const mergedFeed = sortActivities(forgejoFeed.concat(githubFeed)).slice(0, 7);
+
lastCommits.set(mergedFeed);
+
} catch (why) {
+
console.log('could not fetch git activity: ', why);
+
}
+
};
+
+
export const getLastActivity = () => {
+
return get(lastCommits);
+
};
+
+
type Activity = {
+
source: string;
+
description: string;
+
link: string | null;
+
date: Date | null;
+
};
+
+
const parseFeedToActivity = async (url: string) => {
+
const response = await fetch(url);
+
const feed = parseFeed(await response.text());
+
+
const source = new URL(url).host;
+
const results: Activity[] = [];
+
for (const item of feed.items.slice(0, 10)) {
+
const description: string | null = item.description || item.title;
+
if (description === null) continue;
+
results.push({
+
source,
+
description: description.split('</a>').pop(),
+
link: item.url,
+
date: item.published
+
});
+
}
+
+
return results;
+
};
+
+
const sortActivities = (activities: Array<Activity>) => {
+
return activities.sort((a, b) => {
+
if (a.date === null && b.date === null) return 0;
+
if (a.date === null) return 1;
+
if (b.date === null) return -1;
+
return b.date.getTime() - a.date.getTime();
+
});
+
};
+23 -23
src/lib/dateFmt.ts
···
export const renderRelativeDate = (timestamp: number) => {
-
const elapsed = timestamp - (new Date()).getTime()
-
const units: Record<string, number> = {
-
year : 24 * 60 * 60 * 1000 * 365,
-
month : 24 * 60 * 60 * 1000 * 365/12,
-
day : 24 * 60 * 60 * 1000,
-
hour : 60 * 60 * 1000,
-
minute: 60 * 1000,
-
second: 1000
-
}
-
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
-
for (var unit in units)
-
if (Math.abs(elapsed) > units[unit] || unit == 'second')
-
return rtf.format(Math.round(elapsed / units[unit]), unit as Intl.RelativeTimeFormatUnit)
-
return ""
-
}
+
const elapsed = timestamp - new Date().getTime();
+
const units: Record<string, number> = {
+
year: 24 * 60 * 60 * 1000 * 365,
+
month: (24 * 60 * 60 * 1000 * 365) / 12,
+
day: 24 * 60 * 60 * 1000,
+
hour: 60 * 60 * 1000,
+
minute: 60 * 1000,
+
second: 1000
+
};
+
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
+
for (const unit in units)
+
if (Math.abs(elapsed) > units[unit] || unit == 'second')
+
return rtf.format(Math.round(elapsed / units[unit]), unit as Intl.RelativeTimeFormatUnit);
+
return '';
+
};
export const renderDate = (timestamp: number) => {
-
return (new Date(timestamp)).toLocaleString("en-GB", {
-
year: "2-digit",
-
month: "2-digit",
-
day: "2-digit",
-
hour: "2-digit",
-
minute: "2-digit",
-
})
-
}
+
return new Date(timestamp).toLocaleString('en-GB', {
+
year: '2-digit',
+
month: '2-digit',
+
day: '2-digit',
+
hour: '2-digit',
+
minute: '2-digit'
+
});
+
};
+4 -2
src/routes/+page.server.ts
···
import { getLastGame } from '$lib/steam';
import { noteFromBskyPost } from '../components/note.svelte';
import { pushNotification } from '$lib/pushnotif';
+
import { getLastActivity } from '$lib/activity.js';
export const load = async () => {
const lastTrack = getNowPlaying();
const lastGame = getLastGame();
const lastPosts = getLastPosts();
const lastNote = lastPosts.length > 0 ? noteFromBskyPost(lastPosts[0]) : null;
-
let banners: number[] = [];
+
const lastActivity = getLastActivity();
+
const banners: number[] = [];
while (banners.length < 3) {
const no = getBannerNo(banners);
banners.push(no);
}
-
return { banners, lastTrack, lastGame, lastNote };
+
return { banners, lastTrack, lastGame, lastNote, lastActivity };
};
export const actions = {
+27 -6
src/routes/+page.svelte
···
import { PUBLIC_BASE_URL } from '$env/static/public';
import Note from '../components/note.svelte';
import Window from '../components/window.svelte';
-
import LatestStuff from './lateststuff.md';
import { renderDate, renderRelativeDate } from '$lib/dateFmt';
import Tooltip from '../components/tooltip.svelte';
···
<div class="flex flex-col md:flex-row gap-2 md:gap-4 md:h-full h-card">
<div class="flex flex-col gap-2 md:gap-6 ml-auto place-items-end">
-
<Window title="stuff it's doing.." iconUri="/icons/msg_information.webp">
-
<div class="prose prose-ralsei prose-img:m-0 leading-6">
-
<LatestStuff />
-
</div>
-
</Window>
<Window style="md:mr-8" title="status" iconUri="/icons/msn.webp" removePadding>
{#if data.lastNote}
<div class="m-1.5 flex flex-col font-monospace text-sm">
···
</p>
<div class="mt-0 p-1.5 border-4 border-double bg-ralsei-black min-w-full max-w-[60ch]">
<Note note={data.lastNote} onlyContent />
+
</div>
+
</div>
+
{/if}
+
{#if data.lastActivity.length > 0}
+
<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.lastActivity[0].date)}
+
>
+
<a href="/">last git activity…</a>
+
was {renderRelativeDate(data.lastActivity[0].date)}..
+
</p>
+
<div
+
class="prose prose-ralsei mt-0 p-1.5 border-4 border-double bg-ralsei-black min-w-full max-w-[60ch]"
+
>
+
{#each data.lastActivity as activity, index}
+
<div
+
class="text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[60ch]"
+
style="opacity: {1.0 - (index * 1.0) / data.lastActivity.length + index * 0.05};"
+
>
+
<span title={renderDate(activity.date)} class="text-[#f87c32]"
+
>[{activity.source}]</span
+
>
+
<a href={activity.link} title={activity.description}>{activity.description}</a>
+
</div>
+
{/each}
</div>
</div>
{/if}
-7
src/routes/lateststuff.md
···
-
+++
-
layout = false
-
+++
-
-
currently working on a game under the name `packet.runner`, read some very WIP stuff about it [here](https://doc.gaze.systems/LsE08EU7QOSKm7xps_treA).
-
-
<span class="text-xs italic">last updated on: 19-02-2025</span>