Fix GIF display: store gif_url separately, render from API field or fallback to URL in content
This commit is contained in:
parent
75380bb5ed
commit
203713da2c
|
|
@ -83,6 +83,7 @@ def register_routes(app):
|
|||
|
||||
channel = data.get("channel", "general")
|
||||
content = data.get("message", "").strip()
|
||||
gif_url = data.get("gif_url") or None
|
||||
|
||||
if channel not in VALID_CHANNELS:
|
||||
response.status = 400
|
||||
|
|
@ -97,7 +98,7 @@ def register_routes(app):
|
|||
content = _strip_xss(content)
|
||||
|
||||
ip_hash = hashlib.sha256((_salt + ip).encode()).hexdigest()[:16]
|
||||
msg_id = _db.add_message(channel, ip_hash, content)
|
||||
msg_id = _db.add_message(channel, ip_hash, content, gif_url=gif_url)
|
||||
|
||||
return {"success": True, "message_id": msg_id, "ip": ip_hash}
|
||||
except Exception as e:
|
||||
|
|
|
|||
29
index.html
29
index.html
|
|
@ -320,8 +320,10 @@
|
|||
return;
|
||||
}
|
||||
gifGrid.innerHTML = results.map(gif => {
|
||||
const url = gif.media_formats?.tinygif?.url || gif.url;
|
||||
return `<img src="${escapeHtml(url)}" alt="gif" data-gif-url="${escapeHtml(gif.url || url)}">`;
|
||||
// Use the actual media URL, not the Tenor page URL
|
||||
const thumbUrl = gif.media_formats?.tinygif?.url || gif.media_formats?.gif?.url || gif.url;
|
||||
const fullUrl = gif.media_formats?.gif?.url || thumbUrl;
|
||||
return `<img src="${escapeHtml(thumbUrl)}" alt="gif" data-gif-url="${escapeHtml(fullUrl)}">`;
|
||||
}).join('');
|
||||
|
||||
gifGrid.querySelectorAll('img').forEach(img => {
|
||||
|
|
@ -395,11 +397,16 @@
|
|||
let html = '';
|
||||
msgs.forEach(msg => {
|
||||
const time = new Date(msg.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
||||
let contentHtml = escapeHtml(msg.content);
|
||||
// Render inline GIFs from stored <img> tags
|
||||
contentHtml = contentHtml.replace(/<img class="inline-gif" src="(.*?)">/g, (_, url) => {
|
||||
return `<img class="inline-gif" src="${escapeHtml(url)}" alt="gif">`;
|
||||
});
|
||||
let contentHtml = escapeHtml(msg.content || '');
|
||||
// Try gif_url from API first, fallback to parsing content for old messages
|
||||
let gifToShow = msg.gif_url || null;
|
||||
if (!gifToShow) {
|
||||
const gifMatch = msg.content.match(/https?:\/\/[^\s]+\.(gif|webp)(\?[^\s]*)?/i);
|
||||
if (gifMatch) gifToShow = gifMatch[0];
|
||||
}
|
||||
if (gifToShow) {
|
||||
contentHtml += `<img class="inline-gif" src="${escapeHtml(gifToShow)}" alt="gif">`;
|
||||
}
|
||||
html += `<div class="message">
|
||||
<div class="msg-ip">🔑 ${escapeHtml(msg.ip)}</div>
|
||||
<div class="msg-content">${contentHtml}</div>
|
||||
|
|
@ -415,18 +422,14 @@
|
|||
if (!text && !selectedGifUrl) return;
|
||||
|
||||
let payload = text;
|
||||
if (selectedGifUrl) {
|
||||
payload = text
|
||||
? `${text}\n<img class="inline-gif" src="${selectedGifUrl}">`
|
||||
: `<img class="inline-gif" src="${selectedGifUrl}">`;
|
||||
}
|
||||
let gif_url = selectedGifUrl || null;
|
||||
|
||||
sendBtn.disabled = true;
|
||||
try {
|
||||
const r = await fetch('/api/messages', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ channel: currentChannel, message: payload })
|
||||
body: JSON.stringify({ channel: currentChannel, message: payload, gif_url: gif_url })
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ models/base.py — declarative base & the Messages model.
|
|||
|
||||
from sqlalchemy import create_engine, Integer, String, Text
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
|
|
@ -17,6 +18,7 @@ class Messages(Base):
|
|||
channel: Mapped[str] = mapped_column(String(32), nullable=False, default="general")
|
||||
ip: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
gif_url: Mapped[Optional[str]] = mapped_column(Text, nullable=True, default=None)
|
||||
timestamp: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ models/db.py — DBfrontend: encrypted message store.
|
|||
"""
|
||||
|
||||
import time
|
||||
from typing import Optional
|
||||
from cryptography.fernet import Fernet
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
|
|
@ -33,12 +34,13 @@ class DBfrontend:
|
|||
except Exception:
|
||||
return "[Decryption failed]"
|
||||
|
||||
def add_message(self, channel: str, ip: str, content: str) -> int:
|
||||
def add_message(self, channel: str, ip: str, content: str, gif_url: Optional[str] = None) -> int:
|
||||
with self.Session() as session:
|
||||
msg = Messages(
|
||||
channel=channel,
|
||||
ip=ip,
|
||||
content=self._encrypt(content),
|
||||
gif_url=gif_url,
|
||||
timestamp=int(time.time() * 1000),
|
||||
)
|
||||
session.add(msg)
|
||||
|
|
@ -61,6 +63,7 @@ class DBfrontend:
|
|||
"channel": msg.channel,
|
||||
"ip": msg.ip,
|
||||
"content": self._decrypt(msg.content),
|
||||
"gif_url": msg.gif_url,
|
||||
"timestamp": msg.timestamp,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue