@dan-uni/dan-any-plugin-detaolu 0.9.2 → 1.0.1

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.
@@ -37,7 +37,10 @@ and limitations under the License.
37
37
 
38
38
  /**
39
39
  * @author: xmcp(代码主要逻辑来源)
40
- * @see: https://github.com/xmcp/pakku.js
40
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/core/combine_worker.ts
41
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/background/config.ts
42
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/page/options.html
43
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/page/options.ts
41
44
  * @license: GPL-3.0
42
45
  * 本文件内代码来源见上,经部分修改,并整合config注释
43
46
  */
@@ -1,42 +1,65 @@
1
1
  /**
2
2
  * @author: xmcp(代码主要逻辑来源)
3
- * @see: https://github.com/xmcp/pakku.js
3
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/core/combine_worker.ts
4
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/background/config.ts
5
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/page/options.html
6
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/page/options.ts
4
7
  * @license: GPL-3.0
5
8
  * 本文件内代码来源见上,经部分修改,并整合config注释
6
9
  */
7
10
  import type { DanmuChunk, DanmuClusterOutput, DanmuObject } from './types';
8
11
  export declare const DEFAULT_CONFIG: {
9
12
  /**
10
- * 时间阈值(合并n秒内的弹幕):
13
+ * 时间阈值:合并时间差在n秒之内的重复弹幕
11
14
  * 超长(大概 60 秒以上?)的阈值可能会导致程序运行缓慢
12
15
  */
13
16
  THRESHOLD: number;
14
17
  /**
15
18
  * 编辑距离合并阈值:
16
- * 根据编辑距离判断不完全一致但内容相近(例如有错别字)的弹幕,
17
- * 能有效击杀 "<code>你指尖跃动的电光</code>" 和 "<code>你<b>之间</b>跃动的电光</code>" 等
18
- * @example 禁用(0),轻微(≤3),中等(≤5),强力(≤8)
19
+ * 根据编辑距离判断不完全一致但内容相近(例如有错别字)的弹幕
20
+ * 能有效击杀 "你指尖跃动的电光" 和 "你之间跃动的电光" 等
21
+ * @example 禁用(0), 轻微(≤3), 中等(≤5), 强力(≤8)
19
22
  */
20
23
  MAX_DIST: number;
21
24
  /**
22
25
  * 词频向量合并阈值:
23
- * 根据 2-Gram 频率向量的夹角判断不完全一致但内容类似的弹幕,
24
- * 能有效击杀 "<code>yeah!~</code>" 和 "<code>yeah!~yeah!~yeah!~yeah!~</code>" 等
25
- * @example 禁用(1000),轻微(60%),中等(45%),强力(30%)
26
+ * 根据 2-Gram 频率向量的夹角判断不完全一致但内容类似的弹幕
27
+ * 能有效击杀 "yeah!~" 和 "yeah!~yeah!~yeah!~yeah!~" 等
28
+ * @example 禁用(1000), 轻微(60%), 中等(45%), 强力(30%)
26
29
  */
27
30
  MAX_COSINE: number;
28
31
  /**
29
32
  * 识别谐音弹幕:
30
- * 将常用汉字转换为拼音再进行比较,
31
- * 能有效击杀 "<code>布拉迪巴特福来</code>" 和 "<code>布拉迪·八德福莱</code>" 等
33
+ * 将常用汉字转换为拼音再进行比较
34
+ * 能有效击杀 "布拉迪巴特福来" 和 "布拉迪·八德福莱" 等
32
35
  */
33
36
  TRIM_PINYIN: boolean;
34
37
  TRIM_ENDING: boolean;
35
38
  TRIM_SPACE: boolean;
36
39
  TRIM_WIDTH: boolean;
40
+ /**
41
+ * 内容替换:符合这些规则的弹幕,判断是否合并前会先对内容进行替换
42
+ */
37
43
  FORCELIST: string[][];
44
+ /**
45
+ * 内容替换规则命中时:继续尝试匹配后续规则
46
+ */
47
+ FORCELIST_CONTINUE_ON_MATCH: boolean;
48
+ /**
49
+ * 内容替换规则命中时:即使未触发合并也使用替换后的文本
50
+ */
51
+ FORCELIST_APPLY_SINGULAR: boolean;
52
+ /**
53
+ * 强制忽略:符合这些规则的弹幕不会被合并,优先级高于内容替换规则
54
+ */
38
55
  WHITELIST: [string, string][];
56
+ /**
57
+ * 强制删除:符合这些规则的弹幕会直接被删除(未实现)
58
+ */
39
59
  BLACKLIST: [string, string][];
60
+ /**
61
+ * 合并不同类型的弹幕(取消勾选后,底部弹幕不会跟滚动弹幕合并到一起)
62
+ */
40
63
  CROSS_MODE: boolean;
41
64
  PROC_TYPE7: boolean;
42
65
  PROC_TYPE4: boolean;
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@dan-uni/dan-any-plugin-detaolu",
3
- "version": "0.9.2",
3
+ "version": "1.0.1",
4
4
  "description": "A filter, dedupe and anti-spam plugin of dan-any, a danmaku transformer lib, based on pakku.js.",
5
- "keywords": [
6
- "bangumi",
7
- "danmaku"
8
- ],
5
+ "author": "rinne",
9
6
  "license": "GPL-3.0-or-later",
10
7
  "homepage": "https://github.com/ani-uni/danuni/tree/master/packages/dan-any-plugin-detaolu#readme",
11
- "bugs": {
12
- "url": "https://github.com/ani-uni/danuni/issues"
13
- },
14
8
  "repository": {
15
9
  "type": "git",
16
10
  "url": "git+https://github.com/ani-uni/danuni.git"
17
11
  },
18
- "author": "rinne",
12
+ "bugs": {
13
+ "url": "https://github.com/ani-uni/danuni/issues"
14
+ },
15
+ "keywords": [
16
+ "bangumi",
17
+ "danmaku"
18
+ ],
19
19
  "main": "dist/index.js",
20
20
  "module": "src/index.ts",
21
21
  "types": "dist/index.d.ts",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@dan-uni/dan-any": "workspace:^",
34
- "fs-extra": "^11.3.0"
34
+ "fs-extra": "^11.3.3"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/fs-extra": "^11.0.4"
package/src/index.ts CHANGED
@@ -22,8 +22,8 @@ async function detaolu(that: UniPool, config?: DeTaoLuConfig) {
22
22
  if (p.danuni_dans.length === 1) {
23
23
  return p.danuni_dans[0].danuni_dan
24
24
  } else {
25
- const dans = p.danuni_dans,
26
- pool = new UniPool(dans.map((d) => d.danuni_dan))
25
+ const dans = p.danuni_dans
26
+ const pool = new UniPool(dans.map((d) => d.danuni_dan))
27
27
  function isAllBottomMode(p: UniPool) {
28
28
  return p.dans.every((d) => d.mode === UniDMTools.Modes.Bottom)
29
29
  }
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * @author: xmcp(代码主要逻辑来源)
3
- * @see: https://github.com/xmcp/pakku.js
3
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/core/combine_worker.ts
4
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/background/config.ts
5
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/page/options.html
6
+ * @see: https://github.com/xmcp/pakku.js/blob/master/pakkujs/page/options.ts
4
7
  * @license: GPL-3.0
5
8
  * 本文件内代码来源见上,经部分修改,并整合config注释
6
9
  */
@@ -19,28 +22,28 @@ import { Queue, Stats } from './types'
19
22
  export const DEFAULT_CONFIG = {
20
23
  // 弹幕合并
21
24
  /**
22
- * 时间阈值(合并n秒内的弹幕):
25
+ * 时间阈值:合并时间差在n秒之内的重复弹幕
23
26
  * 超长(大概 60 秒以上?)的阈值可能会导致程序运行缓慢
24
27
  */
25
28
  THRESHOLD: 30,
26
29
  /**
27
30
  * 编辑距离合并阈值:
28
- * 根据编辑距离判断不完全一致但内容相近(例如有错别字)的弹幕,
29
- * 能有效击杀 "<code>你指尖跃动的电光</code>" 和 "<code>你<b>之间</b>跃动的电光</code>" 等
30
- * @example 禁用(0),轻微(≤3),中等(≤5),强力(≤8)
31
+ * 根据编辑距离判断不完全一致但内容相近(例如有错别字)的弹幕
32
+ * 能有效击杀 "你指尖跃动的电光" 和 "你之间跃动的电光" 等
33
+ * @example 禁用(0), 轻微(≤3), 中等(≤5), 强力(≤8)
31
34
  */
32
35
  MAX_DIST: 5,
33
36
  /**
34
37
  * 词频向量合并阈值:
35
- * 根据 2-Gram 频率向量的夹角判断不完全一致但内容类似的弹幕,
36
- * 能有效击杀 "<code>yeah!~</code>" 和 "<code>yeah!~yeah!~yeah!~yeah!~</code>" 等
37
- * @example 禁用(1000),轻微(60%),中等(45%),强力(30%)
38
+ * 根据 2-Gram 频率向量的夹角判断不完全一致但内容类似的弹幕
39
+ * 能有效击杀 "yeah!~" 和 "yeah!~yeah!~yeah!~yeah!~" 等
40
+ * @example 禁用(1000), 轻微(60%), 中等(45%), 强力(30%)
38
41
  */
39
42
  MAX_COSINE: 45,
40
43
  /**
41
44
  * 识别谐音弹幕:
42
- * 将常用汉字转换为拼音再进行比较,
43
- * 能有效击杀 "<code>布拉迪巴特福来</code>" 和 "<code>布拉迪·八德福莱</code>" 等
45
+ * 将常用汉字转换为拼音再进行比较
46
+ * 能有效击杀 "布拉迪巴特福来" 和 "布拉迪·八德福莱" 等
44
47
  */
45
48
  TRIM_PINYIN: true,
46
49
  // 比较文本时:
@@ -49,15 +52,35 @@ export const DEFAULT_CONFIG = {
49
52
  TRIM_WIDTH: true, // 忽略全半角差异
50
53
 
51
54
  // 例外设置
55
+ /**
56
+ * 内容替换:符合这些规则的弹幕,判断是否合并前会先对内容进行替换
57
+ */
52
58
  FORCELIST: [
53
59
  ['^23{2,}$', '23333'],
54
60
  ['^6{3,}$', '66666'],
55
- ], // 强制合并(符合这些规则的弹幕,在比较是否相同时会先进行替换)
56
- WHITELIST: [] as [string, string][], // 强制忽略(符合这些规则的弹幕,即使内容相同也不会被合并)
57
- BLACKLIST: [] as [string, string][], // 强制删除(符合这些规则的弹幕,会直接被删除)
58
- CROSS_MODE: true, // 合并不同类型的弹幕(取消勾选后,底部弹幕不会跟滚动弹幕合并到一起)
61
+ ],
62
+ /**
63
+ * 内容替换规则命中时:继续尝试匹配后续规则
64
+ */
65
+ FORCELIST_CONTINUE_ON_MATCH: true,
66
+ /**
67
+ * 内容替换规则命中时:即使未触发合并也使用替换后的文本
68
+ */
69
+ FORCELIST_APPLY_SINGULAR: false,
70
+ /**
71
+ * 强制忽略:符合这些规则的弹幕不会被合并,优先级高于内容替换规则
72
+ */
73
+ WHITELIST: [] as [string, string][],
74
+ /**
75
+ * 强制删除:符合这些规则的弹幕会直接被删除(未实现)
76
+ */
77
+ BLACKLIST: [] as [string, string][],
78
+ /**
79
+ * 合并不同类型的弹幕(取消勾选后,底部弹幕不会跟滚动弹幕合并到一起)
80
+ */
81
+ CROSS_MODE: true,
59
82
  // 放过特定类型的弹幕:
60
- PROC_TYPE7: true, // 高级弹幕
83
+ PROC_TYPE7: true, // 高级弹幕(特殊弹幕)
61
84
  PROC_TYPE4: true, // 底部弹幕
62
85
  PROC_POOL1: false, // 字幕弹幕(位于弹幕池1)
63
86
 
@@ -90,7 +113,7 @@ export type Config = Partial<typeof DEFAULT_CONFIG>
90
113
  interface DanmuIr {
91
114
  obj: DanmuObject
92
115
  str: string // for similarity algorithm
93
- idx: int
116
+ ptr_idx: int
94
117
  sim_reason: string
95
118
  }
96
119
 
@@ -201,13 +224,14 @@ const WIDTH_TABLE = new Map(
201
224
  /**
202
225
  * 反套路
203
226
  */
204
- const detaolu = (inp: string, config: Config) => {
227
+ const detaolu = (inp: string, config: Config): [boolean, string] => {
205
228
  const TRIM_ENDING = config.TRIM_ENDING
206
229
  const TRIM_SPACE = config.TRIM_SPACE
207
230
  const TRIM_WIDTH = config.TRIM_WIDTH
208
231
  const FORCELIST = (config?.FORCELIST ?? DEFAULT_CONFIG.FORCELIST).map(
209
- ([pattern, repl]) => [new RegExp(pattern, 'gi'), repl] as [RegExp, string],
232
+ ([pattern, repl]) => [new RegExp(pattern, 'giu'), repl] as [RegExp, string],
210
233
  )
234
+ const FORCELIST_BREAK_ON_MATCH = !config.FORCELIST_CONTINUE_ON_MATCH
211
235
 
212
236
  let len = inp.length
213
237
  let text = ''
@@ -242,14 +266,16 @@ const detaolu = (inp: string, config: Config) => {
242
266
  )
243
267
  }
244
268
 
269
+ let taolu_matched = false
245
270
  for (const taolu of FORCELIST) {
246
271
  if (taolu[0].test(text)) {
247
272
  text = text.replace(taolu[0], taolu[1])
248
- return [true, text]
273
+ taolu_matched = true
274
+ if (FORCELIST_BREAK_ON_MATCH) break
249
275
  }
250
276
  }
251
277
 
252
- return [false, text]
278
+ return [taolu_matched, text]
253
279
  }
254
280
 
255
281
  /**
@@ -257,7 +283,7 @@ const detaolu = (inp: string, config: Config) => {
257
283
  */
258
284
  const whitelisted = (text: string, config: Config) => {
259
285
  const WHITELIST = (config?.WHITELIST ?? DEFAULT_CONFIG.WHITELIST).map(
260
- (x) => new RegExp(x[0], 'i'),
286
+ (x) => new RegExp(x[0], 'iu'),
261
287
  )
262
288
  if (WHITELIST.length === 0) return false
263
289
  else return WHITELIST.some((re) => re.test(text))
@@ -308,7 +334,7 @@ function trim_dispstr(text: string): string {
308
334
  function select_median_length(strs: string[]): string {
309
335
  if (strs.length === 1) return strs[0]
310
336
 
311
- const sorted = strs.sort((a, b) => a.length - b.length)
337
+ const sorted = strs.toSorted((a, b) => a.length - b.length)
312
338
  const mid = Math.floor(sorted.length / 2)
313
339
  return sorted[mid]
314
340
  }
@@ -316,10 +342,14 @@ function select_median_length(strs: string[]): string {
316
342
  async function load_wasm(wasm_mod?: ArrayBuffer) {
317
343
  await sim_init(
318
344
  wasm_mod ??
319
- (await fs.readFile(new URL('./similarity-gen.wasm', import.meta.url))),
345
+ (await fs.readFile(new URL('similarity-gen.wasm', import.meta.url))),
320
346
  )
321
347
  }
322
348
 
349
+ function make_ptr_idx(idx: int, is_next_chunk: boolean): int {
350
+ return is_next_chunk ? -1 - idx : idx
351
+ }
352
+
323
353
  async function merge(
324
354
  chunk: DanmuChunk<DanmuObject>,
325
355
  // next_chunk: DanmuChunk<DanmuObject>,
@@ -349,9 +379,9 @@ async function merge(
349
379
  function apply_cluster(irs: DanmuIr[]) {
350
380
  if (irs.length === 1) {
351
381
  ret.clusters.push({
352
- peers_ptr: irs.map((ir) => [ir.idx, ir.sim_reason]),
382
+ peers_ptr: irs.map((ir) => [ir.ptr_idx, ir.sim_reason]),
353
383
  desc: [],
354
- chosen_str: irs[0].obj.content, // do not use detaolued str for single danmu
384
+ chosen_str: irs[0].obj.content,
355
385
  // danuni
356
386
  danuni_count: irs.length,
357
387
  // danuni_senders: irs.map((ir) => ir.obj.danuni_sender),
@@ -359,8 +389,8 @@ async function merge(
359
389
  })
360
390
  } else {
361
391
  const text_cnts = new Map()
362
- let most_texts: string[] = [],
363
- most_cnt = 0
392
+ let most_texts: string[] = []
393
+ let most_cnt = 0
364
394
 
365
395
  for (const ir of irs) {
366
396
  const text = ir.str
@@ -378,7 +408,7 @@ async function merge(
378
408
  const most_text = select_median_length(most_texts)
379
409
 
380
410
  ret.clusters.push({
381
- peers_ptr: irs.map((ir) => [ir.idx, ir.sim_reason]),
411
+ peers_ptr: irs.map((ir) => [ir.ptr_idx, ir.sim_reason]),
382
412
  desc: most_cnt > 1 ? [`采用了出现 ${most_cnt} 次的文本`] : [],
383
413
  chosen_str: most_text,
384
414
  // danuni
@@ -389,7 +419,11 @@ async function merge(
389
419
  }
390
420
  }
391
421
 
392
- function obj_to_ir(objs: DanmuObject[], s: Stats | null): DanmuIr[] {
422
+ function obj_to_ir(
423
+ objs: DanmuObject[],
424
+ s: Stats | null,
425
+ is_next_chunk: boolean,
426
+ ): DanmuIr[] {
393
427
  return objs
394
428
  .map((obj, idx) => {
395
429
  if (!config.PROC_POOL1 && obj.pool === 1) {
@@ -465,22 +499,27 @@ async function merge(
465
499
 
466
500
  const [matched_taolu, detaolued] = detaolu(disp_str, config)
467
501
 
468
- if (matched_taolu && s) {
469
- s.num_taolu_matched++
502
+ if (matched_taolu) {
503
+ if (s) s.num_taolu_matched++
504
+ if (config.FORCELIST_APPLY_SINGULAR)
505
+ obj = {
506
+ ...obj,
507
+ content: detaolued,
508
+ }
470
509
  }
471
510
 
472
511
  return {
473
512
  obj,
474
513
  str: detaolued,
475
- idx,
514
+ ptr_idx: make_ptr_idx(idx, is_next_chunk),
476
515
  sim_reason: 'ORIG',
477
516
  }
478
517
  })
479
518
  .filter((obj) => obj !== null) as DanmuIr[]
480
519
  }
481
520
 
482
- const danmus = obj_to_ir(chunk.objs, ret.stats)
483
- // const next_chunk_danmus = obj_to_ir(next_chunk.objs, null)
521
+ const danmus = obj_to_ir(chunk.objs, ret.stats, false)
522
+ // const next_chunk_danmus = obj_to_ir(next_chunk.objs, null, true)
484
523
 
485
524
  const nearby_danmus: Queue<DanmuIr[]> = new Queue()
486
525
 
@@ -504,13 +543,13 @@ async function merge(
504
543
  nearby_danmus.index_l,
505
544
  ret.stats,
506
545
  )
507
- if (sim !== null) {
546
+ if (sim === null) {
547
+ nearby_danmus.push([dm])
548
+ } else {
508
549
  const candidate =
509
550
  nearby_danmus.storage[nearby_danmus.index_r - sim.idx_diff]
510
551
  dm.sim_reason = sim.reason
511
552
  candidate.push(dm)
512
- } else {
513
- nearby_danmus.push([dm])
514
553
  }
515
554
  }
516
555
 
@@ -1,18 +1,5 @@
1
- /* eslint-disable getter-return */
2
- /* eslint-disable unicorn/catch-error-name */
3
- /* eslint-disable unicorn/prefer-number-properties */
4
- /* eslint-disable block-scoped-var */
5
- /* eslint-disable object-shorthand */
6
- /* eslint-disable no-setter-return */
7
- /* eslint-disable prefer-template */
8
- /* eslint-disable unicorn/new-for-builtins */
9
- /* eslint-disable unicorn/throw-new-error */
10
- /* eslint-disable no-console */
11
- /* eslint-disable unicorn/consistent-function-scoping */
12
- /* eslint-disable no-unused-expressions */
13
- /* eslint-disable vars-on-top */
14
- /* eslint-disable import/no-mutable-exports */
15
- /* eslint-disable import/no-default-export */
1
+ /* eslint-disable @eslint-community/eslint-comments/no-unlimited-disable */
2
+ /* eslint-disable */
16
3
  /** @nocollapse */ var Module = function (moduleArg = {}) {
17
4
  var moduleRtn
18
5
 
@@ -75,20 +75,34 @@ export function detect_similarity(
75
75
  const idx_diff = ret & ((1 << 19) - 1)
76
76
 
77
77
  let reason_str
78
- if (reason === CombinedReason.combined_identical) {
79
- S.combined_identical++
80
- reason_str = '=='
81
- } else if (reason === CombinedReason.combined_edit_distance) {
82
- S.combined_edit_distance++
83
- reason_str = `≤${dist}`
84
- } else if (reason === CombinedReason.combined_cosine_distance) {
85
- S.combined_cosine_distance++
86
- reason_str = `${dist}%`
87
- } else if (reason === CombinedReason.combined_pinyin_distance) {
88
- S.combined_pinyin_distance++
89
- reason_str = `P≤${dist}`
90
- } else {
91
- throw new Error(`similarity wasm returned unknown reason: ${ret}`)
78
+ switch (reason) {
79
+ case CombinedReason.combined_identical: {
80
+ S.combined_identical++
81
+ reason_str = '=='
82
+
83
+ break
84
+ }
85
+ case CombinedReason.combined_edit_distance: {
86
+ S.combined_edit_distance++
87
+ reason_str = `≤${dist}`
88
+
89
+ break
90
+ }
91
+ case CombinedReason.combined_cosine_distance: {
92
+ S.combined_cosine_distance++
93
+ reason_str = `${dist}%`
94
+
95
+ break
96
+ }
97
+ case CombinedReason.combined_pinyin_distance: {
98
+ S.combined_pinyin_distance++
99
+ reason_str = `P≤${dist}`
100
+
101
+ break
102
+ }
103
+ default: {
104
+ throw new Error(`similarity wasm returned unknown reason: ${ret}`)
105
+ }
92
106
  }
93
107
 
94
108
  return { reason: reason_str, idx_diff }
package/tsconfig.json CHANGED
@@ -2,14 +2,14 @@
2
2
  "compilerOptions": {
3
3
  /* Visit https://aka.ms/tsconfig to read more about this file */
4
4
  /* Projects */
5
- // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
5
+ "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
6
6
  // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
7
7
  // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
8
8
  // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
9
9
  // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
10
10
  // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11
11
  /* Language and Environment */
12
- "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
12
+ "target": "ES2023" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
13
13
  // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
14
14
  // "jsx": "preserve", /* Specify what JSX code is generated. */
15
15
  // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
@@ -0,0 +1 @@
1
+ {"version":"5.9.3"}