A simple AtProto app to read pet.mewsse.link records on my PDS.

Rework ingestion to save html render for iframes with oembed

Mewsse d5095a3b ea9dbac4

Changed files
+98 -100
assets
lexicons
pet
mewsse
src
lexicons
types
pet
mewsse
lib
views
+13 -5
assets/style.css
···
.item {
text-align: center;
-
display: flex;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom : 1px solid oklch(100% 0% 0 / 10%);
···
.item section {
width: 100%;
padding: 0 1rem 0 0;
+
margin-bottom: 1em;
}
.item .aside {
···
filter: blur(30px);
}
-
.item.big {
-
flex-direction: column;
-
}
-
.item.big .description {
margin-bottom: 1rem;
}
···
h2 {
margin-top: 0;
+
}
+
+
.media {
+
max-width: 600px;
+
width: 100%;
+
display: flex;
+
margin: 10px auto;
+
}
+
+
iframe {
+
width: 100%;
+
min-height: 300px;
}
@media only screen and (max-width: 700px) {
+1 -6
lexicons/pet/mewsse/link.json
···
"createdAt"
],
"properties": {
-
"link": {
+
"url": {
"type": "string",
"maxLength": 3000,
"description": "The link to point to."
···
"type": "boolean",
"default": false,
"description": "Is the link nsfw ?"
-
},
-
"big": {
-
"type": "boolean",
-
"default": false,
-
"description": "Do we need to display the link as large content ?"
},
"createdAt": {
"type": "string",
+5 -8
src/db.ts
···
export type Embed = {
'$type': string,
-
image?: Blob | LegacyBlob,
-
video?: Blob | LegacyBlob,
-
alt?: string,
-
uri?: string
+
url?: string,
+
html?: string,
+
alt?: string
} | null
export type Link = {
rkey: string,
-
link: string,
+
url: string,
title: string,
description: string | null,
embed: Embed,
nsfw: number,
-
big: number,
createdAt: string,
}
···
await db.schema
.createTable('links')
.addColumn('rkey', 'varchar', (col) => col.primaryKey())
-
.addColumn('link', 'varchar', (col) => col.notNull())
+
.addColumn('url', 'varchar', (col) => col.notNull())
.addColumn('title', 'varchar', (col) => col.notNull())
.addColumn('description', 'varchar')
.addColumn('embed', 'varchar')
.addColumn('nsfw', 'integer')
-
.addColumn('big', 'integer')
.addColumn('createdAt', 'varchar', (col) => col.notNull())
.execute()
+12 -13
src/ingester.ts
···
import { RepoReader } from '@atcute/car/v4'
import { decode } from '@atcute/cbor'
import { logger } from "./lib/logger.ts"
+
import { findEmbed } from "./lib/embed.ts"
interface RepoParams {
did: Did<"web"> | Did<"plc">,
···
if (!repoRev) repoRev = entry.rkey
if (entry.collection != "pet.mewsse.link") continue
const link = decode(entry.bytes)
+
console.log(link)
links.push({
rkey: entry.rkey,
-
link: link.link,
+
url: link.url,
title: link.title,
description: link.description,
-
embed: link.embed || null,
+
embed: await findEmbed(did, pds, link.embed),
nsfw: +(link.nsfw || 0),
-
big: +(link.big || 0),
createdAt: link.createdAt
})
}
···
links = links.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1)
for await (const link of links) {
+
const embed = await findEmbed(did, pds, link.embed)
await db
.insertInto('links')
.values(link)
.onConflict((conflict) =>
conflict.column('rkey').doUpdateSet({
-
link: link.link,
+
url: link.url,
title: link.title,
description: link.description,
-
embed: link.embed || null,
+
embed: embed,
nsfw: +(link.nsfw || 0),
-
big: +(link.big || 0),
})
)
.execute()
···
.insertInto('links')
.values({
rkey: rev,
-
link: record.link,
+
url: record.url,
title: record.title,
description: record.description ?? null,
-
embed: record.embed || null,
+
embed: await findEmbed(did, pds, record.embed),
nsfw: +(record.nsfw || 0),
-
big: +(record.big || 0),
createdAt: record.createdAt
})
.execute()
···
await db
.updateTable('links')
.set({
-
link: record.link,
+
url: record.url,
title: record.title,
description: record.description ?? null,
-
embed: record.embed,
+
embed: await findEmbed(did, pds, record.embed),
nsfw: +(record.nsfw || 0),
-
big: +(record.big || 0),
createdAt: record.createdAt
})
.where('rkey', '=', rev)
-
.executeTakeFirstOrThrow()
+
.executeTakeFirst()
logger.info(`Record ${rev} updated`)
})
+1 -6
src/lexicons/types/pet/mewsse/link.ts
···
/*#__PURE__*/ v.object({
$type: /*#__PURE__*/ v.literal("pet.mewsse.link"),
/**
-
* Do we need to display the link as large content ?
-
* @default false
-
*/
-
big: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean(), false),
-
/**
* Client-declared timestamp when this post was originally created.
*/
createdAt: /*#__PURE__*/ v.datetimeString(),
···
* The link to point to.
* @maxLength 3000
*/
-
link: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
+
url: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
/*#__PURE__*/ v.stringLength(0, 3000),
]),
/**
+39 -29
src/lib/embed.ts
···
import type { Did } from '@atcute/lexicons/syntax'
import type { Embed } from '../db.ts'
-
export function findEmbed(did: Did<"web"> | Did<"plc">, pds: string): Function {
-
return async (embed: Embed): Promise<{url?: string, html?:string, $type:string} | null> => {
-
if (!embed) return null
+
export async function findEmbed(did: Did<"web"> | Did<"plc">, pds: string, embed: any): Promise<Embed | null> {
+
if (!embed) return null
-
if (embed.$type === "pet.mewsse.embed.image" || embed.$type === "pet.mewsse.embed.video") {
-
if (!embed.image && !embed.video) return null
+
if (embed.$type === "pet.mewsse.embed.image" || embed.$type === "pet.mewsse.embed.video") {
+
if (!embed.image && !embed.video) return null
-
const embedType = embed.image || embed.video
+
const embedType = embed.image || embed.video
-
if (!embedType) return null
+
if (!embedType) return null
-
const cid = isBlob(embedType) ? embedType.ref.$link : embedType.cid
+
const cid = isBlob(embedType) ? embedType.ref.$link : embedType.cid
-
// let the user pull the blob with their browser directly fomr the pds
-
// decreasing space needed to run the service and prevent duplication
-
// if hosted at the same place as the pds (self host anyone ?)
-
return {
-
'$type': embed.$type,
-
url: `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`
-
}
+
// let the user pull the blob with their browser directly fomr the pds
+
// decreasing space needed to run the service and prevent duplication
+
// if hosted at the same place as the pds (self host anyone ?)
+
return {
+
'$type': embed.$type,
+
url: `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`,
+
alt: embed.alt
}
+
}
-
if (embed.$type === "pet.mewsse.embed.post" && embed.uri) {
-
const at = embed.uri.split("at://").pop()
+
if (embed.$type === "pet.mewsse.embed.post" && embed.uri) {
+
const at = embed.uri.split('at://').pop()
+
const response = await fetch(`https://embed.bsky.app/oembed?url=${embed.uri}`)
+
const body = await response.json()
-
return {
-
'$type': embed.$type,
-
url: `https://embed.bsky.app/embed/${at}`
-
}
+
return {
+
'$type': embed.$type,
+
html: body.html,
+
alt: embed.alt
}
+
}
-
if (embed.$type === "pet.mewsse.embed.external" && embed.uri) {
-
if (embed.uri.indexOf('youtube') != -1) {
-
const id = embed.uri.split("?v=").pop()
+
if (embed.$type === "pet.mewsse.embed.external" && embed.uri) {
+
if (embed.uri.indexOf('youtube') != -1) {
+
const response = await fetch(`https://www.youtube.com/oembed?url=${embed.uri}`)
+
const body = await response.json()
-
return {
-
'$type': embed.$type,
-
url: `http://www.youtube.com/embed/${id}`
-
}
+
return {
+
'$type': embed.$type,
+
html: body.html,
+
alt: embed.alt
}
}
-
return null
+
return {
+
'$type': embed.$type,
+
url: embed.uri,
+
alt: embed.alt
+
}
}
+
+
return null
}
+4 -15
src/routes.ts
···
import { IncomingMessage, ServerResponse } from "node:http"
-
import { findEmbed } from "./lib/embed.ts"
import path from "node:path"
import { Eta } from "eta"
···
export const createRoutes = (ctx: Context) => {
const eta = new Eta({ views: path.join(import.meta.dirname, "views") })
const itemPerPages = parseInt(process.env.ITEM_PER_PAGES || "10", 10)
-
const embed = findEmbed(ctx.did, ctx.pds)
async function renderPage (
req:IncomingMessage,
res:ServerResponse<IncomingMessage>,
params:{[key:string]: string} | undefined
) {
-
const offset = params ? parseInt(params?.page, 10) - 1 : 0
+
const offset = params ? (parseInt(params?.page, 10) - 1) * itemPerPages : 0
const links = await ctx.db
.selectFrom("links")
···
.executeTakeFirstOrThrow()
const pages = Math.ceil(parseInt(totalLink.count.toString(), 10) / itemPerPages)
-
-
const items = await Promise.all(links.map(async (link) => {
-
return {
-
...link,
-
embed: await embed(link.embed)
-
}
-
}))
const body = eta.render("./main", {
-
items: items,
+
items: links,
pages: Array.from(Array(pages).keys()),
-
selected: parseInt(params?.page || "0", 10),
-
embed: embed
+
selected: parseInt(params?.page || "0", 10)
})
res.writeHead(200, { 'Content-Type': 'text/html' })
···
const body = eta.render("./feed", {
items: links,
-
date,
-
embed: embed
+
date
})
res.writeHead(200, { 'Content-Type': 'application/atom+xml' })
res.end(body)
+23 -18
src/views/link.eta
···
<div class="item <% if (it.nsfw) { %>nsfw<% } %> <% if (it.big) { %>big<% } %>">
<section>
-
<h2><a href="<%= it.link %>"><%= it.title %></a></h2>
+
<h2><a href="<%= it.url %>"><%= it.title %></a></h2>
<% if (it.description) { %>
<div class="description"><%~ it.description %></div>
<% } %>
</section>
<% if (it.embed) { %>
-
<% if (it.embed.$type == "pet.mewsse.embed.image") { %>
-
<a class="aside" href="<%= it.link %>">
-
<img src="<%= it.embed.url %>" alt="<%= it.embed.alt || "" %>">
-
</a>
-
<% } %>
+
<div class="media">
+
<% if (it.embed.$type == "pet.mewsse.embed.image") { %>
+
<a class="aside" href="<%= it.url %>">
+
<img src="<%= it.embed.url %>" alt="<%= it.embed.alt || "" %>">
+
</a>
+
<% } %>
-
<% if (it.embed.$type == "pet.mewsse.embed.video") { %>
-
<video controls>
-
<source src="<%= it.embed.url %>">
-
</video>
-
<% } %>
+
<% if (it.embed.$type == "pet.mewsse.embed.video") { %>
+
<video controls>
+
<source src="<%= it.embed.url %>">
+
</video>
+
<% } %>
-
<% if (it.embed.$type == "pet.mewsse.embed.post") { %>
-
<iframe frameborder="0" src="<%= it.embed.url %>"></iframe>
-
<% } %>
-
-
<% if (it.embed.$type == "pet.mewsse.embed.external") {%>
-
<iframe frameborder="0" allowfullscreen src="<%= it.embed.url %>"></iframe>
-
<% } %>
+
<% if (it.embed.$type == "pet.mewsse.embed.post") { %>
+
<%~ it.embed.html %>
+
<% } %>
+
<% if (it.embed.$type == "pet.mewsse.embed.external") {%>
+
<% if (it.embed.html) { %>
+
<%~ it.embed.html %>
+
<% } else { %>
+
<img src="<%= it.embed.url %>">
+
<% } %>
+
<% } %>
+
</div>
<% } %>
</div>