feat: add reply to functionality to log

This commit is contained in:
dusk 2024-12-16 17:14:29 +03:00
parent a3525f1fa4
commit ab8ce81e18
Signed by: dusk
SSH Key Fingerprint: SHA256:Abmvag+juovVufZTxyWY8KcVgrznxvBjQpJesv071Aw
6 changed files with 81 additions and 29 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -13,35 +13,35 @@
},
"devDependencies": {
"@sveltejs/enhanced-img": "^0.3.10",
"@sveltejs/kit": "^2.8.4",
"@sveltejs/kit": "^2.11.1",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/eslint": "^9.6.1",
"@types/node": "^22.10.0",
"@types/node": "^22.10.2",
"autoprefixer": "^10.4.20",
"eslint": "^9.15.0",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.0",
"globals": "^15.12.0",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.13.0",
"mdsvex": "^0.12.3",
"postcss": "^8.4.49",
"prettier": "^3.4.1",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.2",
"svelte": "^4.2.19",
"svelte-adapter-bun": "^0.5.2",
"svelte-check": "^3.8.6",
"sveltekit-rate-limiter": "^0.6.1",
"tailwindcss": "^3.4.15",
"tailwindcss": "^3.4.16",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.16.0",
"typescript-eslint": "^8.18.0",
"vite": "^5.4.11"
},
"type": "module",
"dependencies": {
"@atproto/api": "^0.13.18",
"@neodrag/svelte": "^2.0.6",
"@neodrag/svelte": "^2.2.0",
"@skyware/bot": "^0.3.8",
"@std/toml": "npm:@jsr/std__toml",
"base64url": "^3.0.1",
"nanoid": "^5.0.9",
@ -49,7 +49,7 @@
"rehype-slug": "^6.0.0",
"robots-parser": "^3.0.1",
"steamgriddb": "^2.2.0",
"typescript-svelte-plugin": "^0.3.43"
"typescript-svelte-plugin": "^0.3.44"
},
"trustedDependencies": [
"@sveltejs/kit",

View File

@ -1,8 +1,8 @@
import { env } from '$env/dynamic/private'
import { Agent, CredentialSession, RichText } from '@atproto/api'
import { Bot } from "@skyware/bot";
import { get, writable } from 'svelte/store'
const bskyClient = writable<null | Agent>(null)
const bskyClient = writable<null | Bot>(null)
export const getBskyClient = async () => {
let client = get(bskyClient)
@ -13,19 +13,15 @@ export const getBskyClient = async () => {
return client
}
export const postToBsky = async (text: string) => {
let client = await getBskyClient()
const rt = new RichText({ text })
await rt.detectFacets(client)
const {uri} = await client.post({
text: rt.text,
facets: rt.facets,
})
export const parseAtUri = (uri: string) => {
if (uri.startsWith("https://bsky.gaze.systems")) {
return uri
}
return `https://bsky.gaze.systems/post/${uri.split('/').pop()}`
}
const loginToBsky = async () => {
const creds = new CredentialSession(new URL("https://bsky.social"))
await creds.login({ identifier: 'gaze.systems', password: env.BSKY_PASSWORD ?? "" })
return new Agent(creds)
const bot = new Bot({ service: "https://bsky.social" })
await bot.login({ identifier: 'gaze.systems', password: env.BSKY_PASSWORD ?? "" })
return bot
}

View File

@ -11,6 +11,7 @@ export interface Note {
content: string,
published: number,
outgoingLinks?: OutgoingLinkData[],
replyTo?: NoteId,
}
type NoteId = string
@ -30,6 +31,21 @@ 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 currentNoteId: string | null = id
let currentNote: Note | null = null
while (currentNoteId !== null) {
currentNote = readNote(currentNoteId)
currentNoteId = currentNote.replyTo ?? null
}
if (currentNote === null || currentNoteId === null) {
throw "no note with id found"
}
return {
rootNote: currentNote,
rootNoteId: currentNoteId,
}
}
export const writeNote = (id: NoteId, note: Note) => {
writeFileSync(getNotePath(id), JSON.stringify(note))
// only append to note list if its not in it yet

View File

@ -1,6 +1,7 @@
<script lang="ts">
import Window from '../../components/window.svelte';
import Token from '../../components/token.svelte';
import { parseAtUri } from '$lib/bluesky.js';
export let data;
@ -16,6 +17,12 @@
const highlightedNote = data.notes.get(data.highlightedNote ?? '') ?? null
const getOutgoingLink = (name: string, link: string) => {
if (name === "bsky") {
return parseAtUri(link)
}
return link
}
// this is ASS this should be a tailwind class
const getTextShadowStyle = (color: string) => {
return `text-shadow: 0 0 1px theme(colors.ralsei.black), 0 0 5px ${color};`
@ -52,7 +59,7 @@
<Token v={renderDate(note.published)} small={!isHighlighted}/> <Token v={noteId} keywd small={!isHighlighted}/><Token v="#" punct/>&nbsp;&nbsp;<Token v={note.content} str/>
{#each note.outgoingLinks ?? [] as {name, link}}
{@const color = outgoingLinkColors[name]}
<span class="text-sm"><Token v="(" punct/><a style="color: {color};{getTextShadowStyle(color)}" href={link}>{name}</a><Token v=")" punct/></span>
<span class="text-sm"><Token v="(" punct/><a style="color: {color};{getTextShadowStyle(color)}" href={getOutgoingLink(name, link)}>{name}</a><Token v=")" punct/></span>
{/each}
</div>
{#if index < data.notes.size - 1}

View File

@ -1,10 +1,12 @@
import { env } from '$env/dynamic/private';
import { PUBLIC_BASE_URL } from '$env/static/public';
import { postToBsky } from '$lib/bluesky';
import { createNote, genNoteId, type Note } from '$lib/notes';
import { getBskyClient } from '$lib/bluesky.js';
import { createNote, findReplyRoot, genNoteId, readNote, type Note } from '$lib/notes';
import type { Post, ReplyRef } from '@skyware/bot';
interface NoteData {
content: string,
replyTo?: string,
bskyPosse: boolean,
}
@ -23,14 +25,45 @@ export const POST = async ({ request }) => {
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 bskyUrl = await postToBsky(postContent)
note.outgoingLinks?.push({name: "bsky", link: bskyUrl})
const bot = await getBskyClient()
// 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)
let parentPost: Post | null = null
if (parentUri !== undefined) {
parentPost = await bot.getPost(parentUri)
}
const rootUri = getBskyUri(findReplyRoot(noteData.replyTo).rootNote)
let rootPost: Post | null = null
if (rootUri !== undefined) {
rootPost = await bot.getPost(rootUri)
}
if (parentPost !== null && rootPost !== null) {
replyRef = {
parent: parentPost,
root: rootPost,
}
}
}
const postRef = await bot.post({
text: postContent,
createdAt: new Date(note.published),
replyRef: replyRef ?? undefined
})
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}`)