mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-11 07:10:56 +03:00
Compare commits
6 Commits
angular-up
...
docker-ubu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab5cd409bb | ||
|
|
e0509e8091 | ||
|
|
0c1568b38d | ||
|
|
f8a0d14968 | ||
|
|
f5894e6bc0 | ||
|
|
f205f8e58e |
18
.github/dependabot.yaml
vendored
18
.github/dependabot.yaml
vendored
@@ -1,18 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/.github/workflows"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/backend/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
38
.github/workflows/close-issue-if-noresponse.yml
vendored
38
.github/workflows/close-issue-if-noresponse.yml
vendored
@@ -1,38 +0,0 @@
|
|||||||
name: No Response
|
|
||||||
|
|
||||||
# Both `issue_comment` and `scheduled` event types are required for this Action
|
|
||||||
# to work properly.
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
schedule:
|
|
||||||
# Schedule for five minutes after the hour, every hour
|
|
||||||
- cron: '5 * * * *'
|
|
||||||
|
|
||||||
# By specifying the access of one of the scopes, all of those that are not
|
|
||||||
# specified are set to 'none'.
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
noResponse:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.repository == 'Tzahi12345/YoutubeDL-Material' }}
|
|
||||||
steps:
|
|
||||||
- uses: lee-dohm/no-response@v0.5.0
|
|
||||||
with:
|
|
||||||
token: ${{ github.token }}
|
|
||||||
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
|
||||||
closeComment: >
|
|
||||||
This issue has been automatically closed because there has been no response
|
|
||||||
to our request for more information from the original author. With only the
|
|
||||||
information that is currently in the issue, we don't have enough information
|
|
||||||
to take action. Please reach out if you have or find the answers we need so
|
|
||||||
that we can investigate further. We will re-open this issue if you provide us
|
|
||||||
with the requested information with a comment under this issue.
|
|
||||||
Thank you for your understanding and for trying to help make this application
|
|
||||||
a better one!
|
|
||||||
# Number of days of inactivity before an issue is closed for lack of response.
|
|
||||||
daysUntilClose: 21
|
|
||||||
# Label requiring a response.
|
|
||||||
responseRequiredLabel: "💬 response-needed"
|
|
||||||
43
.github/workflows/docker-release.yml
vendored
43
.github/workflows/docker-release.yml
vendored
@@ -6,25 +6,19 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
description: 'Docker tags'
|
description: 'Docker tags'
|
||||||
required: true
|
required: true
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: checkout code
|
- name: checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set hash
|
- name: Set hash
|
||||||
id: vars
|
id: vars
|
||||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||||
|
|
||||||
- name: create-json
|
- name: create-json
|
||||||
id: create-json
|
id: create-json
|
||||||
uses: jsdaniell/create-json@1.1.2
|
uses: jsdaniell/create-json@1.1.2
|
||||||
@@ -32,49 +26,15 @@ jobs:
|
|||||||
name: "version.json"
|
name: "version.json"
|
||||||
json: '{"type": "docker", "tag": "latest", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
|
json: '{"type": "docker", "tag": "latest", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
|
||||||
dir: 'backend/'
|
dir: 'backend/'
|
||||||
|
|
||||||
- name: Set image tag
|
|
||||||
id: tags
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.event.inputs.tags }}" != "" ]; then
|
|
||||||
echo "::set-output name=tags::${{ github.event.inputs.tags }}"
|
|
||||||
elif [ ${{ github.event.action }} == "release" ]; then
|
|
||||||
echo "::set-output name=tags::${{ github.event.release.tag_name }}"
|
|
||||||
else
|
|
||||||
echo "Unknown workflow trigger: ${{ github.event.action }}! Cannot determine default tag."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Generate Docker image metadata
|
|
||||||
id: docker-meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
|
|
||||||
ghcr.io/${{ github.repository_owner }}/${{ secrets.DOCKERHUB_REPO }}
|
|
||||||
tags: |
|
|
||||||
type=raw,value=${{ steps.tags.outputs.tags }}
|
|
||||||
type=raw,value=latest
|
|
||||||
|
|
||||||
- name: setup platform emulator
|
- name: setup platform emulator
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
- name: setup multi-arch docker build
|
- name: setup multi-arch docker build
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: build & push images
|
- name: build & push images
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -82,5 +42,4 @@ jobs:
|
|||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
tags: ${{ github.event.inputs.tags }}
|
||||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
|
||||||
|
|||||||
40
.github/workflows/docker.yml
vendored
40
.github/workflows/docker.yml
vendored
@@ -13,9 +13,6 @@ on:
|
|||||||
- '**.pem'
|
- '**.pem'
|
||||||
- '.dockerignore'
|
- '.dockerignore'
|
||||||
- '.gitignore'
|
- '.gitignore'
|
||||||
schedule:
|
|
||||||
- cron: '34 4 * * 2'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
@@ -23,15 +20,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: checkout code
|
- name: checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set hash
|
- name: Set hash
|
||||||
id: vars
|
id: vars
|
||||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||||
|
|
||||||
- name: create-json
|
- name: create-json
|
||||||
id: create-json
|
id: create-json
|
||||||
uses: jsdaniell/create-json@1.1.2
|
uses: jsdaniell/create-json@1.1.2
|
||||||
@@ -39,42 +33,15 @@ jobs:
|
|||||||
name: "version.json"
|
name: "version.json"
|
||||||
json: '{"type": "docker", "tag": "${{secrets.DOCKERHUB_MASTER_TAG}}", "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/'
|
dir: 'backend/'
|
||||||
|
|
||||||
- name: setup platform emulator
|
- name: setup platform emulator
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
- name: setup multi-arch docker build
|
- name: setup multi-arch docker build
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- name: Generate Docker image metadata
|
|
||||||
id: docker-meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
# Defaults:
|
|
||||||
# DOCKERHUB_USERNAME : tzahi12345
|
|
||||||
# DOCKERHUB_REPO : youtubedl-material
|
|
||||||
# DOCKERHUB_MASTER_TAG: nightly
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
|
|
||||||
ghcr.io/${{ github.repository_owner }}/${{ secrets.DOCKERHUB_REPO }}
|
|
||||||
tags: |
|
|
||||||
type=raw,${{secrets.DOCKERHUB_MASTER_TAG}}-{{ date 'YYYY-MM-DD' }}
|
|
||||||
type=raw,${{secrets.DOCKERHUB_MASTER_TAG}}
|
|
||||||
type=sha,prefix=sha-,format=short
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: build & push images
|
- name: build & push images
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -82,5 +49,8 @@ jobs:
|
|||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
# Defaults:
|
||||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
# DOCKERHUB_USERNAME : tzahi12345
|
||||||
|
# DOCKERHUB_REPO : youtubedl-material
|
||||||
|
# DOCKERHUB_MASTER_TAG: nightly
|
||||||
|
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:${{secrets.DOCKERHUB_MASTER_TAG}}
|
||||||
|
|||||||
114
Dockerfile
114
Dockerfile
@@ -1,69 +1,77 @@
|
|||||||
# Fetching our ffmpeg
|
|
||||||
FROM ubuntu:22.04 AS ffmpeg
|
FROM ubuntu:22.04 AS ffmpeg
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
# Use script due local build compability
|
|
||||||
COPY ffmpeg-fetch.sh .
|
|
||||||
RUN sh ./ffmpeg-fetch.sh
|
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y software-properties-common
|
||||||
|
RUN add-apt-repository ppa:savoury1/ffmpeg4
|
||||||
|
RUN add-apt-repository ppa:savoury1/ffmpeg5 && apt-get update && apt-get install -y ffmpeg
|
||||||
|
|
||||||
# Create our Ubuntu 22.04 with node 16
|
#--------------# Stage 2
|
||||||
# Go to 20.04
|
|
||||||
FROM ubuntu:20.04 AS base
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV UID=1000
|
|
||||||
ENV GID=1000
|
|
||||||
ENV USER=youtube
|
|
||||||
ENV NO_UPDATE_NOTIFIER=true
|
|
||||||
ENV PM2_HOME=/app/pm2
|
|
||||||
ENV ALLOW_CONFIG_MUTATIONS=true
|
|
||||||
RUN groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER && \
|
|
||||||
apt update && \
|
|
||||||
apt install -y --no-install-recommends curl ca-certificates tzdata && \
|
|
||||||
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
|
|
||||||
apt install -y --no-install-recommends nodejs && \
|
|
||||||
npm -g install npm && \
|
|
||||||
apt clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
|
FROM ubuntu:22.04 as frontend
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get -y install \
|
||||||
|
curl \
|
||||||
|
gnupg \
|
||||||
|
# Ubuntu 22.04 ships Node.JS 12 by default :)
|
||||||
|
nodejs \
|
||||||
|
# needed on 21.10 and before, maybe not on 22.04 YARN: brings along npm, solves dependency conflicts,
|
||||||
|
# spares us this spaghetti approach: https://stackoverflow.com/a/60547197
|
||||||
|
npm && \
|
||||||
|
apt-get install -f && \
|
||||||
|
npm config set strict-ssl false && \
|
||||||
|
npm install -g @angular/cli
|
||||||
|
|
||||||
# Build frontend
|
|
||||||
FROM base as frontend
|
|
||||||
RUN npm install -g @angular/cli
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY [ "package.json", "package-lock.json", "angular.json", "tsconfig.json", "/build/" ]
|
COPY [ "package.json", "package-lock.json", "/build/" ]
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY [ "angular.json", "tsconfig.json", "/build/" ]
|
||||||
COPY [ "src/", "/build/src/" ]
|
COPY [ "src/", "/build/src/" ]
|
||||||
RUN npm install && \
|
RUN npm run build
|
||||||
npm run build && \
|
|
||||||
ls -al /build/backend/public
|
|
||||||
|
|
||||||
|
#--------------# Final Stage
|
||||||
|
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
ENV UID=1000 \
|
||||||
|
GID=1000 \
|
||||||
|
USER=youtube \
|
||||||
|
NO_UPDATE_NOTIFIER=true
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN groupadd -g $GID $USER && useradd --system -g $USER --uid $UID $USER
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get -y install \
|
||||||
|
npm \
|
||||||
|
python2 \
|
||||||
|
python3 \
|
||||||
|
gosu \
|
||||||
|
atomicparsley \
|
||||||
|
--no-install-recommends && \
|
||||||
|
apt-get install -f && \
|
||||||
|
apt-get autoremove --purge && \
|
||||||
|
apt-get autoremove && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt
|
||||||
|
|
||||||
# Install backend deps
|
|
||||||
FROM base as backend
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY [ "backend/","/app/" ]
|
|
||||||
RUN npm config set strict-ssl false && \
|
|
||||||
npm install --prod && \
|
|
||||||
ls -al
|
|
||||||
|
|
||||||
|
|
||||||
# Final image
|
|
||||||
FROM base
|
|
||||||
RUN npm install -g pm2 && \
|
|
||||||
apt update && \
|
|
||||||
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley && \
|
|
||||||
apt clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
RUN pip install tcd
|
|
||||||
WORKDIR /app
|
|
||||||
# User 1000 already exist from base image
|
|
||||||
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffmpeg", "/usr/local/bin/ffmpeg" ]
|
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffmpeg", "/usr/local/bin/ffmpeg" ]
|
||||||
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffprobe", "/usr/local/bin/ffprobe" ]
|
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffprobe", "/usr/local/bin/ffprobe" ]
|
||||||
COPY --chown=$UID:$GID --from=backend ["/app/","/app/"]
|
COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ]
|
||||||
|
ENV PM2_HOME=/app/pm2
|
||||||
|
RUN npm config set strict-ssl false && \
|
||||||
|
npm install pm2 -g && \
|
||||||
|
npm install && chown -R $UID:$GID ./
|
||||||
|
|
||||||
|
# needed for ubuntu, see #596
|
||||||
|
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||||
|
|
||||||
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
||||||
RUN chmod +x /app/fix-scripts/*.sh
|
COPY --chown=$UID:$GID [ "/backend/", "/app/" ]
|
||||||
# Add some persistence data
|
|
||||||
#VOLUME ["/app/appdata"]
|
|
||||||
|
|
||||||
EXPOSE 17442
|
EXPOSE 17442
|
||||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
# ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||||
CMD [ "npm","start" ]
|
CMD [ "pm2-runtime", "pm2.config.js" ]
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
FROM tzahi12345/youtubedl-material:latest
|
FROM tzahi12345/youtubedl-material:nightly
|
||||||
CMD [ "npm", "start" ]
|
CMD [ "pm2-runtime", "pm2.config.js" ]
|
||||||
@@ -97,11 +97,6 @@ paths:
|
|||||||
summary: Get all files
|
summary: Get all files
|
||||||
description: Gets all files and playlists stored in the db
|
description: Gets all files and playlists stored in the db
|
||||||
operationId: get-getAllFiles
|
operationId: get-getAllFiles
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GetAllFilesRequest'
|
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
@@ -134,27 +129,6 @@ paths:
|
|||||||
description: User is not authorized to view the file.
|
description: User is not authorized to view the file.
|
||||||
security:
|
security:
|
||||||
- Auth query parameter: []
|
- Auth query parameter: []
|
||||||
/api/updateFile:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- files
|
|
||||||
summary: Updates file database object
|
|
||||||
description: Updates a file db object using its uid and a change object.
|
|
||||||
operationId: post-updateFile
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UpdateFileRequest'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: OK
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/SuccessObject'
|
|
||||||
security:
|
|
||||||
- Auth query parameter: []
|
|
||||||
/api/enableSharing:
|
/api/enableSharing:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -867,10 +841,17 @@ paths:
|
|||||||
- Auth query parameter: []
|
- Auth query parameter: []
|
||||||
tags:
|
tags:
|
||||||
- downloader
|
- downloader
|
||||||
/api/clearDownloads:
|
/api/clearFinishedDownloads:
|
||||||
post:
|
post:
|
||||||
summary: Clear multiple downloads
|
tags:
|
||||||
operationId: post-api-clear-downloads
|
- downloader
|
||||||
|
summary: Clear finished downloads
|
||||||
|
operationId: post-api-clear-finished-downloads
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
@@ -878,17 +859,8 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/SuccessObject'
|
$ref: '#/components/schemas/SuccessObject'
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ClearDownloadsRequest'
|
|
||||||
description: ''
|
|
||||||
description: "Clears multiple downloads based on a given filter."
|
|
||||||
security:
|
security:
|
||||||
- Auth query parameter: []
|
- Auth query parameter: []
|
||||||
tags:
|
|
||||||
- downloader
|
|
||||||
/api/getTask:
|
/api/getTask:
|
||||||
post:
|
post:
|
||||||
summary: Get info for one task
|
summary: Get info for one task
|
||||||
@@ -1535,8 +1507,6 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
error:
|
|
||||||
type: string
|
|
||||||
FileType:
|
FileType:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
@@ -1637,15 +1607,6 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Download'
|
$ref: '#/components/schemas/Download'
|
||||||
ClearDownloadsRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
clear_finished:
|
|
||||||
type: boolean
|
|
||||||
clear_paused:
|
|
||||||
type: boolean
|
|
||||||
clear_errors:
|
|
||||||
type: boolean
|
|
||||||
GetTaskRequest:
|
GetTaskRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -1729,41 +1690,6 @@ components:
|
|||||||
description: All video playlists
|
description: All video playlists
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Playlist'
|
$ref: '#/components/schemas/Playlist'
|
||||||
GetAllFilesRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
sort:
|
|
||||||
$ref: '#/components/schemas/Sort'
|
|
||||||
range:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: number
|
|
||||||
description: Two elements allowed, start index and end index
|
|
||||||
minItems: 2
|
|
||||||
maxItems: 2
|
|
||||||
text_search:
|
|
||||||
type: string
|
|
||||||
description: Filter files by title
|
|
||||||
file_type_filter:
|
|
||||||
$ref: '#/components/schemas/FileTypeFilter'
|
|
||||||
sub_id:
|
|
||||||
type: string
|
|
||||||
description: Include if you want to filter by subscription
|
|
||||||
Sort:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
by:
|
|
||||||
type: string
|
|
||||||
description: Property to sort by
|
|
||||||
order:
|
|
||||||
type: number
|
|
||||||
description: 1 for ascending, -1 for descending
|
|
||||||
FileTypeFilter:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- audio_only
|
|
||||||
- video_only
|
|
||||||
- both
|
|
||||||
GetAllFilesResponse:
|
GetAllFilesResponse:
|
||||||
required:
|
required:
|
||||||
- files
|
- files
|
||||||
@@ -1801,18 +1727,6 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
file:
|
file:
|
||||||
$ref: '#/components/schemas/DatabaseFile'
|
$ref: '#/components/schemas/DatabaseFile'
|
||||||
UpdateFileRequest:
|
|
||||||
required:
|
|
||||||
- uid
|
|
||||||
- change_obj
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
uid:
|
|
||||||
type: string
|
|
||||||
description: Video UID
|
|
||||||
change_obj:
|
|
||||||
type: object
|
|
||||||
description: Object with fields to update as keys and their new values
|
|
||||||
SharingToggle:
|
SharingToggle:
|
||||||
required:
|
required:
|
||||||
- uid
|
- uid
|
||||||
@@ -1826,6 +1740,7 @@ components:
|
|||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- url
|
- url
|
||||||
|
- streamingOnly
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
@@ -1938,6 +1853,7 @@ components:
|
|||||||
- uids
|
- uids
|
||||||
- playlistName
|
- playlistName
|
||||||
- thumbnailURL
|
- thumbnailURL
|
||||||
|
- type
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
playlistName:
|
playlistName:
|
||||||
@@ -1946,6 +1862,8 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/FileType'
|
||||||
thumbnailURL:
|
thumbnailURL:
|
||||||
type: string
|
type: string
|
||||||
CreatePlaylistResponse:
|
CreatePlaylistResponse:
|
||||||
@@ -1975,17 +1893,15 @@ components:
|
|||||||
required:
|
required:
|
||||||
- playlist
|
- playlist
|
||||||
- success
|
- success
|
||||||
|
- type
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
playlist:
|
playlist:
|
||||||
$ref: '#/components/schemas/Playlist'
|
$ref: '#/components/schemas/Playlist'
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/FileType'
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
file_objs:
|
|
||||||
type: array
|
|
||||||
description: File objects for every uid in the playlist's uids property, in the same order
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/DatabaseFile'
|
|
||||||
GetPlaylistsRequest:
|
GetPlaylistsRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -2010,10 +1926,13 @@ components:
|
|||||||
DeletePlaylistRequest:
|
DeletePlaylistRequest:
|
||||||
required:
|
required:
|
||||||
- playlist_id
|
- playlist_id
|
||||||
|
- type
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
playlist_id:
|
playlist_id:
|
||||||
type: string
|
type: string
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/FileType'
|
||||||
DownloadFileRequest:
|
DownloadFileRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -2234,6 +2153,7 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
result:
|
result:
|
||||||
allOf:
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/file'
|
||||||
- type: object
|
- type: object
|
||||||
properties:
|
properties:
|
||||||
formats:
|
formats:
|
||||||
@@ -2391,9 +2311,6 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
thumbnailURL:
|
thumbnailURL:
|
||||||
type: string
|
type: string
|
||||||
description: Backup if thumbnailPath is not defined
|
|
||||||
thumbnailPath:
|
|
||||||
type: string
|
|
||||||
isAudio:
|
isAudio:
|
||||||
type: boolean
|
type: boolean
|
||||||
duration:
|
duration:
|
||||||
@@ -2405,7 +2322,6 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
size:
|
size:
|
||||||
type: number
|
type: number
|
||||||
description: In bytes
|
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
upload_date:
|
upload_date:
|
||||||
@@ -2414,22 +2330,6 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
sharingEnabled:
|
sharingEnabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
category:
|
|
||||||
$ref: '#/components/schemas/Category'
|
|
||||||
view_count:
|
|
||||||
type: number
|
|
||||||
local_view_count:
|
|
||||||
type: number
|
|
||||||
sub_id:
|
|
||||||
type: string
|
|
||||||
registered:
|
|
||||||
type: number
|
|
||||||
height:
|
|
||||||
type: number
|
|
||||||
description: In pixels, only for videos
|
|
||||||
abr:
|
|
||||||
type: number
|
|
||||||
description: In Kbps
|
|
||||||
Playlist:
|
Playlist:
|
||||||
required:
|
required:
|
||||||
- uids
|
- uids
|
||||||
@@ -2459,8 +2359,6 @@ components:
|
|||||||
type: number
|
type: number
|
||||||
user_uid:
|
user_uid:
|
||||||
type: string
|
type: string
|
||||||
auto:
|
|
||||||
type: boolean
|
|
||||||
Download:
|
Download:
|
||||||
required:
|
required:
|
||||||
- url
|
- url
|
||||||
@@ -2511,8 +2409,6 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
sub_name:
|
sub_name:
|
||||||
type: string
|
type: string
|
||||||
prefetched_info:
|
|
||||||
type: object
|
|
||||||
Task:
|
Task:
|
||||||
required:
|
required:
|
||||||
- key
|
- key
|
||||||
@@ -2527,8 +2423,6 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
key:
|
key:
|
||||||
type: string
|
type: string
|
||||||
title:
|
|
||||||
type: string
|
|
||||||
last_ran:
|
last_ran:
|
||||||
type: number
|
type: number
|
||||||
last_confirmed:
|
last_confirmed:
|
||||||
@@ -2609,6 +2503,7 @@ components:
|
|||||||
- url
|
- url
|
||||||
- type
|
- type
|
||||||
- user_uid
|
- user_uid
|
||||||
|
- streamingOnly
|
||||||
- isPlaylist
|
- isPlaylist
|
||||||
- videos
|
- videos
|
||||||
type: object
|
type: object
|
||||||
@@ -2624,6 +2519,8 @@ components:
|
|||||||
user_uid:
|
user_uid:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
streamingOnly:
|
||||||
|
type: boolean
|
||||||
isPlaylist:
|
isPlaylist:
|
||||||
type: boolean
|
type: boolean
|
||||||
archive:
|
archive:
|
||||||
@@ -2648,6 +2545,28 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
passhash:
|
passhash:
|
||||||
type: string
|
type: string
|
||||||
|
files:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
audio:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/file'
|
||||||
|
video:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/file'
|
||||||
|
playlists:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
audio:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/file'
|
||||||
|
video:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/file'
|
||||||
subscriptions:
|
subscriptions:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -12,6 +12,16 @@ Now with [Docker](#Docker) support!
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
### USAGE OF THE NIGHTLY BUILDS IS HIGHLY RECOMMENDED.
|
||||||
|
|
||||||
|
For much better scaling with large datasets please run your YTDL-M instance with a MongoDB backend rather than the json file-based default.
|
||||||
|
It will fix a lot of performance problems (especially with datasets in the tens of thousands videos/audios)!
|
||||||
|
The (closed) issues as well as the project's Wiki will give you good starting points for your journey!
|
||||||
|
|
||||||
|
For MongoDB specifically there is [this little guide](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Setting-a-MongoDB-backend-to-use-as-database-provider-for-YTDL-M).
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Check out the prerequisites, and go to the installation section. Easy as pie!
|
Check out the prerequisites, and go to the installation section. Easy as pie!
|
||||||
@@ -48,7 +58,6 @@ sudo yum install nodejs youtube-dl ffmpeg ffmpeg-devel
|
|||||||
Optional dependencies:
|
Optional dependencies:
|
||||||
|
|
||||||
* AtomicParsley (for embedding thumbnails, package name `atomicparsley`)
|
* AtomicParsley (for embedding thumbnails, package name `atomicparsley`)
|
||||||
* [tcd](https://github.com/PetterKraabol/Twitch-Chat-Downloader) (for downloading Twitch VOD chats)
|
|
||||||
|
|
||||||
### Installing
|
### Installing
|
||||||
|
|
||||||
@@ -82,7 +91,7 @@ Alternatively, you can port forward the port specified in the config (defaults t
|
|||||||
|
|
||||||
### Host-specific instructions
|
### Host-specific instructions
|
||||||
|
|
||||||
If you're on a Synology NAS, unRAID, Raspberry Pi 4 or any other possible special case you can check if there's known issues or instructions both in the issue tracker and in the [Wiki!](https://github.com/Tzahi12345/YoutubeDL-Material/wiki#environment-specific-guideshelp)
|
If you're on a Synology NAS, unRAID or any other possible special case you can check if there's known issues or instructions both in the issue tracker and in the [Wiki!](https://github.com/Tzahi12345/YoutubeDL-Material/wiki#environment-specific-guideshelp)
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
@@ -93,6 +102,8 @@ If you are looking to setup YoutubeDL-Material with Docker, this section is for
|
|||||||
3. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 17443" or something similar. This tells you the *container-internal* port of the application. Please check your `docker-compose.yml` file for the *external* port. If you downloaded the file as described above, it defaults to **8998**.
|
3. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 17443" or something similar. This tells you the *container-internal* port of the application. Please check your `docker-compose.yml` file for the *external* port. If you downloaded the file as described above, it defaults to **8998**.
|
||||||
4. Make sure you can connect to the specified URL + *external* port, and if so, you are done!
|
4. Make sure you can connect to the specified URL + *external* port, and if so, you are done!
|
||||||
|
|
||||||
|
NOTE: It is currently recommended that you use the `nightly` tag on Docker. To do so, simply update the docker-compose.yml `image` field so that it points to `tzahi12345/youtubedl-material:nightly`.
|
||||||
|
|
||||||
### Custom UID/GID
|
### Custom UID/GID
|
||||||
|
|
||||||
By default, the Docker container runs as non-root with UID=1000 and GID=1000. To set this to your own UID/GID, simply update the `environment` section in your `docker-compose.yml` like so:
|
By default, the Docker container runs as non-root with UID=1000 and GID=1000. To set this to your own UID/GID, simply update the `environment` section in your `docker-compose.yml` like so:
|
||||||
@@ -103,12 +114,6 @@ environment:
|
|||||||
GID: YOUR_GID
|
GID: YOUR_GID
|
||||||
```
|
```
|
||||||
|
|
||||||
## MongoDB
|
|
||||||
|
|
||||||
For much better scaling with large datasets please run your YoutubeDL-Material instance with MongoDB backend rather than the json file-based default. It will fix a lot of performance problems (especially with datasets in the tens of thousands videos/audios)!
|
|
||||||
|
|
||||||
[Tutorial](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Setting-a-MongoDB-backend-to-use-as-database-provider-for-YTDL-M).
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
[API Docs](https://youtubedl-material.stoplight.io/docs/youtubedl-material/Public%20API%20v1.yaml)
|
[API Docs](https://youtubedl-material.stoplight.io/docs/youtubedl-material/Public%20API%20v1.yaml)
|
||||||
|
|||||||
18
SECURITY.md
18
SECURITY.md
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
If you would like to see the latest updates, use the `nightly` tag on Docker.
|
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.
|
||||||
|
|
||||||
If you'd like to stick with more stable releases, use the `latest` tag on Docker or download the [latest release here](https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest).
|
| Version | Supported |
|
||||||
|
| ------------- | ------------------ |
|
||||||
| Version | Supported |
|
| 4.2 Nightlies | :white_check_mark: |
|
||||||
| -------------------- | ------------------ |
|
| 4.2 Release | :x: |
|
||||||
| 4.3 Docker Nightlies | :white_check_mark: |
|
| < 4.2 | :x: |
|
||||||
| 4.3 Release | :white_check_mark: |
|
|
||||||
| 4.2 Release | :x: |
|
|
||||||
| < 4.2 | :x: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|||||||
10
angular.json
10
angular.json
@@ -30,8 +30,7 @@
|
|||||||
"src/backend"
|
"src/backend"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss"
|
||||||
"src/bootstrap.min.css"
|
|
||||||
],
|
],
|
||||||
"scripts": [],
|
"scripts": [],
|
||||||
"vendorChunk": true,
|
"vendorChunk": true,
|
||||||
@@ -119,8 +118,7 @@
|
|||||||
"src/backend"
|
"src/backend"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss"
|
||||||
"src/bootstrap.min.css"
|
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
@@ -153,8 +151,7 @@
|
|||||||
"tsConfig": "src/tsconfig.spec.json",
|
"tsConfig": "src/tsconfig.spec.json",
|
||||||
"scripts": [],
|
"scripts": [],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss"
|
||||||
"src/bootstrap.min.css"
|
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/assets",
|
"src/assets",
|
||||||
@@ -182,6 +179,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"defaultProject": "youtube-dl-material",
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@schematics/angular:component": {
|
"@schematics/angular:component": {
|
||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
|
|||||||
110
backend/app.js
110
backend/app.js
@@ -68,8 +68,7 @@ db.defaults(
|
|||||||
configWriteFlag: false,
|
configWriteFlag: false,
|
||||||
downloads: {},
|
downloads: {},
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
files_to_db_migration_complete: false,
|
files_to_db_migration_complete: false
|
||||||
tasks_manager_role_migration_complete: false
|
|
||||||
}).write();
|
}).write();
|
||||||
|
|
||||||
users_db.defaults(
|
users_db.defaults(
|
||||||
@@ -102,6 +101,7 @@ let backendPort = null;
|
|||||||
let useDefaultDownloadingAgent = null;
|
let useDefaultDownloadingAgent = null;
|
||||||
let customDownloadingAgent = null;
|
let customDownloadingAgent = null;
|
||||||
let allowSubscriptions = null;
|
let allowSubscriptions = null;
|
||||||
|
let archivePath = path.join(__dirname, 'appdata', 'archives');
|
||||||
|
|
||||||
// other needed values
|
// other needed values
|
||||||
let url_domain = null;
|
let url_domain = null;
|
||||||
@@ -148,11 +148,16 @@ if (fs.existsSync('version.json')) {
|
|||||||
|
|
||||||
// don't overwrite config if it already happened.. NOT
|
// don't overwrite config if it already happened.. NOT
|
||||||
// let alreadyWritten = db.get('configWriteFlag').value();
|
// let alreadyWritten = db.get('configWriteFlag').value();
|
||||||
|
let writeConfigMode = process.env.write_ytdl_config;
|
||||||
|
|
||||||
// checks if config exists, if not, a config is auto generated
|
// checks if config exists, if not, a config is auto generated
|
||||||
config_api.configExistsCheck();
|
config_api.configExistsCheck();
|
||||||
|
|
||||||
setAndLoadConfig();
|
if (writeConfigMode) {
|
||||||
|
setAndLoadConfig();
|
||||||
|
} else {
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
@@ -183,22 +188,13 @@ async function checkMigrations() {
|
|||||||
if (!new_db_system_migration_complete) {
|
if (!new_db_system_migration_complete) {
|
||||||
logger.info('Beginning migration: 4.2->4.3+')
|
logger.info('Beginning migration: 4.2->4.3+')
|
||||||
let success = await db_api.importJSONToDB(db.value(), users_db.value());
|
let success = await db_api.importJSONToDB(db.value(), users_db.value());
|
||||||
await tasks_api.setupTasks(); // necessary as tasks were not properly initialized at first
|
|
||||||
// sets migration to complete
|
// sets migration to complete
|
||||||
db.set('new_db_system_migration_complete', true).write();
|
db.set('new_db_system_migration_complete', true).write();
|
||||||
if (success) { logger.info('4.2->4.3+ migration complete!'); }
|
if (success) { logger.info('4.2->4.3+ migration complete!'); }
|
||||||
else { logger.error('Migration failed: 4.2->4.3+'); }
|
else { logger.error('Migration failed: 4.2->4.3+'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks_manager_role_migration_complete = db.get('tasks_manager_role_migration_complete').value();
|
|
||||||
if (!tasks_manager_role_migration_complete) {
|
|
||||||
logger.info('Checking if tasks manager role permissions exist for admin user...');
|
|
||||||
const success = await auth_api.changeRolePermissions('admin', 'tasks_manager', 'yes');
|
|
||||||
if (success) logger.info('Task manager permissions check complete!');
|
|
||||||
else logger.error('Failed to auto add tasks manager permissions to admin role!');
|
|
||||||
db.set('tasks_manager_role_migration_complete', true).write();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,9 +484,8 @@ async function setAndLoadConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setConfigFromEnv() {
|
async function setConfigFromEnv() {
|
||||||
const config_items = getEnvConfigItems();
|
let config_items = getEnvConfigItems();
|
||||||
if (!config_items || config_items.length === 0) return true;
|
let success = config_api.setConfigItems(config_items);
|
||||||
const success = config_api.setConfigItems(config_items);
|
|
||||||
if (success) {
|
if (success) {
|
||||||
logger.info('Config items set using ENV variables.');
|
logger.info('Config items set using ENV variables.');
|
||||||
await utils.wait(100);
|
await utils.wait(100);
|
||||||
@@ -505,13 +500,12 @@ async function loadConfig() {
|
|||||||
loadConfigValues();
|
loadConfigValues();
|
||||||
|
|
||||||
// connect to DB
|
// connect to DB
|
||||||
if (!config_api.getConfigItem('ytdl_use_local_db'))
|
await db_api.connectToDB();
|
||||||
await db_api.connectToDB();
|
|
||||||
db_api.database_initialized = true;
|
db_api.database_initialized = true;
|
||||||
db_api.database_initialized_bs.next(true);
|
db_api.database_initialized_bs.next(true);
|
||||||
|
|
||||||
// creates archive path if missing
|
// creates archive path if missing
|
||||||
await fs.ensureDir(utils.getArchiveFolder());
|
await fs.ensureDir(archivePath);
|
||||||
|
|
||||||
// check migrations
|
// check migrations
|
||||||
await checkMigrations();
|
await checkMigrations();
|
||||||
@@ -581,11 +575,7 @@ async function watchSubscriptions() {
|
|||||||
|
|
||||||
if (!subscriptions) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
// auto pause deprecated streamingOnly mode
|
const valid_subscriptions = subscriptions.filter(sub => !sub.paused);
|
||||||
const streaming_only_subs = subscriptions.filter(sub => sub.streamingOnly);
|
|
||||||
subscriptions_api.updateSubscriptionPropertyMultiple(streaming_only_subs, {paused: true});
|
|
||||||
|
|
||||||
const valid_subscriptions = subscriptions.filter(sub => !sub.paused && !sub.streamingOnly);
|
|
||||||
|
|
||||||
let subscriptions_amount = valid_subscriptions.length;
|
let subscriptions_amount = valid_subscriptions.length;
|
||||||
let delay_interval = calculateSubcriptionRetrievalDelay(subscriptions_amount);
|
let delay_interval = calculateSubcriptionRetrievalDelay(subscriptions_amount);
|
||||||
@@ -922,11 +912,11 @@ app.post('/api/getFile', optionalJwt, async function (req, res) {
|
|||||||
app.post('/api/getAllFiles', optionalJwt, async function (req, res) {
|
app.post('/api/getAllFiles', optionalJwt, async function (req, res) {
|
||||||
// these are returned
|
// these are returned
|
||||||
let files = null;
|
let files = null;
|
||||||
const sort = req.body.sort;
|
let playlists = null;
|
||||||
const range = req.body.range;
|
let sort = req.body.sort;
|
||||||
const text_search = req.body.text_search;
|
let range = req.body.range;
|
||||||
const file_type_filter = req.body.file_type_filter;
|
let text_search = req.body.text_search;
|
||||||
const sub_id = req.body.sub_id;
|
let file_type_filter = req.body.file_type_filter;
|
||||||
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
const filter_obj = {user_uid: uuid};
|
const filter_obj = {user_uid: uuid};
|
||||||
@@ -939,42 +929,27 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sub_id) {
|
|
||||||
filter_obj['sub_id'] = sub_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file_type_filter === 'audio_only') filter_obj['isAudio'] = true;
|
if (file_type_filter === 'audio_only') filter_obj['isAudio'] = true;
|
||||||
else if (file_type_filter === 'video_only') filter_obj['isAudio'] = false;
|
else if (file_type_filter === 'video_only') filter_obj['isAudio'] = false;
|
||||||
|
|
||||||
files = await db_api.getRecords('files', filter_obj, false, sort, range, text_search);
|
files = await db_api.getRecords('files', filter_obj, false, sort, range, text_search);
|
||||||
const file_count = await db_api.getRecords('files', filter_obj, true);
|
let file_count = await db_api.getRecords('files', filter_obj, true);
|
||||||
|
playlists = await db_api.getRecords('playlists', {user_uid: uuid});
|
||||||
|
|
||||||
|
const categories = await categories_api.getCategoriesAsPlaylists(files);
|
||||||
|
if (categories) {
|
||||||
|
playlists = playlists.concat(categories);
|
||||||
|
}
|
||||||
|
|
||||||
files = JSON.parse(JSON.stringify(files));
|
files = JSON.parse(JSON.stringify(files));
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
files: files,
|
files: files,
|
||||||
file_count: file_count,
|
file_count: file_count,
|
||||||
|
playlists: playlists
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/updateFile', optionalJwt, async function (req, res) {
|
|
||||||
const uid = req.body.uid;
|
|
||||||
const change_obj = req.body.change_obj;
|
|
||||||
|
|
||||||
const file = await db_api.updateRecord('files', {uid: uid}, change_obj);
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
res.send({
|
|
||||||
success: false,
|
|
||||||
error: 'File could not be found'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.send({
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/api/checkConcurrentStream', async (req, res) => {
|
app.post('/api/checkConcurrentStream', async (req, res) => {
|
||||||
const uid = req.body.uid;
|
const uid = req.body.uid;
|
||||||
|
|
||||||
@@ -1282,7 +1257,7 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
|||||||
subscription = JSON.parse(JSON.stringify(subscription));
|
subscription = JSON.parse(JSON.stringify(subscription));
|
||||||
|
|
||||||
// get sub videos
|
// get sub videos
|
||||||
if (subscription.name) {
|
if (subscription.name && !subscription.streamingOnly) {
|
||||||
var parsed_files = await db_api.getRecords('files', {sub_id: subscription.id}); // subscription.videos;
|
var parsed_files = await db_api.getRecords('files', {sub_id: subscription.id}); // subscription.videos;
|
||||||
subscription['videos'] = parsed_files;
|
subscription['videos'] = parsed_files;
|
||||||
// loop through files for extra processing
|
// loop through files for extra processing
|
||||||
@@ -1292,6 +1267,19 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
|||||||
if (file && file['url'].includes('twitch.tv')) file['chat_exists'] = fs.existsSync(file['path'].substring(0, file['path'].length - 4) + '.twitch_chat.json');
|
if (file && file['url'].includes('twitch.tv')) file['chat_exists'] = fs.existsSync(file['path'].substring(0, file['path'].length - 4) + '.twitch_chat.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
subscription: subscription,
|
||||||
|
files: parsed_files
|
||||||
|
});
|
||||||
|
} else if (subscription.name && subscription.streamingOnly) {
|
||||||
|
// return list of videos
|
||||||
|
let parsed_files = [];
|
||||||
|
if (subscription.videos) {
|
||||||
|
for (let i = 0; i < subscription.videos.length; i++) {
|
||||||
|
const video = subscription.videos[i];
|
||||||
|
parsed_files.push(new utils.File(video.title, video.title, video.thumbnail, false, video.duration, video.url, video.uploader, video.size, null, null, video.upload_date, video.view_count, video.height, video.abr));
|
||||||
|
}
|
||||||
|
}
|
||||||
res.send({
|
res.send({
|
||||||
subscription: subscription,
|
subscription: subscription,
|
||||||
files: parsed_files
|
files: parsed_files
|
||||||
@@ -1336,8 +1324,9 @@ app.post('/api/getSubscriptions', optionalJwt, async (req, res) => {
|
|||||||
app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
|
app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlistName = req.body.playlistName;
|
let playlistName = req.body.playlistName;
|
||||||
let uids = req.body.uids;
|
let uids = req.body.uids;
|
||||||
|
let type = req.body.type;
|
||||||
|
|
||||||
const new_playlist = await db_api.createPlaylist(playlistName, uids, req.isAuthenticated() ? req.user.uid : null);
|
const new_playlist = await db_api.createPlaylist(playlistName, uids, type, req.isAuthenticated() ? req.user.uid : null);
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
new_playlist: new_playlist,
|
new_playlist: new_playlist,
|
||||||
@@ -1365,6 +1354,7 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
|
|||||||
res.send({
|
res.send({
|
||||||
playlist: playlist,
|
playlist: playlist,
|
||||||
file_objs: file_objs,
|
file_objs: file_objs,
|
||||||
|
type: playlist && playlist.type,
|
||||||
success: !!playlist
|
success: !!playlist
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1375,7 +1365,7 @@ app.post('/api/getPlaylists', optionalJwt, async (req, res) => {
|
|||||||
|
|
||||||
let playlists = await db_api.getRecords('playlists', {user_uid: uuid});
|
let playlists = await db_api.getRecords('playlists', {user_uid: uuid});
|
||||||
if (include_categories) {
|
if (include_categories) {
|
||||||
const categories = await categories_api.getCategoriesAsPlaylists();
|
const categories = await categories_api.getCategoriesAsPlaylists(files);
|
||||||
if (categories) {
|
if (categories) {
|
||||||
playlists = playlists.concat(categories);
|
playlists = playlists.concat(categories);
|
||||||
}
|
}
|
||||||
@@ -1679,15 +1669,9 @@ app.post('/api/download', optionalJwt, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/clearDownloads', optionalJwt, async (req, res) => {
|
app.post('/api/clearFinishedDownloads', optionalJwt, async (req, res) => {
|
||||||
const user_uid = req.isAuthenticated() ? req.user.uid : null;
|
const user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
const clear_finished = req.body.clear_finished;
|
const success = db_api.removeAllRecords('download_queue', {finished: true, user_uid: user_uid});
|
||||||
const clear_paused = req.body.clear_paused;
|
|
||||||
const clear_errors = req.body.clear_errors;
|
|
||||||
let success = true;
|
|
||||||
if (clear_finished) success &= await db_api.removeAllRecords('download_queue', {finished: true, user_uid: user_uid});
|
|
||||||
if (clear_paused) success &= await db_api.removeAllRecords('download_queue', {paused: true, user_uid: user_uid});
|
|
||||||
if (clear_errors) success &= await db_api.removeAllRecords('download_queue', {error: {$ne: null}, user_uid: user_uid});
|
|
||||||
res.send({success: success});
|
res.send({success: success});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,7 @@
|
|||||||
"use_youtube_API": false,
|
"use_youtube_API": false,
|
||||||
"youtube_API_key": "",
|
"youtube_API_key": "",
|
||||||
"use_twitch_API": false,
|
"use_twitch_API": false,
|
||||||
"twitch_client_ID": "",
|
"twitch_API_key": "",
|
||||||
"twitch_client_secret": "",
|
|
||||||
"twitch_auto_download_chat": false,
|
"twitch_auto_download_chat": false,
|
||||||
"use_sponsorblock_API": false,
|
"use_sponsorblock_API": false,
|
||||||
"generate_NFO_files": false
|
"generate_NFO_files": false
|
||||||
@@ -64,7 +63,7 @@
|
|||||||
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
||||||
},
|
},
|
||||||
"Advanced": {
|
"Advanced": {
|
||||||
"default_downloader": "yt-dlp",
|
"default_downloader": "youtube-dl",
|
||||||
"use_default_downloading_agent": true,
|
"use_default_downloading_agent": true,
|
||||||
"custom_downloading_agent": "",
|
"custom_downloading_agent": "",
|
||||||
"multi_user_mode": false,
|
"multi_user_mode": false,
|
||||||
|
|||||||
@@ -171,12 +171,8 @@ exports.registerUser = async function(req, res) {
|
|||||||
|
|
||||||
|
|
||||||
exports.login = async (username, password) => {
|
exports.login = async (username, password) => {
|
||||||
// even if we're using LDAP, we still want users to be able to login using internal credentials
|
|
||||||
const user = await db_api.getRecord('users', {name: username});
|
const user = await db_api.getRecord('users', {name: username});
|
||||||
if (!user) {
|
if (!user) { logger.error(`User ${username} not found`); return false }
|
||||||
if (config_api.getConfigItem('ytdl_auth_method') === 'internal') logger.error(`User ${username} not found`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (user.auth_method && user.auth_method !== 'internal') { return false }
|
if (user.auth_method && user.auth_method !== 'internal') { return false }
|
||||||
return await bcrypt.compare(password, user.passhash) ? user : false;
|
return await bcrypt.compare(password, user.passhash) ? user : false;
|
||||||
}
|
}
|
||||||
@@ -361,6 +357,7 @@ exports.userHasPermission = async function(user_uid, permission) {
|
|||||||
logger.error('Invalid role ' + role);
|
logger.error('Invalid role ' + role);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const role_permissions = (await db_api.getRecords('roles'))['permissions'];
|
||||||
|
|
||||||
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
||||||
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
||||||
@@ -375,8 +372,7 @@ exports.userHasPermission = async function(user_uid, permission) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no overrides, let's check if the role has the permission
|
// no overrides, let's check if the role has the permission
|
||||||
const role_has_permission = await exports.roleHasPermissions(role, permission);
|
if (role_permissions.includes(permission)) {
|
||||||
if (role_has_permission) {
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
|
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
|
||||||
@@ -384,16 +380,6 @@ exports.userHasPermission = async function(user_uid, permission) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.roleHasPermissions = async function(role, permission) {
|
|
||||||
const role_obj = await db_api.getRecord('roles', {key: role})
|
|
||||||
if (!role) {
|
|
||||||
logger.error(`Role ${role} does not exist!`);
|
|
||||||
}
|
|
||||||
const role_permissions = role_obj['permissions'];
|
|
||||||
if (role_permissions && role_permissions.includes(permission)) return true;
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.userPermissions = async function(user_uid) {
|
exports.userPermissions = async function(user_uid) {
|
||||||
let user_permissions = [];
|
let user_permissions = [];
|
||||||
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
|
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
|
||||||
|
|||||||
@@ -55,18 +55,17 @@ async function getCategories() {
|
|||||||
return categories ? categories : null;
|
return categories ? categories : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCategoriesAsPlaylists() {
|
async function getCategoriesAsPlaylists(files = null) {
|
||||||
const categories_as_playlists = [];
|
const categories_as_playlists = [];
|
||||||
const available_categories = await getCategories();
|
const available_categories = await getCategories();
|
||||||
if (available_categories) {
|
if (available_categories && files) {
|
||||||
for (let category of available_categories) {
|
for (let category of available_categories) {
|
||||||
const files_that_match = await db_api.getRecords('files', {'category.uid': category['uid']});
|
const files_that_match = utils.addUIDsToCategory(category, files);
|
||||||
if (files_that_match && files_that_match.length > 0) {
|
if (files_that_match && files_that_match.length > 0) {
|
||||||
category['thumbnailURL'] = files_that_match[0].thumbnailURL;
|
category['thumbnailURL'] = files_that_match[0].thumbnailURL;
|
||||||
category['thumbnailPath'] = files_that_match[0].thumbnailPath;
|
category['thumbnailPath'] = files_that_match[0].thumbnailPath;
|
||||||
category['duration'] = files_that_match.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
|
category['duration'] = files_that_match.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
|
||||||
category['id'] = category['uid'];
|
category['id'] = category['uid'];
|
||||||
category['auto'] = true;
|
|
||||||
categories_as_playlists.push(category);
|
categories_as_playlists.push(category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ function setConfigItem(key, value) {
|
|||||||
success = setConfigFile(config_json);
|
success = setConfigFile(config_json);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
};
|
||||||
|
|
||||||
function setConfigItems(items) {
|
function setConfigItems(items) {
|
||||||
let success = false;
|
let success = false;
|
||||||
@@ -206,8 +206,7 @@ const DEFAULT_CONFIG = {
|
|||||||
"use_youtube_API": false,
|
"use_youtube_API": false,
|
||||||
"youtube_API_key": "",
|
"youtube_API_key": "",
|
||||||
"use_twitch_API": false,
|
"use_twitch_API": false,
|
||||||
"twitch_client_ID": "",
|
"twitch_API_key": "",
|
||||||
"twitch_client_secret": "",
|
|
||||||
"twitch_auto_download_chat": false,
|
"twitch_auto_download_chat": false,
|
||||||
"use_sponsorblock_API": false,
|
"use_sponsorblock_API": false,
|
||||||
"generate_NFO_files": false
|
"generate_NFO_files": false
|
||||||
@@ -239,7 +238,7 @@ const DEFAULT_CONFIG = {
|
|||||||
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
||||||
},
|
},
|
||||||
"Advanced": {
|
"Advanced": {
|
||||||
"default_downloader": "yt-dlp",
|
"default_downloader": "youtube-dl",
|
||||||
"use_default_downloading_agent": true,
|
"use_default_downloading_agent": true,
|
||||||
"custom_downloading_agent": "",
|
"custom_downloading_agent": "",
|
||||||
"multi_user_mode": false,
|
"multi_user_mode": false,
|
||||||
|
|||||||
@@ -102,13 +102,9 @@ exports.CONFIG_ITEMS = {
|
|||||||
'key': 'ytdl_use_twitch_api',
|
'key': 'ytdl_use_twitch_api',
|
||||||
'path': 'YoutubeDLMaterial.API.use_twitch_API'
|
'path': 'YoutubeDLMaterial.API.use_twitch_API'
|
||||||
},
|
},
|
||||||
'ytdl_twitch_client_id': {
|
'ytdl_twitch_api_key': {
|
||||||
'key': 'ytdl_twitch_client_id',
|
'key': 'ytdl_twitch_api_key',
|
||||||
'path': 'YoutubeDLMaterial.API.twitch_client_ID'
|
'path': 'YoutubeDLMaterial.API.twitch_API_key'
|
||||||
},
|
|
||||||
'ytdl_twitch_client_secret': {
|
|
||||||
'key': 'ytdl_twitch_client_secret',
|
|
||||||
'path': 'YoutubeDLMaterial.API.twitch_client_secret'
|
|
||||||
},
|
},
|
||||||
'ytdl_twitch_auto_download_chat': {
|
'ytdl_twitch_auto_download_chat': {
|
||||||
'key': 'ytdl_twitch_auto_download_chat',
|
'key': 'ytdl_twitch_auto_download_chat',
|
||||||
@@ -221,8 +217,7 @@ exports.AVAILABLE_PERMISSIONS = [
|
|||||||
'subscriptions',
|
'subscriptions',
|
||||||
'sharing',
|
'sharing',
|
||||||
'advanced_download',
|
'advanced_download',
|
||||||
'downloads_manager',
|
'downloads_manager'
|
||||||
'tasks_manager'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
exports.DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
|
exports.DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
|
||||||
@@ -306,4 +301,4 @@ const YTDL_ARGS_WITH_VALUES = [
|
|||||||
// we're using a Set here for performance
|
// we're using a Set here for performance
|
||||||
exports.YTDL_ARGS_WITH_VALUES = new Set(YTDL_ARGS_WITH_VALUES);
|
exports.YTDL_ARGS_WITH_VALUES = new Set(YTDL_ARGS_WITH_VALUES);
|
||||||
|
|
||||||
exports.CURRENT_VERSION = 'v4.3';
|
exports.CURRENT_VERSION = 'v4.2';
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ async function registerFileDBManual(file_object) {
|
|||||||
path_object = path.parse(file_object['path']);
|
path_object = path.parse(file_object['path']);
|
||||||
file_object['path'] = path.format(path_object);
|
file_object['path'] = path.format(path_object);
|
||||||
|
|
||||||
await exports.insertRecordIntoTable('files', file_object, {path: file_object['path']})
|
exports.insertRecordIntoTable('files', file_object, {path: file_object['path']})
|
||||||
|
|
||||||
return file_object;
|
return file_object;
|
||||||
}
|
}
|
||||||
@@ -357,7 +357,7 @@ exports.addMetadataPropertyToDB = async (property_key) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createPlaylist = async (playlist_name, uids, user_uid = null) => {
|
exports.createPlaylist = async (playlist_name, uids, type, user_uid = null) => {
|
||||||
const first_video = await exports.getVideo(uids[0]);
|
const first_video = await exports.getVideo(uids[0]);
|
||||||
const thumbnailToUse = first_video['thumbnailURL'];
|
const thumbnailToUse = first_video['thumbnailURL'];
|
||||||
|
|
||||||
@@ -366,6 +366,7 @@ exports.createPlaylist = async (playlist_name, uids, user_uid = null) => {
|
|||||||
uids: uids,
|
uids: uids,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
thumbnailURL: thumbnailToUse,
|
thumbnailURL: thumbnailToUse,
|
||||||
|
type: type,
|
||||||
registered: Date.now(),
|
registered: Date.now(),
|
||||||
randomize_order: false
|
randomize_order: false
|
||||||
};
|
};
|
||||||
@@ -386,9 +387,9 @@ exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = fal
|
|||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
playlist = await exports.getRecord('categories', {uid: playlist_id});
|
playlist = await exports.getRecord('categories', {uid: playlist_id});
|
||||||
if (playlist) {
|
if (playlist) {
|
||||||
const uids = (await exports.getRecords('files', {'category.uid': playlist_id})).map(file => file.uid);
|
// category found
|
||||||
playlist['uids'] = uids;
|
const files = await exports.getFiles(user_uid);
|
||||||
playlist['auto'] = true;
|
utils.addUIDsToCategory(playlist, files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +495,8 @@ exports.deleteFile = async (uid, uuid = null, blacklistMode = false) => {
|
|||||||
|
|
||||||
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||||
if (useYoutubeDLArchive) {
|
if (useYoutubeDLArchive) {
|
||||||
const archive_path = utils.getArchiveFolder(type, uuid);
|
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||||
|
const archive_path = uuid ? path.join(usersFileFolder, uuid, 'archives', `archive_${type}.txt`) : path.join('appdata', 'archives', `archive_${type}.txt`);
|
||||||
|
|
||||||
// get ID from JSON
|
// get ID from JSON
|
||||||
|
|
||||||
@@ -502,8 +504,14 @@ exports.deleteFile = async (uid, uuid = null, blacklistMode = false) => {
|
|||||||
let id = null;
|
let id = null;
|
||||||
if (jsonobj) id = jsonobj.id;
|
if (jsonobj) id = jsonobj.id;
|
||||||
|
|
||||||
// Remove file ID from the archive file, and write it to the blacklist (if enabled)
|
// use subscriptions API to remove video from the archive file, and write it to the blacklist
|
||||||
await utils.deleteFileFromArchive(uid, type, archive_path, id, blacklistMode);
|
if (await fs.pathExists(archive_path)) {
|
||||||
|
const line = id ? await utils.removeIDFromArchive(archive_path, id) : null;
|
||||||
|
if (blacklistMode && line) await writeToBlacklist(type, line);
|
||||||
|
} else {
|
||||||
|
logger.info('Could not find archive file for audio files. Creating...');
|
||||||
|
await fs.close(await fs.open(archive_path, 'w'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonExists) await fs.unlink(jsonPath);
|
if (jsonExists) await fs.unlink(jsonPath);
|
||||||
@@ -621,7 +629,7 @@ exports.bulkInsertRecordsIntoTable = async (table, docs) => {
|
|||||||
exports.getRecord = async (table, filter_obj) => {
|
exports.getRecord = async (table, filter_obj) => {
|
||||||
// local db override
|
// local db override
|
||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
return exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'find').value();
|
return applyFilterLocalDB(local_db.get(table), filter_obj, 'find').value();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await database.collection(table).findOne(filter_obj);
|
return await database.collection(table).findOne(filter_obj);
|
||||||
@@ -630,7 +638,7 @@ exports.getRecord = async (table, filter_obj) => {
|
|||||||
exports.getRecords = async (table, filter_obj = null, return_count = false, sort = null, range = null) => {
|
exports.getRecords = async (table, filter_obj = null, return_count = false, sort = null, range = null) => {
|
||||||
// local db override
|
// local db override
|
||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
let cursor = filter_obj ? exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'filter').value() : local_db.get(table).value();
|
let cursor = filter_obj ? applyFilterLocalDB(local_db.get(table), filter_obj, 'filter').value() : local_db.get(table).value();
|
||||||
if (sort) {
|
if (sort) {
|
||||||
cursor = cursor.sort((a, b) => (a[sort['by']] > b[sort['by']] ? sort['order'] : sort['order']*-1));
|
cursor = cursor.sort((a, b) => (a[sort['by']] > b[sort['by']] ? sort['order'] : sort['order']*-1));
|
||||||
}
|
}
|
||||||
@@ -656,7 +664,7 @@ exports.getRecords = async (table, filter_obj = null, return_count = false, sort
|
|||||||
exports.updateRecord = async (table, filter_obj, update_obj) => {
|
exports.updateRecord = async (table, filter_obj, update_obj) => {
|
||||||
// local db override
|
// local db override
|
||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'find').assign(update_obj).write();
|
applyFilterLocalDB(local_db.get(table), filter_obj, 'find').assign(update_obj).write();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,7 +677,7 @@ exports.updateRecord = async (table, filter_obj, update_obj) => {
|
|||||||
exports.updateRecords = async (table, filter_obj, update_obj) => {
|
exports.updateRecords = async (table, filter_obj, update_obj) => {
|
||||||
// local db override
|
// local db override
|
||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'filter').assign(update_obj).write();
|
applyFilterLocalDB(local_db.get(table), filter_obj, 'filter').assign(update_obj).write();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,7 +722,7 @@ exports.bulkUpdateRecords = async (table, key_label, update_obj) => {
|
|||||||
exports.pushToRecordsArray = async (table, filter_obj, key, value) => {
|
exports.pushToRecordsArray = async (table, filter_obj, key, value) => {
|
||||||
// local db override
|
// local db override
|
||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'find').get(key).push(value).write();
|
applyFilterLocalDB(local_db.get(table), filter_obj, 'find').get(key).push(value).write();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,7 +733,7 @@ exports.pushToRecordsArray = async (table, filter_obj, key, value) => {
|
|||||||
exports.pullFromRecordsArray = async (table, filter_obj, key, value) => {
|
exports.pullFromRecordsArray = async (table, filter_obj, key, value) => {
|
||||||
// local db override
|
// local db override
|
||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'find').get(key).pull(value).write();
|
applyFilterLocalDB(local_db.get(table), filter_obj, 'find').get(key).pull(value).write();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -738,7 +746,7 @@ exports.pullFromRecordsArray = async (table, filter_obj, key, value) => {
|
|||||||
exports.removeRecord = async (table, filter_obj) => {
|
exports.removeRecord = async (table, filter_obj) => {
|
||||||
// local db override
|
// local db override
|
||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
|
applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,7 +757,7 @@ exports.removeRecord = async (table, filter_obj) => {
|
|||||||
// exports.removeRecordsByUIDBulk = async (table, uids) => {
|
// exports.removeRecordsByUIDBulk = async (table, uids) => {
|
||||||
// // local db override
|
// // local db override
|
||||||
// if (using_local_db) {
|
// if (using_local_db) {
|
||||||
// exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
|
// applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
|
||||||
// return true;
|
// return true;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -813,7 +821,7 @@ exports.removeAllRecords = async (table = null, filter_obj = null) => {
|
|||||||
if (using_local_db) {
|
if (using_local_db) {
|
||||||
for (let i = 0; i < tables_to_remove.length; i++) {
|
for (let i = 0; i < tables_to_remove.length; i++) {
|
||||||
const table_to_remove = tables_to_remove[i];
|
const table_to_remove = tables_to_remove[i];
|
||||||
if (filter_obj) exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
|
if (filter_obj) applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
|
||||||
else local_db.assign({[table_to_remove]: []}).write();
|
else local_db.assign({[table_to_remove]: []}).write();
|
||||||
logger.debug(`Successfully removed records from ${table_to_remove}`);
|
logger.debug(`Successfully removed records from ${table_to_remove}`);
|
||||||
}
|
}
|
||||||
@@ -925,7 +933,6 @@ exports.importJSONToDB = async (db_json, users_json) => {
|
|||||||
const createFilesRecords = (files, subscriptions) => {
|
const createFilesRecords = (files, subscriptions) => {
|
||||||
for (let i = 0; i < subscriptions.length; i++) {
|
for (let i = 0; i < subscriptions.length; i++) {
|
||||||
const subscription = subscriptions[i];
|
const subscription = subscriptions[i];
|
||||||
if (!subscription['videos']) continue;
|
|
||||||
subscription['videos'] = subscription['videos'].map(file => ({ ...file, sub_id: subscription['id'], user_uid: subscription['user_uid'] ? subscription['user_uid'] : undefined}));
|
subscription['videos'] = subscription['videos'].map(file => ({ ...file, sub_id: subscription['id'], user_uid: subscription['user_uid'] ? subscription['user_uid'] : undefined}));
|
||||||
files = files.concat(subscriptions[i]['videos']);
|
files = files.concat(subscriptions[i]['videos']);
|
||||||
}
|
}
|
||||||
@@ -986,7 +993,7 @@ exports.backupDB = async () => {
|
|||||||
const backup_file_name = `${using_local_db ? 'local' : 'remote'}_db.json.${Date.now()/1000}.bak`;
|
const backup_file_name = `${using_local_db ? 'local' : 'remote'}_db.json.${Date.now()/1000}.bak`;
|
||||||
const path_to_backups = path.join(backup_dir, backup_file_name);
|
const path_to_backups = path.join(backup_dir, backup_file_name);
|
||||||
|
|
||||||
logger.info(`Backing up ${using_local_db ? 'local' : 'remote'} DB to ${path_to_backups}`);
|
logger.verbose(`Backing up ${using_local_db ? 'local' : 'remote'} DB to ${path_to_backups}`);
|
||||||
|
|
||||||
const table_to_records = {};
|
const table_to_records = {};
|
||||||
for (let i = 0; i < tables_list.length; i++) {
|
for (let i = 0; i < tables_list.length; i++) {
|
||||||
@@ -1033,11 +1040,10 @@ exports.transferDB = async (local_to_remote) => {
|
|||||||
table_to_records[table] = await exports.getRecords(table);
|
table_to_records[table] = await exports.getRecords(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Backup up DB...');
|
|
||||||
await exports.backupDB(); // should backup always
|
|
||||||
|
|
||||||
using_local_db = !local_to_remote;
|
using_local_db = !local_to_remote;
|
||||||
if (local_to_remote) {
|
if (local_to_remote) {
|
||||||
|
logger.debug('Backup up DB...');
|
||||||
|
await exports.backupDB();
|
||||||
const db_connected = await exports.connectToDB(5, true);
|
const db_connected = await exports.connectToDB(5, true);
|
||||||
if (!db_connected) {
|
if (!db_connected) {
|
||||||
logger.error('Failed to transfer database - could not connect to MongoDB. Verify that your connection URL is valid.');
|
logger.error('Failed to transfer database - could not connect to MongoDB. Verify that your connection URL is valid.');
|
||||||
@@ -1069,13 +1075,8 @@ exports.transferDB = async (local_to_remote) => {
|
|||||||
This function is necessary to emulate mongodb's ability to search for null or missing values.
|
This function is necessary to emulate mongodb's ability to search for null or missing values.
|
||||||
A filter of null or undefined for a property will find docs that have that property missing, or have it
|
A filter of null or undefined for a property will find docs that have that property missing, or have it
|
||||||
null or undefined. We want that same functionality for the local DB as well
|
null or undefined. We want that same functionality for the local DB as well
|
||||||
|
|
||||||
error: {$ne: null}
|
|
||||||
^ ^
|
|
||||||
| |
|
|
||||||
filter_prop filter_prop_value
|
|
||||||
*/
|
*/
|
||||||
exports.applyFilterLocalDB = (db_path, filter_obj, operation) => {
|
const applyFilterLocalDB = (db_path, filter_obj, operation) => {
|
||||||
const filter_props = Object.keys(filter_obj);
|
const filter_props = Object.keys(filter_obj);
|
||||||
const return_val = db_path[operation](record => {
|
const return_val = db_path[operation](record => {
|
||||||
if (!filter_props) return true;
|
if (!filter_props) return true;
|
||||||
@@ -1084,20 +1085,14 @@ exports.applyFilterLocalDB = (db_path, filter_obj, operation) => {
|
|||||||
const filter_prop = filter_props[i];
|
const filter_prop = filter_props[i];
|
||||||
const filter_prop_value = filter_obj[filter_prop];
|
const filter_prop_value = filter_obj[filter_prop];
|
||||||
if (filter_prop_value === undefined || filter_prop_value === null) {
|
if (filter_prop_value === undefined || filter_prop_value === null) {
|
||||||
filtered &= record[filter_prop] === undefined || record[filter_prop] === null;
|
filtered &= record[filter_prop] === undefined || record[filter_prop] === null
|
||||||
} else {
|
} else {
|
||||||
if (typeof filter_prop_value === 'object') {
|
if (typeof filter_prop_value === 'object') {
|
||||||
if ('$regex' in filter_prop_value) {
|
if (filter_prop_value['$regex']) {
|
||||||
filtered &= (record[filter_prop].search(new RegExp(filter_prop_value['$regex'], filter_prop_value['$options'])) !== -1);
|
filtered &= (record[filter_prop].search(new RegExp(filter_prop_value['$regex'], filter_prop_value['$options'])) !== -1);
|
||||||
} else if ('$ne' in filter_prop_value) {
|
|
||||||
filtered &= filter_prop in record && record[filter_prop] !== filter_prop_value['$ne'];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// handle case of nested property check
|
filtered &= record[filter_prop] === filter_prop_value;
|
||||||
if (filter_prop.includes('.'))
|
|
||||||
filtered &= utils.searchObjectByString(record, filter_prop) === filter_prop_value;
|
|
||||||
else
|
|
||||||
filtered &= record[filter_prop] === filter_prop_value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1105,3 +1100,15 @@ exports.applyFilterLocalDB = (db_path, filter_obj, operation) => {
|
|||||||
});
|
});
|
||||||
return return_val;
|
return return_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// archive helper functions
|
||||||
|
|
||||||
|
async function writeToBlacklist(type, line) {
|
||||||
|
const archivePath = path.join(__dirname, 'appdata', 'archives');
|
||||||
|
let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
|
||||||
|
// adds newline to the beginning of the line
|
||||||
|
line.replace('\n', '');
|
||||||
|
line.replace('\r', '');
|
||||||
|
line = '\n' + line;
|
||||||
|
await fs.appendFile(blacklistPath, line);
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ const db_api = require('./db');
|
|||||||
const mutex = new Mutex();
|
const mutex = new Mutex();
|
||||||
let should_check_downloads = true;
|
let should_check_downloads = true;
|
||||||
|
|
||||||
|
const archivePath = path.join(__dirname, 'appdata', 'archives');
|
||||||
|
|
||||||
if (db_api.database_initialized) {
|
if (db_api.database_initialized) {
|
||||||
setupDownloads();
|
setupDownloads();
|
||||||
} else {
|
} else {
|
||||||
@@ -26,7 +28,7 @@ if (db_api.database_initialized) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createDownload = async (url, type, options, user_uid = null, sub_id = null, sub_name = null, prefetched_info = null) => {
|
exports.createDownload = async (url, type, options, user_uid = null, sub_id = null, sub_name = null) => {
|
||||||
return await mutex.runExclusive(async () => {
|
return await mutex.runExclusive(async () => {
|
||||||
const download = {
|
const download = {
|
||||||
url: url,
|
url: url,
|
||||||
@@ -35,7 +37,6 @@ exports.createDownload = async (url, type, options, user_uid = null, sub_id = nu
|
|||||||
user_uid: user_uid,
|
user_uid: user_uid,
|
||||||
sub_id: sub_id,
|
sub_id: sub_id,
|
||||||
sub_name: sub_name,
|
sub_name: sub_name,
|
||||||
prefetched_info: prefetched_info,
|
|
||||||
options: options,
|
options: options,
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
step_index: 0,
|
step_index: 0,
|
||||||
@@ -107,7 +108,6 @@ exports.clearDownload = async (download_uid) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownloadError(download_uid, error_message) {
|
async function handleDownloadError(download_uid, error_message) {
|
||||||
if (!download_uid) return;
|
|
||||||
await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error_message, finished: true, running: false});
|
await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error_message, finished: true, running: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ async function collectInfo(download_uid) {
|
|||||||
let args = await exports.generateArgs(url, type, options, download['user_uid']);
|
let args = await exports.generateArgs(url, type, options, download['user_uid']);
|
||||||
|
|
||||||
// get video info prior to download
|
// get video info prior to download
|
||||||
let info = download['prefetched_info'] ? download['prefetched_info'] : await exports.getVideoInfoByURL(url, args, download_uid);
|
let info = await getVideoInfoByURL(url, args, download_uid);
|
||||||
|
|
||||||
if (!info) {
|
if (!info) {
|
||||||
// info failed, error presumably already recorded
|
// info failed, error presumably already recorded
|
||||||
@@ -203,12 +203,9 @@ async function collectInfo(download_uid) {
|
|||||||
options.customOutput = category['custom_output'];
|
options.customOutput = category['custom_output'];
|
||||||
options.noRelativePath = true;
|
options.noRelativePath = true;
|
||||||
args = await exports.generateArgs(url, type, options, download['user_uid']);
|
args = await exports.generateArgs(url, type, options, download['user_uid']);
|
||||||
args = utils.filterArgs(args, ['--no-simulate']);
|
info = await getVideoInfoByURL(url, args, download_uid);
|
||||||
info = await exports.getVideoInfoByURL(url, args, download_uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
download['category'] = category;
|
|
||||||
|
|
||||||
// setup info required to calculate download progress
|
// setup info required to calculate download progress
|
||||||
|
|
||||||
const expected_file_size = utils.getExpectedFileSize(info);
|
const expected_file_size = utils.getExpectedFileSize(info);
|
||||||
@@ -229,8 +226,7 @@ async function collectInfo(download_uid) {
|
|||||||
options: options,
|
options: options,
|
||||||
files_to_check_for_progress: files_to_check_for_progress,
|
files_to_check_for_progress: files_to_check_for_progress,
|
||||||
expected_file_size: expected_file_size,
|
expected_file_size: expected_file_size,
|
||||||
title: playlist_title ? playlist_title : info['title'],
|
title: playlist_title ? playlist_title : info['title']
|
||||||
prefetched_info: null
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +239,6 @@ async function downloadQueuedFile(download_uid) {
|
|||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path');
|
const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path');
|
||||||
const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path');
|
const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path');
|
||||||
const usersFolderPath = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 2, finished_step: false, running: true});
|
await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 2, finished_step: false, running: true});
|
||||||
|
|
||||||
const url = download['url'];
|
const url = download['url'];
|
||||||
@@ -251,11 +246,9 @@ async function downloadQueuedFile(download_uid) {
|
|||||||
const options = download['options'];
|
const options = download['options'];
|
||||||
const args = download['args'];
|
const args = download['args'];
|
||||||
const category = download['category'];
|
const category = download['category'];
|
||||||
let fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath;
|
let fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath; // TODO: fix
|
||||||
if (options.customFileFolderPath) {
|
if (options.customFileFolderPath) {
|
||||||
fileFolderPath = options.customFileFolderPath;
|
fileFolderPath = options.customFileFolderPath;
|
||||||
} else if (download['user_uid']) {
|
|
||||||
fileFolderPath = path.join(usersFolderPath, download['user_uid'], type);
|
|
||||||
}
|
}
|
||||||
fs.ensureDirSync(fileFolderPath);
|
fs.ensureDirSync(fileFolderPath);
|
||||||
|
|
||||||
@@ -357,7 +350,7 @@ async function downloadQueuedFile(download_uid) {
|
|||||||
if (file_objs.length > 1) {
|
if (file_objs.length > 1) {
|
||||||
// create playlist
|
// create playlist
|
||||||
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
|
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
|
||||||
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), download['user_uid']);
|
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, download['user_uid']);
|
||||||
} else if (file_objs.length === 1) {
|
} else if (file_objs.length === 1) {
|
||||||
container = file_objs[0];
|
container = file_objs[0];
|
||||||
} else {
|
} else {
|
||||||
@@ -377,23 +370,15 @@ async function downloadQueuedFile(download_uid) {
|
|||||||
// helper functions
|
// helper functions
|
||||||
|
|
||||||
exports.generateArgs = async (url, type, options, user_uid = null, simulated = false) => {
|
exports.generateArgs = async (url, type, options, user_uid = null, simulated = false) => {
|
||||||
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
|
|
||||||
|
|
||||||
const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path');
|
const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path');
|
||||||
const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path');
|
const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path');
|
||||||
const usersFolderPath = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
|
|
||||||
const videopath = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
|
const videopath = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
|
||||||
const globalArgs = config_api.getConfigItem('ytdl_custom_args');
|
const globalArgs = config_api.getConfigItem('ytdl_custom_args');
|
||||||
const useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
const useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
||||||
const is_audio = type === 'audio';
|
const is_audio = type === 'audio';
|
||||||
|
|
||||||
let fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath; // TODO: fix
|
let fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
|
||||||
if (options.customFileFolderPath) {
|
|
||||||
fileFolderPath = options.customFileFolderPath;
|
|
||||||
} else if (user_uid) {
|
|
||||||
fileFolderPath = path.join(usersFolderPath, user_uid, fileFolderPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.customFileFolderPath) fileFolderPath = options.customFileFolderPath;
|
if (options.customFileFolderPath) fileFolderPath = options.customFileFolderPath;
|
||||||
|
|
||||||
@@ -403,8 +388,6 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
|
|||||||
|
|
||||||
// video-specific args
|
// video-specific args
|
||||||
const selectedHeight = options.selectedHeight;
|
const selectedHeight = options.selectedHeight;
|
||||||
const maxHeight = options.maxHeight;
|
|
||||||
const heightParam = selectedHeight || maxHeight;
|
|
||||||
|
|
||||||
// audio-specific args
|
// audio-specific args
|
||||||
const maxBitrate = options.maxBitrate;
|
const maxBitrate = options.maxBitrate;
|
||||||
@@ -418,6 +401,8 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
|
|||||||
if (!is_audio && !is_youtube) {
|
if (!is_audio && !is_youtube) {
|
||||||
// tiktok videos fail when using the default format
|
// tiktok videos fail when using the default format
|
||||||
qualityPath = null;
|
qualityPath = null;
|
||||||
|
} else if (!is_audio && !is_youtube && (url.includes('reddit') || url.includes('pornhub'))) {
|
||||||
|
qualityPath = ['-f', 'bestvideo+bestaudio']
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customArgs) {
|
if (customArgs) {
|
||||||
@@ -425,8 +410,8 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
|
|||||||
} else {
|
} else {
|
||||||
if (customQualityConfiguration) {
|
if (customQualityConfiguration) {
|
||||||
qualityPath = ['-f', customQualityConfiguration, '--merge-output-format', 'mp4'];
|
qualityPath = ['-f', customQualityConfiguration, '--merge-output-format', 'mp4'];
|
||||||
} else if (heightParam && heightParam !== '' && !is_audio) {
|
} else if (selectedHeight && selectedHeight !== '' && !is_audio) {
|
||||||
qualityPath = ['-f', `'(mp4)[height${maxHeight ? '<' : ''}=${heightParam}]`];
|
qualityPath = ['-f', `'(mp4)[height=${selectedHeight}'`];
|
||||||
} else if (is_audio) {
|
} else if (is_audio) {
|
||||||
qualityPath = ['--audio-quality', maxBitrate ? maxBitrate : '0']
|
qualityPath = ['--audio-quality', maxBitrate ? maxBitrate : '0']
|
||||||
}
|
}
|
||||||
@@ -508,11 +493,9 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
|
|||||||
downloadConfig.push('-r', rate_limit);
|
downloadConfig.push('-r', rate_limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
|
||||||
if (default_downloader === 'yt-dlp') {
|
if (default_downloader === 'yt-dlp') {
|
||||||
downloadConfig = utils.filterArgs(downloadConfig, ['--print-json']);
|
downloadConfig.push('--no-clean-infojson');
|
||||||
|
|
||||||
// in yt-dlp -j --no-simulate is preferable
|
|
||||||
downloadConfig.push('--no-clean-info-json', '-j', '--no-simulate');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -520,11 +503,11 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
|
|||||||
// filter out incompatible args
|
// filter out incompatible args
|
||||||
downloadConfig = filterArgs(downloadConfig, is_audio);
|
downloadConfig = filterArgs(downloadConfig, is_audio);
|
||||||
|
|
||||||
if (!simulated) logger.verbose(`${default_downloader} args being used: ${downloadConfig.join(',')}`);
|
if (!simulated) logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`);
|
||||||
return downloadConfig;
|
return downloadConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getVideoInfoByURL = async (url, args = [], download_uid = null) => {
|
async function getVideoInfoByURL(url, args = [], download_uid = null) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// remove bad args
|
// remove bad args
|
||||||
const new_args = [...args];
|
const new_args = [...args];
|
||||||
@@ -579,7 +562,8 @@ exports.getVideoInfoByURL = async (url, args = [], download_uid = null) => {
|
|||||||
function filterArgs(args, isAudio) {
|
function filterArgs(args, isAudio) {
|
||||||
const video_only_args = ['--add-metadata', '--embed-subs', '--xattrs'];
|
const video_only_args = ['--add-metadata', '--embed-subs', '--xattrs'];
|
||||||
const audio_only_args = ['-x', '--extract-audio', '--embed-thumbnail'];
|
const audio_only_args = ['-x', '--extract-audio', '--embed-thumbnail'];
|
||||||
return utils.filterArgs(args, isAudio ? video_only_args : audio_only_args);
|
const args_to_remove = isAudio ? video_only_args : audio_only_args;
|
||||||
|
return args.filter(x => !args_to_remove.includes(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkDownloadPercent(download_uid) {
|
async function checkDownloadPercent(download_uid) {
|
||||||
@@ -641,6 +625,6 @@ function getArchiveFolder(fileFolderPath, options, user_uid) {
|
|||||||
} else if (user_uid) {
|
} else if (user_uid) {
|
||||||
return path.join(fileFolderPath, 'archives');
|
return path.join(fileFolderPath, 'archives');
|
||||||
} else {
|
} else {
|
||||||
return path.join('appdata', 'archives');
|
return path.join(archivePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
CMD="npm start"
|
CMD="pm2-runtime pm2.config.js"
|
||||||
|
|
||||||
# if the first arg starts with "-" pass it to program
|
# if the first arg starts with "-" pass it to program
|
||||||
if [ "${1#-}" != "$1" ]; then
|
if [ "${1#-}" != "$1" ]; then
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
|
|
||||||
# INTERACTIVE PERMISSIONS FIX SCRIPT FOR YTDL-M
|
# INTERACTIVE PERMISSIONS FIX SCRIPT FOR YTDL-M
|
||||||
# Date: 2022-05-03
|
# Date: 2022-05-03
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
# If you want to run this script on a bare-metal installation instead of within Docker
|
# If you want to run this script on a bare-metal installation instead of within Docker
|
||||||
# make sure that the paths configured below match your paths! (it's wise to use the full paths)
|
# make sure that the paths configured below match your paths! (it's wise to use the full paths)
|
||||||
# USAGE: within your container's bash shell:
|
# USAGE: within your container's bash shell:
|
||||||
# ./fix-scripts/<name of fix-script>
|
# chmod -R +x ./fix-scripts/
|
||||||
|
# ./fix-scripts/001-fix_download_permissions.sh
|
||||||
|
|
||||||
# User defines / Docker env defaults
|
# User defines / Docker env defaults
|
||||||
PATH_SUBS=/app/subscriptions
|
PATH_SUBS=/app/subscriptions
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# INTERACTIVE ARCHIVE-DUPE-ENTRY FIX SCRIPT FOR YTDL-M
|
|
||||||
# Date: 2022-05-09
|
|
||||||
|
|
||||||
# If you want to run this script on a bare-metal installation instead of within Docker
|
|
||||||
# make sure that the paths configured below match your paths! (it's wise to use the full paths)
|
|
||||||
# USAGE: within your container's bash shell:
|
|
||||||
# ./fix-scripts/<name of fix-script>
|
|
||||||
|
|
||||||
# User defines (NO TRAILING SLASHES) / Docker env defaults
|
|
||||||
PATH_SUBSARCHIVE=/app/subscriptions/archives
|
|
||||||
PATH_ONEOFFARCHIVE=/app/appdata/archives
|
|
||||||
|
|
||||||
# Backup paths (substitute with your personal preference if you like)
|
|
||||||
PATH_SUBSARCHIVEBKP=$PATH_SUBSARCHIVE-BKP-$(date +%Y%m%d%H%M%S)
|
|
||||||
PATH_ONEOFFARCHIVEBKP=$PATH_ONEOFFARCHIVE-BKP-$(date +%Y%m%d%H%M%S)
|
|
||||||
|
|
||||||
|
|
||||||
# Define Colors for TUI
|
|
||||||
yellow=$(tput setaf 3)
|
|
||||||
normal=$(tput sgr0)
|
|
||||||
|
|
||||||
tput civis # hide the cursor
|
|
||||||
|
|
||||||
clear -x
|
|
||||||
printf "\n"
|
|
||||||
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - # horizontal line
|
|
||||||
printf "Welcome to the INTERACTIVE ARCHIVE-DUPE-ENTRY FIX SCRIPT FOR YTDL-M."
|
|
||||||
printf "\nThis script will cycle through the archive files in the folders mentioned"
|
|
||||||
printf "\nbelow and remove within each archive the dupe entries. (compact them)"
|
|
||||||
printf "\nDuring some older builds of YTDL-M the archives could receive dupe"
|
|
||||||
printf "\nentries and blow up in size, sometimes causing conflicts with download management."
|
|
||||||
printf '\n%*s' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - # horizontal line
|
|
||||||
printf "\n"
|
|
||||||
|
|
||||||
# check whether dirs exist
|
|
||||||
i=0
|
|
||||||
[ -d $PATH_SUBSARCHIVE ] && i=$((i+1)) && printf "\n✔ (${i}/2) Found Subscriptions archive directory at ${PATH_SUBSARCHIVE}"
|
|
||||||
[ -d $PATH_ONEOFFARCHIVE ] && i=$((i+1)) && printf "\n✔ (${i}/2) Found one-off archive directory at ${PATH_ONEOFFARCHIVE}"
|
|
||||||
|
|
||||||
# Ask to proceed or cancel, exit on missing paths
|
|
||||||
case $i in
|
|
||||||
0)
|
|
||||||
printf "\n\n Couldn't find any archive location path! \n\nPlease edit this script to configure!"
|
|
||||||
tput cnorm
|
|
||||||
exit 2;;
|
|
||||||
2)
|
|
||||||
printf "\n\n Found all archive locations. \n\nProceed? (Y/N)";;
|
|
||||||
*)
|
|
||||||
printf "\n\n Only found ${i} out of 2 archive locations! Something about this script's config must be wrong. \n\nProceed anyways? (Y/N)";;
|
|
||||||
esac
|
|
||||||
old_stty_cfg=$(stty -g)
|
|
||||||
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
|
|
||||||
if echo "$answer" | grep -iq "^y" ;then
|
|
||||||
printf "\n\nRunning jobs now... (this may take a while)\n"
|
|
||||||
|
|
||||||
printf "\nBacking up directories...\n"
|
|
||||||
|
|
||||||
chars="⣾⣽⣻⢿⡿⣟⣯⣷"
|
|
||||||
cp -R $PATH_SUBSARCHIVE $PATH_SUBSARCHIVEBKP &
|
|
||||||
PID=$!
|
|
||||||
i=1
|
|
||||||
echo -n ' '
|
|
||||||
while [ -d /proc/$PID ]
|
|
||||||
do
|
|
||||||
printf "${yellow}\b${chars:i++%${#chars}:1}${normal}"
|
|
||||||
sleep 0.15
|
|
||||||
done
|
|
||||||
[ -d $PATH_SUBSARCHIVEBKP ] && printf "\r✔ Backed up ${PATH_SUBSARCHIVE} to ${PATH_SUBSARCHIVEBKP} ($(du -sh $PATH_SUBSARCHIVEBKP | cut -f1))\n"
|
|
||||||
|
|
||||||
cp -R $PATH_ONEOFFARCHIVE $PATH_ONEOFFARCHIVEBKP &
|
|
||||||
PID2=$!
|
|
||||||
i=1
|
|
||||||
echo -n ' '
|
|
||||||
while [ -d /proc/$PID2 ]
|
|
||||||
do
|
|
||||||
printf "${yellow}\b${chars:i++%${#chars}:1}${normal}"
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
[ -d $PATH_ONEOFFARCHIVEBKP ] && printf "\r✔ Backed up ${PATH_ONEOFFARCHIVE} to ${PATH_ONEOFFARCHIVEBKP} ($(du -sh $PATH_ONEOFFARCHIVEBKP | cut -f1))\n"
|
|
||||||
|
|
||||||
|
|
||||||
printf "\nCompacting files...\n"
|
|
||||||
|
|
||||||
tmpfile=$(mktemp) &&
|
|
||||||
|
|
||||||
[ -d $PATH_SUBSARCHIVE ] &&
|
|
||||||
find $PATH_SUBSARCHIVE -name '*.txt' -print0 | while read -d $'\0' file # Set delimiter to null because we want to catch all possible filenames (WE CANNOT CHANGE IFS HERE) - https://stackoverflow.com/a/15931055
|
|
||||||
do
|
|
||||||
cp "$file" "$tmpfile"
|
|
||||||
{ awk '!x[$0]++' "$tmpfile" > "$file"; } & # https://unix.stackexchange.com/questions/159695/how-does-awk-a0-work
|
|
||||||
PID3=$!
|
|
||||||
i=1
|
|
||||||
echo -n ''
|
|
||||||
while [ -d /proc/$PID3 ]
|
|
||||||
do
|
|
||||||
printf "${yellow}\b${chars:i++%${#chars}:1}${normal}"
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
BEFORE=$(wc -l < $tmpfile)
|
|
||||||
AFTER=$(wc -l < $file)
|
|
||||||
if [[ "$AFTER" -ne "$BEFORE" ]]; then
|
|
||||||
printf "\b✔ Compacted down to ${AFTER} lines from ${BEFORE}: ${file}\n"
|
|
||||||
else
|
|
||||||
printf "\bℹ No action needed for file: ${file}\n"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
[ -d $PATH_ONEOFFARCHIVE ] &&
|
|
||||||
find $PATH_ONEOFFARCHIVE -name '*.txt' -print0 | while read -d $'\0' file
|
|
||||||
do
|
|
||||||
cp "$file" "$tmpfile" &
|
|
||||||
awk '!x[$0]++' "$tmpfile" > "$file" &
|
|
||||||
PID4=$!
|
|
||||||
i=1
|
|
||||||
echo -n ''
|
|
||||||
while [ -d /proc/$PID4 ]
|
|
||||||
do
|
|
||||||
printf "${yellow}\b${chars:i++%${#chars}:1}${normal}"
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
BEFORE=$(wc -l < $tmpfile)
|
|
||||||
AFTER=$(wc -l < $file)
|
|
||||||
if [ "$BEFORE" -ne "$AFTER" ]; then
|
|
||||||
printf "\b✔ Compacted down to ${AFTER} lines from ${BEFORE}: ${file}\n"
|
|
||||||
else
|
|
||||||
printf "\bℹ No action ran for file: ${file}\n"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
tput cnorm # show the cursor
|
|
||||||
rm "$tmpfile"
|
|
||||||
|
|
||||||
printf "\n\n✔ Done."
|
|
||||||
printf "\nℹ Please keep in mind that you may still want to"
|
|
||||||
printf "\n run corruption checks against your archives!\n\n"
|
|
||||||
exit
|
|
||||||
else
|
|
||||||
tput cnorm
|
|
||||||
printf "\nOkay, bye.\n\n"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
1825
backend/package-lock.json
generated
1825
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,20 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "pm2-runtime --raw pm2.config.js",
|
"start": "nodemon app.js",
|
||||||
"debug": "set YTDL_MODE=debug && node app.js"
|
"debug": "set YTDL_MODE=debug && node app.js"
|
||||||
},
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ignore": [
|
||||||
|
"*.js",
|
||||||
|
"appdata/*",
|
||||||
|
"public/*"
|
||||||
|
],
|
||||||
|
"watch": [
|
||||||
|
"restart_update.json",
|
||||||
|
"restart_general.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": ""
|
"url": ""
|
||||||
@@ -36,10 +47,11 @@
|
|||||||
"mocha": "^9.2.2",
|
"mocha": "^9.2.2",
|
||||||
"moment": "^2.29.2",
|
"moment": "^2.29.2",
|
||||||
"mongodb": "^3.6.9",
|
"mongodb": "^3.6.9",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "^1.4.2",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"node-id3": "^0.1.14",
|
"node-id3": "^0.1.14",
|
||||||
"node-schedule": "^2.1.0",
|
"node-schedule": "^2.1.0",
|
||||||
|
"nodemon": "^2.0.7",
|
||||||
"passport": "^0.4.1",
|
"passport": "^0.4.1",
|
||||||
"passport-http": "^0.3.0",
|
"passport-http": "^0.3.0",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (jsonExists) {
|
if (jsonExists) {
|
||||||
retrievedID = fs.readJSONSync(jsonPath)['id'];
|
retrievedID = JSON.parse(await fs.readFile(jsonPath, 'utf8'))['id'];
|
||||||
await fs.unlink(jsonPath);
|
await fs.unlink(jsonPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,11 +196,12 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
|
|||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// check if the user wants the video to be redownloaded (deleteForever === false)
|
// check if the user wants the video to be redownloaded (deleteForever === false)
|
||||||
if (useArchive && retrievedID) {
|
if (!deleteForever && useArchive && sub.archive && retrievedID) {
|
||||||
const archive_path = utils.getArchiveFolder(sub.type, user_uid, sub);
|
const archive_path = path.join(sub.archive, 'archive.txt')
|
||||||
|
// if archive exists, remove line with video ID
|
||||||
// Remove file ID from the archive file, and write it to the blacklist (if enabled)
|
if (await fs.pathExists(archive_path)) {
|
||||||
await utils.deleteFileFromArchive(file_uid, sub.type, archive_path, retrievedID, deleteForever);
|
utils.removeIDFromArchive(archive_path, retrievedID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -241,22 +242,64 @@ async function getVideosForSub(sub, user_uid = null) {
|
|||||||
logger.verbose('Subscription: finished check for ' + sub.name);
|
logger.verbose('Subscription: finished check for ' + sub.name);
|
||||||
if (err && !output) {
|
if (err && !output) {
|
||||||
logger.error(err.stderr ? err.stderr : err.message);
|
logger.error(err.stderr ? err.stderr : err.message);
|
||||||
if (err.stderr.includes('This video is unavailable') || err.stderr.includes('Private video')) {
|
if (err.stderr.includes('This video is unavailable')) {
|
||||||
logger.info('An error was encountered with at least one video, backup method will be used.')
|
logger.info('An error was encountered with at least one video, backup method will be used.')
|
||||||
try {
|
try {
|
||||||
const outputs = err.stdout.split(/\r\n|\r|\n/);
|
// TODO: reimplement
|
||||||
const files_to_download = await handleOutputJSON(outputs, sub, user_uid);
|
|
||||||
resolve(files_to_download);
|
// const outputs = err.stdout.split(/\r\n|\r|\n/);
|
||||||
|
// for (let i = 0; i < outputs.length; i++) {
|
||||||
|
// const output = JSON.parse(outputs[i]);
|
||||||
|
// await handleOutputJSON(sub, output, i === 0, multiUserMode)
|
||||||
|
// if (err.stderr.includes(output['id']) && archive_path) {
|
||||||
|
// // we found a video that errored! add it to the archive to prevent future errors
|
||||||
|
// if (sub.archive) {
|
||||||
|
// archive_dir = sub.archive;
|
||||||
|
// archive_path = path.join(archive_dir, 'archive.txt')
|
||||||
|
// fs.appendFileSync(archive_path, output['id']);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
logger.error('Backup method failed. See error below:');
|
logger.error('Backup method failed. See error below:');
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logger.error('Subscription check failed!');
|
|
||||||
}
|
}
|
||||||
resolve(false);
|
resolve(false);
|
||||||
} else if (output) {
|
} else if (output) {
|
||||||
const files_to_download = await handleOutputJSON(output, sub, user_uid);
|
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;
|
||||||
|
try {
|
||||||
|
output_json = JSON.parse(output[i]);
|
||||||
|
output_jsons.push(output_json);
|
||||||
|
} catch(e) {
|
||||||
|
output_json = null;
|
||||||
|
}
|
||||||
|
if (!output_json) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const files_to_download = await getFilesToDownload(sub, output_jsons);
|
||||||
|
const base_download_options = generateOptionsForSubscriptionDownload(sub, user_uid);
|
||||||
|
|
||||||
|
for (let j = 0; j < files_to_download.length; j++) {
|
||||||
|
const file_to_download = files_to_download[j];
|
||||||
|
await downloader_api.createDownload(file_to_download['webpage_url'], sub.type || 'video', base_download_options, user_uid, sub.id, sub.name);
|
||||||
|
}
|
||||||
|
|
||||||
resolve(files_to_download);
|
resolve(files_to_download);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -266,43 +309,6 @@ async function getVideosForSub(sub, user_uid = null) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleOutputJSON(output, sub, user_uid) {
|
|
||||||
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);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const output_jsons = [];
|
|
||||||
for (let i = 0; i < output.length; i++) {
|
|
||||||
let output_json = null;
|
|
||||||
try {
|
|
||||||
output_json = JSON.parse(output[i]);
|
|
||||||
output_jsons.push(output_json);
|
|
||||||
} catch(e) {
|
|
||||||
output_json = null;
|
|
||||||
}
|
|
||||||
if (!output_json) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const files_to_download = await getFilesToDownload(sub, output_jsons);
|
|
||||||
const base_download_options = generateOptionsForSubscriptionDownload(sub, user_uid);
|
|
||||||
|
|
||||||
for (let j = 0; j < files_to_download.length; j++) {
|
|
||||||
const file_to_download = files_to_download[j];
|
|
||||||
file_to_download['formats'] = utils.stripPropertiesFromObject(file_to_download['formats'], ['format_id', 'filesize', 'filesize_approx']); // prevent download object from blowing up in size
|
|
||||||
await downloader_api.createDownload(file_to_download['webpage_url'], sub.type || 'video', base_download_options, user_uid, sub.id, sub.name, file_to_download);
|
|
||||||
}
|
|
||||||
|
|
||||||
return files_to_download;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateOptionsForSubscriptionDownload(sub, user_uid) {
|
function generateOptionsForSubscriptionDownload(sub, user_uid) {
|
||||||
let basePath = null;
|
let basePath = null;
|
||||||
if (user_uid)
|
if (user_uid)
|
||||||
@@ -313,10 +319,10 @@ function generateOptionsForSubscriptionDownload(sub, user_uid) {
|
|||||||
let default_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
|
let default_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
|
||||||
|
|
||||||
const base_download_options = {
|
const base_download_options = {
|
||||||
maxHeight: sub.maxQuality && sub.maxQuality !== 'best' ? sub.maxQuality : null,
|
selectedHeight: sub.maxQuality && sub.maxQuality !== 'best' ? sub.maxQuality : null,
|
||||||
customFileFolderPath: getAppendedBasePath(sub, basePath),
|
customFileFolderPath: getAppendedBasePath(sub, basePath),
|
||||||
customOutput: sub.custom_output ? `${sub.custom_output}` : `${default_output}`,
|
customOutput: sub.custom_output ? `${sub.custom_output}` : `${default_output}`,
|
||||||
customArchivePath: path.join(basePath, 'archives', sub.name),
|
customArchivePath: path.join(__dirname, basePath, 'archives', sub.name),
|
||||||
additionalArgs: sub.custom_args
|
additionalArgs: sub.custom_args
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +389,11 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
|||||||
downloadConfig.push('--download-archive', archive_path);
|
downloadConfig.push('--download-archive', archive_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if streaming only mode, just get the list of videos
|
||||||
|
if (sub.streamingOnly) {
|
||||||
|
downloadConfig = ['-f', 'best', '--dump-json'];
|
||||||
|
}
|
||||||
|
|
||||||
if (sub.timerange && !redownload) {
|
if (sub.timerange && !redownload) {
|
||||||
downloadConfig.push('--dateafter', sub.timerange);
|
downloadConfig.push('--dateafter', sub.timerange);
|
||||||
}
|
}
|
||||||
@@ -407,11 +418,9 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
|||||||
|
|
||||||
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
|
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
|
||||||
if (default_downloader === 'yt-dlp') {
|
if (default_downloader === 'yt-dlp') {
|
||||||
downloadConfig.push('--no-clean-info-json');
|
downloadConfig.push('--no-clean-infojson');
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadConfig = utils.filterArgs(downloadConfig, ['--write-comments']);
|
|
||||||
|
|
||||||
return downloadConfig;
|
return downloadConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +467,7 @@ async function updateSubscription(sub) {
|
|||||||
|
|
||||||
async function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
|
async function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
|
||||||
subs.forEach(async sub => {
|
subs.forEach(async sub => {
|
||||||
await updateSubscriptionProperty(sub, assignment_obj);
|
await updateSubscriptionProperty(sub, assignment_obj, sub.user_uid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,7 +479,6 @@ async function updateSubscriptionProperty(sub, assignment_obj) {
|
|||||||
|
|
||||||
async function setFreshUploads(sub) {
|
async function setFreshUploads(sub) {
|
||||||
const sub_files = await db_api.getRecords('files', {sub_id: sub.id});
|
const sub_files = await db_api.getRecords('files', {sub_id: sub.id});
|
||||||
if (!sub_files) return;
|
|
||||||
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
||||||
sub_files.forEach(async file => {
|
sub_files.forEach(async file => {
|
||||||
if (current_date === file['upload_date'].replace(/-/g, '')) {
|
if (current_date === file['upload_date'].replace(/-/g, '')) {
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ exports.updateTaskSchedule = async (task_key, schedule) => {
|
|||||||
await db_api.updateRecord('tasks', {key: task_key}, {schedule: schedule});
|
await db_api.updateRecord('tasks', {key: task_key}, {schedule: schedule});
|
||||||
if (TASKS[task_key]['job']) {
|
if (TASKS[task_key]['job']) {
|
||||||
TASKS[task_key]['job'].cancel();
|
TASKS[task_key]['job'].cancel();
|
||||||
TASKS[task_key]['job'] = null;
|
|
||||||
}
|
}
|
||||||
if (schedule) {
|
if (schedule) {
|
||||||
TASKS[task_key]['job'] = scheduleJob(task_key, schedule);
|
TASKS[task_key]['job'] = scheduleJob(task_key, schedule);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
const assert = require('assert');
|
var assert = require('assert');
|
||||||
const low = require('lowdb')
|
const low = require('lowdb')
|
||||||
const winston = require('winston');
|
var winston = require('winston');
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
process.chdir('./backend')
|
process.chdir('./backend')
|
||||||
|
|
||||||
@@ -40,29 +39,9 @@ const utils = require('../utils');
|
|||||||
const subscriptions_api = require('../subscriptions');
|
const subscriptions_api = require('../subscriptions');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { uuid } = require('uuidv4');
|
const { uuid } = require('uuidv4');
|
||||||
const NodeID3 = require('node-id3');
|
|
||||||
|
|
||||||
db_api.initialize(db, users_db);
|
db_api.initialize(db, users_db);
|
||||||
|
|
||||||
const sample_video_json = {
|
|
||||||
id: "Sample Video",
|
|
||||||
title: "Sample Video",
|
|
||||||
thumbnailURL: "https://sampleurl.jpg",
|
|
||||||
isAudio: false,
|
|
||||||
duration: 177.413,
|
|
||||||
url: "sampleurl.com",
|
|
||||||
uploader: "Sample Uploader",
|
|
||||||
size: 2838445,
|
|
||||||
path: "users\\admin\\video\\Sample Video.mp4",
|
|
||||||
upload_date: "2017-07-28",
|
|
||||||
description: null,
|
|
||||||
view_count: 230,
|
|
||||||
abr: 128,
|
|
||||||
thumbnailPath: null,
|
|
||||||
user_uid: "admin",
|
|
||||||
uid: "1ada04ab-2773-4dd4-bbdd-3e2d40761c50",
|
|
||||||
registered: 1628469039377
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Database', async function() {
|
describe('Database', async function() {
|
||||||
describe('Import', async function() {
|
describe('Import', async function() {
|
||||||
@@ -235,7 +214,7 @@ describe('Database', async function() {
|
|||||||
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
|
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
|
||||||
const uid = uuid();
|
const uid = uuid();
|
||||||
if (i === NUM_RECORDS_TO_ADD/2) random_uid = uid;
|
if (i === NUM_RECORDS_TO_ADD/2) random_uid = uid;
|
||||||
test_records.push({"id":"RandomTextRandomText","title":"RandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomText","thumbnailURL":"https://i.ytimg.com/vi/randomurl/maxresdefault.jpg","isAudio":true,"duration":312,"url":"https://www.youtube.com/watch?v=randomvideo","uploader":"randomUploader","size":5060157,"path":"audio\\RandomTextRandomText.mp3","upload_date":"2016-05-11","description":"RandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomTextRandomText","view_count":118689353,"height":null,"abr":160,"uid": uid,"registered":1626672120632});
|
test_records.push({"id":"A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J","title":"A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J","thumbnailURL":"https://i.ytimg.com/vi/tt7gP_IW-1w/maxresdefault.jpg","isAudio":true,"duration":312,"url":"https://www.youtube.com/watch?v=tt7gP_IW-1w","uploader":"asapmobVEVO","size":5060157,"path":"audio\\A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J.mp3","upload_date":"2016-05-11","description":"A$AP Mob ft. Juicy J - \"Yamborghini High\" Get it now on:\niTunes: http://smarturl.it/iYAMH?IQid=yt\nListen on Spotify: http://smarturl.it/sYAMH?IQid=yt\nGoogle Play: http://smarturl.it/gYAMH?IQid=yt\nAmazon: http://smarturl.it/aYAMH?IQid=yt\n\nFollow A$AP Mob:\nhttps://www.facebook.com/asapmobofficial\nhttps://twitter.com/ASAPMOB\nhttp://instagram.com/asapmob \nhttp://www.asapmob.com/\n\n#AsapMob #YamborghiniHigh #Vevo #HipHop #OfficialMusicVideo #JuicyJ","view_count":118689353,"height":null,"abr":160,"uid": uid,"registered":1626672120632});
|
||||||
}
|
}
|
||||||
const insert_start = Date.now();
|
const insert_start = Date.now();
|
||||||
let success = await db_api.bulkInsertRecordsIntoTable('test', test_records);
|
let success = await db_api.bulkInsertRecordsIntoTable('test', test_records);
|
||||||
@@ -256,30 +235,6 @@ describe('Database', async function() {
|
|||||||
assert(success);
|
assert(success);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Local DB Filters', async function() {
|
|
||||||
it('Basic', async function() {
|
|
||||||
const result = db_api.applyFilterLocalDB([{test: 'test'}, {test: 'test1'}], {test: 'test'}, 'find');
|
|
||||||
assert(result && result['test'] === 'test');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Regex', async function() {
|
|
||||||
const filter = {$regex: `\\w+\\d`, $options: 'i'};
|
|
||||||
const result = db_api.applyFilterLocalDB([{test: 'test'}, {test: 'test1'}], {test: filter}, 'find');
|
|
||||||
assert(result && result['test'] === 'test1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Not equals', async function() {
|
|
||||||
const filter = {$ne: 'test'};
|
|
||||||
const result = db_api.applyFilterLocalDB([{test: 'test'}, {test: 'test1'}], {test: filter}, 'find');
|
|
||||||
assert(result && result['test'] === 'test1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Nested', async function() {
|
|
||||||
const result = db_api.applyFilterLocalDB([{test1: {test2: 'test3'}}, {test4: 'test5'}], {'test1.test2': 'test3'}, 'find');
|
|
||||||
assert(result && result['test1']['test2'] === 'test3');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Multi User', async function() {
|
describe('Multi User', async function() {
|
||||||
@@ -298,12 +253,10 @@ describe('Multi User', async function() {
|
|||||||
assert(user);
|
assert(user);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Video player - normal', async function() {
|
describe('Video player - normal', function() {
|
||||||
await db_api.removeRecord('files', {uid: sample_video_json['uid']});
|
const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e';
|
||||||
await db_api.insertRecordIntoTable('files', sample_video_json);
|
|
||||||
const video_to_test = sample_video_json['uid'];
|
|
||||||
it('Get video', async function() {
|
it('Get video', async function() {
|
||||||
const video_obj = await db_api.getVideo(video_to_test);
|
const video_obj = db_api.getVideo(video_to_test, 'admin');
|
||||||
assert(video_obj);
|
assert(video_obj);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -388,9 +341,7 @@ describe('Downloader', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Get file info', async function() {
|
it('Get file info', async function() {
|
||||||
this.timeout(300000);
|
|
||||||
const info = await downloader_api.getVideoInfoByURL(url);
|
|
||||||
assert(!!info);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Download file', async function() {
|
it('Download file', async function() {
|
||||||
@@ -401,19 +352,6 @@ describe('Downloader', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Tag file', async function() {
|
|
||||||
const audio_path = './test/sample.mp3';
|
|
||||||
const sample_json = fs.readJSONSync('./test/sample.info.json');
|
|
||||||
const tags = {
|
|
||||||
title: sample_json['title'],
|
|
||||||
artist: sample_json['artist'] ? sample_json['artist'] : sample_json['uploader'],
|
|
||||||
TRCK: '27'
|
|
||||||
}
|
|
||||||
NodeID3.write(tags, audio_path);
|
|
||||||
const written_tags = NodeID3.read(audio_path);
|
|
||||||
assert(written_tags['raw']['TRCK'] === '27');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Queue file', async function() {
|
it('Queue file', async function() {
|
||||||
this.timeout(300000);
|
this.timeout(300000);
|
||||||
const returned_download = await downloader_api.createDownload(url, 'video', options);
|
const returned_download = await downloader_api.createDownload(url, 'video', options);
|
||||||
@@ -422,23 +360,20 @@ describe('Downloader', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Pause file', async function() {
|
it('Pause file', async function() {
|
||||||
const returned_download = await downloader_api.createDownload(url, 'video', options);
|
|
||||||
await downloader_api.pauseDownload(returned_download['uid']);
|
|
||||||
const updated_download = await db_api.getRecord('download_queue', {uid: returned_download['uid']});
|
|
||||||
assert(updated_download['paused'] && !updated_download['running']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Generate args', async function() {
|
it('Generate args', async function() {
|
||||||
const args = await downloader_api.generateArgs(url, 'video', options);
|
const args = await downloader_api.generateArgs(url, 'video', options);
|
||||||
assert(args.length > 0);
|
console.log(args);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Generate args - subscription', async function() {
|
it('Generate args - subscription', async function() {
|
||||||
|
subscriptions_api.initialize(db_api, logger);
|
||||||
const sub = await subscriptions_api.getSubscription(sub_id);
|
const sub = await subscriptions_api.getSubscription(sub_id);
|
||||||
const sub_options = subscriptions_api.generateOptionsForSubscriptionDownload(sub, 'admin');
|
const sub_options = subscriptions_api.generateOptionsForSubscriptionDownload(sub, 'admin');
|
||||||
const args_normal = await downloader_api.generateArgs(url, 'video', options);
|
const args = await downloader_api.generateArgs(url, 'video', sub_options, 'admin');
|
||||||
const args_sub = await downloader_api.generateArgs(url, 'video', sub_options, 'admin');
|
console.log(args);
|
||||||
console.log(JSON.stringify(args_normal) !== JSON.stringify(args_sub));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Generate kodi NFO file', async function() {
|
it('Generate kodi NFO file', async function() {
|
||||||
@@ -466,20 +401,6 @@ describe('Downloader', function() {
|
|||||||
console.log(updated_args2);
|
console.log(updated_args2);
|
||||||
assert(JSON.stringify(updated_args2), JSON.stringify(expected_args2));
|
assert(JSON.stringify(updated_args2), JSON.stringify(expected_args2));
|
||||||
});
|
});
|
||||||
describe('Twitch', async function () {
|
|
||||||
const twitch_api = require('../twitch');
|
|
||||||
const example_vod = '1493770675';
|
|
||||||
it('Download VOD', async function() {
|
|
||||||
const sample_path = path.join('test', 'sample.twitch_chat.json');
|
|
||||||
if (fs.existsSync(sample_path)) fs.unlinkSync(sample_path);
|
|
||||||
this.timeout(300000);
|
|
||||||
await twitch_api.downloadTwitchChatByVODID(example_vod, 'sample', null, null, null, './test');
|
|
||||||
assert(fs.existsSync(sample_path));
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
if (fs.existsSync(sample_path)) fs.unlinkSync(sample_path);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Tasks', function() {
|
describe('Tasks', function() {
|
||||||
@@ -496,7 +417,7 @@ describe('Tasks', function() {
|
|||||||
};
|
};
|
||||||
tasks_api.TASKS['dummy_task'] = dummy_task;
|
tasks_api.TASKS['dummy_task'] = dummy_task;
|
||||||
|
|
||||||
await tasks_api.setupTasks();
|
await tasks_api.initialize();
|
||||||
});
|
});
|
||||||
it('Backup db', async function() {
|
it('Backup db', async function() {
|
||||||
const backups_original = await utils.recFindByExt('appdata', 'bak');
|
const backups_original = await utils.recFindByExt('appdata', 'bak');
|
||||||
@@ -508,13 +429,12 @@ describe('Tasks', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Check for missing files', async function() {
|
it('Check for missing files', async function() {
|
||||||
this.timeout(300000);
|
|
||||||
await db_api.removeAllRecords('files', {uid: 'test'});
|
await db_api.removeAllRecords('files', {uid: 'test'});
|
||||||
const test_missing_file = {uid: 'test', path: 'test/missing_file.mp4'};
|
const test_missing_file = {uid: 'test', path: 'test/missing_file.mp4'};
|
||||||
await db_api.insertRecordIntoTable('files', test_missing_file);
|
await db_api.insertRecordIntoTable('files', test_missing_file);
|
||||||
await tasks_api.executeTask('missing_files_check');
|
await tasks_api.executeTask('missing_files_check');
|
||||||
const missing_file_db_record = await db_api.getRecord('files', {uid: 'test'});
|
const task_obj = await db_api.getRecord('tasks', {key: 'missing_files_check'});
|
||||||
assert(!missing_file_db_record, true);
|
assert(task_obj['data'] && task_obj['data']['uids'] && task_obj['data']['uids'].length >= 1, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Check for duplicate files', async function() {
|
it('Check for duplicate files', async function() {
|
||||||
@@ -527,13 +447,10 @@ describe('Tasks', function() {
|
|||||||
await db_api.insertRecordIntoTable('files', test_duplicate_file1);
|
await db_api.insertRecordIntoTable('files', test_duplicate_file1);
|
||||||
await db_api.insertRecordIntoTable('files', test_duplicate_file2);
|
await db_api.insertRecordIntoTable('files', test_duplicate_file2);
|
||||||
await db_api.insertRecordIntoTable('files', test_duplicate_file3);
|
await db_api.insertRecordIntoTable('files', test_duplicate_file3);
|
||||||
|
|
||||||
await tasks_api.executeRun('duplicate_files_check');
|
|
||||||
const task_obj = await db_api.getRecord('tasks', {key: 'duplicate_files_check'});
|
|
||||||
assert(task_obj['data'] && task_obj['data']['uids'] && task_obj['data']['uids'].length >= 1, true);
|
|
||||||
|
|
||||||
await tasks_api.executeTask('duplicate_files_check');
|
await tasks_api.executeTask('duplicate_files_check');
|
||||||
|
const task_obj = await db_api.getRecord('tasks', {key: 'duplicate_files_check'});
|
||||||
const duplicated_record_count = await db_api.getRecords('files', {path: 'test/missing_file.mp4'}, true);
|
const duplicated_record_count = await db_api.getRecords('files', {path: 'test/missing_file.mp4'}, true);
|
||||||
|
assert(task_obj['data'] && task_obj['data']['uids'] && task_obj['data']['uids'].length >= 1, true);
|
||||||
assert(duplicated_record_count == 1, true);
|
assert(duplicated_record_count == 1, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -558,72 +475,22 @@ describe('Tasks', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Schedule and cancel task', async function() {
|
it('Schedule and cancel task', async function() {
|
||||||
this.timeout(5000);
|
const today_4_hours = new Date();
|
||||||
const today_one_year = new Date();
|
today_4_hours.setHours(today_4_hours.getHours() + 4);
|
||||||
today_one_year.setFullYear(today_one_year.getFullYear() + 1);
|
await tasks_api.updateTaskSchedule('dummy_task', today_4_hours);
|
||||||
const schedule_obj = {
|
assert(!!tasks_api.TASKS['dummy_task']['job'], true);
|
||||||
type: 'timestamp',
|
|
||||||
data: { timestamp: today_one_year.getTime() }
|
|
||||||
}
|
|
||||||
await tasks_api.updateTaskSchedule('dummy_task', schedule_obj);
|
|
||||||
const dummy_task = await db_api.getRecord('tasks', {key: 'dummy_task'});
|
|
||||||
assert(!!tasks_api.TASKS['dummy_task']['job']);
|
|
||||||
assert(!!dummy_task['schedule']);
|
|
||||||
|
|
||||||
await tasks_api.updateTaskSchedule('dummy_task', null);
|
await tasks_api.updateTaskSchedule('dummy_task', null);
|
||||||
const dummy_task_updated = await db_api.getRecord('tasks', {key: 'dummy_task'});
|
assert(!!tasks_api.TASKS['dummy_task']['job'], false);
|
||||||
assert(!tasks_api.TASKS['dummy_task']['job']);
|
|
||||||
assert(!dummy_task_updated['schedule']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Schedule and run task', async function() {
|
it('Schedule and run task', async function() {
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
const today_1_second = new Date();
|
const today_1_second = new Date();
|
||||||
today_1_second.setSeconds(today_1_second.getSeconds() + 1);
|
today_1_second.setSeconds(today_1_second.getSeconds() + 1);
|
||||||
const schedule_obj = {
|
await tasks_api.updateTaskSchedule('dummy_task', today_1_second);
|
||||||
type: 'timestamp',
|
assert(!!tasks_api.TASKS['dummy_task']['job'], true);
|
||||||
data: { timestamp: today_1_second.getTime() }
|
|
||||||
}
|
|
||||||
await tasks_api.updateTaskSchedule('dummy_task', schedule_obj);
|
|
||||||
assert(!!tasks_api.TASKS['dummy_task']['job']);
|
|
||||||
await utils.wait(2000);
|
await utils.wait(2000);
|
||||||
const dummy_task_obj = await db_api.getRecord('tasks', {key: 'dummy_task'});
|
const dummy_task_obj = await db_api.getRecord('tasks', {key: 'dummy_task'});
|
||||||
assert(dummy_task_obj['data']);
|
assert(dummy_task_obj['data'], true);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Archive', async function() {
|
|
||||||
const archive_path = path.join('test', 'archives');
|
|
||||||
fs.ensureDirSync(archive_path);
|
|
||||||
const archive_file_path = path.join(archive_path, 'archive_video.txt');
|
|
||||||
const blacklist_file_path = path.join(archive_path, 'blacklist_video.txt');
|
|
||||||
beforeEach(async function() {
|
|
||||||
if (fs.existsSync(archive_file_path)) fs.unlinkSync(archive_file_path);
|
|
||||||
fs.writeFileSync(archive_file_path, 'youtube testing1\nyoutube testing2\nyoutube testing3\n');
|
|
||||||
|
|
||||||
if (fs.existsSync(blacklist_file_path)) fs.unlinkSync(blacklist_file_path);
|
|
||||||
fs.writeFileSync(blacklist_file_path, '');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Delete from archive', async function() {
|
|
||||||
await utils.deleteFileFromArchive('N/A', 'video', archive_path, 'testing2', false);
|
|
||||||
const new_archive = fs.readFileSync(archive_file_path);
|
|
||||||
assert(!new_archive.includes('testing2'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Delete from archive - blacklist', async function() {
|
|
||||||
await utils.deleteFileFromArchive('N/A', 'video', archive_path, 'testing2', true);
|
|
||||||
const new_archive = fs.readFileSync(archive_file_path);
|
|
||||||
const new_blacklist = fs.readFileSync(blacklist_file_path);
|
|
||||||
assert(!new_archive.includes('testing2'));
|
|
||||||
assert(new_blacklist.includes('testing2'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Utils', async function() {
|
|
||||||
it('Strip properties', async function() {
|
|
||||||
const test_obj = {test1: 'test1', test2: 'test2', test3: 'test3'};
|
|
||||||
const stripped_obj = utils.stripPropertiesFromObject(test_obj, ['test1', 'test3']);
|
|
||||||
assert(!stripped_obj['test1'] && stripped_obj['test2'] && !stripped_obj['test3'])
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,64 +1,90 @@
|
|||||||
|
var moment = require('moment');
|
||||||
|
var Axios = require('axios');
|
||||||
|
var fs = require('fs-extra')
|
||||||
|
var path = require('path');
|
||||||
const config_api = require('./config');
|
const config_api = require('./config');
|
||||||
const logger = require('./logger');
|
|
||||||
|
|
||||||
const moment = require('moment');
|
async function getCommentsForVOD(clientID, vodId) {
|
||||||
const fs = require('fs-extra')
|
let url = `https://api.twitch.tv/v5/videos/${vodId}/comments?content_offset_seconds=0`,
|
||||||
const path = require('path');
|
batch,
|
||||||
|
cursor;
|
||||||
|
|
||||||
async function getCommentsForVOD(clientID, clientSecret, vodId) {
|
let comments = null;
|
||||||
const { promisify } = require('util');
|
|
||||||
const child_process = require('child_process');
|
|
||||||
const exec = promisify(child_process.exec);
|
|
||||||
|
|
||||||
// Reject invalid params to prevent command injection attack
|
try {
|
||||||
if (!clientID.match(/^[0-9a-z]+$/) || !clientSecret.match(/^[0-9a-z]+$/) || !vodId.match(/^[0-9a-z]+$/)) {
|
do {
|
||||||
logger.error('Client ID, client secret, and VOD ID must be purely alphanumeric. Twitch chat download failed!');
|
batch = (await Axios.get(url, {
|
||||||
return null;
|
headers: {
|
||||||
|
'Client-ID': clientID,
|
||||||
|
Accept: 'application/vnd.twitchtv.v5+json; charset=UTF-8',
|
||||||
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
|
}
|
||||||
|
})).data;
|
||||||
|
|
||||||
|
const str = batch.comments.map(c => {
|
||||||
|
let {
|
||||||
|
created_at: msgCreated,
|
||||||
|
content_offset_seconds: timestamp,
|
||||||
|
commenter: {
|
||||||
|
name,
|
||||||
|
_id,
|
||||||
|
created_at: acctCreated
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
body: msg,
|
||||||
|
user_color: user_color
|
||||||
|
}
|
||||||
|
} = c;
|
||||||
|
|
||||||
|
const timestamp_str = moment.duration(timestamp, 'seconds')
|
||||||
|
.toISOString()
|
||||||
|
.replace(/P.*?T(?:(\d+?)H)?(?:(\d+?)M)?(?:(\d+).*?S)?/,
|
||||||
|
(_, ...ms) => {
|
||||||
|
const seg = v => v ? v.padStart(2, '0') : '00';
|
||||||
|
return `${seg(ms[0])}:${seg(ms[1])}:${seg(ms[2])}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
acctCreated = moment(acctCreated).utc();
|
||||||
|
msgCreated = moment(msgCreated).utc();
|
||||||
|
|
||||||
|
if (!comments) comments = [];
|
||||||
|
|
||||||
|
comments.push({
|
||||||
|
timestamp: timestamp,
|
||||||
|
timestamp_str: timestamp_str,
|
||||||
|
name: name,
|
||||||
|
message: msg,
|
||||||
|
user_color: user_color
|
||||||
|
});
|
||||||
|
// let line = `${timestamp},${msgCreated.format(tsFormat)},${name},${_id},"${msg.replace(/"/g, '""')}",${acctCreated.format(tsFormat)}`;
|
||||||
|
// return line;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
cursor = batch._next;
|
||||||
|
url = `https://api.twitch.tv/v5/videos/${vodId}/comments?cursor=${cursor}`;
|
||||||
|
await new Promise(res => setTimeout(res, 300));
|
||||||
|
} while (cursor);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await exec(`tcd --video ${vodId} --client-id ${clientID} --client-secret ${clientSecret} --format json -o appdata`, {stdio:[0,1,2]});
|
return comments;
|
||||||
|
|
||||||
if (result['stderr']) {
|
|
||||||
logger.error(`Failed to download twitch comments for ${vodId}`);
|
|
||||||
logger.error(result['stderr']);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp_chat_path = path.join('appdata', `${vodId}.json`);
|
|
||||||
|
|
||||||
const raw_json = fs.readJSONSync(temp_chat_path);
|
|
||||||
const new_json = raw_json.comments.map(comment_obj => {
|
|
||||||
return {
|
|
||||||
timestamp: comment_obj.content_offset_seconds,
|
|
||||||
timestamp_str: convertTimestamp(comment_obj.content_offset_seconds),
|
|
||||||
name: comment_obj.commenter.name,
|
|
||||||
message: comment_obj.message.body,
|
|
||||||
user_color: comment_obj.message.user_color
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.unlinkSync(temp_chat_path);
|
|
||||||
|
|
||||||
return new_json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) {
|
async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) {
|
||||||
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
const subscriptionsFileFolder = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
|
||||||
let file_path = null;
|
let file_path = null;
|
||||||
|
|
||||||
if (user_uid) {
|
if (user_uid) {
|
||||||
if (sub) {
|
if (sub) {
|
||||||
file_path = path.join(usersFileFolder, user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, `${id}.twitch_chat.json`);
|
file_path = path.join('users', user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
|
||||||
} else {
|
} else {
|
||||||
file_path = path.join(usersFileFolder, user_uid, type, `${id}.twitch_chat.json`);
|
file_path = path.join('users', user_uid, type, id + '.twitch_chat.json');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sub) {
|
if (sub) {
|
||||||
file_path = path.join(subscriptionsFileFolder, sub.isPlaylist ? 'playlists' : 'channels', sub.name, `${id}.twitch_chat.json`);
|
file_path = path.join('subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
|
||||||
} else {
|
} else {
|
||||||
const typeFolder = config_api.getConfigItem(`ytdl_${type}_folder_path`);
|
file_path = path.join(type, id + '.twitch_chat.json');
|
||||||
file_path = path.join(typeFolder, `${id}.twitch_chat.json`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,28 +96,23 @@ async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) {
|
|||||||
return chat_file;
|
return chat_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub, customFileFolderPath = null) {
|
async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub) {
|
||||||
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
const twitch_api_key = config_api.getConfigItem('ytdl_twitch_api_key');
|
||||||
const subscriptionsFileFolder = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
const chat = await getCommentsForVOD(twitch_api_key, vodId);
|
||||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
|
||||||
const twitch_client_secret = config_api.getConfigItem('ytdl_twitch_client_secret');
|
|
||||||
const chat = await getCommentsForVOD(twitch_client_id, twitch_client_secret, vodId);
|
|
||||||
|
|
||||||
// save file if needed params are included
|
// save file if needed params are included
|
||||||
let file_path = null;
|
let file_path = null;
|
||||||
if (customFileFolderPath) {
|
if (user_uid) {
|
||||||
file_path = path.join(customFileFolderPath, `${id}.twitch_chat.json`)
|
|
||||||
} else if (user_uid) {
|
|
||||||
if (sub) {
|
if (sub) {
|
||||||
file_path = path.join(usersFileFolder, user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, `${id}.twitch_chat.json`);
|
file_path = path.join('users', user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
|
||||||
} else {
|
} else {
|
||||||
file_path = path.join(usersFileFolder, user_uid, type, `${id}.twitch_chat.json`);
|
file_path = path.join('users', user_uid, type, id + '.twitch_chat.json');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sub) {
|
if (sub) {
|
||||||
file_path = path.join(subscriptionsFileFolder, sub.isPlaylist ? 'playlists' : 'channels', sub.name, `${id}.twitch_chat.json`);
|
file_path = path.join('subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
|
||||||
} else {
|
} else {
|
||||||
file_path = path.join(type, `${id}.twitch_chat.json`);
|
file_path = path.join(type, id + '.twitch_chat.json');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,14 +121,6 @@ async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub, customF
|
|||||||
return chat;
|
return chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertTimestamp = (timestamp) => moment.duration(timestamp, 'seconds')
|
|
||||||
.toISOString()
|
|
||||||
.replace(/P.*?T(?:(\d+?)H)?(?:(\d+?)M)?(?:(\d+).*?S)?/,
|
|
||||||
(_, ...ms) => {
|
|
||||||
const seg = v => v ? v.padStart(2, '0') : '00';
|
|
||||||
return `${seg(ms[0])}:${seg(ms[1])}:${seg(ms[2])}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getCommentsForVOD: getCommentsForVOD,
|
getCommentsForVOD: getCommentsForVOD,
|
||||||
getTwitchChatByFileID: getTwitchChatByFileID,
|
getTwitchChatByFileID: getTwitchChatByFileID,
|
||||||
|
|||||||
108
backend/utils.js
108
backend/utils.js
@@ -172,13 +172,11 @@ function getExpectedFileSize(input_info_jsons) {
|
|||||||
const formats = info_json['format_id'].split('+');
|
const formats = info_json['format_id'].split('+');
|
||||||
let individual_expected_filesize = 0;
|
let individual_expected_filesize = 0;
|
||||||
formats.forEach(format_id => {
|
formats.forEach(format_id => {
|
||||||
if (info_json.formats !== undefined) {
|
info_json.formats.forEach(available_format => {
|
||||||
info_json.formats.forEach(available_format => {
|
if (available_format.format_id === format_id && available_format.filesize) {
|
||||||
if (available_format.format_id === format_id && (available_format.filesize || available_format.filesize_approx)) {
|
individual_expected_filesize += available_format.filesize;
|
||||||
individual_expected_filesize += (available_format.filesize ? available_format.filesize : available_format.filesize_approx);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
expected_filesize += individual_expected_filesize;
|
expected_filesize += individual_expected_filesize;
|
||||||
});
|
});
|
||||||
@@ -220,11 +218,8 @@ function deleteJSONFile(file_path, type) {
|
|||||||
if (fs.existsSync(alternate_json_path)) fs.unlinkSync(alternate_json_path);
|
if (fs.existsSync(alternate_json_path)) fs.unlinkSync(alternate_json_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// archive helper functions
|
async function removeIDFromArchive(archive_path, id) {
|
||||||
|
let data = await fs.readFile(archive_path, {encoding: 'utf-8'});
|
||||||
async function removeIDFromArchive(archive_path, type, id) {
|
|
||||||
const archive_file = path.join(archive_path, `archive_${type}.txt`);
|
|
||||||
const data = await fs.readFile(archive_file, {encoding: 'utf-8'});
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
logger.error('Archive could not be found.');
|
logger.error('Archive could not be found.');
|
||||||
return;
|
return;
|
||||||
@@ -241,34 +236,12 @@ async function removeIDFromArchive(archive_path, type, id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastIndex === -1) return null;
|
|
||||||
|
|
||||||
const line = dataArray.splice(lastIndex, 1); // remove the keyword id from the data Array
|
const line = dataArray.splice(lastIndex, 1); // remove the keyword id from the data Array
|
||||||
|
|
||||||
// UPDATE FILE WITH NEW DATA
|
// UPDATE FILE WITH NEW DATA
|
||||||
const updatedData = dataArray.join('\n');
|
const updatedData = dataArray.join('\n');
|
||||||
await fs.writeFile(archive_file, updatedData);
|
await fs.writeFile(archive_path, updatedData);
|
||||||
if (line) return Array.isArray(line) && line.length === 1 ? line[0] : line;
|
if (line) return line;
|
||||||
}
|
|
||||||
|
|
||||||
async function writeToBlacklist(archive_folder, type, line) {
|
|
||||||
let blacklistPath = path.join(archive_folder, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
|
|
||||||
// adds newline to the beginning of the line
|
|
||||||
line.replace('\n', '');
|
|
||||||
line.replace('\r', '');
|
|
||||||
line = '\n' + line;
|
|
||||||
await fs.appendFile(blacklistPath, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteFileFromArchive(uid, type, archive_path, id, blacklistMode) {
|
|
||||||
const archive_file = path.join(archive_path, `archive_${type}.txt`);
|
|
||||||
if (await fs.pathExists(archive_path)) {
|
|
||||||
const line = id ? await removeIDFromArchive(archive_path, type, id) : null;
|
|
||||||
if (blacklistMode && line) await writeToBlacklist(archive_path, type, line);
|
|
||||||
} else {
|
|
||||||
logger.info(`Could not find archive file for file ${uid}. Creating...`);
|
|
||||||
await fs.close(await fs.open(archive_file, 'w'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function durationStringToNumber(dur_str) {
|
function durationStringToNumber(dur_str) {
|
||||||
@@ -445,7 +418,7 @@ async function fetchFile(url, path, file_label) {
|
|||||||
async function restartServer(is_update = false) {
|
async function restartServer(is_update = false) {
|
||||||
logger.info(`${is_update ? 'Update complete! ' : ''}Restarting server...`);
|
logger.info(`${is_update ? 'Update complete! ' : ''}Restarting server...`);
|
||||||
|
|
||||||
// the following line restarts the server through pm2
|
// the following line restarts the server through nodemon
|
||||||
fs.writeFileSync(`restart${is_update ? '_update' : '_general'}.json`, 'internal use only');
|
fs.writeFileSync(`restart${is_update ? '_update' : '_general'}.json`, 'internal use only');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -483,60 +456,6 @@ function injectArgs(original_args, new_args) {
|
|||||||
return updated_args;
|
return updated_args;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterArgs(args, args_to_remove) {
|
|
||||||
return args.filter(x => !args_to_remove.includes(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchObjectByString = function(o, s) {
|
|
||||||
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
|
|
||||||
s = s.replace(/^\./, ''); // strip a leading dot
|
|
||||||
var a = s.split('.');
|
|
||||||
for (var i = 0, n = a.length; i < n; ++i) {
|
|
||||||
var k = a[i];
|
|
||||||
if (k in o) {
|
|
||||||
o = o[k];
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripPropertiesFromObject(obj, properties, whitelist = false) {
|
|
||||||
if (!whitelist) {
|
|
||||||
const new_obj = JSON.parse(JSON.stringify(obj));
|
|
||||||
for (let field of properties) {
|
|
||||||
delete new_obj[field];
|
|
||||||
}
|
|
||||||
return new_obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
const new_obj = {};
|
|
||||||
for (let field of properties) {
|
|
||||||
new_obj[field] = obj[field];
|
|
||||||
}
|
|
||||||
return new_obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArchiveFolder(type, user_uid = null, sub = null) {
|
|
||||||
const usersFolderPath = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
const subsFolderPath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
|
||||||
|
|
||||||
if (user_uid) {
|
|
||||||
if (sub) {
|
|
||||||
return path.join(usersFolderPath, user_uid, 'subscriptions', 'archives', sub.name);
|
|
||||||
} else {
|
|
||||||
return path.join(usersFolderPath, user_uid, type, 'archives');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (sub) {
|
|
||||||
return path.join(subsFolderPath, 'archives', sub.name);
|
|
||||||
} else {
|
|
||||||
return path.join('appdata', 'archives');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// objects
|
// objects
|
||||||
|
|
||||||
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) {
|
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) {
|
||||||
@@ -566,12 +485,11 @@ module.exports = {
|
|||||||
fixVideoMetadataPerms: fixVideoMetadataPerms,
|
fixVideoMetadataPerms: fixVideoMetadataPerms,
|
||||||
deleteJSONFile: deleteJSONFile,
|
deleteJSONFile: deleteJSONFile,
|
||||||
removeIDFromArchive: removeIDFromArchive,
|
removeIDFromArchive: removeIDFromArchive,
|
||||||
writeToBlacklist: writeToBlacklist,
|
|
||||||
deleteFileFromArchive: deleteFileFromArchive,
|
|
||||||
getDownloadedFilesByType: getDownloadedFilesByType,
|
getDownloadedFilesByType: getDownloadedFilesByType,
|
||||||
createContainerZipFile: createContainerZipFile,
|
createContainerZipFile: createContainerZipFile,
|
||||||
durationStringToNumber: durationStringToNumber,
|
durationStringToNumber: durationStringToNumber,
|
||||||
getMatchingCategoryFiles: getMatchingCategoryFiles,
|
getMatchingCategoryFiles: getMatchingCategoryFiles,
|
||||||
|
addUIDsToCategory: addUIDsToCategory,
|
||||||
getCurrentDownloader: getCurrentDownloader,
|
getCurrentDownloader: getCurrentDownloader,
|
||||||
recFindByExt: recFindByExt,
|
recFindByExt: recFindByExt,
|
||||||
removeFileExtension: removeFileExtension,
|
removeFileExtension: removeFileExtension,
|
||||||
@@ -583,9 +501,5 @@ module.exports = {
|
|||||||
fetchFile: fetchFile,
|
fetchFile: fetchFile,
|
||||||
restartServer: restartServer,
|
restartServer: restartServer,
|
||||||
injectArgs: injectArgs,
|
injectArgs: injectArgs,
|
||||||
filterArgs: filterArgs,
|
|
||||||
searchObjectByString: searchObjectByString,
|
|
||||||
stripPropertiesFromObject: stripPropertiesFromObject,
|
|
||||||
getArchiveFolder: getArchiveFolder,
|
|
||||||
File: File
|
File: File
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ exports.updateYoutubeDL = async (latest_update_version) => {
|
|||||||
|
|
||||||
exports.verifyBinaryExistsLinux = () => {
|
exports.verifyBinaryExistsLinux = () => {
|
||||||
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
|
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
|
||||||
if (!is_windows && details_json && (!details_json['path'] || details_json['path'].includes('.exe'))) {
|
if (!is_windows && details_json && details_json['path'].includes('.exe')) {
|
||||||
details_json['path'] = 'node_modules/youtube-dl/bin/youtube-dl';
|
details_json['path'] = 'node_modules/youtube-dl/bin/youtube-dl';
|
||||||
details_json['exec'] = 'youtube-dl';
|
details_json['exec'] = 'youtube-dl';
|
||||||
details_json['version'] = OUTDATED_VERSION;
|
details_json['version'] = OUTDATED_VERSION;
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ version: 0.1.0
|
|||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
# It is recommended to use it with quotes.
|
# It is recommended to use it with quotes.
|
||||||
appVersion: "4.3"
|
appVersion: "4.2"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ version: "2"
|
|||||||
services:
|
services:
|
||||||
ytdl_material:
|
ytdl_material:
|
||||||
environment:
|
environment:
|
||||||
|
ALLOW_CONFIG_MUTATIONS: 'true'
|
||||||
ytdl_mongodb_connection_string: 'mongodb://ytdl-mongo-db:27017'
|
ytdl_mongodb_connection_string: 'mongodb://ytdl-mongo-db:27017'
|
||||||
ytdl_use_local_db: 'false'
|
ytdl_use_local_db: 'false'
|
||||||
write_ytdl_config: 'true'
|
write_ytdl_config: 'true'
|
||||||
@@ -16,9 +17,11 @@ services:
|
|||||||
- ./users:/app/users
|
- ./users:/app/users
|
||||||
ports:
|
ports:
|
||||||
- "8998:17442"
|
- "8998:17442"
|
||||||
image: tzahi12345/youtubedl-material:latest
|
image: tzahi12345/youtubedl-material:nightly
|
||||||
ytdl-mongo-db:
|
ytdl-mongo-db:
|
||||||
image: mongo
|
image: mongo
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
logging:
|
logging:
|
||||||
driver: "none"
|
driver: "none"
|
||||||
container_name: mongo-db
|
container_name: mongo-db
|
||||||
|
|||||||
3791
package-lock.json
generated
3791
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-dl-material",
|
"name": "youtube-dl-material",
|
||||||
"version": "4.3.0",
|
"version": "4.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build --configuration production",
|
"build": "ng build --configuration production",
|
||||||
"prebuild": "node src/postbuild.mjs",
|
"prebuild": "node src/postbuild.js",
|
||||||
"heroku-postbuild": "npm install --prefix backend",
|
"heroku-postbuild": "npm install --prefix backend",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"electron": "ng build --base-href ./ && electron .",
|
"electron": "ng build --base-href ./ && electron .",
|
||||||
"generate": "openapi --input ./\"Public API v1.yaml\" --output ./src/api-types --exportCore false --exportServices false --exportModels true",
|
"generate": "openapi --input ./\"Public API v1.yaml\" --output ./src/api-types --exportCore false --exportServices false --exportModels true",
|
||||||
"i18n-source": "ng extract-i18n --output-path=src/assets/i18n --out-file=messages.en.xlf"
|
"i18n-source": "ng extract-i18n --output-path=src/assets/i18n"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "12.3.1",
|
"node": "12.3.1",
|
||||||
@@ -21,18 +21,18 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/core": "^14.0.4",
|
"@angular-devkit/core": "^13.3.3",
|
||||||
"@angular/animations": "^14.0.4",
|
"@angular/animations": "^13.3.4",
|
||||||
"@angular/cdk": "^14.0.4",
|
"@angular/cdk": "^13.3.4",
|
||||||
"@angular/common": "^14.0.4",
|
"@angular/common": "^13.3.4",
|
||||||
"@angular/compiler": "^14.0.4",
|
"@angular/compiler": "^13.3.4",
|
||||||
"@angular/core": "^14.0.4",
|
"@angular/core": "^13.3.4",
|
||||||
"@angular/forms": "^14.0.4",
|
"@angular/forms": "^13.3.4",
|
||||||
"@angular/localize": "^14.0.4",
|
"@angular/localize": "^13.3.4",
|
||||||
"@angular/material": "^14.0.4",
|
"@angular/material": "^13.3.4",
|
||||||
"@angular/platform-browser": "^14.0.4",
|
"@angular/platform-browser": "^13.3.4",
|
||||||
"@angular/platform-browser-dynamic": "^14.0.4",
|
"@angular/platform-browser-dynamic": "^13.3.4",
|
||||||
"@angular/router": "^14.0.4",
|
"@angular/router": "^13.3.4",
|
||||||
"@fontsource/material-icons": "^4.5.4",
|
"@fontsource/material-icons": "^4.5.4",
|
||||||
"@ngneat/content-loader": "^5.0.0",
|
"@ngneat/content-loader": "^5.0.0",
|
||||||
"@videogular/ngx-videogular": "^5.0.1",
|
"@videogular/ngx-videogular": "^5.0.1",
|
||||||
@@ -55,10 +55,10 @@
|
|||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^14.0.4",
|
"@angular-devkit/build-angular": "^13.3.3",
|
||||||
"@angular/cli": "^14.0.4",
|
"@angular/cli": "^13.3.3",
|
||||||
"@angular/compiler-cli": "^14.0.4",
|
"@angular/compiler-cli": "^13.3.4",
|
||||||
"@angular/language-service": "^14.0.4",
|
"@angular/language-service": "^13.3.4",
|
||||||
"@types/core-js": "^2.5.2",
|
"@types/core-js": "^2.5.2",
|
||||||
"@types/file-saver": "^2.0.1",
|
"@types/file-saver": "^2.0.1",
|
||||||
"@types/jasmine": "~3.6.0",
|
"@types/jasmine": "~3.6.0",
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||||
"@typescript-eslint/parser": "^4.29.0",
|
"@typescript-eslint/parser": "^4.29.0",
|
||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.0",
|
||||||
"electron": "^19.0.6",
|
"electron": "^13.6.6",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~5.0.0",
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
|
|||||||
BIN
releases/youtubedl-material-latest.zip
Normal file
BIN
releases/youtubedl-material-latest.zip
Normal file
Binary file not shown.
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
export type { AddFileToPlaylistRequest } from './models/AddFileToPlaylistRequest';
|
export type { AddFileToPlaylistRequest } from './models/AddFileToPlaylistRequest';
|
||||||
export type { BaseChangePermissionsRequest } from './models/BaseChangePermissionsRequest';
|
export type { BaseChangePermissionsRequest } from './models/BaseChangePermissionsRequest';
|
||||||
export type { binary } from './models/binary';
|
|
||||||
export type { body_19 } from './models/body_19';
|
export type { body_19 } from './models/body_19';
|
||||||
export type { body_20 } from './models/body_20';
|
export type { body_20 } from './models/body_20';
|
||||||
export type { Category } from './models/Category';
|
export type { Category } from './models/Category';
|
||||||
@@ -13,7 +12,6 @@ export type { ChangeRolePermissionsRequest } from './models/ChangeRolePermission
|
|||||||
export type { ChangeUserPermissionsRequest } from './models/ChangeUserPermissionsRequest';
|
export type { ChangeUserPermissionsRequest } from './models/ChangeUserPermissionsRequest';
|
||||||
export type { CheckConcurrentStreamRequest } from './models/CheckConcurrentStreamRequest';
|
export type { CheckConcurrentStreamRequest } from './models/CheckConcurrentStreamRequest';
|
||||||
export type { CheckConcurrentStreamResponse } from './models/CheckConcurrentStreamResponse';
|
export type { CheckConcurrentStreamResponse } from './models/CheckConcurrentStreamResponse';
|
||||||
export type { ClearDownloadsRequest } from './models/ClearDownloadsRequest';
|
|
||||||
export type { ConcurrentStream } from './models/ConcurrentStream';
|
export type { ConcurrentStream } from './models/ConcurrentStream';
|
||||||
export type { Config } from './models/Config';
|
export type { Config } from './models/Config';
|
||||||
export type { ConfigResponse } from './models/ConfigResponse';
|
export type { ConfigResponse } from './models/ConfigResponse';
|
||||||
@@ -25,7 +23,6 @@ export type { CropFileSettings } from './models/CropFileSettings';
|
|||||||
export type { DatabaseFile } from './models/DatabaseFile';
|
export type { DatabaseFile } from './models/DatabaseFile';
|
||||||
export { DBBackup } from './models/DBBackup';
|
export { DBBackup } from './models/DBBackup';
|
||||||
export type { DBInfoResponse } from './models/DBInfoResponse';
|
export type { DBInfoResponse } from './models/DBInfoResponse';
|
||||||
export type { DeleteAllFilesResponse } from './models/DeleteAllFilesResponse';
|
|
||||||
export type { DeleteCategoryRequest } from './models/DeleteCategoryRequest';
|
export type { DeleteCategoryRequest } from './models/DeleteCategoryRequest';
|
||||||
export type { DeleteMp3Mp4Request } from './models/DeleteMp3Mp4Request';
|
export type { DeleteMp3Mp4Request } from './models/DeleteMp3Mp4Request';
|
||||||
export type { DeletePlaylistRequest } from './models/DeletePlaylistRequest';
|
export type { DeletePlaylistRequest } from './models/DeletePlaylistRequest';
|
||||||
@@ -39,14 +36,13 @@ export type { DownloadResponse } from './models/DownloadResponse';
|
|||||||
export type { DownloadTwitchChatByVODIDRequest } from './models/DownloadTwitchChatByVODIDRequest';
|
export type { DownloadTwitchChatByVODIDRequest } from './models/DownloadTwitchChatByVODIDRequest';
|
||||||
export type { DownloadTwitchChatByVODIDResponse } from './models/DownloadTwitchChatByVODIDResponse';
|
export type { DownloadTwitchChatByVODIDResponse } from './models/DownloadTwitchChatByVODIDResponse';
|
||||||
export type { DownloadVideosForSubscriptionRequest } from './models/DownloadVideosForSubscriptionRequest';
|
export type { DownloadVideosForSubscriptionRequest } from './models/DownloadVideosForSubscriptionRequest';
|
||||||
|
export type { File } from './models/File';
|
||||||
export { FileType } from './models/FileType';
|
export { FileType } from './models/FileType';
|
||||||
export { FileTypeFilter } from './models/FileTypeFilter';
|
|
||||||
export type { GenerateArgsResponse } from './models/GenerateArgsResponse';
|
export type { GenerateArgsResponse } from './models/GenerateArgsResponse';
|
||||||
export type { GenerateNewApiKeyResponse } from './models/GenerateNewApiKeyResponse';
|
export type { GenerateNewApiKeyResponse } from './models/GenerateNewApiKeyResponse';
|
||||||
export type { GetAllCategoriesResponse } from './models/GetAllCategoriesResponse';
|
export type { GetAllCategoriesResponse } from './models/GetAllCategoriesResponse';
|
||||||
export type { GetAllDownloadsRequest } from './models/GetAllDownloadsRequest';
|
export type { GetAllDownloadsRequest } from './models/GetAllDownloadsRequest';
|
||||||
export type { GetAllDownloadsResponse } from './models/GetAllDownloadsResponse';
|
export type { GetAllDownloadsResponse } from './models/GetAllDownloadsResponse';
|
||||||
export type { GetAllFilesRequest } from './models/GetAllFilesRequest';
|
|
||||||
export type { GetAllFilesResponse } from './models/GetAllFilesResponse';
|
export type { GetAllFilesResponse } from './models/GetAllFilesResponse';
|
||||||
export type { GetAllSubscriptionsResponse } from './models/GetAllSubscriptionsResponse';
|
export type { GetAllSubscriptionsResponse } from './models/GetAllSubscriptionsResponse';
|
||||||
export type { GetAllTasksResponse } from './models/GetAllTasksResponse';
|
export type { GetAllTasksResponse } from './models/GetAllTasksResponse';
|
||||||
@@ -84,7 +80,6 @@ export type { RestoreDBBackupRequest } from './models/RestoreDBBackupRequest';
|
|||||||
export { Schedule } from './models/Schedule';
|
export { Schedule } from './models/Schedule';
|
||||||
export type { SetConfigRequest } from './models/SetConfigRequest';
|
export type { SetConfigRequest } from './models/SetConfigRequest';
|
||||||
export type { SharingToggle } from './models/SharingToggle';
|
export type { SharingToggle } from './models/SharingToggle';
|
||||||
export type { Sort } from './models/Sort';
|
|
||||||
export type { SubscribeRequest } from './models/SubscribeRequest';
|
export type { SubscribeRequest } from './models/SubscribeRequest';
|
||||||
export type { SubscribeResponse } from './models/SubscribeResponse';
|
export type { SubscribeResponse } from './models/SubscribeResponse';
|
||||||
export type { Subscription } from './models/Subscription';
|
export type { Subscription } from './models/Subscription';
|
||||||
@@ -103,7 +98,6 @@ export type { UpdateCategoriesRequest } from './models/UpdateCategoriesRequest';
|
|||||||
export type { UpdateCategoryRequest } from './models/UpdateCategoryRequest';
|
export type { UpdateCategoryRequest } from './models/UpdateCategoryRequest';
|
||||||
export type { UpdateConcurrentStreamRequest } from './models/UpdateConcurrentStreamRequest';
|
export type { UpdateConcurrentStreamRequest } from './models/UpdateConcurrentStreamRequest';
|
||||||
export type { UpdateConcurrentStreamResponse } from './models/UpdateConcurrentStreamResponse';
|
export type { UpdateConcurrentStreamResponse } from './models/UpdateConcurrentStreamResponse';
|
||||||
export type { UpdateFileRequest } from './models/UpdateFileRequest';
|
|
||||||
export type { UpdatePlaylistRequest } from './models/UpdatePlaylistRequest';
|
export type { UpdatePlaylistRequest } from './models/UpdatePlaylistRequest';
|
||||||
export type { UpdaterStatus } from './models/UpdaterStatus';
|
export type { UpdaterStatus } from './models/UpdaterStatus';
|
||||||
export type { UpdateServerRequest } from './models/UpdateServerRequest';
|
export type { UpdateServerRequest } from './models/UpdateServerRequest';
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type AddFileToPlaylistRequest = {
|
|
||||||
|
export interface AddFileToPlaylistRequest {
|
||||||
file_uid: string;
|
file_uid: string;
|
||||||
playlist_id: string;
|
playlist_id: string;
|
||||||
};
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { UserPermission } from './UserPermission';
|
import { UserPermission } from './UserPermission';
|
||||||
import type { YesNo } from './YesNo';
|
import { YesNo } from './YesNo';
|
||||||
|
|
||||||
export type BaseChangePermissionsRequest = {
|
export interface BaseChangePermissionsRequest {
|
||||||
permission: UserPermission;
|
permission: UserPermission;
|
||||||
new_value: YesNo;
|
new_value: YesNo;
|
||||||
};
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { CategoryRule } from './CategoryRule';
|
import { CategoryRule } from './CategoryRule';
|
||||||
|
|
||||||
export type Category = {
|
export interface Category {
|
||||||
name?: string;
|
name?: string;
|
||||||
uid?: string;
|
uid?: string;
|
||||||
rules?: Array<CategoryRule>;
|
rules?: Array<CategoryRule>;
|
||||||
@@ -12,4 +12,4 @@ export type Category = {
|
|||||||
* Overrides file output for downloaded files in category
|
* Overrides file output for downloaded files in category
|
||||||
*/
|
*/
|
||||||
custom_output?: string;
|
custom_output?: string;
|
||||||
};
|
}
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type CategoryRule = {
|
|
||||||
|
export interface CategoryRule {
|
||||||
preceding_operator?: CategoryRule.preceding_operator;
|
preceding_operator?: CategoryRule.preceding_operator;
|
||||||
comparator?: CategoryRule.comparator;
|
comparator?: CategoryRule.comparator;
|
||||||
};
|
}
|
||||||
|
|
||||||
export namespace CategoryRule {
|
export namespace CategoryRule {
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
|
import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
|
||||||
|
|
||||||
export type ChangeRolePermissionsRequest = (BaseChangePermissionsRequest & {
|
export interface ChangeRolePermissionsRequest extends BaseChangePermissionsRequest {
|
||||||
role: string;
|
role: string;
|
||||||
});
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
|
import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
|
||||||
|
|
||||||
export type ChangeUserPermissionsRequest = (BaseChangePermissionsRequest & {
|
export interface ChangeUserPermissionsRequest extends BaseChangePermissionsRequest {
|
||||||
user_uid: string;
|
user_uid: string;
|
||||||
});
|
}
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type CheckConcurrentStreamRequest = {
|
|
||||||
|
export interface CheckConcurrentStreamRequest {
|
||||||
/**
|
/**
|
||||||
* UID of the concurrent stream
|
* UID of the concurrent stream
|
||||||
*/
|
*/
|
||||||
uid: string;
|
uid: string;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { ConcurrentStream } from './ConcurrentStream';
|
import { ConcurrentStream } from './ConcurrentStream';
|
||||||
|
|
||||||
export type CheckConcurrentStreamResponse = {
|
export interface CheckConcurrentStreamResponse {
|
||||||
stream: ConcurrentStream;
|
stream: ConcurrentStream;
|
||||||
};
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/* istanbul ignore file */
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
export type ClearDownloadsRequest = {
|
|
||||||
clear_finished?: boolean;
|
|
||||||
clear_paused?: boolean;
|
|
||||||
clear_errors?: boolean;
|
|
||||||
};
|
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type ConcurrentStream = {
|
|
||||||
|
export interface ConcurrentStream {
|
||||||
playback_timestamp?: number;
|
playback_timestamp?: number;
|
||||||
unix_timestamp?: number;
|
unix_timestamp?: number;
|
||||||
playing?: boolean;
|
playing?: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type Config = {
|
|
||||||
|
export interface Config {
|
||||||
YoutubeDLMaterial: any;
|
YoutubeDLMaterial: any;
|
||||||
};
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Config } from './Config';
|
import { Config } from './Config';
|
||||||
|
|
||||||
export type ConfigResponse = {
|
export interface ConfigResponse {
|
||||||
config_file: Config;
|
config_file: Config;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type CreateCategoryRequest = {
|
|
||||||
|
export interface CreateCategoryRequest {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Category } from './Category';
|
import { Category } from './Category';
|
||||||
|
|
||||||
export type CreateCategoryResponse = {
|
export interface CreateCategoryResponse {
|
||||||
new_category?: Category;
|
new_category?: Category;
|
||||||
success?: boolean;
|
success?: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,11 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type CreatePlaylistRequest = {
|
import { FileType } from './FileType';
|
||||||
|
|
||||||
|
export interface CreatePlaylistRequest {
|
||||||
playlistName: string;
|
playlistName: string;
|
||||||
uids: Array<string>;
|
uids: Array<string>;
|
||||||
|
type: FileType;
|
||||||
thumbnailURL: string;
|
thumbnailURL: string;
|
||||||
};
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Playlist } from './Playlist';
|
import { Playlist } from './Playlist';
|
||||||
|
|
||||||
export type CreatePlaylistResponse = {
|
export interface CreatePlaylistResponse {
|
||||||
new_playlist: Playlist;
|
new_playlist: Playlist;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type CropFileSettings = {
|
|
||||||
|
export interface CropFileSettings {
|
||||||
cropFileStart: number;
|
cropFileStart: number;
|
||||||
cropFileEnd: number;
|
cropFileEnd: number;
|
||||||
};
|
}
|
||||||
@@ -2,12 +2,13 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type DBBackup = {
|
|
||||||
|
export interface DBBackup {
|
||||||
name: string;
|
name: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
size: number;
|
size: number;
|
||||||
source: DBBackup.source;
|
source: DBBackup.source;
|
||||||
};
|
}
|
||||||
|
|
||||||
export namespace DBBackup {
|
export namespace DBBackup {
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { TableInfo } from './TableInfo';
|
import { TableInfo } from './TableInfo';
|
||||||
|
|
||||||
export type DBInfoResponse = {
|
export interface DBInfoResponse {
|
||||||
using_local_db?: boolean;
|
using_local_db?: boolean;
|
||||||
stats_by_table?: {
|
stats_by_table?: {
|
||||||
files?: TableInfo;
|
files?: TableInfo,
|
||||||
playlists?: TableInfo;
|
playlists?: TableInfo,
|
||||||
categories?: TableInfo;
|
categories?: TableInfo,
|
||||||
subscriptions?: TableInfo;
|
subscriptions?: TableInfo,
|
||||||
users?: TableInfo;
|
users?: TableInfo,
|
||||||
roles?: TableInfo;
|
roles?: TableInfo,
|
||||||
download_queue?: TableInfo;
|
download_queue?: TableInfo,
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
@@ -2,16 +2,11 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Category } from './Category';
|
|
||||||
|
|
||||||
export type DatabaseFile = {
|
export interface DatabaseFile {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
/**
|
|
||||||
* Backup if thumbnailPath is not defined
|
|
||||||
*/
|
|
||||||
thumbnailURL: string;
|
thumbnailURL: string;
|
||||||
thumbnailPath?: string;
|
|
||||||
isAudio: boolean;
|
isAudio: boolean;
|
||||||
/**
|
/**
|
||||||
* In seconds
|
* In seconds
|
||||||
@@ -19,25 +14,9 @@ export type DatabaseFile = {
|
|||||||
duration: number;
|
duration: number;
|
||||||
url: string;
|
url: string;
|
||||||
uploader: string;
|
uploader: string;
|
||||||
/**
|
|
||||||
* In bytes
|
|
||||||
*/
|
|
||||||
size: number;
|
size: number;
|
||||||
path: string;
|
path: string;
|
||||||
upload_date: string;
|
upload_date: string;
|
||||||
uid: string;
|
uid: string;
|
||||||
sharingEnabled?: boolean;
|
sharingEnabled?: boolean;
|
||||||
category?: Category;
|
}
|
||||||
view_count?: number;
|
|
||||||
local_view_count?: number;
|
|
||||||
sub_id?: string;
|
|
||||||
registered?: number;
|
|
||||||
/**
|
|
||||||
* In pixels, only for videos
|
|
||||||
*/
|
|
||||||
height?: number;
|
|
||||||
/**
|
|
||||||
* In Kbps
|
|
||||||
*/
|
|
||||||
abr?: number;
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/* istanbul ignore file */
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
export type DeleteAllFilesResponse = {
|
|
||||||
/**
|
|
||||||
* Number of files found matching search parameters
|
|
||||||
*/
|
|
||||||
file_count?: number;
|
|
||||||
/**
|
|
||||||
* Number of files removed
|
|
||||||
*/
|
|
||||||
delete_count?: number;
|
|
||||||
};
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type DeleteCategoryRequest = {
|
|
||||||
|
export interface DeleteCategoryRequest {
|
||||||
category_uid: string;
|
category_uid: string;
|
||||||
};
|
}
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type DeleteMp3Mp4Request = {
|
|
||||||
|
export interface DeleteMp3Mp4Request {
|
||||||
uid: string;
|
uid: string;
|
||||||
blacklistMode?: boolean;
|
blacklistMode?: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type DeletePlaylistRequest = {
|
import { FileType } from './FileType';
|
||||||
|
|
||||||
|
export interface DeletePlaylistRequest {
|
||||||
playlist_id: string;
|
playlist_id: string;
|
||||||
};
|
type: FileType;
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { SubscriptionRequestData } from './SubscriptionRequestData';
|
import { SubscriptionRequestData } from './SubscriptionRequestData';
|
||||||
|
|
||||||
export type DeleteSubscriptionFileRequest = {
|
export interface DeleteSubscriptionFileRequest {
|
||||||
file: string;
|
file: string;
|
||||||
file_uid?: string;
|
file_uid?: string;
|
||||||
sub: SubscriptionRequestData;
|
sub: SubscriptionRequestData;
|
||||||
@@ -12,4 +12,4 @@ export type DeleteSubscriptionFileRequest = {
|
|||||||
* If true, does not remove id from archive. Only valid if youtube-dl archive is enabled in settings.
|
* If true, does not remove id from archive. Only valid if youtube-dl archive is enabled in settings.
|
||||||
*/
|
*/
|
||||||
deleteForever?: boolean;
|
deleteForever?: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type DeleteUserRequest = {
|
|
||||||
|
export interface DeleteUserRequest {
|
||||||
uid: string;
|
uid: string;
|
||||||
};
|
}
|
||||||
7
src/api-types/models/Dictionary.ts
Normal file
7
src/api-types/models/Dictionary.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
export type Dictionary<T> = {
|
||||||
|
[key: string]: T;
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type Download = {
|
|
||||||
|
export interface Download {
|
||||||
uid: string;
|
uid: string;
|
||||||
ui_uid?: string;
|
ui_uid?: string;
|
||||||
running: boolean;
|
running: boolean;
|
||||||
@@ -22,5 +23,4 @@ export type Download = {
|
|||||||
user_uid?: string;
|
user_uid?: string;
|
||||||
sub_id?: string;
|
sub_id?: string;
|
||||||
sub_name?: string;
|
sub_name?: string;
|
||||||
prefetched_info?: any;
|
}
|
||||||
};
|
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type DownloadArchiveRequest = {
|
|
||||||
|
export interface DownloadArchiveRequest {
|
||||||
sub: {
|
sub: {
|
||||||
archive_dir: string;
|
archive_dir: string,
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { FileType } from './FileType';
|
import { FileType } from './FileType';
|
||||||
|
|
||||||
export type DownloadFileRequest = {
|
export interface DownloadFileRequest {
|
||||||
uid?: string;
|
uid?: string;
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
sub_id?: string;
|
sub_id?: string;
|
||||||
playlist_id?: string;
|
playlist_id?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
type?: FileType;
|
type?: FileType;
|
||||||
};
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { CropFileSettings } from './CropFileSettings';
|
import { CropFileSettings } from './CropFileSettings';
|
||||||
import type { FileType } from './FileType';
|
import { FileType } from './FileType';
|
||||||
|
|
||||||
export type DownloadRequest = {
|
export interface DownloadRequest {
|
||||||
url: string;
|
url: string;
|
||||||
/**
|
/**
|
||||||
* Video format code. Overrides other quality options.
|
* Video format code. Overrides other quality options.
|
||||||
@@ -41,4 +41,4 @@ export type DownloadRequest = {
|
|||||||
maxBitrate?: string;
|
maxBitrate?: string;
|
||||||
type?: FileType;
|
type?: FileType;
|
||||||
cropFileSettings?: CropFileSettings;
|
cropFileSettings?: CropFileSettings;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Download } from './Download';
|
import { Download } from './Download';
|
||||||
|
|
||||||
export type DownloadResponse = {
|
export interface DownloadResponse {
|
||||||
download?: Download;
|
download?: Download;
|
||||||
};
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { FileType } from './FileType';
|
import { FileType } from './FileType';
|
||||||
import type { Subscription } from './Subscription';
|
import { Subscription } from './Subscription';
|
||||||
|
|
||||||
export type DownloadTwitchChatByVODIDRequest = {
|
export interface DownloadTwitchChatByVODIDRequest {
|
||||||
/**
|
/**
|
||||||
* File ID
|
* File ID
|
||||||
*/
|
*/
|
||||||
@@ -20,4 +20,4 @@ export type DownloadTwitchChatByVODIDRequest = {
|
|||||||
*/
|
*/
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
sub?: Subscription;
|
sub?: Subscription;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { TwitchChatMessage } from './TwitchChatMessage';
|
import { TwitchChatMessage } from './TwitchChatMessage';
|
||||||
|
|
||||||
export type DownloadTwitchChatByVODIDResponse = {
|
export interface DownloadTwitchChatByVODIDResponse {
|
||||||
chat: Array<TwitchChatMessage>;
|
chat: Array<TwitchChatMessage>;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type DownloadVideosForSubscriptionRequest = {
|
|
||||||
|
export interface DownloadVideosForSubscriptionRequest {
|
||||||
subID: string;
|
subID: string;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type binary = {
|
|
||||||
|
export interface File {
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
|
|
||||||
export enum FileType {
|
export enum FileType {
|
||||||
AUDIO = 'audio',
|
AUDIO = 'audio',
|
||||||
VIDEO = 'video',
|
VIDEO = 'video',
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
/* istanbul ignore file */
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
export enum FileTypeFilter {
|
|
||||||
AUDIO_ONLY = 'audio_only',
|
|
||||||
VIDEO_ONLY = 'video_only',
|
|
||||||
BOTH = 'both',
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GenerateArgsResponse = {
|
|
||||||
|
export interface GenerateArgsResponse {
|
||||||
args?: Array<string>;
|
args?: Array<string>;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GenerateNewApiKeyResponse = {
|
|
||||||
|
export interface GenerateNewApiKeyResponse {
|
||||||
new_api_key: string;
|
new_api_key: string;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Category } from './Category';
|
import { Category } from './Category';
|
||||||
|
|
||||||
export type GetAllCategoriesResponse = {
|
export interface GetAllCategoriesResponse {
|
||||||
categories: Array<Category>;
|
categories: Array<Category>;
|
||||||
};
|
}
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetAllDownloadsRequest = {
|
|
||||||
|
export interface GetAllDownloadsRequest {
|
||||||
/**
|
/**
|
||||||
* Filters downloads with the array
|
* Filters downloads with the array
|
||||||
*/
|
*/
|
||||||
uids?: Array<string> | null;
|
uids?: Array<string> | null;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Download } from './Download';
|
import { Download } from './Download';
|
||||||
|
|
||||||
export type GetAllDownloadsResponse = {
|
export interface GetAllDownloadsResponse {
|
||||||
downloads?: Array<Download>;
|
downloads?: Array<Download>;
|
||||||
};
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/* istanbul ignore file */
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import type { FileTypeFilter } from './FileTypeFilter';
|
|
||||||
import type { Sort } from './Sort';
|
|
||||||
|
|
||||||
export type GetAllFilesRequest = {
|
|
||||||
sort?: Sort;
|
|
||||||
range?: Array<number>;
|
|
||||||
/**
|
|
||||||
* Filter files by title
|
|
||||||
*/
|
|
||||||
text_search?: string;
|
|
||||||
file_type_filter?: FileTypeFilter;
|
|
||||||
/**
|
|
||||||
* Include if you want to filter by subscription
|
|
||||||
*/
|
|
||||||
sub_id?: string;
|
|
||||||
};
|
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { DatabaseFile } from './DatabaseFile';
|
import { DatabaseFile } from './DatabaseFile';
|
||||||
import type { Playlist } from './Playlist';
|
import { Playlist } from './Playlist';
|
||||||
|
|
||||||
export type GetAllFilesResponse = {
|
export interface GetAllFilesResponse {
|
||||||
files: Array<DatabaseFile>;
|
files: Array<DatabaseFile>;
|
||||||
/**
|
/**
|
||||||
* All video playlists
|
* All video playlists
|
||||||
*/
|
*/
|
||||||
playlists: Array<Playlist>;
|
playlists: Array<Playlist>;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Subscription } from './Subscription';
|
import { Subscription } from './Subscription';
|
||||||
|
|
||||||
export type GetAllSubscriptionsResponse = {
|
export interface GetAllSubscriptionsResponse {
|
||||||
subscriptions: Array<Subscription>;
|
subscriptions: Array<Subscription>;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Task } from './Task';
|
import { Task } from './Task';
|
||||||
|
|
||||||
export type GetAllTasksResponse = {
|
export interface GetAllTasksResponse {
|
||||||
tasks?: Array<Task>;
|
tasks?: Array<Task>;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { DBBackup } from './DBBackup';
|
import { DBBackup } from './DBBackup';
|
||||||
|
|
||||||
export type GetDBBackupsResponse = {
|
export interface GetDBBackupsResponse {
|
||||||
tasks?: Array<DBBackup>;
|
tasks?: Array<DBBackup>;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetDownloadRequest = {
|
|
||||||
|
export interface GetDownloadRequest {
|
||||||
download_uid: string;
|
download_uid: string;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Download } from './Download';
|
import { Download } from './Download';
|
||||||
|
|
||||||
export type GetDownloadResponse = {
|
export interface GetDownloadResponse {
|
||||||
download?: Download;
|
download?: Download;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetFileFormatsRequest = {
|
|
||||||
|
export interface GetFileFormatsRequest {
|
||||||
url?: string;
|
url?: string;
|
||||||
};
|
}
|
||||||
@@ -2,9 +2,11 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetFileFormatsResponse = {
|
import { File } from './File';
|
||||||
|
|
||||||
|
export interface GetFileFormatsResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
result: {
|
result: {
|
||||||
formats?: Array<any>;
|
formats?: Array<any>,
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { FileType } from './FileType';
|
import { FileType } from './FileType';
|
||||||
|
|
||||||
export type GetFileRequest = {
|
export interface GetFileRequest {
|
||||||
/**
|
/**
|
||||||
* Video UID
|
* Video UID
|
||||||
*/
|
*/
|
||||||
@@ -14,4 +14,4 @@ export type GetFileRequest = {
|
|||||||
* User UID
|
* User UID
|
||||||
*/
|
*/
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
};
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { DatabaseFile } from './DatabaseFile';
|
import { DatabaseFile } from './DatabaseFile';
|
||||||
|
|
||||||
export type GetFileResponse = {
|
export interface GetFileResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
file?: DatabaseFile;
|
file?: DatabaseFile;
|
||||||
};
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { FileType } from './FileType';
|
import { FileType } from './FileType';
|
||||||
import type { Subscription } from './Subscription';
|
import { Subscription } from './Subscription';
|
||||||
|
|
||||||
export type GetFullTwitchChatRequest = {
|
export interface GetFullTwitchChatRequest {
|
||||||
/**
|
/**
|
||||||
* File ID
|
* File ID
|
||||||
*/
|
*/
|
||||||
@@ -16,4 +16,4 @@ export type GetFullTwitchChatRequest = {
|
|||||||
*/
|
*/
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
sub?: Subscription;
|
sub?: Subscription;
|
||||||
};
|
}
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetFullTwitchChatResponse = {
|
|
||||||
|
export interface GetFullTwitchChatResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetLogsRequest = {
|
|
||||||
|
export interface GetLogsRequest {
|
||||||
lines?: number;
|
lines?: number;
|
||||||
};
|
}
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetLogsResponse = {
|
|
||||||
|
export interface GetLogsResponse {
|
||||||
/**
|
/**
|
||||||
* Number of lines to retrieve from the bottom
|
* Number of lines to retrieve from the bottom
|
||||||
*/
|
*/
|
||||||
logs?: string;
|
logs?: string;
|
||||||
success?: boolean;
|
success?: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { DatabaseFile } from './DatabaseFile';
|
import { DatabaseFile } from './DatabaseFile';
|
||||||
import type { Playlist } from './Playlist';
|
import { Playlist } from './Playlist';
|
||||||
|
|
||||||
export type GetMp3sResponse = {
|
export interface GetMp3sResponse {
|
||||||
mp3s: Array<DatabaseFile>;
|
mp3s: Array<DatabaseFile>;
|
||||||
/**
|
/**
|
||||||
* All audio playlists
|
* All audio playlists
|
||||||
*/
|
*/
|
||||||
playlists: Array<Playlist>;
|
playlists: Array<Playlist>;
|
||||||
};
|
}
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { DatabaseFile } from './DatabaseFile';
|
import { DatabaseFile } from './DatabaseFile';
|
||||||
import type { Playlist } from './Playlist';
|
import { Playlist } from './Playlist';
|
||||||
|
|
||||||
export type GetMp4sResponse = {
|
export interface GetMp4sResponse {
|
||||||
mp4s: Array<DatabaseFile>;
|
mp4s: Array<DatabaseFile>;
|
||||||
/**
|
/**
|
||||||
* All video playlists
|
* All video playlists
|
||||||
*/
|
*/
|
||||||
playlists: Array<Playlist>;
|
playlists: Array<Playlist>;
|
||||||
};
|
}
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { FileType } from './FileType';
|
import { FileType } from './FileType';
|
||||||
|
|
||||||
export type GetPlaylistRequest = {
|
export interface GetPlaylistRequest {
|
||||||
playlist_id: string;
|
playlist_id: string;
|
||||||
type?: FileType;
|
type?: FileType;
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
include_file_metadata?: boolean;
|
include_file_metadata?: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,14 +2,11 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { DatabaseFile } from './DatabaseFile';
|
import { FileType } from './FileType';
|
||||||
import type { Playlist } from './Playlist';
|
import { Playlist } from './Playlist';
|
||||||
|
|
||||||
export type GetPlaylistResponse = {
|
export interface GetPlaylistResponse {
|
||||||
playlist: Playlist;
|
playlist: Playlist;
|
||||||
|
type: FileType;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
/**
|
}
|
||||||
* File objects for every uid in the playlist's uids property, in the same order
|
|
||||||
*/
|
|
||||||
file_objs?: Array<DatabaseFile>;
|
|
||||||
};
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type GetPlaylistsRequest = {
|
|
||||||
|
export interface GetPlaylistsRequest {
|
||||||
include_categories?: boolean;
|
include_categories?: boolean;
|
||||||
};
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Playlist } from './Playlist';
|
import { Playlist } from './Playlist';
|
||||||
|
|
||||||
export type GetPlaylistsResponse = {
|
export interface GetPlaylistsResponse {
|
||||||
playlists: Array<Playlist>;
|
playlists: Array<Playlist>;
|
||||||
};
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user