From f4d1ba897b52646db0e9b0029078b976984f40de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 3 Dec 2024 18:35:39 +0100 Subject: [PATCH 1/2] Store and serve local profile icons for user pages Shown e.g. in browser tabs and as RSS icon --- feeds.ts | 2 +- handlers/user-event.ts | 2 +- handlers/user-profile.ts | 2 +- html.ts | 6 ++-- magick.ts | 66 ++++++++++++++++++++++++++++++++-------- models/profile.ts | 8 +++++ 6 files changed, 69 insertions(+), 17 deletions(-) diff --git a/feeds.ts b/feeds.ts index 6a4332c..1613968 100644 --- a/feeds.ts +++ b/feeds.ts @@ -36,7 +36,7 @@ export async function profileAtomFeed( ${profile.name} on Nostr (Articles) ${feedId} ${isoDate(lastUpdate)} - ${profile.picture} + ${profile.avatarImageUrl} ${name} diff --git a/handlers/user-event.ts b/handlers/user-event.ts index 342bf5e..87057bc 100644 --- a/handlers/user-event.ts +++ b/handlers/user-event.ts @@ -27,7 +27,7 @@ const userEventHandler = async function (ctx: Context) { const article = new Article(articleEvent); const profile = new Profile(profileEvent, username); const html = await articleHtml(article, profile); - generateOgProfileImage(profile); + await generateOgProfileImage(profile); ctx.response.body = html; } else { diff --git a/handlers/user-profile.ts b/handlers/user-profile.ts index 5721f07..c01a007 100644 --- a/handlers/user-profile.ts +++ b/handlers/user-profile.ts @@ -21,7 +21,7 @@ const userProfileHandler = async function (ctx: Context) { const profile = new Profile(profileEvent, username); const articles = await fetchArticlesByAuthor(pubkey, 210); const html = await profilePageHtml(profile, articles); - generateOgProfileImage(profile); + await generateOgProfileImage(profile); ctx.response.body = html; } else { diff --git a/html.ts b/html.ts index c5a7818..6971855 100644 --- a/html.ts +++ b/html.ts @@ -55,7 +55,7 @@ export async function articleHtml( ${draftLabel}

${titleHtml(article.title)}

- User Avatar + User Avatar
${profile.name} ${publishedAtFormatted} @@ -148,7 +148,7 @@ export async function profilePageHtml( const body = `
- User Avatar + User Avatar

${profile.name}

${nip05Html} @@ -211,6 +211,7 @@ function feedLinksHtml(profile: Profile) { function profileMetaHtml(profile: Profile) { return ` + @@ -229,6 +230,7 @@ function articleMetaHtml(article: Article, profile: Profile) { const imageUrl = article.image || profile.ogImageUrl; return ` + diff --git a/magick.ts b/magick.ts index fdb7891..5603696 100644 --- a/magick.ts +++ b/magick.ts @@ -8,7 +8,7 @@ if (!magick) { log("ImageMagick is not installed. Cannot generate preview images", "yellow") } -function createRoundedImage(profile: Profile) { +function createProfileImage(profile: Profile) { if (!magick || !profile.picture) return false; const args = [ @@ -16,24 +16,44 @@ function createRoundedImage(profile: Profile) { '-resize', '256x256^', '-gravity', 'center', '-extent', '256x256', - '(', '+clone', '-alpha', 'extract', - '-draw', "fill black polygon 0,0 0,128 128,0 fill white circle 128,128 128,0", - '(', '+clone', '-flip', ')', '-compose', 'Multiply', '-composite', - '(', '+clone', '-flop', ')', '-compose', 'Multiply', '-composite', - ')', - '-alpha', 'off', - '-compose', 'CopyOpacity', - '-composite', - `${tmpImgDir}/p-${profile.event.id}-rounded.png` + `${tmpImgDir}/p-${profile.event.id}.png` ]; return runCommand(magick, args); } +async function createRoundedProfileImage(profile: Profile) { + if (!magick || !profile.picture) return false; + + const status = await generateProfileImage(profile); + + if (status && status.success) { + const args = [ + `${tmpImgDir}/p-${profile.event.id}.png`, + '-resize', '256x256^', + '-gravity', 'center', + '-extent', '256x256', + '(', '+clone', '-alpha', 'extract', + '-draw', "fill black polygon 0,0 0,128 128,0 fill white circle 128,128 128,0", + '(', '+clone', '-flip', ')', '-compose', 'Multiply', '-composite', + '(', '+clone', '-flop', ')', '-compose', 'Multiply', '-composite', + ')', + '-alpha', 'off', + '-compose', 'CopyOpacity', + '-composite', + `${tmpImgDir}/p-${profile.event.id}-rounded.png` + ]; + + return runCommand(magick, args); + } else { + return false; + } +} + async function createOgImage(profile: Profile, ogImagePath: string, backgroundColor: string) { if (!magick) return false; - const status = await createRoundedImage(profile); + const status = await createRoundedProfileImage(profile); if (status && status.success) { const args = [ @@ -51,6 +71,25 @@ async function createOgImage(profile: Profile, ogImagePath: string, backgroundCo } }; +export async function generateProfileImage(profile: Profile) { + if (!magick || !profile.picture) return false; + + const imagePath = `${tmpImgDir}/p-${profile.event.id}.png`; + const fileExists = await checkFileExists(imagePath); + + if (fileExists) { + return { success: true }; + } else { + const status = await createProfileImage(profile); + if (status && status.success) { + log(`Created avatar image for ${profile.username}: ${imagePath}`, "blue") + return status; + } else { + log(`Could not create avatar image for ${profile.username}`, "yellow") + } + } +} + export async function generateOgProfileImage(profile: Profile) { if (!magick || !profile.picture) return false; @@ -58,10 +97,13 @@ export async function generateOgProfileImage(profile: Profile) { const backgroundColor = "#333333"; const fileExists = await checkFileExists(ogImagePath); - if (!fileExists) { + if (fileExists) { + return { success: true }; + } else { const status = await createOgImage(profile, ogImagePath, backgroundColor); if (status && status.success) { log(`Created OG image for ${profile.username}: ${ogImagePath}`, "blue") + return status; } else { log(`Could not create OG image for ${profile.username}`, "yellow") } diff --git a/models/profile.ts b/models/profile.ts index 05e5415..f213fa0 100644 --- a/models/profile.ts +++ b/models/profile.ts @@ -63,6 +63,14 @@ export default class Profile { return `${config.base_url}/@${this.username}`; } + get avatarImageUrl(): string { + if (magick) { + return `${config.base_url}/assets/g/img/p-${this.event.id}.png`; + } else { + return this.picture || ""; + } + } + get ogImageUrl(): string { if (magick) { return `${config.base_url}/assets/g/img/og-p-${this.event.id}.png`; From 8e802f314ac8d3932392fcb40142daf932f04c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 3 Dec 2024 18:50:40 +0100 Subject: [PATCH 2/2] Remove debug statements --- handlers/nprofile.ts | 1 - server.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/handlers/nprofile.ts b/handlers/nprofile.ts index 6b1adb1..e84e03f 100644 --- a/handlers/nprofile.ts +++ b/handlers/nprofile.ts @@ -9,7 +9,6 @@ const nprofileHandler = async function (ctx: Context) { try { data = nip19.decode(nprofile).data as nip19.ProfilePointer; - console.log(data); } catch (_e) { notFoundHandler(ctx); return; diff --git a/server.ts b/server.ts index ee7a02a..068437f 100644 --- a/server.ts +++ b/server.ts @@ -85,7 +85,6 @@ router.get("/:username/:identifier", async (ctx) => { }); router.get("/assets/:path*", async (ctx) => { - console.log(import.meta.dirname); try { let filePath = ctx.params.path || ""; let root: string;