@ctrl/torrent-file 3.0.0 → 4.1.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 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 and keeps dependencies to a minimum.
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
- /// <reference types="node" resolution-mode="require"/>
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
- const INTEGER_START = 0x69; // 'i'
2
- const STRING_DELIM = 0x3a; // ':'
3
- const DICTIONARY_START = 0x64; // 'd'
4
- const LIST_START = 0x6c; // 'l'
5
- const END_OF_TYPE = 0x65; // 'e'
6
- /**
7
- * Replaces parseInt(buffer.toString('ascii', start, end)).
8
- * For strings with less then ~30 charachters, this is actually a lot faster.
9
- */
10
- function getIntFromBuffer(buffer, start, end) {
11
- let sum = 0;
12
- let sign = 1;
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
- if (num < 58 && num >= 48) {
19
- sum = sum * 10 + (num - 48);
20
- continue;
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
- if (i === start && num === 43) {
23
- // +
24
- continue;
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
- if (i === start && num === 45) {
27
- // -
28
- sign = -1;
29
- continue;
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
- if (num === 46) {
32
- // .
33
- // its a float. break here.
34
- break;
41
+ const result = this.readByte();
42
+ if (result === null) {
43
+ return '';
35
44
  }
36
- throw new Error(`not a number: buffer['${i}'] = ${num}`);
45
+ this.idx--;
46
+ return result;
37
47
  }
38
- return sum * sign;
39
- }
40
- /**
41
- * Decodes bencoded data
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
- function find(state, chr) {
70
- let i = state.position;
71
- const c = state.data.length;
72
- const d = state.data;
73
- while (i < c) {
74
- if (d[i] === chr) {
75
- return i;
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
- throw new Error('Invalid data: Missing delimiter "' +
80
- String.fromCharCode(chr) +
81
- '" [0x' +
82
- chr.toString(16) +
83
- ']');
84
- }
85
- function dictionary(state) {
86
- state.position++;
87
- const dict = {};
88
- while (state.data[state.position] !== END_OF_TYPE) {
89
- dict[buffer(state)] = next(state);
90
- }
91
- state.position++;
92
- return dict;
93
- }
94
- function list(state) {
95
- state.position++;
96
- const lst = [];
97
- while (state.data[state.position] !== END_OF_TYPE) {
98
- lst.push(next(state));
99
- }
100
- state.position++;
101
- return lst;
102
- }
103
- function integer(state) {
104
- const end = find(state, END_OF_TYPE);
105
- const number = getIntFromBuffer(state.data, state.position + 1, end);
106
- state.position += end + 1 - state.position;
107
- return number;
108
- }
109
- function buffer(state) {
110
- let sep = find(state, STRING_DELIM);
111
- const length = getIntFromBuffer(state.data, state.position, sep);
112
- const end = ++sep + length;
113
- state.position = end;
114
- return state.encoding
115
- ? state.data.toString(state.encoding, sep, end)
116
- : state.data.slice(sep, end);
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
- /// <reference types="node" resolution-mode="require"/>
2
- /**
3
- * Encodes data in bencode.
4
- */
5
- export declare function encode(data: Buffer | string | any[] | ArrayBuffer | number | boolean | Record<string, unknown> | object, buffer?: unknown, offset?: number, disableFloatConversionWarning?: boolean): Buffer;
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
- /* eslint-disable @typescript-eslint/ban-types */
2
- import { isArrayBuffer } from '../isArrayBuffer.js';
3
- /**
4
- * Encodes data in bencode.
5
- */
6
- export function encode(data, buffer, offset, disableFloatConversionWarning = false) {
7
- const buffers = [];
8
- const state = {
9
- bytes: -1,
10
- disableFloatConversionWarning,
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
- function getType(value) {
22
- if (Buffer.isBuffer(value)) {
23
- return 'buffer';
24
- }
25
- if (Array.isArray(value)) {
26
- return 'array';
27
- }
28
- if (ArrayBuffer.isView(value)) {
29
- return 'arraybufferview';
30
- }
31
- if (value instanceof Number) {
32
- return 'number';
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
- const buffE = Buffer.from('e');
76
- const buffD = Buffer.from('d');
77
- const buffL = Buffer.from('l');
78
- function buffer(buffers, data) {
79
- buffers.push(Buffer.from(`${data.length}:`), data);
80
- }
81
- function string(buffers, data) {
82
- buffers.push(Buffer.from(`${Buffer.byteLength(data)}:${data}`));
83
- }
84
- function number(state, buffers, data) {
85
- const maxLo = 0x80000000;
86
- const hi = (data / maxLo) << 0;
87
- const lo = data % maxLo << 0;
88
- const val = hi * maxLo + lo;
89
- buffers.push(Buffer.from(`i${val}e`));
90
- if (val !== data && !state.disableFloatConversionWarning) {
91
- state.disableFloatConversionWarning = true;
92
- console.warn(`WARNING: Possible data corruption detected with value "${data}":`, `Bencoding only defines support for integers, value was converted to "${val}"`);
93
- console.trace();
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
- function dict(state, buffers, data) {
97
- buffers.push(buffD);
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
- string(buffers, k);
106
- _encode(state, buffers, data[k]);
107
- }
108
- buffers.push(buffE);
109
- }
110
- function list(state, buffers, data) {
111
- let i = 0;
112
- const c = data.length;
113
- buffers.push(buffL);
114
- for (; i < c; i++) {
115
- if (data[i] === null) {
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
- buffers.push(buffE);
121
- }
73
+ };
74
+ export default encode;
@@ -0,0 +1,3 @@
1
+ export declare const cmpRawString: (str1: string, str2: string) => number;
2
+ export declare const typedArraysAreEqual: <T extends Uint8Array>(a: T, b: T) => boolean;
3
+ export declare const isValidUTF8: (buf: Uint8Array) => boolean;
@@ -0,0 +1,94 @@
1
+ /* eslint-disable no-bitwise */
2
+ // str1 > str2: 1
3
+ // str1 === str2: 0
4
+ // str1 < str2: -1
5
+ export const cmpRawString = (str1, str2) => {
6
+ const te = new TextEncoder();
7
+ const v1 = te.encode(str1);
8
+ const v2 = te.encode(str2);
9
+ for (let i = 0; i < Math.min(v1.length, v2.length); i++) {
10
+ if (v1[i] < v2[i]) {
11
+ return -1;
12
+ }
13
+ if (v1[i] > v2[i]) {
14
+ return 1;
15
+ }
16
+ }
17
+ if (v1.length === v2.length) {
18
+ return 0;
19
+ }
20
+ return v1.length < v2.length ? -1 : 1;
21
+ };
22
+ export const typedArraysAreEqual = (a, b) => {
23
+ if (a.byteLength !== b.byteLength) {
24
+ return false;
25
+ }
26
+ return a.every((val, i) => val === b[i]);
27
+ };
28
+ // Copied from https://github.com/hcodes/isutf8/blob/master/src/index.ts
29
+ /*
30
+ https://tools.ietf.org/html/rfc3629
31
+ UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
32
+ UTF8-1 = %x00-7F
33
+ UTF8-2 = %xC2-DF UTF8-tail
34
+ UTF8-3 = %xE0 %xA0-BF UTF8-tail
35
+ %xE1-EC 2( UTF8-tail )
36
+ %xED %x80-9F UTF8-tail
37
+ %xEE-EF 2( UTF8-tail )
38
+ UTF8-4 = %xF0 %x90-BF 2( UTF8-tail )
39
+ %xF1-F3 3( UTF8-tail )
40
+ %xF4 %x80-8F 2( UTF8-tail )
41
+ UTF8-tail = %x80-BF
42
+ */
43
+ export const isValidUTF8 = (buf) => {
44
+ let i = 0;
45
+ const len = buf.length;
46
+ while (i < len) {
47
+ // UTF8-1 = %x00-7F
48
+ if (buf[i] <= 0x7f) {
49
+ i++;
50
+ continue;
51
+ }
52
+ // UTF8-2 = %xC2-DF UTF8-tail
53
+ if (buf[i] >= 0xc2 && buf[i] <= 0xdf) {
54
+ // if(buf[i + 1] >= 0x80 && buf[i + 1] <= 0xBF) {
55
+ if (buf[i + 1] >> 6 === 2) {
56
+ i += 2;
57
+ continue;
58
+ // biome-ignore lint/style/noUselessElse: false positive
59
+ }
60
+ else {
61
+ return false;
62
+ }
63
+ }
64
+ // UTF8-3 = %xE0 %xA0-BF UTF8-tail
65
+ // UTF8-3 = %xED %x80-9F UTF8-tail
66
+ if (((buf[i] === 0xe0 && buf[i + 1] >= 0xa0 && buf[i + 1] <= 0xbf) ||
67
+ (buf[i] === 0xed && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x9f)) &&
68
+ buf[i + 2] >> 6 === 2) {
69
+ i += 3;
70
+ continue;
71
+ }
72
+ // UTF8-3 = %xE1-EC 2( UTF8-tail )
73
+ // UTF8-3 = %xEE-EF 2( UTF8-tail )
74
+ if (((buf[i] >= 0xe1 && buf[i] <= 0xec) || (buf[i] >= 0xee && buf[i] <= 0xef)) &&
75
+ buf[i + 1] >> 6 === 2 &&
76
+ buf[i + 2] >> 6 === 2) {
77
+ i += 3;
78
+ continue;
79
+ }
80
+ // UTF8-4 = %xF0 %x90-BF 2( UTF8-tail )
81
+ // %xF1-F3 3( UTF8-tail )
82
+ // %xF4 %x80-8F 2( UTF8-tail )
83
+ if (((buf[i] === 0xf0 && buf[i + 1] >= 0x90 && buf[i + 1] <= 0xbf) ||
84
+ (buf[i] >= 0xf1 && buf[i] <= 0xf3 && buf[i + 1] >> 6 === 2) ||
85
+ (buf[i] === 0xf4 && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x8f)) &&
86
+ buf[i + 2] >> 6 === 2 &&
87
+ buf[i + 3] >> 6 === 2) {
88
+ i += 4;
89
+ continue;
90
+ }
91
+ return false;
92
+ }
93
+ return true;
94
+ };
@@ -1,2 +1 @@
1
- export * from './bencode/index.js';
2
1
  export * from './torrentFile.js';
package/dist/src/index.js CHANGED
@@ -1,2 +1 @@
1
- export * from './bencode/index.js';
2
1
  export * from './torrentFile.js';
@@ -1,8 +1,8 @@
1
- /// <reference types="node" resolution-mode="require"/>
1
+ export declare const sha1: (input: Uint8Array) => string;
2
2
  /**
3
3
  * sha1 of torrent file info. This hash is commenly used by torrent clients as the ID of the torrent.
4
4
  */
5
- export declare function hash(file: Buffer): Promise<string>;
5
+ export declare function hash(file: Uint8Array): string;
6
6
  export interface TorrentFileData {
7
7
  length: number;
8
8
  files: Array<{
@@ -27,7 +27,7 @@ export interface TorrentFileData {
27
27
  /**
28
28
  * data about the files the torrent contains
29
29
  */
30
- export declare function files(file: Buffer): TorrentFileData;
30
+ export declare function files(file: Uint8Array): TorrentFileData;
31
31
  export interface TorrentInfo {
32
32
  name: string;
33
33
  /**
@@ -55,4 +55,4 @@ export interface TorrentInfo {
55
55
  /**
56
56
  * torrent file info
57
57
  */
58
- export declare function info(file: Buffer): TorrentInfo;
58
+ export declare function info(file: Uint8Array): TorrentInfo;
@@ -1,10 +1,17 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { join, sep } from 'node:path';
2
- import { sha1 } from 'crypto-hash';
3
+ import { isUint8Array, uint8ArrayToHex, uint8ArrayToString } from 'uint8array-extras';
3
4
  import { decode, encode } from './bencode/index.js';
5
+ export const sha1 = (input) => {
6
+ const hash = createHash('sha1');
7
+ // Update the hash object with the data
8
+ hash.update(input);
9
+ return hash.digest('hex');
10
+ };
4
11
  /**
5
12
  * sha1 of torrent file info. This hash is commenly used by torrent clients as the ID of the torrent.
6
13
  */
7
- export async function hash(file) {
14
+ export function hash(file) {
8
15
  const torrent = decode(file);
9
16
  return sha1(encode(torrent.info));
10
17
  }
@@ -44,7 +51,7 @@ function sumLength(sum, file) {
44
51
  function splitPieces(buf) {
45
52
  const pieces = [];
46
53
  for (let i = 0; i < buf.length; i += 20) {
47
- pieces.push(buf.slice(i, i + 20).toString('hex'));
54
+ pieces.push(uint8ArrayToHex(buf.slice(i, i + 20)));
48
55
  }
49
56
  return pieces;
50
57
  }
@@ -67,8 +74,8 @@ export function info(file) {
67
74
  if (torrent['created by']) {
68
75
  result.createdBy = torrent['created by'].toString();
69
76
  }
70
- if (Buffer.isBuffer(torrent.comment)) {
71
- result.comment = torrent.comment.toString();
77
+ if (isUint8Array(torrent.comment)) {
78
+ result.comment = uint8ArrayToString(torrent.comment);
72
79
  }
73
80
  // announce and announce-list will be missing if metadata fetched via ut_metadata
74
81
  if (Array.isArray(torrent['announce-list']) &&
@@ -87,7 +94,7 @@ export function info(file) {
87
94
  result.announce = Array.from(new Set(result.announce));
88
95
  }
89
96
  // web seeds
90
- if (Buffer.isBuffer(torrent['url-list'])) {
97
+ if (isUint8Array(torrent['url-list'])) {
91
98
  // some clients set url-list to empty string
92
99
  torrent['url-list'] = torrent['url-list'].length > 0 ? [torrent['url-list']] : [];
93
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl/torrent-file",
3
- "version": "3.0.0",
3
+ "version": "4.1.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": "eslint --ext .ts .",
18
- "lint:fix": "eslint --fix --ext .ts .",
17
+ "lint": "pnpm run '/^(lint:biome|lint:eslint)$/'",
18
+ "lint:biome": "biome check .",
19
+ "lint:eslint": "eslint .",
20
+ "lint:fix": "pnpm run '/^(lint:biome|lint:eslint):fix$/'",
21
+ "lint:eslint:fix": "eslint . --fix",
22
+ "lint:biome:fix": "biome check . --apply",
19
23
  "prepare": "npm run build",
20
24
  "build": "tsc",
21
25
  "test": "vitest run",
@@ -23,16 +27,17 @@
23
27
  "test:ci": "vitest run --coverage --reporter=default --reporter=junit --outputFile=./junit.xml"
24
28
  },
25
29
  "dependencies": {
26
- "crypto-hash": "^2.0.1"
30
+ "uint8array-extras": "^1.4.0"
27
31
  },
28
32
  "devDependencies": {
29
- "@ctrl/eslint-config": "4.0.6",
30
- "@sindresorhus/tsconfig": "4.0.0",
31
- "@types/node": "20.6.0",
32
- "@vitest/coverage-v8": "0.34.4",
33
- "parse-torrent": "11.0.14",
34
- "typescript": "5.2.2",
35
- "vitest": "0.34.4"
33
+ "@biomejs/biome": "1.8.3",
34
+ "@ctrl/eslint-config-biome": "4.2.1",
35
+ "@sindresorhus/tsconfig": "6.0.0",
36
+ "@types/node": "22.5.4",
37
+ "@vitest/coverage-v8": "2.0.5",
38
+ "parse-torrent": "11.0.17",
39
+ "typescript": "5.6.2",
40
+ "vitest": "2.0.5"
36
41
  },
37
42
  "publishConfig": {
38
43
  "access": "public",
@@ -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
- }