jghf
This commit is contained in:
@@ -503,12 +503,17 @@ export default {
|
|||||||
`;
|
`;
|
||||||
if (unfilteredItem[0]) {
|
if (unfilteredItem[0]) {
|
||||||
// Item exists but was filtered - return minimal data for OG tags with blurred thumbnail
|
// Item exists but was filtered - return minimal data for OG tags with blurred thumbnail
|
||||||
|
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Sorry, this post is currently not visible.",
|
message: "Sorry, this post is currently not visible.",
|
||||||
item: {
|
item: {
|
||||||
id: itemid,
|
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
|
else if (userMode === 2 && isTagged) modeBlocked = true; // Untagged mode, item has tags
|
||||||
|
|
||||||
if (modeBlocked) {
|
if (modeBlocked) {
|
||||||
|
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Sorry, this post is currently not visible.",
|
message: "Sorry, this post is currently not visible.",
|
||||||
item: {
|
item: {
|
||||||
id: itemid,
|
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`,
|
thumbnail: `${cfg.websrv.paths.thumbnails}/${actitem.id}.webp`,
|
||||||
og_thumbnail: `${cfg.websrv.paths.thumbnails}/${actitem.id}${(isNsfw || isNsfl) ? '_blur' : ''}.webp`,
|
og_thumbnail: `${cfg.websrv.paths.thumbnails}/${actitem.id}${(isNsfw || isNsfl) ? '_blur' : ''}.webp`,
|
||||||
|
// og_url: canonical URL for OG/bots — hall context preserved, plain /<id> 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,
|
coverart: coverartUrl,
|
||||||
dest: actitem.mime === 'video/youtube' ? actitem.dest : `${cfg.websrv.paths.images}/${actitem.dest}`,
|
dest: actitem.mime === 'video/youtube' ? actitem.dest : `${cfg.websrv.paths.images}/${actitem.dest}`,
|
||||||
mime: actitem.mime,
|
mime: actitem.mime,
|
||||||
|
|||||||
94
src/inc/trigger/info.mjs
Normal file
94
src/inc/trigger/info.mjs
Normal file
@@ -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:
|
||||||
|
// /<id>
|
||||||
|
// /h/<hall>/<id>
|
||||||
|
// /tag/<tag>/<id>
|
||||||
|
// /user/<user>/<id>
|
||||||
|
// /user/<user>/favs/<id>
|
||||||
|
// etc.
|
||||||
|
const buildSiteItemRegex = (domain) => {
|
||||||
|
// Escape dots in domain for use in regex
|
||||||
|
const escapedDomain = domain.replace(/\./g, '\\.');
|
||||||
|
// Match https://domain/<anything>/<digits> or https://domain/<digits>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
};
|
||||||
@@ -54,18 +54,18 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
@if(typeof item !== 'undefined')
|
@if(typeof item !== 'undefined')
|
||||||
<link rel="canonical" href="https://{{ domain }}/{{ item.id }}" />
|
<link rel="canonical" href="{{ item.og_url }}" />
|
||||||
<meta property="og:site_name" content="{{ domain }}" />
|
<meta property="og:site_name" content="{{ domain }}" />
|
||||||
<meta property="og:title" content="{{ item.id }}" />
|
<meta property="og:title" content="{{ item.id }} [{{ item.is_nsfl ? 'NSFL' : (item.is_nsfw ? 'NSFW' : (item.is_sfw ? 'SFW' : '?')) }}] - {{ domain }}" />
|
||||||
<meta property="og:url" content="https://{{ domain }}/{{ item.id }}" />
|
<meta property="og:url" content="{{ item.og_url }}" />
|
||||||
<meta property="og:image" content="https://{{ domain }}{{ item.og_thumbnail }}" />
|
<meta property="og:image" content="https://{{ domain }}{{ item.og_thumbnail }}" />
|
||||||
<meta name="description" content="{{ site_description }}" />
|
<meta name="description" content="{{ item.og_description }}" />
|
||||||
<meta property="og:description" content="{{ site_description }}" />
|
<meta property="og:description" content="{{ item.og_description }}" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="twitter:card" content="summary" />
|
<meta property="twitter:card" content="summary" />
|
||||||
<meta property="twitter:title" content="{{ item.id }}" />
|
<meta property="twitter:title" content="{{ item.id }} [{{ item.is_nsfl ? 'NSFL' : (item.is_nsfw ? 'NSFW' : (item.is_sfw ? 'SFW' : '?')) }}] - {{ domain }}" />
|
||||||
<meta property="twitter:image" content="https://{{ domain }}{{ item.og_thumbnail }}" />
|
<meta property="twitter:image" content="https://{{ domain }}{{ item.og_thumbnail }}" />
|
||||||
<meta property="twitter:url" content="https://{{ domain }}/{{ item.id }}" />
|
<meta property="twitter:url" content="{{ item.og_url }}" />
|
||||||
@else
|
@else
|
||||||
<meta property="og:site_name" content="{{ domain }}" />
|
<meta property="og:site_name" content="{{ domain }}" />
|
||||||
<meta property="og:title" content="@if(typeof page_meta !== 'undefined' && page_meta.title){{ page_meta.title }} - {{ domain }}@else{{ domain }}@endif" />
|
<meta property="og:title" content="@if(typeof page_meta !== 'undefined' && page_meta.title){{ page_meta.title }} - {{ domain }}@else{{ domain }}@endif" />
|
||||||
|
|||||||
Reference in New Issue
Block a user