mirror of
https://github.com/tedkulp/vidgrab
synced 2026-03-05 13:20:27 -05:00
Got the basics working with angular
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -37,3 +37,7 @@ testem.log
|
|||||||
# System Files
|
# System Files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Custom stuff
|
||||||
|
/package
|
||||||
|
/*.tgz
|
||||||
|
|||||||
@@ -1,15 +1,41 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { BullModule } from '@nestjs/bull';
|
||||||
|
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
import { WebModule } from '../web/web.module';
|
||||||
|
import { YtdlModule } from '../ytdl/ytdl.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
ignoreEnvFile: true,
|
||||||
|
isGlobal: true,
|
||||||
|
// load: [configuration],
|
||||||
|
}),
|
||||||
|
EventEmitterModule.forRoot({
|
||||||
|
wildcard: true,
|
||||||
|
}),
|
||||||
|
BullModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
redis: {
|
||||||
|
host: configService.get('redisHost'),
|
||||||
|
port: configService.get('redisPort'),
|
||||||
|
},
|
||||||
|
prefix: 'vgqueue',
|
||||||
|
}),
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
ServeStaticModule.forRoot({
|
ServeStaticModule.forRoot({
|
||||||
rootPath: join(__dirname, '..', 'client'),
|
rootPath: join(__dirname, '..', 'client'),
|
||||||
}),
|
}),
|
||||||
|
YtdlModule,
|
||||||
|
WebModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
|
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
|
|
||||||
import { AppModule } from './app/app.module';
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||||
const globalPrefix = 'api';
|
const globalPrefix = 'api';
|
||||||
app.setGlobalPrefix(globalPrefix);
|
app.setGlobalPrefix(globalPrefix);
|
||||||
const port = process.env.PORT || 3333;
|
const port = process.env.PORT || 3333;
|
||||||
|
|||||||
123
apps/api/src/web/web.controller.ts
Normal file
123
apps/api/src/web/web.controller.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
Logger,
|
||||||
|
Post,
|
||||||
|
Render,
|
||||||
|
Req,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { Queue } from 'bull';
|
||||||
|
import { pick, trimEnd } from 'lodash';
|
||||||
|
|
||||||
|
import { QueueDto, UploadDto } from '@vidgrab2/api-interfaces';
|
||||||
|
import { YtdlService } from '../ytdl/ytdl.service';
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class WebController {
|
||||||
|
private readonly logger = new Logger(WebController.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectQueue('vidgrab') private readonly vidgrabQueue: Queue,
|
||||||
|
private readonly ytdlService: YtdlService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Render('Index')
|
||||||
|
async root(@Req() request: any) {
|
||||||
|
const fullUrl =
|
||||||
|
request.protocol + '://' + request.get('host') + request.originalUrl;
|
||||||
|
const bookmarklet = `javascript:(function(){var xhr=new XMLHttpRequest();xhr.open('POST',encodeURI('${trimEnd(
|
||||||
|
fullUrl,
|
||||||
|
'/',
|
||||||
|
)}/queue'));xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');xhr.send('url='+document.location.href.replace(/ /g,'+'));}());`;
|
||||||
|
|
||||||
|
const jobs = await this.vidgrabQueue.getJobs([
|
||||||
|
'completed',
|
||||||
|
'waiting',
|
||||||
|
'active',
|
||||||
|
'delayed',
|
||||||
|
'failed',
|
||||||
|
'paused',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const jobList = await Promise.all(
|
||||||
|
jobs.map(async (j) => {
|
||||||
|
const state = await j.getState();
|
||||||
|
const progress = j.progress();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pick(j, ['id', 'name', 'data']),
|
||||||
|
progress: progress ? `${progress}%` : 'n/a',
|
||||||
|
state: state,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
bookmarklet,
|
||||||
|
jobs: JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
jobList
|
||||||
|
.sort(
|
||||||
|
(a: {id: string}, b: { id: string }) => parseInt(b.id.toString()) - parseInt(a.id.toString()),
|
||||||
|
)
|
||||||
|
.slice(0, 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/extractors')
|
||||||
|
async listExtractors() {
|
||||||
|
const extractors = await this.ytdlService.listExtractors();
|
||||||
|
|
||||||
|
return {
|
||||||
|
extractors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/getinfo')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true }))
|
||||||
|
async getInfo(@Body() body: UploadDto) {
|
||||||
|
const videoInfo = await this.ytdlService.getVideoInfo(body.url);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: videoInfo.title,
|
||||||
|
extractor: videoInfo.extractor,
|
||||||
|
description: videoInfo.description,
|
||||||
|
videoUrl: videoInfo.webpage_url,
|
||||||
|
thumbnails: videoInfo.thumbnails,
|
||||||
|
upload_date: videoInfo.upload_date,
|
||||||
|
duration: videoInfo.duration,
|
||||||
|
formats: videoInfo.formats.map((f) =>
|
||||||
|
pick(f, ['format_id', 'format', 'filesize', 'format_note', 'ext']),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/queue')
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true }))
|
||||||
|
async queue(@Body() body: QueueDto) {
|
||||||
|
if (!body.extractor || !body.title) {
|
||||||
|
const videoInfo = await this.ytdlService.getVideoInfo(body.url);
|
||||||
|
body.extractor = videoInfo.extractor;
|
||||||
|
body.title = videoInfo.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
const job = await this.vidgrabQueue.add('download', body);
|
||||||
|
|
||||||
|
this.eventEmitter.emit('job.added', { job });
|
||||||
|
|
||||||
|
return {
|
||||||
|
jobId: job.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
16
apps/api/src/web/web.module.ts
Normal file
16
apps/api/src/web/web.module.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { BullModule } from '@nestjs/bull';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { YtdlModule } from '../ytdl/ytdl.module';
|
||||||
|
|
||||||
|
import { WebController } from './web.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
BullModule.registerQueue({
|
||||||
|
name: 'vidgrab',
|
||||||
|
}),
|
||||||
|
YtdlModule,
|
||||||
|
],
|
||||||
|
controllers: [WebController],
|
||||||
|
})
|
||||||
|
export class WebModule {}
|
||||||
9
apps/api/src/ytdl/ytdl.module.ts
Normal file
9
apps/api/src/ytdl/ytdl.module.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { YtdlService } from './ytdl.service';
|
||||||
|
import { YtdlProcessor } from './ytdl.processor';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [YtdlProcessor, YtdlService],
|
||||||
|
exports: [YtdlProcessor, YtdlService],
|
||||||
|
})
|
||||||
|
export class YtdlModule {}
|
||||||
116
apps/api/src/ytdl/ytdl.processor.ts
Normal file
116
apps/api/src/ytdl/ytdl.processor.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
OnQueueActive,
|
||||||
|
OnQueueCompleted,
|
||||||
|
OnQueueError,
|
||||||
|
OnQueueFailed,
|
||||||
|
OnQueueProgress,
|
||||||
|
Process,
|
||||||
|
Processor,
|
||||||
|
} from '@nestjs/bull';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { Job } from 'bull';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
import split from 'split2';
|
||||||
|
import { QueueDto } from '@vidgrab2/api-interfaces';
|
||||||
|
import { raw } from 'youtube-dl-exec';
|
||||||
|
|
||||||
|
@Processor('vidgrab')
|
||||||
|
export class YtdlProcessor {
|
||||||
|
private readonly logger = new Logger(YtdlProcessor.name);
|
||||||
|
private setProgress = throttle((job: Job, progress: number) => {
|
||||||
|
job.progress(progress);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Process('download')
|
||||||
|
async handleDownload(job: Job<QueueDto>) {
|
||||||
|
this.logger.debug('Start downloading...');
|
||||||
|
this.logger.debug(job.data);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const execaProcess = raw(job.data.url, {
|
||||||
|
format: job.data.format,
|
||||||
|
output: `${this.configService.get(
|
||||||
|
'fileDir',
|
||||||
|
)}/%(title)s-%(format_id)s.%(ext)s`,
|
||||||
|
newline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
execaProcess.stdout?.pipe(split()).on('data', (line: string) => {
|
||||||
|
this.logger.verbose(`youtube-dl stdout: ${line}`);
|
||||||
|
|
||||||
|
const percentDone = line.match(/\[download\]\s+([0-9.]+)% of/);
|
||||||
|
if (percentDone) {
|
||||||
|
this.setProgress(job, parseFloat(percentDone[1]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
execaProcess.stderr?.pipe(split()).on('data', (line: any) => {
|
||||||
|
this.logger.error(`youtube-dl stderr: ${line}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
await execaProcess.then();
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug('Downloading completed');
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnQueueActive()
|
||||||
|
onActive(job: Job) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Processing job ${job.id} of type ${job.name} with data ${JSON.stringify(
|
||||||
|
job.data,
|
||||||
|
)}...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.eventEmitter.emit('job.updated', { job });
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnQueueCompleted()
|
||||||
|
onCompleted(job: Job, result: any) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Completed job ${job.id} of type ${job.name} with data ${JSON.stringify(
|
||||||
|
job.data,
|
||||||
|
)} and result ${result}...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.eventEmitter.emit('job.updated', { job });
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnQueueFailed()
|
||||||
|
onFailed(job: Job, err: Error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed job ${job.id} of type ${job.name} with data ${JSON.stringify(
|
||||||
|
job.data,
|
||||||
|
)} and error ${err}...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.eventEmitter.emit('job.failed', { job, error: err });
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnQueueError()
|
||||||
|
onError(err: Error) {
|
||||||
|
this.logger.error(`Error is ${err}...`);
|
||||||
|
|
||||||
|
this.eventEmitter.emit('job.error', { error: err });
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnQueueProgress()
|
||||||
|
onProgress(job: Job, progress: number) {
|
||||||
|
this.logger.verbose(
|
||||||
|
`Progress on ${job.id} of type ${job.name} with data ${JSON.stringify(
|
||||||
|
job.data,
|
||||||
|
)} and progress ${progress}...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.eventEmitter.emit('job.updated', { job });
|
||||||
|
}
|
||||||
|
}
|
||||||
26
apps/api/src/ytdl/ytdl.service.ts
Normal file
26
apps/api/src/ytdl/ytdl.service.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { default as ytdl } from 'youtube-dl-exec';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class YtdlService {
|
||||||
|
private readonly logger = new Logger(YtdlService.name);
|
||||||
|
|
||||||
|
async getVideoInfo(url: string) {
|
||||||
|
return ytdl(url, {
|
||||||
|
dumpSingleJson: true,
|
||||||
|
noWarnings: true,
|
||||||
|
noCallHome: true,
|
||||||
|
noCheckCertificate: true,
|
||||||
|
preferFreeFormats: true,
|
||||||
|
youtubeSkipDashManifest: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async listExtractors() {
|
||||||
|
const extractors = (await ytdl('', {
|
||||||
|
listExtractors: true,
|
||||||
|
})) as unknown;
|
||||||
|
|
||||||
|
return (extractors as string).split('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div style="text-align: center">
|
<!-- <div style="text-align: center">
|
||||||
<h1>Welcome to client!</h1>
|
<h1>Welcome to client!</h1>
|
||||||
<img
|
<img
|
||||||
width="450"
|
width="450"
|
||||||
@@ -6,4 +6,5 @@
|
|||||||
alt="Nx - Smart, Extensible Build Framework"
|
alt="Nx - Smart, Extensible Build Framework"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>Message: {{ hello$ | async | json }}</div>
|
<div>Message: {{ hello$ | async | json }}</div> -->
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { ExtractorsComponent } from './extractors/extractors.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: AppComponent },
|
||||||
|
{ path: 'extractors', component: ExtractorsComponent },
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent, ExtractorsComponent],
|
||||||
imports: [BrowserModule, HttpClientModule],
|
imports: [BrowserModule, HttpClientModule, RouterModule.forRoot(routes)],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
13
apps/client/src/app/extractors/extractors.component.html
Normal file
13
apps/client/src/app/extractors/extractors.component.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
Here is the list of currently supported services. This is generated
|
||||||
|
dynamically because youtube-dl adds new services all the time.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let e of extractors$ | async">{{ e }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
20
apps/client/src/app/extractors/extractors.component.ts
Normal file
20
apps/client/src/app/extractors/extractors.component.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/* eslint-disable @angular-eslint/no-empty-lifecycle-method */
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'vidgrab2-extractors',
|
||||||
|
templateUrl: './extractors.component.html',
|
||||||
|
styleUrls: ['./extractors.component.scss']
|
||||||
|
})
|
||||||
|
export class ExtractorsComponent implements OnInit {
|
||||||
|
|
||||||
|
extractors$ = this.http.get<{extractors: string[]}>('/api/extractors').pipe(map(e => e.extractors));
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Does a thing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,8 +6,30 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<vidgrab2-root></vidgrab2-root>
|
<div id="wrapper">
|
||||||
|
<section class="section has-background-light">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="level">
|
||||||
|
<h1 class="title is-1 level-item has-text-centered">Vidgrab</h1>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<vidgrab2-root></vidgrab2-root>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer id="footer" class="footer">
|
||||||
|
<div class="content has-text-centered">
|
||||||
|
<p>
|
||||||
|
Code and "Design" by <a href="#">Ted Kulp</a>. Based on youtube-dl.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1 +1,20 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
padding-top: 2em;
|
||||||
|
padding-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmarklet {
|
||||||
|
border: 1px dashed black;
|
||||||
|
padding: 10px 20px 10px 20px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
export interface Message {
|
export interface Message {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UploadDto {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueueDto {
|
||||||
|
format: string;
|
||||||
|
url: string;
|
||||||
|
title?: string;
|
||||||
|
extractor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JobEvent = {
|
||||||
|
job?: unknown;
|
||||||
|
err?: Error;
|
||||||
|
};
|
||||||
|
|||||||
2714
package-lock.json
generated
2714
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -40,14 +40,25 @@
|
|||||||
"@angular/platform-browser": "^12.1.0",
|
"@angular/platform-browser": "^12.1.0",
|
||||||
"@angular/platform-browser-dynamic": "^12.1.0",
|
"@angular/platform-browser-dynamic": "^12.1.0",
|
||||||
"@angular/router": "^12.1.0",
|
"@angular/router": "^12.1.0",
|
||||||
|
"@nestjs/bull": "^0.4.0",
|
||||||
"@nestjs/common": "^7.0.0",
|
"@nestjs/common": "^7.0.0",
|
||||||
|
"@nestjs/config": "^1.0.1",
|
||||||
"@nestjs/core": "^7.0.0",
|
"@nestjs/core": "^7.0.0",
|
||||||
|
"@nestjs/event-emitter": "^1.0.0",
|
||||||
"@nestjs/platform-express": "^7.0.0",
|
"@nestjs/platform-express": "^7.0.0",
|
||||||
|
"@nestjs/platform-socket.io": "^7.6.18",
|
||||||
"@nestjs/serve-static": "^2.2.2",
|
"@nestjs/serve-static": "^2.2.2",
|
||||||
|
"@nestjs/websockets": "^7.6.18",
|
||||||
"@nrwl/angular": "12.6.3",
|
"@nrwl/angular": "12.6.3",
|
||||||
|
"bull": "^3.27.0",
|
||||||
|
"class-transformer": "^0.4.0",
|
||||||
|
"class-validator": "^0.13.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "~6.6.0",
|
"rxjs": "~6.6.0",
|
||||||
|
"split2": "^3.2.2",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
|
"youtube-dl-exec": "github:tedkulp/youtube-dl-exec#master",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -65,7 +76,7 @@
|
|||||||
"@nrwl/eslint-plugin-nx": "12.6.3",
|
"@nrwl/eslint-plugin-nx": "12.6.3",
|
||||||
"@nrwl/jest": "12.6.3",
|
"@nrwl/jest": "12.6.3",
|
||||||
"@nrwl/linter": "12.6.3",
|
"@nrwl/linter": "12.6.3",
|
||||||
"@nrwl/nest": "12.6.3",
|
"@nrwl/nest": "^12.6.3",
|
||||||
"@nrwl/node": "12.6.3",
|
"@nrwl/node": "12.6.3",
|
||||||
"@nrwl/tao": "12.6.3",
|
"@nrwl/tao": "12.6.3",
|
||||||
"@nrwl/workspace": "12.6.3",
|
"@nrwl/workspace": "12.6.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user