@xpadev-net/niconicomments 0.2.55 → 0.2.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- niconicomments.js v0.2.55
2
+ niconicomments.js v0.2.57
3
3
  (c) 2021 xpadev-net https://xpadev.net
4
4
  Released under the MIT License.
5
5
  */
@@ -48,6 +48,11 @@
48
48
  setPlugins: setPlugins
49
49
  });
50
50
 
51
+ let isDebug = false;
52
+ const setIsDebug = (val) => {
53
+ isDebug = val;
54
+ };
55
+
51
56
  let defaultConfig;
52
57
  const updateConfig = (config) => {
53
58
  defaultConfig = config;
@@ -147,7 +152,7 @@
147
152
  };
148
153
 
149
154
  const hex2rgb = (hex) => {
150
- if (hex.slice(0, 1) === "#")
155
+ if (hex.startsWith("#"))
151
156
  hex = hex.slice(1);
152
157
  if (hex.length === 3)
153
158
  hex =
@@ -162,7 +167,7 @@
162
167
  });
163
168
  };
164
169
  const hex2rgba = (hex) => {
165
- if (hex.slice(0, 1) === "#")
170
+ if (hex.startsWith("#"))
166
171
  hex = hex.slice(1);
167
172
  if (hex.length === 4)
168
173
  hex =
@@ -252,6 +257,7 @@
252
257
  "mail",
253
258
  "user_id",
254
259
  "layer",
260
+ "is_my_post",
255
261
  ]),
256
262
  comments: (i) => {
257
263
  if (typeof i !== "object")
@@ -390,27 +396,30 @@
390
396
  },
391
397
  nicoScript: {
392
398
  range: {
393
- target: (i) => typeof i === "string" && !!i.match(/^(?:\u6295?\u30b3\u30e1|\u5168)$/),
399
+ target: (i) => typeof i === "string" &&
400
+ !!RegExp(/^(?:\u6295?\u30b3\u30e1|\u5168)$/).exec(i),
394
401
  },
395
402
  replace: {
396
- range: (i) => typeof i === "string" && !!i.match(/^[\u5358\u5168]$/),
403
+ range: (i) => typeof i === "string" && !!RegExp(/^[\u5358\u5168]$/).exec(i),
397
404
  target: (i) => typeof i === "string" &&
398
- !!i.match(/^(?:\u30b3\u30e1|\u6295\u30b3\u30e1|\u5168|\u542b\u3080|\u542b\u307e\u306a\u3044)$/),
405
+ !!RegExp(/^(?:\u30b3\u30e1|\u6295\u30b3\u30e1|\u5168|\u542b\u3080|\u542b\u307e\u306a\u3044)$/).exec(i),
399
406
  condition: (i) => typeof i === "string" &&
400
- !!i.match(/^(?:\u90e8\u5206\u4e00\u81f4|\u5b8c\u5168\u4e00\u81f4)$/),
407
+ !!RegExp(/^(?:\u90e8\u5206\u4e00\u81f4|\u5b8c\u5168\u4e00\u81f4)$/).exec(i),
401
408
  },
402
409
  },
403
410
  comment: {
404
- font: (i) => typeof i === "string" && !!i.match(/^(?:gothic|mincho|defont)$/),
405
- loc: (i) => typeof i === "string" && !!i.match(/^(?:ue|naka|shita)$/),
406
- size: (i) => typeof i === "string" && !!i.match(/^(?:big|medium|small)$/),
411
+ font: (i) => typeof i === "string" && !!RegExp(/^(?:gothic|mincho|defont)$/).exec(i),
412
+ loc: (i) => typeof i === "string" && !!RegExp(/^(?:ue|naka|shita)$/).exec(i),
413
+ size: (i) => typeof i === "string" && !!RegExp(/^(?:big|medium|small)$/).exec(i),
407
414
  command: {
408
- key: (i) => typeof i === "string" && !!i.match(/^(?:full|ender|_live|invisible)$/),
415
+ key: (i) => typeof i === "string" &&
416
+ !!RegExp(/^(?:full|ender|_live|invisible)$/).exec(i),
409
417
  },
410
418
  color: (i) => typeof i === "string" && Object.keys(colors).includes(i),
411
- colorCode: (i) => typeof i === "string" && !!i.match(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/),
419
+ colorCode: (i) => typeof i === "string" &&
420
+ !!RegExp(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/).exec(i),
412
421
  colorCodeAllowAlpha: (i) => typeof i === "string" &&
413
- !!i.match(/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/),
422
+ !!RegExp(/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/).exec(i),
414
423
  },
415
424
  config: {
416
425
  initOptions: (item) => {
@@ -429,7 +438,7 @@
429
438
  scale: isNumber,
430
439
  config: isObject,
431
440
  format: (i) => typeof i === "string" &&
432
- !!i.match(/^(XMLDocument|niconicome|formatted|legacy|legacyOwner|owner|v1|default|empty)$/),
441
+ !!RegExp(/^(XMLDocument|niconicome|formatted|legacy|legacyOwner|owner|v1|default|empty)$/).exec(i),
433
442
  video: (i) => typeof i === "object" && i.nodeName === "VIDEO",
434
443
  };
435
444
  for (const key of Object.keys(keys)) {
@@ -442,6 +451,20 @@
442
451
  return true;
443
452
  },
444
453
  },
454
+ internal: {
455
+ CommentMeasuredContentItem: (i) => objectVerify(i, ["content", "slicedContent", "width"]),
456
+ CommentMeasuredContentItemArray: (i) => Array.isArray(i) &&
457
+ i.every(typeGuard.internal.CommentMeasuredContentItem),
458
+ MultiConfigItem: (i) => typeof i === "object" && objectVerify(i, ["html5", "flash"]),
459
+ HTML5Fonts: (i) => i === "defont" || i === "mincho" || i === "gothic",
460
+ MeasureInput: (i) => objectVerify(i, [
461
+ "font",
462
+ "content",
463
+ "lineHeight",
464
+ "charSize",
465
+ "lineCount",
466
+ ]),
467
+ },
445
468
  };
446
469
  const objectVerify = (item, keys) => {
447
470
  if (typeof item !== "object" || !item)
@@ -468,13 +491,10 @@
468
491
  });
469
492
 
470
493
  const getConfig = (input, isFlash = false) => {
471
- if (Object.prototype.hasOwnProperty.call(input, "html5") &&
472
- Object.prototype.hasOwnProperty.call(input, "flash")) {
494
+ if (typeGuard.internal.MultiConfigItem(input)) {
473
495
  return input[isFlash ? "flash" : "html5"];
474
496
  }
475
- else {
476
- return input;
477
- }
497
+ return input;
478
498
  };
479
499
 
480
500
  const isLineBreakResize = (comment) => {
@@ -535,12 +555,12 @@
535
555
  processNicoscript(comment, commands);
536
556
  const defaultCommand = getDefaultCommand(comment.vpos);
537
557
  applyNicoScriptReplace(comment, commands);
538
- const size = commands.size || defaultCommand.size || "medium";
558
+ const size = commands.size ?? defaultCommand.size ?? "medium";
539
559
  return {
540
560
  size: size,
541
- loc: commands.loc || defaultCommand.loc || "naka",
542
- color: commands.color || defaultCommand.color || "#FFFFFF",
543
- font: commands.font || defaultCommand.font || "defont",
561
+ loc: commands.loc ?? defaultCommand.loc ?? "naka",
562
+ color: commands.color ?? defaultCommand.color ?? "#FFFFFF",
563
+ font: commands.font ?? defaultCommand.font ?? "defont",
544
564
  fontSize: getConfig(config.fontSize, isFlash)[size].default,
545
565
  long: commands.long ? Math.floor(Number(commands.long) * 100) : 300,
546
566
  flash: isFlash,
@@ -551,26 +571,27 @@
551
571
  strokeColor: commands.strokeColor,
552
572
  wakuColor: commands.wakuColor,
553
573
  fillColor: commands.fillColor,
574
+ button: commands.button,
554
575
  };
555
576
  };
556
577
  const parseBrackets = (input) => {
557
578
  const content = input.split(""), result = [];
558
579
  let quote = "", last_i = "", string = "";
559
580
  for (const i of content) {
560
- if (i.match(/["'\u300c]/) && quote === "") {
581
+ if (RegExp(/^["'\u300c]$/).exec(i) && quote === "") {
561
582
  quote = i;
562
583
  }
563
- else if (i.match(/["']/) && quote === i && last_i !== "\\") {
584
+ else if (RegExp(/^["']$/).exec(i) && quote === i && last_i !== "\\") {
564
585
  result.push(string.replaceAll("\\n", "\n"));
565
586
  quote = "";
566
587
  string = "";
567
588
  }
568
- else if (i.match(/\u300d/) && quote === "\u300c") {
589
+ else if (i === "\u300d" && quote === "\u300c") {
569
590
  result.push(string);
570
591
  quote = "";
571
592
  string = "";
572
593
  }
573
- else if (quote === "" && i.match(/\s+/)) {
594
+ else if (quote === "" && RegExp(/^\s+$/).exec(i)) {
574
595
  if (string) {
575
596
  result.push(string);
576
597
  string = "";
@@ -598,10 +619,10 @@
598
619
  start: comment.vpos,
599
620
  long: commands.long === undefined ? undefined : Math.floor(commands.long * 100),
600
621
  keyword: result[0],
601
- replace: result[1] || "",
602
- range: result[2] || "\u5358",
603
- target: result[3] || "\u30b3\u30e1",
604
- condition: result[4] || "\u90e8\u5206\u4e00\u81f4",
622
+ replace: result[1] ?? "",
623
+ range: result[2] ?? "\u5358",
624
+ target: result[3] ?? "\u30b3\u30e1",
625
+ condition: result[4] ?? "\u90e8\u5206\u4e00\u81f4",
605
626
  color: commands.color,
606
627
  size: commands.size,
607
628
  font: commands.font,
@@ -624,8 +645,14 @@
624
645
  });
625
646
  };
626
647
  const processNicoscript = (comment, commands) => {
627
- const nicoscript = comment.content.match(/^[@\uff20](\u30c7\u30d5\u30a9\u30eb\u30c8|\u7f6e\u63db|\u9006|\u30b3\u30e1\u30f3\u30c8\u7981\u6b62|\u30b7\u30fc\u30af\u7981\u6b62|\u30b8\u30e3\u30f3\u30d7)(.*)/);
628
- if (!nicoscript || !comment.owner)
648
+ const nicoscript = RegExp(/^[@\uff20](\u30c7\u30d5\u30a9\u30eb\u30c8|\u7f6e\u63db|\u9006|\u30b3\u30e1\u30f3\u30c8\u7981\u6b62|\u30b7\u30fc\u30af\u7981\u6b62|\u30b8\u30e3\u30f3\u30d7|\u30dc\u30bf\u30f3)(?:\s(.+))?/).exec(comment.content);
649
+ if (!nicoscript)
650
+ return;
651
+ if (nicoscript[1] === "\u30dc\u30bf\u30f3" && nicoscript[2]) {
652
+ processAtButton(comment, commands);
653
+ return;
654
+ }
655
+ if (!comment.owner)
629
656
  return;
630
657
  commands.invisible = true;
631
658
  if (nicoscript[1] === "\u30c7\u30d5\u30a9\u30eb\u30c8") {
@@ -663,8 +690,8 @@
663
690
  });
664
691
  };
665
692
  const processReverseScript = (comment, commands) => {
666
- const reverse = comment.content.match(/^[@\uff20]\u9006(?:\s+)?(\u5168|\u30b3\u30e1|\u6295\u30b3\u30e1)?/);
667
- if (!reverse || !reverse[1] || !typeGuard.nicoScript.range.target(reverse[1]))
693
+ const reverse = RegExp(/^[@\uff20]\u9006(?:\s+)?(\u5168|\u30b3\u30e1|\u6295\u30b3\u30e1)?/).exec(comment.content);
694
+ if (!reverse?.[1] || !typeGuard.nicoScript.range.target(reverse[1]))
668
695
  return;
669
696
  if (commands.long === undefined) {
670
697
  commands.long = 30;
@@ -694,8 +721,8 @@
694
721
  });
695
722
  };
696
723
  const processJumpScript$1 = (comment, commands, input) => {
697
- const options = input.match(/\s*((?:sm|so|nm|\uff53\uff4d|\uff53\uff4f|\uff4e\uff4d)?[1-9\uff11-\uff19][0-9\uff11-\uff19]*|#[0-9]+:[0-9]+(?:\.[0-9]+)?)\s+(.*)/);
698
- if (!options || !options[1])
724
+ const options = RegExp(/\s*((?:sm|so|nm|\uff53\uff4d|\uff53\uff4f|\uff4e\uff4d)?[1-9\uff11-\uff19][0-9\uff11-\uff19]*|#[0-9]+:[0-9]+(?:\.[0-9]+)?)\s+(.*)/).exec(input);
725
+ if (!options?.[1])
699
726
  return;
700
727
  nicoScripts.jump.unshift({
701
728
  start: comment.vpos,
@@ -704,6 +731,27 @@
704
731
  message: options[2],
705
732
  });
706
733
  };
734
+ const processAtButton = (comment, commands) => {
735
+ const args = parseBrackets(comment.content);
736
+ if (args[1] === undefined)
737
+ return;
738
+ commands.invisible = false;
739
+ const content = RegExp(/^(?:(?<before>.*?)\[)?(?<body>.*?)(?:\](?<after>[^\]]*?))?$/su).exec(args[1]);
740
+ const message = {
741
+ before: content.groups?.before ?? "",
742
+ body: content.groups?.body ?? "",
743
+ after: content.groups?.after ?? "",
744
+ };
745
+ commands.button = {
746
+ message,
747
+ commentMessage: args[2] ?? `${message.before}${message.body}${message.after}`,
748
+ commentVisible: args[3] !== "\u975e\u8868\u793a",
749
+ commentMail: args[4]?.split(",") ?? [],
750
+ limit: Number(args[5] ?? 1),
751
+ local: comment.mail.includes("local"),
752
+ hidden: comment.mail.includes("hidden"),
753
+ };
754
+ };
707
755
  const parseCommands = (comment) => {
708
756
  const commands = comment.mail, isFlash = isFlashComment(comment);
709
757
  const result = {
@@ -730,22 +778,22 @@
730
778
  };
731
779
  const parseCommand = (comment, _command, result, isFlash) => {
732
780
  const command = _command.toLowerCase();
733
- const long = command.match(/^[@\uff20]([0-9.]+)/);
781
+ const long = RegExp(/^[@\uff20]([0-9.]+)/).exec(command);
734
782
  if (long) {
735
783
  result.long = Number(long[1]);
736
784
  return;
737
785
  }
738
- const strokeColor = getColor(command.match(/^nico:stroke:(.+)$/));
786
+ const strokeColor = getColor(RegExp(/^nico:stroke:(.+)$/).exec(command));
739
787
  if (strokeColor) {
740
788
  result.strokeColor ??= strokeColor;
741
789
  return;
742
790
  }
743
- const rectColor = getColor(command.match(/^nico:waku:(.+)$/));
791
+ const rectColor = getColor(RegExp(/^nico:waku:(.+)$/).exec(command));
744
792
  if (rectColor) {
745
793
  result.wakuColor ??= rectColor;
746
794
  return;
747
795
  }
748
- const fillColor = getColor(command.match(/^nico:fill:(.+)$/));
796
+ const fillColor = getColor(RegExp(/^nico:fill:(.+)$/).exec(command));
749
797
  if (fillColor) {
750
798
  result.fillColor ??= fillColor;
751
799
  return;
@@ -763,7 +811,7 @@
763
811
  result.color ??= config.colors[command];
764
812
  return;
765
813
  }
766
- const colorCode = command.match(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/);
814
+ const colorCode = RegExp(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/).exec(command);
767
815
  if (colorCode && comment.premium) {
768
816
  result.color ??= colorCode[0].toUpperCase();
769
817
  return;
@@ -948,7 +996,7 @@
948
996
  const changeCALayer = (rawData) => {
949
997
  const userScoreList = getUsersScore(rawData);
950
998
  const filteredComments = removeDuplicateCommentArt(rawData);
951
- const commentArts = filteredComments.filter((comment) => (userScoreList[comment.user_id] || 0) >= config.sameCAMinScore &&
999
+ const commentArts = filteredComments.filter((comment) => (userScoreList[comment.user_id] ?? 0) >= config.sameCAMinScore &&
952
1000
  !comment.owner);
953
1001
  const commentArtsGroupedByUser = groupCommentsByUser(commentArts);
954
1002
  const commentArtsGroupedByTimes = groupCommentsByTime(commentArtsGroupedByUser);
@@ -967,7 +1015,7 @@
967
1015
  comment.mail.includes("full")) {
968
1016
  userScoreList[comment.user_id] += 5;
969
1017
  }
970
- const lineCount = (comment.content.match(/\r\n|\n|\r/g) || []).length;
1018
+ const lineCount = (comment.content.match(/\r\n|\n|\r/g) ?? []).length;
971
1019
  if (lineCount > 2) {
972
1020
  userScoreList[comment.user_id] += lineCount / 2;
973
1021
  }
@@ -979,7 +1027,7 @@
979
1027
  return comments.filter((comment) => {
980
1028
  const key = `${comment.content}@@${[...comment.mail]
981
1029
  .sort()
982
- .filter((e) => !e.match(/@[\d.]+|184|device:.+|patissier|ca/))
1030
+ .filter((e) => !RegExp(/@[\d.]+|184|device:.+|patissier|ca/).exec(e))
983
1031
  .join("")}`, lastComment = index[key];
984
1032
  if (lastComment === undefined) {
985
1033
  index[key] = comment;
@@ -1091,7 +1139,7 @@
1091
1139
  return index;
1092
1140
  };
1093
1141
  const getFlashFontName = (font) => {
1094
- if (font.match("^simsun.+"))
1142
+ if (font === "simsunStrong" || font === "simsunWeak")
1095
1143
  return "simsun";
1096
1144
  if (font === "gothic")
1097
1145
  return "defont";
@@ -1099,11 +1147,11 @@
1099
1147
  };
1100
1148
  const parseContent = (content) => {
1101
1149
  const results = [];
1102
- const lines = (content.match(/\n|[^\n]+/g) || []).map((val) => Array.from(val.match(/[ -~。-゚]+|[^ -~。-゚]+/g) || []));
1150
+ const lines = Array.from(content.match(/\n|[^\n]+/g) ?? []);
1103
1151
  for (const line of lines) {
1104
1152
  const lineContent = parseLine(line);
1105
1153
  const firstContent = lineContent[0];
1106
- if (firstContent && firstContent.font) {
1154
+ if (firstContent?.font) {
1107
1155
  results.push(...lineContent.map((val) => {
1108
1156
  if (!val.font) {
1109
1157
  val.font = firstContent.font;
@@ -1118,8 +1166,9 @@
1118
1166
  return results;
1119
1167
  };
1120
1168
  const parseLine = (line) => {
1169
+ const parts = Array.from(line.match(/[ -~。-゚]+|[^ -~。-゚]+/g) ?? []);
1121
1170
  const lineContent = [];
1122
- for (const part of line) {
1171
+ for (const part of parts) {
1123
1172
  if (part.match(/[ -~。-゚]+/g) !== null) {
1124
1173
  lineContent.push({ content: part, slicedContent: part.split("\n") });
1125
1174
  continue;
@@ -1200,6 +1249,115 @@
1200
1249
  font: getFlashFontName(secondVal.font),
1201
1250
  });
1202
1251
  };
1252
+ const getButtonParts = (comment) => {
1253
+ let leftParts = undefined;
1254
+ const parts = [];
1255
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
1256
+ const lineOffset = comment.lineOffset;
1257
+ const lineHeight = comment.fontSize * comment.lineHeight;
1258
+ const offsetKey = comment.resizedY ? "resized" : "default";
1259
+ const offsetY = config.commentYPaddingTop[offsetKey] +
1260
+ comment.fontSize *
1261
+ comment.lineHeight *
1262
+ config.commentYOffset[comment.size][offsetKey];
1263
+ let leftOffset = 0, lineCount = 0, isLastButton = false;
1264
+ for (const item of comment.content) {
1265
+ const lines = item.slicedContent;
1266
+ for (let j = 0, n = lines.length; j < n; j++) {
1267
+ const line = lines[j];
1268
+ if (line === undefined)
1269
+ continue;
1270
+ const posY = (lineOffset + lineCount + 1) * lineHeight + offsetY;
1271
+ const partWidth = item.width[j] ?? 0;
1272
+ if (comment.button && !comment.button.hidden) {
1273
+ if (!isLastButton && item.isButton) {
1274
+ leftParts = {
1275
+ type: "left",
1276
+ left: leftOffset + atButtonPadding,
1277
+ top: posY - lineHeight + atButtonPadding,
1278
+ width: partWidth + atButtonPadding,
1279
+ height: lineHeight,
1280
+ };
1281
+ leftOffset += atButtonPadding * 2;
1282
+ }
1283
+ else if (isLastButton && item.isButton) {
1284
+ parts.push({
1285
+ type: "middle",
1286
+ left: leftOffset,
1287
+ top: posY - lineHeight + atButtonPadding,
1288
+ width: partWidth,
1289
+ height: lineHeight,
1290
+ });
1291
+ }
1292
+ else if (isLastButton && !item.isButton) {
1293
+ if (leftParts) {
1294
+ comment.buttonObjects = {
1295
+ left: leftParts,
1296
+ middle: parts,
1297
+ right: {
1298
+ type: "right",
1299
+ right: leftOffset + atButtonPadding,
1300
+ top: posY - lineHeight + atButtonPadding,
1301
+ height: lineHeight,
1302
+ },
1303
+ };
1304
+ }
1305
+ return comment;
1306
+ }
1307
+ }
1308
+ if (j < n - 1) {
1309
+ leftOffset = 0;
1310
+ lineCount += 1;
1311
+ continue;
1312
+ }
1313
+ leftOffset += partWidth;
1314
+ }
1315
+ isLastButton = !!item.isButton;
1316
+ }
1317
+ if (comment.button && !comment.button.hidden && isLastButton && leftParts) {
1318
+ const posY = (lineOffset + lineCount + 1) * lineHeight + offsetY;
1319
+ comment.buttonObjects = {
1320
+ left: leftParts,
1321
+ middle: parts,
1322
+ right: {
1323
+ type: "right",
1324
+ right: leftOffset + atButtonPadding,
1325
+ top: posY - lineHeight + atButtonPadding,
1326
+ height: lineHeight,
1327
+ },
1328
+ };
1329
+ }
1330
+ return comment;
1331
+ };
1332
+ const buildAtButtonComment = (comment, vpos) => {
1333
+ if (!comment.button || comment.button.limit <= 0)
1334
+ return;
1335
+ comment.button.limit -= 1;
1336
+ const mail = [...comment.button.commentMail];
1337
+ if (!comment.button.commentVisible) {
1338
+ mail.push("invisible");
1339
+ }
1340
+ return {
1341
+ id: -1,
1342
+ vpos,
1343
+ content: comment.button.commentMessage,
1344
+ date: -1,
1345
+ date_usec: -1,
1346
+ owner: false,
1347
+ premium: true,
1348
+ mail,
1349
+ user_id: -10,
1350
+ layer: -1,
1351
+ is_my_post: true,
1352
+ };
1353
+ };
1354
+
1355
+ class TypeGuardError extends Error {
1356
+ constructor(options = {}) {
1357
+ super("Type Guard Error\nAn error occurred due to unexpected values\nPlease contact the developer on GitHub", options);
1358
+ }
1359
+ }
1360
+ TypeGuardError.prototype.name = "TypeGuardError";
1203
1361
 
1204
1362
  const getLineHeight = (fontSize, isFlash, resized = false) => {
1205
1363
  const lineCounts = getConfig(config.lineCounts, isFlash), CommentStageSize = getConfig(config.CommentStageSize, isFlash), lineHeight = CommentStageSize.height / lineCounts.doubleResized[fontSize], defaultLineCount = lineCounts.default[fontSize];
@@ -1228,10 +1386,13 @@
1228
1386
  let currentWidth = 0;
1229
1387
  for (const item of comment.content) {
1230
1388
  const lines = item.content.split("\n");
1231
- context.font = parseFont(item.font || comment.font, fontSize);
1389
+ context.font = parseFont(item.font ?? comment.font, fontSize);
1232
1390
  const width = [];
1233
1391
  for (let j = 0, n = lines.length; j < n; j++) {
1234
- const measure = context.measureText(lines[j]);
1392
+ const line = lines[j];
1393
+ if (line === undefined)
1394
+ throw new TypeGuardError();
1395
+ const measure = context.measureText(line);
1235
1396
  currentWidth += measure.width;
1236
1397
  width.push(measure.width);
1237
1398
  if (j < lines.length - 1) {
@@ -1268,7 +1429,9 @@
1268
1429
  __proto__: null,
1269
1430
  ArrayEqual: ArrayEqual,
1270
1431
  ArrayPush: ArrayPush,
1432
+ buildAtButtonComment: buildAtButtonComment,
1271
1433
  changeCALayer: changeCALayer,
1434
+ getButtonParts: getButtonParts,
1272
1435
  getCharSize: getCharSize,
1273
1436
  getConfig: getConfig,
1274
1437
  getDefaultCommand: getDefaultCommand,
@@ -1311,12 +1474,15 @@
1311
1474
  context;
1312
1475
  cacheKey;
1313
1476
  comment;
1477
+ pos;
1314
1478
  posY;
1315
1479
  pluginName = "BaseComment";
1316
1480
  image;
1481
+ buttonImage;
1317
1482
  constructor(comment, context) {
1318
1483
  this.context = context;
1319
1484
  this.posY = 0;
1485
+ this.pos = { x: 0, y: 0 };
1320
1486
  comment.content = comment.content.replace(/\t/g, "\u2003\u2003");
1321
1487
  this.comment = this.convertComment(comment);
1322
1488
  this.cacheKey =
@@ -1380,7 +1546,7 @@
1380
1546
  console.error("convertComment method is not implemented", comment);
1381
1547
  throw new NotImplementedError(this.pluginName, "convertComment");
1382
1548
  }
1383
- draw(vpos, showCollision, debug) {
1549
+ draw(vpos, showCollision, cursor) {
1384
1550
  if (isBanActive(vpos))
1385
1551
  return;
1386
1552
  const reverse = isReverseActive(vpos, this.comment.owner);
@@ -1388,13 +1554,17 @@
1388
1554
  const posY = this.comment.loc === "shita"
1389
1555
  ? config.canvasHeight - this.posY - this.comment.height
1390
1556
  : this.posY;
1557
+ this.pos = {
1558
+ x: posX,
1559
+ y: posY,
1560
+ };
1391
1561
  this._drawBackgroundColor(posX, posY);
1392
- this._draw(posX, posY);
1562
+ this._draw(posX, posY, cursor);
1393
1563
  this._drawRectColor(posX, posY);
1394
1564
  this._drawCollision(posX, posY, showCollision);
1395
- this._drawDebugInfo(posX, posY, debug);
1565
+ this._drawDebugInfo(posX, posY);
1396
1566
  }
1397
- _draw(posX, posY) {
1567
+ _draw(posX, posY, cursor) {
1398
1568
  if (this.image === undefined) {
1399
1569
  this.image = this.getTextImage();
1400
1570
  }
@@ -1406,6 +1576,10 @@
1406
1576
  else {
1407
1577
  this.context.globalAlpha = 1;
1408
1578
  }
1579
+ if (this.comment.button && !this.comment.button.hidden) {
1580
+ const button = this.getButtonImage(posX, posY, cursor);
1581
+ button && drawImage(this.context, button, posX, posY);
1582
+ }
1409
1583
  drawImage(this.context, this.image, posX, posY);
1410
1584
  this.context.restore();
1411
1585
  }
@@ -1426,8 +1600,8 @@
1426
1600
  this.context.restore();
1427
1601
  }
1428
1602
  }
1429
- _drawDebugInfo(posX, posY, debug) {
1430
- if (debug) {
1603
+ _drawDebugInfo(posX, posY) {
1604
+ if (isDebug) {
1431
1605
  this.context.save();
1432
1606
  const font = this.context.font;
1433
1607
  const fillStyle = this.context.fillStyle;
@@ -1491,19 +1665,60 @@
1491
1665
  context,
1492
1666
  };
1493
1667
  }
1668
+ getButtonImage(posX, posY, cursor) {
1669
+ console.error("getButtonImage method is not implemented", posX, posY, cursor);
1670
+ throw new NotImplementedError(this.pluginName, "getButtonImage");
1671
+ }
1672
+ isHovered(cursor, posX, posY) {
1673
+ console.error("isHovered method is not implemented", posX, posY, cursor);
1674
+ throw new NotImplementedError(this.pluginName, "getButtonImage");
1675
+ }
1494
1676
  }
1495
1677
 
1678
+ const drawLeftBorder = (context, left, top, width, height, radius) => {
1679
+ context.save();
1680
+ context.beginPath();
1681
+ context.moveTo(left + width, top);
1682
+ context.lineTo(left + radius, top);
1683
+ context.quadraticCurveTo(left, top, left, top + radius);
1684
+ context.lineTo(left, top + height - radius);
1685
+ context.quadraticCurveTo(left, top + height, left + radius, top + height);
1686
+ context.lineTo(left + width, top + height);
1687
+ context.stroke();
1688
+ context.restore();
1689
+ };
1690
+ const drawMiddleBorder = (context, left, top, width, height) => {
1691
+ context.save();
1692
+ context.beginPath();
1693
+ context.moveTo(left + width, top);
1694
+ context.lineTo(left, top);
1695
+ context.moveTo(left + width, top + height);
1696
+ context.lineTo(left, top + height);
1697
+ context.stroke();
1698
+ context.restore();
1699
+ };
1700
+ const drawRightBorder = (context, right, top, height, radius) => {
1701
+ context.save();
1702
+ context.beginPath();
1703
+ context.moveTo(right - radius, top);
1704
+ context.quadraticCurveTo(right, top, right, top + radius);
1705
+ context.lineTo(right, top + height - radius);
1706
+ context.quadraticCurveTo(right, top + height, right - radius, top + height);
1707
+ context.stroke();
1708
+ context.restore();
1709
+ };
1710
+
1496
1711
  class FlashComment extends BaseComment {
1497
1712
  _globalScale;
1498
- scale;
1499
- scaleX;
1500
1713
  pluginName = "FlashComment";
1714
+ buttonImage;
1715
+ buttonContext;
1501
1716
  constructor(comment, context) {
1502
1717
  super(comment, context);
1503
- this.scale ??= 1;
1504
- this.scaleX ??= 1;
1505
1718
  this._globalScale ??= getConfig(config.commentScale, true);
1506
- this.posY ??= 0;
1719
+ const button = this.createCanvas();
1720
+ this.buttonImage = button.image;
1721
+ this.buttonContext = button.context;
1507
1722
  }
1508
1723
  get content() {
1509
1724
  return this.comment.rawContent;
@@ -1518,7 +1733,7 @@
1518
1733
  lineOffset,
1519
1734
  };
1520
1735
  const val = content[0];
1521
- if (val && val.font) {
1736
+ if (val?.font) {
1522
1737
  comment.font = val.font;
1523
1738
  }
1524
1739
  this.comment = this.getCommentSize(comment);
@@ -1529,50 +1744,57 @@
1529
1744
  delete this.image;
1530
1745
  }
1531
1746
  convertComment(comment) {
1532
- this.scale = 1;
1533
- this.scaleX = 1;
1534
1747
  this._globalScale = getConfig(config.commentScale, true);
1535
- this.posY = 0;
1536
- return this.getCommentSize(this.parseCommandAndNicoscript(comment));
1748
+ return getButtonParts(this.getCommentSize(this.parseCommandAndNicoscript(comment)));
1537
1749
  }
1538
1750
  getCommentSize(parsedData) {
1539
- this.context.save();
1540
- this.context.font = parseFont(parsedData.font, parsedData.fontSize);
1541
- const size = parsedData;
1542
1751
  if (parsedData.invisible) {
1543
- size.height = 0;
1544
- size.width = 0;
1545
- size.lineHeight = 0;
1546
- size.fontSize = 0;
1547
- size.resized = false;
1548
- size.resizedX = false;
1549
- size.resizedY = false;
1550
- size.charSize = 0;
1551
- this.context.restore();
1552
- return size;
1752
+ return {
1753
+ ...parsedData,
1754
+ height: 0,
1755
+ width: 0,
1756
+ lineHeight: 0,
1757
+ fontSize: 0,
1758
+ resized: false,
1759
+ resizedX: false,
1760
+ resizedY: false,
1761
+ charSize: 0,
1762
+ scale: 1,
1763
+ scaleX: 1,
1764
+ content: [],
1765
+ };
1553
1766
  }
1554
- const measure = this.measureText(parsedData);
1555
- if (options.scale !== 1 && size.layer === -1) {
1767
+ this.context.save();
1768
+ this.context.font = parseFont(parsedData.font, parsedData.fontSize);
1769
+ const measure = this.measureText({ ...parsedData, scale: 1 });
1770
+ if (options.scale !== 1 && parsedData.layer === -1) {
1556
1771
  measure.height *= options.scale;
1557
1772
  measure.width *= options.scale;
1558
1773
  }
1559
- size.height = measure.height * this._globalScale;
1560
- size.width = measure.width * this._globalScale;
1561
- size.lineHeight = measure.lineHeight;
1562
- size.fontSize = measure.fontSize;
1563
- size.content = measure.content;
1564
- size.resized = measure.resized;
1565
- size.resizedX = measure.resizedX;
1566
- size.resizedY = measure.resizedY;
1567
- size.charSize = measure.charSize;
1568
1774
  this.context.restore();
1569
- return size;
1775
+ if (parsedData.button && !parsedData.button.hidden) {
1776
+ measure.width += getConfig(config.atButtonPadding, true) * 4;
1777
+ }
1778
+ return {
1779
+ ...parsedData,
1780
+ height: measure.height * this._globalScale,
1781
+ width: measure.width * this._globalScale,
1782
+ lineHeight: measure.lineHeight,
1783
+ fontSize: measure.fontSize,
1784
+ resized: measure.resized,
1785
+ resizedX: measure.resizedX,
1786
+ resizedY: measure.resizedY,
1787
+ charSize: measure.charSize,
1788
+ scale: measure.scale,
1789
+ scaleX: measure.scaleX,
1790
+ content: measure.content,
1791
+ };
1570
1792
  }
1571
1793
  parseCommandAndNicoscript(comment) {
1572
1794
  const data = parseCommandAndNicoScript(comment);
1573
- const { content, lineCount, lineOffset } = this.parseContent(comment.content);
1795
+ const { content, lineCount, lineOffset } = this.parseContent(comment.content, data.button);
1574
1796
  const val = content[0];
1575
- if (val && val.font) {
1797
+ if (val?.font) {
1576
1798
  data.font = val.font;
1577
1799
  }
1578
1800
  return {
@@ -1584,16 +1806,23 @@
1584
1806
  lineOffset,
1585
1807
  };
1586
1808
  }
1587
- parseContent(input) {
1588
- const content = parseContent(input);
1589
- const lineCount = content.reduce((pv, val) => {
1590
- return pv + (val.content.match(/\n/g)?.length || 0);
1591
- }, 1);
1592
- const lineOffset = (input.match(new RegExp(config.FlashScriptChar.super, "g"))?.length ||
1809
+ parseContent(input, button) {
1810
+ const content = button
1811
+ ? [
1812
+ ...parseContent(button.message.before),
1813
+ ...parseContent(button.message.body).map((val) => {
1814
+ val.isButton = true;
1815
+ return val;
1816
+ }),
1817
+ ...parseContent(button.message.after),
1818
+ ]
1819
+ : parseContent(input);
1820
+ const lineCount = (input.match(/\n/g)?.length ?? 0) + 1;
1821
+ const lineOffset = (input.match(new RegExp(config.FlashScriptChar.super, "g"))?.length ??
1593
1822
  0) *
1594
1823
  -1 *
1595
1824
  config.scriptCharOffset +
1596
- (input.match(new RegExp(config.FlashScriptChar.sub, "g"))?.length || 0) *
1825
+ (input.match(new RegExp(config.FlashScriptChar.sub, "g"))?.length ?? 0) *
1597
1826
  config.scriptCharOffset;
1598
1827
  return {
1599
1828
  content,
@@ -1615,32 +1844,34 @@
1615
1844
  const { width_arr, spacedWidth_arr } = this._measureContent(comment);
1616
1845
  const leadLine = (function () {
1617
1846
  let max = 0, index = -1;
1618
- for (let i = 0, l = spacedWidth_arr.length; i < l; i++) {
1619
- const val = spacedWidth_arr[i];
1620
- if (val && max < val) {
1847
+ spacedWidth_arr.forEach((val, i) => {
1848
+ if (max < val) {
1621
1849
  max = val;
1622
1850
  index = i;
1623
1851
  }
1624
- }
1852
+ });
1625
1853
  return { max, index };
1626
1854
  })();
1627
1855
  const width = leadLine.max;
1628
- this.scaleX = leadLine.max / (width_arr[leadLine.index] || 1);
1629
- const width_max = width * this.scale;
1856
+ const scaleX = leadLine.max / (width_arr[leadLine.index] ?? 1);
1857
+ const width_max = width * comment.scale;
1630
1858
  const height = (comment.fontSize * comment.lineHeight * lineCount +
1631
1859
  config.commentYPaddingTop[comment.resizedY ? "resized" : "default"]) *
1632
- this.scale;
1860
+ comment.scale;
1633
1861
  if (comment.loc !== "naka") {
1634
1862
  const widthLimit = getConfig(config.CommentStageSize, true)[comment.full ? "fullWidth" : "width"];
1635
1863
  if (width_max > widthLimit && !comment.resizedX) {
1636
1864
  comment.fontSize = configFontSize[comment.size].default;
1637
1865
  comment.lineHeight = configLineHeight[comment.size].default;
1638
- this.scale = widthLimit / width_max;
1866
+ comment.scale = widthLimit / width_max;
1639
1867
  comment.resizedX = true;
1640
1868
  comment.resized = true;
1641
1869
  return this.measureText(comment);
1642
1870
  }
1643
1871
  }
1872
+ if (!typeGuard.internal.CommentMeasuredContentItemArray(comment.content)) {
1873
+ throw new TypeGuardError();
1874
+ }
1644
1875
  return {
1645
1876
  width: width_max,
1646
1877
  charSize: 0,
@@ -1651,6 +1882,8 @@
1651
1882
  content: comment.content,
1652
1883
  resizedX: !!comment.resizedX,
1653
1884
  resizedY: !!comment.resizedY,
1885
+ scale: comment.scale,
1886
+ scaleX,
1654
1887
  };
1655
1888
  }
1656
1889
  _measureContent(comment) {
@@ -1659,7 +1892,7 @@
1659
1892
  for (const item of comment.content) {
1660
1893
  const lines = item.content.split("\n");
1661
1894
  const widths = [];
1662
- this.context.font = parseFont(item.font || comment.font, comment.fontSize);
1895
+ this.context.font = parseFont(item.font ?? comment.font, comment.fontSize);
1663
1896
  for (let i = 0, n = lines.length; i < n; i++) {
1664
1897
  const value = lines[i];
1665
1898
  if (value === undefined)
@@ -1690,13 +1923,13 @@
1690
1923
  for (let i = 0, n = this.comment.lineCount; i < n; i++) {
1691
1924
  const linePosY = ((i + 1) * (this.comment.fontSize * this.comment.lineHeight) +
1692
1925
  config.commentYPaddingTop[this.comment.resizedY ? "resized" : "default"]) *
1693
- this.scale;
1926
+ this.comment.scale;
1694
1927
  this.context.strokeStyle = `rgba(255,255,0,0.25)`;
1695
1928
  this.context.strokeRect(posX, posY + linePosY * this._globalScale, this.comment.width, this.comment.fontSize *
1696
1929
  this.comment.lineHeight *
1697
1930
  -1 *
1698
1931
  this._globalScale *
1699
- this.scale *
1932
+ this.comment.scale *
1700
1933
  (this.comment.layer === -1 ? options.scale : 1));
1701
1934
  }
1702
1935
  this.context.restore();
@@ -1704,27 +1937,19 @@
1704
1937
  }
1705
1938
  _generateTextImage() {
1706
1939
  const { image, context } = this.createCanvas();
1707
- image.width = this.comment.width;
1708
- image.height = this.comment.height;
1709
- context.strokeStyle = getStrokeColor(this.comment);
1710
- context.fillStyle = this.comment.color;
1711
- context.textAlign = "start";
1712
- context.textBaseline = "alphabetic";
1713
- context.lineWidth = 4;
1714
- context.font = parseFont(this.comment.font, this.comment.fontSize);
1715
- const scale = this._globalScale *
1716
- this.scale *
1717
- (this.comment.layer === -1 ? options.scale : 1);
1718
- context.scale(scale * this.scaleX, scale);
1940
+ this._setupCanvas(image, context);
1941
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
1719
1942
  const lineOffset = this.comment.lineOffset;
1943
+ const lineHeight = this.comment.fontSize * this.comment.lineHeight;
1720
1944
  const offsetKey = this.comment.resizedY ? "resized" : "default";
1721
1945
  const offsetY = config.commentYPaddingTop[offsetKey] +
1722
1946
  this.comment.fontSize *
1723
1947
  this.comment.lineHeight *
1724
1948
  config.commentYOffset[this.comment.size][offsetKey];
1725
- let lastFont = this.comment.font, leftOffset = 0, lineCount = 0;
1949
+ let lastFont = this.comment.font, leftOffset = 0, lineCount = 0, isLastButton = false;
1950
+ console.log(this.comment);
1726
1951
  for (const item of this.comment.content) {
1727
- const font = item.font || this.comment.font;
1952
+ const font = item.font ?? this.comment.font;
1728
1953
  if (lastFont !== font) {
1729
1954
  lastFont = font;
1730
1955
  context.font = parseFont(font, this.comment.fontSize);
@@ -1734,9 +1959,16 @@
1734
1959
  const line = lines[j];
1735
1960
  if (line === undefined)
1736
1961
  continue;
1737
- const posY = (lineOffset + lineCount + 1) *
1738
- (this.comment.fontSize * this.comment.lineHeight) +
1739
- offsetY;
1962
+ const posY = (lineOffset + lineCount + 1) * lineHeight + offsetY;
1963
+ const partWidth = item.width[j] ?? 0;
1964
+ if (this.comment.button && !this.comment.button.hidden) {
1965
+ if (!isLastButton && item.isButton) {
1966
+ leftOffset += atButtonPadding * 2;
1967
+ }
1968
+ else if (isLastButton && !item.isButton) {
1969
+ leftOffset += atButtonPadding * 2;
1970
+ }
1971
+ }
1740
1972
  context.strokeText(line, leftOffset, posY);
1741
1973
  context.fillText(line, leftOffset, posY);
1742
1974
  if (j < n - 1) {
@@ -1744,11 +1976,79 @@
1744
1976
  lineCount += 1;
1745
1977
  continue;
1746
1978
  }
1747
- leftOffset += item.width[j] || 0;
1979
+ leftOffset += partWidth;
1748
1980
  }
1981
+ isLastButton = !!item.isButton;
1749
1982
  }
1750
1983
  return image;
1751
1984
  }
1985
+ getButtonImage(posX, posY, cursor) {
1986
+ if (!this.comment.button || this.comment.button.hidden)
1987
+ return undefined;
1988
+ const { image, context } = this._setupCanvas(this.buttonImage, this.buttonContext);
1989
+ const parts = this.comment.buttonObjects;
1990
+ if (!parts)
1991
+ return undefined;
1992
+ const atButtonRadius = getConfig(config.atButtonRadius, true);
1993
+ const isHover = this.isHovered(cursor, posX, posY);
1994
+ context.save();
1995
+ context.strokeStyle = isHover ? this.comment.color : "white";
1996
+ drawLeftBorder(context, parts.left.left, parts.left.top, parts.left.width, parts.left.height, atButtonRadius);
1997
+ for (const part of parts.middle) {
1998
+ drawMiddleBorder(context, part.left, part.top, part.width, part.height);
1999
+ }
2000
+ drawRightBorder(context, parts.right.right, parts.right.top, parts.right.height, atButtonRadius);
2001
+ context.restore();
2002
+ return image;
2003
+ }
2004
+ isHovered(_cursor, _posX, _posY) {
2005
+ if (!_cursor || !this.comment.buttonObjects)
2006
+ return false;
2007
+ const { left, middle, right } = this.comment.buttonObjects;
2008
+ const scale = this._globalScale *
2009
+ this.comment.scale *
2010
+ (this.comment.layer === -1 ? options.scale : 1);
2011
+ const posX = (_posX ?? this.pos.x) / scale;
2012
+ const posY = (_posY ?? this.pos.y) / scale;
2013
+ const cursor = {
2014
+ x: _cursor.x / scale,
2015
+ y: _cursor.y / scale,
2016
+ };
2017
+ if (cursor.x < posX ||
2018
+ posX + this.comment.width < cursor.x ||
2019
+ cursor.y < posY + left.top ||
2020
+ posY + right.top + right.height < cursor.y) {
2021
+ return false;
2022
+ }
2023
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
2024
+ const between = (val, min, max) => {
2025
+ return min < val && val < max;
2026
+ };
2027
+ for (const part of [left, ...middle]) {
2028
+ if (between(cursor.x, posX + part.left, posX + part.left + part.width) &&
2029
+ between(cursor.y, posY + part.top, posY + part.top + part.height)) {
2030
+ return true;
2031
+ }
2032
+ }
2033
+ return (between(cursor.x, posX + right.right - atButtonPadding, posX + right.right + atButtonPadding * 2) && between(cursor.y, posY + right.top, posY + right.top + right.height));
2034
+ }
2035
+ _setupCanvas(image, context) {
2036
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
2037
+ image.width = this.comment.width;
2038
+ image.height =
2039
+ this.comment.height + (this.comment.button ? atButtonPadding * 2 : 0);
2040
+ context.strokeStyle = getStrokeColor(this.comment);
2041
+ context.fillStyle = this.comment.color;
2042
+ context.textAlign = "start";
2043
+ context.textBaseline = "alphabetic";
2044
+ context.lineWidth = 4;
2045
+ context.font = parseFont(this.comment.font, this.comment.fontSize);
2046
+ const scale = this._globalScale *
2047
+ this.comment.scale *
2048
+ (this.comment.layer === -1 ? options.scale : 1);
2049
+ context.scale(scale * this.comment.scaleX, scale);
2050
+ return { image, context };
2051
+ }
1752
2052
  }
1753
2053
 
1754
2054
  class HTML5Comment extends BaseComment {
@@ -1780,38 +2080,46 @@
1780
2080
  return this.getCommentSize(this.parseCommandAndNicoscript(comment));
1781
2081
  }
1782
2082
  getCommentSize(parsedData) {
1783
- this.context.save();
1784
- this.context.font = parseFont(parsedData.font, parsedData.fontSize);
1785
- const size = parsedData;
1786
2083
  if (parsedData.invisible) {
1787
- size.height = 0;
1788
- size.width = 0;
1789
- size.lineHeight = 0;
1790
- size.fontSize = 0;
1791
- size.resized = false;
1792
- size.resizedX = false;
1793
- size.resizedY = false;
1794
- size.charSize = 0;
1795
2084
  this.context.restore();
1796
- return size;
2085
+ return {
2086
+ ...parsedData,
2087
+ height: 0,
2088
+ width: 0,
2089
+ lineHeight: 0,
2090
+ fontSize: 0,
2091
+ resized: false,
2092
+ resizedX: false,
2093
+ resizedY: false,
2094
+ charSize: 0,
2095
+ content: [],
2096
+ scaleX: 1,
2097
+ scale: 1,
2098
+ };
1797
2099
  }
1798
- const measure = this.measureText(parsedData);
1799
- if (options.scale !== 1 && size.layer === -1) {
2100
+ this.context.save();
2101
+ this.context.font = parseFont(parsedData.font, parsedData.fontSize);
2102
+ const measure = this.measureText({ ...parsedData, scale: 1 });
2103
+ if (options.scale !== 1 && parsedData.layer === -1) {
1800
2104
  measure.height *= options.scale;
1801
2105
  measure.width *= options.scale;
1802
2106
  measure.fontSize *= options.scale;
1803
2107
  }
1804
- size.height = measure.height;
1805
- size.width = measure.width;
1806
- size.lineHeight = measure.lineHeight;
1807
- size.fontSize = measure.fontSize;
1808
- size.content = measure.content;
1809
- size.resized = measure.resized;
1810
- size.resizedX = measure.resizedX;
1811
- size.resizedY = measure.resizedY;
1812
- size.charSize = measure.charSize;
1813
2108
  this.context.restore();
1814
- return size;
2109
+ return {
2110
+ ...parsedData,
2111
+ height: measure.height,
2112
+ width: measure.width,
2113
+ lineHeight: measure.lineHeight,
2114
+ fontSize: measure.fontSize,
2115
+ resized: measure.resized,
2116
+ resizedX: measure.resizedX,
2117
+ resizedY: measure.resizedY,
2118
+ charSize: measure.charSize,
2119
+ content: measure.content,
2120
+ scaleX: measure.scaleX,
2121
+ scale: measure.scale,
2122
+ };
1815
2123
  }
1816
2124
  parseCommandAndNicoscript(comment) {
1817
2125
  const data = parseCommandAndNicoScript(comment);
@@ -1832,7 +2140,7 @@
1832
2140
  slicedContent: input.split("\n"),
1833
2141
  });
1834
2142
  const lineCount = content.reduce((pv, val) => {
1835
- return pv + (val.content.match(/\n/g)?.length || 0);
2143
+ return pv + (val.content.match(/\n/g)?.length ?? 0);
1836
2144
  }, 1);
1837
2145
  const lineOffset = 0;
1838
2146
  return {
@@ -1865,21 +2173,28 @@
1865
2173
  continue;
1866
2174
  item.width = itemWidth[i];
1867
2175
  }
1868
- comment.fontSize = (comment.charSize || 0) * 0.8;
2176
+ comment.fontSize = (comment.charSize ?? 0) * 0.8;
2177
+ if (!typeGuard.internal.CommentMeasuredContentItemArray(comment.content)) {
2178
+ throw new TypeGuardError();
2179
+ }
1869
2180
  return {
1870
2181
  width: width * scale,
1871
2182
  height: height * scale,
1872
2183
  resized: !!comment.resized,
1873
2184
  fontSize: comment.fontSize,
1874
- lineHeight: comment.lineHeight || 0,
2185
+ lineHeight: comment.lineHeight ?? 0,
1875
2186
  content: comment.content,
1876
2187
  resizedX: !!comment.resizedX,
1877
2188
  resizedY: !!comment.resizedY,
1878
- charSize: comment.charSize || 0,
2189
+ charSize: comment.charSize ?? 0,
2190
+ scaleX: 1,
2191
+ scale: 1,
1879
2192
  };
1880
2193
  }
1881
2194
  _measureComment(comment) {
1882
2195
  const widthLimit = getConfig(config.CommentStageSize, false)[comment.full ? "fullWidth" : "width"];
2196
+ if (!typeGuard.internal.MeasureInput(comment))
2197
+ throw new TypeGuardError();
1883
2198
  const measureResult = measure(comment, this.context);
1884
2199
  if (comment.loc !== "naka" && measureResult.width > widthLimit) {
1885
2200
  return this._processResizeX(comment, measureResult.width);
@@ -1893,9 +2208,11 @@
1893
2208
  const scale = widthLimit / width;
1894
2209
  comment.resizedX = true;
1895
2210
  let _comment = { ...comment };
1896
- _comment.charSize = (_comment.charSize || 0) * scale;
1897
- _comment.lineHeight = (_comment.lineHeight || 0) * scale;
2211
+ _comment.charSize = (_comment.charSize ?? 0) * scale;
2212
+ _comment.lineHeight = (_comment.lineHeight ?? 0) * scale;
1898
2213
  _comment.fontSize = _comment.charSize * 0.8;
2214
+ if (!typeGuard.internal.MeasureInput(_comment))
2215
+ throw new TypeGuardError();
1899
2216
  let result = measure(_comment, this.context);
1900
2217
  if (result.width > widthLimit) {
1901
2218
  while (result.width >= widthLimit) {
@@ -1919,7 +2236,7 @@
1919
2236
  _comment = lastComment;
1920
2237
  }
1921
2238
  if (comment.resizedY) {
1922
- const scale = (_comment.charSize || 0) / (comment.charSize || 0);
2239
+ const scale = (_comment.charSize ?? 0) / (comment.charSize ?? 0);
1923
2240
  comment.charSize = scale * charSize;
1924
2241
  comment.lineHeight = scale * lineHeight;
1925
2242
  }
@@ -1927,7 +2244,9 @@
1927
2244
  comment.charSize = _comment.charSize;
1928
2245
  comment.lineHeight = _comment.lineHeight;
1929
2246
  }
1930
- comment.fontSize = (comment.charSize || 0) * 0.8;
2247
+ comment.fontSize = (comment.charSize ?? 0) * 0.8;
2248
+ if (!typeGuard.internal.MeasureInput(comment))
2249
+ throw new TypeGuardError();
1931
2250
  return measure(comment, this.context);
1932
2251
  }
1933
2252
  _drawCollision(posX, posY, showCollision) {
@@ -1937,11 +2256,12 @@
1937
2256
  this.context.strokeStyle = "rgba(0,255,255,1)";
1938
2257
  this.context.strokeRect(posX, posY, this.comment.width, this.comment.height);
1939
2258
  for (let i = 0, n = this.comment.lineCount; i < n; i++) {
2259
+ if (!typeGuard.internal.HTML5Fonts(this.comment.font))
2260
+ throw new TypeGuardError();
1940
2261
  const linePosY = (this.comment.lineHeight * (i + 1) +
1941
2262
  (this.comment.charSize - this.comment.lineHeight) / 2 +
1942
2263
  this.comment.lineHeight * -0.16 +
1943
- (config.fonts[this.comment.font]?.offset ||
1944
- 0)) *
2264
+ (config.fonts[this.comment.font]?.offset || 0)) *
1945
2265
  scale;
1946
2266
  this.context.strokeStyle = "rgba(255,255,0,0.5)";
1947
2267
  this.context.strokeRect(posX, posY + linePosY, this.comment.width, this.comment.fontSize * -1 * scale);
@@ -1969,6 +2289,8 @@
1969
2289
  (this.comment.layer === -1 ? options.scale : 1);
1970
2290
  context.scale(drawScale, drawScale);
1971
2291
  let lineCount = 0;
2292
+ if (!typeGuard.internal.HTML5Fonts(this.comment.font))
2293
+ throw new TypeGuardError();
1972
2294
  const offsetY = (this.comment.charSize - this.comment.lineHeight) / 2 +
1973
2295
  this.comment.lineHeight * -0.16 +
1974
2296
  (config.fonts[this.comment.font]?.offset || 0);
@@ -1987,6 +2309,12 @@
1987
2309
  }
1988
2310
  return image;
1989
2311
  }
2312
+ getButtonImage() {
2313
+ return undefined;
2314
+ }
2315
+ isHovered() {
2316
+ return false;
2317
+ }
1990
2318
  }
1991
2319
 
1992
2320
  var index = /*#__PURE__*/Object.freeze({
@@ -2142,17 +2470,17 @@
2142
2470
 
2143
2471
  const initConfig = () => {
2144
2472
  const platform = (function (ua) {
2145
- if (ua.match(/windows nt 6\.[12]/i))
2473
+ if (RegExp(/windows nt 6\.[12]/i).exec(ua))
2146
2474
  return "win7";
2147
- else if (ua.match(/windows nt (6\.3|10\.\d+)|win32/i))
2475
+ else if (RegExp(/windows nt (6\.3|10\.\d+)|win32/i).exec(ua))
2148
2476
  return "win8_1";
2149
- else if (ua.match(/windows nt/i))
2477
+ else if (RegExp(/windows nt/i).exec(ua))
2150
2478
  return "win";
2151
- else if (ua.match(/mac os x 10(.|_)(9|10)/i))
2479
+ else if (RegExp(/mac os x 10(.|_)(9|10)/i).exec(ua))
2152
2480
  return "mac10_9";
2153
- else if (ua.match(/mac os x 10(.|_)\d{2}|darwin/i))
2481
+ else if (RegExp(/mac os x 10(.|_)\d{2}|darwin/i).exec(ua))
2154
2482
  return "mac10_11";
2155
- else if (ua.match(/mac os x/i))
2483
+ else if (RegExp(/mac os x/i).exec(ua))
2156
2484
  return "mac";
2157
2485
  return "other";
2158
2486
  })(typeof navigator !== "undefined" ? navigator.userAgent : process.platform);
@@ -2308,6 +2636,8 @@
2308
2636
  },
2309
2637
  ],
2310
2638
  nakaCommentSpeedOffset: 0.95,
2639
+ atButtonPadding: 5,
2640
+ atButtonRadius: 7,
2311
2641
  };
2312
2642
  updateConfig(defaultConfig);
2313
2643
  };
@@ -2463,14 +2793,15 @@
2463
2793
  mail: [],
2464
2794
  user_id: -1,
2465
2795
  layer: -1,
2796
+ is_my_post: false,
2466
2797
  };
2467
2798
  if (item.getAttribute("mail")) {
2468
- tmpParam.mail = item.getAttribute("mail")?.split(/\s+/g) || [];
2799
+ tmpParam.mail = item.getAttribute("mail")?.split(/\s+/g) ?? [];
2469
2800
  }
2470
2801
  if (tmpParam.content.startsWith("/") && tmpParam.owner) {
2471
2802
  tmpParam.mail.push("invisible");
2472
2803
  }
2473
- const userId = item.getAttribute("user_id") || "";
2804
+ const userId = item.getAttribute("user_id") ?? "";
2474
2805
  const isUserExist = userList.indexOf(userId);
2475
2806
  if (isUserExist === -1) {
2476
2807
  tmpParam.user_id = userList.length;
@@ -2484,16 +2815,17 @@
2484
2815
  return data_;
2485
2816
  };
2486
2817
  const fromFormatted = (data) => {
2487
- const tmpData = data;
2488
- if (!typeGuard.formatted.comments(data)) {
2489
- for (const item of tmpData) {
2490
- item.layer = -1;
2491
- item.user_id = 0;
2492
- if (!item.date_usec)
2493
- item.date_usec = 0;
2818
+ return data.map((comment) => {
2819
+ if (!typeGuard.formatted.comment(comment)) {
2820
+ return {
2821
+ ...comment,
2822
+ layer: -1,
2823
+ user_id: 0,
2824
+ is_my_post: false,
2825
+ };
2494
2826
  }
2495
- }
2496
- return tmpData;
2827
+ return comment;
2828
+ });
2497
2829
  };
2498
2830
  const fromLegacy = (data) => {
2499
2831
  const data_ = [], userList = [];
@@ -2513,6 +2845,7 @@
2513
2845
  mail: [],
2514
2846
  user_id: -1,
2515
2847
  layer: -1,
2848
+ is_my_post: false,
2516
2849
  };
2517
2850
  if (value.mail) {
2518
2851
  tmpParam.mail = value.mail.split(/\s+/g);
@@ -2551,7 +2884,7 @@
2551
2884
  const tmpParam = {
2552
2885
  id: i,
2553
2886
  vpos: Number(commentData[0]) * 100,
2554
- content: commentData[2] || "",
2887
+ content: commentData[2] ?? "",
2555
2888
  date: i,
2556
2889
  date_usec: 0,
2557
2890
  owner: true,
@@ -2559,6 +2892,7 @@
2559
2892
  mail: [],
2560
2893
  user_id: -1,
2561
2894
  layer: -1,
2895
+ is_my_post: false,
2562
2896
  };
2563
2897
  if (commentData[1]) {
2564
2898
  tmpParam.mail = commentData[1].split(/[\s+]/g);
@@ -2587,6 +2921,7 @@
2587
2921
  mail: [],
2588
2922
  user_id: -1,
2589
2923
  layer: -1,
2924
+ is_my_post: false,
2590
2925
  };
2591
2926
  if (value.command) {
2592
2927
  tmpParam.mail = value.command.split(/\s+/g);
@@ -2614,6 +2949,7 @@
2614
2949
  mail: value.commands,
2615
2950
  user_id: -1,
2616
2951
  layer: -1,
2952
+ is_my_post: value.isMyPost,
2617
2953
  };
2618
2954
  if (tmpParam.content.startsWith("/") && tmpParam.owner) {
2619
2955
  tmpParam.mail.push("invisible");
@@ -2650,7 +2986,7 @@
2650
2986
  return data;
2651
2987
  };
2652
2988
  const time2vpos = (time_str) => {
2653
- const time = time_str.match(/^(?:(\d+):(\d+)\.(\d+)|(\d+):(\d+)|(\d+)\.(\d+)|(\d+))$/);
2989
+ const time = RegExp(/^(?:(\d+):(\d+)\.(\d+)|(\d+):(\d+)|(\d+)\.(\d+)|(\d+))$/).exec(time_str);
2654
2990
  if (time) {
2655
2991
  if (time[1] !== undefined &&
2656
2992
  time[2] !== undefined &&
@@ -2706,7 +3042,6 @@
2706
3042
  utils: index$1
2707
3043
  });
2708
3044
 
2709
- let isDebug = false;
2710
3045
  class NiconiComments {
2711
3046
  enableLegacyPiP;
2712
3047
  showCollision;
@@ -2732,7 +3067,7 @@
2732
3067
  throw new InvalidOptionError();
2733
3068
  setOptions(Object.assign(defaultOptions, initOptions));
2734
3069
  setConfig(Object.assign(defaultConfig, options.config));
2735
- isDebug = options.debug;
3070
+ setIsDebug(options.debug);
2736
3071
  resetImageCache();
2737
3072
  resetNicoScripts();
2738
3073
  this.canvas = canvas;
@@ -2754,16 +3089,18 @@
2754
3089
  options.mode = "html5";
2755
3090
  }
2756
3091
  const parsedData = convert2formattedComment(data, formatType);
2757
- this.video = options.video || undefined;
3092
+ this.video = options.video ?? undefined;
2758
3093
  this.showCollision = options.showCollision;
2759
3094
  this.showFPS = options.showFPS;
2760
3095
  this.showCommentCount = options.showCommentCount;
2761
3096
  this.enableLegacyPiP = options.enableLegacyPiP;
2762
3097
  this.timeline = {};
2763
- this.collision = ["ue", "shita", "right", "left"].reduce((pv, value) => {
2764
- pv[value] = [];
2765
- return pv;
2766
- }, {});
3098
+ this.collision = {
3099
+ ue: [],
3100
+ shita: [],
3101
+ left: [],
3102
+ right: [],
3103
+ };
2767
3104
  this.lastVpos = -1;
2768
3105
  this.preRendering(parsedData);
2769
3106
  logger(`constructor complete: ${performance.now() - constructorStart}ms`);
@@ -2773,7 +3110,7 @@
2773
3110
  if (options.keepCA) {
2774
3111
  rawData = changeCALayer(rawData);
2775
3112
  }
2776
- const instances = rawData.reduce((pv, val) => {
3113
+ let instances = rawData.reduce((pv, val) => {
2777
3114
  pv.push(createCommentInstance(val, this.context));
2778
3115
  return pv;
2779
3116
  }, []);
@@ -2782,7 +3119,15 @@
2782
3119
  const plugins = [];
2783
3120
  for (const plugin of config.plugins) {
2784
3121
  try {
2785
- plugins.push(new plugin(this.canvas, instances));
3122
+ const canvas = generateCanvas();
3123
+ const pluginInstance = new plugin(canvas, instances);
3124
+ plugins.push({
3125
+ canvas,
3126
+ instance: pluginInstance,
3127
+ });
3128
+ if (pluginInstance.transformComments) {
3129
+ instances = pluginInstance.transformComments(instances);
3130
+ }
2786
3131
  }
2787
3132
  catch (e) {
2788
3133
  console.error("Failed to init plugin");
@@ -2829,9 +3174,10 @@
2829
3174
  pv.push(createCommentInstance(val, this.context));
2830
3175
  return pv;
2831
3176
  }, []);
3177
+ console.log(comments);
2832
3178
  for (const plugin of plugins) {
2833
3179
  try {
2834
- plugin.addComments(comments);
3180
+ plugin.instance.addComments?.(comments);
2835
3181
  }
2836
3182
  catch (e) {
2837
3183
  console.error("Failed to add comments");
@@ -2848,7 +3194,7 @@
2848
3194
  }
2849
3195
  }
2850
3196
  }
2851
- drawCanvas(vpos, forceRendering = false) {
3197
+ drawCanvas(vpos, forceRendering = false, cursor) {
2852
3198
  const drawCanvasStart = performance.now();
2853
3199
  if (this.lastVpos === vpos && !forceRendering)
2854
3200
  return false;
@@ -2859,7 +3205,7 @@
2859
3205
  timelineRange?.filter((item) => item.loc === "naka").length === 0 &&
2860
3206
  this.timeline[this.lastVpos]?.filter((item) => item.loc === "naka")
2861
3207
  ?.length === 0) {
2862
- const current = timelineRange.filter((item) => item.loc !== "naka"), last = this.timeline[this.lastVpos]?.filter((item) => item.loc !== "naka") ||
3208
+ const current = timelineRange.filter((item) => item.loc !== "naka"), last = this.timeline[this.lastVpos]?.filter((item) => item.loc !== "naka") ??
2863
3209
  [];
2864
3210
  if (ArrayEqual(current, last))
2865
3211
  return false;
@@ -2867,16 +3213,17 @@
2867
3213
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
2868
3214
  this.lastVpos = vpos;
2869
3215
  this._drawVideo();
2870
- this._drawCollision(vpos);
2871
- this._drawComments(timelineRange, vpos);
2872
3216
  for (const plugin of plugins) {
2873
3217
  try {
2874
- plugin.draw(vpos);
3218
+ plugin.instance.draw?.(vpos);
3219
+ this.context.drawImage(plugin.canvas, 0, 0);
2875
3220
  }
2876
3221
  catch (e) {
2877
3222
  console.error(`Failed to draw comments`);
2878
3223
  }
2879
3224
  }
3225
+ this._drawCollision(vpos);
3226
+ this._drawComments(timelineRange, vpos, cursor);
2880
3227
  this._drawFPS(drawCanvasStart);
2881
3228
  this._drawCommentCount(timelineRange?.length);
2882
3229
  logger(`drawCanvas complete: ${performance.now() - drawCanvasStart}ms`);
@@ -2896,7 +3243,7 @@
2896
3243
  this.context.drawImage(this.video, offsetX, offsetY, this.video.videoWidth * scale, this.video.videoHeight * scale);
2897
3244
  }
2898
3245
  }
2899
- _drawComments(timelineRange, vpos) {
3246
+ _drawComments(timelineRange, vpos, cursor) {
2900
3247
  if (timelineRange) {
2901
3248
  const targetComment = (() => {
2902
3249
  if (config.commentLimit === undefined) {
@@ -2911,7 +3258,7 @@
2911
3258
  if (comment.invisible) {
2912
3259
  continue;
2913
3260
  }
2914
- comment.draw(vpos, this.showCollision, isDebug);
3261
+ comment.draw(vpos, this.showCollision, cursor);
2915
3262
  }
2916
3263
  }
2917
3264
  }
@@ -2952,8 +3299,8 @@
2952
3299
  this.context.font = parseFont("defont", 60);
2953
3300
  this.context.fillStyle = "#00FF00";
2954
3301
  this.context.strokeStyle = `rgba(${hex2rgb(config.contextStrokeColor).join(",")},${config.contextStrokeOpacity})`;
2955
- this.context.strokeText(`Count:${count || 0}`, 100, 200);
2956
- this.context.fillText(`Count:${count || 0}`, 100, 200);
3302
+ this.context.strokeText(`Count:${count ?? 0}`, 100, 200);
3303
+ this.context.fillText(`Count:${count ?? 0}`, 100, 200);
2957
3304
  this.context.restore();
2958
3305
  }
2959
3306
  }
@@ -2966,6 +3313,20 @@
2966
3313
  clear() {
2967
3314
  this.context.clearRect(0, 0, config.canvasWidth, config.canvasHeight);
2968
3315
  }
3316
+ click(vpos, pos) {
3317
+ const _comments = this.timeline[vpos];
3318
+ if (!_comments)
3319
+ return;
3320
+ const comments = [..._comments].reverse();
3321
+ for (const comment of comments) {
3322
+ if (comment.isHovered(pos)) {
3323
+ const newComment = buildAtButtonComment(comment.comment, vpos);
3324
+ if (!newComment)
3325
+ continue;
3326
+ this.addComments(newComment);
3327
+ }
3328
+ }
3329
+ }
2969
3330
  }
2970
3331
  const logger = (msg) => {
2971
3332
  if (isDebug)