@dan-uni/dan-any 0.8.1 → 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.
@@ -5,6 +5,11 @@ import { UniDM } from './utils/dm-gen';
5
5
  import * as UniDMTools from './utils/dm-gen';
6
6
  import * as UniIDTools from './utils/id-gen';
7
7
  import * as platform from './utils/platform';
8
+ interface DanUniConvertTip {
9
+ meassage: string;
10
+ version: string;
11
+ data?: string;
12
+ }
8
13
  export interface DM_XML_Bili {
9
14
  i: {
10
15
  chatserver: string;
@@ -28,12 +33,14 @@ export interface DM_JSON_Dplayer {
28
33
  data: [number, number, number, string, string][];
29
34
  }
30
35
  export interface DM_JSON_Artplayer {
31
- text: string;
32
- time?: number;
33
- mode?: number;
34
- color?: string;
35
- border?: boolean;
36
- style?: {};
36
+ danmuku: {
37
+ text: string;
38
+ time?: number;
39
+ mode?: number;
40
+ color?: string;
41
+ border?: boolean;
42
+ style?: {};
43
+ }[];
37
44
  }
38
45
  export interface DM_JSON_DDPlay {
39
46
  count: number | string;
@@ -54,7 +61,13 @@ interface Options {
54
61
  export declare class UniPool {
55
62
  dans: UniDM[];
56
63
  options: Options;
57
- constructor(dans: UniDM[], options?: Options);
64
+ private info;
65
+ constructor(dans: UniDM[], options?: Options, info?: {
66
+ /**
67
+ * 是否从已被转换过的第三方格式弹幕再次转换而来
68
+ */
69
+ fromConverted: boolean;
70
+ });
58
71
  pipe(fn: UniPoolPipe): Promise<UniPool>;
59
72
  pipeSync(fn: UniPoolPipeSync): UniPool;
60
73
  get shared(): shareItems;
@@ -125,25 +138,54 @@ export declare class UniPool {
125
138
  */
126
139
  merge(lifetime?: number): UniPool;
127
140
  minify(): (Partial<UniDMTools.UniDMObj> & Pick<UniDMTools.UniDMObj, "SOID">)[];
128
- convert2(format: DM_format, continue_on_error?: boolean): string | Uint8Array<ArrayBufferLike> | UniDM[] | DM_JSON_Dplayer | DM_JSON_DDPlay | DM_JSON_Artplayer[];
141
+ convert2(format: DM_format, continue_on_error?: boolean): string | Uint8Array<ArrayBufferLike> | UniDM[] | (DM_JSON_Dplayer & {
142
+ danuni?: DanUniConvertTip;
143
+ }) | (DM_JSON_Artplayer & {
144
+ danuni?: DanUniConvertTip;
145
+ }) | (DM_JSON_DDPlay & {
146
+ danuni?: DanUniConvertTip;
147
+ });
129
148
  static fromPb(bin: Uint8Array | ArrayBuffer, options?: Options): UniPool;
130
149
  /**
131
150
  * 转为 protobuf 二进制
132
151
  */
133
152
  toPb(): Uint8Array<ArrayBufferLike>;
134
153
  static fromBiliXML(xml: string, options?: Options): UniPool;
135
- toBiliXML(): string;
154
+ toBiliXML(options?: {
155
+ /**
156
+ * 当SOID非来源bili时,若此处指定则使用该值为cid,否则使用SOID
157
+ */
158
+ cid?: bigint;
159
+ /**
160
+ * 当仅含有来自bili的弹幕时,启用将保持发送者标识不含`@`
161
+ * @description
162
+ * bili的弹幕含midHash(crc),不启用该处使用senderID填充,启用则去除`@bili`部分,提高兼容性
163
+ */
164
+ avoidSenderIDWithAt?: boolean;
165
+ }): string;
136
166
  static fromBiliGrpc(bin: Uint8Array | ArrayBuffer, options?: Options): UniPool;
137
167
  /**
138
168
  * @param bin 符合`DmWebViewReplySchema`(bili视频meta)的protobuf二进制
139
169
  */
140
170
  static fromBiliCommandGrpc(bin: Uint8Array | ArrayBuffer, options?: Options): UniPool;
141
- static fromDplayer(json: DM_JSON_Dplayer, playerID: string, domain?: string, options?: Options): UniPool;
142
- toDplayer(): DM_JSON_Dplayer;
143
- static fromArtplayer(json: DM_JSON_Artplayer[], playerID: string, domain?: string, options?: Options): UniPool;
144
- toArtplayer(): DM_JSON_Artplayer[];
145
- static fromDDPlay(json: DM_JSON_DDPlay, episodeId: string, options?: Options): UniPool;
146
- toDDplay(): DM_JSON_DDPlay;
171
+ static fromDplayer(json: DM_JSON_Dplayer & {
172
+ danuni?: DanUniConvertTip;
173
+ }, playerID: string, domain?: string, options?: Options): UniPool;
174
+ toDplayer(): DM_JSON_Dplayer & {
175
+ danuni?: DanUniConvertTip;
176
+ };
177
+ static fromArtplayer(json: DM_JSON_Artplayer & {
178
+ danuni?: DanUniConvertTip;
179
+ }, playerID: string, domain?: string, options?: Options): UniPool;
180
+ toArtplayer(): DM_JSON_Artplayer & {
181
+ danuni?: DanUniConvertTip;
182
+ };
183
+ static fromDDPlay(json: DM_JSON_DDPlay & {
184
+ danuni?: DanUniConvertTip;
185
+ }, episodeId: string, options?: Options): UniPool;
186
+ toDDplay(): DM_JSON_DDPlay & {
187
+ danuni?: DanUniConvertTip;
188
+ };
147
189
  static fromASS(ass: string): UniPool;
148
190
  /**
149
191
  * 转换为ASS字幕格式的弹幕,需播放器支持多行ASS渲染
@@ -272,9 +272,13 @@ export declare class UniDM {
272
272
  */
273
273
  static transCtime(oriCtime: ctime, tsUnit?: 'ms' | 's'): Date;
274
274
  static transMode(oriMode: number, fmt: 'bili' | 'dplayer' | 'artplayer' | 'ddplay'): Modes;
275
- static fromBili(args: DMBili, cid?: bigint, options?: Options): UniDM;
275
+ static fromBili(args: DMBili, cid?: bigint, options?: Options, recSOID?: string): UniDM;
276
276
  toBiliXML(options?: {
277
- skipBiliCommand: boolean;
277
+ skipBiliCommand?: boolean;
278
+ /**
279
+ * 见 ../index.ts UniPool.toBiliXML() 的 options,该option不宜手动调用,判断逻辑未封装
280
+ */
281
+ avoidSenderIDWithAt?: boolean;
278
282
  }): {
279
283
  '#text': string;
280
284
  '@_p': string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-uni/dan-any",
3
- "version": "0.8.1",
3
+ "version": "0.8.5",
4
4
  "description": "A danmaku transformer lib, supporting danmaku from different platforms.",
5
5
  "keywords": [
6
6
  "bangumi",
package/src/index.test.ts CHANGED
@@ -34,18 +34,21 @@ describe('转化自', () => {
34
34
  console.info(xml)
35
35
  console.info(pool)
36
36
  console.info(pool.toBiliXML())
37
+ console.info(pool.toBiliXML({ avoidSenderIDWithAt: true }))
37
38
  })
38
39
  it('artplayer(json)', () => {
39
- const json = [
40
- {
41
- text: 'artplayer测试弹幕', // 弹幕文本
42
- time: 10, // 弹幕时间, 默认为当前播放器时间
43
- mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
44
- color: '#FFFFFF', // 弹幕颜色,默认为白色
45
- border: false, // 弹幕是否有描边, 默认为 false
46
- style: { border: '10rem' }, // 弹幕自定义样式, 默认为空对象
47
- },
48
- ],
40
+ const json = {
41
+ danmuku: [
42
+ {
43
+ text: 'artplayer测试弹幕', // 弹幕文本
44
+ time: 10, // 弹幕时间, 默认为当前播放器时间
45
+ mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
46
+ color: '#FFFFFF', // 弹幕颜色,默认为白色
47
+ border: false, // 弹幕是否有描边, 默认为 false
48
+ style: { border: '10rem' }, // 弹幕自定义样式, 默认为空对象
49
+ },
50
+ ],
51
+ },
49
52
  pool = UniPool.fromArtplayer(json, 'playerid-test', 'acfun')
50
53
  console.info(json)
51
54
  console.info(pool)
@@ -57,6 +60,16 @@ describe('转化自', () => {
57
60
  console.info(ass)
58
61
  console.info(UniPool.fromASS(ass))
59
62
  })
63
+ it('pb[双向]', () => {
64
+ const pool = UniPool.fromBiliXML(xml)
65
+ const pb = pool.toPb()
66
+ console.info(UniPool.fromPb(pb))
67
+ })
68
+ it('DDplay[双向]', () => {
69
+ const pool = UniPool.fromBiliXML(xml)
70
+ const ddplay = pool.toDDplay()
71
+ console.info(UniPool.fromDDPlay(ddplay, '1'))
72
+ })
60
73
  })
61
74
 
62
75
  describe('共通值', () => {
package/src/index.ts CHANGED
@@ -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())
@@ -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()
@@ -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
  }