mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-13 16:20:59 +03:00
Compare commits
93 Commits
tasks-and-
...
better-doc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a36761e96a | ||
|
|
88c16d7195 | ||
|
|
8a323f028d | ||
|
|
a68726e7cb | ||
|
|
dab0b7a8b6 | ||
|
|
9f9054ed9d | ||
|
|
4c06c430eb | ||
|
|
2981f843c3 | ||
|
|
3a48ff2d50 | ||
|
|
ac2c3dc8a1 | ||
|
|
0abe252d1e | ||
|
|
f5f00e1732 | ||
|
|
c309e41a91 | ||
|
|
754d837059 | ||
|
|
d5626f1dae | ||
|
|
9c0733453a | ||
|
|
2a41028253 | ||
|
|
67b2e480f8 | ||
|
|
2cdc1cee98 | ||
|
|
bd1ed2b705 | ||
|
|
33ca0f0817 | ||
|
|
d5ab0d7b96 | ||
|
|
777aebe508 | ||
|
|
efaecaa059 | ||
|
|
39ddefab5c | ||
|
|
60f2ab449f | ||
|
|
958f80e200 | ||
|
|
7aa5c1bf7f | ||
|
|
3bcbe0d3e7 | ||
|
|
80fcecdaea | ||
|
|
0329cd9718 | ||
|
|
493e876a97 | ||
|
|
574edd74ab | ||
|
|
fe91484f24 | ||
|
|
dda6e40a42 | ||
|
|
c0fb838931 | ||
|
|
28924cc7a0 | ||
|
|
2527051eab | ||
|
|
fcf7d14f46 | ||
|
|
0a8aba54d2 | ||
|
|
2c6485acb2 | ||
|
|
aea4f52267 | ||
|
|
5ac5fca482 | ||
|
|
7874f1b71a | ||
|
|
960c545f37 | ||
|
|
5e3eb68b03 | ||
|
|
4dd3b97515 | ||
|
|
701066eec1 | ||
|
|
7f61ccb5f5 | ||
|
|
4f227ca442 | ||
|
|
666bd2057d | ||
|
|
37c858f950 | ||
|
|
ebb7f6a2b0 | ||
|
|
48e46db071 | ||
|
|
768ec59f30 | ||
|
|
aa8f602856 | ||
|
|
d5c1361e64 | ||
|
|
901a96aada | ||
|
|
21507ee36d | ||
|
|
0585943d67 | ||
|
|
0bc2193f25 | ||
|
|
f3398fce1a | ||
|
|
60e8973f52 | ||
|
|
d94857b0a5 | ||
|
|
5fda56d7af | ||
|
|
abb80b33f3 | ||
|
|
9977340161 | ||
|
|
8ded160775 | ||
|
|
2ee64c7a65 | ||
|
|
2ec7efa1ac | ||
|
|
4d51384ce6 | ||
|
|
aa616af118 | ||
|
|
feebe3e2ba | ||
|
|
02e90fe818 | ||
|
|
a4cfafe63c | ||
|
|
63e2e6dd3c | ||
|
|
5a44229e24 | ||
|
|
5025b235b7 | ||
|
|
5d540fc52a | ||
|
|
55dfc17d62 | ||
|
|
2459403b22 | ||
|
|
ed5f910c33 | ||
|
|
468e7153e4 | ||
|
|
1bd713fe17 | ||
|
|
3df377a260 | ||
|
|
8314ab8fce | ||
|
|
c008171850 | ||
|
|
0f4f5293de | ||
|
|
16943847fc | ||
|
|
f79b254040 | ||
|
|
e7989e41f9 | ||
|
|
a4d421d398 | ||
|
|
b46b9ea386 |
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
.git
|
||||
db
|
||||
appdata
|
||||
audio
|
||||
video
|
||||
subscriptions
|
||||
users
|
||||
27
.github/workflows/docker-pr.yml
vendored
Normal file
27
.github/workflows/docker-pr.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: docker-pr
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set hash
|
||||
id: vars
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: create-json
|
||||
id: create-json
|
||||
uses: jsdaniell/create-json@1.1.2
|
||||
with:
|
||||
name: "version.json"
|
||||
json: '{"type": "docker", "tag": "nightly", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
|
||||
dir: 'backend/'
|
||||
- name: Build docker images
|
||||
run: docker build . -t tzahi12345/youtubedl-material:nightly-pr
|
||||
8
.github/workflows/docker.yml
vendored
8
.github/workflows/docker.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: jsdaniell/create-json@1.1.2
|
||||
with:
|
||||
name: "version.json"
|
||||
json: '{"type": "docker", "tag": "nightly", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
|
||||
json: '{"type": "docker", "tag": "${{secrets.DOCKERHUB_MASTER_TAG}}", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
|
||||
dir: 'backend/'
|
||||
- name: setup platform emulator
|
||||
uses: docker/setup-qemu-action@v1
|
||||
@@ -39,4 +39,8 @@ jobs:
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
||||
push: true
|
||||
tags: tzahi12345/youtubedl-material:nightly
|
||||
# Defaults:
|
||||
# DOCKERHUB_USERNAME : tzahi12345
|
||||
# DOCKERHUB_REPO : youtubedl-material
|
||||
# DOCKERHUB_MASTER_TAG: nightly
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:${{secrets.DOCKERHUB_MASTER_TAG}}
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -25,6 +25,7 @@
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
@@ -67,3 +68,11 @@ backend/users/*
|
||||
backend/appdata/cookies.txt
|
||||
backend/public
|
||||
src/assets/i18n/*.json
|
||||
|
||||
# User Files
|
||||
db/
|
||||
appdata/
|
||||
audio/
|
||||
video/
|
||||
subscriptions/
|
||||
users/
|
||||
55
Dockerfile
55
Dockerfile
@@ -1,9 +1,25 @@
|
||||
FROM alpine:latest as frontend
|
||||
FROM ubuntu:20.04 AS ffmpeg
|
||||
|
||||
RUN apk add --no-cache \
|
||||
npm
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN npm install -g @angular/cli
|
||||
COPY docker-build.sh .
|
||||
RUN sh ./docker-build.sh
|
||||
|
||||
FROM ubuntu:20.04 as frontend
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl \
|
||||
gnupg && \
|
||||
curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
|
||||
apt-get -y install \
|
||||
nodejs \
|
||||
# YARN: brings along npm, solves dependency conflicts,
|
||||
# spares us this spaghetti approach: https://stackoverflow.com/a/60547197
|
||||
yarn && \
|
||||
apt-get install -f && \
|
||||
npm config set strict-ssl false && \
|
||||
npm install -g @angular/cli
|
||||
|
||||
WORKDIR /build
|
||||
COPY [ "package.json", "package-lock.json", "/build/" ]
|
||||
@@ -15,34 +31,41 @@ RUN npm run build
|
||||
|
||||
#--------------#
|
||||
|
||||
FROM alpine:latest
|
||||
FROM ubuntu:20.04
|
||||
|
||||
ENV UID=1000 \
|
||||
GID=1000 \
|
||||
USER=youtube
|
||||
USER=youtube \
|
||||
NO_UPDATE_NOTIFIER=true
|
||||
|
||||
ENV NO_UPDATE_NOTIFIER=true
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID
|
||||
RUN groupadd -g $GID $USER && useradd --system -g $USER --uid $UID $USER
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ffmpeg \
|
||||
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
|
||||
RUN apt-get update && apt-get -y install \
|
||||
npm \
|
||||
python2 \
|
||||
python3 \
|
||||
su-exec \
|
||||
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
|
||||
atomicparsley
|
||||
atomicparsley && \
|
||||
apt-get install -f && \
|
||||
apt-get autoremove --purge && \
|
||||
apt-get autoremove && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=ffmpeg /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
|
||||
COPY --from=ffmpeg /usr/local/bin/ffprobe /usr/local/bin/ffprobe
|
||||
COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ]
|
||||
ENV PM2_HOME=/app/pm2
|
||||
RUN npm install pm2 -g
|
||||
RUN npm install && chown -R $UID:$GID ./
|
||||
RUN npm config set strict-ssl false && \
|
||||
npm install pm2 -g && \
|
||||
npm install && chown -R $UID:$GID ./
|
||||
|
||||
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
||||
COPY --chown=$UID:$GID [ "/backend/", "/app/" ]
|
||||
|
||||
EXPOSE 17442
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
# ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD [ "pm2-runtime", "pm2.config.js" ]
|
||||
|
||||
@@ -16,7 +16,7 @@ paths:
|
||||
- downloader
|
||||
summary: Download video file
|
||||
description: |-
|
||||
Downloads a video file with the given URL. Will include global args if they exist.
|
||||
Downloads a file with the given URL. Will include global args if they exist.
|
||||
|
||||
|
||||
HTTP requests will return once the video file download completes. In the future, it will (by default) return once the download starts, and a separate API call will be used for checking the download status.
|
||||
@@ -41,7 +41,7 @@ paths:
|
||||
post:
|
||||
tags:
|
||||
- downloader
|
||||
summary: Download video file
|
||||
summary: Generates arguments used to download file
|
||||
description: Generates args, used for checking what args would run if you ran downloadFile
|
||||
operationId: post-generateArgs
|
||||
requestBody:
|
||||
@@ -482,6 +482,26 @@ paths:
|
||||
description: OK
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
/api/deleteAllFiles:
|
||||
post:
|
||||
tags:
|
||||
- files
|
||||
summary: Delete all downloaded files
|
||||
operationId: post-api-deleteAllFiles
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeleteMp3Mp4Request'
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeleteAllFilesResponse'
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
/api/downloadArchive:
|
||||
post:
|
||||
tags:
|
||||
@@ -1765,6 +1785,15 @@ components:
|
||||
type: boolean
|
||||
error:
|
||||
type: string
|
||||
DeleteAllFilesResponse:
|
||||
type: object
|
||||
properties:
|
||||
file_count:
|
||||
type: number
|
||||
description: Number of files found matching search parameters
|
||||
delete_count:
|
||||
type: number
|
||||
description: Number of files removed
|
||||
DeleteSubscriptionFileRequest:
|
||||
required:
|
||||
- file
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/Tzahi12345/YoutubeDL-Material/issues)
|
||||
[](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/LICENSE.md)
|
||||
|
||||
YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 11](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend.
|
||||
YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 13](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend.
|
||||
|
||||
Now with [Docker](#Docker) support!
|
||||
|
||||
|
||||
21
SECURITY.md
Normal file
21
SECURITY.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Currently all work on this project goes into the nightly builds.
|
||||
4.2's RELEASE build is now quite old and should be considered legacy.
|
||||
We urge users to use the nightly releases, because the project
|
||||
constantly sees fixes.
|
||||
|
||||
| Version | Supported |
|
||||
| ------------- | ------------------ |
|
||||
| 4.2 Nightlies | :white_check_mark: |
|
||||
| 4.2 Release | :x: |
|
||||
| < 4.2 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please file an issue in our GitHub's repo, because this app
|
||||
isn't meant to be safe to run as public instance yet, but rather as a LAN facing app.
|
||||
|
||||
We welcome PRs and help in general in making YTDL-M more secure, but it's not a priority as of now.
|
||||
36
angular.json
36
angular.json
@@ -17,7 +17,6 @@
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"aot": true,
|
||||
"outputPath": "backend/public",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
@@ -33,7 +32,17 @@
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
"scripts": [],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true,
|
||||
"allowedCommonJsDependencies": [
|
||||
"rxjs",
|
||||
"crypto-js"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -46,7 +55,6 @@
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
@@ -60,7 +68,8 @@
|
||||
"es": {
|
||||
"localize": ["es"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": ""
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
@@ -152,16 +161,6 @@
|
||||
"src/backend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -176,15 +175,6 @@
|
||||
"protractorConfig": "./protractor.conf.js",
|
||||
"devServerTarget": "youtube-dl-material:serve"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"e2e/tsconfig.e2e.json"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
FROM alpine:3.12 as frontend
|
||||
|
||||
RUN apk add --no-cache \
|
||||
npm \
|
||||
curl
|
||||
|
||||
RUN npm install -g @angular/cli
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
RUN curl -L https://github.com/balena-io/qemu/releases/download/v3.0.0%2Bresin/qemu-3.0.0+resin-arm.tar.gz | tar zxvf - -C . && mv qemu-3.0.0+resin-arm/qemu-arm-static .
|
||||
|
||||
COPY [ "package.json", "package-lock.json", "/build/" ]
|
||||
RUN npm install
|
||||
|
||||
COPY [ "angular.json", "tsconfig.json", "/build/" ]
|
||||
COPY [ "src/", "/build/src/" ]
|
||||
RUN ng build --prod
|
||||
|
||||
#--------------#
|
||||
|
||||
FROM arm32v7/alpine:3.12
|
||||
|
||||
COPY --from=frontend /build/qemu-arm-static /usr/bin
|
||||
|
||||
ENV UID=1000 \
|
||||
GID=1000 \
|
||||
USER=youtube
|
||||
|
||||
RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ffmpeg \
|
||||
npm \
|
||||
python2 \
|
||||
su-exec \
|
||||
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
|
||||
atomicparsley
|
||||
|
||||
WORKDIR /app
|
||||
COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ]
|
||||
RUN npm install && chown -R $UID:$GID ./
|
||||
|
||||
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
||||
COPY --chown=$UID:$GID [ "/backend/", "/app/" ]
|
||||
|
||||
EXPOSE 17442
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD [ "node", "app.js" ]
|
||||
@@ -803,7 +803,7 @@ app.post('/api/testConnectionString', optionalJwt, async (req, res) => {
|
||||
app.post('/api/downloadFile', optionalJwt, async function(req, res) {
|
||||
req.setTimeout(0); // remove timeout in case of long videos
|
||||
const url = req.body.url;
|
||||
const type = req.body.type;
|
||||
const type = req.body.type ? req.body.type : 'video';
|
||||
const user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||
const options = {
|
||||
customArgs: req.body.customArgs,
|
||||
@@ -1434,6 +1434,46 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => {
|
||||
res.send(wasDeleted);
|
||||
});
|
||||
|
||||
app.post('/api/deleteAllFiles', optionalJwt, async (req, res) => {
|
||||
const blacklistMode = false;
|
||||
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||
|
||||
let files = null;
|
||||
let text_search = req.body.text_search;
|
||||
let file_type_filter = req.body.file_type_filter;
|
||||
|
||||
const filter_obj = {user_uid: uuid};
|
||||
const regex = true;
|
||||
if (text_search) {
|
||||
if (regex) {
|
||||
filter_obj['title'] = {$regex: `.*${text_search}.*`, $options: 'i'};
|
||||
} else {
|
||||
filter_obj['$text'] = { $search: utils.createEdgeNGrams(text_search) };
|
||||
}
|
||||
}
|
||||
|
||||
if (file_type_filter === 'audio_only') filter_obj['isAudio'] = true;
|
||||
else if (file_type_filter === 'video_only') filter_obj['isAudio'] = false;
|
||||
|
||||
files = await db_api.getRecords('files', filter_obj);
|
||||
|
||||
let file_count = await db_api.getRecords('files', filter_obj, true);
|
||||
let delete_count = 0;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let wasDeleted = false;
|
||||
wasDeleted = await db_api.deleteFile(files[i].uid, uuid, blacklistMode);
|
||||
if (wasDeleted) {
|
||||
delete_count++;
|
||||
}
|
||||
}
|
||||
|
||||
res.send({
|
||||
file_count: file_count,
|
||||
delete_count: delete_count
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/downloadFileFromServer', optionalJwt, async (req, res) => {
|
||||
let uid = req.body.uid;
|
||||
let uuid = req.body.uuid;
|
||||
@@ -1456,18 +1496,14 @@ app.post('/api/downloadFileFromServer', optionalJwt, async (req, res) => {
|
||||
}
|
||||
|
||||
// generate zip
|
||||
file_path_to_download = await utils.createContainerZipFile(playlist, playlist_files_to_download);
|
||||
file_path_to_download = await utils.createContainerZipFile(playlist['name'], playlist_files_to_download);
|
||||
} else if (sub_id && !uid) {
|
||||
zip_file_generated = true;
|
||||
const sub_files_to_download = [];
|
||||
const sub = subscriptions_api.getSubscription(sub_id, uuid);
|
||||
for (let i = 0; i < sub['videos'].length; i++) {
|
||||
const sub_file = sub['videos'][i];
|
||||
sub_files_to_download.push(sub_file);
|
||||
}
|
||||
const sub = await db_api.getRecord('subscriptions', {id: sub_id});
|
||||
const sub_files_to_download = await db_api.getRecords('files', {sub_id: sub_id});
|
||||
|
||||
// generate zip
|
||||
file_path_to_download = await utils.createContainerZipFile(sub, sub_files_to_download);
|
||||
file_path_to_download = await utils.createContainerZipFile(sub['name'], sub_files_to_download);
|
||||
} else {
|
||||
const file_obj = await db_api.getVideo(uid, uuid, sub_id)
|
||||
file_path_to_download = file_obj.path;
|
||||
|
||||
@@ -18,10 +18,19 @@ let JWT_EXPIRATION = null;
|
||||
let opts = null;
|
||||
let saltRounds = null;
|
||||
|
||||
exports.initialize = function() {
|
||||
exports.initialize = function () {
|
||||
/*************************
|
||||
* Authentication module
|
||||
************************/
|
||||
|
||||
if (db_api.database_initialized) {
|
||||
setupRoles();
|
||||
} else {
|
||||
db_api.database_initialized_bs.subscribe(init => {
|
||||
if (init) setupRoles();
|
||||
});
|
||||
}
|
||||
|
||||
saltRounds = 10;
|
||||
|
||||
JWT_EXPIRATION = config_api.getConfigItem('ytdl_jwt_expiration');
|
||||
@@ -49,6 +58,41 @@ exports.initialize = function() {
|
||||
}));
|
||||
}
|
||||
|
||||
const setupRoles = async () => {
|
||||
const required_roles = {
|
||||
admin: {
|
||||
permissions: [
|
||||
'filemanager',
|
||||
'settings',
|
||||
'subscriptions',
|
||||
'sharing',
|
||||
'advanced_download',
|
||||
'downloads_manager'
|
||||
]
|
||||
},
|
||||
user: {
|
||||
permissions: [
|
||||
'filemanager',
|
||||
'subscriptions',
|
||||
'sharing'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const role_keys = Object.keys(required_roles);
|
||||
for (let i = 0; i < role_keys.length; i++) {
|
||||
const role_key = role_keys[i];
|
||||
const role_in_db = await db_api.getRecord('roles', {key: role_key});
|
||||
if (!role_in_db) {
|
||||
// insert task metadata into table if missing
|
||||
await db_api.insertRecordIntoTable('roles', {
|
||||
key: role_key,
|
||||
permissions: required_roles[role_key]['permissions']
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.passport = require('passport');
|
||||
|
||||
exports.passport.serializeUser(function(user, done) {
|
||||
|
||||
@@ -85,6 +85,7 @@ exports.initialize = (input_db, input_users_db) => {
|
||||
}
|
||||
|
||||
exports.connectToDB = async (retries = 5, no_fallback = false, custom_connection_string = null) => {
|
||||
using_local_db = config_api.getConfigItem('ytdl_use_local_db'); // verify
|
||||
if (using_local_db && !custom_connection_string) return;
|
||||
const success = await exports._connectToDB(custom_connection_string);
|
||||
if (success) return true;
|
||||
|
||||
@@ -3,7 +3,6 @@ const { uuid } = require('uuidv4');
|
||||
const path = require('path');
|
||||
const mergeFiles = require('merge-files');
|
||||
const NodeID3 = require('node-id3')
|
||||
const glob = require('glob')
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
const youtubedl = require('youtube-dl');
|
||||
@@ -583,20 +582,26 @@ async function checkDownloadPercent(download_uid) {
|
||||
if (!resulting_file_size) return;
|
||||
|
||||
let sum_size = 0;
|
||||
glob(`{${files_to_check_for_progress.join(',')}, }*`, async (err, files) => {
|
||||
files.forEach(async file => {
|
||||
try {
|
||||
const file_stats = fs.statSync(file);
|
||||
if (file_stats && file_stats.size) {
|
||||
sum_size += file_stats.size;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
for (let i = 0; i < files_to_check_for_progress.length; i++) {
|
||||
const file_to_check_for_progress = files_to_check_for_progress[i];
|
||||
const dir = path.dirname(file_to_check_for_progress);
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
fs.readdir(dir, async (err, files) => {
|
||||
for (let j = 0; j < files.length; j++) {
|
||||
const file = files[j];
|
||||
if (!file.includes(path.basename(file_to_check_for_progress))) continue;
|
||||
try {
|
||||
const file_stats = fs.statSync(path.join(dir, file));
|
||||
if (file_stats && file_stats.size) {
|
||||
sum_size += file_stats.size;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const percent_complete = (sum_size/resulting_file_size * 100).toFixed(2);
|
||||
await db_api.updateRecord('download_queue', {uid: download_uid}, {percent_complete: percent_complete});
|
||||
});
|
||||
const percent_complete = (sum_size/resulting_file_size * 100).toFixed(2);
|
||||
await db_api.updateRecord('download_queue', {uid: download_uid}, {percent_complete: percent_complete});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.generateNFOFile = (info, output_path) => {
|
||||
|
||||
940
backend/package-lock.json
generated
940
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -30,23 +30,21 @@
|
||||
},
|
||||
"homepage": "",
|
||||
"dependencies": {
|
||||
"archiver": "^3.1.1",
|
||||
"async": "^3.1.0",
|
||||
"archiver": "^5.3.1",
|
||||
"async": "^3.2.3",
|
||||
"async-mutex": "^0.3.1",
|
||||
"axios": "^0.21.2",
|
||||
"bcryptjs": "^2.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"config": "^3.2.3",
|
||||
"exe": "^1.0.2",
|
||||
"express": "^4.17.1",
|
||||
"express": "^4.17.3",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lowdb": "^1.0.0",
|
||||
"md5": "^2.2.1",
|
||||
"merge-files": "^0.1.2",
|
||||
"mocha": "^8.4.0",
|
||||
"mocha": "^9.2.2",
|
||||
"moment": "^2.29.2",
|
||||
"mongodb": "^3.6.9",
|
||||
"multer": "^1.4.2",
|
||||
@@ -57,7 +55,7 @@
|
||||
"passport": "^0.4.1",
|
||||
"passport-http": "^0.3.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-ldapauth": "^2.1.4",
|
||||
"passport-ldapauth": "^3.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"progress": "^2.0.3",
|
||||
"ps-node": "^0.1.6",
|
||||
@@ -66,7 +64,7 @@
|
||||
"shortid": "^2.2.15",
|
||||
"unzipper": "^0.10.10",
|
||||
"uuidv4": "^6.0.6",
|
||||
"winston": "^3.3.3",
|
||||
"winston": "^3.7.2",
|
||||
"xmlbuilder2": "^3.0.2",
|
||||
"youtube-dl": "^3.0.2"
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ async function unsubscribe(sub, deleteMode, user_uid = null) {
|
||||
if (sub.archive && (await fs.pathExists(sub.archive))) {
|
||||
const archive_file_path = path.join(sub.archive, 'archive.txt');
|
||||
// deletes archive if it exists
|
||||
// TODO: Keep entries in blacklist_video.txt by moving them to a global blacklist
|
||||
if (await fs.pathExists(archive_file_path)) {
|
||||
await fs.unlink(archive_file_path);
|
||||
}
|
||||
@@ -266,11 +267,17 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
}
|
||||
resolve(false);
|
||||
} else if (output) {
|
||||
if (config_api.getConfigItem('ytdl_subscriptions_redownload_fresh_uploads')) {
|
||||
await setFreshUploads(sub, user_uid);
|
||||
checkVideosForFreshUploads(sub, user_uid);
|
||||
}
|
||||
|
||||
if (output.length === 0 || (output.length === 1 && output[0] === '')) {
|
||||
logger.verbose('No additional videos to download for ' + sub.name);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const output_jsons = [];
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
let output_json = null;
|
||||
@@ -294,14 +301,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
}
|
||||
|
||||
resolve(files_to_download);
|
||||
|
||||
if (config_api.getConfigItem('ytdl_subscriptions_redownload_fresh_uploads')) {
|
||||
await setFreshUploads(sub, user_uid);
|
||||
checkVideosForFreshUploads(sub, user_uid);
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, err => {
|
||||
logger.error(err);
|
||||
@@ -380,7 +380,11 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
||||
if (useArchive && !redownload) {
|
||||
if (sub.archive) {
|
||||
archive_dir = sub.archive;
|
||||
archive_path = path.join(archive_dir, 'archive.txt')
|
||||
if (sub.type && sub.type === 'audio') {
|
||||
archive_path = path.join(archive_dir, 'merged_audio.txt');
|
||||
} else {
|
||||
archive_path = path.join(archive_dir, 'merged_video.txt');
|
||||
}
|
||||
}
|
||||
downloadConfig.push('--download-archive', archive_path);
|
||||
}
|
||||
@@ -473,22 +477,24 @@ async function updateSubscriptionProperty(sub, assignment_obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function setFreshUploads(sub, user_uid) {
|
||||
async function setFreshUploads(sub) {
|
||||
const sub_files = await db_api.getRecords('files', {sub_id: sub.id});
|
||||
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
||||
sub.videos.forEach(async video => {
|
||||
if (current_date === video['upload_date'].replace(/-/g, '')) {
|
||||
sub_files.forEach(async file => {
|
||||
if (current_date === file['upload_date'].replace(/-/g, '')) {
|
||||
// set upload as fresh
|
||||
const video_uid = video['uid'];
|
||||
await db_api.setVideoProperty(video_uid, {'fresh_upload': true}, user_uid, sub['id']);
|
||||
const file_uid = file['uid'];
|
||||
await db_api.setVideoProperty(file_uid, {'fresh_upload': true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function checkVideosForFreshUploads(sub, user_uid) {
|
||||
const sub_files = await db_api.getRecords('files', {sub_id: sub.id});
|
||||
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
||||
sub.videos.forEach(async video => {
|
||||
if (video['fresh_upload'] && current_date > video['upload_date'].replace(/-/g, '')) {
|
||||
await checkVideoIfBetterExists(video, sub, user_uid)
|
||||
sub_files.forEach(async file => {
|
||||
if (file['fresh_upload'] && current_date > file['upload_date'].replace(/-/g, '')) {
|
||||
await checkVideoIfBetterExists(file, sub, user_uid)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -510,13 +516,13 @@ async function checkVideoIfBetterExists(file_obj, sub, user_uid) {
|
||||
logger.verbose(`Failed to download better version of video ${file_obj['id']}`);
|
||||
} else if (output) {
|
||||
logger.verbose(`Successfully upgraded video ${file_obj['id']}'s ${metric_to_compare} from ${file_obj[metric_to_compare]} to ${output[metric_to_compare]}`);
|
||||
await db_api.setVideoProperty(file_obj['uid'], {[metric_to_compare]: output[metric_to_compare]}, user_uid, sub['id']);
|
||||
await db_api.setVideoProperty(file_obj['uid'], {[metric_to_compare]: output[metric_to_compare]});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
await db_api.setVideoProperty(file_obj['uid'], {'fresh_upload': false}, user_uid, sub['id']);
|
||||
await db_api.setVideoProperty(file_obj['uid'], {'fresh_upload': false});
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
@@ -42,9 +42,9 @@ function scheduleJob(task_key, schedule) {
|
||||
if (schedule['type'] === 'timestamp') {
|
||||
converted_schedule = new Date(schedule['data']['timestamp']);
|
||||
} else if (schedule['type'] === 'recurring') {
|
||||
const dayOfWeek = schedule['data']['dayOfWeek'] ? schedule['data']['dayOfWeek'] : null;
|
||||
const hour = schedule['data']['hour'] ? schedule['data']['hour'] : null;
|
||||
const minute = schedule['data']['minute'] ? schedule['data']['minute'] : null;
|
||||
const dayOfWeek = schedule['data']['dayOfWeek'] != null ? schedule['data']['dayOfWeek'] : null;
|
||||
const hour = schedule['data']['hour'] != null ? schedule['data']['hour'] : null;
|
||||
const minute = schedule['data']['minute'] != null ? schedule['data']['minute'] : null;
|
||||
converted_schedule = new scheduler.RecurrenceRule(null, null, null, dayOfWeek, hour, minute);
|
||||
} else {
|
||||
logger.error(`Failed to schedule job '${task_key}' as the type '${schedule['type']}' is invalid.`)
|
||||
|
||||
@@ -58,13 +58,13 @@ async function getDownloadedFilesByType(basePath, type, full_metadata = false) {
|
||||
return files;
|
||||
}
|
||||
|
||||
async function createContainerZipFile(container_obj, container_file_objs) {
|
||||
async function createContainerZipFile(file_name, container_file_objs) {
|
||||
const container_files_to_download = [];
|
||||
for (let i = 0; i < container_file_objs.length; i++) {
|
||||
const container_file_obj = container_file_objs[i];
|
||||
container_files_to_download.push(container_file_obj.path);
|
||||
}
|
||||
return await createZipFile(path.join('appdata', container_obj.name + '.zip'), container_files_to_download);
|
||||
return await createZipFile(path.join('appdata', file_name + '.zip'), container_files_to_download);
|
||||
}
|
||||
|
||||
async function createZipFile(zip_file_path, file_paths) {
|
||||
|
||||
43
docker-build.sh
Normal file
43
docker-build.sh
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
# THANK YOU TALULAH (https://github.com/nottalulah) for your help in figuring this out
|
||||
# and also optimizing some code with this commit.
|
||||
# xoxo :D
|
||||
|
||||
case $(uname -m) in
|
||||
x86_64)
|
||||
ARCH=amd64;;
|
||||
aarch64)
|
||||
ARCH=arm64;;
|
||||
armhf)
|
||||
ARCH=armhf;;
|
||||
armv7)
|
||||
ARCH=armel;;
|
||||
armv7l)
|
||||
ARCH=armel;;
|
||||
*)
|
||||
echo "Unsupported architecture: $(uname -m)"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
echo "(INFO) Architecture detected: $ARCH"
|
||||
echo "(1/5) READY - Acquire temp dependencies in ffmpeg obtain layer"
|
||||
apt-get update && apt-get -y install curl xz-utils
|
||||
echo "(2/5) DOWNLOAD - Acquire latest ffmpeg and ffprobe from John van Sickle's master-sourced builds in ffmpeg obtain layer"
|
||||
curl -o ffmpeg.txz \
|
||||
--connect-timeout 5 \
|
||||
--max-time 10 \
|
||||
--retry 5 \
|
||||
--retry-delay 0 \
|
||||
--retry-max-time 40 \
|
||||
"https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-${ARCH}-static.tar.xz"
|
||||
mkdir /tmp/ffmpeg
|
||||
tar xf ffmpeg.txz -C /tmp/ffmpeg
|
||||
echo "(3/5) CLEANUP - Remove temp dependencies from ffmpeg obtain layer"
|
||||
apt-get -y remove curl xz-utils
|
||||
apt-get -y autoremove
|
||||
echo "(4/5) PROVISION - Provide ffmpeg and ffprobe from ffmpeg obtain layer"
|
||||
cp /tmp/ffmpeg/*/ffmpeg /usr/local/bin/ffmpeg
|
||||
cp /tmp/ffmpeg/*/ffprobe /usr/local/bin/ffprobe
|
||||
echo "(5/5) CLEANUP - Remove temporary downloads from ffmpeg obtain layer"
|
||||
rm -rf /tmp/ffmpeg ffmpeg.txz
|
||||
@@ -7,6 +7,8 @@ services:
|
||||
ytdl_use_local_db: 'false'
|
||||
write_ytdl_config: 'true'
|
||||
restart: always
|
||||
depends_on:
|
||||
- ytdl-mongo-db
|
||||
volumes:
|
||||
- ./appdata:/app/appdata
|
||||
- ./audio:/app/audio
|
||||
@@ -23,5 +25,6 @@ services:
|
||||
logging:
|
||||
driver: "none"
|
||||
container_name: mongo-db
|
||||
restart: always
|
||||
volumes:
|
||||
- ./db/:/data/db
|
||||
11845
package-lock.json
generated
11845
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build --prod",
|
||||
"build": "ng build --configuration production",
|
||||
"prebuild": "node src/postbuild.mjs",
|
||||
"heroku-postbuild": "npm install --prefix backend",
|
||||
"test": "ng test",
|
||||
@@ -20,44 +20,44 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "^11.0.4",
|
||||
"@angular/animations": "^11.0.4",
|
||||
"@angular/cdk": "^11.0.2",
|
||||
"@angular/common": "^11.0.4",
|
||||
"@angular/compiler": "^11.0.4",
|
||||
"@angular/core": "^11.0.4",
|
||||
"@angular/forms": "^11.0.4",
|
||||
"@angular/localize": "^11.0.4",
|
||||
"@angular/material": "^11.0.2",
|
||||
"@angular/platform-browser": "^11.0.4",
|
||||
"@angular/platform-browser-dynamic": "^11.0.4",
|
||||
"@angular/router": "^11.0.4",
|
||||
"@angular-devkit/core": "^13.3.3",
|
||||
"@angular/animations": "^13.3.4",
|
||||
"@angular/cdk": "^13.3.4",
|
||||
"@angular/common": "^13.3.4",
|
||||
"@angular/compiler": "^13.3.4",
|
||||
"@angular/core": "^13.3.4",
|
||||
"@angular/forms": "^13.3.4",
|
||||
"@angular/localize": "^13.3.4",
|
||||
"@angular/material": "^13.3.4",
|
||||
"@angular/platform-browser": "^13.3.4",
|
||||
"@angular/platform-browser-dynamic": "^13.3.4",
|
||||
"@angular/router": "^13.3.4",
|
||||
"@fontsource/material-icons": "^4.5.4",
|
||||
"@ngneat/content-loader": "^5.0.0",
|
||||
"@videogular/ngx-videogular": "^2.1.0",
|
||||
"@videogular/ngx-videogular": "^5.0.1",
|
||||
"core-js": "^2.4.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"file-saver": "^2.0.2",
|
||||
"filesize": "^6.1.0",
|
||||
"fingerprintjs2": "^2.1.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"material-icons": "^0.5.4",
|
||||
"material-icons": "^1.10.8",
|
||||
"nan": "^2.14.1",
|
||||
"ng-lazyload-image": "^7.0.1",
|
||||
"ngx-avatar": "^4.0.0",
|
||||
"ngx-file-drop": "^9.0.1",
|
||||
"ngx-avatars": "^1.3.1",
|
||||
"ngx-file-drop": "^13.0.0",
|
||||
"rxjs": "^6.6.3",
|
||||
"rxjs-compat": "^6.0.0-rc.0",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "~4.0.5",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"typescript": "~4.6.3",
|
||||
"xliff-to-json": "^1.0.4",
|
||||
"zone.js": "~0.10.2"
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.1100.4",
|
||||
"@angular/cli": "^11.0.4",
|
||||
"@angular/compiler-cli": "^11.0.4",
|
||||
"@angular/language-service": "^11.0.4",
|
||||
"@angular-devkit/build-angular": "^13.3.3",
|
||||
"@angular/cli": "^13.3.3",
|
||||
"@angular/compiler-cli": "^13.3.4",
|
||||
"@angular/language-service": "^13.3.4",
|
||||
"@types/core-js": "^2.5.2",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/jasmine": "~3.6.0",
|
||||
@@ -75,7 +75,7 @@
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"openapi-typescript-codegen": "^0.4.11",
|
||||
"openapi-typescript-codegen": "^0.21.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~3.0.4",
|
||||
"tslint": "~6.1.0"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Coolors Exported Palette - coolors.co/e8aeb7-b8e1ff-a9fff7-94fbab-82aba1 */
|
||||
|
||||
/* HSL */
|
||||
$color1: hsla(351%, 56%, 80%, 1);
|
||||
$softblue: hsla(205%, 100%, 86%, 1);
|
||||
$color3: hsla(174%, 100%, 83%, 1);
|
||||
$color4: hsla(133%, 93%, 78%, 1);
|
||||
$color5: hsla(165%, 20%, 59%, 1);
|
||||
$color1: hsla(351, 56%, 80%, 1);
|
||||
$softblue: hsla(205, 100%, 86%, 1);
|
||||
$color3: hsla(174, 100%, 83%, 1);
|
||||
$color4: hsla(133, 93%, 78%, 1);
|
||||
$color5: hsla(165, 20%, 59%, 1);
|
||||
|
||||
/* RGB */
|
||||
$color1: rgba(232, 174, 183, 1);
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</ng-container>
|
||||
<ng-container *ngIf="postsService.config && allowSubscriptions && postsService.subscriptions && postsService.hasPermission('subscriptions')">
|
||||
<mat-divider *ngIf="postsService.subscriptions.length > 0"></mat-divider>
|
||||
<a *ngFor="let subscription of postsService.subscriptions" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" [routerLink]="['/subscription', { id: subscription.id }]"><ngx-avatar [style.margin-right]="'10px'" size="32" [name]="subscription.name"></ngx-avatar>{{subscription.name}}</a>
|
||||
<a *ngFor="let subscription of postsService.subscriptions" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" [routerLink]="['/subscription', { id: subscription.id }]"><ngx-avatars [style.margin-right]="'10px'" size="32" [name]="subscription.name"></ngx-avatars>{{subscription.name}}</a>
|
||||
</ng-container>
|
||||
</mat-nav-list>
|
||||
</mat-sidenav>
|
||||
|
||||
@@ -45,8 +45,6 @@ import { VgBufferingModule } from '@videogular/ngx-videogular/buffering';
|
||||
import { VgOverlayPlayModule } from '@videogular/ngx-videogular/overlay-play';
|
||||
import { VgCoreModule } from '@videogular/ngx-videogular/core';
|
||||
import { InputDialogComponent } from './input-dialog/input-dialog.component';
|
||||
import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image';
|
||||
import { audioFilesMouseHovering, videoFilesMouseHovering, audioFilesOpened, videoFilesOpened } from './main/main.component';
|
||||
import { CreatePlaylistComponent } from './create-playlist/create-playlist.component';
|
||||
import { SubscriptionsComponent } from './subscriptions/subscriptions.component';
|
||||
import { SubscribeDialogComponent } from './dialogs/subscribe-dialog/subscribe-dialog.component';
|
||||
@@ -56,7 +54,7 @@ import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dia
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { NgxFileDropModule } from 'ngx-file-drop';
|
||||
import { AvatarModule } from 'ngx-avatar';
|
||||
import { AvatarModule } from 'ngx-avatars';
|
||||
import { ContentLoaderModule } from '@ngneat/content-loader';
|
||||
|
||||
import es from '@angular/common/locales/es';
|
||||
@@ -94,122 +92,110 @@ import { RestoreDbDialogComponent } from './dialogs/restore-db-dialog/restore-db
|
||||
|
||||
registerLocaleData(es, 'es');
|
||||
|
||||
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
||||
return (element.id === 'video' ? videoFilesMouseHovering || videoFilesOpened : audioFilesMouseHovering || audioFilesOpened);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MainComponent,
|
||||
PlayerComponent,
|
||||
InputDialogComponent,
|
||||
CreatePlaylistComponent,
|
||||
SubscriptionsComponent,
|
||||
SubscribeDialogComponent,
|
||||
SubscriptionComponent,
|
||||
SubscriptionFileCardComponent,
|
||||
SubscriptionInfoDialogComponent,
|
||||
SettingsComponent,
|
||||
AboutDialogComponent,
|
||||
VideoInfoDialogComponent,
|
||||
ArgModifierDialogComponent,
|
||||
HighlightPipe,
|
||||
LinkifyPipe,
|
||||
UpdaterComponent,
|
||||
UpdateProgressDialogComponent,
|
||||
ShareMediaDialogComponent,
|
||||
LoginComponent,
|
||||
DownloadsComponent,
|
||||
UserProfileDialogComponent,
|
||||
SetDefaultAdminDialogComponent,
|
||||
ModifyUsersComponent,
|
||||
AddUserDialogComponent,
|
||||
ManageUserComponent,
|
||||
ManageRoleComponent,
|
||||
CookiesUploaderDialogComponent,
|
||||
LogsViewerComponent,
|
||||
ModifyPlaylistComponent,
|
||||
ConfirmDialogComponent,
|
||||
UnifiedFileCardComponent,
|
||||
RecentVideosComponent,
|
||||
EditSubscriptionDialogComponent,
|
||||
CustomPlaylistsComponent,
|
||||
EditCategoryDialogComponent,
|
||||
TwitchChatComponent,
|
||||
SeeMoreComponent,
|
||||
ConcurrentStreamComponent,
|
||||
SkipAdButtonComponent,
|
||||
TasksComponent,
|
||||
UpdateTaskScheduleDialogComponent,
|
||||
RestoreDbDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
MatNativeDateModule,
|
||||
MatRadioModule,
|
||||
FormsModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
MatToolbarModule,
|
||||
MatCardModule,
|
||||
MatSnackBarModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatSidenavModule,
|
||||
MatIconModule,
|
||||
MatListModule,
|
||||
MatGridListModule,
|
||||
MatExpansionModule,
|
||||
MatProgressBarModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatButtonToggleModule,
|
||||
MatRippleModule,
|
||||
MatMenuModule,
|
||||
MatDialogModule,
|
||||
MatSlideToggleModule,
|
||||
MatAutocompleteModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
MatDatepickerModule,
|
||||
MatChipsModule,
|
||||
DragDropModule,
|
||||
ClipboardModule,
|
||||
TextFieldModule,
|
||||
NgxFileDropModule,
|
||||
AvatarModule,
|
||||
ContentLoaderModule,
|
||||
VgCoreModule,
|
||||
VgControlsModule,
|
||||
VgOverlayPlayModule,
|
||||
VgBufferingModule,
|
||||
LazyLoadImageModule.forRoot({ isVisible }),
|
||||
RouterModule,
|
||||
AppRoutingModule,
|
||||
],
|
||||
entryComponents: [
|
||||
InputDialogComponent,
|
||||
CreatePlaylistComponent,
|
||||
SubscribeDialogComponent,
|
||||
SubscriptionInfoDialogComponent,
|
||||
SettingsComponent
|
||||
],
|
||||
providers: [
|
||||
PostsService,
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: H401Interceptor, multi: true }
|
||||
],
|
||||
exports: [
|
||||
HighlightPipe,
|
||||
LinkifyPipe
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MainComponent,
|
||||
PlayerComponent,
|
||||
InputDialogComponent,
|
||||
CreatePlaylistComponent,
|
||||
SubscriptionsComponent,
|
||||
SubscribeDialogComponent,
|
||||
SubscriptionComponent,
|
||||
SubscriptionFileCardComponent,
|
||||
SubscriptionInfoDialogComponent,
|
||||
SettingsComponent,
|
||||
AboutDialogComponent,
|
||||
VideoInfoDialogComponent,
|
||||
ArgModifierDialogComponent,
|
||||
HighlightPipe,
|
||||
LinkifyPipe,
|
||||
UpdaterComponent,
|
||||
UpdateProgressDialogComponent,
|
||||
ShareMediaDialogComponent,
|
||||
LoginComponent,
|
||||
DownloadsComponent,
|
||||
UserProfileDialogComponent,
|
||||
SetDefaultAdminDialogComponent,
|
||||
ModifyUsersComponent,
|
||||
AddUserDialogComponent,
|
||||
ManageUserComponent,
|
||||
ManageRoleComponent,
|
||||
CookiesUploaderDialogComponent,
|
||||
LogsViewerComponent,
|
||||
ModifyPlaylistComponent,
|
||||
ConfirmDialogComponent,
|
||||
UnifiedFileCardComponent,
|
||||
RecentVideosComponent,
|
||||
EditSubscriptionDialogComponent,
|
||||
CustomPlaylistsComponent,
|
||||
EditCategoryDialogComponent,
|
||||
TwitchChatComponent,
|
||||
SeeMoreComponent,
|
||||
ConcurrentStreamComponent,
|
||||
SkipAdButtonComponent,
|
||||
TasksComponent,
|
||||
UpdateTaskScheduleDialogComponent,
|
||||
RestoreDbDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
MatNativeDateModule,
|
||||
MatRadioModule,
|
||||
FormsModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
MatToolbarModule,
|
||||
MatCardModule,
|
||||
MatSnackBarModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatSidenavModule,
|
||||
MatIconModule,
|
||||
MatListModule,
|
||||
MatGridListModule,
|
||||
MatExpansionModule,
|
||||
MatProgressBarModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatButtonToggleModule,
|
||||
MatRippleModule,
|
||||
MatMenuModule,
|
||||
MatDialogModule,
|
||||
MatSlideToggleModule,
|
||||
MatAutocompleteModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
MatDatepickerModule,
|
||||
MatChipsModule,
|
||||
DragDropModule,
|
||||
ClipboardModule,
|
||||
TextFieldModule,
|
||||
NgxFileDropModule,
|
||||
AvatarModule,
|
||||
ContentLoaderModule,
|
||||
VgCoreModule,
|
||||
VgControlsModule,
|
||||
VgOverlayPlayModule,
|
||||
VgBufferingModule,
|
||||
RouterModule,
|
||||
AppRoutingModule,
|
||||
],
|
||||
providers: [
|
||||
PostsService,
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: H401Interceptor, multi: true }
|
||||
],
|
||||
exports: [
|
||||
HighlightPipe,
|
||||
LinkifyPipe
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
||||
export class AppModule { }
|
||||
|
||||
@@ -42,6 +42,11 @@ export class ConcurrentStreamComponent implements OnInit {
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.check_timeout) { clearInterval(this.check_timeout); }
|
||||
if (this.update_timeout) { clearInterval(this.update_timeout); }
|
||||
}
|
||||
|
||||
startServer() {
|
||||
this.started = true;
|
||||
this.server_started = true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<span class="text" [ngStyle]="{'-webkit-line-clamp': !see_more_active ? line_limit : null}" [innerHTML]="text | linkify"></span>
|
||||
<span>
|
||||
<a [routerLink]="" (click)="toggleSeeMore()">
|
||||
<a [routerLink]="[]" (click)="toggleSeeMore()">
|
||||
<ng-container *ngIf="!see_more_active" i18n="See more">
|
||||
See more.
|
||||
</ng-container>
|
||||
|
||||
@@ -62,7 +62,7 @@ export class SkipAdButtonComponent implements OnInit {
|
||||
|
||||
getVideoIDHashFromURL(video_id) {
|
||||
if (!video_id) return null;
|
||||
return CryptoJS.SHA256(video_id).toString(CryptoJS.enc.Hex);;
|
||||
return CryptoJS.SHA256(video_id).toString(CryptoJS.enc.Hex);
|
||||
}
|
||||
|
||||
getVideoIDFromURL(url) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { Component, ElementRef, Input, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
@@ -6,7 +6,7 @@ import { PostsService } from 'app/posts.services';
|
||||
templateUrl: './twitch-chat.component.html',
|
||||
styleUrls: ['./twitch-chat.component.scss']
|
||||
})
|
||||
export class TwitchChatComponent implements OnInit, AfterViewInit {
|
||||
export class TwitchChatComponent implements OnInit, OnDestroy {
|
||||
|
||||
full_chat = null;
|
||||
visible_chat = null;
|
||||
@@ -33,7 +33,8 @@ export class TwitchChatComponent implements OnInit, AfterViewInit {
|
||||
this.getFullChat();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
ngOnDestroy(): void {
|
||||
if (this.chat_check_interval_obj) { clearInterval(this.chat_check_interval_obj); }
|
||||
}
|
||||
|
||||
private isUserNearBottom(): boolean {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { Schedule } from 'api-types';
|
||||
import { Schedule, Task } from 'api-types';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
@@ -18,7 +18,7 @@ export class UpdateTaskScheduleDialogComponent implements OnInit {
|
||||
date = null;
|
||||
today = new Date();
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: any, private dialogRef: MatDialogRef<UpdateTaskScheduleDialogComponent>, private postsService: PostsService) {
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: {task: Task}, private dialogRef: MatDialogRef<UpdateTaskScheduleDialogComponent>, private postsService: PostsService) {
|
||||
this.processTask(this.data.task);
|
||||
this.postsService.getTask(this.data.task.key).subscribe(res => {
|
||||
this.processTask(res['task']);
|
||||
@@ -28,7 +28,7 @@ export class UpdateTaskScheduleDialogComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
processTask(task) {
|
||||
processTask(task: Task): void {
|
||||
if (!task['schedule']) {
|
||||
this.enabled = false;
|
||||
return;
|
||||
@@ -39,7 +39,11 @@ export class UpdateTaskScheduleDialogComponent implements OnInit {
|
||||
this.recurring = schedule['type'] === Schedule.type.RECURRING;
|
||||
|
||||
if (this.recurring) {
|
||||
this.time = `${schedule['data']['hour']}:${schedule['data']['minute']}`;
|
||||
const hour = schedule['data']['hour'];
|
||||
const minute = schedule['data']['minute'];
|
||||
|
||||
// add padding 0s if necessary to hours and minutes
|
||||
this.time = (hour < 10 ? '0' : '') + hour + ':' + (minute < 10 ? '0' : '') + minute;
|
||||
|
||||
if (schedule['data']['dayOfWeek']) {
|
||||
this.days_of_week = schedule['data']['dayOfWeek'];
|
||||
@@ -75,7 +79,6 @@ export class UpdateTaskScheduleDialogComponent implements OnInit {
|
||||
}
|
||||
} else {
|
||||
this.date.setHours(hours, minutes);
|
||||
console.log(this.date);
|
||||
schedule['data'] = {timestamp: this.date.getTime()};
|
||||
}
|
||||
this.dialogRef.close(schedule);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Injectable, isDevMode, Inject} from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import { THEMES_CONFIG } from '../themes';
|
||||
|
||||
@@ -49,8 +49,10 @@ export class SubscriptionComponent implements OnInit, OnDestroy {
|
||||
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router, private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit() {
|
||||
if (this.route.snapshot.paramMap.get('id')) {
|
||||
this.id = this.route.snapshot.paramMap.get('id');
|
||||
this.route.params.subscribe(params => {
|
||||
this.id = params['id'];
|
||||
|
||||
if (this.sub_interval) { clearInterval(this.sub_interval); }
|
||||
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
@@ -59,7 +61,7 @@ export class SubscriptionComponent implements OnInit, OnDestroy {
|
||||
this.sub_interval = setInterval(() => this.getSubscription(true), 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// set filter property to cached
|
||||
const cached_filter_property = localStorage.getItem('filter_property');
|
||||
|
||||
@@ -20,44 +20,14 @@ import '@angular/localize/init';
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||
// import 'core-js/es6/symbol';
|
||||
// import 'core-js/es6/object';
|
||||
// import 'core-js/es6/function';
|
||||
// import 'core-js/es6/parse-int';
|
||||
// import 'core-js/es6/parse-float';
|
||||
// import 'core-js/es6/number';
|
||||
// import 'core-js/es6/math';
|
||||
// import 'core-js/es6/string';
|
||||
// import 'core-js/es6/date';
|
||||
// import 'core-js/es6/array';
|
||||
// import 'core-js/es6/regexp';
|
||||
// import 'core-js/es6/map';
|
||||
// import 'core-js/es6/weak-map';
|
||||
// import 'core-js/es6/set';
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
/** Evergreen browsers require these. **/
|
||||
// import 'core-js/es6/reflect';
|
||||
|
||||
|
||||
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
|
||||
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
*/ // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ __karma__.loaded = function () {};
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
}
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
|
||||
Reference in New Issue
Block a user