@dan-uni/dan-any 1.2.2 → 1.2.7
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/README.md +1 -0
- package/dist/browser/17.min.js +28 -18
- package/dist/browser/705.min.js +77 -0
- package/dist/browser/index.min.js +1 -1
- package/dist/browser/plugins/bili.min.js +1 -0
- package/dist/browser/plugins/index.min.js +1 -1
- package/dist/browser/src/index.d.ts +13 -5
- package/dist/browser/src/plugins/bili/history-danmaku-fast-forward.d.ts +18 -0
- package/dist/browser/src/plugins/bili/index.d.ts +2 -0
- package/dist/browser/src/plugins/index.d.ts +1 -1
- package/dist/node/17.js +28 -18
- package/dist/node/705.js +77 -0
- package/dist/node/index.js +1 -1
- package/dist/node/plugins/bili.js +1 -0
- package/dist/node/plugins/index.js +1 -1
- package/dist/node/src/index.d.ts +13 -5
- package/dist/node/src/plugins/bili/history-danmaku-fast-forward.d.ts +18 -0
- package/dist/node/src/plugins/bili/index.d.ts +2 -0
- package/dist/node/src/plugins/index.d.ts +1 -1
- package/dist/umd/index.umd.min.js +44 -24
- package/dist/umd/plugins/{bili-dedupe.umd.min.js → bili.umd.min.js} +4644 -29
- package/dist/umd/plugins/index.umd.min.js +4646 -31
- package/dist/umd/src/index.d.ts +13 -5
- package/dist/umd/src/plugins/bili/history-danmaku-fast-forward.d.ts +18 -0
- package/dist/umd/src/plugins/bili/index.d.ts +2 -0
- package/dist/umd/src/plugins/index.d.ts +1 -1
- package/package.json +5 -3
- package/rslib.config.ts +1 -1
- package/src/index.ts +26 -32
- package/src/plugins/bili/README.md +87 -0
- package/src/plugins/bili/history-danmaku-fast-forward.ts +86 -0
- package/src/plugins/bili/index.test.ts +129 -0
- package/src/plugins/bili/index.ts +2 -0
- package/src/plugins/index.ts +1 -1
- package/src/plugins/stats/README.md +44 -0
- package/src/plugins/stats/index.test.ts +1 -1
- package/src/utils/dm-gen.ts +2 -2
- package/dist/browser/157.min.js +0 -30
- package/dist/browser/plugins/bili-dedupe.min.js +0 -1
- package/dist/node/157.js +0 -30
- package/dist/node/plugins/bili-dedupe.js +0 -1
- package/src/plugins/bili-dedupe/index.test.ts +0 -43
- /package/dist/browser/src/plugins/{bili-dedupe/index.d.ts → bili/dedupe.d.ts} +0 -0
- /package/dist/browser/src/plugins/{bili-dedupe → bili}/index.test.d.ts +0 -0
- /package/dist/node/src/plugins/{bili-dedupe/index.d.ts → bili/dedupe.d.ts} +0 -0
- /package/dist/node/src/plugins/{bili-dedupe → bili}/index.test.d.ts +0 -0
- /package/dist/umd/plugins/{bili-dedupe.umd.min.js.LICENSE.txt → bili.umd.min.js.LICENSE.txt} +0 -0
- /package/dist/umd/src/plugins/{bili-dedupe/index.d.ts → bili/dedupe.d.ts} +0 -0
- /package/dist/umd/src/plugins/{bili-dedupe → bili}/index.test.d.ts +0 -0
- /package/src/plugins/{bili-dedupe/index.ts → bili/dedupe.ts} +0 -0
package/dist/umd/src/index.d.ts
CHANGED
|
@@ -116,12 +116,21 @@ export interface DM_JSON_DDPlay {
|
|
|
116
116
|
m: string;
|
|
117
117
|
}[];
|
|
118
118
|
}
|
|
119
|
-
export
|
|
119
|
+
export declare enum DM_format {
|
|
120
|
+
DanuniJson = "danuni.json",
|
|
121
|
+
DanuniPbBin = "danuni.pb.bin",
|
|
122
|
+
BiliXml = "bili.xml",
|
|
123
|
+
BiliPbBin = "bili.pb.bin",
|
|
124
|
+
BiliCmdPbBin = "bili.cmd.pb.bin",
|
|
125
|
+
BiliUpJson = "bili.up.json",
|
|
126
|
+
DplayerJson = "dplayer.json",
|
|
127
|
+
ArtplayerJson = "artplayer.json",
|
|
128
|
+
DdplayJson = "ddplay.json",
|
|
129
|
+
CommonAss = "common.ass"
|
|
130
|
+
}
|
|
120
131
|
type shareItems = Partial<Pick<UniDMTools.UniDMObj, 'SOID' | 'senderID' | 'platform' | 'SOID' | 'pool' | 'mode' | 'color'>>;
|
|
121
132
|
type statItems = Partial<Pick<UniDMTools.UniDMObj, 'SOID' | 'mode' | 'fontsize' | 'color' | 'senderID' | 'content' | 'weight' | 'pool' | 'platform'>>;
|
|
122
133
|
type Stats<T extends keyof statItems> = Map<statItems[T], number>;
|
|
123
|
-
type UniPoolPipe = (that: UniPool) => Promise<any>;
|
|
124
|
-
type UniPoolPipeSync = (that: UniPool) => any;
|
|
125
134
|
export interface Options {
|
|
126
135
|
dedupe?: boolean;
|
|
127
136
|
/**
|
|
@@ -145,8 +154,7 @@ export declare class UniPool {
|
|
|
145
154
|
*/
|
|
146
155
|
fromConverted: boolean;
|
|
147
156
|
});
|
|
148
|
-
pipe<T extends (...args: any) => any
|
|
149
|
-
pipeSync<T extends (...args: any) => any = UniPoolPipeSync>(fn: T): ReturnType<T>;
|
|
157
|
+
pipe<T extends (...args: any) => any>(fn: T): ReturnType<T>;
|
|
150
158
|
/**
|
|
151
159
|
* @deprecated 使用 `getShared` 代替
|
|
152
160
|
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注:该方法受BiliPlus全弹幕下载器启发
|
|
3
|
+
* 详情参考以下链接
|
|
4
|
+
* @see https://github.com/HengXin666/BiLiBiLi_DanMu_Crawling/issues/14
|
|
5
|
+
*/
|
|
6
|
+
import type { UniPool } from '../..';
|
|
7
|
+
interface BiliHistoryDanmakuFastForwardResult {
|
|
8
|
+
earliest: string | null;
|
|
9
|
+
FastForward: string[];
|
|
10
|
+
skip: string[];
|
|
11
|
+
SpecificDate: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function bili_history_fast_forward(
|
|
14
|
+
/**
|
|
15
|
+
* 请求api时使用的日期(UTC+8, yyyy-MM-dd)
|
|
16
|
+
*/
|
|
17
|
+
query_history_date: string): (that: UniPool) => BiliHistoryDanmakuFastForwardResult;
|
|
18
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * as
|
|
1
|
+
export * as bili from './bili';
|
|
2
2
|
export * as stats from './stats';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dan-uni/dan-any",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.7",
|
|
5
5
|
"description": "A danmaku transformer lib, supporting danmaku from different platforms.",
|
|
6
6
|
"author": "rinne",
|
|
7
7
|
"license": "LGPL-3.0-or-later",
|
|
@@ -36,19 +36,21 @@
|
|
|
36
36
|
"base16384": "^1.0.0",
|
|
37
37
|
"class-transformer": "^0.5.1",
|
|
38
38
|
"class-validator": "^0.14.3",
|
|
39
|
-
"fast-xml-parser": "^5.3.
|
|
39
|
+
"fast-xml-parser": "^5.3.5",
|
|
40
40
|
"fs-extra": "^11.3.3",
|
|
41
41
|
"hh-mm-ss": "^1.2.0",
|
|
42
42
|
"json-bigint": "^1.0.0",
|
|
43
43
|
"jssha": "^3.3.1",
|
|
44
|
+
"luxon": "^3.7.2",
|
|
44
45
|
"reflect-metadata": "^0.2.2"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"@bufbuild/buf": "^1.
|
|
48
|
+
"@bufbuild/buf": "^1.65.0",
|
|
48
49
|
"@bufbuild/protoc-gen-es": "^2.11.0",
|
|
49
50
|
"@types/fs-extra": "^11.0.4",
|
|
50
51
|
"@types/hh-mm-ss": "^1.2.3",
|
|
51
52
|
"@types/json-bigint": "^1.0.4",
|
|
53
|
+
"@types/luxon": "^3.7.1",
|
|
52
54
|
"canvas": "^3.2.1",
|
|
53
55
|
"protobufjs": "^8.0.0"
|
|
54
56
|
}
|
package/rslib.config.ts
CHANGED
|
@@ -8,7 +8,7 @@ export default defineConfig({
|
|
|
8
8
|
entry: {
|
|
9
9
|
index: './src/index.ts',
|
|
10
10
|
'plugins/index': './src/plugins/index.ts',
|
|
11
|
-
'plugins/bili
|
|
11
|
+
'plugins/bili': './src/plugins/bili/index.ts',
|
|
12
12
|
'plugins/stats': './src/plugins/stats/index.ts',
|
|
13
13
|
},
|
|
14
14
|
},
|
package/src/index.ts
CHANGED
|
@@ -151,17 +151,18 @@ export interface DM_JSON_DDPlay {
|
|
|
151
151
|
}[]
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
export
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
154
|
+
export enum DM_format {
|
|
155
|
+
DanuniJson = 'danuni.json',
|
|
156
|
+
DanuniPbBin = 'danuni.pb.bin',
|
|
157
|
+
BiliXml = 'bili.xml',
|
|
158
|
+
BiliPbBin = 'bili.pb.bin',
|
|
159
|
+
BiliCmdPbBin = 'bili.cmd.pb.bin',
|
|
160
|
+
BiliUpJson = 'bili.up.json',
|
|
161
|
+
DplayerJson = 'dplayer.json',
|
|
162
|
+
ArtplayerJson = 'artplayer.json',
|
|
163
|
+
DdplayJson = 'ddplay.json',
|
|
164
|
+
CommonAss = 'common.ass',
|
|
165
|
+
}
|
|
165
166
|
|
|
166
167
|
type shareItems = Partial<
|
|
167
168
|
Pick<
|
|
@@ -185,9 +186,6 @@ type statItems = Partial<
|
|
|
185
186
|
>
|
|
186
187
|
type Stats<T extends keyof statItems> = Map<statItems[T], number>
|
|
187
188
|
|
|
188
|
-
type UniPoolPipe = (that: UniPool) => Promise<any>
|
|
189
|
-
type UniPoolPipeSync = (that: UniPool) => any
|
|
190
|
-
|
|
191
189
|
export interface Options {
|
|
192
190
|
dedupe?: boolean
|
|
193
191
|
/**
|
|
@@ -211,14 +209,7 @@ export class UniPool {
|
|
|
211
209
|
if (options.dedupe !== false) options.dedupe = true
|
|
212
210
|
if (this.options.dedupe) this.dedupe()
|
|
213
211
|
}
|
|
214
|
-
|
|
215
|
-
fn: T,
|
|
216
|
-
): Promise<ReturnType<T>> {
|
|
217
|
-
return fn(this)
|
|
218
|
-
}
|
|
219
|
-
pipeSync<T extends (...args: any) => any = UniPoolPipeSync>(
|
|
220
|
-
fn: T,
|
|
221
|
-
): ReturnType<T> {
|
|
212
|
+
pipe<T extends (...args: any) => any>(fn: T): ReturnType<T> {
|
|
222
213
|
return fn(this)
|
|
223
214
|
}
|
|
224
215
|
/**
|
|
@@ -470,7 +461,7 @@ export class UniPool {
|
|
|
470
461
|
): { pool: UniPool; fmt: DM_format } | undefined => {
|
|
471
462
|
try {
|
|
472
463
|
if (Array.isArray(json) && json.every((d) => d.SOID)) {
|
|
473
|
-
return { pool: new UniPool(json, options), fmt:
|
|
464
|
+
return { pool: new UniPool(json, options), fmt: DM_format.DanuniJson }
|
|
474
465
|
} else if (json.danmuku && json.danmuku.every((d) => d.text)) {
|
|
475
466
|
return {
|
|
476
467
|
pool: this.fromArtplayer(
|
|
@@ -479,7 +470,7 @@ export class UniPool {
|
|
|
479
470
|
undefined,
|
|
480
471
|
options,
|
|
481
472
|
),
|
|
482
|
-
fmt:
|
|
473
|
+
fmt: DM_format.ArtplayerJson,
|
|
483
474
|
}
|
|
484
475
|
} else if (
|
|
485
476
|
json.count &&
|
|
@@ -489,7 +480,7 @@ export class UniPool {
|
|
|
489
480
|
) {
|
|
490
481
|
return {
|
|
491
482
|
pool: this.fromDDPlay(json, json.danuni?.data ?? '', options),
|
|
492
|
-
fmt:
|
|
483
|
+
fmt: DM_format.DdplayJson,
|
|
493
484
|
}
|
|
494
485
|
} else if (
|
|
495
486
|
json.code == 0 &&
|
|
@@ -504,7 +495,7 @@ export class UniPool {
|
|
|
504
495
|
undefined,
|
|
505
496
|
options,
|
|
506
497
|
),
|
|
507
|
-
fmt:
|
|
498
|
+
fmt: DM_format.DplayerJson,
|
|
508
499
|
}
|
|
509
500
|
} else if (
|
|
510
501
|
json.code == 0 &&
|
|
@@ -517,7 +508,7 @@ export class UniPool {
|
|
|
517
508
|
) {
|
|
518
509
|
return {
|
|
519
510
|
pool: this.fromBiliUp(json, options),
|
|
520
|
-
fmt:
|
|
511
|
+
fmt: DM_format.BiliUpJson,
|
|
521
512
|
}
|
|
522
513
|
}
|
|
523
514
|
} catch {}
|
|
@@ -539,10 +530,13 @@ export class UniPool {
|
|
|
539
530
|
const xmlParser = new XMLParser({ ignoreAttributes: false })
|
|
540
531
|
const xml = xmlParser.parse(file)
|
|
541
532
|
if (xml?.i?.d)
|
|
542
|
-
return {
|
|
533
|
+
return {
|
|
534
|
+
pool: this.fromBiliXML(file, options),
|
|
535
|
+
fmt: DM_format.BiliXml,
|
|
536
|
+
}
|
|
543
537
|
} catch {}
|
|
544
538
|
try {
|
|
545
|
-
return { pool: this.fromASS(file, options), fmt:
|
|
539
|
+
return { pool: this.fromASS(file, options), fmt: DM_format.CommonAss }
|
|
546
540
|
} catch {}
|
|
547
541
|
}
|
|
548
542
|
}
|
|
@@ -552,15 +546,15 @@ export class UniPool {
|
|
|
552
546
|
// pure-bin (pb)
|
|
553
547
|
if (mod.includes('bin')) {
|
|
554
548
|
try {
|
|
555
|
-
return { pool: this.fromPb(file), fmt:
|
|
549
|
+
return { pool: this.fromPb(file), fmt: DM_format.DanuniPbBin }
|
|
556
550
|
} catch {}
|
|
557
551
|
try {
|
|
558
|
-
return { pool: this.fromBiliGrpc(file), fmt:
|
|
552
|
+
return { pool: this.fromBiliGrpc(file), fmt: DM_format.BiliPbBin }
|
|
559
553
|
} catch {}
|
|
560
554
|
try {
|
|
561
555
|
return {
|
|
562
556
|
pool: this.fromBiliCommandGrpc(file),
|
|
563
|
-
fmt:
|
|
557
|
+
fmt: DM_format.BiliCmdPbBin,
|
|
564
558
|
}
|
|
565
559
|
} catch {}
|
|
566
560
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Bili 插件
|
|
2
|
+
|
|
3
|
+
为 B 站(哔哩哔哩)弹幕处理提供的实用工具集合。
|
|
4
|
+
|
|
5
|
+
## 功能模块
|
|
6
|
+
|
|
7
|
+
### 1. 去重 (`dedupe`)
|
|
8
|
+
|
|
9
|
+
针对直接从 B 站 API 获取到的弹幕进行去重处理。
|
|
10
|
+
|
|
11
|
+
#### 导出函数
|
|
12
|
+
|
|
13
|
+
- **`to_bili_deduped(pool: UniPool): UniPool`** - 返回去重后的新 UniPool 实例
|
|
14
|
+
- **`bili_dedupe(pool: UniPool): void`** - 原地修改 UniPool 实例,移除重复弹幕
|
|
15
|
+
|
|
16
|
+
#### 去重示例
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { bili } from '@danuni/dan-any/plugins'
|
|
20
|
+
|
|
21
|
+
const pool = UniPool.import(biliData)
|
|
22
|
+
|
|
23
|
+
// 方式1:获取去重后的新实例
|
|
24
|
+
const _deduped = pool.pipe(bili.bili_dedupe.to_bili_deduped)
|
|
25
|
+
|
|
26
|
+
// 方式2:原地去重
|
|
27
|
+
pool.pipe(bili.bili_dedupe.bili_dedupe)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### 说明
|
|
31
|
+
|
|
32
|
+
- 使用 B 站 API 中的 `dmid`(弹幕 ID)作为去重依据
|
|
33
|
+
- **仅支持** B 站主站直接获取的弹幕(需要包含 `extra.bili.dmid` 字段)
|
|
34
|
+
- 若弹幕缺少 `dmid` 字段会抛出错误
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### 2. 历史弹幕逆向快进算法 (`history-danmaku-fast-forward`)
|
|
39
|
+
|
|
40
|
+
针对 B 站历史弹幕采用时间逆序查询时,快速确定完成获取/下一次获取位置的算法。
|
|
41
|
+
|
|
42
|
+
该方法受 [BiliPlus 全弹幕下载器](https://github.com/HengXin666/BiLiBiLi_DanMu_Crawling/issues/14) 启发。
|
|
43
|
+
|
|
44
|
+
#### 快进算法函数
|
|
45
|
+
|
|
46
|
+
- **`bili_history_fast_forward(query_history_date: string): Function`** - 返回处理函数
|
|
47
|
+
|
|
48
|
+
#### 快进算法示例
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { bili } from '@danuni/dan-any/plugins'
|
|
52
|
+
|
|
53
|
+
const pool = UniPool.import(biliHistoryData)
|
|
54
|
+
|
|
55
|
+
// 查询 2024-01-10 这一天的历史弹幕
|
|
56
|
+
const _result = pool.pipe(bili.bili_history_fast_forward('2024-01-10'))
|
|
57
|
+
|
|
58
|
+
// 返回值示例:
|
|
59
|
+
// {
|
|
60
|
+
// earliest: '2024-01-01', // 最早弹幕的日期
|
|
61
|
+
// FastForward: ['2024-01-01', '2024-01-02', '2024-01-04'], // 有弹幕的日期
|
|
62
|
+
// skip: ['2024-01-03', '2024-01-05', ...], // 无弹幕的日期
|
|
63
|
+
// SpecicDate: '2024-01-10' // 查询的日期
|
|
64
|
+
// }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### 返回值说明
|
|
68
|
+
|
|
69
|
+
| 字段 | 类型 | 说明 |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `earliest` | `string \| null` | 最早弹幕的日期(`yyyy-MM-dd` 格式),无弹幕时为 `null` |
|
|
72
|
+
| `FastForward` | `string[]` | 早于查询日期、且有弹幕的日期列表 |
|
|
73
|
+
| `skip` | `string[]` | `earliest` 到查询日期之间、无弹幕的日期列表 |
|
|
74
|
+
| `SpecicDate` | `string` | 查询的日期(`yyyy-MM-dd` 格式) |
|
|
75
|
+
|
|
76
|
+
#### 工作原理
|
|
77
|
+
|
|
78
|
+
1. 筛选出早于查询日期的所有弹幕
|
|
79
|
+
2. 找出最早弹幕的日期作为 `earliest`
|
|
80
|
+
3. 收集有弹幕的日期到 `FastForward` 列表
|
|
81
|
+
4. 找出没有弹幕的日期到 `skip` 列表
|
|
82
|
+
|
|
83
|
+
#### 时间处理
|
|
84
|
+
|
|
85
|
+
- 所有日期基于时区 `Asia/Shanghai`(UTC+8)
|
|
86
|
+
- 输入格式:`yyyy-MM-dd`
|
|
87
|
+
- 输出格式:`yyyy-MM-dd`
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注:该方法受BiliPlus全弹幕下载器启发
|
|
3
|
+
* 详情参考以下链接
|
|
4
|
+
* @see https://github.com/HengXin666/BiLiBiLi_DanMu_Crawling/issues/14
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { DateTime } from 'luxon'
|
|
8
|
+
import type { UniPool } from '../..'
|
|
9
|
+
|
|
10
|
+
interface BiliHistoryDanmakuFastForwardResult {
|
|
11
|
+
earliest: string | null
|
|
12
|
+
FastForward: string[]
|
|
13
|
+
skip: string[]
|
|
14
|
+
SpecificDate: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 针对B站历史弹幕使用时间逆序+快进以确定完成获取/下一次获取位置的算法
|
|
19
|
+
*
|
|
20
|
+
* 传入值是一个包含历史弹幕的UniPool,来自对任意一天(UTC+8, yyyy-MM-dd)的历史弹幕api
|
|
21
|
+
*/
|
|
22
|
+
function main(
|
|
23
|
+
that: UniPool,
|
|
24
|
+
query_history_date: string,
|
|
25
|
+
): BiliHistoryDanmakuFastForwardResult {
|
|
26
|
+
const qhd = DateTime.fromFormat(query_history_date, 'yyyy-MM-dd', {
|
|
27
|
+
zone: 'Asia/Shanghai',
|
|
28
|
+
}).setZone('Asia/Shanghai')
|
|
29
|
+
if (!qhd.isValid) throw new Error('Invalid query_history_date')
|
|
30
|
+
const s = qhd.startOf('day')
|
|
31
|
+
const before = that.dans.filter((d) => d.ctime < s.toJSDate())
|
|
32
|
+
|
|
33
|
+
if (before.length === 0) {
|
|
34
|
+
return {
|
|
35
|
+
earliest: null,
|
|
36
|
+
FastForward: [],
|
|
37
|
+
skip: [],
|
|
38
|
+
SpecificDate: qhd.toFormat('yyyy-MM-dd'),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 获取最早的日期(字符串格式)
|
|
43
|
+
const earliestCtime = before.toSorted(
|
|
44
|
+
(a, b) => a.ctime.getTime() - b.ctime.getTime(),
|
|
45
|
+
)[0].ctime
|
|
46
|
+
const earliestDate = DateTime.fromJSDate(earliestCtime)
|
|
47
|
+
.setZone('Asia/Shanghai')
|
|
48
|
+
.startOf('day')
|
|
49
|
+
|
|
50
|
+
// 提取有弹幕的日期集合
|
|
51
|
+
const datesWithDanmaku = new Set<string>()
|
|
52
|
+
for (const dan of before) {
|
|
53
|
+
const dateStr = DateTime.fromJSDate(dan.ctime)
|
|
54
|
+
.setZone('Asia/Shanghai')
|
|
55
|
+
.toFormat('yyyy-MM-dd')
|
|
56
|
+
datesWithDanmaku.add(dateStr)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 计算earliest到query_history_date之间的所有日期
|
|
60
|
+
let current = earliestDate
|
|
61
|
+
const allDates: string[] = []
|
|
62
|
+
while (current < s) {
|
|
63
|
+
allDates.push(current.toFormat('yyyy-MM-dd'))
|
|
64
|
+
current = current.plus({ days: 1 })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 区分fast-forward和skip
|
|
68
|
+
const FastForward = allDates.filter((d) => datesWithDanmaku.has(d))
|
|
69
|
+
const skip = allDates.filter((d) => !datesWithDanmaku.has(d))
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
earliest: earliestDate.toFormat('yyyy-MM-dd'),
|
|
73
|
+
FastForward,
|
|
74
|
+
skip,
|
|
75
|
+
SpecificDate: qhd.toFormat('yyyy-MM-dd'),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function bili_history_fast_forward(
|
|
80
|
+
/**
|
|
81
|
+
* 请求api时使用的日期(UTC+8, yyyy-MM-dd)
|
|
82
|
+
*/
|
|
83
|
+
query_history_date: string,
|
|
84
|
+
) {
|
|
85
|
+
return (that: UniPool) => main(that, query_history_date)
|
|
86
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
//基于以下注释,根据vitest生成测试用例
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { UniDM, UniPool } from '../..'
|
|
5
|
+
import { bili_dedupe, to_bili_deduped } from './dedupe'
|
|
6
|
+
import { bili_history_fast_forward } from './history-danmaku-fast-forward'
|
|
7
|
+
|
|
8
|
+
const xml = `<i>
|
|
9
|
+
<chatserver>chat.bilibili.com</chatserver>
|
|
10
|
+
<chatid>1156756312</chatid>
|
|
11
|
+
<mission>0</mission>
|
|
12
|
+
<maxlimit>2947</maxlimit>
|
|
13
|
+
<state>0</state>
|
|
14
|
+
<real_name>0</real_name>
|
|
15
|
+
<source>k-v</source>
|
|
16
|
+
<d p="13.213,1,25,16777215,1686314041,3,ff41173d,1335658005672492032">喜欢</d>
|
|
17
|
+
<d p="13.213,1,25,16777215,1686590010,0,296b35b5,1337972999512832512">来了 哈哈~~</d>
|
|
18
|
+
<d p="13.246,1,25,16777215,1686276875,0,5664cfc4,1335346233459549696">就是</d>
|
|
19
|
+
<d p="13.266,1,25,16777215,1686283375,0,c7e6646f,1335400761013670912">什么鬼?</d>
|
|
20
|
+
<d p="13.284,1,25,16777215,1686291338,0,38662881,1335517923728804864">哇哦</d>
|
|
21
|
+
<d p="13.306,1,25,16777215,1686268410,0,4c01de10,1335275224983600896">试试</d>
|
|
22
|
+
<d p="13.331,1,25,16777215,1686948453,3,56a3c5d5,1340979831550069760">不喜欢</d>
|
|
23
|
+
<d p="13.374,1,25,16777215,1686300770,3,647fe355,1335546672880933888">不喜欢</d>
|
|
24
|
+
<d p="13.376,1,25,16777215,1686297921,0,469d94b8,1335522778300134400">哦豁</d>
|
|
25
|
+
<d p="13.419,1,25,8700107,1686268005,0,be402447,1335271828100244224">太酷啦</d>
|
|
26
|
+
<d p="13.419,1,25,16777215,1686316828,3,7ffb6619,1335681385016736768">喜欢</d>
|
|
27
|
+
<d p="13.459,1,25,16777215,1686299729,0,45834405,1335537942797634048">一般,不好看</d>
|
|
28
|
+
<d p="13.462,1,25,16777215,1686302133,0,3cab672c,1335558106620590080">哈哈哈</d>
|
|
29
|
+
<d p="13.481,1,25,16777215,1686297342,0,ce67fafd,1335517923728804864">?</d>
|
|
30
|
+
<d p="13.499,1,25,16777215,1686301548,3,2848bf1c,1335517923728804864">不喜欢</d>
|
|
31
|
+
</i>`
|
|
32
|
+
|
|
33
|
+
describe('B站弹幕相关', () => {
|
|
34
|
+
const pool = UniPool.fromBiliXML(xml)
|
|
35
|
+
it('B站dmid去重(仅限主站直接获取的弹幕)', () => {
|
|
36
|
+
const ori = pool.dans.length
|
|
37
|
+
expect(ori).toBe(15)
|
|
38
|
+
const n = pool.pipe(to_bili_deduped)
|
|
39
|
+
expect(n.dans.length).toBe(13)
|
|
40
|
+
expect(pool.dans.length).toBe(15)
|
|
41
|
+
pool.pipe(bili_dedupe)
|
|
42
|
+
expect(pool.dans.length).toBe(13)
|
|
43
|
+
})
|
|
44
|
+
describe('B站历史弹幕反向快进算法', () => {
|
|
45
|
+
it('正常测试', () => {
|
|
46
|
+
// Mock一些历史弹幕数据:跨越2024-01-01到2024-01-10
|
|
47
|
+
const mockDans = [
|
|
48
|
+
// 2024-01-01 有弹幕
|
|
49
|
+
UniDM.create({
|
|
50
|
+
SOID: '123@bili',
|
|
51
|
+
content: 'test1',
|
|
52
|
+
progress: 10,
|
|
53
|
+
ctime: new Date('2024-01-01T10:00:00+08:00'),
|
|
54
|
+
}),
|
|
55
|
+
// 2024-01-02 有弹幕
|
|
56
|
+
UniDM.create({
|
|
57
|
+
SOID: '123@bili',
|
|
58
|
+
content: 'test2',
|
|
59
|
+
progress: 20,
|
|
60
|
+
ctime: new Date('2024-01-02T15:30:00+08:00'),
|
|
61
|
+
}),
|
|
62
|
+
// 2024-01-03 无弹幕(跳过)
|
|
63
|
+
// 2024-01-04 有弹幕
|
|
64
|
+
UniDM.create({
|
|
65
|
+
SOID: '123@bili',
|
|
66
|
+
content: 'test3',
|
|
67
|
+
progress: 30,
|
|
68
|
+
ctime: new Date('2024-01-04T08:00:00+08:00'),
|
|
69
|
+
}),
|
|
70
|
+
// 2024-01-05 到 2024-01-09 无弹幕(跳过)
|
|
71
|
+
// 2024-01-10 有弹幕(但这是查询日期,不应该包含在结果中)
|
|
72
|
+
UniDM.create({
|
|
73
|
+
SOID: '123@bili',
|
|
74
|
+
content: 'test4',
|
|
75
|
+
progress: 40,
|
|
76
|
+
ctime: new Date('2024-01-10T12:00:00+08:00'),
|
|
77
|
+
}),
|
|
78
|
+
// 2024-01-11 有弹幕(晚于查询日期,不应该包含)
|
|
79
|
+
UniDM.create({
|
|
80
|
+
SOID: '123@bili',
|
|
81
|
+
content: 'test5',
|
|
82
|
+
progress: 50,
|
|
83
|
+
ctime: new Date('2024-01-11T09:00:00+08:00'),
|
|
84
|
+
}),
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
const pool = new UniPool(mockDans)
|
|
88
|
+
const result = pool.pipe(bili_history_fast_forward('2024-01-10'))
|
|
89
|
+
|
|
90
|
+
// 验证结果
|
|
91
|
+
expect(result.earliest).toBe('2024-01-01')
|
|
92
|
+
expect(result.SpecificDate).toBe('2024-01-10')
|
|
93
|
+
|
|
94
|
+
// fast-forward 应该包含有弹幕的日期(2024-01-01, 02, 04)
|
|
95
|
+
expect(result.FastForward).toEqual([
|
|
96
|
+
'2024-01-01',
|
|
97
|
+
'2024-01-02',
|
|
98
|
+
'2024-01-04',
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
// skip 应该包含没有弹幕的日期(2024-01-03, 05-09)
|
|
102
|
+
expect(result.skip).toEqual([
|
|
103
|
+
'2024-01-03',
|
|
104
|
+
'2024-01-05',
|
|
105
|
+
'2024-01-06',
|
|
106
|
+
'2024-01-07',
|
|
107
|
+
'2024-01-08',
|
|
108
|
+
'2024-01-09',
|
|
109
|
+
])
|
|
110
|
+
})
|
|
111
|
+
it('无历史弹幕', () => {
|
|
112
|
+
const mockDans = [
|
|
113
|
+
UniDM.create({
|
|
114
|
+
SOID: '123@bili',
|
|
115
|
+
content: 'test',
|
|
116
|
+
progress: 10,
|
|
117
|
+
ctime: new Date('2024-01-10T12:00:00+08:00'),
|
|
118
|
+
}),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
const pool = new UniPool(mockDans)
|
|
122
|
+
const result = pool.pipe(bili_history_fast_forward('2024-01-10'))
|
|
123
|
+
|
|
124
|
+
expect(result.earliest).toBe(null)
|
|
125
|
+
expect(result.FastForward).toEqual([])
|
|
126
|
+
expect(result.skip).toEqual([])
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
})
|
package/src/plugins/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * as
|
|
1
|
+
export * as bili from './bili'
|
|
2
2
|
export * as stats from './stats'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 统计插件
|
|
2
|
+
|
|
3
|
+
为弹幕池提供统计和查询功能的工具集合。
|
|
4
|
+
|
|
5
|
+
## 功能模块
|
|
6
|
+
|
|
7
|
+
### 获取最新弹幕 (`getLatestDan`)
|
|
8
|
+
|
|
9
|
+
从弹幕池中查找出最新发送的一条弹幕。
|
|
10
|
+
|
|
11
|
+
#### 导出函数
|
|
12
|
+
|
|
13
|
+
- **`getLatestDan(pool: UniPool): UniDM | null`** - 获取最新的一条弹幕
|
|
14
|
+
|
|
15
|
+
#### 使用示例
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { stats } from '@danuni/dan-any/plugins'
|
|
19
|
+
|
|
20
|
+
const pool = UniPool.import(biliData)
|
|
21
|
+
|
|
22
|
+
// 获取最新弹幕
|
|
23
|
+
const latest = pool.pipe(stats.getLatestDan)
|
|
24
|
+
|
|
25
|
+
if (latest) {
|
|
26
|
+
console.log(`最新弹幕: ${latest.content}`)
|
|
27
|
+
console.log(`发送时间: ${latest.ctime}`)
|
|
28
|
+
} else {
|
|
29
|
+
console.log('弹幕池为空')
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### 返回值
|
|
34
|
+
|
|
35
|
+
- **`UniDM`** - 最新的弹幕对象(包含内容、时间、发送者等信息)
|
|
36
|
+
- **`null`** - 当弹幕池为空时
|
|
37
|
+
|
|
38
|
+
#### 工作原理
|
|
39
|
+
|
|
40
|
+
按照弹幕的创建时间(`ctime`)进行比较,通过遍历整个弹幕池找出时间戳最新的一条弹幕。
|
|
41
|
+
|
|
42
|
+
#### 时间复杂度
|
|
43
|
+
|
|
44
|
+
- 平均/最坏情况:**O(n)** — n 是弹幕数量
|
|
@@ -31,7 +31,7 @@ const xml = `<i>
|
|
|
31
31
|
describe('统计', () => {
|
|
32
32
|
it('获取最新的一条弹幕', () => {
|
|
33
33
|
const pool = UniPool.fromBiliXML(xml)
|
|
34
|
-
const latest = pool.
|
|
34
|
+
const latest = pool.pipe(getLatestDan)
|
|
35
35
|
expect(latest).not.toBeNull()
|
|
36
36
|
expect(latest?.content).toBe('不喜欢')
|
|
37
37
|
expect(latest?.ctime).toStrictEqual(new Date(1686948453 * 1000))
|
package/src/utils/dm-gen.ts
CHANGED
|
@@ -626,7 +626,7 @@ export class UniDM {
|
|
|
626
626
|
if (this.attr.length > 0) result.attr = this.attr
|
|
627
627
|
if (this.platform !== undefined) result.platform = this.platform
|
|
628
628
|
if (this.extraStr && this.extraStr !== '{}') result.extraStr = this.extraStr
|
|
629
|
-
if (this.DMID !== undefined) result.DMID = this.DMID
|
|
629
|
+
if (this.DMID !== undefined && this.options.dmid) result.DMID = this.DMID
|
|
630
630
|
return result
|
|
631
631
|
}
|
|
632
632
|
@Expose()
|
|
@@ -791,7 +791,7 @@ export class UniDM {
|
|
|
791
791
|
const senderID = isEmail(args.midHash, { require_tld: false })
|
|
792
792
|
? args.midHash
|
|
793
793
|
: ID.fromBili({ midHash: args.midHash })
|
|
794
|
-
let mode
|
|
794
|
+
let mode: Modes
|
|
795
795
|
const pool = args.pool //暂时不做处理,兼容bili的pool格式
|
|
796
796
|
const extra: TExtra = {
|
|
797
797
|
bili: {
|
package/dist/browser/157.min.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { __webpack_require__ } from "./rslib-runtime.min.js";
|
|
2
|
-
import { UniPool, platform_PlatformVideoSource } from "./17.min.js";
|
|
3
|
-
var bili_dedupe_namespaceObject = {};
|
|
4
|
-
__webpack_require__.r(bili_dedupe_namespaceObject);
|
|
5
|
-
__webpack_require__.d(bili_dedupe_namespaceObject, {
|
|
6
|
-
bili_dedupe: ()=>bili_dedupe,
|
|
7
|
-
to_bili_deduped: ()=>to_bili_deduped
|
|
8
|
-
});
|
|
9
|
-
function main(that) {
|
|
10
|
-
that.dans.forEach((d)=>{
|
|
11
|
-
if (d.platform !== platform_PlatformVideoSource.Bilibili) throw new Error('bili-dedupe: 仅支持B站(主站)的弹幕');
|
|
12
|
-
if (!d.extra.bili?.dmid) throw new Error('bili-dedupe: 弹幕缺少bili extra dmid字段');
|
|
13
|
-
});
|
|
14
|
-
const map = new Map();
|
|
15
|
-
that.dans.forEach((d)=>map.set(d.extra.bili.dmid, d));
|
|
16
|
-
return map;
|
|
17
|
-
}
|
|
18
|
-
function to_bili_deduped(that) {
|
|
19
|
-
const map = main(that);
|
|
20
|
-
return new UniPool([
|
|
21
|
-
...map.values()
|
|
22
|
-
], that.options, that.info);
|
|
23
|
-
}
|
|
24
|
-
function bili_dedupe(that) {
|
|
25
|
-
const map = main(that);
|
|
26
|
-
that.dans = [
|
|
27
|
-
...map.values()
|
|
28
|
-
];
|
|
29
|
-
}
|
|
30
|
-
export { bili_dedupe as bili_dedupe_0, bili_dedupe_namespaceObject as bili_dedupe, to_bili_deduped };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { bili_dedupe_0 as bili_dedupe, to_bili_deduped } from "../157.min.js";
|