@ctrl/torrent-file 2.0.3 → 4.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.
- package/README.md +1 -18
- package/dist/src/bencode/decode.d.ts +2 -5
- package/dist/src/bencode/decode.js +116 -107
- package/dist/src/bencode/encode.d.ts +5 -5
- package/dist/src/bencode/encode.js +68 -115
- package/dist/src/bencode/utils.d.ts +3 -0
- package/dist/src/bencode/utils.js +92 -0
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/torrentFile.d.ts +3 -4
- package/dist/src/torrentFile.js +5 -4
- package/package.json +18 -13
- package/dist/src/isArrayBuffer.d.ts +0 -11
- package/dist/src/isArrayBuffer.js +0 -16
package/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
> Parse a torrent file and read encoded data.
|
4
4
|
|
5
|
-
This project is based on [parse-torrent](https://www.npmjs.com/package/parse-torrent) and [node-bencode](https://github.com/themasch/node-bencode) to parse the data of a torrent file. This library implements its own [bencode](http://www.bittorrent.org/beps/bep_0003.html) encoder and decoder
|
5
|
+
This project is based on [parse-torrent](https://www.npmjs.com/package/parse-torrent) and [node-bencode](https://github.com/themasch/node-bencode) to parse the data of a torrent file. This library implements its own [bencode](http://www.bittorrent.org/beps/bep_0003.html) encoder and decoder that does not use `Buffer`.
|
6
6
|
|
7
7
|
### Install
|
8
8
|
```console
|
@@ -43,23 +43,6 @@ import { hash } from '@ctrl/torrent-file';
|
|
43
43
|
})()
|
44
44
|
```
|
45
45
|
|
46
|
-
### encode
|
47
|
-
Use the built in bencode encoder
|
48
|
-
```ts
|
49
|
-
import fs from 'fs';
|
50
|
-
import { encode } from '@ctrl/torrent-file';
|
51
|
-
|
52
|
-
encode({ name: 'my string to encode' });
|
53
|
-
```
|
54
|
-
|
55
|
-
### decode
|
56
|
-
Easily get the raw data inside a torrent file.
|
57
|
-
```ts
|
58
|
-
import fs from 'fs';
|
59
|
-
import { decode } from '@ctrl/torrent-file';
|
60
|
-
|
61
|
-
decode(fs.readFileSync('myfile'));
|
62
|
-
```
|
63
46
|
|
64
47
|
### See Also
|
65
48
|
[parse-torrent](https://www.npmjs.com/package/parse-torrent) - "@ctrl/torrent-file" torrent parsing based very heavily off this project
|
@@ -1,5 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
* Decodes bencoded data
|
4
|
-
*/
|
5
|
-
export declare function decode(data: Buffer | string | null, encoding?: string | null): Record<string, unknown> | any[] | Buffer | string | number | null;
|
1
|
+
import type { bencodeValue } from './encode.js';
|
2
|
+
export declare const decode: (payload: ArrayBufferView | ArrayBuffer | string) => bencodeValue;
|
@@ -1,117 +1,126 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
const
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
for (let i = start; i < end; i++) {
|
14
|
-
const num = buffer[i];
|
15
|
-
if (typeof num === 'undefined') {
|
16
|
-
continue;
|
1
|
+
import { stringToUint8Array, uint8ArrayToString } from 'uint8array-extras';
|
2
|
+
import { isValidUTF8 } from './utils.js';
|
3
|
+
const td = new TextDecoder();
|
4
|
+
class Decoder {
|
5
|
+
buf;
|
6
|
+
idx = 0;
|
7
|
+
constructor(buf) {
|
8
|
+
this.buf = buf;
|
9
|
+
}
|
10
|
+
readByte() {
|
11
|
+
if (this.idx >= this.buf.length) {
|
12
|
+
return null;
|
17
13
|
}
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
return String.fromCharCode(this.buf[this.idx++]);
|
15
|
+
}
|
16
|
+
readBytes(length) {
|
17
|
+
if (this.idx + length > this.buf.length) {
|
18
|
+
throw new Error(`could not read ${length} bytes, insufficient content`);
|
21
19
|
}
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
const result = this.buf.slice(this.idx, this.idx + length);
|
21
|
+
this.idx += length;
|
22
|
+
return result;
|
23
|
+
}
|
24
|
+
readUntil(char) {
|
25
|
+
const targetIdx = this.buf.indexOf(char.charCodeAt(0), this.idx);
|
26
|
+
if (targetIdx === -1) {
|
27
|
+
throw new Error(`could not find terminated char: ${char}`);
|
25
28
|
}
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
const result = this.buf.slice(this.idx, targetIdx);
|
30
|
+
this.idx = targetIdx;
|
31
|
+
return result;
|
32
|
+
}
|
33
|
+
readNumber() {
|
34
|
+
const buf = this.readUntil(':');
|
35
|
+
return parseInt(uint8ArrayToString(buf), 10);
|
36
|
+
}
|
37
|
+
peekByte() {
|
38
|
+
if (this.idx >= this.buf.length) {
|
39
|
+
return '';
|
30
40
|
}
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
break;
|
41
|
+
const result = this.readByte();
|
42
|
+
if (result === null) {
|
43
|
+
return '';
|
35
44
|
}
|
36
|
-
|
45
|
+
this.idx--;
|
46
|
+
return result;
|
37
47
|
}
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
export function decode(data, encoding = null) {
|
44
|
-
const state = {
|
45
|
-
bytes: 0,
|
46
|
-
position: 0,
|
47
|
-
data: null,
|
48
|
-
encoding,
|
49
|
-
};
|
50
|
-
if (data === null || data.length === 0) {
|
51
|
-
return null;
|
52
|
-
}
|
53
|
-
state.data = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
54
|
-
state.bytes = state.data.length;
|
55
|
-
return next(state);
|
56
|
-
}
|
57
|
-
function next(state) {
|
58
|
-
switch (state.data[state.position]) {
|
59
|
-
case DICTIONARY_START:
|
60
|
-
return dictionary(state);
|
61
|
-
case LIST_START:
|
62
|
-
return list(state);
|
63
|
-
case INTEGER_START:
|
64
|
-
return integer(state);
|
65
|
-
default:
|
66
|
-
return buffer(state);
|
48
|
+
assertByte(expected) {
|
49
|
+
const b = this.readByte();
|
50
|
+
if (b !== expected) {
|
51
|
+
throw new Error(`expecte ${expected}, got ${b}`);
|
52
|
+
}
|
67
53
|
}
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
54
|
+
next() {
|
55
|
+
switch (this.peekByte()) {
|
56
|
+
case 'd': {
|
57
|
+
return this.nextDictionary();
|
58
|
+
}
|
59
|
+
case 'l': {
|
60
|
+
return this.nextList();
|
61
|
+
}
|
62
|
+
case 'i': {
|
63
|
+
return this.nextNumber();
|
64
|
+
}
|
65
|
+
default: {
|
66
|
+
return this.nextBufOrString();
|
67
|
+
}
|
76
68
|
}
|
77
|
-
i++;
|
78
69
|
}
|
79
|
-
|
80
|
-
|
81
|
-
'
|
82
|
-
|
83
|
-
|
84
|
-
}
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
}
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
}
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
70
|
+
nextBufOrString() {
|
71
|
+
const length = this.readNumber();
|
72
|
+
this.assertByte(':');
|
73
|
+
const buf = this.readBytes(length);
|
74
|
+
return isValidUTF8(buf) ? td.decode(buf) : buf;
|
75
|
+
}
|
76
|
+
nextString() {
|
77
|
+
const length = this.readNumber();
|
78
|
+
this.assertByte(':');
|
79
|
+
return td.decode(this.readBytes(length));
|
80
|
+
}
|
81
|
+
nextNumber() {
|
82
|
+
this.assertByte('i');
|
83
|
+
const content = td.decode(this.readUntil('e'));
|
84
|
+
this.assertByte('e');
|
85
|
+
const result = Number(content);
|
86
|
+
if (isNaN(result)) {
|
87
|
+
throw new Error(`not a number: ${content}`);
|
88
|
+
}
|
89
|
+
return result;
|
90
|
+
}
|
91
|
+
nextList() {
|
92
|
+
this.assertByte('l');
|
93
|
+
const result = [];
|
94
|
+
while (this.peekByte() !== 'e') {
|
95
|
+
result.push(this.next());
|
96
|
+
}
|
97
|
+
this.assertByte('e');
|
98
|
+
return result;
|
99
|
+
}
|
100
|
+
nextDictionary() {
|
101
|
+
this.assertByte('d');
|
102
|
+
const result = {};
|
103
|
+
while (this.peekByte() !== 'e') {
|
104
|
+
result[this.nextString()] = this.next();
|
105
|
+
}
|
106
|
+
this.assertByte('e');
|
107
|
+
return result;
|
108
|
+
}
|
117
109
|
}
|
110
|
+
export const decode = (payload) => {
|
111
|
+
let buf;
|
112
|
+
if (typeof payload === 'string') {
|
113
|
+
buf = stringToUint8Array(payload);
|
114
|
+
}
|
115
|
+
else if (payload instanceof ArrayBuffer) {
|
116
|
+
buf = new Uint8Array(payload);
|
117
|
+
}
|
118
|
+
else if ('buffer' in payload) {
|
119
|
+
buf = new Uint8Array(payload.buffer);
|
120
|
+
}
|
121
|
+
else {
|
122
|
+
throw new Error(`invalid payload type`);
|
123
|
+
}
|
124
|
+
const decoder = new Decoder(buf);
|
125
|
+
return decoder.next();
|
126
|
+
};
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
export
|
1
|
+
export type bencodeValue = string | Uint8Array | number | {
|
2
|
+
[key: string]: bencodeValue;
|
3
|
+
} | bencodeValue[];
|
4
|
+
export declare const encode: (data: bencodeValue | bencodeValue[]) => Uint8Array;
|
5
|
+
export default encode;
|
@@ -1,121 +1,74 @@
|
|
1
|
-
|
2
|
-
import {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
const
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
};
|
12
|
-
_encode(state, buffers, data);
|
13
|
-
const result = Buffer.concat(buffers);
|
14
|
-
state.bytes = result.length;
|
15
|
-
if (Buffer.isBuffer(buffer)) {
|
16
|
-
result.copy(buffer, offset);
|
17
|
-
return buffer;
|
18
|
-
}
|
1
|
+
import { concatUint8Arrays, stringToUint8Array } from 'uint8array-extras';
|
2
|
+
import { cmpRawString } from './utils.js';
|
3
|
+
const te = new TextEncoder();
|
4
|
+
const encodeString = (str) => {
|
5
|
+
const lengthBytes = new TextEncoder().encode(str.length.toString());
|
6
|
+
const content = te.encode(str);
|
7
|
+
const result = new Uint8Array(lengthBytes.byteLength + 1 + content.byteLength);
|
8
|
+
result.set(lengthBytes);
|
9
|
+
result.set(te.encode(':'), lengthBytes.byteLength);
|
10
|
+
result.set(content, lengthBytes.byteLength + 1);
|
19
11
|
return result;
|
20
|
-
}
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
if (value instanceof Boolean) {
|
35
|
-
return 'boolean';
|
36
|
-
}
|
37
|
-
if (isArrayBuffer(value)) {
|
38
|
-
return 'arraybuffer';
|
39
|
-
}
|
40
|
-
return typeof value;
|
41
|
-
}
|
42
|
-
function _encode(state, buffers, data) {
|
43
|
-
if (data === null) {
|
44
|
-
return;
|
45
|
-
}
|
46
|
-
switch (getType(data)) {
|
47
|
-
case 'buffer':
|
48
|
-
buffer(buffers, data);
|
49
|
-
break;
|
50
|
-
case 'object':
|
51
|
-
dict(state, buffers, data);
|
52
|
-
break;
|
53
|
-
case 'array':
|
54
|
-
list(state, buffers, data);
|
55
|
-
break;
|
56
|
-
case 'string':
|
57
|
-
string(buffers, data);
|
58
|
-
break;
|
59
|
-
case 'number':
|
60
|
-
number(state, buffers, data);
|
61
|
-
break;
|
62
|
-
case 'boolean':
|
63
|
-
number(state, buffers, data);
|
64
|
-
break;
|
65
|
-
case 'arraybufferview':
|
66
|
-
buffer(buffers, Buffer.from(data.buffer, data.byteOffset, data.byteLength));
|
67
|
-
break;
|
68
|
-
case 'arraybuffer':
|
69
|
-
buffer(buffers, Buffer.from(data));
|
70
|
-
break;
|
71
|
-
default:
|
72
|
-
break;
|
12
|
+
};
|
13
|
+
const encodeBuf = (buf) => {
|
14
|
+
const lengthBytes = new TextEncoder().encode(buf.byteLength.toString());
|
15
|
+
const result = new Uint8Array(lengthBytes.byteLength + 1 + buf.byteLength);
|
16
|
+
result.set(lengthBytes);
|
17
|
+
result.set(te.encode(':'), lengthBytes.byteLength);
|
18
|
+
result.set(buf, lengthBytes.byteLength + 1);
|
19
|
+
return result;
|
20
|
+
};
|
21
|
+
const encodeNumber = (num) => {
|
22
|
+
// NOTE: only support integers
|
23
|
+
const int = Math.floor(num);
|
24
|
+
if (int !== num) {
|
25
|
+
throw new Error(`bencode only support integers, got ${num}`);
|
73
26
|
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
const
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
27
|
+
return concatUint8Arrays([
|
28
|
+
stringToUint8Array('i'),
|
29
|
+
stringToUint8Array(int.toString()),
|
30
|
+
stringToUint8Array('e'),
|
31
|
+
]);
|
32
|
+
};
|
33
|
+
const encodeDictionary = (obj) => {
|
34
|
+
const results = [];
|
35
|
+
Object.keys(obj)
|
36
|
+
.sort(cmpRawString)
|
37
|
+
.forEach(key => {
|
38
|
+
results.push(encodeString(key));
|
39
|
+
results.push(new Uint8Array(encode(obj[key])));
|
40
|
+
});
|
41
|
+
const d = stringToUint8Array('d');
|
42
|
+
const e = stringToUint8Array('e');
|
43
|
+
return concatUint8Arrays([d, ...results, e]);
|
44
|
+
};
|
45
|
+
const encodeArray = (arr) => {
|
46
|
+
const prefixSuffix = te.encode('le'); // Combined prefix and suffix
|
47
|
+
const encodedElements = arr.map(encode); // Encode each element
|
48
|
+
// Concatenate the encoded elements directly into a Uint8Array
|
49
|
+
const result = concatUint8Arrays([prefixSuffix, ...encodedElements.flat()]);
|
50
|
+
return result;
|
51
|
+
};
|
52
|
+
export const encode = (data) => {
|
53
|
+
if (Array.isArray(data)) {
|
54
|
+
return encodeArray(data);
|
94
55
|
}
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
// sorted dicts
|
99
|
-
const keys = Object.keys(data).sort();
|
100
|
-
for (const k of keys) {
|
101
|
-
// filter out null / undefined elements
|
102
|
-
if (data[k] === null || data[k] === undefined) {
|
103
|
-
continue;
|
56
|
+
switch (typeof data) {
|
57
|
+
case 'string': {
|
58
|
+
return encodeString(data);
|
104
59
|
}
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
continue;
|
60
|
+
case 'number': {
|
61
|
+
return encodeNumber(data);
|
62
|
+
}
|
63
|
+
case 'object': {
|
64
|
+
if (data instanceof Uint8Array) {
|
65
|
+
return encodeBuf(data);
|
66
|
+
}
|
67
|
+
return encodeDictionary(data);
|
68
|
+
}
|
69
|
+
default: {
|
70
|
+
throw new Error(`unsupport data type: ${typeof data}`);
|
117
71
|
}
|
118
|
-
_encode(state, buffers, data[i]);
|
119
72
|
}
|
120
|
-
|
121
|
-
|
73
|
+
};
|
74
|
+
export default encode;
|
@@ -0,0 +1,92 @@
|
|
1
|
+
// str1 > str2: 1
|
2
|
+
// str1 === str2: 0
|
3
|
+
// str1 < str2: -1
|
4
|
+
export const cmpRawString = (str1, str2) => {
|
5
|
+
const te = new TextEncoder();
|
6
|
+
const v1 = te.encode(str1);
|
7
|
+
const v2 = te.encode(str2);
|
8
|
+
for (let i = 0; i < Math.min(v1.length, v2.length); i++) {
|
9
|
+
if (v1[i] < v2[i]) {
|
10
|
+
return -1;
|
11
|
+
}
|
12
|
+
if (v1[i] > v2[i]) {
|
13
|
+
return 1;
|
14
|
+
}
|
15
|
+
}
|
16
|
+
if (v1.length === v2.length) {
|
17
|
+
return 0;
|
18
|
+
}
|
19
|
+
return v1.length < v2.length ? -1 : 1;
|
20
|
+
};
|
21
|
+
export const typedArraysAreEqual = (a, b) => {
|
22
|
+
if (a.byteLength !== b.byteLength) {
|
23
|
+
return false;
|
24
|
+
}
|
25
|
+
return a.every((val, i) => val === b[i]);
|
26
|
+
};
|
27
|
+
// Copied from https://github.com/hcodes/isutf8/blob/master/src/index.ts
|
28
|
+
/*
|
29
|
+
https://tools.ietf.org/html/rfc3629
|
30
|
+
UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
|
31
|
+
UTF8-1 = %x00-7F
|
32
|
+
UTF8-2 = %xC2-DF UTF8-tail
|
33
|
+
UTF8-3 = %xE0 %xA0-BF UTF8-tail
|
34
|
+
%xE1-EC 2( UTF8-tail )
|
35
|
+
%xED %x80-9F UTF8-tail
|
36
|
+
%xEE-EF 2( UTF8-tail )
|
37
|
+
UTF8-4 = %xF0 %x90-BF 2( UTF8-tail )
|
38
|
+
%xF1-F3 3( UTF8-tail )
|
39
|
+
%xF4 %x80-8F 2( UTF8-tail )
|
40
|
+
UTF8-tail = %x80-BF
|
41
|
+
*/
|
42
|
+
export const isValidUTF8 = (buf) => {
|
43
|
+
let i = 0;
|
44
|
+
const len = buf.length;
|
45
|
+
while (i < len) {
|
46
|
+
// UTF8-1 = %x00-7F
|
47
|
+
if (buf[i] <= 0x7f) {
|
48
|
+
i++;
|
49
|
+
continue;
|
50
|
+
}
|
51
|
+
// UTF8-2 = %xC2-DF UTF8-tail
|
52
|
+
if (buf[i] >= 0xc2 && buf[i] <= 0xdf) {
|
53
|
+
// if(buf[i + 1] >= 0x80 && buf[i + 1] <= 0xBF) {
|
54
|
+
if (buf[i + 1] >> 6 === 2) {
|
55
|
+
i += 2;
|
56
|
+
continue;
|
57
|
+
}
|
58
|
+
else {
|
59
|
+
return false;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
// UTF8-3 = %xE0 %xA0-BF UTF8-tail
|
63
|
+
// UTF8-3 = %xED %x80-9F UTF8-tail
|
64
|
+
if (((buf[i] === 0xe0 && buf[i + 1] >= 0xa0 && buf[i + 1] <= 0xbf) ||
|
65
|
+
(buf[i] === 0xed && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x9f)) &&
|
66
|
+
buf[i + 2] >> 6 === 2) {
|
67
|
+
i += 3;
|
68
|
+
continue;
|
69
|
+
}
|
70
|
+
// UTF8-3 = %xE1-EC 2( UTF8-tail )
|
71
|
+
// UTF8-3 = %xEE-EF 2( UTF8-tail )
|
72
|
+
if (((buf[i] >= 0xe1 && buf[i] <= 0xec) || (buf[i] >= 0xee && buf[i] <= 0xef)) &&
|
73
|
+
buf[i + 1] >> 6 === 2 &&
|
74
|
+
buf[i + 2] >> 6 === 2) {
|
75
|
+
i += 3;
|
76
|
+
continue;
|
77
|
+
}
|
78
|
+
// UTF8-4 = %xF0 %x90-BF 2( UTF8-tail )
|
79
|
+
// %xF1-F3 3( UTF8-tail )
|
80
|
+
// %xF4 %x80-8F 2( UTF8-tail )
|
81
|
+
if (((buf[i] === 0xf0 && buf[i + 1] >= 0x90 && buf[i + 1] <= 0xbf) ||
|
82
|
+
(buf[i] >= 0xf1 && buf[i] <= 0xf3 && buf[i + 1] >> 6 === 2) ||
|
83
|
+
(buf[i] === 0xf4 && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x8f)) &&
|
84
|
+
buf[i + 2] >> 6 === 2 &&
|
85
|
+
buf[i + 3] >> 6 === 2) {
|
86
|
+
i += 4;
|
87
|
+
continue;
|
88
|
+
}
|
89
|
+
return false;
|
90
|
+
}
|
91
|
+
return true;
|
92
|
+
};
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
2
1
|
/**
|
3
2
|
* sha1 of torrent file info. This hash is commenly used by torrent clients as the ID of the torrent.
|
4
3
|
*/
|
5
|
-
export declare function hash(file:
|
4
|
+
export declare function hash(file: Uint8Array): Promise<string>;
|
6
5
|
export interface TorrentFileData {
|
7
6
|
length: number;
|
8
7
|
files: Array<{
|
@@ -27,7 +26,7 @@ export interface TorrentFileData {
|
|
27
26
|
/**
|
28
27
|
* data about the files the torrent contains
|
29
28
|
*/
|
30
|
-
export declare function files(file:
|
29
|
+
export declare function files(file: Uint8Array): TorrentFileData;
|
31
30
|
export interface TorrentInfo {
|
32
31
|
name: string;
|
33
32
|
/**
|
@@ -55,4 +54,4 @@ export interface TorrentInfo {
|
|
55
54
|
/**
|
56
55
|
* torrent file info
|
57
56
|
*/
|
58
|
-
export declare function info(file:
|
57
|
+
export declare function info(file: Uint8Array): TorrentInfo;
|
package/dist/src/torrentFile.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import { join, sep } from 'node:path';
|
2
2
|
import { sha1 } from 'crypto-hash';
|
3
|
+
import { isUint8Array, uint8ArrayToHex, uint8ArrayToString } from 'uint8array-extras';
|
3
4
|
import { decode, encode } from './bencode/index.js';
|
4
5
|
/**
|
5
6
|
* sha1 of torrent file info. This hash is commenly used by torrent clients as the ID of the torrent.
|
@@ -44,7 +45,7 @@ function sumLength(sum, file) {
|
|
44
45
|
function splitPieces(buf) {
|
45
46
|
const pieces = [];
|
46
47
|
for (let i = 0; i < buf.length; i += 20) {
|
47
|
-
pieces.push(buf.slice(i, i + 20)
|
48
|
+
pieces.push(uint8ArrayToHex(buf.slice(i, i + 20)));
|
48
49
|
}
|
49
50
|
return pieces;
|
50
51
|
}
|
@@ -67,8 +68,8 @@ export function info(file) {
|
|
67
68
|
if (torrent['created by']) {
|
68
69
|
result.createdBy = torrent['created by'].toString();
|
69
70
|
}
|
70
|
-
if (
|
71
|
-
result.comment = torrent.comment
|
71
|
+
if (isUint8Array(torrent.comment)) {
|
72
|
+
result.comment = uint8ArrayToString(torrent.comment);
|
72
73
|
}
|
73
74
|
// announce and announce-list will be missing if metadata fetched via ut_metadata
|
74
75
|
if (Array.isArray(torrent['announce-list']) &&
|
@@ -87,7 +88,7 @@ export function info(file) {
|
|
87
88
|
result.announce = Array.from(new Set(result.announce));
|
88
89
|
}
|
89
90
|
// web seeds
|
90
|
-
if (
|
91
|
+
if (isUint8Array(torrent['url-list'])) {
|
91
92
|
// some clients set url-list to empty string
|
92
93
|
torrent['url-list'] = torrent['url-list'].length > 0 ? [torrent['url-list']] : [];
|
93
94
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ctrl/torrent-file",
|
3
|
-
"version": "
|
3
|
+
"version": "4.0.0",
|
4
4
|
"description": "Parse a torrent file (name, hash, files, pieces)",
|
5
5
|
"author": "Scott Cooper <scttcper@gmail.com>",
|
6
6
|
"license": "MIT",
|
@@ -14,8 +14,12 @@
|
|
14
14
|
"sideEffects": false,
|
15
15
|
"keywords": [],
|
16
16
|
"scripts": {
|
17
|
-
"lint": "
|
18
|
-
"lint:
|
17
|
+
"lint": "pnpm run '/^(lint:biome|lint:eslint)$/'",
|
18
|
+
"lint:biome": "biome check .",
|
19
|
+
"lint:eslint": "eslint --ext .ts,.tsx .",
|
20
|
+
"lint:fix": "pnpm run '/^(lint:biome|lint:eslint):fix$/'",
|
21
|
+
"lint:eslint:fix": "eslint --ext .ts,.tsx . --fix",
|
22
|
+
"lint:biome:fix": "biome check . --apply",
|
19
23
|
"prepare": "npm run build",
|
20
24
|
"build": "tsc",
|
21
25
|
"test": "vitest run",
|
@@ -23,17 +27,18 @@
|
|
23
27
|
"test:ci": "vitest run --coverage --reporter=default --reporter=junit --outputFile=./junit.xml"
|
24
28
|
},
|
25
29
|
"dependencies": {
|
26
|
-
"crypto-hash": "^
|
30
|
+
"crypto-hash": "^3.0.0",
|
31
|
+
"uint8array-extras": "^1.1.0"
|
27
32
|
},
|
28
33
|
"devDependencies": {
|
29
|
-
"@
|
30
|
-
"@
|
31
|
-
"@
|
32
|
-
"@
|
33
|
-
"
|
34
|
-
"parse-torrent": "11.0.
|
35
|
-
"typescript": "5.
|
36
|
-
"vitest": "
|
34
|
+
"@biomejs/biome": "1.6.0",
|
35
|
+
"@ctrl/eslint-config-biome": "2.1.1",
|
36
|
+
"@sindresorhus/tsconfig": "5.0.0",
|
37
|
+
"@types/node": "20.11.25",
|
38
|
+
"@vitest/coverage-v8": "1.3.1",
|
39
|
+
"parse-torrent": "11.0.16",
|
40
|
+
"typescript": "5.4.2",
|
41
|
+
"vitest": "1.3.1"
|
37
42
|
},
|
38
43
|
"publishConfig": {
|
39
44
|
"access": "public",
|
@@ -45,6 +50,6 @@
|
|
45
50
|
]
|
46
51
|
},
|
47
52
|
"engines": {
|
48
|
-
"node": ">=
|
53
|
+
"node": ">=18"
|
49
54
|
}
|
50
55
|
}
|
@@ -1,11 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Check if the given value is an ArrayBuffer.
|
3
|
-
* @param value - The value to check.
|
4
|
-
* @returns `true` if the given value is an ArrayBuffer, else `false`.
|
5
|
-
* @example
|
6
|
-
* isArrayBuffer(new ArrayBuffer())
|
7
|
-
* // => true
|
8
|
-
* isArrayBuffer([])
|
9
|
-
* // => false
|
10
|
-
*/
|
11
|
-
export declare function isArrayBuffer(value: unknown): boolean;
|
@@ -1,16 +0,0 @@
|
|
1
|
-
const hasArrayBuffer = typeof ArrayBuffer === 'function';
|
2
|
-
const { toString } = Object.prototype;
|
3
|
-
/**
|
4
|
-
* Check if the given value is an ArrayBuffer.
|
5
|
-
* @param value - The value to check.
|
6
|
-
* @returns `true` if the given value is an ArrayBuffer, else `false`.
|
7
|
-
* @example
|
8
|
-
* isArrayBuffer(new ArrayBuffer())
|
9
|
-
* // => true
|
10
|
-
* isArrayBuffer([])
|
11
|
-
* // => false
|
12
|
-
*/
|
13
|
-
export function isArrayBuffer(value) {
|
14
|
-
return (hasArrayBuffer &&
|
15
|
-
(value instanceof ArrayBuffer || toString.call(value) === '[object ArrayBuffer]'));
|
16
|
-
}
|