converts irc logs to html files that look like discord
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
4.5 KiB

2 years ago
  1. import { escapeHtmlShitty, HtmlEnt, JSX } from './jsx';
  2. const { readFileSync } = require("fs");
  3. const contents = readFileSync(0);
  4. const lines: string[] = contents.toString().split('\n');
  5. type IrcUser = {
  6. mode?: '+' | '@' | ' ',
  7. name: string
  8. };
  9. type IrcMessage = {
  10. time: string,
  11. type: 'status',
  12. content: string
  13. } | {
  14. time: string,
  15. type: 'action',
  16. user: IrcUser,
  17. content: string
  18. } | {
  19. time: string,
  20. type: 'message',
  21. user: IrcUser,
  22. content: string
  23. };
  24. function parse(s: string): IrcMessage {
  25. s = s.trimEnd();
  26. const time = s.slice(0, 5);
  27. switch(s[6]) {
  28. case '-':
  29. return {
  30. time,
  31. type: 'status',
  32. content: s.slice(10)
  33. };
  34. case '<': {
  35. const mode = s[7];
  36. if (mode !== '@' && mode !== '+' && mode !== ' ') throw `invalid mode: ${mode}`;
  37. const name = s.slice(8).split('>')[0];
  38. return {
  39. time,
  40. type: 'message',
  41. user: { mode, name },
  42. content: s.slice(10 + name.length)
  43. }
  44. }
  45. case ' ': {
  46. const name = s.slice(8).split(' ')[1];
  47. return {
  48. time,
  49. type: 'action',
  50. user: { name },
  51. content: s.slice(10 + name.length)
  52. }
  53. }
  54. }
  55. throw `couldn't parse message ${s}`;
  56. };
  57. const messages = lines.map(l => l.trim()).filter(l => l !== '.' && l !== "").map((v, i) => { try { return parse(v) } catch(e) { throw `${e} ${i}` } });
  58. type MsgGroup = {
  59. user?: IrcUser,
  60. type: "status" | "message" | "action",
  61. messages: IrcMessage[]
  62. }
  63. function group(messages: IrcMessage[]): MsgGroup[] {
  64. const groups: MsgGroup[] = [];
  65. let group: IrcMessage[] = [];
  66. let discrim: IrcUser | undefined = undefined;
  67. let type: "status" | "action" | "message" | undefined = undefined;
  68. for (const m of messages) {
  69. if (m.type === "status") {
  70. groups.push({
  71. user: discrim,
  72. type: type ?? "message",
  73. messages: group
  74. });
  75. discrim = undefined;
  76. type = "status";
  77. group = [];
  78. groups.push({
  79. type: m.type,
  80. messages: [m]
  81. })
  82. continue;
  83. } else if (discrim !== undefined && (m.user.mode === undefined || m.user.mode === discrim.mode) && m.user.name === discrim.name) {
  84. group.push(m)
  85. } else {
  86. if (group.length !== 0)
  87. groups.push({
  88. user: discrim,
  89. type: type ?? "message",
  90. messages: group
  91. });
  92. group = [m];
  93. discrim = m.user;
  94. type = m.type;
  95. }
  96. }
  97. if (group.length !== 0) {
  98. if (type === "status") {
  99. groups.push({
  100. user: undefined,
  101. type,
  102. messages: group
  103. })
  104. } else if (discrim !== undefined) {
  105. groups.push({
  106. user: discrim,
  107. type: type ?? "message",
  108. messages: group
  109. });
  110. }
  111. }
  112. return groups;
  113. }
  114. const groups = group(messages);
  115. const Message = ( {message, inclTs }: { message: IrcMessage, inclTs?: boolean }) => {
  116. let txt = escapeHtmlShitty(message.content);
  117. if (message.type === "action") {
  118. txt = "* " + txt;
  119. }
  120. return <div class={ `mg-m mg-${message.type}` }>
  121. {
  122. (inclTs === undefined || inclTs === true) ? <span class="mg-ts"> { message.time } </span> : undefined
  123. }
  124. <span class={ `mg-txt mg-${message.type}` }> { txt } </span>
  125. </div>
  126. }
  127. function renderMessageGroup(m: MsgGroup): HtmlEnt {
  128. const u = m.user;
  129. console.error(m);
  130. if (u !== undefined) {
  131. return <div class="mg-user">
  132. <div class="mg-pfp">
  133. <img src={ `https://offtopia.org/pages/people/${u.name.toLocaleLowerCase()}.jpg` } alt={ `${u.name}'s profile picture`} />
  134. </div>
  135. <div class="mg-contents">
  136. <span class="mg-ts-u">
  137. <span class="mg-ts-u-u"> { m.user?.name } </span>
  138. <span class="mg-ts-u-ts"> { m.messages[0].time } </span>
  139. </span>
  140. <Message message={m.messages[0]} inclTs={false} />
  141. {
  142. m.messages.slice(1).map(x =>
  143. <Message message={x} />
  144. )
  145. }
  146. </div>
  147. </div>
  148. } else {
  149. if (m.messages.length < 1) {
  150. return <span />
  151. }
  152. return <div class="mg-no-user">
  153. { m.messages.map(x => <Message message={x} />) }
  154. </div>;
  155. }
  156. }
  157. const rendered = groups.map(renderMessageGroup);
  158. const elem = <html>
  159. <body>
  160. { rendered }
  161. <svg id="squircle-container" width="0" height="0">
  162. <clipPath id="squircle" clipPathUnits="objectBoundingBox">
  163. <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" />
  164. </clipPath>
  165. </svg>
  166. </body>
  167. <style>
  168. { readFileSync("style.css") }
  169. </style>
  170. </html>;
  171. console.log(elem.toHtmlStr());