tor-dl 1.0.2 → 1.0.3

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.
Files changed (42) hide show
  1. package/README.md +22 -180
  2. package/dist/cli/display.d.ts.map +1 -1
  3. package/dist/cli/display.js +9 -7
  4. package/dist/cli/display.js.map +1 -1
  5. package/dist/cli/parser.d.ts.map +1 -1
  6. package/dist/cli/parser.js +21 -27
  7. package/dist/cli/parser.js.map +1 -1
  8. package/dist/commands/download.d.ts +1 -1
  9. package/dist/commands/download.d.ts.map +1 -1
  10. package/dist/commands/download.js +55 -49
  11. package/dist/commands/download.js.map +1 -1
  12. package/dist/download/engine.d.ts +1 -3
  13. package/dist/download/engine.d.ts.map +1 -1
  14. package/dist/download/engine.js +0 -129
  15. package/dist/download/engine.js.map +1 -1
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +0 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/sources/nyaa.d.ts.map +1 -1
  21. package/dist/sources/nyaa.js +3 -0
  22. package/dist/sources/nyaa.js.map +1 -1
  23. package/dist/sources/thepiratebay.d.ts.map +1 -1
  24. package/dist/sources/thepiratebay.js +1 -0
  25. package/dist/sources/thepiratebay.js.map +1 -1
  26. package/dist/sources/yts.d.ts.map +1 -1
  27. package/dist/sources/yts.js +1 -0
  28. package/dist/sources/yts.js.map +1 -1
  29. package/dist/types.d.ts +1 -0
  30. package/dist/types.d.ts.map +1 -1
  31. package/package.json +3 -5
  32. package/src/cli/display.ts +10 -7
  33. package/src/cli/parser.ts +23 -30
  34. package/src/commands/download.ts +48 -15
  35. package/src/commands/search.ts +52 -52
  36. package/src/download/engine.ts +1 -144
  37. package/src/index.ts +0 -1
  38. package/src/sources/nyaa.ts +3 -0
  39. package/src/sources/thepiratebay.ts +1 -0
  40. package/src/sources/yts.ts +1 -0
  41. package/src/types.ts +1 -0
  42. package/src/cli/progress.ts +0 -78
@@ -1,53 +1,53 @@
1
- import ora from 'ora';
2
- import { SearchOptions, TorrentResult } from '../types';
3
- import { getEnabledSources } from '../sources/registry';
4
- import { filterByCategory, filterBySize, filterBySeeds, sortResults } from '../filters';
5
- import { displayResults, displayError } from '../cli/display';
6
- import { cacheResults } from '../download/engine';
7
-
8
- export async function searchCommand(options: SearchOptions): Promise<void> {
9
- const spinner = ora('Searching torrent sources...').start();
10
-
11
- let sources = getEnabledSources();
12
-
13
- if (options.sources && options.sources.length > 0) {
14
- const requested = options.sources.map(s => s.toLowerCase());
15
- sources = sources.filter(s => requested.includes(s.name.toLowerCase()));
16
- }
17
-
18
- const allResults: TorrentResult[] = [];
19
-
20
- for (const source of sources) {
21
- try {
22
- spinner.text = `Searching ${source.name}...`;
23
- const results = await source.search(options.query, options.category);
24
- allResults.push(...results);
25
- } catch (error) {
26
- spinner.warn(`Failed to search ${source.name}`);
27
- }
28
- }
29
-
30
- spinner.succeed(`Search complete. Found ${allResults.length} results.`);
31
-
32
- let filtered = filterByCategory(allResults, options.category || 'all');
33
-
34
- if (options.minSeeds && options.minSeeds > 0) {
35
- filtered = filterBySeeds(filtered, options.minSeeds);
36
- }
37
-
38
- if (options.minSize || options.maxSize) {
39
- filtered = filterBySize(filtered, options.minSize, options.maxSize);
40
- }
41
-
42
- filtered = sortResults(filtered, options.sortBy || 'seeds', options.order || 'desc');
43
-
44
- if (options.limit && options.limit > 0) {
45
- filtered = filtered.slice(0, options.limit);
46
- }
47
-
48
- filtered = filtered.map((r, i) => ({ ...r, num: i + 1 }));
49
-
50
- cacheResults(filtered);
51
-
52
- displayResults(filtered);
1
+ import ora from 'ora';
2
+ import { SearchOptions, TorrentResult } from '../types';
3
+ import { getEnabledSources } from '../sources/registry';
4
+ import { filterByCategory, filterBySize, filterBySeeds, sortResults } from '../filters';
5
+ import { displayResults, displayError } from '../cli/display';
6
+ import { cacheResults } from '../download/engine';
7
+
8
+ export async function searchCommand(options: SearchOptions): Promise<void> {
9
+ const spinner = ora('Searching torrent sources...').start();
10
+
11
+ let sources = getEnabledSources();
12
+
13
+ if (options.sources && options.sources.length > 0) {
14
+ const requested = options.sources.map(s => s.toLowerCase());
15
+ sources = sources.filter(s => requested.includes(s.name.toLowerCase()));
16
+ }
17
+
18
+ const allResults: TorrentResult[] = [];
19
+
20
+ for (const source of sources) {
21
+ try {
22
+ spinner.text = `Searching ${source.name}...`;
23
+ const results = await source.search(options.query, options.category);
24
+ allResults.push(...results);
25
+ } catch (error) {
26
+ spinner.warn(`Failed to search ${source.name}`);
27
+ }
28
+ }
29
+
30
+ spinner.succeed(`Search complete. Found ${allResults.length} results.`);
31
+
32
+ let filtered = filterByCategory(allResults, options.category || 'all');
33
+
34
+ if (options.minSeeds && options.minSeeds > 0) {
35
+ filtered = filterBySeeds(filtered, options.minSeeds);
36
+ }
37
+
38
+ if (options.minSize || options.maxSize) {
39
+ filtered = filterBySize(filtered, options.minSize, options.maxSize);
40
+ }
41
+
42
+ filtered = sortResults(filtered, options.sortBy || 'seeds', options.order || 'desc');
43
+
44
+ if (options.limit && options.limit > 0) {
45
+ filtered = filtered.slice(0, options.limit);
46
+ }
47
+
48
+ filtered = filtered.map((r, i) => ({ ...r, num: i + 1 }));
49
+
50
+ cacheResults(filtered);
51
+
52
+ displayResults(filtered);
53
53
  }
@@ -1,7 +1,4 @@
1
- import WebTorrent from 'webtorrent';
2
- import { DownloadProgress } from '../cli/progress';
3
- import { TorrentResult, DownloadOptions } from '../types';
4
- import axios from 'axios';
1
+ import { TorrentResult } from '../types';
5
2
  import { writeFileSync, readFileSync, existsSync } from 'fs';
6
3
  import { join } from 'path';
7
4
 
@@ -34,144 +31,4 @@ export function getCachedResults(): TorrentResult[] {
34
31
  cachedResults = loadCache();
35
32
  }
36
33
  return cachedResults;
37
- }
38
-
39
- export async function downloadTorrent(
40
- result: TorrentResult,
41
- options: DownloadOptions = {}
42
- ): Promise<void> {
43
- const client = new WebTorrent();
44
- const progress = new DownloadProgress();
45
- const savePath = options.savePath || process.cwd();
46
-
47
- let torrentId: string;
48
-
49
- if (result.magnet && result.magnet.startsWith('magnet:')) {
50
- torrentId = result.magnet;
51
- } else if (result.url && result.url.includes('magnet:')) {
52
- torrentId = result.url;
53
- } else if (result.url && result.url.includes('nyaa.si')) {
54
- console.log('Fetching magnet link from Nyaa...');
55
- const magnet = await getMagnetFromResult(result);
56
- if (magnet) {
57
- torrentId = magnet;
58
- } else {
59
- throw new Error('Could not get magnet link from Nyaa page');
60
- }
61
- } else {
62
- console.log('Fetching torrent file...');
63
- const torrentUrl = await getTorrentUrl(result);
64
- if (torrentUrl) {
65
- const response = await axios.get(torrentUrl, { responseType: 'arraybuffer' });
66
- const tempFile = join(savePath, 'temp.torrent');
67
- writeFileSync(tempFile, response.data);
68
- torrentId = tempFile;
69
- } else {
70
- throw new Error('Could not get torrent URL');
71
- }
72
- }
73
-
74
- console.log(`\nDownloading: ${result.name}`);
75
- console.log(`Saving to: ${savePath}\n`);
76
-
77
- return new Promise((resolve, reject) => {
78
- console.log('Adding torrent to client...');
79
- console.log('Torrent ID:', torrentId.substring(0, 60) + '...');
80
-
81
- const torrent = client.add(torrentId, {
82
- path: savePath
83
- });
84
-
85
- torrent.on('warning', (warn) => {
86
- console.log('Torrent warning:', warn.message);
87
- });
88
-
89
- torrent.on('peer', (peer) => {
90
- console.log('New peer connected:', peer);
91
- });
92
-
93
- console.log('Torrent added, waiting for ready...');
94
-
95
- torrent.on('ready', () => {
96
- const total = torrent.length;
97
- console.log(`✓ Torrent ready! Total size: ${(total / (1024*1024)).toFixed(2)} MB`);
98
- console.log(` Files: ${torrent.files.map(f => f.name).join(', ')}`);
99
- console.log(` Info hash: ${torrent.infoHash}`);
100
- progress.start(total);
101
-
102
- torrent.on('download', (bytes: number) => {
103
- const percent = ((torrent.downloaded / total) * 100).toFixed(1);
104
- process.stdout.write(`\rDownloading: ${percent}% (${(torrent.downloaded/1024/1024).toFixed(1)} MB / ${(total/1024/1024).toFixed(1)} MB) - ${torrent.peers.length} peers `);
105
- progress.update(torrent.downloaded, total);
106
- });
107
-
108
- torrent.on('done', () => {
109
- progress.stop();
110
- console.log('\n\n✓ Download complete!');
111
- console.log(`Downloaded to: ${savePath}`);
112
- client.destroy();
113
- resolve();
114
- });
115
- });
116
-
117
- torrent.on('error', (err: Error) => {
118
- console.error('\nDownload error:', err.message);
119
- client.destroy();
120
- reject(err);
121
- });
122
-
123
- setTimeout(() => {
124
- console.log('\n\nDebug:');
125
- console.log(' Downloaded:', torrent.downloaded);
126
- console.log(' Length:', torrent.length);
127
- console.log(' Peers:', torrent.peers?.length || 0);
128
- console.log(' Done:', torrent.done);
129
-
130
- if (torrent.downloaded > 0) {
131
- console.log('\nDownload in progress but taking long...');
132
- console.log(`Progress: ${(torrent.downloaded/torrent.length*100).toFixed(1)}%`);
133
- } else {
134
- console.log('\nNo progress after 60s - may be stuck or no peers');
135
- }
136
- }, 60000);
137
- });
138
- }
139
-
140
- async function getTorrentUrl(result: TorrentResult): Promise<string> {
141
- if (result.magnet && result.magnet.startsWith('magnet:')) {
142
- return result.magnet;
143
- }
144
- return result.url;
145
- }
146
-
147
- async function getMagnetFromResult(result: TorrentResult): Promise<string> {
148
- if (result.magnet) return result.magnet;
149
- if (!result.url) return '';
150
-
151
- try {
152
- const { data } = await axios.get(result.url, { timeout: 15000 });
153
- const cheerio = require('cheerio');
154
- const $ = cheerio.load(data);
155
- return $('a[href^="magnet:"]').attr('href') || '';
156
- } catch {
157
- return '';
158
- }
159
- }
160
-
161
- export async function downloadByNumber(
162
- number: number,
163
- savePath?: string
164
- ): Promise<void> {
165
- const results = getCachedResults();
166
-
167
- if (results.length === 0) {
168
- throw new Error('No search results. Run "tor-dl search" first.');
169
- }
170
-
171
- if (number < 1 || number > results.length) {
172
- throw new Error(`Invalid number. Choose between 1 and ${results.length}`);
173
- }
174
-
175
- const result = results[number - 1];
176
- await downloadTorrent(result, { savePath });
177
34
  }
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from './types';
2
2
  export { createParser, loadFilters } from './cli/parser';
3
3
  export * from './cli/display';
4
- export * from './cli/progress';
5
4
  export * from './sources/registry';
6
5
  export * from './filters';
7
6
  export * from './download/engine';
@@ -36,6 +36,8 @@ export class NyaaScraper implements SourceScraper {
36
36
  const peers = parseInt($(el).find('td:nth-child(7)').text().trim()) || 0;
37
37
 
38
38
  if (title && title.length > 3) {
39
+ const idMatch = link.match(/\/view\/(\d+)/);
40
+ const torrentUrl = idMatch ? `https://nyaa.si/download/${idMatch[1]}.torrent` : '';
39
41
  results.push({
40
42
  num: results.length + 1,
41
43
  name: title,
@@ -45,6 +47,7 @@ export class NyaaScraper implements SourceScraper {
45
47
  peers,
46
48
  source: 'Nyaa',
47
49
  url: link,
50
+ torrentUrl,
48
51
  magnet: ''
49
52
  });
50
53
  }
@@ -30,6 +30,7 @@ export class ThePirateBayScraper implements SourceScraper {
30
30
  peers: parseInt(item.leechers) || 0,
31
31
  source: 'ThePirateBay',
32
32
  url: `https://thepiratebay.org/torrent/${item.id}`,
33
+ torrentUrl: `magnet:?xt=urn:btih:${item.info_hash}`,
33
34
  magnet: `magnet:?xt=urn:btih:${item.info_hash}`,
34
35
  hash: item.info_hash
35
36
  }));
@@ -34,6 +34,7 @@ export class YtsScraper implements SourceScraper {
34
34
  peers: movie.torrents?.[0]?.peers || 0,
35
35
  source: 'YTS',
36
36
  url: movie.torrents?.[0]?.url || '',
37
+ torrentUrl: movie.torrents?.[0]?.url || '',
37
38
  magnet: movie.torrents?.[0]?.hash ? `magnet:?xt=urn:btih:${movie.torrents[0].hash}` : '',
38
39
  hash: movie.torrents?.[0]?.hash
39
40
  });
package/src/types.ts CHANGED
@@ -11,6 +11,7 @@ export interface TorrentResult {
11
11
  hash?: string;
12
12
  category?: string;
13
13
  date?: string;
14
+ torrentUrl?: string;
14
15
  }
15
16
 
16
17
  export interface SourceConfig {
@@ -1,78 +0,0 @@
1
- import cliProgress from 'cli-progress';
2
- import chalk from 'chalk';
3
-
4
- export class DownloadProgress {
5
- private bar: cliProgress.SingleBar;
6
- private startTime: number;
7
- private totalBytes: number = 0;
8
- private downloadedBytes: number = 0;
9
-
10
- constructor() {
11
- this.startTime = Date.now();
12
- this.bar = new cliProgress.SingleBar({
13
- format: '[{bar}] {percentage}% | {speed} | ETA: {eta} | {downloaded}',
14
- barCompleteChar: '\u2588',
15
- barIncompleteChar: '\u2591',
16
- hideCursor: true,
17
- fps: 10,
18
- etaAsynchronous: true
19
- });
20
- }
21
-
22
- start(total: number): void {
23
- this.totalBytes = total;
24
- this.bar.start(total, 0, {
25
- speed: '0 B/s',
26
- downloaded: '0 MB'
27
- });
28
- }
29
-
30
- update(downloaded: number, total: number): void {
31
- this.downloadedBytes = downloaded;
32
- this.totalBytes = total || this.totalBytes;
33
-
34
- const speed = this.calculateSpeed();
35
- const eta = this.calculateETA();
36
- const downloadedMB = (this.downloadedBytes / (1024 * 1024)).toFixed(2);
37
-
38
- this.bar.update(this.downloadedBytes, {
39
- speed,
40
- downloaded: `${downloadedMB} MB`,
41
- eta
42
- });
43
- }
44
-
45
- private calculateSpeed(): string {
46
- const elapsed = (Date.now() - this.startTime) / 1000;
47
- const bytesPerSecond = elapsed > 0 ? this.downloadedBytes / elapsed : 0;
48
- return this.formatBytes(bytesPerSecond) + '/s';
49
- }
50
-
51
- private calculateETA(): string {
52
- const elapsed = (Date.now() - this.startTime) / 1000;
53
- if (this.downloadedBytes === 0 || this.totalBytes === 0) return 'N/A';
54
-
55
- const speed = this.downloadedBytes / elapsed;
56
- const remaining = this.totalBytes - this.downloadedBytes;
57
- const seconds = remaining / speed;
58
-
59
- if (seconds < 60) return `${Math.round(seconds)}s`;
60
- if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
61
- return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
62
- }
63
-
64
- private formatBytes(bytes: number): string {
65
- if (bytes < 1024) return bytes + ' B';
66
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
67
- if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
68
- return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
69
- }
70
-
71
- stop(): void {
72
- this.bar.stop();
73
- }
74
-
75
- getTotalDownloaded(): number {
76
- return this.downloadedBytes;
77
- }
78
- }