From a1f80d74077260c94370672c151cbc0add70207f Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Thu, 16 Jul 2026 00:09:58 +0200 Subject: [PATCH] tg sticker v5.5 --- migrations/f0ckm_schema.sql | 24 +++++++++++++++++++ src/inc/routes/emojis.mjs | 40 +++++++++++++++++++++++++++++++ views/admin/emojis.html | 48 +++++++++++++++++++++++++++++++++---- 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/migrations/f0ckm_schema.sql b/migrations/f0ckm_schema.sql index e7a4ca2..af67b1f 100644 --- a/migrations/f0ckm_schema.sql +++ b/migrations/f0ckm_schema.sql @@ -2906,4 +2906,28 @@ CREATE INDEX IF NOT EXISTS idx_comment_poll_options_poll ON public.comment_poll CREATE INDEX IF NOT EXISTS idx_comment_poll_votes_poll ON public.comment_poll_votes(poll_id); CREATE INDEX IF NOT EXISTS idx_comment_poll_votes_user ON public.comment_poll_votes(user_id); +-- Sticker Packs (Migration 010) +-- sticker_packs table: holds imported Telegram (or custom) sticker pack metadata +CREATE TABLE IF NOT EXISTS public.sticker_packs ( + id serial PRIMARY KEY, + name text NOT NULL, + tg_name text UNIQUE, + tg_title text, + thumb_url text, + sticker_count integer DEFAULT 0, + created_at timestamp with time zone DEFAULT now() +); + +ALTER TABLE public.sticker_packs OWNER TO f0ckm; + +-- Link custom_emojis to a sticker pack (NULL = standalone emoji) +ALTER TABLE public.custom_emojis + ADD COLUMN IF NOT EXISTS pack_id integer REFERENCES public.sticker_packs(id) ON DELETE SET NULL; + +-- Ensure thumb_url exists even on databases created before this column was added +ALTER TABLE public.sticker_packs + ADD COLUMN IF NOT EXISTS thumb_url text; + +CREATE INDEX IF NOT EXISTS custom_emojis_pack_id_idx ON public.custom_emojis (pack_id); + \unrestrict RMNKNzVQLV2ZcwmM3bmhglTot5nRoju9FmRyi3eUMfNy6iJUBfHRIgXnbrpJikG diff --git a/src/inc/routes/emojis.mjs b/src/inc/routes/emojis.mjs index 0797dd6..dc8734c 100644 --- a/src/inc/routes/emojis.mjs +++ b/src/inc/routes/emojis.mjs @@ -192,6 +192,46 @@ export default (router, tpl) => { } }); + // Set tab thumbnail for a sticker pack (Admin) + router.post(/\/api\/v2\/admin\/sticker-packs\/(?\d+)\/thumb/, async (req, res) => { + if (!req.session || !req.session.admin) { + return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Forbidden" }) }); + } + const csrfToken = req.headers['x-csrf-token']; + if (!req.session.csrf_token || !csrfToken || csrfToken !== req.session.csrf_token) { + return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Invalid CSRF token" }) }); + } + const id = req.params.id; + let body = {}; + try { + const raw = req.body || req.post || {}; + body = typeof raw === 'string' ? JSON.parse(raw) : raw; + } catch (_) {} + const thumbUrl = (body.thumb_url || '').trim(); + // Only allow local emoji paths to prevent SSRF + if (!thumbUrl || !thumbUrl.startsWith('/s/emojis/')) { + return res.reply({ code: 400, body: JSON.stringify({ success: false, message: 'thumb_url must be a local /s/emojis/ path' }) }); + } + try { + try { + await db`UPDATE sticker_packs SET thumb_url = ${thumbUrl} WHERE id = ${id}`; + } catch (colErr) { + // Column might not exist yet — add it and retry once + if (colErr.message && colErr.message.includes('thumb_url')) { + await db`ALTER TABLE public.sticker_packs ADD COLUMN IF NOT EXISTS thumb_url text`; + await db`UPDATE sticker_packs SET thumb_url = ${thumbUrl} WHERE id = ${id}`; + } else { + throw colErr; + } + } + await db`NOTIFY emojis_updated, '{}'`; + return res.reply({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: true }) }); + } catch (e) { + console.error(e); + return res.reply({ code: 500, body: JSON.stringify({ success: false, message: "Database error" }) }); + } + }); + // Reconvert all existing emoji files to WebP (Admin only) router.post('/api/v2/admin/emojis/reconvert', async (req, res) => { if (!req.session || !req.session.admin) { diff --git a/views/admin/emojis.html b/views/admin/emojis.html index 863b2ef..ba53b2e 100644 --- a/views/admin/emojis.html +++ b/views/admin/emojis.html @@ -182,23 +182,28 @@ }).join(''); } - function togglePackEmojis(packId) { + function renderPackGrid(packId) { var container = document.getElementById('sp-emojis-' + packId); if (!container) return; - if (container.style.display !== 'none') { container.style.display = 'none'; return; } - container.style.display = 'block'; - if (container.dataset.loaded) return; - + var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === packId; }); + var currentThumb = pack ? (pack.thumb_url || '') : ''; var packEmojis = (window.emojiAdmin.emojis || []).filter(function(e) { return e.pack_id === packId; }); if (packEmojis.length === 0) { container.innerHTML = '

No stickers.

'; } else { container.innerHTML = '
' + packEmojis.map(function(e) { + var isCurrent = currentThumb && e.url === currentThumb; + var btnBg = isCurrent ? 'rgba(40,167,69,0.18)' : 'rgba(255,200,0,0.15)'; + var btnClr = isCurrent ? '#4caf50' : '#ffd700'; + var btnBdr = isCurrent ? 'rgba(40,167,69,0.35)' : 'rgba(255,200,0,0.3)'; + var btnLbl = isCurrent ? '✓ Current Icon' : '⭐ Set as icon'; return '
' + '' + stickerEl(e.url, e.name, ' loading="lazy"') + ':' + esc(e.name) + ':' + + '' + '
'; }).join('') + '
'; @@ -206,6 +211,14 @@ container.dataset.loaded = '1'; } + function togglePackEmojis(packId) { + var container = document.getElementById('sp-emojis-' + packId); + if (!container) return; + if (container.style.display !== 'none') { container.style.display = 'none'; return; } + container.style.display = 'block'; + renderPackGrid(packId); + } + /* ── STANDALONE EMOJI ACTIONS ── */ function addEmoji(e) { if (e) e.preventDefault(); @@ -377,6 +390,30 @@ .catch(function(err) { alert('Error: ' + err.message); }); } + function setPackThumb(packId, emojiId) { + var emoji = (window.emojiAdmin.emojis || []).find(function(e) { return e.id === emojiId; }); + if (!emoji || !emoji.url) return alert('Could not find emoji URL.'); + var thumbUrl = emoji.url; + fetch('/api/v2/admin/sticker-packs/' + packId + '/thumb', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, 'X-Requested-With': 'XMLHttpRequest' }, + body: JSON.stringify({ thumb_url: thumbUrl }) + }) + .then(function(r) { return r.json(); }) + .then(function(data) { + if (data.success) { + // Update local pack data so re-render shows correct current icon + var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === packId; }); + if (pack) pack.thumb_url = thumbUrl; + // Re-render only this pack's grid — grid stays open + renderPackGrid(packId); + } else { + alert('Failed to set icon: ' + (data.message || 'Unknown error')); + } + }) + .catch(function(err) { alert('Error: ' + err.message); }); + } + // Global scope for onclick handlers window.emojiAdmin = { deleteEmoji: deleteEmoji, @@ -386,6 +423,7 @@ importPack: importPack, deletePack: deletePack, renamePack: renamePack, + setPackThumb: setPackThumb, togglePackEmojis: togglePackEmojis, emojis: [], packs: []