From 78fe42ef3abb77a3dfefbea1726c48d514c9ed66 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Fri, 29 May 2026 19:33:45 +0200 Subject: [PATCH] jghf --- src/inc/routeinc/f0cklib.mjs | 26 +++++++++- src/inc/trigger/info.mjs | 94 ++++++++++++++++++++++++++++++++++++ views/snippets/header.html | 14 +++--- 3 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 src/inc/trigger/info.mjs diff --git a/src/inc/routeinc/f0cklib.mjs b/src/inc/routeinc/f0cklib.mjs index 0767ec9..019d16f 100644 --- a/src/inc/routeinc/f0cklib.mjs +++ b/src/inc/routeinc/f0cklib.mjs @@ -503,12 +503,17 @@ export default { `; if (unfilteredItem[0]) { // Item exists but was filtered - return minimal data for OG tags with blurred thumbnail + const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall; return { success: false, message: "Sorry, this post is currently not visible.", item: { id: itemid, - og_thumbnail: `${cfg.websrv.paths.thumbnails}/${itemid}_blur.webp` + og_thumbnail: `${cfg.websrv.paths.thumbnails}/${itemid}_blur.webp`, + og_url: hallSlug + ? `https://${cfg.main.url.domain}/h/${encodeURIComponent(hallSlug)}/${itemid}` + : `https://${cfg.main.url.domain}/${itemid}`, + og_description: `Content not visible in current mode` } }; } @@ -669,12 +674,17 @@ export default { else if (userMode === 2 && isTagged) modeBlocked = true; // Untagged mode, item has tags if (modeBlocked) { + const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall; return { success: false, message: "Sorry, this post is currently not visible.", item: { id: itemid, - og_thumbnail: `${cfg.websrv.paths.thumbnails}/${itemid}${isNsfw ? '_blur' : ''}.webp` + og_thumbnail: `${cfg.websrv.paths.thumbnails}/${itemid}${isNsfw ? '_blur' : ''}.webp`, + og_url: hallSlug + ? `https://${cfg.main.url.domain}/h/${encodeURIComponent(hallSlug)}/${itemid}` + : `https://${cfg.main.url.domain}/${itemid}`, + og_description: `Content not visible in current mode` } }; } @@ -706,6 +716,18 @@ export default { }, thumbnail: `${cfg.websrv.paths.thumbnails}/${actitem.id}.webp`, og_thumbnail: `${cfg.websrv.paths.thumbnails}/${actitem.id}${(isNsfw || isNsfl) ? '_blur' : ''}.webp`, + // og_url: canonical URL for OG/bots — hall context preserved, plain / as fallback + og_url: (() => { + const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall; + if (hallSlug) return `https://${cfg.main.url.domain}/h/${encodeURIComponent(hallSlug)}/${actitem.id}`; + return `https://${cfg.main.url.domain}/${actitem.id}`; + })(), + // og_description: include rating + uploader for bots (Matrix, Discord, etc.) + og_description: (() => { + const ratingLabel = isNsfl ? 'NSFL' : (isNsfw ? 'NSFW' : (isSfw ? 'SFW' : 'Untagged')); + const titlePart = actitem.title ? ` · "${actitem.title}"` : ''; + return `${ratingLabel}${titlePart} · uploaded by ${actitem.username}`; + })(), coverart: coverartUrl, dest: actitem.mime === 'video/youtube' ? actitem.dest : `${cfg.websrv.paths.images}/${actitem.dest}`, mime: actitem.mime, diff --git a/src/inc/trigger/info.mjs b/src/inc/trigger/info.mjs new file mode 100644 index 0000000..7a856b0 --- /dev/null +++ b/src/inc/trigger/info.mjs @@ -0,0 +1,94 @@ +import cfg from "../config.mjs"; +import db from "../sql.mjs"; +import lib from "../lib.mjs"; + +// Matches any URL that contains the site's own domain followed by an item ID, +// handling all URL patterns: +// / +// /h// +// /tag// +// /user// +// /user//favs/ +// etc. +const buildSiteItemRegex = (domain) => { + // Escape dots in domain for use in regex + const escapedDomain = domain.replace(/\./g, '\\.'); + // Match https://domain// or https://domain/ + return new RegExp( + `https?://${escapedDomain}/(?:[^\\s/]+/)*?(\\d+)(?:[/?#]|$)`, + 'gi' + ); +}; + +export default async bot => { + const domain = cfg.main.url.domain; + const siteItemRegex = buildSiteItemRegex(domain); + + return [{ + name: "info", + // Match any message from matrix that contains a URL with the site domain + item ID + call: new RegExp( + `https?://${domain.replace(/\./g, '\\.')}(?:/[^\\s]*)?/(\\d+)`, + 'i' + ), + active: true, + clients: ["matrix"], + level: 0, + f: async e => { + // Reset lastIndex before exec since the regex is shared/stateful + siteItemRegex.lastIndex = 0; + + // Collect all item IDs mentioned in the message (deduplicated) + const itemIds = new Set(); + let match; + const msgRegex = buildSiteItemRegex(domain); + while ((match = msgRegex.exec(e.message)) !== null) { + const id = parseInt(match[1], 10); + if (id > 0) itemIds.add(id); + } + + if (itemIds.size === 0) return; + + for (const itemid of itemIds) { + try { + const rows = await db` + SELECT + i.id, + i.mime, + i.size, + i.username, + i.stamp, + ( + SELECT t.tag + FROM tags_assign ta + JOIN tags t ON t.id = ta.tag_id + WHERE ta.item_id = i.id + AND ta.tag_id IN (1, 2, ${cfg.nsfl_tag_id || 3}) + ORDER BY ta.tag_id ASC + LIMIT 1 + ) as rating + FROM items i + WHERE i.id = ${itemid} + AND i.active = true + AND i.is_deleted = false + LIMIT 1 + `; + + if (!rows.length) continue; + + const item = rows[0]; + const rating = item.rating || 'untagged'; + // Format: "Sun Apr 12 2026 12:32:19" — matches existing bot output style + const d = new Date(item.stamp * 1000); + const dateStr = d.toString().replace(/\s*\(.+\)$/, '').replace(/\s+GMT[+-]\d+/, '').trim(); + const size = lib.formatSize(item.size); + + const reply = `ID: ${item.id} - ${rating} - user: ${item.username} - ~${size} - ${item.mime} - ${dateStr}`; + await e.reply(reply); + } catch (err) { + console.error(`[INFO] Failed to look up item ${itemid}:`, err); + } + } + } + }]; +}; diff --git a/views/snippets/header.html b/views/snippets/header.html index 5059cb4..4c704a0 100644 --- a/views/snippets/header.html +++ b/views/snippets/header.html @@ -54,18 +54,18 @@ @if(typeof item !== 'undefined') - + - - + + - - + + - + - + @else