|
|
- import { escapeHtmlShitty, HtmlEnt, JSX } from './jsx';
-
- const { readFileSync } = require("fs");
- const contents = readFileSync(0);
- const lines: string[] = contents.toString().split('\n');
-
- type MessageFrag = {
- type: 'url',
- link: string
- } | {
- type: 'text',
- content: string
- } | {
- type: 'error',
- content: string
- };
-
- type IrcUser = {
- mode?: '+' | '@' | ' ',
- name: string
- };
-
- type IrcMessage = {
- time: string,
- type: 'status',
- content: MessageFrag[]
- } | {
- time: string,
- type: 'action',
- user: IrcUser,
- content: MessageFrag[]
- } | {
- time: string,
- type: 'message',
- user: IrcUser,
- content: MessageFrag[]
- } | {
- type: 'day_change'
- } | {
- type: 'redacted'
- };
-
- const URL = /(((?:https?|gopher):\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/|spotify:track:)((?:\(?[^\s()<>]+\)?)*[^ \s`!\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019]))/i;
-
- function parseMessage(m: string): MessageFrag[] {
- m = m.trimEnd();
- const parts: MessageFrag[] = [];
- let match: RegExpMatchArray | null;
- while ((match = m.match(URL)) != null) {
- parts.push({ type: 'text', content: m.slice(0, match.index) });
- parts.push({ type: 'url', link: match[0] });
- m = m.slice((match.index ?? 0) + match[0].length);
- }
- if (m !== "") {
- parts.push({ type: 'text', content: m });
- }
- return parts;
- }
-
- function parse(s: string): IrcMessage {
- s = s.trimEnd();
- if (s.startsWith("--- Day changed")) {
- return {
- type: "day_change"
- };
- } else if (s === "[REDACTED]") {
- return {
- type: "redacted"
- };
- }
-
- const time = s.slice(0, 5);
- switch (s[6]) {
- case '-':
- return {
- time,
- type: 'status',
- content: parseMessage(s.slice(10))
- };
- case '<': {
- const mode = s[7];
- if (mode !== '@' && mode !== '+' && mode !== ' ') throw `invalid mode: ${mode}`;
- const name = s.slice(8).split('>')[0];
- return {
- time,
- type: 'message',
- user: { mode, name },
- content: parseMessage(s.slice(10 + name.length))
- }
- }
- case ' ': {
- const name = s.slice(8).split(' ')[1];
- return {
- time,
- type: 'action',
- user: { name },
- content: parseMessage(s.slice(10 + name.length))
- }
- }
- }
- return {
- time: "error",
- type: 'status',
- content: [{
- type: 'error',
- content: `could not parse message: ${s}`
- }]
- };
- };
-
- const messages = lines.map(l => l.trim()).filter(l => l !== '.' && l !== "").map((v, i) => { try { return parse(v) } catch (e) { throw `${e} ${i}` } });
-
- type MsgGroup = {
- user?: IrcUser,
- type: "status" | "message" | "action" | "day_change" | "redacted",
- messages: IrcMessage[]
- }
-
- function group(messages: IrcMessage[]): MsgGroup[] {
- const groups: MsgGroup[] = [];
-
- let group: IrcMessage[] = [];
- let discrim: IrcUser | undefined = undefined;
- let type: "status" | "action" | "message" | undefined = undefined;
-
- for (const m of messages) {
- if (m.type === "status" || m.type === "day_change" || m.type == "redacted") {
- groups.push({
- user: discrim,
- type: type ?? "message",
- messages: group
- });
-
- discrim = undefined;
- type = "status";
- group = [];
- groups.push({
- type: m.type,
- messages: [m]
- })
- continue;
- } else if (discrim !== undefined && (m.user.mode === undefined || m.user.mode === discrim.mode) && m.user.name === discrim.name) {
- group.push(m)
- } else {
- if (group.length !== 0)
- groups.push({
- user: discrim,
- type: type ?? "message",
- messages: group
- });
- group = [m];
- discrim = m.user;
- type = m.type;
- }
- }
-
- if (group.length !== 0) {
- if (type === "status") {
- groups.push({
- user: undefined,
- type,
- messages: group
- })
- } else if (discrim !== undefined) {
- groups.push({
- user: discrim,
- type: type ?? "message",
- messages: group
- });
- }
- }
-
- return groups;
- }
-
- const groups = group(messages);
-
- const render = (c: MessageFrag): JSX.Element => {
- if (c.type === "text") {
- return JSX.createTextNode(escapeHtmlShitty(c.content));
- } else if (c.type === "url") {
- return <a href={c.link}>{escapeHtmlShitty(c.link)}</a>;
- } else if (c.type === "error") {
- return <span style="color: red; font-family: monospace">{escapeHtmlShitty(c.content)}</span>;
- } else {
- throw "impossible!" // thanks TypeScript
- }
- }
-
- const Message = ({ message, inclTs }: { message: IrcMessage, inclTs?: boolean }) => {
- if (message.type === "day_change") {
- return <div class="mg-sep">
- <span>Today</span>
- </div>
- };
-
- if (message.type === "redacted") {
- return <div class="mg-redacted">
- [REDACTED]
- </div>;
- }
-
- return <div class={`mg-m mg-${message.type}`}>
- {
- (inclTs === undefined || inclTs === true) ? <span class="mg-ts"> {message.time} </span> : undefined
- }
- <span class={`mg-txt mg-${message.type}`}>
- { message.content.map(c => render(c)) }
- </span>
- </div>
- }
-
- function renderMessageGroup(m: MsgGroup): HtmlEnt {
- const u = m.user;
- const m0 = m.messages[0];
-
- if (u !== undefined) {
- return <div class="mg-user">
- <div class="mg-pfp">
- <img src={`https://offtopia.org/pages/people/${u.name.toLocaleLowerCase()}.jpg`} alt={`${u.name}'s profile picture`} />
- </div>
- <div class="mg-contents">
- <span class="mg-ts-u">
- <span class="mg-ts-u-u"> {m.user?.name} </span>
- <span class="mg-ts-u-ts"> {(m0.type === "day_change" || m0.type === "redacted") ? "what" : m0.time} </span>
- </span>
-
- <Message message={m.messages[0]} inclTs={false} />
-
- {
- m.messages.slice(1).map(x =>
- <Message message={x} />
- )
- }
- </div>
- </div>
- } else {
- if (m.messages.length < 1) {
- return <hr style="display: none;" />
- }
-
- return <div class="mg-no-user">
- {m.messages.map(x => <Message message={x} />)}
- </div>;
- }
- }
-
- const rendered = groups.map(renderMessageGroup);
-
- const elem = <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
- </head>
- <body>
- {rendered}
- <svg id="squircle-container" width="0" height="0">
- <clipPath id="squircle" clipPathUnits="objectBoundingBox">
- <path fill="red" stroke="none" d="M 0,0.5 C 0,0 0,0 0.5,0 S 1,0 1,0.5 1,1 0.5,1 0,1 0,0.5" />
- </clipPath>
- </svg>
- </body>
- <style>
- {readFileSync("style.css")}
- </style>
-
- </html>;
-
- console.log(elem.toHtmlStr());
|