From 0f32bb70bb9fae30993c4f5ac60ef830b32f0081 Mon Sep 17 00:00:00 2001 From: Krishna Rowter Date: Sat, 16 Mar 2024 16:16:11 +0700 Subject: [PATCH] fix: handle messages of type poll --- src/components/chat-bubble/ChatBubble.tsx | 8 ++- src/components/chat-bubble/MediaPoll.scss | 50 +++++++++++++++++ src/components/chat-bubble/MediaPoll.tsx | 61 ++++++++++++++++++++ src/service/Parser.ts | 47 +++++++++++++++- tests/service/Parser.test.ts | 68 +++++++++++++++++++++++ 5 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 src/components/chat-bubble/MediaPoll.scss create mode 100644 src/components/chat-bubble/MediaPoll.tsx diff --git a/src/components/chat-bubble/ChatBubble.tsx b/src/components/chat-bubble/ChatBubble.tsx index c3fa905..0af5b8f 100644 --- a/src/components/chat-bubble/ChatBubble.tsx +++ b/src/components/chat-bubble/ChatBubble.tsx @@ -1,5 +1,6 @@ import { Match, Show, Switch } from "solid-js"; import type { TelegramMessage } from "~/service/Parser"; +import { MediaPoll } from "./MediaPoll"; import "./ChatBubble.scss"; type ChatBubbleProps = TelegramMessage & { @@ -47,18 +48,19 @@ export function ChatBubble(props: ChatBubbleProps) {
+ {props.hasMedia && props.mediaType === "poll" && }
diff --git a/src/components/chat-bubble/MediaPoll.scss b/src/components/chat-bubble/MediaPoll.scss new file mode 100644 index 0000000..908363b --- /dev/null +++ b/src/components/chat-bubble/MediaPoll.scss @@ -0,0 +1,50 @@ +@use "../../styles/variables.scss"; + +.poll { + display: flex; + align-items: center; + justify-content: center; + gap: 8rem; + padding: 2rem; + + &-chart { + width: 50%; + + rect { + stroke: variables.$black; + stroke-width: 0.01rem; + } + } + + &-legend { + span { + display: block; + } + span::before { + content: ""; + display: inline-block; + width: 2rem; + height: 2rem; + margin-right: 0.5rem; + background-color: var(--marker-color); + border: 0.2rem variables.$black solid; + } + } +} +@media (max-width: variables.$screen-md-min) { + .poll { + flex-direction: column; + gap: 2rem; + + &-chart { + width: 80%; + } + + &-legend { + span::before { + width: 1rem; + height: 1rem; + } + } + } +} diff --git a/src/components/chat-bubble/MediaPoll.tsx b/src/components/chat-bubble/MediaPoll.tsx new file mode 100644 index 0000000..2c8f0b4 --- /dev/null +++ b/src/components/chat-bubble/MediaPoll.tsx @@ -0,0 +1,61 @@ +import type { TelegramMessagePoll } from "~/service/Parser"; +import "./MediaPoll.scss"; +import { createSignal } from "solid-js"; + +// colorblind-friendly colors +// and Telegram has maximum of 10 options +const colors = [ + "#1F77B4", // Strong Blue + "#FF7F0E", // Strong Orange + "#2CA02C", // Strong Green + "#D62728", // Strong Red + "#9467BD", // Strong Purple + "#8C564B", // Strong Brown + "#E377C2", // Strong Pink + "#7F7F7F", // Medium Gray + "#BCBD22", // Strong Yellow + "#17BECF", // Strong Cyan +]; + +export function MediaPoll(props: TelegramMessagePoll) { + const chartSize = 20; + const barWidth = 20 / props.pollResult.length; + + const maxVotes = Math.max(...props.pollResult.map((opt) => opt.votes)); + const normalizeVotes = (vote: number) => (vote / maxVotes) * chartSize * /* provide some top padding */ 0.8; + + return ( +
+ + + + + {props.pollResult.map((option, i) => ( + <> + + + {option.votes} + + + ))} + + +
+ {props.pollResult.map((option, i) => ( + {option.text} + ))} +
+
+ ); +} diff --git a/src/service/Parser.ts b/src/service/Parser.ts index 6a0613f..9aa7419 100644 --- a/src/service/Parser.ts +++ b/src/service/Parser.ts @@ -54,12 +54,23 @@ export type TelegramMessageWithVideoFile = { height: number; } & TelegramMessageBase; +export type TelegramMessagePoll = { + hasMedia: true; + mediaType: "poll"; + pollResult: { + text: string; + votes: number; + }[]; + explanation?: string; +} & TelegramMessageBase; + export type TelegramMessage = | (TelegramMessageBase & { hasMedia: false }) | TelegramMessageWithAnimation | TelegramMessageWithPhoto | TelegramMessageWithSticker - | TelegramMessageWithVideoFile; + | TelegramMessageWithVideoFile + | TelegramMessagePoll; export type TelegramChat = { chatName: string; @@ -131,6 +142,20 @@ const exportedChatHistorySchema = z.object({ }) ) .optional(), + poll: z + .object({ + question: z.string(), + closed: z.boolean(), + total_voters: z.number(), + answers: z.array( + z.object({ + text: z.string(), + voters: z.number(), + chosen: z.boolean(), + }) + ), + }) + .optional(), }) ), }); @@ -349,6 +374,26 @@ export class Parser { mimeType: message.mime_type ?? "", }); continue; + } else if (message.poll !== undefined) { + telegramChat.message.push({ + date: new Date(message.date), + from: { + name: message.from, + id: Number.parseInt(message.from_id.replace("user", "")), + }, + messageId: message.id, + replyToMessageId: message.reply_to_message_id, + text: message.poll.question, + hasMedia: true, + mediaType: "poll", + pollResult: message.poll.answers.map((answer) => { + return { + text: answer.text, + votes: answer.voters, + }; + }), + }); + continue; } else { telegramChat.message.push({ date: new Date(message.date), diff --git a/tests/service/Parser.test.ts b/tests/service/Parser.test.ts index e18b5cb..da9638c 100644 --- a/tests/service/Parser.test.ts +++ b/tests/service/Parser.test.ts @@ -1709,3 +1709,71 @@ test("parse without text_entity", () => { }, ]); }); + +test("message poll", () => { + const stub = `{ + "name": "Teknologi Umum v2.0", + "type": "public_supergroup", + "id": 1712691810, + "messages": [ + { + "id": 632576, + "type": "message", + "date": "2024-02-25T11:53:31", + "date_unixtime": "1708836811", + "from": "Reinaldy", + "from_id": "user1462097294", + "poll": { + "question": "Apakah Anda tahu kalau SSL certificate bisa digunakan selain untuk keperluan website (https)?", + "closed": false, + "total_voters": 25, + "answers": [ + { + "text": "Tahu", + "voters": 11, + "chosen": true + }, + { + "text": "Tidak", + "voters": 14, + "chosen": false + } + ] + }, + "text": "", + "text_entities": [] + } + ] + }`; + + const parser = new Parser(); + + const telegramChat = parser.fromExportedChatHistory(stub); + + expect(telegramChat.chatId).toEqual(1712691810); + expect(telegramChat.chatName).toEqual("Teknologi Umum v2.0"); + expect(telegramChat.message).toStrictEqual([ + { + date: new Date("2024-02-25T11:53:31"), + from: { + id: 1462097294, + name: "Reinaldy", + }, + hasMedia: true, + mediaType: "poll", + messageId: 632576, + replyToMessageId: undefined, + text: "Apakah Anda tahu kalau SSL certificate bisa digunakan selain untuk keperluan website (https)?", + pollResult: [ + { + text: "Tahu", + votes: 11, + }, + { + text: "Tidak", + votes: 14, + }, + ], + }, + ]); +});