feat: add reply to functionality to log
This commit is contained in:
parent
a3525f1fa4
commit
ab8ce81e18
22
package.json
22
package.json
@ -13,35 +13,35 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/enhanced-img": "^0.3.10",
|
"@sveltejs/enhanced-img": "^0.3.10",
|
||||||
"@sveltejs/kit": "^2.8.4",
|
"@sveltejs/kit": "^2.11.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.15.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.46.0",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"globals": "^15.12.0",
|
"globals": "^15.13.0",
|
||||||
"mdsvex": "^0.12.3",
|
"mdsvex": "^0.12.3",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.4.1",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.2",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^4.2.19",
|
||||||
"svelte-adapter-bun": "^0.5.2",
|
"svelte-adapter-bun": "^0.5.2",
|
||||||
"svelte-check": "^3.8.6",
|
"svelte-check": "^3.8.6",
|
||||||
"sveltekit-rate-limiter": "^0.6.1",
|
"sveltekit-rate-limiter": "^0.6.1",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.16",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"typescript-eslint": "^8.16.0",
|
"typescript-eslint": "^8.18.0",
|
||||||
"vite": "^5.4.11"
|
"vite": "^5.4.11"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.13.18",
|
"@neodrag/svelte": "^2.2.0",
|
||||||
"@neodrag/svelte": "^2.0.6",
|
"@skyware/bot": "^0.3.8",
|
||||||
"@std/toml": "npm:@jsr/std__toml",
|
"@std/toml": "npm:@jsr/std__toml",
|
||||||
"base64url": "^3.0.1",
|
"base64url": "^3.0.1",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
@ -49,7 +49,7 @@
|
|||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"robots-parser": "^3.0.1",
|
"robots-parser": "^3.0.1",
|
||||||
"steamgriddb": "^2.2.0",
|
"steamgriddb": "^2.2.0",
|
||||||
"typescript-svelte-plugin": "^0.3.43"
|
"typescript-svelte-plugin": "^0.3.44"
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
"@sveltejs/kit",
|
"@sveltejs/kit",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { env } from '$env/dynamic/private'
|
import { env } from '$env/dynamic/private'
|
||||||
import { Agent, CredentialSession, RichText } from '@atproto/api'
|
import { Bot } from "@skyware/bot";
|
||||||
import { get, writable } from 'svelte/store'
|
import { get, writable } from 'svelte/store'
|
||||||
|
|
||||||
const bskyClient = writable<null | Agent>(null)
|
const bskyClient = writable<null | Bot>(null)
|
||||||
|
|
||||||
export const getBskyClient = async () => {
|
export const getBskyClient = async () => {
|
||||||
let client = get(bskyClient)
|
let client = get(bskyClient)
|
||||||
@ -13,19 +13,15 @@ export const getBskyClient = async () => {
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postToBsky = async (text: string) => {
|
export const parseAtUri = (uri: string) => {
|
||||||
let client = await getBskyClient()
|
if (uri.startsWith("https://bsky.gaze.systems")) {
|
||||||
const rt = new RichText({ text })
|
return uri
|
||||||
await rt.detectFacets(client)
|
}
|
||||||
const {uri} = await client.post({
|
|
||||||
text: rt.text,
|
|
||||||
facets: rt.facets,
|
|
||||||
})
|
|
||||||
return `https://bsky.gaze.systems/post/${uri.split('/').pop()}`
|
return `https://bsky.gaze.systems/post/${uri.split('/').pop()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginToBsky = async () => {
|
const loginToBsky = async () => {
|
||||||
const creds = new CredentialSession(new URL("https://bsky.social"))
|
const bot = new Bot({ service: "https://bsky.social" })
|
||||||
await creds.login({ identifier: 'gaze.systems', password: env.BSKY_PASSWORD ?? "" })
|
await bot.login({ identifier: 'gaze.systems', password: env.BSKY_PASSWORD ?? "" })
|
||||||
return new Agent(creds)
|
return bot
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ export interface Note {
|
|||||||
content: string,
|
content: string,
|
||||||
published: number,
|
published: number,
|
||||||
outgoingLinks?: OutgoingLinkData[],
|
outgoingLinks?: OutgoingLinkData[],
|
||||||
|
replyTo?: NoteId,
|
||||||
}
|
}
|
||||||
type NoteId = string
|
type NoteId = string
|
||||||
|
|
||||||
@ -30,6 +31,21 @@ export const noteExists = (id: NoteId) => { return existsSync(getNotePath(id)) }
|
|||||||
export const readNote = (id: NoteId): Note => {
|
export const readNote = (id: NoteId): Note => {
|
||||||
return JSON.parse(readFileSync(getNotePath(id)).toString())
|
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) => {
|
export const writeNote = (id: NoteId, note: Note) => {
|
||||||
writeFileSync(getNotePath(id), JSON.stringify(note))
|
writeFileSync(getNotePath(id), JSON.stringify(note))
|
||||||
// only append to note list if its not in it yet
|
// only append to note list if its not in it yet
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Window from '../../components/window.svelte';
|
import Window from '../../components/window.svelte';
|
||||||
import Token from '../../components/token.svelte';
|
import Token from '../../components/token.svelte';
|
||||||
|
import { parseAtUri } from '$lib/bluesky.js';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@ -16,6 +17,12 @@
|
|||||||
|
|
||||||
const highlightedNote = data.notes.get(data.highlightedNote ?? '') ?? null
|
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
|
// this is ASS this should be a tailwind class
|
||||||
const getTextShadowStyle = (color: string) => {
|
const getTextShadowStyle = (color: string) => {
|
||||||
return `text-shadow: 0 0 1px theme(colors.ralsei.black), 0 0 5px ${color};`
|
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/> <Token v={note.content} str/>
|
<Token v={renderDate(note.published)} small={!isHighlighted}/> <Token v={noteId} keywd small={!isHighlighted}/><Token v="#" punct/> <Token v={note.content} str/>
|
||||||
{#each note.outgoingLinks ?? [] as {name, link}}
|
{#each note.outgoingLinks ?? [] as {name, link}}
|
||||||
{@const color = outgoingLinkColors[name]}
|
{@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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if index < data.notes.size - 1}
|
{#if index < data.notes.size - 1}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||||
import { postToBsky } from '$lib/bluesky';
|
import { getBskyClient } from '$lib/bluesky.js';
|
||||||
import { createNote, genNoteId, type Note } from '$lib/notes';
|
import { createNote, findReplyRoot, genNoteId, readNote, type Note } from '$lib/notes';
|
||||||
|
import type { Post, ReplyRef } from '@skyware/bot';
|
||||||
|
|
||||||
interface NoteData {
|
interface NoteData {
|
||||||
content: string,
|
content: string,
|
||||||
|
replyTo?: string,
|
||||||
bskyPosse: boolean,
|
bskyPosse: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,14 +25,45 @@ export const POST = async ({ request }) => {
|
|||||||
content: noteData.content,
|
content: noteData.content,
|
||||||
published: Date.now(),
|
published: Date.now(),
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
|
replyTo: noteData.replyTo,
|
||||||
}
|
}
|
||||||
let errors: string[] = []
|
let errors: string[] = []
|
||||||
|
let repliedNote: Note | null = null
|
||||||
|
if (noteData.replyTo !== undefined) {
|
||||||
|
repliedNote = readNote(noteData.replyTo)
|
||||||
|
}
|
||||||
// bridge to bsky if want to bridge
|
// bridge to bsky if want to bridge
|
||||||
if (noteData.bskyPosse) {
|
if (noteData.bskyPosse) {
|
||||||
const postContent = `${noteData.content} (${PUBLIC_BASE_URL}/log?id=${noteId})`
|
const postContent = `${noteData.content} (${PUBLIC_BASE_URL}/log?id=${noteId})`
|
||||||
try {
|
try {
|
||||||
const bskyUrl = await postToBsky(postContent)
|
const bot = await getBskyClient()
|
||||||
note.outgoingLinks?.push({name: "bsky", link: bskyUrl})
|
// 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) {
|
} catch(why) {
|
||||||
console.log(`failed to post note #${noteId} to bsky: `, why)
|
console.log(`failed to post note #${noteId} to bsky: `, why)
|
||||||
errors.push(`error while posting to bsky: ${why}`)
|
errors.push(`error while posting to bsky: ${why}`)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user