dot-torrent
Advanced tools
Comparing version 1.1.0 to 2.0.0
@@ -24,3 +24,3 @@ /// <reference types="node" /> | ||
/** | ||
* Process buffer remainings | ||
* Process remaining buffer | ||
*/ | ||
@@ -27,0 +27,0 @@ private _flushBuffer; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
@@ -14,9 +37,2 @@ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
}; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -48,4 +64,4 @@ const crypto_1 = __importDefault(require("crypto")); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
for (let i = 0; i < this._files.length; i++) { | ||
yield this._readFile(this._files[i]); | ||
for (const file of this._files) { | ||
yield this._readFile(file); | ||
} | ||
@@ -62,3 +78,3 @@ this._flushBuffer(); | ||
/** | ||
* Process buffer remainings | ||
* Process remaining buffer | ||
*/ | ||
@@ -74,7 +90,9 @@ _flushBuffer() { | ||
_readFile(filePath) { | ||
return new Promise((resolve, reject) => { | ||
const stream = fs.createReadStream(filePath); | ||
stream.on('data', this._onFileData.bind(this)); | ||
stream.on('error', reject); | ||
stream.on('close', resolve); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return new Promise((resolve, reject) => { | ||
const stream = fs.createReadStream(filePath); | ||
stream.on('data', this._onFileData.bind(this)); | ||
stream.on('error', reject); | ||
stream.on('close', resolve); | ||
}); | ||
}); | ||
@@ -81,0 +99,0 @@ } |
import { ISourceFileInfo } from '../types'; | ||
export default class SourceParser { | ||
/** | ||
* Get the file path relative to the source | ||
*/ | ||
private static _getRelativePath; | ||
/** | ||
* Get full path to the file | ||
*/ | ||
private static _getFullPath; | ||
private _files; | ||
@@ -20,7 +12,7 @@ private readonly _source; | ||
*/ | ||
getFiles(): ISourceFileInfo[]; | ||
getFiles(): Array<ISourceFileInfo>; | ||
/** | ||
* Get all file locations | ||
*/ | ||
getFilePathList(): string[]; | ||
getFilePathList(): Array<string>; | ||
/** | ||
@@ -43,5 +35,5 @@ * Get total of files size | ||
/** | ||
* Find all files in directory and collent info | ||
* Find all files in directory and collect info | ||
*/ | ||
private _collectDirectoryFilesRecursively; | ||
} |
"use strict"; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const path = __importStar(require("path")); | ||
const fs = __importStar(require("fs")); | ||
const fs_1 = __importDefault(require("fs")); | ||
const path_1 = __importDefault(require("path")); | ||
class SourceParser { | ||
@@ -22,14 +18,2 @@ /** | ||
/** | ||
* Get the file path relative to the source | ||
*/ | ||
static _getRelativePath(source, filePath) { | ||
return path.relative(source, filePath); | ||
} | ||
/** | ||
* Get full path to the file | ||
*/ | ||
static _getFullPath(filePath) { | ||
return path.resolve(filePath); | ||
} | ||
/** | ||
* Get files info | ||
@@ -45,4 +29,4 @@ */ | ||
const filePathList = []; | ||
for (let i = 0; i < this._files.length; i++) { | ||
filePathList.push(this._files[i].fullPath); | ||
for (const file of this._files) { | ||
filePathList.push(file.fullPath); | ||
} | ||
@@ -56,4 +40,4 @@ return filePathList; | ||
let total = 0; | ||
for (let i = 0; i < this._files.length; i++) { | ||
total += this._files[i].length; | ||
for (const file of this._files) { | ||
total += file.length; | ||
} | ||
@@ -67,5 +51,5 @@ return total; | ||
return { | ||
fullPath: SourceParser._getFullPath(filePath), | ||
relativePath: SourceParser._getRelativePath(this._source, filePath), | ||
length: size ? size : fs.lstatSync(this._source).size, | ||
fullPath: path_1.default.resolve(filePath), | ||
relativePath: path_1.default.relative(this._source, filePath), | ||
length: size ? size : fs_1.default.lstatSync(this._source).size, | ||
}; | ||
@@ -77,3 +61,3 @@ } | ||
_parseSource() { | ||
const stats = fs.lstatSync(this._source); | ||
const stats = fs_1.default.lstatSync(this._source); | ||
if (stats.isDirectory()) { | ||
@@ -84,9 +68,9 @@ this._collectDirectoryFilesRecursively(this._source); | ||
/** | ||
* Find all files in directory and collent info | ||
* Find all files in directory and collect info | ||
*/ | ||
_collectDirectoryFilesRecursively(directoryPath) { | ||
const files = fs.readdirSync(directoryPath); | ||
const files = fs_1.default.readdirSync(directoryPath); | ||
for (const file of files) { | ||
const filePath = path.join(directoryPath, file); | ||
const fileStats = fs.lstatSync(filePath); | ||
const filePath = path_1.default.join(directoryPath, file); | ||
const fileStats = fs_1.default.lstatSync(filePath); | ||
fileStats.isDirectory() | ||
@@ -93,0 +77,0 @@ ? this._collectDirectoryFilesRecursively(filePath) |
@@ -14,15 +14,8 @@ "use strict"; | ||
}; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bencodec_1 = __importDefault(require("bencodec")); | ||
const fs = __importStar(require("fs")); | ||
const path = __importStar(require("path")); | ||
const fs_1 = __importDefault(require("fs")); | ||
const path_1 = __importDefault(require("path")); | ||
const PieceCollector_1 = __importDefault(require("./PieceCollector")); | ||
const SourceParser_1 = __importDefault(require("./SourceParser")); | ||
const PieceCollector_1 = __importDefault(require("./PieceCollector")); | ||
class TorrentGenerator { | ||
@@ -43,23 +36,25 @@ /** | ||
create(outPath) { | ||
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
// TODO: validate mandatory fields | ||
this._announce(); | ||
this._announceList(); | ||
this._comment(); | ||
this._createdBy(); | ||
this._creationDate(); | ||
this._encoding(); | ||
this._files(); | ||
this._length(); | ||
this._name(); | ||
this._pieceLength(); | ||
this._private(); | ||
this._publisher(); | ||
this._publisherUrl(); | ||
yield this._pieces(); | ||
this._torrent.info = this._torrentInfo; | ||
fs.writeFile(outPath, bencodec_1.default.encode(this._torrent), (err => { | ||
err ? reject(err) : resolve(true); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
// TODO: validate mandatory fields | ||
this._announce(); | ||
this._announceList(); | ||
this._comment(); | ||
this._createdBy(); | ||
this._creationDate(); | ||
this._encoding(); | ||
this._files(); | ||
this._length(); | ||
this._name(); | ||
this._pieceLength(); | ||
this._private(); | ||
this._publisher(); | ||
this._publisherUrl(); | ||
yield this._pieces(); | ||
this._torrent.info = this._torrentInfo; | ||
fs_1.default.writeFile(outPath, bencodec_1.default.encode(this._torrent), (err => { | ||
err ? reject(err) : resolve(true); | ||
})); | ||
})); | ||
})); | ||
}); | ||
} | ||
@@ -138,3 +133,3 @@ /** | ||
? this._parameters.name | ||
: path.basename(this._parameters.source); | ||
: path_1.default.basename(this._parameters.source); | ||
} | ||
@@ -145,3 +140,3 @@ /** | ||
_pieceLength() { | ||
return this._torrentInfo['piece length'] = this._pieceCollector.getPieceLength(); | ||
this._torrentInfo['piece length'] = this._pieceCollector.getPieceLength(); | ||
} | ||
@@ -148,0 +143,0 @@ /** |
/// <reference types="node" /> | ||
import { ICreateTorrentParams } from './types'; | ||
import TorrentGenerator from './create/TorrentGenerator'; | ||
import TorrentParser from './parse/TorrentParser'; | ||
import { ICreateTorrentParams, IDotTorrent } from './types'; | ||
export * from './types'; | ||
export { create, parse, parseFromFileSync, parseFromFileAsync, TorrentParser, TorrentGenerator }; | ||
/** | ||
* Parse torrent from Buffer or string | ||
*/ | ||
declare function parse(data: Buffer | string): import("./types").IDotTorrent; | ||
declare function parse(data: Buffer | string): IDotTorrent; | ||
/** | ||
* Parse torrent file | ||
* Parse torrent from file sync | ||
*/ | ||
declare function parseFile(filePath: string): import("./types").IDotTorrent; | ||
declare function parseFromFileSync(filePath: string): IDotTorrent; | ||
/** | ||
* Parse torrent from file async | ||
*/ | ||
declare function parseFromFileAsync(filePath: string): Promise<IDotTorrent>; | ||
/** | ||
* Create torrent file | ||
*/ | ||
declare function create(parameters: ICreateTorrentParams, outPath: string): Promise<boolean>; | ||
export { parse, parseFile, create }; | ||
declare const _default: { | ||
create: typeof create; | ||
parse: typeof parse; | ||
parseFile: typeof parseFile; | ||
create: typeof create; | ||
parseFromFileSync: typeof parseFromFileSync; | ||
parseFromFileAsync: typeof parseFromFileAsync; | ||
}; | ||
export default _default; |
"use strict"; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -13,5 +29,10 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fs = __importStar(require("fs")); | ||
exports.TorrentGenerator = exports.TorrentParser = exports.parseFromFileAsync = exports.parseFromFileSync = exports.parse = exports.create = void 0; | ||
const fs_1 = require("fs"); | ||
const promises_1 = require("fs/promises"); | ||
const TorrentGenerator_1 = __importDefault(require("./create/TorrentGenerator")); | ||
exports.TorrentGenerator = TorrentGenerator_1.default; | ||
const TorrentParser_1 = __importDefault(require("./parse/TorrentParser")); | ||
exports.TorrentParser = TorrentParser_1.default; | ||
__exportStar(require("./types"), exports); | ||
/** | ||
@@ -21,19 +42,35 @@ * Parse torrent from Buffer or string | ||
function parse(data) { | ||
return new TorrentParser_1.default(data).parse(); | ||
const torrentParser = new TorrentParser_1.default(); | ||
return torrentParser.parse(data); | ||
} | ||
exports.parse = parse; | ||
/** | ||
* Parse torrent file | ||
* Parse torrent from file sync | ||
*/ | ||
function parseFile(filePath) { | ||
return new TorrentParser_1.default(fs.readFileSync(filePath)).parse(); | ||
function parseFromFileSync(filePath) { | ||
const torrentParser = new TorrentParser_1.default(); | ||
const data = (0, fs_1.readFileSync)(filePath); | ||
return torrentParser.parse(data); | ||
} | ||
exports.parseFile = parseFile; | ||
exports.parseFromFileSync = parseFromFileSync; | ||
/** | ||
* Parse torrent from file async | ||
*/ | ||
function parseFromFileAsync(filePath) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const torrentParser = new TorrentParser_1.default(); | ||
const data = yield (0, promises_1.readFile)(filePath); | ||
return torrentParser.parse(data); | ||
}); | ||
} | ||
exports.parseFromFileAsync = parseFromFileAsync; | ||
/** | ||
* Create torrent file | ||
*/ | ||
function create(parameters, outPath) { | ||
return new TorrentGenerator_1.default(parameters).create(outPath); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return new TorrentGenerator_1.default(parameters).create(outPath); | ||
}); | ||
} | ||
exports.create = create; | ||
exports.default = { parse, parseFile, create }; | ||
exports.default = { create, parse, parseFromFileSync, parseFromFileAsync }; |
/// <reference types="node" /> | ||
import { IDotTorrent } from '../types'; | ||
export default class TorrentParser { | ||
private readonly _rawTorrent; | ||
/** | ||
* Constructor | ||
*/ | ||
constructor(data: Buffer | string); | ||
constructor(); | ||
/** | ||
* Parse torrent file | ||
*/ | ||
parse(): IDotTorrent; | ||
parse(data: Buffer | string): IDotTorrent; | ||
/** | ||
* Get announce | ||
*/ | ||
private _announce; | ||
private static _announce; | ||
/** | ||
* Get announceList | ||
*/ | ||
private _announceList; | ||
private static _announceList; | ||
/** | ||
* Get comment | ||
*/ | ||
private _comment; | ||
private static _comment; | ||
/** | ||
* Get createdBy | ||
*/ | ||
private _createdBy; | ||
private static _createdBy; | ||
/** | ||
* Get creationDate | ||
*/ | ||
private _creationDate; | ||
private static _creationDate; | ||
/** | ||
* Get encoding | ||
*/ | ||
private _encoding; | ||
private static _encoding; | ||
/** | ||
* Get files | ||
*/ | ||
private _files; | ||
private static _files; | ||
/** | ||
* Get name | ||
*/ | ||
private _name; | ||
private static _name; | ||
/** | ||
* Get pieceLength | ||
*/ | ||
private _pieceLength; | ||
private static _pieceLength; | ||
/** | ||
* Get pieces | ||
*/ | ||
private _pieces; | ||
private static _pieces; | ||
/** | ||
* Get private | ||
*/ | ||
private _private; | ||
private static _private; | ||
/** | ||
* Get publisher | ||
*/ | ||
private _publisher; | ||
private static _publisher; | ||
/** | ||
* Get publisherUrl | ||
*/ | ||
private _publisherUrl; | ||
private static _publisherUrl; | ||
/** | ||
* Get total files length | ||
*/ | ||
private _totalLength; | ||
private static _totalLength; | ||
/** | ||
* Get infoHash | ||
*/ | ||
private _infoHash; | ||
private static _infoHash; | ||
/** | ||
* Get torrent info | ||
*/ | ||
private static _info; | ||
private static _getStringFromBencode; | ||
} |
@@ -12,29 +12,29 @@ "use strict"; | ||
*/ | ||
constructor(data) { | ||
const decodedData = bencodec_1.default.decode(data); | ||
if (Array.isArray(decodedData) || typeof decodedData !== 'object' || Buffer.isBuffer(decodedData)) { | ||
throw new Error('Invalid data'); | ||
} | ||
this._rawTorrent = decodedData; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor | ||
constructor() { } | ||
/** | ||
* Parse torrent file | ||
*/ | ||
parse() { | ||
parse(data) { | ||
const decodedData = bencodec_1.default.decode(data); | ||
; | ||
if (Array.isArray(decodedData) || Buffer.isBuffer(decodedData) || typeof decodedData !== 'object') { | ||
throw new Error('TorrentParser failed to parse torrent. Invalid data'); | ||
} | ||
return { | ||
announce: this._announce(), | ||
announceList: this._announceList(), | ||
comment: this._comment(), | ||
createdBy: this._createdBy(), | ||
createdAt: this._creationDate(), | ||
encoding: this._encoding(), | ||
files: this._files(), | ||
infoHash: this._infoHash(), | ||
name: this._name(), | ||
pieceLength: this._pieceLength(), | ||
pieces: this._pieces(), | ||
private: this._private(), | ||
publisher: this._publisher(), | ||
publisherUrl: this._publisherUrl(), | ||
totalLength: this._totalLength() | ||
announce: TorrentParser._announce(decodedData), | ||
announceList: TorrentParser._announceList(decodedData), | ||
comment: TorrentParser._comment(decodedData), | ||
createdBy: TorrentParser._createdBy(decodedData), | ||
createdAt: TorrentParser._creationDate(decodedData), | ||
encoding: TorrentParser._encoding(decodedData), | ||
files: TorrentParser._files(decodedData), | ||
infoHash: TorrentParser._infoHash(decodedData), | ||
name: TorrentParser._name(decodedData), | ||
pieceLength: TorrentParser._pieceLength(decodedData), | ||
pieces: TorrentParser._pieces(decodedData), | ||
private: TorrentParser._private(decodedData), | ||
publisher: TorrentParser._publisher(decodedData), | ||
publisherUrl: TorrentParser._publisherUrl(decodedData), | ||
totalLength: TorrentParser._totalLength(decodedData) | ||
}; | ||
@@ -45,7 +45,4 @@ } | ||
*/ | ||
_announce() { | ||
if (!this._rawTorrent.announce) { | ||
return null; | ||
} | ||
return this._rawTorrent.announce.toString('utf-8'); | ||
static _announce(decodedData) { | ||
return TorrentParser._getStringFromBencode(decodedData.announce); | ||
} | ||
@@ -55,14 +52,18 @@ /** | ||
*/ | ||
_announceList() { | ||
static _announceList(decodedData) { | ||
const announceList = new Set(); | ||
const announceValue = this._announce(); | ||
const announceValue = TorrentParser._announce(decodedData); | ||
if (announceValue) { | ||
announceList.add(announceValue); | ||
} | ||
if (!this._rawTorrent['announce-list']) { | ||
if (!Array.isArray(decodedData['announce-list'])) { | ||
return Array.from(announceList); | ||
} | ||
for (const announceWrapperArray of this._rawTorrent['announce-list']) { | ||
for (const announce of announceWrapperArray) { | ||
announceList.add(announce.toString('utf-8')); | ||
for (const announceItem of decodedData['announce-list']) { | ||
if (Array.isArray(announceItem)) { | ||
for (const announce of announceItem) { | ||
if (Buffer.isBuffer(announce)) { | ||
announceList.add(announce.toString('utf-8')); | ||
} | ||
} | ||
} | ||
@@ -75,7 +76,4 @@ } | ||
*/ | ||
_comment() { | ||
if (!this._rawTorrent.comment) { | ||
return null; | ||
} | ||
return this._rawTorrent.comment.toString('utf-8'); | ||
static _comment(decodedData) { | ||
return TorrentParser._getStringFromBencode(decodedData.comment); | ||
} | ||
@@ -85,7 +83,4 @@ /** | ||
*/ | ||
_createdBy() { | ||
if (!this._rawTorrent['created by']) { | ||
return null; | ||
} | ||
return this._rawTorrent['created by'].toString('utf-8'); | ||
static _createdBy(decodedData) { | ||
return TorrentParser._getStringFromBencode(decodedData['created by']); | ||
} | ||
@@ -95,7 +90,7 @@ /** | ||
*/ | ||
_creationDate() { | ||
if (!this._rawTorrent['creation date']) { | ||
static _creationDate(decodedData) { | ||
if (typeof decodedData['creation date'] !== 'number') { | ||
return null; | ||
} | ||
return this._rawTorrent['creation date']; | ||
return decodedData['creation date']; | ||
} | ||
@@ -105,7 +100,4 @@ /** | ||
*/ | ||
_encoding() { | ||
if (!this._rawTorrent.encoding) { | ||
return null; | ||
} | ||
return this._rawTorrent.encoding.toString('utf-8'); | ||
static _encoding(decodedData) { | ||
return TorrentParser._getStringFromBencode(decodedData.encoding); | ||
} | ||
@@ -115,27 +107,37 @@ /** | ||
*/ | ||
_files() { | ||
const files = []; | ||
if (!this._rawTorrent.info) { | ||
return files; | ||
static _files(decodedData) { | ||
const torrentInfo = TorrentParser._info(decodedData); | ||
if (!torrentInfo) { | ||
return []; | ||
} | ||
if (!this._rawTorrent.info.files || !this._rawTorrent.info.files.length) { | ||
if (!this._rawTorrent.info.name) { | ||
return files; | ||
const fileMap = new Map(); | ||
// Single file | ||
if (!Array.isArray(torrentInfo.files) || !torrentInfo.files.length) { | ||
if (!torrentInfo.name || typeof torrentInfo.length !== 'number') { | ||
return []; | ||
} | ||
files.push({ | ||
length: this._rawTorrent.info.length || 0, | ||
path: this._rawTorrent.info.name.toString('utf-8') | ||
}); | ||
return files; | ||
const filePath = TorrentParser._getStringFromBencode(torrentInfo.name); | ||
if (!filePath) { | ||
return []; | ||
} | ||
fileMap.set(filePath, torrentInfo.length); | ||
} | ||
for (const file of this._rawTorrent.info.files) { | ||
if (!file.path || !file.length) { | ||
continue; | ||
// Multiple files | ||
else { | ||
for (const fileInfo of torrentInfo.files) { | ||
if (Buffer.isBuffer(fileInfo) || Array.isArray(fileInfo) || typeof fileInfo !== 'object') { | ||
continue; | ||
} | ||
if (typeof fileInfo.length !== 'number' || !Array.isArray(fileInfo.path)) { | ||
continue; | ||
} | ||
const filePath = TorrentParser._getStringFromBencode(fileInfo.path[0]); | ||
if (!filePath) { | ||
continue; | ||
} | ||
fileMap.set(filePath, fileInfo.length); | ||
} | ||
files.push({ | ||
length: file.length, | ||
path: file.path[0].toString('utf-8'), | ||
}); | ||
} | ||
return files; | ||
return Array.from(fileMap.entries()) | ||
.map(([key, value]) => ({ path: key, length: value })); | ||
} | ||
@@ -145,7 +147,8 @@ /** | ||
*/ | ||
_name() { | ||
if (!this._rawTorrent.info || !this._rawTorrent.info.name) { | ||
static _name(decodedData) { | ||
const torrentInfo = TorrentParser._info(decodedData); | ||
if (!torrentInfo || !torrentInfo.name) { | ||
return null; | ||
} | ||
return this._rawTorrent.info.name.toString('utf-8'); | ||
return TorrentParser._getStringFromBencode(torrentInfo.name); | ||
} | ||
@@ -155,7 +158,7 @@ /** | ||
*/ | ||
_pieceLength() { | ||
if (!this._rawTorrent.info || !this._rawTorrent.info['piece length']) { | ||
return 0; | ||
} | ||
return this._rawTorrent.info['piece length']; | ||
static _pieceLength(decodedData) { | ||
const torrentInfo = TorrentParser._info(decodedData); | ||
return torrentInfo && typeof torrentInfo['piece length'] === 'number' | ||
? torrentInfo['piece length'] | ||
: null; | ||
} | ||
@@ -165,10 +168,11 @@ /** | ||
*/ | ||
_pieces() { | ||
const pieces = []; | ||
const pieceLength = this._pieceLength(); | ||
if (!pieceLength || !this._rawTorrent.info || !this._rawTorrent.info.pieces) { | ||
static _pieces(decodedData) { | ||
const torrentInfo = TorrentParser._info(decodedData); | ||
const pieceLength = TorrentParser._pieceLength(decodedData); | ||
if (!torrentInfo || !pieceLength || !Buffer.isBuffer(torrentInfo.pieces)) { | ||
return []; | ||
} | ||
for (let i = 0; i < this._rawTorrent.info.pieces.length; i += pieceLength) { | ||
pieces.push(this._rawTorrent.info.pieces.slice(i, i + pieceLength)); | ||
const pieces = []; | ||
for (let i = 0; i < torrentInfo.pieces.length; i += pieceLength) { | ||
pieces.push(torrentInfo.pieces.subarray(i, i + pieceLength)); | ||
} | ||
@@ -180,7 +184,7 @@ return pieces; | ||
*/ | ||
_private() { | ||
if (!this._rawTorrent.info) { | ||
return false; | ||
} | ||
return !!this._rawTorrent.info.private; | ||
static _private(decodedData) { | ||
const torrentInfo = TorrentParser._info(decodedData); | ||
return torrentInfo && typeof torrentInfo.private === 'number' | ||
? Boolean(torrentInfo.private) | ||
: null; | ||
} | ||
@@ -190,7 +194,4 @@ /** | ||
*/ | ||
_publisher() { | ||
if (!this._rawTorrent.publisher) { | ||
return null; | ||
} | ||
return this._rawTorrent.publisher.toString('utf-8'); | ||
static _publisher(decodedData) { | ||
return TorrentParser._getStringFromBencode(decodedData.publisher); | ||
} | ||
@@ -200,7 +201,4 @@ /** | ||
*/ | ||
_publisherUrl() { | ||
if (!this._rawTorrent['publisher-url']) { | ||
return null; | ||
} | ||
return this._rawTorrent['publisher-url'].toString('utf-8'); | ||
static _publisherUrl(decodedData) { | ||
return TorrentParser._getStringFromBencode(decodedData['publisher-url']); | ||
} | ||
@@ -210,9 +208,8 @@ /** | ||
*/ | ||
_totalLength() { | ||
let totalLength = 0; | ||
const files = this._files(); | ||
for (let i = 0; i < files.length; i++) { | ||
totalLength += files[i].length; | ||
static _totalLength(decodedData) { | ||
const files = TorrentParser._files(decodedData); | ||
if (!files.length) { | ||
return null; | ||
} | ||
return totalLength; | ||
return files.reduce((acc, next) => acc += next.length, 0); | ||
} | ||
@@ -222,8 +219,23 @@ /** | ||
*/ | ||
_infoHash() { | ||
static _infoHash(decodedData) { | ||
return crypto_1.default.createHash('sha1') | ||
.update(bencodec_1.default.encode(this._rawTorrent.info || '')) | ||
.update(bencodec_1.default.encode(TorrentParser._info(decodedData) || '')) | ||
.digest('hex'); | ||
} | ||
/** | ||
* Get torrent info | ||
*/ | ||
static _info(decodedData) { | ||
if (!decodedData.info || Buffer.isBuffer(decodedData.info) || Array.isArray(decodedData.info) || typeof decodedData.info !== 'object') { | ||
return null; | ||
} | ||
return decodedData.info; | ||
} | ||
static _getStringFromBencode(data) { | ||
if (!data || !Buffer.isBuffer(data)) { | ||
return null; | ||
} | ||
return data.toString('utf-8'); | ||
} | ||
} | ||
exports.default = TorrentParser; |
/// <reference types="node" /> | ||
/** | ||
* A list of dictionaries each corresponding to a file (only when multiple files are being shared) | ||
*/ | ||
export interface ITorrentFile { | ||
'length': number; | ||
'path': Array<Buffer> | Array<string>; | ||
} | ||
/** | ||
* This maps to a dictionary whose keys are dependent on whether one or more files are being shared | ||
*/ | ||
export interface ITorrentInfo { | ||
'files': Array<ITorrentFile>; | ||
'length'?: number; | ||
'name': Buffer | string; | ||
'piece length': number; | ||
'pieces': Buffer; | ||
'private'?: number; | ||
} | ||
/** | ||
* A torrent file contains a list of files and integrity metadata about all the pieces, | ||
* and optionally contains a list of trackers | ||
*/ | ||
export interface ITorrent { | ||
'announce': Buffer | string; | ||
'announce-list': Array<Array<Buffer | string>>; | ||
'comment'?: Buffer | string; | ||
'created by'?: Buffer | string; | ||
'creation date'?: number; | ||
'encoding'?: Buffer | string; | ||
'info': ITorrentInfo; | ||
'publisher'?: Buffer | string; | ||
'publisher-url'?: Buffer | string; | ||
} | ||
/** | ||
* Parsed file info from provided source | ||
@@ -58,3 +25,3 @@ */ | ||
*/ | ||
export interface IDotTorrentFile { | ||
export interface IDotTorrentFileInfo { | ||
length: number; | ||
@@ -73,11 +40,11 @@ path: string; | ||
encoding: string | null; | ||
files: Array<IDotTorrentFile>; | ||
files: Array<IDotTorrentFileInfo>; | ||
infoHash: string; | ||
name: string | null; | ||
pieceLength: number; | ||
pieceLength: number | null; | ||
pieces: Array<Buffer>; | ||
private: boolean; | ||
private: boolean | null; | ||
publisher: string | null; | ||
publisherUrl: string | null; | ||
totalLength: number; | ||
totalLength: number | null; | ||
} |
{ | ||
"name": "dot-torrent", | ||
"version": "1.1.0", | ||
"description": "Parse and create torrent files.", | ||
"version": "2.0.0", | ||
"description": "Parse and create torrent files", | ||
"main": "./lib/index.js", | ||
"scripts": { | ||
"build": "./node_modules/.bin/tsc", | ||
"lint": "./node_modules/tslint/bin/tslint -p . -c tslint.json", | ||
"lint": "eslint src/**/*.ts", | ||
"test": "npm run lint && ./node_modules/.bin/jest --runInBand --passWithNoTests", | ||
"prepare": "npm run lint && npm run build", | ||
"prepare": "npm run build", | ||
"prepublishOnly": "npm test" | ||
@@ -15,3 +15,3 @@ }, | ||
"type": "git", | ||
"url": "git+https://github.com/IvanSolomakhin/dot-torrent.git" | ||
"url": "git+https://github.com/isolomak/dot-torrent.git" | ||
}, | ||
@@ -32,16 +32,18 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://github.com/IvanSolomakhin/dot-torrent/issues" | ||
"url": "https://github.com/isolomak/dot-torrent/issues" | ||
}, | ||
"homepage": "https://github.com/IvanSolomakhin/dot-torrent#readme", | ||
"homepage": "https://github.com/isolomak/dot-torrent#readme", | ||
"dependencies": { | ||
"bencodec": "^2.3.1" | ||
"bencodec": "^2.4.3" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^25.2.1", | ||
"@types/node": "^13.11.0", | ||
"jest": "^25.2.7", | ||
"ts-jest": "^25.3.1", | ||
"tslint": "^6.1.1", | ||
"tslint-eslint-rules": "^5.4.0", | ||
"typescript": "^3.8.3" | ||
"@types/jest": "^29.2.3", | ||
"@types/node": "^18.11.10", | ||
"@typescript-eslint/eslint-plugin": "^5.45.0", | ||
"@typescript-eslint/parser": "^5.45.0", | ||
"eslint": "^8.29.0", | ||
"eslint-plugin-jest": "^27.1.6", | ||
"jest": "^29.3.1", | ||
"ts-jest": "^29.0.3", | ||
"typescript": "^4.9.3" | ||
}, | ||
@@ -48,0 +50,0 @@ "types": "./lib/index.d.ts", |
124
README.md
[![NPM](https://nodei.co/npm/dot-torrent.png)](https://npmjs.org/package/dot-torrent) | ||
# Dot-Torrent | ||
@@ -9,57 +9,57 @@ ![ci](https://github.com/IvanSolomakhin/dot-torrent/workflows/ci/badge.svg) | ||
## Dot-Torrent | ||
Library for parsing and creating [torrent](https://en.wikipedia.org/wiki/Torrent_file) files | ||
Library for parsing and creating [torrent](https://en.wikipedia.org/wiki/Torrent_file) files. | ||
Fast and easy to use | ||
Written in TypeScript | ||
Fully tested with 100% code coverage | ||
---- | ||
Fast and easy to use. | ||
Written in TypeScript. | ||
Fully tested with 100% code coverage. | ||
## Installation | ||
| npm | yarn | | ||
|---|---| | ||
| `npm install --save dot-torrent` | `yarn add dot-torrent` | | ||
``` bash | ||
npm install --save dot-torrent | ||
``` | ||
---- | ||
## Getting Started | ||
### Import library | ||
### Parse torrent file | ||
| typescript | javascript | | ||
|---|---| | ||
| `import dotTorrent from 'dot-torrent'` | `const dotTorrent = require('dot-torrent')`| | ||
``` typescript | ||
import dotTorrent from 'dot-torrent'; | ||
### Parse torrent file | ||
// parse from the buffer or the string | ||
const torrent = dotTorrent.parse( fs.readFileSync('path/to/the/file.torrent') ); | ||
// parse from file sync | ||
const torrent = dotTorrent.parseFromFileSync('path/to/the/file.torrent'); | ||
// parse from file async | ||
const torrent = await dotTorrent.parseFromFileAsync('path/to/the/file.torrent'); | ||
``` typescript | ||
// parse from the Buffer or the string | ||
const torrent = dotTorrent.parse( fs.readFileSync('path/to/the/file.torrent') ); | ||
// parse file | ||
const torrent = dotTorrent.parseFile('path/to/the/file.torrent'); | ||
// torrent | ||
{ | ||
announce: 'https://testtracker-1.net/testtopic.php?t=1111111', | ||
announceList: [ | ||
'https://testtracker-1.net/testtopic.php?t=1111111', | ||
'https://testtracker-2.net/testtopic.php?t=2222222', | ||
'https://testtracker-3.net/testtopic.php?t=3333333' | ||
], | ||
comment: 'Amazing torrent', | ||
createdBy: 'https://www.npmjs.com/package/dot-torrent', | ||
createdAt: 1586292962, | ||
encoding: 'UTF-8', | ||
files: [ { | ||
length: 100500, | ||
path: 'awesome_torrent.txt' | ||
} ], | ||
infoHash: '600ccd1b71569232d01d110bc63e906beab04d8c', | ||
name: 'awesome_torrent.txt', | ||
pieceLength: 32768, | ||
pieces: <Buffer 4d 92 fb 38 ... fe 74 59 b4 67 56 05>, | ||
private: false, | ||
publisher: 'isolomak', | ||
publisherUrl: 'https://www.npmjs.com/package/dot-torrent', | ||
totalLength: 100500 | ||
} | ||
// torrent | ||
{ | ||
announce: 'https://test-tracker-1.net/testtopic.php?t=1111111', | ||
announceList: [ | ||
'https://test-tracker-1.net/testtopic.php?t=1111111', | ||
'https://test-tracker-2.net/testtopic.php?t=2222222', | ||
'https://test-tracker-3.net/testtopic.php?t=3333333' | ||
], | ||
comment: 'Amazing torrent', | ||
createdBy: 'https://www.npmjs.com/package/dot-torrent', | ||
createdAt: 1586292962, | ||
encoding: 'UTF-8', | ||
files: [ { | ||
length: 100500, | ||
path: 'awesome_torrent.txt' | ||
} ], | ||
infoHash: '600ccd1b71569232d01d110bc63e906beab04d8c', | ||
name: 'awesome_torrent.txt', | ||
private: false, | ||
publisher: 'isolomak', | ||
publisherUrl: 'https://www.npmjs.com/package/dot-torrent', | ||
totalLength: 100500, | ||
pieceLength: 32768, | ||
pieces: <Buffer 4d 92 fb 38 ... fe 74 59 b4 67 56 05> | ||
} | ||
``` | ||
@@ -70,19 +70,23 @@ | ||
``` typescript | ||
const parameters = { | ||
announceList: [ | ||
'https://testtracker-1.net/testtopic.php?t=1111111' | ||
], | ||
source: './path/to/the/folder/or/a/file', | ||
comment: 'Amazing torrent', // optional | ||
name: 'awesome_torrent', // optional | ||
private: false, // optional (false by default) | ||
publisher: 'isolomak', // optional | ||
publisherUrl: 'https://www.npmjs.com/package/dot-torrent' // optional | ||
}; | ||
import dotTorrent from 'dot-torrent'; | ||
const outputPath = './awesome.torrent'; | ||
const parameters = { | ||
announceList: [ | ||
'https://testtracker-1.net/testtopic.php?t=1111111' | ||
], | ||
source: './path/to/the/folder/or/a/file', | ||
comment: 'Amazing torrent', // optional | ||
name: 'awesome_torrent.txt', // optional | ||
private: false, // optional (false by default) | ||
publisher: 'isolomak', // optional | ||
publisherUrl: 'https://www.npmjs.com/package/dot-torrent' // optional | ||
}; | ||
await dotTorrent.create(parameters, outputPath); | ||
const outputPath = './torrents/awesome.torrent'; | ||
await dotTorrent.create(parameters, outputPath); | ||
``` | ||
---- | ||
## Tests | ||
@@ -94,4 +98,6 @@ | ||
---- | ||
## License | ||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
34280
966
101
9
5
Updatedbencodec@^2.4.3