tg sticker v5.5

This commit is contained in:
2026-07-16 00:09:58 +02:00
parent d94831db0f
commit a1f80d7407
3 changed files with 107 additions and 5 deletions

View File

@@ -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

View File

@@ -192,6 +192,46 @@ export default (router, tpl) => {
}
});
// Set tab thumbnail for a sticker pack (Admin)
router.post(/\/api\/v2\/admin\/sticker-packs\/(?<id>\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) {

View File

@@ -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 = '<p style="opacity:0.5;font-size:0.82em;padding:8px 0;">No stickers.</p>';
} else {
container.innerHTML = '<div class="emoji-grid" style="max-height:320px;overflow-y:auto;">' +
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 ? '&#10003; Current Icon' : '&#11088; Set as icon';
return '<div class="emoji-card" style="position:relative;">' +
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">x</button>' +
stickerEl(e.url, e.name, ' loading="lazy"') +
'<span class="emoji-label">:' + esc(e.name) + ':</span>' +
'<button onclick="window.emojiAdmin.setPackThumb(' + packId + ',' + e.id + ')"' +
' title="Use as tab icon" style="margin-top:5px;width:100%;padding:3px 0;font-size:0.72em;background:' + btnBg + ';color:' + btnClr + ';border:1px solid ' + btnBdr + ';border-radius:3px;cursor:pointer;">' + btnLbl + '</button>' +
'</div>';
}).join('') +
'</div>';
@@ -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: []