initial commit
This commit is contained in:
commit
e2466b202a
50 changed files with 4356 additions and 0 deletions
88
utils/atproto.ts
Normal file
88
utils/atproto.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Copyright (c) 2025 favewa
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { withInterval } from "./temp.ts";
|
||||
|
||||
export interface Report {
|
||||
rkey: string;
|
||||
cid: string;
|
||||
title: string;
|
||||
excerpt: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
tags?: string[];
|
||||
visibility: string;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
async function xrpc(
|
||||
method: string,
|
||||
params: Record<string, string>,
|
||||
): Promise<any> {
|
||||
const url = new URL(`https://blacksky.app/xrpc/${method}`);
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
url.searchParams.set(key, value);
|
||||
});
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`XRPC call failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
const rkey = (input: string) => input.slice(input.lastIndexOf("/") + 1 || 0);
|
||||
|
||||
export const getCachedReports: () => Report[] = await withInterval(
|
||||
getPosts,
|
||||
300,
|
||||
);
|
||||
|
||||
export async function getPosts(cursor?: string): Promise<Report[]> {
|
||||
const params: Record<string, string> = {
|
||||
collection: "com.whtwnd.blog.entry",
|
||||
repo: "acpi.at",
|
||||
};
|
||||
if (cursor) params.cursor = cursor;
|
||||
|
||||
const response = await xrpc("com.atproto.repo.listRecords", params);
|
||||
if (!response) return [];
|
||||
|
||||
const reports = (response.records as Record<string, any>[]).map((
|
||||
{ cid, uri, value },
|
||||
) => ({
|
||||
...value,
|
||||
rkey: rkey(uri),
|
||||
date: new Date(value.createdAt),
|
||||
createdAt: new Date(value.createdAt).toISOString().slice(0, 10).replace(
|
||||
/-/g,
|
||||
".",
|
||||
),
|
||||
cid,
|
||||
} as Report)).filter((r) => r.visibility === "author").sort((a, b) =>
|
||||
b.date as any - (a.date as any)
|
||||
);
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
export async function retrieveReport(rkey: string): Promise<Report | null> {
|
||||
const response = await xrpc("com.atproto.repo.getRecord", {
|
||||
collection: "com.whtwnd.blog.entry",
|
||||
repo: "acpi.at",
|
||||
rkey,
|
||||
});
|
||||
|
||||
if (!response) return null;
|
||||
|
||||
return {
|
||||
...response.value,
|
||||
cid: response.cid,
|
||||
rkey,
|
||||
} as Report;
|
||||
}
|
||||
35
utils/fm.ts
Normal file
35
utils/fm.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (c) 2025 favewa
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { LastClient } from "@musicorum/lastfm";
|
||||
import { withInterval } from "./temp.ts";
|
||||
|
||||
interface Track {
|
||||
name: string;
|
||||
artist: string;
|
||||
loved: boolean;
|
||||
cover?: string;
|
||||
}
|
||||
|
||||
const secret = Deno.env.get("FM_SECRET");
|
||||
export let tracks: (() => Track[]) | undefined;
|
||||
|
||||
if (secret) {
|
||||
const client = new LastClient(secret);
|
||||
|
||||
tracks = await withInterval(async () => {
|
||||
const { tracks: raw } = await client.user.getRecentTracks("favewa", {
|
||||
limit: 5,
|
||||
extended: true,
|
||||
});
|
||||
|
||||
return raw?.filter((x) => !x.nowPlaying).map((track) => ({
|
||||
artist: track.artist.name,
|
||||
name: track.name,
|
||||
loved: "loved" in track ? track.loved as boolean : false,
|
||||
cover: track.images.pop()?.url,
|
||||
}));
|
||||
}, 60);
|
||||
}
|
||||
25
utils/temp.ts
Normal file
25
utils/temp.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) 2025 favewa
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export async function withInterval<T>(
|
||||
callback: () => Promise<T>,
|
||||
seconds: number,
|
||||
): Promise<() => T> {
|
||||
let value: T = await callback();
|
||||
let timer: number | undefined;
|
||||
|
||||
async function tick() {
|
||||
clearInterval(timer);
|
||||
try {
|
||||
value = await callback();
|
||||
} catch (_) {
|
||||
// no-op
|
||||
}
|
||||
timer = setInterval(tick, seconds * 1000);
|
||||
}
|
||||
timer = setInterval(tick, seconds * 1000);
|
||||
|
||||
return () => value;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue