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