@dan-uni/dan-any 0.7.8 → 0.8.5

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/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import 'reflect-metadata/lite'
2
2
 
3
3
  import { XMLBuilder, XMLParser } from 'fast-xml-parser'
4
4
  import JSONbig from 'json-bigint'
5
- import type { Options as AssGenOptions } from './ass-gen'
5
+ import type { Options as AssGenOptions, CanvasCtx } from './ass-gen'
6
6
  import type { CommandDm as DM_JSON_BiliCommandGrpc } from './proto/gen/bili/dm_pb'
7
7
 
8
8
  import { create, fromBinary, toBinary } from '@bufbuild/protobuf'
@@ -12,6 +12,7 @@ import {
12
12
  timestampNow,
13
13
  } from '@bufbuild/protobuf/wkt'
14
14
 
15
+ import pkg from '../package.json'
15
16
  import { generateASS, parseAssRawField } from './ass-gen'
16
17
  import {
17
18
  // DanmakuElem as DM_JSON_BiliGrpc,
@@ -31,6 +32,17 @@ const JSON = JSONbig({
31
32
  useNativeBigInt: true,
32
33
  })
33
34
 
35
+ const DanUniConvertTipTemplate: DanUniConvertTip = {
36
+ meassage: 'Converted by DanUni!',
37
+ version: `JS/TS ${pkg.name} (v${pkg.version})`,
38
+ }
39
+
40
+ interface DanUniConvertTip {
41
+ meassage: string
42
+ version: string
43
+ data?: string
44
+ }
45
+
34
46
  export interface DM_XML_Bili {
35
47
  i: {
36
48
  chatserver: string
@@ -54,12 +66,14 @@ export interface DM_JSON_Dplayer {
54
66
  data: [number, number, number, string, string][]
55
67
  }
56
68
  export interface DM_JSON_Artplayer {
57
- text: string // 弹幕文本
58
- time?: number // 弹幕时间,默认为当前播放器时间
59
- mode?: number // 弹幕模式:0: 滚动 (默认),1: 顶部,2: 底部
60
- color?: string // 弹幕颜色,默认为白色
61
- border?: boolean // 弹幕是否有描边,默认为 false
62
- style?: {} // 弹幕自定义样式,默认为空对象
69
+ danmuku: {
70
+ text: string // 弹幕文本
71
+ time?: number // 弹幕时间,默认为当前播放器时间
72
+ mode?: number // 弹幕模式:0: 滚动 (默认),1: 顶部,2: 底部
73
+ color?: string // 弹幕颜色,默认为白色
74
+ border?: boolean // 弹幕是否有描边,默认为 false
75
+ style?: {} // 弹幕自定义样式,默认为空对象
76
+ }[]
63
77
  }
64
78
  export interface DM_JSON_DDPlay {
65
79
  count: number | string
@@ -101,6 +115,12 @@ export class UniPool {
101
115
  constructor(
102
116
  public dans: UniDM[],
103
117
  public options: Options = {},
118
+ private info = {
119
+ /**
120
+ * 是否从已被转换过的第三方格式弹幕再次转换而来
121
+ */
122
+ fromConverted: false,
123
+ },
104
124
  ) {
105
125
  if (options.dedupe !== false) options.dedupe = true
106
126
  if (this.options.dedupe) this.dedupe()
@@ -232,11 +252,15 @@ export class UniPool {
232
252
  */
233
253
  assign(dans: UniPool | UniDM | UniDM[]) {
234
254
  if (dans instanceof UniPool) {
235
- return new UniPool([...this.dans, ...dans.dans])
255
+ return new UniPool(
256
+ [...this.dans, ...dans.dans],
257
+ { ...this.options, ...dans.options },
258
+ { ...this.info, ...dans.info },
259
+ )
236
260
  } else if (dans instanceof UniDM) {
237
- return new UniPool([...this.dans, dans])
261
+ return new UniPool([...this.dans, dans], this.options, this.info)
238
262
  } else if (Array.isArray(dans) && dans.every((d) => d instanceof UniDM)) {
239
- return new UniPool([...this.dans, ...dans])
263
+ return new UniPool([...this.dans, ...dans], this.options, this.info)
240
264
  } else return this
241
265
  }
242
266
  /**
@@ -248,7 +272,8 @@ export class UniPool {
248
272
  return [...set].map((v) => {
249
273
  return new UniPool(
250
274
  this.dans.filter((d) => d[key] === v),
251
- { dedupe: false },
275
+ { ...this.options, dedupe: false },
276
+ this.info,
252
277
  )
253
278
  })
254
279
  }
@@ -366,7 +391,7 @@ export class UniPool {
366
391
  }
367
392
  }
368
393
  })
369
- return new UniPool(result)
394
+ return new UniPool(result, this.options, this.info)
370
395
  }
371
396
  minify() {
372
397
  return this.dans.map((d) => d.minify())
@@ -377,8 +402,8 @@ export class UniPool {
377
402
  return this.dans
378
403
  case 'danuni.bin':
379
404
  return this.toPb()
380
- // case 'bili.xml':
381
- // return this.toBiliXML()
405
+ case 'bili.xml':
406
+ return this.toBiliXML()
382
407
  // case 'bili.bin':
383
408
  // return this.toBiliBin()
384
409
  // case 'bili.cmd.bin':
@@ -389,8 +414,8 @@ export class UniPool {
389
414
  return this.toArtplayer()
390
415
  case 'ddplay.json':
391
416
  return this.toDDplay()
392
- case 'common.ass':
393
- return this.toASS()
417
+ // case 'common.ass':
418
+ // return this.toASS()
394
419
  default: {
395
420
  const message = '(err) Unknown format or unsupported now!'
396
421
  if (continue_on_error) return message
@@ -405,6 +430,7 @@ export class UniPool {
405
430
  UniDM.create(
406
431
  {
407
432
  ...d,
433
+ progress: d.progress / 1000,
408
434
  mode: d.mode as number,
409
435
  ctime: timestampDate(d.ctime || timestampNow()),
410
436
  pool: d.pool as number,
@@ -427,7 +453,7 @@ export class UniPool {
427
453
  return {
428
454
  SOID: d.SOID,
429
455
  DMID: d.DMID,
430
- progress: d.progress,
456
+ progress: d.progress * 1000,
431
457
  mode: d.mode as number,
432
458
  fontsize: d.fontsize,
433
459
  color: d.color,
@@ -446,8 +472,10 @@ export class UniPool {
446
472
  }
447
473
  static fromBiliXML(xml: string, options?: Options) {
448
474
  const parser = new XMLParser({ ignoreAttributes: false }),
449
- oriData: DM_XML_Bili = parser.parse(xml),
450
- dans = oriData.i.d
475
+ oriData: DM_XML_Bili & { danuni?: DanUniConvertTip } = parser.parse(xml),
476
+ dans = oriData.i.d,
477
+ fromConverted = !!oriData.danuni,
478
+ cid = BigInt(oriData.i.chatid)
451
479
  return new UniPool(
452
480
  dans
453
481
  .map((d) => {
@@ -466,15 +494,28 @@ export class UniPool {
466
494
  id: BigInt(p_arr[7]),
467
495
  weight: Number.parseInt(p_arr[8]),
468
496
  },
469
- BigInt(oriData.i.chatid),
497
+ cid,
470
498
  options,
499
+ fromConverted ? oriData.danuni?.data : undefined,
471
500
  )
472
501
  })
473
502
  .filter((d) => d !== null),
474
503
  options,
504
+ { fromConverted },
475
505
  )
476
506
  }
477
- toBiliXML(): string {
507
+ toBiliXML(options?: {
508
+ /**
509
+ * 当SOID非来源bili时,若此处指定则使用该值为cid,否则使用SOID
510
+ */
511
+ cid?: bigint
512
+ /**
513
+ * 当仅含有来自bili的弹幕时,启用将保持发送者标识不含`@`
514
+ * @description
515
+ * bili的弹幕含midHash(crc),不启用该处使用senderID填充,启用则去除`@bili`部分,提高兼容性
516
+ */
517
+ avoidSenderIDWithAt?: boolean
518
+ }): string {
478
519
  const genCID = (id: string) => {
479
520
  const UniID = ID.fromString(id)
480
521
  if (UniID.domain === platform.PlatformVideoSource.Bilibili) {
@@ -485,7 +526,13 @@ export class UniPool {
485
526
 
486
527
  if (cid) return cid
487
528
  }
488
- return Number.parseInt(Buffer.from(id).toString('hex'), 16).toString()
529
+ return !options?.cid || id
530
+ }
531
+ if (options?.avoidSenderIDWithAt) {
532
+ const ok = this.dans.every((d) =>
533
+ d.senderID.endsWith(`@${platform.PlatformVideoSource.Bilibili}`),
534
+ )
535
+ if (!ok) throw new Error('存在其他来源的senderID,请关闭该功能再试!')
489
536
  }
490
537
  const builder = new XMLBuilder({ ignoreAttributes: false })
491
538
  return builder.build({
@@ -493,6 +540,7 @@ export class UniPool {
493
540
  '@_version': '1.0',
494
541
  '@_encoding': 'UTF-8',
495
542
  },
543
+ danuni: { ...DanUniConvertTipTemplate, data: this.shared.SOID },
496
544
  i: {
497
545
  chatserver: 'chat.bilibili.com',
498
546
  chatid: genCID(this.dans[0].SOID),
@@ -501,7 +549,7 @@ export class UniPool {
501
549
  state: 0,
502
550
  real_name: 0,
503
551
  source: 'k-v',
504
- d: this.dans.map((dan) => dan.toBiliXML()),
552
+ d: this.dans.map((dan) => dan.toBiliXML(options)),
505
553
  },
506
554
  })
507
555
  }
@@ -533,7 +581,7 @@ export class UniPool {
533
581
  )
534
582
  }
535
583
  static fromDplayer(
536
- json: DM_JSON_Dplayer,
584
+ json: DM_JSON_Dplayer & { danuni?: DanUniConvertTip },
537
585
  playerID: string,
538
586
  domain = 'other',
539
587
  options?: Options,
@@ -557,11 +605,13 @@ export class UniPool {
557
605
  )
558
606
  }),
559
607
  options,
608
+ { fromConverted: !!json.danuni },
560
609
  )
561
610
  }
562
- toDplayer(): DM_JSON_Dplayer {
611
+ toDplayer(): DM_JSON_Dplayer & { danuni?: DanUniConvertTip } {
563
612
  return {
564
613
  code: 0,
614
+ danuni: DanUniConvertTipTemplate,
565
615
  data: this.dans.map((dan) => {
566
616
  const d = dan.toDplayer()
567
617
  return [d.progress, d.mode, d.color, d.midHash, d.content]
@@ -569,13 +619,13 @@ export class UniPool {
569
619
  }
570
620
  }
571
621
  static fromArtplayer(
572
- json: DM_JSON_Artplayer[],
622
+ json: DM_JSON_Artplayer & { danuni?: DanUniConvertTip },
573
623
  playerID: string,
574
624
  domain = 'other',
575
625
  options?: Options,
576
626
  ) {
577
627
  return new UniPool(
578
- json.map((d) => {
628
+ json.danmuku.map((d) => {
579
629
  // let TYPE = 0
580
630
  // if (d.mode === 1) TYPE = 5
581
631
  // else if (d.mode === 2) TYPE = 4
@@ -593,23 +643,27 @@ export class UniPool {
593
643
  )
594
644
  }),
595
645
  options,
646
+ { fromConverted: !!json.danuni },
596
647
  )
597
648
  }
598
- toArtplayer(): DM_JSON_Artplayer[] {
599
- return this.dans.map((dan) => {
600
- const d = dan.toArtplayer()
601
- return {
602
- text: d.content,
603
- time: d.progress,
604
- mode: d.mode as 0 | 1 | 2,
605
- color: `#${d.color.toString(16).toUpperCase() || 'FFFFFF'}`,
606
- border: d.border,
607
- style: d.style,
608
- }
609
- })
649
+ toArtplayer(): DM_JSON_Artplayer & { danuni?: DanUniConvertTip } {
650
+ return {
651
+ danuni: DanUniConvertTipTemplate,
652
+ danmuku: this.dans.map((dan) => {
653
+ const d = dan.toArtplayer()
654
+ return {
655
+ text: d.content,
656
+ time: d.progress,
657
+ mode: d.mode as 0 | 1 | 2,
658
+ color: `#${d.color.toString(16).toUpperCase() || 'FFFFFF'}`,
659
+ border: d.border,
660
+ style: d.style,
661
+ }
662
+ }),
663
+ }
610
664
  }
611
665
  static fromDDPlay(
612
- json: DM_JSON_DDPlay,
666
+ json: DM_JSON_DDPlay & { danuni?: DanUniConvertTip },
613
667
  episodeId: string,
614
668
  options?: Options,
615
669
  ) {
@@ -631,10 +685,12 @@ export class UniPool {
631
685
  )
632
686
  }),
633
687
  options,
688
+ { fromConverted: !!json.danuni },
634
689
  )
635
690
  }
636
- toDDplay(): DM_JSON_DDPlay {
691
+ toDDplay(): DM_JSON_DDPlay & { danuni?: DanUniConvertTip } {
637
692
  return {
693
+ danuni: DanUniConvertTipTemplate,
638
694
  count: this.dans.length,
639
695
  comments: this.dans.map((dan) => {
640
696
  const d = dan.toDDplay()
@@ -649,9 +705,15 @@ export class UniPool {
649
705
  static fromASS(ass: string) {
650
706
  return parseAssRawField(ass)
651
707
  }
652
- toASS(options: AssGenOptions = { substyle: {} }): string {
708
+ /**
709
+ * 转换为ASS字幕格式的弹幕,需播放器支持多行ASS渲染
710
+ */
711
+ toASS(
712
+ canvasCtx: CanvasCtx,
713
+ options: AssGenOptions = { substyle: {} },
714
+ ): string {
653
715
  const fn = this.shared.SOID
654
- return generateASS(this, { filename: fn, title: fn, ...options })
716
+ return generateASS(this, { filename: fn, title: fn, ...options }, canvasCtx)
655
717
  }
656
718
  }
657
719
 
@@ -1,4 +1,4 @@
1
- // @generated by protoc-gen-es v2.6.2 with parameter "target=ts"
1
+ // @generated by protoc-gen-es v2.6.3 with parameter "target=ts"
2
2
  // @generated from file bili/dm.proto (package bilibili.community.service.dm.v1, syntax proto3)
3
3
  /* eslint-disable */
4
4
 
@@ -1,4 +1,4 @@
1
- // @generated by protoc-gen-es v2.6.2 with parameter "target=ts"
1
+ // @generated by protoc-gen-es v2.6.3 with parameter "target=ts"
2
2
  // @generated from file danuni.proto (package danuni.danmaku.v1, syntax proto3)
3
3
  /* eslint-disable */
4
4
 
@@ -2,6 +2,7 @@ import { Expose, plainToInstance } from 'class-transformer'
2
2
  import {
3
3
  IsDate,
4
4
  IsEmail,
5
+ isEmail,
5
6
  IsEnum,
6
7
  IsInt,
7
8
  IsNotEmpty,
@@ -733,13 +734,22 @@ export class UniDM {
733
734
  }
734
735
  return mode
735
736
  }
736
- static fromBili(args: DMBili, cid?: bigint, options?: Options) {
737
+ static fromBili(
738
+ args: DMBili,
739
+ cid?: bigint,
740
+ options?: Options,
741
+ recSOID?: string,
742
+ ) {
737
743
  interface TExtra extends Extra {
738
744
  bili: ExtraBili
739
745
  }
740
746
  if (args.oid && !cid) cid = args.oid
741
- const SOID = `def_${PlatformVideoSource.Bilibili}+${ID.fromBili({ cid })}`,
742
- senderID = ID.fromBili({ midHash: args.midHash })
747
+ const SOID =
748
+ recSOID ||
749
+ `def_${PlatformVideoSource.Bilibili}+${ID.fromBili({ cid })}`,
750
+ senderID = isEmail(args.midHash, { require_tld: false })
751
+ ? args.midHash
752
+ : ID.fromBili({ midHash: args.midHash })
743
753
  let mode = Modes.Normal
744
754
  const pool = args.pool, //暂时不做处理,兼容bili的pool格式
745
755
  extra: TExtra = {
@@ -797,7 +807,13 @@ export class UniDM {
797
807
  )
798
808
  }
799
809
  @Expose()
800
- toBiliXML(options?: { skipBiliCommand: boolean }) {
810
+ toBiliXML(options?: {
811
+ skipBiliCommand?: boolean
812
+ /**
813
+ * 见 ../index.ts UniPool.toBiliXML() 的 options,该option不宜手动调用,判断逻辑未封装
814
+ */
815
+ avoidSenderIDWithAt?: boolean
816
+ }) {
801
817
  if (options?.skipBiliCommand && this.extra.bili?.command) {
802
818
  return null
803
819
  }
@@ -846,7 +862,9 @@ export class UniDM {
846
862
  this.color,
847
863
  this.ctime.getTime() / 1000,
848
864
  this.extra.bili?.pool || this.pool, // 目前pool与bili兼容
849
- this.senderID,
865
+ options?.avoidSenderIDWithAt
866
+ ? this.senderID.replaceAll(`@${PlatformVideoSource.Bilibili}`, '')
867
+ : this.senderID,
850
868
  this.extra.bili?.dmid || this.DMID || this.toDMID(),
851
869
  this.weight,
852
870
  ].join(','),
@@ -1001,7 +1019,9 @@ export class UniDM {
1001
1019
  color: this.color,
1002
1020
  uid: this.senderID,
1003
1021
  m: this.content,
1004
- cid: Number(this.DMID) || 1,
1022
+ cid: this.DMID
1023
+ ? Number.parseInt(Buffer.from(this.DMID).toString('hex'), 16)
1024
+ : 0,
1005
1025
  }
1006
1026
  }
1007
1027
  }