@dan-uni/dan-any 0.9.8 → 1.0.0

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.
@@ -116,17 +116,18 @@ export interface DM_JSON_DDPlay {
116
116
  m: string;
117
117
  }[];
118
118
  }
119
- export type DM_format = 'danuni.json' | 'danuni.pb.bin' | 'bili.xml' | 'bili.pb.bin' | 'bili.cmd.pb.bin' | 'dplayer.json' | 'artplayer.json' | 'ddplay.json' | 'common.ass';
119
+ export type DM_format = 'danuni.json' | 'danuni.pb.bin' | 'bili.xml' | 'bili.pb.bin' | 'bili.cmd.pb.bin' | 'bili.up.json' | 'dplayer.json' | 'artplayer.json' | 'ddplay.json' | 'common.ass';
120
120
  type shareItems = Partial<Pick<UniDMTools.UniDMObj, 'SOID' | 'senderID' | 'platform' | 'SOID' | 'pool' | 'mode' | 'color'>>;
121
121
  type statItems = Partial<Pick<UniDMTools.UniDMObj, 'SOID' | 'mode' | 'fontsize' | 'color' | 'senderID' | 'content' | 'weight' | 'pool' | 'platform'>>;
122
- interface Stat {
123
- val: statItems[keyof statItems];
124
- count: number;
125
- }
122
+ type Stats<T extends keyof statItems> = Map<statItems[T], number>;
126
123
  type UniPoolPipe = (that: UniPool) => Promise<UniPool>;
127
124
  type UniPoolPipeSync = (that: UniPool) => UniPool;
128
125
  export interface Options {
129
126
  dedupe?: boolean;
127
+ /**
128
+ * @description
129
+ * 当为`false`时,关闭DMID生成; 当为正整数时,表示生成DMID的截取位数; 或可传入一个DMID生成器实例
130
+ */
130
131
  dmid?: boolean | number | UniIDTools.DMIDGenerator;
131
132
  }
132
133
  export declare class UniPool {
@@ -146,10 +147,19 @@ export declare class UniPool {
146
147
  });
147
148
  pipe(fn: UniPoolPipe): Promise<UniPool>;
148
149
  pipeSync(fn: UniPoolPipeSync): UniPool;
150
+ /**
151
+ * @deprecated 使用 `getShared` 代替
152
+ */
149
153
  get shared(): shareItems;
150
- getShared(key: keyof shareItems): shareItems[keyof shareItems];
151
- getStat(key: keyof statItems): Stat[];
152
- getMost(key: keyof statItems): Stat;
154
+ getShared<K extends keyof shareItems>(key: K): shareItems[K];
155
+ getStat<K extends keyof statItems>(key: K): Stats<K>;
156
+ getMost<K extends keyof statItems>(key: K): {
157
+ val: Partial<Pick<UniDMTools.UniDMObj, "SOID" | "mode" | "fontsize" | "color" | "senderID" | "content" | "weight" | "pool" | "platform">>[K] | undefined;
158
+ count: number;
159
+ };
160
+ /**
161
+ * @deprecated 使用 `getMost` 代替
162
+ */
153
163
  get most(): {
154
164
  mode: UniDMTools.Modes;
155
165
  fontsize: number;
@@ -76,9 +76,9 @@ export type Danmaku = Message<"danuni.danmaku.v1.Danmaku"> & {
76
76
  */
77
77
  attr: string[];
78
78
  /**
79
- * @generated from field: string platform = 13;
79
+ * @generated from field: optional string platform = 13;
80
80
  */
81
- platform: string;
81
+ platform?: string;
82
82
  /**
83
83
  * @generated from field: optional string extra = 14;
84
84
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-uni/dan-any",
3
- "version": "0.9.8",
3
+ "version": "1.0.0",
4
4
  "description": "A danmaku transformer lib, supporting danmaku from different platforms.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -32,11 +32,11 @@
32
32
  "buf": "buf generate"
33
33
  },
34
34
  "dependencies": {
35
- "@bufbuild/protobuf": "^2.9.0",
35
+ "@bufbuild/protobuf": "^2.10.1",
36
36
  "base16384": "^1.0.0",
37
37
  "class-transformer": "^0.5.1",
38
38
  "class-validator": "^0.14.2",
39
- "fast-xml-parser": "^5.3.0",
39
+ "fast-xml-parser": "^5.3.2",
40
40
  "fs-extra": "^11.3.2",
41
41
  "hh-mm-ss": "^1.2.0",
42
42
  "json-bigint": "^1.0.0",
@@ -44,8 +44,8 @@
44
44
  "reflect-metadata": "^0.2.2"
45
45
  },
46
46
  "devDependencies": {
47
- "@bufbuild/buf": "^1.58.0",
48
- "@bufbuild/protoc-gen-es": "^2.9.0",
47
+ "@bufbuild/buf": "^1.60.0",
48
+ "@bufbuild/protoc-gen-es": "^2.10.1",
49
49
  "@types/fs-extra": "^11.0.4",
50
50
  "@types/hh-mm-ss": "^1.2.3",
51
51
  "@types/json-bigint": "^1.0.4",
@@ -34,11 +34,11 @@ const scrollCommand = ({
34
34
  start: number
35
35
  end: number
36
36
  top: number
37
- }) => `\\move(${start},${top},${end},${top})`
37
+ }) => String.raw`\move(${start},${top},${end},${top})`
38
38
  const fixCommand = ({ top, left }: { top: number; left: number }) =>
39
- `\\an8\\pos(${left},${top})`
40
- const colorCommand = (color: RGB) => `\\c${formatColor(color)}`
41
- const borderColorCommand = (color: RGB) => `\\3c${formatColor(color)}`
39
+ String.raw`\an8\pos(${left},${top})`
40
+ const colorCommand = (color: RGB) => String.raw`\c${formatColor(color)}`
41
+ const borderColorCommand = (color: RGB) => String.raw`\3c${formatColor(color)}`
42
42
 
43
43
  export const dialogue = (
44
44
  danmaku: {
package/src/index.ts CHANGED
@@ -157,6 +157,7 @@ export type DM_format =
157
157
  | 'bili.xml'
158
158
  | 'bili.pb.bin'
159
159
  | 'bili.cmd.pb.bin'
160
+ | 'bili.up.json'
160
161
  | 'dplayer.json'
161
162
  | 'artplayer.json'
162
163
  | 'ddplay.json'
@@ -182,16 +183,17 @@ type statItems = Partial<
182
183
  | 'platform'
183
184
  >
184
185
  >
185
- interface Stat {
186
- val: statItems[keyof statItems]
187
- count: number
188
- }
186
+ type Stats<T extends keyof statItems> = Map<statItems[T], number>
189
187
 
190
188
  type UniPoolPipe = (that: UniPool) => Promise<UniPool>
191
189
  type UniPoolPipeSync = (that: UniPool) => UniPool
192
190
 
193
191
  export interface Options {
194
192
  dedupe?: boolean
193
+ /**
194
+ * @description
195
+ * 当为`false`时,关闭DMID生成; 当为正整数时,表示生成DMID的截取位数; 或可传入一个DMID生成器实例
196
+ */
195
197
  dmid?: boolean | number | UniIDTools.DMIDGenerator
196
198
  }
197
199
 
@@ -215,51 +217,109 @@ export class UniPool {
215
217
  pipeSync(fn: UniPoolPipeSync): UniPool {
216
218
  return fn(this)
217
219
  }
220
+ /**
221
+ * @deprecated 使用 `getShared` 代替
222
+ */
218
223
  get shared(): shareItems {
219
- const isShared = (key: keyof UniDMTools.UniDMObj) => {
220
- return this.dans.every((d) => d[key])
224
+ if (this.dans.length === 0) return {}
225
+ const keys: (keyof shareItems)[] = [
226
+ 'SOID',
227
+ 'senderID',
228
+ 'platform',
229
+ 'pool',
230
+ 'mode',
231
+ 'color',
232
+ ]
233
+ const result: shareItems = {} as shareItems
234
+ for (const key of keys) {
235
+ const sharedVal = this.getShared(key)
236
+ if (sharedVal !== undefined) {
237
+ result[key] = sharedVal as any
238
+ }
221
239
  }
222
- return {
223
- SOID: isShared('SOID') ? this.dans[0].SOID : undefined,
224
- senderID: isShared('senderID') ? this.dans[0].senderID : undefined,
225
- platform: isShared('platform') ? this.dans[0].platform : undefined,
226
- pool: isShared('pool') ? this.dans[0].pool : undefined,
227
- mode: isShared('mode') ? this.dans[0].mode : undefined,
228
- color: isShared('color') ? this.dans[0].color : undefined,
240
+ return result
241
+ }
242
+ getShared<K extends keyof shareItems>(key: K): shareItems[K] {
243
+ if (this.dans.length === 0) return undefined
244
+ const firstVal = this.dans[0][key]
245
+ for (let i = 1; i < this.dans.length; i++) {
246
+ if (this.dans[i][key] !== firstVal) {
247
+ return undefined
248
+ }
229
249
  }
250
+ return firstVal
230
251
  }
231
- getShared(key: keyof shareItems): shareItems[keyof shareItems] {
232
- const isShared = (key: keyof UniDMTools.UniDMObj) => {
233
- return this.dans.every((d) => d[key])
252
+ getStat<K extends keyof statItems>(key: K): Stats<K> {
253
+ const statMap = new Map<statItems[K], number>()
254
+ for (const dan of this.dans) {
255
+ const val = dan[key]
256
+ statMap.set(val, (statMap.get(val) || 0) + 1)
234
257
  }
235
- return isShared(key) ? this.dans[0][key] : undefined
236
- }
237
- getStat(key: keyof statItems): Stat[] {
238
- const default_stat: Stat[] = []
239
- const stats = this.dans.reduce((stat, dan) => {
240
- const valWithCount = stat.find((i) => i.val === dan[key])
241
- if (valWithCount) {
242
- valWithCount.count++
243
- } else {
244
- stat.push({ val: dan[key], count: 1 })
258
+ return statMap
259
+ }
260
+ getMost<K extends keyof statItems>(key: K) {
261
+ const stats = this.getStat(key)
262
+ if (stats.size === 0) return { val: undefined, count: 0 }
263
+ let mostVal: statItems[K] | undefined
264
+ let maxCount = 0
265
+ for (const [val, count] of stats.entries()) {
266
+ if (count > maxCount) {
267
+ maxCount = count
268
+ mostVal = val
245
269
  }
246
- return stat
247
- }, default_stat)
248
- return stats
249
- }
250
- getMost(key: keyof statItems) {
251
- return this.getStat(key).toSorted((a, b) => b.count - a.count)[0]
270
+ }
271
+ return { val: mostVal, count: maxCount }
252
272
  }
273
+ /**
274
+ * @deprecated 使用 `getMost` 代替
275
+ */
253
276
  get most() {
277
+ const keys: (keyof statItems)[] = [
278
+ 'mode',
279
+ 'fontsize',
280
+ 'color',
281
+ 'senderID',
282
+ 'content',
283
+ 'weight',
284
+ 'pool',
285
+ 'platform',
286
+ ]
287
+ const statMaps = new Map<
288
+ keyof statItems,
289
+ Map<statItems[keyof statItems], number>
290
+ >()
291
+ for (const dan of this.dans) {
292
+ for (const key of keys) {
293
+ if (!statMaps.has(key)) {
294
+ statMaps.set(key, new Map())
295
+ }
296
+ const statMap = statMaps.get(key)!
297
+ const val = dan[key]
298
+ statMap.set(val, (statMap.get(val) || 0) + 1)
299
+ }
300
+ }
301
+ const result: Record<string, any> = {}
302
+ for (const key of keys) {
303
+ const statMap = statMaps.get(key)!
304
+ let mostVal: statItems[keyof statItems] | undefined
305
+ let maxCount = 0
306
+ for (const [val, count] of statMap.entries()) {
307
+ if (count > maxCount) {
308
+ maxCount = count
309
+ mostVal = val
310
+ }
311
+ }
312
+ result[key] = mostVal
313
+ }
254
314
  return {
255
- mode: this.getMost('mode').val as UniDMTools.Modes,
256
- fontsize: this.getMost('fontsize').val as number,
257
- color: this.getMost('color').val as number,
258
- senderID: this.getMost('senderID').val as string,
259
- content: this.getMost('content').val as string,
260
- weight: this.getMost('weight').val as number,
261
- pool: this.getMost('pool').val as UniDMTools.Pools,
262
- platform: this.getMost('platform').val as string | undefined,
315
+ mode: result.mode as UniDMTools.Modes,
316
+ fontsize: result.fontsize as number,
317
+ color: result.color as number,
318
+ senderID: result.senderID as string,
319
+ content: result.content as string,
320
+ weight: result.weight as number,
321
+ pool: result.pool as UniDMTools.Pools,
322
+ platform: result.platform as string | undefined,
263
323
  }
264
324
  }
265
325
  static create(options?: Options) {
@@ -285,7 +345,7 @@ export class UniPool {
285
345
  * 按共通属性拆分弹幕库
286
346
  */
287
347
  split(key: keyof shareItems) {
288
- if (this.shared[key]) return [this]
348
+ if (this.getShared(key)) return [this]
289
349
  const set = new Set(this.dans.map((d) => d[key]))
290
350
  return [...set].map((v) => {
291
351
  return new UniPool(
@@ -299,6 +359,7 @@ export class UniPool {
299
359
  * 基于DMID的基本去重功能,用于解决该class下dans为array而非Set的问题
300
360
  */
301
361
  private dedupe() {
362
+ // 这里基本上没有性能瓶颈(大文件测试与AI优化下无明显区别)
302
363
  if (this.options.dmid !== false) {
303
364
  const map = new Map()
304
365
  this.dans.forEach((d) => map.set(d.DMID || d.toDMID(), d))
@@ -311,104 +372,77 @@ export class UniPool {
311
372
  * @param lifetime 查重时间区段,单位秒 (默认为 0,表示不查重)
312
373
  */
313
374
  merge(lifetime = 0) {
314
- if (!this.shared.SOID) {
375
+ if (!this.getShared('SOID')) {
315
376
  console.error(
316
377
  "本功能仅支持同弹幕库内使用,可先 .split('SOID') 在分别使用",
317
378
  )
318
379
  return this
319
380
  }
320
381
  if (lifetime <= 0) return this
321
- const mergeContext = this.dans.reduce<
322
- [
323
- UniDM[],
324
- Record<string, UniDM>,
325
- Record<string, UniDMTools.ExtraDanUniMerge>,
326
- ]
327
- >(
328
- ([result, cache, mergeObj], danmaku) => {
329
- const key = ['content', 'mode', 'pool', 'platform']
330
- .map((k) => danmaku[k as keyof UniDM])
331
- .join('|')
332
- const cached = cache[key]
333
- const lastAppearTime = cached?.progress || 0
334
- if (
335
- cached &&
336
- danmaku.progress - lastAppearTime <= lifetime &&
337
- danmaku.isSameAs(cached, { skipDanuniMerge: true })
338
- ) {
339
- const senders = mergeObj[key].senders
340
- senders.push(danmaku.senderID)
341
- const extra = danmaku.extra
342
- extra.danuni = extra.danuni || {}
343
- extra.danuni.merge = {
344
- count: senders.length,
345
- duration: Number.parseFloat(
346
- (danmaku.progress - cached.progress).toFixed(3),
347
- ),
348
- senders,
349
- taolu_count: senders.length,
350
- taolu_senders: senders,
351
- }
352
- danmaku.extraStr = JSON.stringify(extra)
353
- cache[key] = danmaku
354
- mergeObj[key] = extra.danuni.merge
355
- return [result, cache, mergeObj]
356
- } else {
357
- mergeObj[key] = {
358
- count: 1,
359
- duration: 0,
360
- senders: [danmaku.senderID],
361
- taolu_count: 1,
362
- taolu_senders: [danmaku.senderID],
363
- }
364
- cache[key] = danmaku
365
- // 初始化merge信息,包含第一个sender
366
- const extra = danmaku.extra
367
- extra.danuni = extra.danuni || {}
368
- extra.danuni.merge = mergeObj[key]
369
- danmaku.extraStr = JSON.stringify(extra)
370
- result.push(danmaku)
371
- return [result, cache, mergeObj]
382
+ const result: UniDM[] = []
383
+ const cache: Record<string, UniDM> = {}
384
+ const mergeObj: Record<string, UniDMTools.ExtraDanUniMerge> = {}
385
+ // 第一遍:合并弹幕
386
+ for (const danmaku of this.dans) {
387
+ const key = `${danmaku.content}|${danmaku.mode}|${danmaku.pool}|${danmaku.platform}`
388
+ const cached = cache[key]
389
+
390
+ if (
391
+ cached &&
392
+ danmaku.progress - cached.progress <= lifetime &&
393
+ danmaku.isSameAs(cached, { skipDanuniMerge: true })
394
+ ) {
395
+ // 更新已存在的弹幕
396
+ mergeObj[key].senders.push(danmaku.senderID)
397
+ mergeObj[key].count = mergeObj[key].senders.length
398
+ mergeObj[key].taolu_count = mergeObj[key].count
399
+ mergeObj[key].taolu_senders = mergeObj[key].senders
400
+ mergeObj[key].duration = Number.parseFloat(
401
+ (danmaku.progress - cached.progress).toFixed(3),
402
+ )
403
+ cache[key] = danmaku
404
+ } else {
405
+ // 新弹幕
406
+ mergeObj[key] = {
407
+ count: 1,
408
+ duration: 0,
409
+ senders: [danmaku.senderID],
410
+ taolu_count: 1,
411
+ taolu_senders: [danmaku.senderID],
372
412
  }
373
- },
374
- [[], {}, {}],
375
- )
376
- // 处理结果,删除senders<=1的merge字段
377
- const [result, _cache, mergeObj] = mergeContext
378
- result.forEach((danmaku, i) => {
379
- const key = ['content', 'mode', 'platform', 'pool']
380
- .map((k) => danmaku[k as keyof UniDM])
381
- .join('|')
382
- const extra = result[i].extra
413
+ cache[key] = danmaku
414
+ result.push(danmaku)
415
+ }
416
+ }
417
+ // 第二遍:更新 extraStr
418
+ for (const danmaku of result) {
419
+ const key = `${danmaku.content}|${danmaku.mode}|${danmaku.pool}|${danmaku.platform}`
383
420
  const mergeData = mergeObj[key]
384
- result[i].extraStr = JSON.stringify({
385
- ...extra,
386
- danuni: {
387
- ...extra.danuni,
388
- merge: mergeData,
389
- },
390
- } satisfies UniDMTools.Extra)
391
- if (mergeData?.count) {
392
- if (mergeData.count <= 1) {
393
- const updatedExtra = { ...extra }
394
- if (updatedExtra.danuni) {
395
- delete updatedExtra.danuni.merge
396
- if (Object.keys(updatedExtra.danuni).length === 0) {
397
- delete updatedExtra.danuni
398
- }
421
+ const extra = danmaku.extra
422
+ if (mergeData.count > 1) {
423
+ // 多个发送者:设置为机器人并添加保护标记
424
+ danmaku.senderID = 'merge[bot]@dan-any'
425
+ if (!danmaku.attr) {
426
+ danmaku.attr = [UniDMTools.DMAttr.Protect]
427
+ } else if (!danmaku.attr.includes(UniDMTools.DMAttr.Protect)) {
428
+ danmaku.attr.push(UniDMTools.DMAttr.Protect)
429
+ }
430
+
431
+ extra.danuni = extra.danuni || {}
432
+ extra.danuni.merge = mergeData
433
+ danmaku.extraStr = JSON.stringify(extra)
434
+ } else {
435
+ // 单个发送者:清理 merge 字段
436
+ if (extra.danuni?.merge) {
437
+ delete extra.danuni.merge
438
+ if (Object.keys(extra.danuni).length === 0) {
439
+ delete extra.danuni
399
440
  }
400
- result[i].extraStr =
401
- Object.keys(updatedExtra).length > 0
402
- ? JSON.stringify(updatedExtra)
403
- : undefined
404
- } else {
405
- result[i].senderID = 'merge[bot]@dan-any'
406
- result[i].attr
407
- ? result[i].attr.push(UniDMTools.DMAttr.Protect)
408
- : (result[i].attr = [UniDMTools.DMAttr.Protect])
409
441
  }
442
+ danmaku.extraStr =
443
+ Object.keys(extra).length > 0 ? JSON.stringify(extra) : undefined
410
444
  }
411
- })
445
+ }
412
446
  return new UniPool(result, this.options, this.info)
413
447
  }
414
448
  minify() {
@@ -427,7 +461,8 @@ export class UniPool {
427
461
  const parseJSON = (
428
462
  json: DM_JSON_Artplayer &
429
463
  DM_JSON_DDPlay &
430
- DM_JSON_Dplayer & { danuni?: DanUniConvertTip },
464
+ DM_JSON_Dplayer &
465
+ DM_JSON_BiliUp & { danuni?: DanUniConvertTip },
431
466
  ): { pool: UniPool; fmt: DM_format } | undefined => {
432
467
  try {
433
468
  if (Array.isArray(json) && json.every((d) => d.SOID)) {
@@ -445,6 +480,7 @@ export class UniPool {
445
480
  } else if (
446
481
  json.count &&
447
482
  json.comments &&
483
+ Array.isArray(json.comments) &&
448
484
  json.comments.every((d) => d.m)
449
485
  ) {
450
486
  return {
@@ -452,8 +488,9 @@ export class UniPool {
452
488
  fmt: 'ddplay.json',
453
489
  }
454
490
  } else if (
455
- json?.code == 0 &&
491
+ json.code == 0 &&
456
492
  json.data &&
493
+ Array.isArray(json.data) &&
457
494
  json.data.every((d) => Array.isArray(d))
458
495
  ) {
459
496
  return {
@@ -465,6 +502,19 @@ export class UniPool {
465
502
  ),
466
503
  fmt: 'dplayer.json',
467
504
  }
505
+ } else if (
506
+ json.code == 0 &&
507
+ json.message == '0' &&
508
+ json.data &&
509
+ json.data.page &&
510
+ json.data.result &&
511
+ Array.isArray(json.data.result) &&
512
+ json.data.result.every((d) => d.id && d.oid)
513
+ ) {
514
+ return {
515
+ pool: this.fromBiliUp(json, options),
516
+ fmt: 'bili.up.json',
517
+ }
468
518
  }
469
519
  } catch {}
470
520
  }
@@ -678,7 +728,7 @@ export class UniPool {
678
728
  state: 0,
679
729
  real_name: 0,
680
730
  source: 'k-v',
681
- danuni: { ...DanUniConvertTipTemplate, data: this.shared.SOID },
731
+ danuni: { ...DanUniConvertTipTemplate, data: this.getShared('SOID') },
682
732
  d: this.dans.map((dan) => dan.toBiliXML(options)),
683
733
  },
684
734
  })
@@ -880,7 +930,7 @@ export class UniPool {
880
930
  toASS(canvasCtx: CanvasCtx, options?: AssGenOptions): string {
881
931
  const defaultOptions: AssGenOptions = { substyle: {} }
882
932
  const finalOptions = options ?? defaultOptions
883
- const fn = this.shared.SOID
933
+ const fn = this.getShared('SOID')
884
934
  return generateASS(
885
935
  this,
886
936
  { filename: fn, title: fn, ...finalOptions },
@@ -1,4 +1,4 @@
1
- // @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
1
+ // @generated by protoc-gen-es v2.10.1 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.9.0 with parameter "target=ts"
1
+ // @generated by protoc-gen-es v2.10.1 with parameter "target=ts"
2
2
  // @generated from file danuni.proto (package danuni.danmaku.v1, syntax proto3)
3
3
  /* eslint-disable */
4
4
 
@@ -12,7 +12,7 @@ import type { Message } from "@bufbuild/protobuf";
12
12
  * Describes the file danuni.proto.
13
13
  */
14
14
  export const file_danuni: GenFile = /*@__PURE__*/
15
- fileDesc("CgxkYW51bmkucHJvdG8SEWRhbnVuaS5kYW5tYWt1LnYxIjIKCmxpc3REYW5SZXESCgoCSUQYASABKAkSEAoDc2VnGAIgASgFSACIAQFCBgoEX3NlZyLCAgoHRGFubWFrdRIMCgRTT0lEGAEgASgJEgwKBERNSUQYAiABKAkSEAoIcHJvZ3Jlc3MYAyABKAUSJQoEbW9kZRgEIAEoDjIXLmRhbnVuaS5kYW5tYWt1LnYxLk1vZGUSEAoIZm9udHNpemUYBSABKAUSDQoFY29sb3IYBiABKAUSEAoIc2VuZGVySUQYByABKAkSDwoHY29udGVudBgIIAEoCRIpCgVjdGltZRgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDgoGd2VpZ2h0GAogASgFEiUKBHBvb2wYCyABKA4yFy5kYW51bmkuZGFubWFrdS52MS5Qb29sEgwKBGF0dHIYDCADKAkSEAoIcGxhdGZvcm0YDSABKAkSEgoFZXh0cmEYDiABKAlIAIgBAUIICgZfZXh0cmEiPAoMRGFubWFrdVJlcGx5EiwKCGRhbm1ha3VzGAEgAygLMhouZGFudW5pLmRhbm1ha3UudjEuRGFubWFrdSo9CgRNb2RlEgoKBk5vcm1hbBAAEgoKBkJvdHRvbRABEgcKA1RvcBACEgsKB1JldmVyc2UQAxIHCgNFeHQQBCopCgRQb29sEgcKA0RlZhAAEgcKA1N1YhABEgcKA0FkdhACEgYKAkl4EAMyXQoORGFubWFrdVNlcnZpY2USSwoHbGlzdERhbhIdLmRhbnVuaS5kYW5tYWt1LnYxLmxpc3REYW5SZXEaHy5kYW51bmkuZGFubWFrdS52MS5EYW5tYWt1UmVwbHkiAFAAYgZwcm90bzM", [file_google_protobuf_timestamp]);
15
+ fileDesc("CgxkYW51bmkucHJvdG8SEWRhbnVuaS5kYW5tYWt1LnYxIjIKCmxpc3REYW5SZXESCgoCSUQYASABKAkSEAoDc2VnGAIgASgFSACIAQFCBgoEX3NlZyLUAgoHRGFubWFrdRIMCgRTT0lEGAEgASgJEgwKBERNSUQYAiABKAkSEAoIcHJvZ3Jlc3MYAyABKAUSJQoEbW9kZRgEIAEoDjIXLmRhbnVuaS5kYW5tYWt1LnYxLk1vZGUSEAoIZm9udHNpemUYBSABKAUSDQoFY29sb3IYBiABKAUSEAoIc2VuZGVySUQYByABKAkSDwoHY29udGVudBgIIAEoCRIpCgVjdGltZRgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDgoGd2VpZ2h0GAogASgFEiUKBHBvb2wYCyABKA4yFy5kYW51bmkuZGFubWFrdS52MS5Qb29sEgwKBGF0dHIYDCADKAkSFQoIcGxhdGZvcm0YDSABKAlIAIgBARISCgVleHRyYRgOIAEoCUgBiAEBQgsKCV9wbGF0Zm9ybUIICgZfZXh0cmEiPAoMRGFubWFrdVJlcGx5EiwKCGRhbm1ha3VzGAEgAygLMhouZGFudW5pLmRhbm1ha3UudjEuRGFubWFrdSo9CgRNb2RlEgoKBk5vcm1hbBAAEgoKBkJvdHRvbRABEgcKA1RvcBACEgsKB1JldmVyc2UQAxIHCgNFeHQQBCopCgRQb29sEgcKA0RlZhAAEgcKA1N1YhABEgcKA0FkdhACEgYKAkl4EAMyXQoORGFubWFrdVNlcnZpY2USSwoHbGlzdERhbhIdLmRhbnVuaS5kYW5tYWt1LnYxLmxpc3REYW5SZXEaHy5kYW51bmkuZGFubWFrdS52MS5EYW5tYWt1UmVwbHkiAFAAYgZwcm90bzM", [file_google_protobuf_timestamp]);
16
16
 
17
17
  /**
18
18
  * @generated from message danuni.danmaku.v1.listDanReq
@@ -101,9 +101,9 @@ export type Danmaku = Message<"danuni.danmaku.v1.Danmaku"> & {
101
101
  attr: string[];
102
102
 
103
103
  /**
104
- * @generated from field: string platform = 13;
104
+ * @generated from field: optional string platform = 13;
105
105
  */
106
- platform: string;
106
+ platform?: string;
107
107
 
108
108
  /**
109
109
  * @generated from field: optional string extra = 14;
@@ -41,7 +41,7 @@ message Danmaku {
41
41
  int32 weight = 10;
42
42
  Pool pool = 11;
43
43
  repeated string attr = 12;
44
- string platform = 13;
44
+ optional string platform = 13;
45
45
  optional string extra = 14;
46
46
  }
47
47
 
@@ -562,6 +562,8 @@ export class UniDM {
562
562
  }
563
563
  @Expose()
564
564
  isSameAs(dan: UniDM, options?: { skipDanuniMerge?: boolean }): boolean {
565
+ // 引用相同直接返回
566
+ if (this === dan) return true
565
567
  // 不支持比较高级弹幕
566
568
  if (this.mode === Modes.Ext || dan.mode === Modes.Ext) return false
567
569
  // 合并过视为不同,防止存在合并完成弹幕后再次合并造成计数错误
@@ -586,25 +588,20 @@ export class UniDM {
586
588
  @Expose()
587
589
  minify() {
588
590
  type UObj = Partial<UniDMObj> & Pick<UniDMObj, 'SOID'>
589
- const def: UObj = UniDM.create()
590
- const dan: UObj = UniDM.create(this)
591
- const shouldKeep = (key: keyof UObj, value: UObj[keyof UObj]) => {
592
- if (key === 'SOID') return true
593
- if (value === undefined || value === null) return false
594
- if (value === def[key]) return false
595
- if (key === 'attr' && Array.isArray(value) && value.length === 0)
596
- return false
597
- if (key === 'extraStr' && value === '{}') return false
598
- return true
599
- }
600
- const result: UObj = { SOID: dan.SOID }
601
- for (const key of Object.keys(dan) as (keyof UObj)[]) {
602
- const value = dan[key]
603
- if (shouldKeep(key, value)) {
604
- if (key === 'SOID') continue
605
- Reflect.set(result, key, value)
606
- }
607
- }
591
+ const def = new UniDM()
592
+ const result: UObj = { SOID: this.SOID }
593
+ if (this.progress !== def.progress) result.progress = this.progress
594
+ if (this.mode !== def.mode) result.mode = this.mode
595
+ if (this.fontsize !== def.fontsize) result.fontsize = this.fontsize
596
+ if (this.color !== def.color) result.color = this.color
597
+ if (this.senderID !== def.senderID) result.senderID = this.senderID
598
+ if (this.content !== def.content) result.content = this.content
599
+ if (this.weight !== def.weight) result.weight = this.weight
600
+ if (this.pool !== def.pool) result.pool = this.pool
601
+ if (this.attr.length > 0) result.attr = this.attr
602
+ if (this.platform !== undefined) result.platform = this.platform
603
+ if (this.extraStr && this.extraStr !== '{}') result.extraStr = this.extraStr
604
+ if (this.DMID !== undefined) result.DMID = this.DMID
608
605
  return result
609
606
  }
610
607
  @Expose()