twitch-video-downloader-plus 1.6.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.
- package/LICENSE +9 -0
- package/README.md +284 -0
- package/build/chat-downloader.d.ts +13 -0
- package/build/chat-downloader.js +46 -0
- package/build/chat-downloader.js.map +1 -0
- package/build/downloader.d.ts +12 -0
- package/build/downloader.js +92 -0
- package/build/downloader.js.map +1 -0
- package/build/enums/errors.d.ts +8 -0
- package/build/enums/errors.js +13 -0
- package/build/enums/errors.js.map +1 -0
- package/build/enums/twitch-api.d.ts +3 -0
- package/build/enums/twitch-api.js +8 -0
- package/build/enums/twitch-api.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +14 -0
- package/build/index.js.map +1 -0
- package/build/interfaces/access-token.d.ts +32 -0
- package/build/interfaces/access-token.js +3 -0
- package/build/interfaces/access-token.js.map +1 -0
- package/build/interfaces/comment.d.ts +18 -0
- package/build/interfaces/comment.js +3 -0
- package/build/interfaces/comment.js.map +1 -0
- package/build/interfaces/fragments-response.d.ts +4 -0
- package/build/interfaces/fragments-response.js +3 -0
- package/build/interfaces/fragments-response.js.map +1 -0
- package/build/interfaces/hls-video.d.ts +5 -0
- package/build/interfaces/hls-video.js +3 -0
- package/build/interfaces/hls-video.js.map +1 -0
- package/build/interfaces/manifiest-response.d.ts +6 -0
- package/build/interfaces/manifiest-response.js +3 -0
- package/build/interfaces/manifiest-response.js.map +1 -0
- package/build/interfaces/mkv-video.d.ts +5 -0
- package/build/interfaces/mkv-video.js +3 -0
- package/build/interfaces/mkv-video.js.map +1 -0
- package/build/interfaces/transcode-options.d.ts +4 -0
- package/build/interfaces/transcode-options.js +3 -0
- package/build/interfaces/transcode-options.js.map +1 -0
- package/build/interfaces/video-download-information.d.ts +5 -0
- package/build/interfaces/video-download-information.js +3 -0
- package/build/interfaces/video-download-information.js.map +1 -0
- package/build/twitch-oauth.d.ts +9 -0
- package/build/twitch-oauth.js +37 -0
- package/build/twitch-oauth.js.map +1 -0
- package/build/utils/filesystem.d.ts +1 -0
- package/build/utils/filesystem.js +11 -0
- package/build/utils/filesystem.js.map +1 -0
- package/build/utils/is-json.d.ts +1 -0
- package/build/utils/is-json.js +14 -0
- package/build/utils/is-json.js.map +1 -0
- package/build/utils/logger.d.ts +1 -0
- package/build/utils/logger.js +39 -0
- package/build/utils/logger.js.map +1 -0
- package/build/utils/parse-url.d.ts +1 -0
- package/build/utils/parse-url.js +14 -0
- package/build/utils/parse-url.js.map +1 -0
- package/build/video-downloader.d.ts +32 -0
- package/build/video-downloader.js +158 -0
- package/build/video-downloader.js.map +1 -0
- package/build/video-fragments.d.ts +7 -0
- package/build/video-fragments.js +33 -0
- package/build/video-fragments.js.map +1 -0
- package/build/video-manifiest.d.ts +12 -0
- package/build/video-manifiest.js +83 -0
- package/build/video-manifiest.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# TWICHT-VIDEO-DOWNLOADER
|
|
2
|
+
|
|
3
|
+
Library to download the videos, videos for subs and comments from twitch.
|
|
4
|
+
|
|
5
|
+
## Fork changes
|
|
6
|
+
|
|
7
|
+
Added `fragment-downloaded` event & deleted broken chat download
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
$ pnpm add twitch-video-downloader
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
The [FFMPEG](https://www.ffmpeg.org/) library is needed to transcode video files from m3u8 to mkv
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
import { writeFileSync } from "fs";
|
|
24
|
+
|
|
25
|
+
import { VideoDownloader, ensureDirectoryExists } from "twitch-video-downloader";
|
|
26
|
+
|
|
27
|
+
(async () => {
|
|
28
|
+
try {
|
|
29
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240");
|
|
30
|
+
|
|
31
|
+
downloader.on("fragment-downloaded", (file) => console.log(`${file}`));
|
|
32
|
+
|
|
33
|
+
downloader.on("progress-download", (progress) => console.log(`Downloaded ${progress.toFixed(2)}%`));
|
|
34
|
+
downloader.on("progress-transcode", (progress) => console.log(`Transcoded ${progress.toFixed(2)}%`));
|
|
35
|
+
|
|
36
|
+
downloader.on("start-download", (e) => console.log(`Download started! on `, e));
|
|
37
|
+
downloader.on("start-transcode", () => console.log(`Transcoded started!`));
|
|
38
|
+
|
|
39
|
+
// Get all resolutions available for this video
|
|
40
|
+
const resolutions = await downloader.getVideoResolutionsAvailable();
|
|
41
|
+
|
|
42
|
+
// Donwload specific resolution
|
|
43
|
+
const download = await downloader.download(resolutions[0]);
|
|
44
|
+
|
|
45
|
+
// Information and path of downloaded hls files
|
|
46
|
+
console.log(download);
|
|
47
|
+
|
|
48
|
+
// Trancoded video, from HLS to MKV
|
|
49
|
+
const transcode = await downloader.transcode(download);
|
|
50
|
+
|
|
51
|
+
// Information and path of trancoded video
|
|
52
|
+
console.log(transcode);
|
|
53
|
+
|
|
54
|
+
// Download offline chat
|
|
55
|
+
const comments = await downloader.downloadChat();
|
|
56
|
+
|
|
57
|
+
// Verify that the directory exists, if not create it
|
|
58
|
+
ensureDirectoryExists(join(__dirname, "./../downloads/chats"));
|
|
59
|
+
|
|
60
|
+
// Save all comments
|
|
61
|
+
writeFileSync(join(__dirname, `./../downloads/chats/${comments.vodID}.chat`), comments.content);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.log(error);
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This code can be very useful to have a quick overview of the library. If you clone the [repository](https://github.com/Alejandro095/twitch-video-downloader) you can find this same file in the following path.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
twitch-video-downloader
|
|
72
|
+
│
|
|
73
|
+
└───example
|
|
74
|
+
│ index.ts
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Once the project dependencies are installed with
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
$ pnpm install
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
You can play with this file by modifying it and downloading the videos that interest you. To run the script run the following command
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$ pnpm run start:watch
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If you want to know everything the library is doing, execute the following command to run the script in debug mode
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
$ pnpm run dev:watch
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or you can pass the debug parameter in the options
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240", {
|
|
99
|
+
debug: true
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API
|
|
104
|
+
|
|
105
|
+
VideoDownloader is the main class of the library, it is the entry point to start downloading videos or chat
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
const defaultOptions = {
|
|
109
|
+
clientID: "kimne78kx3ncx6brgo4mv6wki5h1ko",
|
|
110
|
+
debug: false,
|
|
111
|
+
downloadFolder: process.cwd(),
|
|
112
|
+
oAuthToken: "",
|
|
113
|
+
poolLimit: 20
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240", defaultOptions);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Option | Definition | Default |
|
|
120
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
|
121
|
+
| clientID | This is a parameter used by the twitch platform, the value is the same no matter what account is used, so you probably shouldn't change it | kimne78kx3ncx6brgo4mv6wki5h1ko |
|
|
122
|
+
| debug | To start the library in debug mode, a debug.log file will be created where everything the library is doing will be saved. | false |
|
|
123
|
+
| downloadFolder | The folder where the videos will be saved. Default is the working directory | process.cwd() |
|
|
124
|
+
| oAuthToken | This parameter is very important because with this you can download videos only for subscribers. It is not magic, before you must already have access to the video in your twitch account to be able to download it | "" |
|
|
125
|
+
| poolLimit | They are the maximum parallel downloads when downloading a video | 20 |
|
|
126
|
+
|
|
127
|
+
### Where do I get my oAuthToken to download subscriber-only videos?
|
|
128
|
+
|
|
129
|
+
You have two options, you can extract it from Twitch cookies once you are logged in, the field is called auth-token. Here are the steps you must follow:
|
|
130
|
+
|
|
131
|
+
* Sign in to your Twitch account
|
|
132
|
+
* With the Twitch tab open, open the chrome devtools (press f12)
|
|
133
|
+
* With the devtools window open, now go to the application tab
|
|
134
|
+
* Select 'https://www.twitch.tv' in the Cookies section
|
|
135
|
+
* And look in the 'name' column for the field that says 'auth-token' and copy what is in the 'value' column
|
|
136
|
+
|
|
137
|
+
The second option (It is still in development, it is not recommended) is to use the TwitchOAuth class
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
import { TwitchOAuth, LoginOptions } from "twitch-video-downloader";
|
|
141
|
+
|
|
142
|
+
const loginDefaultOptions: LoginOptions = {
|
|
143
|
+
authy_token: "", // This is the only useful option. Use it when you have the Two-factor activated, copy the code from the Authenticator application. Make sure the code is still valid when you run this method
|
|
144
|
+
client_id: "kimne78kx3ncx6brgo4mv6wki5h1ko",
|
|
145
|
+
remember_me: true,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const twitchOAuth = new TwitchOAuth();
|
|
149
|
+
|
|
150
|
+
twitchOAuth.login("<YOUR TWITCH USER>", "<YOUR PASSWORD>", loginDefaultOptions).then(async (oAuthToken) => {
|
|
151
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240", {
|
|
152
|
+
oAuthToken: oAuthToken,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
...
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Warning: THIS CLASS IS NOT YET FINISHED DEVELOPING, so problems may arise. And we have not yet developed the option to solve the catchas when Twitch asks you to log in
|
|
160
|
+
|
|
161
|
+
IT IS RECOMMENDED THAT YOU USE THE FIRST METHOD TO GET YOUR OAUTH TOKEN FROM COOKIES BEFORE THIS METHOD
|
|
162
|
+
|
|
163
|
+
## Events
|
|
164
|
+
|
|
165
|
+
| Event name | Description | Parameters |
|
|
166
|
+
| ------------------ | --------------------------------------------------------------- | ---------------------------------------------------- |
|
|
167
|
+
| progress-download | The event is called each time the download progress is updated | decimal |
|
|
168
|
+
| progress-transcode | The event is called each time the transcode progress is updated | decimal |
|
|
169
|
+
| start-download | The event is called when the download starts | { vodID: string, quality: string, folderPath:string} |
|
|
170
|
+
| start-transcode | The event is called when the transcode starts | void |
|
|
171
|
+
|
|
172
|
+
Example to register your listeners
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240");
|
|
176
|
+
|
|
177
|
+
downloader.on("<EVENT NAME>", (param) => console.log(param));
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Methods
|
|
181
|
+
|
|
182
|
+
### getVideoResolutionsAvailable: Asynchronous method to get all available resolutions
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240");
|
|
186
|
+
|
|
187
|
+
const resolutions = await downloader.getVideoResolutionsAvailable();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The function returns an array with the following information
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
[
|
|
194
|
+
{
|
|
195
|
+
quality: '1080p60',
|
|
196
|
+
resolution: '1920x1080',
|
|
197
|
+
url: 'https://...index-dvr.m3u8'
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
quality: '1080p',
|
|
201
|
+
resolution: '1920x1080',
|
|
202
|
+
url: 'https://...index-dvr.m3u8'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
quality: '720p60',
|
|
206
|
+
resolution: '1080x720',
|
|
207
|
+
url: 'https://...index-dvr.m3u8'
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
...
|
|
211
|
+
]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### download: Asynchronous method to download a video
|
|
215
|
+
|
|
216
|
+
This function allows you to download a specific resolution, as a parameter you have to pass an object with the fields quality, resolution and url
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240");
|
|
220
|
+
|
|
221
|
+
const resolutions = await downloader.getVideoResolutionsAvailable();
|
|
222
|
+
|
|
223
|
+
// Donwload specific resolution
|
|
224
|
+
const download = await downloader.download(resolutions[0]);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Once the function is finished executing, it returns an object with the following information
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
{
|
|
231
|
+
vodID: '800558240',
|
|
232
|
+
quality: '1080p60',
|
|
233
|
+
folderPath: 'D:\\Projects\\twitch-video-downloader\\downloads\\videos\\800558240\\hls\\1080p60'
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### transcode: Asynchronous method to transcode a video
|
|
238
|
+
|
|
239
|
+
This function allows you to transcode a video, as a parameter you have to pass an object with the fields quality, resolution and folderPath, this fields are return by downloader method
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240");
|
|
243
|
+
|
|
244
|
+
const resolutions = await downloader.getVideoResolutionsAvailable();
|
|
245
|
+
|
|
246
|
+
const download = await downloader.download(resolutions[0]);
|
|
247
|
+
|
|
248
|
+
// Trancoded video, from HLS to MKV
|
|
249
|
+
const transcode = await downloader.transcode(download);
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The function also receives an optional second argument in the form of an object with additional settings.
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
const transcode = await downloader.transcode(download, {
|
|
256
|
+
deleteHslFiles: false, // Default value
|
|
257
|
+
outputPath: "<WORKING DIRECTORY>/downloads/videos/<VIDEO ID>/mkv/", // Default value
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Once the function is finished executing, it returns an object with the following information
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
{
|
|
265
|
+
vodID: '800558240',
|
|
266
|
+
quality: '1080p60',
|
|
267
|
+
filePath: 'D:\\Projects\\twitch-video-downloader\\downloads\\videos\\800558240\\mkv\\1080p60.mkv'
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### downloadChat: Asynchronous method to download chat
|
|
272
|
+
|
|
273
|
+
This function allows you to download the chat of a video. The return of the function is the raw data from the twitch api
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
const downloader = new VideoDownloader("https://www.twitch.tv/videos/800558240");
|
|
277
|
+
|
|
278
|
+
// Download offline chat
|
|
279
|
+
const comments = await downloader.downloadChat();
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Credits
|
|
283
|
+
|
|
284
|
+
Some of the code is from libraries like [twitch-m3u8](https://github.com/dudik/twitch-m3u8) and [twitch-tools](https://github.com/HugoJF/twitch-tools)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CommentsPage } from "./interfaces/comment";
|
|
2
|
+
export declare class ChatDownloader {
|
|
3
|
+
private _vodID;
|
|
4
|
+
private _clientID;
|
|
5
|
+
private _allComments;
|
|
6
|
+
constructor(vodID: string, clientID: string);
|
|
7
|
+
private _api;
|
|
8
|
+
private _getEndpoint;
|
|
9
|
+
download(): Promise<{
|
|
10
|
+
vodID: string;
|
|
11
|
+
content: CommentsPage[];
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ChatDownloader = void 0;
|
|
7
|
+
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
8
|
+
const logger_1 = require("./utils/logger");
|
|
9
|
+
class ChatDownloader {
|
|
10
|
+
constructor(vodID, clientID) {
|
|
11
|
+
this._allComments = [];
|
|
12
|
+
this._vodID = vodID;
|
|
13
|
+
this._clientID = clientID;
|
|
14
|
+
}
|
|
15
|
+
async _api(cursor) {
|
|
16
|
+
const headers = {
|
|
17
|
+
"Client-ID": this._clientID
|
|
18
|
+
};
|
|
19
|
+
return (0, cross_fetch_1.default)(this._getEndpoint(cursor), { headers });
|
|
20
|
+
}
|
|
21
|
+
_getEndpoint(cursor = "") {
|
|
22
|
+
return `https://api.twitch.tv/v5/videos/${this._vodID}/comments?cursor=${cursor}`;
|
|
23
|
+
}
|
|
24
|
+
async download() {
|
|
25
|
+
let cursor = undefined;
|
|
26
|
+
(0, logger_1.log)(`[ChatDownloader] Downloading chat for ${this._vodID}`);
|
|
27
|
+
do {
|
|
28
|
+
const response = (await this._api(cursor).then((resp) => resp.json()));
|
|
29
|
+
if (response.comments.length) {
|
|
30
|
+
this._allComments.push(response);
|
|
31
|
+
(0, logger_1.log)(`[ChatDownloader] ${response.comments.length} comments downloaded, cursor ${cursor || "initial"}.`);
|
|
32
|
+
cursor = response._next;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
cursor = undefined;
|
|
36
|
+
}
|
|
37
|
+
} while (cursor);
|
|
38
|
+
(0, logger_1.log)(`[ChatDownloader] Chat downloaded.`);
|
|
39
|
+
return {
|
|
40
|
+
vodID: this._vodID,
|
|
41
|
+
content: this._allComments
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.ChatDownloader = ChatDownloader;
|
|
46
|
+
//# sourceMappingURL=chat-downloader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-downloader.js","sourceRoot":"","sources":["../src/chat-downloader.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAgC;AAGhC,2CAAqC;AAErC,MAAa,cAAc;IAMvB,YAAY,KAAa,EAAE,QAAgB;QAFnC,iBAAY,GAAmB,EAAE,CAAC;QAGtC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,MAAe;QAC9B,MAAM,OAAO,GAAG;YACZ,WAAW,EAAE,IAAI,CAAC,SAAS;SAC9B,CAAC;QAEF,OAAO,IAAA,qBAAK,EAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACzD,CAAC;IAEO,YAAY,CAAC,SAAiB,EAAE;QACpC,OAAO,mCAAmC,IAAI,CAAC,MAAM,oBAAoB,MAAM,EAAE,CAAC;IACtF,CAAC;IAEM,KAAK,CAAC,QAAQ;QACjB,IAAI,MAAM,GAAG,SAAS,CAAC;QAEvB,IAAA,YAAG,EAAC,yCAAyC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAE5D,GAAG;YACC,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAQ,CAAC;YAE9E,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE;gBAC1B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACjC,IAAA,YAAG,EAAC,oBAAoB,QAAQ,CAAC,QAAQ,CAAC,MAAM,gCAAgC,MAAM,IAAI,SAAS,GAAG,CAAC,CAAC;gBACxG,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC;aAC3B;iBAAM;gBACH,MAAM,GAAG,SAAS,CAAC;aACtB;SACJ,QAAQ,MAAM,EAAE;QACjB,IAAA,YAAG,EAAC,mCAAmC,CAAC,CAAC;QACzC,OAAO;YACH,KAAK,EAAE,IAAI,CAAC,MAAM;YAClB,OAAO,EAAE,IAAI,CAAC,YAAY;SAC7B,CAAC;IACN,CAAC;CACJ;AA7CD,wCA6CC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
export declare class Downloader extends EventEmitter {
|
|
4
|
+
private _url;
|
|
5
|
+
private _path;
|
|
6
|
+
private _tries;
|
|
7
|
+
private _maxTries;
|
|
8
|
+
constructor(url: string, path: string);
|
|
9
|
+
download(): Promise<void>;
|
|
10
|
+
private _handlerError;
|
|
11
|
+
private _init;
|
|
12
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Downloader = void 0;
|
|
7
|
+
const https_1 = __importDefault(require("https"));
|
|
8
|
+
const events_1 = require("events");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const attempt_1 = require("@lifeomic/attempt");
|
|
11
|
+
const logger_1 = require("./utils/logger");
|
|
12
|
+
class Downloader extends events_1.EventEmitter {
|
|
13
|
+
constructor(url, path) {
|
|
14
|
+
super();
|
|
15
|
+
this._tries = 0;
|
|
16
|
+
this._maxTries = 4;
|
|
17
|
+
this._url = url;
|
|
18
|
+
this._path = path;
|
|
19
|
+
}
|
|
20
|
+
async download() {
|
|
21
|
+
try {
|
|
22
|
+
await this._init();
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
await this._handlerError(error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async _handlerError(error, reject) {
|
|
29
|
+
this._tries = ++this._tries;
|
|
30
|
+
(0, logger_1.log)(`[Downloader] [handlerError] ${error}`);
|
|
31
|
+
if (this._tries > this._maxTries) {
|
|
32
|
+
const errorMessage = `Failed to download fragment with url ${this._url}`;
|
|
33
|
+
(0, logger_1.log)(errorMessage);
|
|
34
|
+
if (reject)
|
|
35
|
+
reject(errorMessage);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const errorMessage = `Retrying fragment: ${this._url}, attempts made: ${this._tries}`;
|
|
39
|
+
(0, logger_1.log)(errorMessage);
|
|
40
|
+
await this.download();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
_init() {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const filePathTemp = `${this._path}.progress`;
|
|
46
|
+
const filePath = this._path;
|
|
47
|
+
const fileExist = (0, fs_1.existsSync)(filePath);
|
|
48
|
+
if (fileExist) {
|
|
49
|
+
this.emit("fragment-downloaded", filePath);
|
|
50
|
+
(0, logger_1.log)(`[Downloader] Fragment download completed ${this._url}`);
|
|
51
|
+
resolve(filePath);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const file = (0, fs_1.createWriteStream)(filePathTemp);
|
|
55
|
+
const handlerError = (error) => {
|
|
56
|
+
file.close();
|
|
57
|
+
(0, fs_1.unlink)(filePathTemp, () => {
|
|
58
|
+
this._handlerError(error, reject);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const request = https_1.default.get(this._url, (response) => {
|
|
62
|
+
if (response.statusCode != 200) {
|
|
63
|
+
handlerError(response.statusMessage);
|
|
64
|
+
}
|
|
65
|
+
response.pipe(file);
|
|
66
|
+
});
|
|
67
|
+
request.on("error", handlerError);
|
|
68
|
+
file.on("error", handlerError);
|
|
69
|
+
file.on("finish", () => {
|
|
70
|
+
handlerFinishFile();
|
|
71
|
+
});
|
|
72
|
+
const handlerFinishFile = async () => {
|
|
73
|
+
try {
|
|
74
|
+
await (0, attempt_1.retry)(async () => (0, fs_1.renameSync)(filePathTemp, filePath), {
|
|
75
|
+
delay: 500,
|
|
76
|
+
factor: 4,
|
|
77
|
+
maxDelay: 1000
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
this._handlerError(error, reject);
|
|
82
|
+
}
|
|
83
|
+
this.emit("fragment-downloaded", filePath);
|
|
84
|
+
(0, logger_1.log)(`[Downloader] Fragment download completed ${this._url}`);
|
|
85
|
+
resolve(filePath);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.Downloader = Downloader;
|
|
92
|
+
//# sourceMappingURL=downloader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloader.js","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare enum ERRORS {
|
|
2
|
+
CANT_GENERATE_ACCESS_TOKEN = "Can\u00B4t generate access token.",
|
|
3
|
+
MANIFIEST_IS_RESTRICRED = "Manifiest is restricted.",
|
|
4
|
+
INVALID_VIDEO_METADATA = "Invalid video metadata object.",
|
|
5
|
+
NOT_FOUND_FRAGMENTS = "Not found fragments.",
|
|
6
|
+
URL_MANIFIEST_REQUIRED = "URL manifiest requiered",
|
|
7
|
+
M3U8_DOES_NOT_EXISTS = "The file M3U8 does not exists"
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ERRORS = void 0;
|
|
4
|
+
var ERRORS;
|
|
5
|
+
(function (ERRORS) {
|
|
6
|
+
ERRORS["CANT_GENERATE_ACCESS_TOKEN"] = "Can\u00B4t generate access token.";
|
|
7
|
+
ERRORS["MANIFIEST_IS_RESTRICRED"] = "Manifiest is restricted.";
|
|
8
|
+
ERRORS["INVALID_VIDEO_METADATA"] = "Invalid video metadata object.";
|
|
9
|
+
ERRORS["NOT_FOUND_FRAGMENTS"] = "Not found fragments.";
|
|
10
|
+
ERRORS["URL_MANIFIEST_REQUIRED"] = "URL manifiest requiered";
|
|
11
|
+
ERRORS["M3U8_DOES_NOT_EXISTS"] = "The file M3U8 does not exists";
|
|
12
|
+
})(ERRORS = exports.ERRORS || (exports.ERRORS = {}));
|
|
13
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/enums/errors.ts"],"names":[],"mappings":";;;AAAA,IAAY,MAOX;AAPD,WAAY,MAAM;IACd,0EAA2D,CAAA;IAC3D,8DAAoD,CAAA;IACpD,mEAAyD,CAAA;IACzD,sDAA4C,CAAA;IAC5C,4DAAkD,CAAA;IAClD,gEAAsD,CAAA;AAC1D,CAAC,EAPW,MAAM,GAAN,cAAM,KAAN,cAAM,QAOjB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TWITCH_API_ERROR = void 0;
|
|
4
|
+
var TWITCH_API_ERROR;
|
|
5
|
+
(function (TWITCH_API_ERROR) {
|
|
6
|
+
TWITCH_API_ERROR["VOD_MANIFIEST_RESTRICTED"] = "vod_manifest_restricted";
|
|
7
|
+
})(TWITCH_API_ERROR = exports.TWITCH_API_ERROR || (exports.TWITCH_API_ERROR = {}));
|
|
8
|
+
//# sourceMappingURL=twitch-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitch-api.js","sourceRoot":"","sources":["../../src/enums/twitch-api.ts"],"names":[],"mappings":";;;AAAA,IAAY,gBAEX;AAFD,WAAY,gBAAgB;IACxB,wEAAoD,CAAA;AACxD,CAAC,EAFW,gBAAgB,GAAhB,wBAAgB,KAAhB,wBAAgB,QAE3B"}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TWITCH_API_ERROR = exports.ERRORS = exports.ensureDirectoryExists = exports.TwitchOAuth = exports.VideoDownloader = void 0;
|
|
4
|
+
var video_downloader_1 = require("./video-downloader");
|
|
5
|
+
Object.defineProperty(exports, "VideoDownloader", { enumerable: true, get: function () { return video_downloader_1.VideoDownloader; } });
|
|
6
|
+
var twitch_oauth_1 = require("./twitch-oauth");
|
|
7
|
+
Object.defineProperty(exports, "TwitchOAuth", { enumerable: true, get: function () { return twitch_oauth_1.TwitchOAuth; } });
|
|
8
|
+
var filesystem_1 = require("./utils/filesystem");
|
|
9
|
+
Object.defineProperty(exports, "ensureDirectoryExists", { enumerable: true, get: function () { return filesystem_1.ensureDirectoryExists; } });
|
|
10
|
+
var errors_1 = require("./enums/errors");
|
|
11
|
+
Object.defineProperty(exports, "ERRORS", { enumerable: true, get: function () { return errors_1.ERRORS; } });
|
|
12
|
+
var twitch_api_1 = require("./enums/twitch-api");
|
|
13
|
+
Object.defineProperty(exports, "TWITCH_API_ERROR", { enumerable: true, get: function () { return twitch_api_1.TWITCH_API_ERROR; } });
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAA5C,mHAAA,eAAe,OAAA;AACxB,+CAA4C;AAAnC,2GAAA,WAAW,OAAA;AAGpB,iDAA2D;AAAlD,mHAAA,qBAAqB,OAAA;AAG9B,yCAAwC;AAA/B,gGAAA,MAAM,OAAA;AACf,iDAAsD;AAA7C,8GAAA,gBAAgB,OAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Authorization {
|
|
2
|
+
forbidden: boolean;
|
|
3
|
+
reason: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Chansub {
|
|
6
|
+
restricted_bitrates: any[];
|
|
7
|
+
}
|
|
8
|
+
export interface AccessToken {
|
|
9
|
+
authorization: Authorization;
|
|
10
|
+
chansub: Chansub;
|
|
11
|
+
device_id?: any;
|
|
12
|
+
expires: number;
|
|
13
|
+
https_required: boolean;
|
|
14
|
+
privileged: boolean;
|
|
15
|
+
user_id?: any;
|
|
16
|
+
version: number;
|
|
17
|
+
vod_id: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PlaybackAccessToken {
|
|
20
|
+
data: {
|
|
21
|
+
videoPlaybackAccessToken: {
|
|
22
|
+
value: AccessToken;
|
|
23
|
+
signature: string;
|
|
24
|
+
__typename: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
extenesion: {
|
|
28
|
+
durationMilliseconds: number;
|
|
29
|
+
operationName: string;
|
|
30
|
+
requestID: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-token.js","sourceRoot":"","sources":["../../src/interfaces/access-token.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CommentsPage {
|
|
2
|
+
comments: Comment[];
|
|
3
|
+
_next?: string;
|
|
4
|
+
_prev?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Comment {
|
|
7
|
+
_id: string;
|
|
8
|
+
created_at: Date;
|
|
9
|
+
updated_at: Date;
|
|
10
|
+
channel_id: string;
|
|
11
|
+
content_type: any;
|
|
12
|
+
content_id: string;
|
|
13
|
+
content_offset_seconds: number;
|
|
14
|
+
commenter: any;
|
|
15
|
+
source: any;
|
|
16
|
+
state: any;
|
|
17
|
+
message: any;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment.js","sourceRoot":"","sources":["../../src/interfaces/comment.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragments-response.js","sourceRoot":"","sources":["../../src/interfaces/fragments-response.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hls-video.js","sourceRoot":"","sources":["../../src/interfaces/hls-video.ts"],"names":[],"mappings":""}
|