@dan-uni/dan-any 1.1.1 → 1.2.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/README.md +1 -0
- package/dist/browser/17.min.js +13286 -0
- package/dist/browser/705.min.js +77 -0
- package/dist/browser/898.min.js +10 -0
- package/dist/browser/index.min.js +1 -13248
- package/dist/browser/plugins/bili.min.js +1 -0
- package/dist/browser/plugins/index.min.js +2 -1
- package/dist/browser/plugins/stats.min.js +1 -0
- package/dist/browser/src/index.d.ts +1 -4
- package/dist/browser/src/plugins/bili/dedupe.d.ts +3 -0
- 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 +2 -2
- package/dist/browser/src/plugins/stats/getLatestDan.d.ts +6 -0
- package/dist/browser/src/plugins/stats/index.d.ts +1 -0
- package/dist/node/17.js +1861 -0
- package/dist/node/705.js +77 -0
- package/dist/node/898.js +10 -0
- package/dist/node/index.js +1 -1823
- package/dist/node/plugins/bili.js +1 -0
- package/dist/node/plugins/index.js +2 -1
- package/dist/node/plugins/stats.js +1 -0
- package/dist/node/src/index.d.ts +1 -4
- package/dist/node/src/plugins/bili/dedupe.d.ts +3 -0
- 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 +2 -2
- package/dist/node/src/plugins/stats/getLatestDan.d.ts +6 -0
- package/dist/node/src/plugins/stats/index.d.ts +1 -0
- package/dist/node/src/plugins/stats/index.test.d.ts +1 -0
- package/dist/umd/index.umd.min.js +73 -68
- package/dist/umd/plugins/bili.umd.min.js +29639 -0
- package/dist/umd/plugins/bili.umd.min.js.LICENSE.txt +34 -0
- package/dist/umd/plugins/index.umd.min.js +29584 -23
- package/dist/umd/plugins/index.umd.min.js.LICENSE.txt +34 -0
- package/dist/umd/plugins/stats.umd.min.js +39 -0
- package/dist/umd/src/index.d.ts +1 -4
- package/dist/umd/src/plugins/bili/dedupe.d.ts +3 -0
- 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/bili/index.test.d.ts +1 -0
- package/dist/umd/src/plugins/index.d.ts +2 -2
- package/dist/umd/src/plugins/stats/getLatestDan.d.ts +6 -0
- package/dist/umd/src/plugins/stats/index.d.ts +1 -0
- package/dist/umd/src/plugins/stats/index.test.d.ts +1 -0
- package/package.json +4 -2
- package/rslib.config.ts +2 -1
- package/src/index.ts +1 -7
- package/src/plugins/bili/README.md +87 -0
- package/src/plugins/{bili-dedupe/index.ts → bili/dedupe.ts} +13 -3
- 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 +2 -3
- package/src/plugins/stats/README.md +44 -0
- package/src/plugins/stats/getLatestDan.ts +13 -0
- package/src/plugins/{bili-dedupe → stats}/index.test.ts +10 -11
- package/src/plugins/stats/index.ts +1 -0
- package/dist/browser/157.min.js +0 -14
- package/dist/browser/42.min.js +0 -45
- package/dist/browser/plugins/bili-dedupe.min.js +0 -1
- package/dist/browser/src/plugins/bili-dedupe/index.d.ts +0 -5
- package/dist/node/157.js +0 -14
- package/dist/node/42.js +0 -45
- package/dist/node/plugins/bili-dedupe.js +0 -1
- package/dist/node/src/plugins/bili-dedupe/index.d.ts +0 -5
- package/dist/umd/plugins/bili-dedupe.umd.min.js +0 -79
- package/dist/umd/src/plugins/bili-dedupe/index.d.ts +0 -5
- /package/dist/browser/{index.min.js.LICENSE.txt → 17.min.js.LICENSE.txt} +0 -0
- /package/dist/browser/src/plugins/{bili-dedupe → bili}/index.test.d.ts +0 -0
- /package/dist/{node/src/plugins/bili-dedupe → browser/src/plugins/stats}/index.test.d.ts +0 -0
- /package/dist/{umd/src/plugins/bili-dedupe → node/src/plugins/bili}/index.test.d.ts +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* The buffer module from node.js, for the browser.
|
|
3
|
+
*
|
|
4
|
+
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
|
|
5
|
+
* @license MIT
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/*!
|
|
9
|
+
* The buffer module from node.js, for the browser.
|
|
10
|
+
*
|
|
11
|
+
* @author Feross Aboukhadijeh <https://feross.org>
|
|
12
|
+
* @license MIT
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/*! *****************************************************************************
|
|
16
|
+
Copyright (C) Microsoft. All rights reserved.
|
|
17
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
18
|
+
this file except in compliance with the License. You may obtain a copy of the
|
|
19
|
+
License at http://www.apache.org/licenses/LICENSE-2.0
|
|
20
|
+
|
|
21
|
+
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
22
|
+
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
|
23
|
+
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
|
24
|
+
MERCHANTABLITY OR NON-INFRINGEMENT.
|
|
25
|
+
|
|
26
|
+
See the Apache Version 2.0 License for specific language governing permissions
|
|
27
|
+
and limitations under the License.
|
|
28
|
+
***************************************************************************** */
|
|
29
|
+
|
|
30
|
+
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
31
|
+
|
|
32
|
+
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
33
|
+
|
|
34
|
+
/*! zero-fill. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
(function(root, factory) {
|
|
2
|
+
if ('object' == typeof exports && 'object' == typeof module) module.exports = factory();
|
|
3
|
+
else if ('function' == typeof define && define.amd) define([], factory);
|
|
4
|
+
else if ('object' == typeof exports) exports["@dan-uni/dan-any"] = factory();
|
|
5
|
+
else root["@dan-uni/dan-any"] = factory();
|
|
6
|
+
})(globalThis, ()=>(()=>{
|
|
7
|
+
"use strict";
|
|
8
|
+
var __webpack_require__ = {};
|
|
9
|
+
(()=>{
|
|
10
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
11
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: definition[key]
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
(()=>{
|
|
18
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
19
|
+
})();
|
|
20
|
+
(()=>{
|
|
21
|
+
__webpack_require__.r = (exports1)=>{
|
|
22
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
23
|
+
value: 'Module'
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
26
|
+
value: true
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
})();
|
|
30
|
+
var __webpack_exports__ = {};
|
|
31
|
+
__webpack_require__.r(__webpack_exports__);
|
|
32
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
33
|
+
getLatestDan: ()=>getLatestDan
|
|
34
|
+
});
|
|
35
|
+
function getLatestDan(that) {
|
|
36
|
+
return 0 === that.dans.length ? null : that.dans.reduce((latest, current)=>current.ctime > latest.ctime ? current : latest);
|
|
37
|
+
}
|
|
38
|
+
return __webpack_exports__;
|
|
39
|
+
})());
|
package/dist/umd/src/index.d.ts
CHANGED
|
@@ -120,8 +120,6 @@ export type DM_format = 'danuni.json' | 'danuni.pb.bin' | 'bili.xml' | 'bili.pb.
|
|
|
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
122
|
type Stats<T extends keyof statItems> = Map<statItems[T], number>;
|
|
123
|
-
type UniPoolPipe = (that: UniPool) => Promise<UniPool>;
|
|
124
|
-
type UniPoolPipeSync = (that: UniPool) => UniPool;
|
|
125
123
|
export interface Options {
|
|
126
124
|
dedupe?: boolean;
|
|
127
125
|
/**
|
|
@@ -145,8 +143,7 @@ export declare class UniPool {
|
|
|
145
143
|
*/
|
|
146
144
|
fromConverted: boolean;
|
|
147
145
|
});
|
|
148
|
-
pipe(fn:
|
|
149
|
-
pipeSync(fn: UniPoolPipeSync): UniPool;
|
|
146
|
+
pipe<T extends (...args: any) => any>(fn: T): ReturnType<T>;
|
|
150
147
|
/**
|
|
151
148
|
* @deprecated 使用 `getShared` 代替
|
|
152
149
|
*/
|
|
@@ -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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export * as bili from './bili';
|
|
2
|
+
export * as stats from './stats';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as getLatestDan } from './getLatestDan';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dan-uni/dan-any",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.5",
|
|
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,11 +36,12 @@
|
|
|
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.4",
|
|
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": {
|
|
@@ -49,6 +50,7 @@
|
|
|
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,8 @@ 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
|
+
'plugins/stats': './src/plugins/stats/index.ts',
|
|
12
13
|
},
|
|
13
14
|
},
|
|
14
15
|
lib: [
|
package/src/index.ts
CHANGED
|
@@ -185,9 +185,6 @@ type statItems = Partial<
|
|
|
185
185
|
>
|
|
186
186
|
type Stats<T extends keyof statItems> = Map<statItems[T], number>
|
|
187
187
|
|
|
188
|
-
type UniPoolPipe = (that: UniPool) => Promise<UniPool>
|
|
189
|
-
type UniPoolPipeSync = (that: UniPool) => UniPool
|
|
190
|
-
|
|
191
188
|
export interface Options {
|
|
192
189
|
dedupe?: boolean
|
|
193
190
|
/**
|
|
@@ -211,10 +208,7 @@ export class UniPool {
|
|
|
211
208
|
if (options.dedupe !== false) options.dedupe = true
|
|
212
209
|
if (this.options.dedupe) this.dedupe()
|
|
213
210
|
}
|
|
214
|
-
|
|
215
|
-
return fn(this)
|
|
216
|
-
}
|
|
217
|
-
pipeSync(fn: UniPoolPipeSync): UniPool {
|
|
211
|
+
pipe<T extends (...args: any) => any>(fn: T): ReturnType<T> {
|
|
218
212
|
return fn(this)
|
|
219
213
|
}
|
|
220
214
|
/**
|
|
@@ -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`
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { UniDM
|
|
1
|
+
import type { UniDM } from '../..'
|
|
2
2
|
|
|
3
|
+
import { UniPool } from '../..'
|
|
3
4
|
import { PlatformVideoSource } from '../../utils/platform'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 用于过滤直接从B站api获取到的弹幕中去除dmid重复的弹幕
|
|
7
8
|
*/
|
|
8
|
-
|
|
9
|
+
function main(that: UniPool) {
|
|
9
10
|
that.dans.forEach((d) => {
|
|
10
11
|
if (d.platform !== PlatformVideoSource.Bilibili)
|
|
11
12
|
throw new Error('bili-dedupe: 仅支持B站(主站)的弹幕')
|
|
@@ -14,6 +15,15 @@ export default function bili_dedupe(that: UniPool): UniPool {
|
|
|
14
15
|
})
|
|
15
16
|
const map = new Map<bigint, UniDM>()
|
|
16
17
|
that.dans.forEach((d) => map.set(d.extra.bili!.dmid!, d)) // 已由上方的检查保证存在
|
|
18
|
+
return map
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function to_bili_deduped(that: UniPool): UniPool {
|
|
22
|
+
const map = main(that)
|
|
23
|
+
return new UniPool([...map.values()], that.options, that.info)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function bili_dedupe(that: UniPool): void {
|
|
27
|
+
const map = main(that)
|
|
17
28
|
that.dans = [...map.values()]
|
|
18
|
-
return that
|
|
19
29
|
}
|
|
@@ -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,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { bili_dedupe }
|
|
1
|
+
export * as bili from './bili'
|
|
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 是弹幕数量
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UniDM, UniPool } from '../..'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 从弹幕池中查找出最新的一条弹幕
|
|
5
|
+
* 按照弹幕的创建时间(ctime)排序,返回最新的一条
|
|
6
|
+
*/
|
|
7
|
+
export default function getLatestDan(that: UniPool): UniDM | null {
|
|
8
|
+
return that.dans.length === 0
|
|
9
|
+
? null
|
|
10
|
+
: that.dans.reduce((latest, current) =>
|
|
11
|
+
current.ctime > latest.ctime ? current : latest,
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
//基于以下注释,根据vitest生成测试用例
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
4
|
-
import bili_dedupe from '.'
|
|
5
3
|
import { UniPool } from '../..'
|
|
4
|
+
import getLatestDan from './getLatestDan'
|
|
6
5
|
|
|
7
6
|
const xml = `<i>
|
|
8
7
|
<chatserver>chat.bilibili.com</chatserver>
|
|
@@ -16,7 +15,7 @@ const xml = `<i>
|
|
|
16
15
|
<d p="13.213,1,25,16777215,1686590010,0,296b35b5,1337972999512832512">来了 哈哈~~</d>
|
|
17
16
|
<d p="13.246,1,25,16777215,1686276875,0,5664cfc4,1335346233459549696">就是</d>
|
|
18
17
|
<d p="13.266,1,25,16777215,1686283375,0,c7e6646f,1335400761013670912">什么鬼?</d>
|
|
19
|
-
<d p="13.284,1,25,16777215,1686291338,0,38662881,
|
|
18
|
+
<d p="13.284,1,25,16777215,1686291338,0,38662881,1335467554877267456">哇哦</d>
|
|
20
19
|
<d p="13.306,1,25,16777215,1686268410,0,4c01de10,1335275224983600896">试试</d>
|
|
21
20
|
<d p="13.331,1,25,16777215,1686948453,3,56a3c5d5,1340979831550069760">不喜欢</d>
|
|
22
21
|
<d p="13.374,1,25,16777215,1686300770,3,647fe355,1335546672880933888">不喜欢</d>
|
|
@@ -26,15 +25,15 @@ const xml = `<i>
|
|
|
26
25
|
<d p="13.459,1,25,16777215,1686299729,0,45834405,1335537942797634048">一般,不好看</d>
|
|
27
26
|
<d p="13.462,1,25,16777215,1686302133,0,3cab672c,1335558106620590080">哈哈哈</d>
|
|
28
27
|
<d p="13.481,1,25,16777215,1686297342,0,ce67fafd,1335517923728804864">?</d>
|
|
29
|
-
<d p="13.499,1,25,16777215,1686301548,3,2848bf1c,
|
|
28
|
+
<d p="13.499,1,25,16777215,1686301548,3,2848bf1c,1335553202649003264">不喜欢</d>
|
|
30
29
|
</i>`
|
|
31
30
|
|
|
32
|
-
describe('
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
expect(
|
|
37
|
-
|
|
38
|
-
expect(
|
|
31
|
+
describe('统计', () => {
|
|
32
|
+
it('获取最新的一条弹幕', () => {
|
|
33
|
+
const pool = UniPool.fromBiliXML(xml)
|
|
34
|
+
const latest = pool.pipe(getLatestDan)
|
|
35
|
+
expect(latest).not.toBeNull()
|
|
36
|
+
expect(latest?.content).toBe('不喜欢')
|
|
37
|
+
expect(latest?.ctime).toStrictEqual(new Date(1686948453 * 1000))
|
|
39
38
|
})
|
|
40
39
|
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as getLatestDan } from './getLatestDan'
|