tg sticker v5.5
This commit is contained in:
@@ -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_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);
|
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
|
\unrestrict RMNKNzVQLV2ZcwmM3bmhglTot5nRoju9FmRyi3eUMfNy6iJUBfHRIgXnbrpJikG
|
||||||
|
|||||||
@@ -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)
|
// Reconvert all existing emoji files to WebP (Admin only)
|
||||||
router.post('/api/v2/admin/emojis/reconvert', async (req, res) => {
|
router.post('/api/v2/admin/emojis/reconvert', async (req, res) => {
|
||||||
if (!req.session || !req.session.admin) {
|
if (!req.session || !req.session.admin) {
|
||||||
|
|||||||
@@ -182,23 +182,28 @@
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePackEmojis(packId) {
|
function renderPackGrid(packId) {
|
||||||
var container = document.getElementById('sp-emojis-' + packId);
|
var container = document.getElementById('sp-emojis-' + packId);
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
if (container.style.display !== 'none') { container.style.display = 'none'; return; }
|
var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === packId; });
|
||||||
container.style.display = 'block';
|
var currentThumb = pack ? (pack.thumb_url || '') : '';
|
||||||
if (container.dataset.loaded) return;
|
|
||||||
|
|
||||||
var packEmojis = (window.emojiAdmin.emojis || []).filter(function(e) { return e.pack_id === packId; });
|
var packEmojis = (window.emojiAdmin.emojis || []).filter(function(e) { return e.pack_id === packId; });
|
||||||
if (packEmojis.length === 0) {
|
if (packEmojis.length === 0) {
|
||||||
container.innerHTML = '<p style="opacity:0.5;font-size:0.82em;padding:8px 0;">No stickers.</p>';
|
container.innerHTML = '<p style="opacity:0.5;font-size:0.82em;padding:8px 0;">No stickers.</p>';
|
||||||
} else {
|
} else {
|
||||||
container.innerHTML = '<div class="emoji-grid" style="max-height:320px;overflow-y:auto;">' +
|
container.innerHTML = '<div class="emoji-grid" style="max-height:320px;overflow-y:auto;">' +
|
||||||
packEmojis.map(function(e) {
|
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 '<div class="emoji-card" style="position:relative;">' +
|
return '<div class="emoji-card" style="position:relative;">' +
|
||||||
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">x</button>' +
|
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">x</button>' +
|
||||||
stickerEl(e.url, e.name, ' loading="lazy"') +
|
stickerEl(e.url, e.name, ' loading="lazy"') +
|
||||||
'<span class="emoji-label">:' + esc(e.name) + ':</span>' +
|
'<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>';
|
'</div>';
|
||||||
}).join('') +
|
}).join('') +
|
||||||
'</div>';
|
'</div>';
|
||||||
@@ -206,6 +211,14 @@
|
|||||||
container.dataset.loaded = '1';
|
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 ── */
|
/* ── STANDALONE EMOJI ACTIONS ── */
|
||||||
function addEmoji(e) {
|
function addEmoji(e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
@@ -377,6 +390,30 @@
|
|||||||
.catch(function(err) { alert('Error: ' + err.message); });
|
.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
|
// Global scope for onclick handlers
|
||||||
window.emojiAdmin = {
|
window.emojiAdmin = {
|
||||||
deleteEmoji: deleteEmoji,
|
deleteEmoji: deleteEmoji,
|
||||||
@@ -386,6 +423,7 @@
|
|||||||
importPack: importPack,
|
importPack: importPack,
|
||||||
deletePack: deletePack,
|
deletePack: deletePack,
|
||||||
renamePack: renamePack,
|
renamePack: renamePack,
|
||||||
|
setPackThumb: setPackThumb,
|
||||||
togglePackEmojis: togglePackEmojis,
|
togglePackEmojis: togglePackEmojis,
|
||||||
emojis: [],
|
emojis: [],
|
||||||
packs: []
|
packs: []
|
||||||
|
|||||||
Reference in New Issue
Block a user