From b5982063c49761e92b1ead9949ac8a570ba45779 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 22 Jul 2026 14:52:55 +0300 Subject: [PATCH] FEATURE: added pols to frontend --- chat.db | Bin 24576 -> 24576 bytes index.html | 131 ++++++++++-------------- models/__pycache__/base.cpython-314.pyc | Bin 5009 -> 5122 bytes models/__pycache__/db.cpython-314.pyc | Bin 14896 -> 16478 bytes models/base.py | 1 + models/db.py | 29 +++++- 6 files changed, 83 insertions(+), 78 deletions(-) diff --git a/chat.db b/chat.db index bf0161f3fe510a25144e2e4be9b3941074af9de4..23b1d026f12bb7ffd7ddbfe475d40c68275791fd 100644 GIT binary patch delta 510 zcmYk2Pixv>0LI_Qw3_15jzKBK5e5Sb%`j#$@gTI$*ZMDwn#LM0qKW2@@lB%67%#2t zw4FA(`(W2yR{R1z?345Z?6eQCY%u5#p6B{|cy8{on|tij(LN3Uh=uu2?DGjj;NbPg z=gDLgMDZcOhj-|A0^hjU&Os`Ly=QLI#Lri*W7~aGBdZse@Eo>JNIcPOSB0~-Le`-| zrcLb_Ki$}7>`OWUr}=v{p*Hl2l)XUn2e zVbvBCdAiGVyDnOomQnIoB{k~?exZPU{qQ~cYoIR7BxjNDSPMHqO}0_K5RvUqH%6ehvn`E)iZ{Rt8q?PzHXvjd@= { - const isClosed = poll.status === 'closed'; - const isExpired = poll.expires_at && Date.now() > poll.expires_at; - const hasVoted = votedPolls.has(poll.id) || poll.options.some(o => o.votes > 0 && false); - const total = poll.total_votes || 0; - let optionsHtml = ''; - poll.options.forEach(opt => { - const pct = total > 0 ? Math.round((opt.votes / total) * 100) : 0; - const votedClass = hasVoted ? ' voted' : ''; - if (isClosed || isExpired || hasVoted) { - optionsHtml += `
-
- ${escapeHtml(opt.label)} - ${pct}% - ${opt.votes} -
`; - } else { - optionsHtml += `
- ${escapeHtml(opt.label)} - ${opt.votes} votes -
`; - } - }); - const statusLabel = isClosed || poll.status === 'closed' ? 'Closed' : - isExpired ? 'Expired' : 'Active'; - const statusClass = isClosed || isExpired ? 'closed' : 'active'; - const creatorLabel = poll.is_anonymous ? 'Anonymous' : escapeHtml(poll.creator_ip); - html += `
-
๐Ÿ“Š ${escapeHtml(poll.question)}
-
by ${creatorLabel} ยท ${total} vote${total !== 1 ? 's' : ''} ยท ${statusLabel}
-
${optionsHtml}
-
`; - }); - return html; } async function loadMessages() { @@ -813,28 +761,61 @@ } function renderMessages(msgs) { - const pollsHtml = renderPolls(); - if (msgs.length === 0 && !pollsHtml) { + if (msgs.length === 0) { messagesEl.innerHTML = '
๐Ÿ’ฌ No messages yet. Be the first!
'; return; } - let html = pollsHtml; + 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 || ''); - 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 (msg.poll) { + const poll = msg.poll; + const isClosed = poll.status === 'closed'; + const isExpired = poll.expires_at && Date.now() > poll.expires_at; + const hasVoted = votedPolls.has(poll.id); + const total = poll.total_votes || 0; + let optionsHtml = ''; + poll.options.forEach(opt => { + const pct = total > 0 ? Math.round((opt.votes / total) * 100) : 0; + const votedClass = hasVoted ? ' voted' : ''; + if (isClosed || isExpired || hasVoted) { + optionsHtml += `
+
+ ${escapeHtml(opt.label)} + ${pct}% + ${opt.votes} +
`; + } else { + optionsHtml += `
+ ${escapeHtml(opt.label)} + ${opt.votes} votes +
`; + } + }); + const statusLabel = isClosed ? 'Closed' : isExpired ? 'Expired' : 'Active'; + const statusClass = isClosed || isExpired ? 'closed' : 'active'; + const creatorLabel = poll.is_anonymous ? 'Anonymous' : escapeHtml(poll.creator_ip); + html += `
+
๐Ÿ“Š ${escapeHtml(poll.question)}
+
by ${creatorLabel} ยท ${total} vote${total !== 1 ? 's' : ''} ยท ${statusLabel}
+
${optionsHtml}
+
`; + } else { + const time = new Date(msg.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); + let contentHtml = escapeHtml(msg.content || ''); + 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 += `gif`; + } + html += `
+
๐Ÿ”‘ ${escapeHtml(msg.ip)}
+
${contentHtml}
+
${time}
+
`; } - if (gifToShow) { - contentHtml += `gif`; - } - html += `
-
๐Ÿ”‘ ${escapeHtml(msg.ip)}
-
${contentHtml}
-
${time}
-
`; }); messagesEl.innerHTML = html; attachPollHandlers(); @@ -859,10 +840,10 @@ votedPolls.add(pollId); saveVotedPolls(); showToast('Vote recorded!', 'success'); - loadPolls().then(() => renderMessages(window._lastMessages || [])); + loadMessages(); } else { showToast(data.error || 'Failed to vote', 'error'); - loadPolls().then(() => renderMessages(window._lastMessages || [])); + loadMessages(); } } catch (e) { showToast('Network error', 'error'); @@ -904,17 +885,13 @@ loadVotedPolls(); renderChannels(); loadMessages(); - loadPolls(); sendBtn.addEventListener('click', sendMessage); inputEl.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); }); - // Auto-poll every 3 seconds + // Auto-refresh every 3 seconds setInterval(() => { loadMessages(); - loadPolls().then(() => { - if (window._lastMessages) renderMessages(window._lastMessages); - }); }, 3000); diff --git a/models/__pycache__/base.cpython-314.pyc b/models/__pycache__/base.cpython-314.pyc index 7d3d6db80203a62456b14b469eedd2e5f947a036..584c546ceedc7f9ae1775bf9d062f8b3877c1f8c 100644 GIT binary patch delta 566 zcmXw#%WD%+6vpqF$DPbGkCDcb1X87K(k7Z}AhjeaiJ)TZW6~E$s?e5@5=bkS{BDpiz#y709ST(~i%U0BfnpkhnGU2*QHck$itoO{mi9BzB?PC_3xbcMKQ z==t@f&o6c0VGI5IYL2sf7R~YOi4e!Ah<&6&Ey0p<+`pWEnj8m94wJgi30R5~oF+~P z*ZC^!;W8|*>`ssX?1V_5r#=5*xhs@*O*dipx)9rRC23-kPH}b`m+1(M;4>P7Tlj^} zMEj4u$bFprTAtbHpKYD}78`n;do#5WE49x5Hu0#Gg=l9%dJXgxL!HJxIZm_8YIu=` zaZbLkjIxkP6nEuUFp6thZ(!ApTcko9u!zIWWpG_tfjrjLL~`tZy?w!TPr&xjMb~f9 zM$@S^ZrCSTNeSPnameBiH7wdGH-r0Xlor_2IL5VuN}gFJ87yiwm%gSYpn#j&_mD?Z zykEQBa29XcDg2}l!zKKsM*`l$92Ay(Rmvj1l0q1TNLqUsA|Y?2JSg~gxz~RIBy>L1NyRwwg3PC delta 537 zcmZqDn5fRH&Bx2d00fMy60&kP@-i|r9+}L~yhxEHh~1nCB*DN?!WYJ1%oxNe&XB`V z!XLy{A`r$i@SOhlD5y-y9 zmS0eknV(l=3sT|)B0z5U(-Z_cUQ`ic8Ay~FM5u!Zejss+y&yj)Cq6S}az2j?n+-_V zd-6mc2`+086GZ4w-prE;v|f%|aJ$A~N}kupR(%dwJLZ diff --git a/models/__pycache__/db.cpython-314.pyc b/models/__pycache__/db.cpython-314.pyc index dc611d9b5c7ecb0478da02aa823408eae5890356..b16e7d39c82f9d46c52cceeadf2b5477bc0d00bc 100644 GIT binary patch delta 4407 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 G@BaqDtHvP! diff --git a/models/base.py b/models/base.py index f4f9248..c5907b2 100644 --- a/models/base.py +++ b/models/base.py @@ -22,6 +22,7 @@ class Messages(Base): 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) + poll_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, default=None) timestamp: Mapped[int] = mapped_column(Integer, nullable=False) class Poll(Base): diff --git a/models/db.py b/models/db.py index 08bd35a..0b9d355 100644 --- a/models/db.py +++ b/models/db.py @@ -18,6 +18,16 @@ class DBfrontend: self.engine = make_engine(db_cfg) self.Session = sessionmaker(bind=self.engine) self.cipher = self._get_cipher(secret, salt) + self._migrate() + + def _migrate(self): + with self.Session() as session: + from sqlalchemy import inspect, text + inspector = inspect(session.connection()) + columns = [c["name"] for c in inspector.get_columns("messages")] + if "poll_id" not in columns: + session.execute(text("ALTER TABLE messages ADD COLUMN poll_id INTEGER")) + session.commit() @staticmethod def _get_cipher(secret: bytes, salt: bytes) -> Fernet: @@ -34,13 +44,14 @@ class DBfrontend: except Exception: return "[Decryption failed]" - def add_message(self, channel: str, ip: str, content: str, gif_url: Optional[str] = None) -> int: + def add_message(self, channel: str, ip: str, content: str, gif_url: Optional[str] = None, poll_id: Optional[int] = None) -> int: with self.Session() as session: msg = Messages( channel=channel, ip=ip, content=self._encrypt(content), gif_url=gif_url, + poll_id=poll_id, timestamp=int(time.time() * 1000), ) session.add(msg) @@ -57,6 +68,11 @@ class DBfrontend: ) result = [] for msg in reversed(q.all()): + poll_data = None + if msg.poll_id: + poll = session.get(Poll, msg.poll_id) + if poll and poll.status != "deleted": + poll_data = self._serialize_poll(session, poll) result.append( { "id": msg.id, @@ -64,6 +80,8 @@ class DBfrontend: "ip": msg.ip, "content": self._decrypt(msg.content), "gif_url": msg.gif_url, + "poll_id": msg.poll_id, + "poll": poll_data, "timestamp": msg.timestamp, } ) @@ -106,6 +124,15 @@ class DBfrontend: opt = PollOption(poll_id=poll.id, label=self._encrypt(label), position=i) session.add(opt) + msg = Messages( + channel=channel, + ip=creator_ip, + content=self._encrypt(question), + poll_id=poll.id, + timestamp=int(time.time() * 1000), + ) + session.add(msg) + session.commit() return poll.id