Compare commits
5 Commits
c4c4cae944
...
1aa91543fe
Author | SHA1 | Date | |
---|---|---|---|
1aa91543fe | |||
a90bf50558 | |||
4b81de80d5 | |||
faafb4bd7c | |||
2538bb614c |
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
24
package.json
24
package.json
@ -13,38 +13,40 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/enhanced-img": "^0.3.10",
|
||||
"@sveltejs/kit": "^2.15.1",
|
||||
"@sveltejs/kit": "^2.17.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/node": "^22.10.3",
|
||||
"@types/node": "^22.13.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"globals": "^15.14.0",
|
||||
"mdsvex": "^0.12.3",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss": "^8.5.1",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-adapter-bun": "^0.5.2",
|
||||
"svelte-check": "^3.8.6",
|
||||
"sveltekit-rate-limiter": "^0.6.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.19.0",
|
||||
"vite": "^5.4.11"
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.23.0",
|
||||
"vite": "^5.4.14"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@neodrag/svelte": "^2.2.0",
|
||||
"@neodrag/svelte": "^2.3.0",
|
||||
"@skyware/bot": "^0.3.8",
|
||||
"@std/toml": "npm:@jsr/std__toml",
|
||||
"@types/node-schedule": "^2.1.7",
|
||||
"base64url": "^3.0.1",
|
||||
"nanoid": "^5.0.9",
|
||||
"node-schedule": "^2.1.1",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"robots-parser": "^3.0.1",
|
||||
|
@ -1,9 +1,32 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Post } from "@skyware/bot";
|
||||
|
||||
export interface OutgoingLink {
|
||||
name: string,
|
||||
link: string,
|
||||
}
|
||||
export interface NoteData {
|
||||
content: string,
|
||||
published: number,
|
||||
hasMedia: boolean,
|
||||
hasQuote: boolean,
|
||||
outgoingLinks?: OutgoingLink[],
|
||||
}
|
||||
|
||||
export const noteFromBskyPost = (post: Post): NoteData => {
|
||||
return {
|
||||
content: post.text,
|
||||
published: post.createdAt.getTime(),
|
||||
outgoingLinks: [{ name: "bsky", link: post.uri }],
|
||||
hasMedia: (post.embed?.isImages() || post.embed?.isVideo()) ?? false,
|
||||
hasQuote: post.embed?.isRecord() ?? false,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import type { Note } from "$lib/notes";
|
||||
import Token from "./token.svelte";
|
||||
|
||||
export let id: string;
|
||||
export let note: Note;
|
||||
export let note: NoteData;
|
||||
export let isHighlighted = false;
|
||||
export let onlyContent = false;
|
||||
|
||||
@ -19,10 +42,7 @@
|
||||
|
||||
const getOutgoingLink = (name: string, link: string) => {
|
||||
if (name === "bsky") {
|
||||
if (link.startsWith("https://bsky.gaze.systems")) {
|
||||
return link
|
||||
}
|
||||
return `https://bsky.gaze.systems/post/${link.split('/').pop()}`
|
||||
return `https://bsky.app/profile/gaze.systems/post/${link.split('/').pop()}`
|
||||
}
|
||||
return link
|
||||
}
|
||||
@ -36,9 +56,11 @@
|
||||
</script>
|
||||
|
||||
<div class="text-wrap break-words max-w-[70ch] leading-none">
|
||||
{#if !onlyContent}<Token v={renderDate(note.published)} small={!isHighlighted}/> <Token v={id} keywd small={!isHighlighted}/><Token v="#" punct/> {/if}<Token v={note.content} str/>
|
||||
{#if !onlyContent}<Token v={renderDate(note.published)} small={!isHighlighted}/> {/if}<Token v={note.content} str/>
|
||||
{#if note.hasMedia}<Token v="-contains media-" keywd small/>{/if}
|
||||
{#if note.hasQuote}<Token v="-contains quote-" keywd small/>{/if}
|
||||
{#each note.outgoingLinks ?? [] as {name, link}}
|
||||
{@const color = outgoingLinkColors[name]}
|
||||
<span class="text-sm"><Token v="(" punct/><a style="color: {color};{getTextShadowStyle(color)}" href={getOutgoingLink(name, link)}>{name}</a><Token v=")" punct/></span>
|
||||
<span class="text-sm"><Token v="(" punct/><a class="hover:motion-safe:animate-squiggle hover:underline" style="color: {color};{getTextShadowStyle(color)}" href={getOutgoingLink(name, link)}>{name}</a><Token v=")" punct/></span>
|
||||
{/each}
|
||||
</div>
|
17
src/hooks.server.ts
Normal file
17
src/hooks.server.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { updateLastPosts } from '$lib/bluesky';
|
||||
import { lastFmUpdateNowPlaying } from '$lib/lastfm';
|
||||
import { steamUpdateNowPlaying } from '$lib/steam'
|
||||
import { cancelJob, scheduleJob, scheduledJobs } from 'node-schedule'
|
||||
|
||||
const UPDATE_LAST_JOB_NAME = "update steam game, lastfm track, bsky posts"
|
||||
|
||||
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(`starting ${UPDATE_LAST_JOB_NAME} job...`);
|
||||
scheduleJob(UPDATE_LAST_JOB_NAME, "*/1 * * * *", async () => {
|
||||
console.log(`running ${UPDATE_LAST_JOB_NAME} job...`)
|
||||
await Promise.all([steamUpdateNowPlaying(), lastFmUpdateNowPlaying(), updateLastPosts()])
|
||||
}).invoke() // invoke once immediately
|
@ -1,5 +1,5 @@
|
||||
import { env } from '$env/dynamic/private'
|
||||
import { Bot } from "@skyware/bot";
|
||||
import { Bot, Post } from "@skyware/bot";
|
||||
import { get, writable } from 'svelte/store'
|
||||
|
||||
const bskyClient = writable<null | Bot>(null)
|
||||
@ -17,4 +17,28 @@ const loginToBsky = async () => {
|
||||
const bot = new Bot({ service: "https://bsky.social" })
|
||||
await bot.login({ identifier: 'gaze.systems', password: env.BSKY_PASSWORD ?? "" })
|
||||
return bot
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserPosts = async (did: string, includeReposts: boolean = false, count: number = 10) => {
|
||||
const client = await getBskyClient()
|
||||
let feedCursor = undefined;
|
||||
let posts: Post[] = []
|
||||
// fetch requested amount of posts
|
||||
while (posts.length < count || feedCursor === undefined) {
|
||||
let feedData = await client.getUserPosts(
|
||||
did, { limit: count, filter: 'posts_no_replies', cursor: feedCursor }
|
||||
)
|
||||
posts.push(...feedData.posts.filter((post) => !includeReposts && post.author.did === did))
|
||||
feedCursor = feedData.cursor
|
||||
}
|
||||
return posts
|
||||
}
|
||||
|
||||
const lastPosts = writable<Post[]>([])
|
||||
|
||||
export const updateLastPosts = async () => {
|
||||
const posts = await getUserPosts("did:plc:dfl62fgb7wtjj3fcbb72naae", false, 13)
|
||||
lastPosts.set(posts)
|
||||
}
|
||||
|
||||
export const getLastPosts = () => { return get(lastPosts) }
|
@ -1,17 +1,11 @@
|
||||
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 CACHE_EXPIRY_SECONDS = 10
|
||||
|
||||
type LastTrack = {name: string, artist: string, image: string | null, link: string}
|
||||
type CachedLastTrack = {track: LastTrack | null, since: number}
|
||||
const cachedLastTrack = writable<CachedLastTrack>({track: null, since: 0})
|
||||
const lastTrack = writable<LastTrack | null>(null)
|
||||
|
||||
export const lastFmGetNowPlaying: () => Promise<LastTrack | null> = async () => {
|
||||
var cached = get(cachedLastTrack)
|
||||
if (Date.now() - cached.since < CACHE_EXPIRY_SECONDS * 1000) {
|
||||
return cached.track
|
||||
}
|
||||
export const lastFmUpdateNowPlaying = async () => {
|
||||
try {
|
||||
var resp = await (await fetch(GET_RECENT_TRACKS_ENDPOINT)).json()
|
||||
var track = resp.recenttracks.track[0] ?? null
|
||||
@ -24,11 +18,11 @@ export const lastFmGetNowPlaying: () => Promise<LastTrack | null> = async () =>
|
||||
image: track.image[2]['#text'] ?? null,
|
||||
link: track.url,
|
||||
}
|
||||
cachedLastTrack.set({track: data, since: Date.now()})
|
||||
return data
|
||||
lastTrack.set(data)
|
||||
} catch(why) {
|
||||
console.log("could not fetch last fm: ", why)
|
||||
cachedLastTrack.set({track: null, since: Date.now()})
|
||||
return null
|
||||
lastTrack.set(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getNowPlaying = () => { return get(lastTrack) }
|
@ -1,68 +0,0 @@
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { env } from '$env/dynamic/private'
|
||||
|
||||
export interface OutgoingLinkData {
|
||||
name: string,
|
||||
link: string,
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
content: string,
|
||||
published: number,
|
||||
outgoingLinks?: OutgoingLinkData[],
|
||||
replyTo?: NoteId,
|
||||
}
|
||||
type NoteId = string
|
||||
|
||||
export const notesFolder = `${env.WEBSITE_DATA_DIR}/note`
|
||||
export const notesListFile = `${env.WEBSITE_DATA_DIR}/notes`
|
||||
export const noteIdLength = 8;
|
||||
|
||||
export const getNotePath = (id: NoteId) => { return `${notesFolder}/${id}` }
|
||||
export const genNoteId = () => {
|
||||
let id = nanoid(noteIdLength)
|
||||
while (existsSync(getNotePath(id))) {
|
||||
id = nanoid(noteIdLength)
|
||||
}
|
||||
return id
|
||||
}
|
||||
export const noteExists = (id: NoteId) => { return existsSync(getNotePath(id)) }
|
||||
export const readNote = (id: NoteId): Note => {
|
||||
return JSON.parse(readFileSync(getNotePath(id)).toString())
|
||||
}
|
||||
export const findReplyRoot = (id: NoteId): {rootNote: Note, rootNoteId: NoteId} => {
|
||||
let noteId: string | null = id
|
||||
let current: {rootNote?: Note, rootNoteId?: NoteId} = {}
|
||||
while (noteId !== null) {
|
||||
current.rootNote = readNote(noteId)
|
||||
current.rootNoteId = noteId
|
||||
noteId = current.rootNote.replyTo ?? null
|
||||
}
|
||||
if (current.rootNote === undefined || current.rootNoteId === undefined) {
|
||||
throw "no note with id found"
|
||||
}
|
||||
return {
|
||||
rootNote: current.rootNote,
|
||||
rootNoteId: current.rootNoteId,
|
||||
}
|
||||
}
|
||||
export const writeNote = (id: NoteId, note: Note) => {
|
||||
writeFileSync(getNotePath(id), JSON.stringify(note))
|
||||
// only append to note list if its not in it yet
|
||||
let noteList = readNotesList()
|
||||
if (noteList.indexOf(id) === -1) {
|
||||
writeNotesList([id].concat(noteList))
|
||||
}
|
||||
}
|
||||
export const createNote = (id: NoteId, note: Note) => {
|
||||
writeNote(id, note)
|
||||
return id
|
||||
}
|
||||
|
||||
export const readNotesList = (): NoteId[] => {
|
||||
return JSON.parse(readFileSync(notesListFile).toString())
|
||||
}
|
||||
export const writeNotesList = (note_ids: NoteId[]) => {
|
||||
writeFileSync(notesListFile, JSON.stringify(note_ids))
|
||||
}
|
@ -4,42 +4,36 @@ 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 CACHE_EXPIRY_SECONDS = 10
|
||||
|
||||
type LastGame = {name: string, link: string, icon: string, pfp: string}
|
||||
type CachedLastGame = {game: LastGame | null, since: number}
|
||||
|
||||
const steamgriddbClient = writable<SGDB | null>(null);
|
||||
const cachedLastGame = writable<CachedLastGame>({game: null, since: 0})
|
||||
const steamgriddbClient = writable<SGDB | null>(null)
|
||||
const lastGame = writable<LastGame | null>(null)
|
||||
|
||||
export const steamGetNowPlaying: () => Promise<LastGame | null> = async () => {
|
||||
export const steamUpdateNowPlaying = async () => {
|
||||
var griddbClient = get(steamgriddbClient)
|
||||
if (griddbClient === null) {
|
||||
griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY)
|
||||
steamgriddbClient.set(griddbClient)
|
||||
}
|
||||
var cached = get(cachedLastGame)
|
||||
if (Date.now() - cached.since < CACHE_EXPIRY_SECONDS * 1000) {
|
||||
return cached.game
|
||||
}
|
||||
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'])
|
||||
console.log(icons)
|
||||
var game = {
|
||||
//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,
|
||||
}
|
||||
cachedLastGame.set({game, since: Date.now()})
|
||||
return game
|
||||
lastGame.set(game)
|
||||
} catch(why) {
|
||||
console.log("could not fetch steam: ", why)
|
||||
cachedLastGame.set({game: null, since: Date.now()})
|
||||
return null
|
||||
lastGame.set(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getLastGame = () => { return get(lastGame) }
|
@ -178,7 +178,7 @@
|
||||
rss:
|
||||
<a class="align-middle hover:underline" href="/entries/_rss">posts</a>
|
||||
/
|
||||
<a class="align-middle hover:underline" href="/log/_rss">log</a>
|
||||
<a class="align-middle hover:underline" href="https://bsky.app/profile/did:plc:dfl62fgb7wtjj3fcbb72naae/rss">log</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { lastFmGetNowPlaying } from "$lib/lastfm"
|
||||
import { readNote, readNotesList } from "$lib/notes.js"
|
||||
import { steamGetNowPlaying } from "$lib/steam"
|
||||
import { getLastPosts } from "$lib/bluesky.js"
|
||||
import { getNowPlaying } from "$lib/lastfm"
|
||||
import { getLastGame } from "$lib/steam"
|
||||
import { noteFromBskyPost } from "../components/note.svelte"
|
||||
|
||||
export const load = async ({}) => {
|
||||
const lastTrack = await lastFmGetNowPlaying()
|
||||
const lastGame = await steamGetNowPlaying()
|
||||
const lastTrack = getNowPlaying()
|
||||
const lastGame = getLastGame()
|
||||
const lastPosts = getLastPosts()
|
||||
const lastNote = lastPosts.length > 0 ? noteFromBskyPost(lastPosts[0]) : null
|
||||
let banners: number[] = []
|
||||
while (banners.length < 3) {
|
||||
const no = getBannerNo(banners)
|
||||
banners.push(no)
|
||||
}
|
||||
const lastNoteId = readNotesList()[0]
|
||||
const lastNote = readNote(lastNoteId)
|
||||
return {banners, lastTrack, lastGame, lastNote, lastNoteId}
|
||||
return {banners, lastTrack, lastGame, lastNote}
|
||||
}
|
||||
|
||||
const getBannerNo = (others: number[]) => {
|
||||
|
@ -146,6 +146,7 @@
|
||||
</div>
|
||||
</Window>
|
||||
<Window title="status" style="mt-auto" removePadding>
|
||||
{#if data.lastNote}
|
||||
<div class="m-1.5 flex flex-col font-monospace">
|
||||
<div
|
||||
class="prose prose-ralsei items-center p-1 border-4 text-sm font-bold bg-ralsei-black"
|
||||
@ -155,9 +156,10 @@
|
||||
<span class="border-4 pl-[1ch]" style="border-style: none none none double;">published on {renderDate(data.lastNote.published)}</span>
|
||||
</div>
|
||||
<div class="mt-0 p-1 border-4 border-double bg-ralsei-black min-w-full max-w-[40ch]">
|
||||
<Note id={data.lastNoteId} note={data.lastNote} onlyContent/>
|
||||
<Note note={data.lastNote} onlyContent/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if data.lastTrack}
|
||||
<div class="flex flex-row m-1.5 border-4 border-double bg-ralsei-black">
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
|
@ -1,15 +1,3 @@
|
||||
import {_load as load_logs} from '../log/+page.server.ts'
|
||||
|
||||
export const load = (params) => {
|
||||
var url = params.url
|
||||
var log_id = url.searchParams.get("log_id")
|
||||
if (log_id !== null) {
|
||||
url.searchParams.append("id", log_id)
|
||||
}
|
||||
var log_page = url.searchParams.get("log_page")
|
||||
if (log_page !== null) {
|
||||
url.searchParams.append("page", log_page)
|
||||
}
|
||||
var logs_result = load_logs({url})
|
||||
return logs_result
|
||||
}
|
||||
export const load = load_logs
|
@ -1,41 +1,12 @@
|
||||
import { noteExists, readNote, readNotesList } from '$lib/notes'
|
||||
import { getLastPosts, getUserPosts } from '$lib/bluesky.js';
|
||||
import { noteFromBskyPost } from '../../components/note.svelte';
|
||||
|
||||
const notesPerPage: number = 15
|
||||
|
||||
export const load = ({ url }) => {
|
||||
return _load({ url })
|
||||
export const load = async ({ }) => {
|
||||
return _load()
|
||||
}
|
||||
|
||||
export const _load = ({ url }: { url: URL }) => {
|
||||
// get the note id to search for and display the page it is in
|
||||
const noteId = url.searchParams.get("id")
|
||||
// get the page no if one is provided, otherwise default to 1
|
||||
let page = parseInt(url.searchParams.get("page") || "1")
|
||||
if (isNaN(page)) { page = 1 }
|
||||
|
||||
// calculate page count
|
||||
const notesList = readNotesList()
|
||||
const pageCount = Math.ceil(notesList.length / notesPerPage)
|
||||
|
||||
// find what page the note id if supplied is from
|
||||
if (noteId !== null && noteExists(noteId)) {
|
||||
const noteIndex = notesList.lastIndexOf(noteId)
|
||||
if (noteIndex > -1) {
|
||||
page = Math.floor(noteIndex / notesPerPage) + 1
|
||||
}
|
||||
export const _load = async () => {
|
||||
return {
|
||||
feedPosts: getLastPosts().map(noteFromBskyPost),
|
||||
}
|
||||
|
||||
// clamp page between our min and max
|
||||
page = Math.min(page, pageCount)
|
||||
page = Math.max(page, 1)
|
||||
|
||||
// get the notes from the chosen page
|
||||
const notes = new Map(
|
||||
notesList.slice((page - 1) * notesPerPage, page * notesPerPage)
|
||||
.map(
|
||||
(id) => { return [id, readNote(id)] }
|
||||
)
|
||||
)
|
||||
|
||||
return { notes, highlightedNote: noteId, page }
|
||||
}
|
@ -4,17 +4,8 @@
|
||||
import Note from '../../components/note.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
const highlightedNote = data.notes.get(data.highlightedNote ?? '') ?? null
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if highlightedNote !== null}
|
||||
<meta property="og:description" content={highlightedNote.content} />
|
||||
<meta property="og:title" content="log #{data.highlightedNote}" />
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<Window title="terminal" removePadding>
|
||||
<div
|
||||
class="
|
||||
@ -29,10 +20,9 @@
|
||||
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="ls" funct/> <Token v="log" /> <Token v="|" punct/> <Token v="each" funct/> <Token v="{" punct/><Token v="|" punct/><Token v="file"/><Token v="|" punct/> <Token v="render" funct/> <Token v="(" punct/><Token v="open" funct/> <Token v="$file.name" /><Token v=")" punct/><Token v="}" punct/>
|
||||
<br>
|
||||
<br>
|
||||
{#each data.notes as [id, note], index}
|
||||
{@const isHighlighted = id === data.highlightedNote}
|
||||
<Note {id} {note} {isHighlighted}/>
|
||||
{#if index < data.notes.size - 1}
|
||||
{#each data.feedPosts as note, index}
|
||||
<Note {note}/>
|
||||
{#if index < data.feedPosts.length - 1}
|
||||
<div class="mt-3"/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
@ -1,38 +0,0 @@
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
import { readNote, readNotesList, type Note } from '$lib/notes.ts';
|
||||
|
||||
const logUrl = `${PUBLIC_BASE_URL}/log`;
|
||||
|
||||
interface NoteData {
|
||||
data: Note,
|
||||
id: string,
|
||||
}
|
||||
|
||||
export const GET = async ({ }) => {
|
||||
const log = readNotesList().map((id) => {return { data: readNote(id), id }})
|
||||
return new Response(
|
||||
render(log),
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'application/xml',
|
||||
'cache-control': 'no-store',
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const render = (log: NoteData[]) => `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<atom:link href="${logUrl}/_rss" rel="self" type="application/rss+xml" />
|
||||
<title>dusk's notes (@gaze.systems)</title>
|
||||
<link>${logUrl}</link>
|
||||
<description>a collection of random notes i write whenever, aka my microblogging spot</description>
|
||||
${log.map((note) => `<item>
|
||||
<guid>${logUrl}/?id=${note.id}</guid>
|
||||
<link>${logUrl}/?id=${note.id}</link>
|
||||
<description>${note.data.content}</description>
|
||||
<pubDate>${new Date(note.data.published).toUTCString()}</pubDate>
|
||||
</item>`).join('')}
|
||||
</channel>
|
||||
</rss>
|
||||
`;
|
@ -1,76 +0,0 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
import { getBskyClient } from '$lib/bluesky.js';
|
||||
import { createNote, findReplyRoot, genNoteId, readNote, type Note } from '$lib/notes';
|
||||
import type { Post, PostPayload, PostReference, ReplyRef } from '@skyware/bot';
|
||||
|
||||
interface NoteData {
|
||||
content: string,
|
||||
replyTo?: string,
|
||||
embedUri?: string,
|
||||
bskyPosse: boolean,
|
||||
}
|
||||
|
||||
export const POST = async ({ request }) => {
|
||||
const token = request.headers.get('authorization')
|
||||
if (token !== env.GAZEBOT_TOKEN) {
|
||||
return new Response("rizz failed", { status: 403 })
|
||||
}
|
||||
// get id
|
||||
const noteId = genNoteId()
|
||||
// get note data
|
||||
const noteData: NoteData = await request.json()
|
||||
console.log(`want to create note #${noteId} with data: `, noteData)
|
||||
// get a date before we start publishing to other platforms
|
||||
let note: Note = {
|
||||
content: noteData.content,
|
||||
published: Date.now(),
|
||||
outgoingLinks: [],
|
||||
replyTo: noteData.replyTo,
|
||||
}
|
||||
let errors: string[] = []
|
||||
let repliedNote: Note | null = null
|
||||
if (noteData.replyTo !== undefined) {
|
||||
repliedNote = readNote(noteData.replyTo)
|
||||
}
|
||||
// bridge to bsky if want to bridge
|
||||
if (noteData.bskyPosse) {
|
||||
const postContent = `${noteData.content} (${PUBLIC_BASE_URL}/log?id=${noteId})`
|
||||
try {
|
||||
const bot = await getBskyClient()
|
||||
let postPayload: PostPayload = {
|
||||
text: postContent,
|
||||
createdAt: new Date(note.published),
|
||||
external: noteData.embedUri,
|
||||
}
|
||||
let postRef: PostReference
|
||||
// find parent and reply posts
|
||||
let replyRef: ReplyRef | null = null
|
||||
if (noteData.replyTo !== undefined && repliedNote !== null) {
|
||||
const getBskyUri = (note: Note) => { return note.outgoingLinks?.find((v) => {return v.name === "bsky"})?.link }
|
||||
const parentUri = getBskyUri(repliedNote)
|
||||
if (parentUri !== undefined) {
|
||||
const parentPost = await bot.getPost(parentUri)
|
||||
postRef = await parentPost.reply(postPayload)
|
||||
} else {
|
||||
throw "a reply was requested but no reply is found"
|
||||
}
|
||||
} else {
|
||||
postRef = await bot.post(postPayload)
|
||||
}
|
||||
note.outgoingLinks?.push({name: "bsky", link: postRef.uri})
|
||||
} catch(why) {
|
||||
console.log(`failed to post note #${noteId} to bsky: `, why)
|
||||
errors.push(`error while posting to bsky: ${why}`)
|
||||
}
|
||||
}
|
||||
// create note (this should never fail otherwise it would defeat the whole purpose lol)
|
||||
createNote(noteId, note)
|
||||
// send back created note id and any errors that occurred
|
||||
return new Response(JSON.stringify({ noteId, errors }), {
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'cache-control': 'no-store',
|
||||
}
|
||||
})
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user