Compare commits

..

2 Commits

Author SHA1 Message Date
Glassed Silver
e1a48f67fa Change of node.js version mention. (12 -> 116)
In relation to PR #609
2022-05-07 11:43:04 +02:00
Glassed Silver
bbb5e8ebb9 Adding Node.js version to README 2022-05-02 08:44:01 +02:00
178 changed files with 2499 additions and 9310 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -6,25 +6,19 @@ on:
tags:
description: 'Docker tags'
required: true
release:
types: [published]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v2
- name: Set hash
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: create-json
id: create-json
uses: jsdaniell/create-json@1.1.2
@@ -32,49 +26,15 @@ jobs:
name: "version.json"
json: '{"type": "docker", "tag": "latest", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
dir: 'backend/'
- name: Set image tag
id: tags
run: |
if [ ${{ github.event.action }} == "workflow_dispatch" ]; 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: |
raw=${{ steps.tags.outputs.tags }}
raw=latest
- name: setup platform emulator
uses: docker/setup-qemu-action@v1
- name: setup multi-arch docker build
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
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
uses: docker/build-push-action@v2
with:
@@ -82,5 +42,4 @@ jobs:
file: ./Dockerfile
platforms: linux/amd64,linux/arm,linux/arm64/v8
push: true
tags: ${{ steps.docker-meta.outputs.tags }}
labels: ${{ steps.docker-meta.outputs.labels }}
tags: ${{ github.event.inputs.tags }}

View File

@@ -8,9 +8,9 @@ on:
- '.vscode/**'
- 'chrome-extension/**'
- 'releases/**'
- '**/**.md'
- '**.crx'
- '**.pem'
- '**.md'
- '.dockerignore'
- '.gitignore'
@@ -20,15 +20,12 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v2
- name: Set hash
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: create-json
id: create-json
uses: jsdaniell/create-json@1.1.2
@@ -36,42 +33,15 @@ jobs:
name: "version.json"
json: '{"type": "docker", "tag": "${{secrets.DOCKERHUB_MASTER_TAG}}", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
dir: 'backend/'
- name: setup platform emulator
uses: docker/setup-qemu-action@v1
- name: setup multi-arch docker build
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
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
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
uses: docker/build-push-action@v2
with:
@@ -79,5 +49,8 @@ jobs:
file: ./Dockerfile
platforms: linux/amd64,linux/arm,linux/arm64/v8
push: true
tags: ${{ steps.docker-meta.outputs.tags }}
labels: ${{ steps.docker-meta.outputs.labels }}
# Defaults:
# DOCKERHUB_USERNAME : tzahi12345
# DOCKERHUB_REPO : youtubedl-material
# DOCKERHUB_MASTER_TAG: nightly
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:${{secrets.DOCKERHUB_MASTER_TAG}}

View File

@@ -1,66 +1,71 @@
# Fetching our ffmpeg
FROM ubuntu:22.04 AS ffmpeg
FROM ubuntu:20.04 AS ffmpeg
ENV DEBIAN_FRONTEND=noninteractive
# Use script due local build compability
COPY ffmpeg-fetch.sh .
RUN sh ./ffmpeg-fetch.sh
COPY docker-build.sh .
RUN sh ./docker-build.sh
FROM ubuntu:20.04 as frontend
# Create our Ubuntu 22.04 with node 16
# Go to 20.04
FROM ubuntu:20.04 AS base
ENV DEBIAN_FRONTEND=noninteractive
ENV UID=1000
ENV GID=1000
ENV USER=youtube
ENV NO_UPDATE_NOTIFIER=true
ENV PM2_HOME=/app/pm2
RUN groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER && \
apt update && \
apt install -y --no-install-recommends curl ca-certificates && \
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/*
RUN apt-get update && apt-get -y install \
curl \
gnupg && \
curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
apt-get -y install \
nodejs \
# YARN: brings along npm, solves dependency conflicts,
# spares us this spaghetti approach: https://stackoverflow.com/a/60547197
yarn && \
apt-get install -f && \
npm config set strict-ssl false && \
npm install -g @angular/cli
# Build frontend
FROM base as frontend
RUN npm install -g @angular/cli
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/" ]
RUN npm install && \
npm run build && \
ls -al /build/backend/public
RUN npm run build
#--------------#
FROM ubuntu:20.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 curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update && apt-get -y install \
npm \
python2 \
python3 \
atomicparsley && \
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
COPY [ "backend/","/app/" ]
COPY --from=ffmpeg /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=ffmpeg /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ]
ENV PM2_HOME=/app/pm2
RUN npm config set strict-ssl false && \
npm install --prod && \
ls -al
npm install pm2 -g && \
npm install && chown -R $UID:$GID ./
# Final image
FROM base
RUN npm install -g pm2 && \
apt update && \
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 atomicparsley && \
apt clean && \
rm -rf /var/lib/apt/lists/*
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/ffprobe", "/usr/local/bin/ffprobe" ]
COPY --chown=$UID:$GID --from=backend ["/app/","/app/"]
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
# Add some persistence data
#VOLUME ["/app/appdata"]
COPY --chown=$UID:$GID [ "/backend/", "/app/" ]
EXPOSE 17442
ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD [ "pm2-runtime","--raw","pm2.config.js" ]
# ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD [ "pm2-runtime", "pm2.config.js" ]

View File

@@ -1,2 +0,0 @@
FROM tzahi12345/youtubedl-material:nightly
CMD [ "pm2-runtime", "pm2.config.js" ]

1
Procfile Normal file
View File

@@ -0,0 +1 @@
web: npm start --prefix backend

View File

@@ -129,27 +129,6 @@ paths:
description: User is not authorized to view the file.
security:
- 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:
post:
tags:
@@ -862,10 +841,17 @@ paths:
- Auth query parameter: []
tags:
- downloader
/api/clearDownloads:
/api/clearFinishedDownloads:
post:
summary: Clear multiple downloads
operationId: post-api-clear-downloads
tags:
- downloader
summary: Clear finished downloads
operationId: post-api-clear-finished-downloads
requestBody:
content:
application/json:
schema:
type: object
responses:
'200':
description: OK
@@ -873,17 +859,8 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/SuccessObject'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ClearDownloadsRequest'
description: ''
description: "Clears multiple downloads based on a given filter."
security:
- Auth query parameter: []
tags:
- downloader
/api/getTask:
post:
summary: Get info for one task
@@ -1530,8 +1507,6 @@ components:
properties:
success:
type: boolean
error:
type: string
FileType:
type: string
enum:
@@ -1632,15 +1607,6 @@ components:
type: array
items:
$ref: '#/components/schemas/Download'
ClearDownloadsRequest:
type: object
properties:
clear_finished:
type: boolean
clear_paused:
type: boolean
clear_errors:
type: boolean
GetTaskRequest:
type: object
properties:
@@ -1761,18 +1727,6 @@ components:
type: boolean
file:
$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:
required:
- uid
@@ -2199,6 +2153,7 @@ components:
type: boolean
result:
allOf:
- $ref: '#/components/schemas/file'
- type: object
properties:
formats:
@@ -2356,9 +2311,6 @@ components:
type: string
thumbnailURL:
type: string
description: Backup if thumbnailPath is not defined
thumbnailPath:
type: string
isAudio:
type: boolean
duration:
@@ -2370,7 +2322,6 @@ components:
type: string
size:
type: number
description: In bytes
path:
type: string
upload_date:
@@ -2379,12 +2330,6 @@ components:
type: string
sharingEnabled:
type: boolean
category:
$ref: '#/components/schemas/Category'
view_count:
type: number
local_view_count:
type: number
Playlist:
required:
- uids
@@ -2414,8 +2359,6 @@ components:
type: number
user_uid:
type: string
auto:
type: boolean
Download:
required:
- url
@@ -2602,6 +2545,28 @@ components:
type: string
passhash:
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:
type: array
items:

View File

@@ -6,7 +6,7 @@
[![GitHub issues badge](https://img.shields.io/github/issues/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/issues)
[![License badge](https://img.shields.io/github/license/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/LICENSE.md)
YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 13](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend.
YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 13](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) 16 on the backend.
Now with [Docker](#Docker) support!

View File

@@ -30,8 +30,7 @@
"src/backend"
],
"styles": [
"src/styles.scss",
"src/bootstrap.min.css"
"src/styles.scss"
],
"scripts": [],
"vendorChunk": true,
@@ -119,8 +118,7 @@
"src/backend"
],
"styles": [
"src/styles.scss",
"src/bootstrap.min.css"
"src/styles.scss"
],
"scripts": []
},
@@ -153,8 +151,7 @@
"tsConfig": "src/tsconfig.spec.json",
"scripts": [],
"styles": [
"src/styles.scss",
"src/bootstrap.min.css"
"src/styles.scss"
],
"assets": [
"src/assets",

View File

@@ -2,7 +2,6 @@
"name": "YoutubeDL-Material",
"description": "An open-source and self-hosted YouTube downloader based on Google's Material Design specifications.",
"repository": "https://github.com/Tzahi12345/YoutubeDL-Material",
"stack": "container",
"logo": "https://i.imgur.com/GPzvPiU.png",
"keywords": ["youtube-dl", "youtubedl-material", "nodejs"]
}

View File

@@ -249,6 +249,14 @@ async function startServer() {
});
}
async function restartServer(is_update = false) {
logger.info(`${is_update ? 'Update complete! ' : ''}Restarting server...`);
// the following line restarts the server through nodemon
fs.writeFileSync(`restart${is_update ? '_update' : '_general'}.json`, 'internal use only');
process.exit(1);
}
async function updateServer(tag) {
// no tag provided means update to the latest version
if (!tag) {
@@ -289,7 +297,7 @@ async function updateServer(tag) {
updating: true,
'details': 'Update complete! Restarting server...'
}
utils.restartServer(true);
restartServer(true);
}, err => {
logger.error(err);
updaterStatus = {
@@ -668,7 +676,6 @@ async function getUrlInfos(url) {
async function startYoutubeDL() {
// auto update youtube-dl
youtubedl_api.verifyBinaryExistsLinux();
const update_available = await youtubedl_api.checkForYoutubeDLUpdate();
if (update_available) await youtubedl_api.updateYoutubeDL(update_available);
}
@@ -757,7 +764,7 @@ app.get('/api/versionInfo', (req, res) => {
app.post('/api/restartServer', optionalJwt, (req, res) => {
// delayed by a little bit so that the client gets a response
setTimeout(() => {utils.restartServer()}, 100);
setTimeout(() => {restartServer()}, 100);
res.send({success: true});
});
@@ -933,34 +940,23 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) {
else if (file_type_filter === 'video_only') filter_obj['isAudio'] = false;
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));
res.send({
files: files,
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) => {
const uid = req.body.uid;
@@ -1376,7 +1372,7 @@ app.post('/api/getPlaylists', optionalJwt, async (req, res) => {
let playlists = await db_api.getRecords('playlists', {user_uid: uuid});
if (include_categories) {
const categories = await categories_api.getCategoriesAsPlaylists();
const categories = await categories_api.getCategoriesAsPlaylists(files);
if (categories) {
playlists = playlists.concat(categories);
}
@@ -1680,15 +1676,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 clear_finished = req.body.clear_finished;
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});
const success = db_api.removeAllRecords('download_queue', {finished: true, user_uid: user_uid});
res.send({success: success});
});
@@ -1812,7 +1802,6 @@ app.post('/api/updateTaskData', optionalJwt, async (req, res) => {
app.post('/api/getDBBackups', optionalJwt, async (req, res) => {
const backup_dir = path.join('appdata', 'db_backup');
fs.ensureDirSync(backup_dir);
const db_backups = [];
const candidate_backups = await utils.recFindByExt(backup_dir, 'bak', null, [], false);

View File

@@ -171,12 +171,8 @@ exports.registerUser = async function(req, res) {
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});
if (!user) {
if (config_api.getConfigItem('ytdl_auth_method') === 'internal') logger.error(`User ${username} not found`);
return false;
}
if (!user) { logger.error(`User ${username} not found`); return false }
if (user.auth_method && user.auth_method !== 'internal') { return false }
return await bcrypt.compare(password, user.passhash) ? user : false;
}

View File

@@ -55,18 +55,17 @@ async function getCategories() {
return categories ? categories : null;
}
async function getCategoriesAsPlaylists() {
async function getCategoriesAsPlaylists(files = null) {
const categories_as_playlists = [];
const available_categories = await getCategories();
if (available_categories) {
if (available_categories && files) {
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) {
category['thumbnailURL'] = files_that_match[0].thumbnailURL;
category['thumbnailPath'] = files_that_match[0].thumbnailPath;
category['duration'] = files_that_match.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
category['id'] = category['uid'];
category['auto'] = true;
categories_as_playlists.push(category);
}
}

View File

@@ -127,7 +127,7 @@ function setConfigItem(key, value) {
success = setConfigFile(config_json);
return success;
}
};
function setConfigItems(items) {
let success = false;

View File

@@ -222,83 +222,4 @@ exports.AVAILABLE_PERMISSIONS = [
exports.DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
// args that have a value after it (e.g. -o <output> or -f <format>)
const YTDL_ARGS_WITH_VALUES = [
'--default-search',
'--config-location',
'--proxy',
'--socket-timeout',
'--source-address',
'--geo-verification-proxy',
'--geo-bypass-country',
'--geo-bypass-ip-block',
'--playlist-start',
'--playlist-end',
'--playlist-items',
'--match-title',
'--reject-title',
'--max-downloads',
'--min-filesize',
'--max-filesize',
'--date',
'--datebefore',
'--dateafter',
'--min-views',
'--max-views',
'--match-filter',
'--age-limit',
'--download-archive',
'-r',
'--limit-rate',
'-R',
'--retries',
'--fragment-retries',
'--buffer-size',
'--http-chunk-size',
'--external-downloader',
'--external-downloader-args',
'-a',
'--batch-file',
'-o',
'--output',
'--output-na-placeholder',
'--autonumber-start',
'--load-info-json',
'--cookies',
'--cache-dir',
'--encoding',
'--user-agent',
'--referer',
'--add-header',
'--sleep-interval',
'--max-sleep-interval',
'-f',
'--format',
'--merge-output-format',
'--sub-format',
'--sub-lang',
'-u',
'--username',
'-p',
'--password',
'-2',
'--twofactor',
'--video-password',
'--ap-mso',
'--ap-username',
'--ap-password',
'--audio-format',
'--audio-quality',
'--recode-video',
'--postprocessor-args',
'--metadata-from-title',
'--fixup',
'--ffmpeg-location',
'--exec',
'--convert-subs'
];
// we're using a Set here for performance
exports.YTDL_ARGS_WITH_VALUES = new Set(YTDL_ARGS_WITH_VALUES);
exports.CURRENT_VERSION = 'v4.2';

View File

@@ -85,6 +85,8 @@ exports.initialize = (input_db, input_users_db) => {
}
exports.connectToDB = async (retries = 5, no_fallback = false, custom_connection_string = null) => {
using_local_db = config_api.getConfigItem('ytdl_use_local_db'); // verify
if (using_local_db && !custom_connection_string) return;
const success = await exports._connectToDB(custom_connection_string);
if (success) return true;
@@ -387,9 +389,9 @@ exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = fal
if (!playlist) {
playlist = await exports.getRecord('categories', {uid: playlist_id});
if (playlist) {
const uids = (await exports.getRecords('files', {'category.uid': playlist_id})).map(file => file.uid);
playlist['uids'] = uids;
playlist['auto'] = true;
// category found
const files = await exports.getFiles(user_uid);
utils.addUIDsToCategory(playlist, files);
}
}
@@ -629,7 +631,7 @@ exports.bulkInsertRecordsIntoTable = async (table, docs) => {
exports.getRecord = async (table, filter_obj) => {
// local db override
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);
@@ -638,7 +640,7 @@ exports.getRecord = async (table, filter_obj) => {
exports.getRecords = async (table, filter_obj = null, return_count = false, sort = null, range = null) => {
// local db override
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) {
cursor = cursor.sort((a, b) => (a[sort['by']] > b[sort['by']] ? sort['order'] : sort['order']*-1));
}
@@ -664,7 +666,7 @@ exports.getRecords = async (table, filter_obj = null, return_count = false, sort
exports.updateRecord = async (table, filter_obj, update_obj) => {
// local db override
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;
}
@@ -677,7 +679,7 @@ exports.updateRecord = async (table, filter_obj, update_obj) => {
exports.updateRecords = async (table, filter_obj, update_obj) => {
// local db override
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;
}
@@ -722,7 +724,7 @@ exports.bulkUpdateRecords = async (table, key_label, update_obj) => {
exports.pushToRecordsArray = async (table, filter_obj, key, value) => {
// local db override
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;
}
@@ -733,7 +735,7 @@ exports.pushToRecordsArray = async (table, filter_obj, key, value) => {
exports.pullFromRecordsArray = async (table, filter_obj, key, value) => {
// local db override
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;
}
@@ -746,7 +748,7 @@ exports.pullFromRecordsArray = async (table, filter_obj, key, value) => {
exports.removeRecord = async (table, filter_obj) => {
// local db override
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;
}
@@ -757,7 +759,7 @@ exports.removeRecord = async (table, filter_obj) => {
// exports.removeRecordsByUIDBulk = async (table, uids) => {
// // local db override
// 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;
// }
@@ -821,7 +823,7 @@ exports.removeAllRecords = async (table = null, filter_obj = null) => {
if (using_local_db) {
for (let i = 0; i < tables_to_remove.length; 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();
logger.debug(`Successfully removed records from ${table_to_remove}`);
}
@@ -1075,13 +1077,8 @@ exports.transferDB = async (local_to_remote) => {
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
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 return_val = db_path[operation](record => {
if (!filter_props) return true;
@@ -1090,20 +1087,14 @@ exports.applyFilterLocalDB = (db_path, filter_obj, operation) => {
const filter_prop = filter_props[i];
const filter_prop_value = filter_obj[filter_prop];
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 {
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);
} else if ('$ne' in filter_prop_value) {
filtered &= filter_prop in record && record[filter_prop] !== filter_prop_value['$ne'];
}
} else {
// handle case of nested property check
if (filter_prop.includes('.'))
filtered &= utils.searchObjectByString(record, filter_prop) === filter_prop_value;
else
filtered &= record[filter_prop] === filter_prop_value;
filtered &= record[filter_prop] === filter_prop_value;
}
}
}

View File

@@ -108,7 +108,6 @@ exports.clearDownload = async (download_uid) => {
}
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});
}
@@ -187,7 +186,7 @@ async function collectInfo(download_uid) {
let args = await exports.generateArgs(url, type, options, download['user_uid']);
// get video info prior to download
let info = await exports.getVideoInfoByURL(url, args, download_uid);
let info = await getVideoInfoByURL(url, args, download_uid);
if (!info) {
// info failed, error presumably already recorded
@@ -204,11 +203,9 @@ async function collectInfo(download_uid) {
options.customOutput = category['custom_output'];
options.noRelativePath = true;
args = await exports.generateArgs(url, type, options, download['user_uid']);
info = await exports.getVideoInfoByURL(url, args, download_uid);
info = await getVideoInfoByURL(url, args, download_uid);
}
download['category'] = category;
// setup info required to calculate download progress
const expected_file_size = utils.getExpectedFileSize(info);
@@ -488,7 +485,7 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
}
if (options.additionalArgs && options.additionalArgs !== '') {
downloadConfig = utils.injectArgs(downloadConfig, options.additionalArgs.split(',,'));
downloadConfig = downloadConfig.concat(options.additionalArgs.split(',,'));
}
const rate_limit = config_api.getConfigItem('ytdl_download_rate_limit');
@@ -510,7 +507,7 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
return downloadConfig;
}
exports.getVideoInfoByURL = async (url, args = [], download_uid = null) => {
async function getVideoInfoByURL(url, args = [], download_uid = null) {
return new Promise(resolve => {
// remove bad args
const new_args = [...args];

View File

@@ -11,7 +11,7 @@ fi
# chown current working directory to current user
if [ "$*" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
find . \! -user "$UID" -exec chown "$UID:$GID" -R '{}' + || echo "WARNING! Could not change directory ownership. If you manage permissions externally this is fine, otherwise you may experience issues when downloading or deleting videos."
exec gosu "$UID:$GID" "$0" "$@"
exec su-exec "$UID:$GID" "$0" "$@"
fi
exec "$@"

View File

@@ -1,58 +0,0 @@
#!/bin/sh
# INTERACTIVE PERMISSIONS FIX SCRIPT FOR YTDL-M
# Date: 2022-05-03
# 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:
# chmod -R +x ./fix-scripts/
# ./fix-scripts/001-fix_download_permissions.sh
# User defines / Docker env defaults
PATH_SUBS=/app/subscriptions
PATH_AUDIO=/app/audio
PATH_VIDS=/app/video
clear -x
echo "\n"
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - # horizontal line
echo "Welcome to the INTERACTIVE PERMISSIONS FIX SCRIPT FOR YTDL-M."
echo "This script will set YTDL-M's download paths' owner to ${USER} (${UID}:${GID})"
echo "and permissions to the default of 644."
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - # horizontal line
echo "\n"
# check whether dirs exist
i=0
[ -d $PATH_SUBS ] && i=$((i+1)) && echo "✔ (${i}/3) Found Subscriptions directory at ${PATH_SUBS}"
[ -d $PATH_AUDIO ] && i=$((i+1)) && echo "✔ (${i}/3) Found Audio directory at ${PATH_AUDIO}"
[ -d $PATH_VIDS ] && i=$((i+1)) && echo "✔ (${i}/3) Found Video directory at ${PATH_VIDS}"
# Ask to proceed or cancel, exit on missing paths
case $i in
0)
echo "\nCouldn't find any download path to fix permissions for! \nPlease edit this script to configure!"
exit 2;;
3)
echo "\nFound all download paths to fix permissions for. \nProceed? (Y/N)";;
*)
echo "\nOnly found ${i} out of 3 download paths! Something about this script's config must be wrong. \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
echo "\n Running jobs now... (this may take a while)\n"
[ -d $PATH_SUBS ] && chown "$UID:$GID" -R $PATH_SUBS && echo "✔ Set owner of ${PATH_SUBS} to ${USER}."
[ -d $PATH_SUBS ] && chmod 644 -R $PATH_SUBS && echo "✔ Set permissions of ${PATH_SUBS} to 644."
[ -d $PATH_AUDIO ] && chown "$UID:$GID" -R $PATH_AUDIO && echo "✔ Set owner of ${PATH_AUDIO} to ${USER}."
[ -d $PATH_AUDIO ] && chmod 644 -R $PATH_AUDIO && echo "✔ Set permissions of ${PATH_AUDIO} to 644."
[ -d $PATH_VIDS ] && chown "$UID:$GID" -R $PATH_VIDS && echo "✔ Set owner of ${PATH_VIDS} to ${USER}."
[ -d $PATH_VIDS ] && chmod 644 -R $PATH_VIDS && echo "✔ Set permissions of ${PATH_VIDS} to 644."
echo "\n✔ Done."
echo "\n If you noticed file access errors those MAY be due to currently running downloads."
echo " Feel free to re-run this script, however download parts should have correct file permissions anyhow. :)"
exit
else
echo "\nOkay, bye."
fi

View File

@@ -148,7 +148,6 @@ exports.updateTaskSchedule = async (task_key, schedule) => {
await db_api.updateRecord('tasks', {key: task_key}, {schedule: schedule});
if (TASKS[task_key]['job']) {
TASKS[task_key]['job'].cancel();
TASKS[task_key]['job'] = null;
}
if (schedule) {
TASKS[task_key]['job'] = scheduleJob(task_key, schedule);

View File

@@ -42,25 +42,6 @@ const { uuid } = require('uuidv4');
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('Import', async function() {
@@ -233,7 +214,7 @@ describe('Database', async function() {
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
const uid = uuid();
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();
let success = await db_api.bulkInsertRecordsIntoTable('test', test_records);
@@ -254,30 +235,6 @@ describe('Database', async function() {
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() {
@@ -296,12 +253,10 @@ describe('Multi User', async function() {
assert(user);
});
});
describe('Video player - normal', async function() {
await db_api.removeRecord('files', {uid: sample_video_json['uid']});
await db_api.insertRecordIntoTable('files', sample_video_json);
const video_to_test = sample_video_json['uid'];
describe('Video player - normal', function() {
const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e';
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);
});
@@ -386,9 +341,7 @@ describe('Downloader', function() {
});
it('Get file info', async function() {
this.timeout(300000);
const info = await downloader_api.getVideoInfoByURL(url);
assert(!!info);
});
it('Download file', async function() {
@@ -407,23 +360,20 @@ describe('Downloader', 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() {
const args = await downloader_api.generateArgs(url, 'video', options);
assert(args.length > 0);
console.log(args);
});
it('Generate args - subscription', async function() {
subscriptions_api.initialize(db_api, logger);
const sub = await subscriptions_api.getSubscription(sub_id);
const sub_options = subscriptions_api.generateOptionsForSubscriptionDownload(sub, 'admin');
const args_normal = await downloader_api.generateArgs(url, 'video', options);
const args_sub = await downloader_api.generateArgs(url, 'video', sub_options, 'admin');
console.log(JSON.stringify(args_normal) !== JSON.stringify(args_sub));
const args = await downloader_api.generateArgs(url, 'video', sub_options, 'admin');
console.log(args);
});
it('Generate kodi NFO file', async function() {
@@ -436,21 +386,6 @@ describe('Downloader', function() {
assert(fs.existsSync(nfo_file_path), true);
fs.unlinkSync(nfo_file_path);
});
it('Inject args', async function() {
const original_args1 = ['--no-resize-buffer', '-o', '%(title)s', '--no-mtime'];
const new_args1 = ['--age-limit', '25', '--yes-playlist', '--abort-on-error', '-o', '%(id)s'];
const updated_args1 = utils.injectArgs(original_args1, new_args1);
const expected_args1 = ['--no-resize-buffer', '--no-mtime', '--age-limit', '25', '--yes-playlist', '--abort-on-error', '-o', '%(id)s'];
assert(JSON.stringify(updated_args1), JSON.stringify(expected_args1));
const original_args2 = ['-o', '%(title)s.%(ext)s', '--write-info-json', '--print-json', '--audio-quality', '0', '-x', '--audio-format', 'mp3'];
const new_args2 = ['--add-metadata', '--embed-thumbnail', '--convert-thumbnails', 'jpg'];
const updated_args2 = utils.injectArgs(original_args2, new_args2);
const expected_args2 = ['-o', '%(title)s.%(ext)s', '--write-info-json', '--print-json', '--audio-quality', '0', '-x', '--audio-format', 'mp3', '--add-metadata', '--embed-thumbnail', '--convert_thumbnails', 'jpg'];
console.log(updated_args2);
assert(JSON.stringify(updated_args2), JSON.stringify(expected_args2));
});
});
describe('Tasks', function() {
@@ -467,7 +402,7 @@ describe('Tasks', function() {
};
tasks_api.TASKS['dummy_task'] = dummy_task;
await tasks_api.setupTasks();
await tasks_api.initialize();
});
it('Backup db', async function() {
const backups_original = await utils.recFindByExt('appdata', 'bak');
@@ -479,13 +414,12 @@ describe('Tasks', function() {
});
it('Check for missing files', async function() {
this.timeout(300000);
await db_api.removeAllRecords('files', {uid: 'test'});
const test_missing_file = {uid: 'test', path: 'test/missing_file.mp4'};
await db_api.insertRecordIntoTable('files', test_missing_file);
await tasks_api.executeTask('missing_files_check');
const missing_file_db_record = await db_api.getRecord('files', {uid: 'test'});
assert(!missing_file_db_record, true);
const task_obj = await db_api.getRecord('tasks', {key: 'missing_files_check'});
assert(task_obj['data'] && task_obj['data']['uids'] && task_obj['data']['uids'].length >= 1, true);
});
it('Check for duplicate files', async function() {
@@ -498,13 +432,10 @@ describe('Tasks', function() {
await db_api.insertRecordIntoTable('files', test_duplicate_file1);
await db_api.insertRecordIntoTable('files', test_duplicate_file2);
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');
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);
assert(task_obj['data'] && task_obj['data']['uids'] && task_obj['data']['uids'].length >= 1, true);
assert(duplicated_record_count == 1, true);
});
@@ -529,36 +460,22 @@ describe('Tasks', function() {
});
it('Schedule and cancel task', async function() {
this.timeout(5000);
const today_one_year = new Date();
today_one_year.setFullYear(today_one_year.getFullYear() + 1);
const schedule_obj = {
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']);
const today_4_hours = new Date();
today_4_hours.setHours(today_4_hours.getHours() + 4);
await tasks_api.updateTaskSchedule('dummy_task', today_4_hours);
assert(!!tasks_api.TASKS['dummy_task']['job'], true);
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']);
assert(!dummy_task_updated['schedule']);
assert(!!tasks_api.TASKS['dummy_task']['job'], false);
});
it('Schedule and run task', async function() {
this.timeout(5000);
const today_1_second = new Date();
today_1_second.setSeconds(today_1_second.getSeconds() + 1);
const schedule_obj = {
type: 'timestamp',
data: { timestamp: today_1_second.getTime() }
}
await tasks_api.updateTaskSchedule('dummy_task', schedule_obj);
assert(!!tasks_api.TASKS['dummy_task']['job']);
await tasks_api.updateTaskSchedule('dummy_task', today_1_second);
assert(!!tasks_api.TASKS['dummy_task']['job'], true);
await utils.wait(2000);
const dummy_task_obj = await db_api.getRecord('tasks', {key: 'dummy_task'});
assert(dummy_task_obj['data']);
assert(dummy_task_obj['data'], true);
});
});

View File

@@ -415,62 +415,6 @@ async function fetchFile(url, path, file_label) {
});
}
async function restartServer(is_update = false) {
logger.info(`${is_update ? 'Update complete! ' : ''}Restarting server...`);
// the following line restarts the server through nodemon
fs.writeFileSync(`restart${is_update ? '_update' : '_general'}.json`, 'internal use only');
process.exit(1);
}
// adds or replaces args according to the following rules:
// - if it already exists and has value, then replace both arg and value
// - if already exists and doesn't have value, ignore
// - if it doesn't exist and has value, add both arg and value
// - if it doesn't exist and doesn't have value, add arg
function injectArgs(original_args, new_args) {
const updated_args = original_args.slice();
try {
for (let i = 0; i < new_args.length; i++) {
const new_arg = new_args[i];
if (!new_arg.startsWith('-') && !new_arg.startsWith('--') && i > 0 && original_args.includes(new_args[i - 1])) continue;
if (CONSTS.YTDL_ARGS_WITH_VALUES.has(new_arg)) {
if (original_args.includes(new_arg)) {
const original_index = original_args.indexOf(new_arg);
original_args.splice(original_index, 2);
}
updated_args.push(new_arg, new_args[i + 1]);
} else {
if (!original_args.includes(new_arg)) {
updated_args.push(new_arg);
}
}
}
} catch (err) {
logger.warn(err);
logger.warn(`Failed to inject args (${new_args}) into (${original_args})`);
}
return updated_args;
}
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;
}
// objects
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) {
@@ -504,6 +448,7 @@ module.exports = {
createContainerZipFile: createContainerZipFile,
durationStringToNumber: durationStringToNumber,
getMatchingCategoryFiles: getMatchingCategoryFiles,
addUIDsToCategory: addUIDsToCategory,
getCurrentDownloader: getCurrentDownloader,
recFindByExt: recFindByExt,
removeFileExtension: removeFileExtension,
@@ -513,8 +458,5 @@ module.exports = {
wait: wait,
checkExistsWithTimeout: checkExistsWithTimeout,
fetchFile: fetchFile,
restartServer: restartServer,
injectArgs: injectArgs,
searchObjectByString: searchObjectByString,
File: File
}

View File

@@ -6,8 +6,6 @@ const utils = require('./utils');
const CONSTS = require('./consts');
const config_api = require('./config.js');
const OUTDATED_VERSION = "2020.00.00";
const is_windows = process.platform === 'win32';
const download_sources = {
@@ -33,7 +31,7 @@ exports.checkForYoutubeDLUpdate = async () => {
let current_app_details_exists = fs.existsSync(CONSTS.DETAILS_BIN_PATH);
if (!current_app_details_exists) {
logger.warn(`Failed to get youtube-dl binary details at location '${CONSTS.DETAILS_BIN_PATH}'. Generating file...`);
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, {"version": OUTDATED_VERSION, "downloader": default_downloader});
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, {"version":"2020.00.00", "downloader": default_downloader});
}
let current_app_details = JSON.parse(fs.readFileSync(CONSTS.DETAILS_BIN_PATH));
let current_version = current_app_details['version'];
@@ -88,18 +86,6 @@ exports.updateYoutubeDL = async (latest_update_version) => {
await download_sources[default_downloader]['func'](latest_update_version);
}
exports.verifyBinaryExistsLinux = () => {
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
if (!is_windows && details_json && (details_json['path'].includes('.exe') || !details_json['path'])) {
details_json['path'] = 'node_modules/youtube-dl/bin/youtube-dl';
details_json['exec'] = 'youtube-dl';
details_json['version'] = OUTDATED_VERSION;
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, details_json);
utils.restartServer();
}
}
async function downloadLatestYoutubeDLBinary(new_version) {
const file_ext = is_windows ? '.exe' : '';

28
chrome-extension.pem Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMX9Wk5SM5cIfY
6ReKX3ybY1rsbNbOzG8ceN7yyeXB0mor8pVsX1MOna2HewOyBuaaYNJRO4tJBxic
7a8zQErfgHL/i/QrVvVCpfJ7xKvq6zij5NYoqd/FBUwawqjeH5/voIcAp9z5Vmsr
kL0sxJUKy6b4IWNp3noU7Nvq2RwxnXQbKDhz8FrX6oQAnDC6gsG5a2OSPsaE4oqw
6nmonORJypmpP5hqyHY8ffXBT2lAxjHT7OnYbaCBe2TQP8+rH6rDBOhjVNtUJ089
ocTQL6LtQEPkcF4yKJmtcOwHl8OPGZs5l9i8xb4j9RuSPkm2lbzZX8sOsdGGoqJZ
q68nYhsHAgMBAAECggEAXmtKEzfPObq88B/kAcgSk+FngMHZzcmR7bgD3GwdSxnQ
dkRI9zvk7eQ35tcUwntAr4Lat6/ILjFqlBmVLxrdXHuF5Xz9jcZLYgKzz61xdYM9
dC6FKF0u5eGIIvbauGAo7jaeGFX1F3Zu5b4lP9kEOGwU1B7sxF0FzsQM5+dtCJgv
We/hWQeF+9gtoVnkCSS/Mq2p0UomXXHW0Bz4+HuHlTR9aiYbviYnotABiLUhZyzt
v5yUaktb9qniBfdLpRlq8cp06xYlTEA9gJpa4Pnok8OWUsbAiW6EiXUSaZ/cchVa
AnO8WWYvVOnnt6WHI3+QdFTnqVjE5TBX4N/7bVhHGQKBgQD0dtbFqp7vZK/jVYvE
z0WPdySOg2ZDmoSfk5ZlR1+Y9zWToHv0qu8zqoOjL8Ubxrh9fGlOow+cCVdkEuaa
jWC2AWetuRvW0Z5A3XMXr0/N/1fgOkTqtp3WNrUPjVJahEg3lN+90opgFoT8swSi
s1oxW0oLcVIlrjhGBXAPCfsAuQKBgQDWBLRhHsRAvGcK5wGuVnxVApTIyBOermsW
3bJt+7+UI+4sYrBAwkWdQG93IG0cQtn48TEPBgmR2fjRF5IFT9M4/u+QOeeByT7I
we7nVtHgSY5ByC9N0mjWbcmSg8fktz/LonjldNC4kWdOFb75fxGf8kOGS5rUaMA4
zHucfB6ZvwKBgQCPHJrysMXGY21MaqIeHzEboaX3ABl37hdBzAa5V6UxSVdGCydF
vmO2HVZey/JaJmWOoKyNaowSzq0oWqBBTg6VvhDR9JHFmoVId9uOvAS+FYN+Mt5x
gWK5KuGoLxVNBC+6yh6JY526TrSfsrU+Aj0Es+qO9FIg2PL8muZVB4S3kQKBgH/5
CDMaxpc/EQ5/2413wZjDllwI51J3USm3Hz6Mzp2ybnSz/lh60k2Zfg1polTH1Lb6
4i7tmUNRZ2sAARyUAuWN64n+VeRRhe1dqZFDZPQMh7fmEAMk0fOGaoXlrt2ghdEq
Mchi9Xun1nHmpu9hgBR4NNBU3RwuFuLfwvprbZDZAoGAWa62QJChE86xQGP1MrL2
SbIzw3cfeP5xdQ3MKldJiy5IkbMR7Z13WZ7FwvPTy0g/onLHD1rqlm1kUMsGRHpD
5vH06PNpKXQ6x8BYaRGtE6P39jLycO/X+WK/lYTrWo1bR+mGCebDh4B5XrwT3gI6
x4Gvz134pZCTyQCf5JCwbQs=
-----END PRIVATE KEY-----

View File

@@ -40,4 +40,4 @@ echo "(4/5) PROVISION - Provide ffmpeg and ffprobe from ffmpeg obtain layer"
cp /tmp/ffmpeg/*/ffmpeg /usr/local/bin/ffmpeg
cp /tmp/ffmpeg/*/ffprobe /usr/local/bin/ffprobe
echo "(5/5) CLEANUP - Remove temporary downloads from ffmpeg obtain layer"
rm -rf /tmp/ffmpeg ffmpeg.txz
rm -rf /tmp/ffmpeg ffmpeg.txz

View File

@@ -1,3 +0,0 @@
build:
docker:
web: Dockerfile.heroku

59
package-lock.json generated
View File

@@ -3295,12 +3295,65 @@
"safer-buffer": "~2.1.0"
}
},
"asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"dev": true,
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true
}
}
},
"assert": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
"integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
"dev": true,
"requires": {
"object-assign": "^4.1.1",
"util": "0.10.3"
},
"dependencies": {
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
"dev": true
},
"util": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"dev": true,
"requires": {
"inherits": "2.0.1"
}
}
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
"assign-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true
},
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
@@ -3314,9 +3367,9 @@
"dev": true
},
"async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": {
"lodash": "^4.17.14"

View File

@@ -12,8 +12,7 @@
"lint": "ng lint",
"e2e": "ng e2e",
"electron": "ng build --base-href ./ && electron .",
"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"
"generate": "openapi --input ./\"Public API v1.yaml\" --output ./src/api-types --exportCore false --exportServices false --exportModels true"
},
"engines": {
"node": "12.3.1",

View File

@@ -4,7 +4,6 @@
export type { AddFileToPlaylistRequest } from './models/AddFileToPlaylistRequest';
export type { BaseChangePermissionsRequest } from './models/BaseChangePermissionsRequest';
export type { binary } from './models/binary';
export type { body_19 } from './models/body_19';
export type { body_20 } from './models/body_20';
export type { Category } from './models/Category';
@@ -13,7 +12,6 @@ export type { ChangeRolePermissionsRequest } from './models/ChangeRolePermission
export type { ChangeUserPermissionsRequest } from './models/ChangeUserPermissionsRequest';
export type { CheckConcurrentStreamRequest } from './models/CheckConcurrentStreamRequest';
export type { CheckConcurrentStreamResponse } from './models/CheckConcurrentStreamResponse';
export type { ClearDownloadsRequest } from './models/ClearDownloadsRequest';
export type { ConcurrentStream } from './models/ConcurrentStream';
export type { Config } from './models/Config';
export type { ConfigResponse } from './models/ConfigResponse';
@@ -25,7 +23,6 @@ export type { CropFileSettings } from './models/CropFileSettings';
export type { DatabaseFile } from './models/DatabaseFile';
export { DBBackup } from './models/DBBackup';
export type { DBInfoResponse } from './models/DBInfoResponse';
export type { DeleteAllFilesResponse } from './models/DeleteAllFilesResponse';
export type { DeleteCategoryRequest } from './models/DeleteCategoryRequest';
export type { DeleteMp3Mp4Request } from './models/DeleteMp3Mp4Request';
export type { DeletePlaylistRequest } from './models/DeletePlaylistRequest';
@@ -39,6 +36,7 @@ export type { DownloadResponse } from './models/DownloadResponse';
export type { DownloadTwitchChatByVODIDRequest } from './models/DownloadTwitchChatByVODIDRequest';
export type { DownloadTwitchChatByVODIDResponse } from './models/DownloadTwitchChatByVODIDResponse';
export type { DownloadVideosForSubscriptionRequest } from './models/DownloadVideosForSubscriptionRequest';
export type { File } from './models/File';
export { FileType } from './models/FileType';
export type { GenerateArgsResponse } from './models/GenerateArgsResponse';
export type { GenerateNewApiKeyResponse } from './models/GenerateNewApiKeyResponse';
@@ -100,7 +98,6 @@ export type { UpdateCategoriesRequest } from './models/UpdateCategoriesRequest';
export type { UpdateCategoryRequest } from './models/UpdateCategoryRequest';
export type { UpdateConcurrentStreamRequest } from './models/UpdateConcurrentStreamRequest';
export type { UpdateConcurrentStreamResponse } from './models/UpdateConcurrentStreamResponse';
export type { UpdateFileRequest } from './models/UpdateFileRequest';
export type { UpdatePlaylistRequest } from './models/UpdatePlaylistRequest';
export type { UpdaterStatus } from './models/UpdaterStatus';
export type { UpdateServerRequest } from './models/UpdateServerRequest';

View File

@@ -2,7 +2,8 @@
/* tslint:disable */
/* eslint-disable */
export type AddFileToPlaylistRequest = {
export interface AddFileToPlaylistRequest {
file_uid: string;
playlist_id: string;
};
}

View File

@@ -2,10 +2,10 @@
/* tslint:disable */
/* eslint-disable */
import type { UserPermission } from './UserPermission';
import type { YesNo } from './YesNo';
import { UserPermission } from './UserPermission';
import { YesNo } from './YesNo';
export type BaseChangePermissionsRequest = {
export interface BaseChangePermissionsRequest {
permission: UserPermission;
new_value: YesNo;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { CategoryRule } from './CategoryRule';
import { CategoryRule } from './CategoryRule';
export type Category = {
export interface Category {
name?: string;
uid?: string;
rules?: Array<CategoryRule>;
@@ -12,4 +12,4 @@ export type Category = {
* Overrides file output for downloaded files in category
*/
custom_output?: string;
};
}

View File

@@ -2,10 +2,11 @@
/* tslint:disable */
/* eslint-disable */
export type CategoryRule = {
export interface CategoryRule {
preceding_operator?: CategoryRule.preceding_operator;
comparator?: CategoryRule.comparator;
};
}
export namespace CategoryRule {

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
export type ChangeRolePermissionsRequest = (BaseChangePermissionsRequest & {
role: string;
});
export interface ChangeRolePermissionsRequest extends BaseChangePermissionsRequest {
role: string;
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
export type ChangeUserPermissionsRequest = (BaseChangePermissionsRequest & {
user_uid: string;
});
export interface ChangeUserPermissionsRequest extends BaseChangePermissionsRequest {
user_uid: string;
}

View File

@@ -2,9 +2,10 @@
/* tslint:disable */
/* eslint-disable */
export type CheckConcurrentStreamRequest = {
export interface CheckConcurrentStreamRequest {
/**
* UID of the concurrent stream
*/
uid: string;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { ConcurrentStream } from './ConcurrentStream';
import { ConcurrentStream } from './ConcurrentStream';
export type CheckConcurrentStreamResponse = {
export interface CheckConcurrentStreamResponse {
stream: ConcurrentStream;
};
}

View File

@@ -1,9 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ClearDownloadsRequest = {
clear_finished?: boolean;
clear_paused?: boolean;
clear_errors?: boolean;
};

View File

@@ -2,8 +2,9 @@
/* tslint:disable */
/* eslint-disable */
export type ConcurrentStream = {
export interface ConcurrentStream {
playback_timestamp?: number;
unix_timestamp?: number;
playing?: boolean;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type Config = {
export interface Config {
YoutubeDLMaterial: any;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { Config } from './Config';
import { Config } from './Config';
export type ConfigResponse = {
export interface ConfigResponse {
config_file: Config;
success: boolean;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type CreateCategoryRequest = {
export interface CreateCategoryRequest {
name: string;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { Category } from './Category';
import { Category } from './Category';
export type CreateCategoryResponse = {
export interface CreateCategoryResponse {
new_category?: Category;
success?: boolean;
};
}

View File

@@ -2,11 +2,11 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import { FileType } from './FileType';
export type CreatePlaylistRequest = {
export interface CreatePlaylistRequest {
playlistName: string;
uids: Array<string>;
type: FileType;
thumbnailURL: string;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { Playlist } from './Playlist';
import { Playlist } from './Playlist';
export type CreatePlaylistResponse = {
export interface CreatePlaylistResponse {
new_playlist: Playlist;
success: boolean;
};
}

View File

@@ -2,7 +2,8 @@
/* tslint:disable */
/* eslint-disable */
export type CropFileSettings = {
export interface CropFileSettings {
cropFileStart: number;
cropFileEnd: number;
};
}

View File

@@ -2,12 +2,13 @@
/* tslint:disable */
/* eslint-disable */
export type DBBackup = {
export interface DBBackup {
name: string;
timestamp: number;
size: number;
source: DBBackup.source;
};
}
export namespace DBBackup {

View File

@@ -2,17 +2,17 @@
/* tslint:disable */
/* eslint-disable */
import type { TableInfo } from './TableInfo';
import { TableInfo } from './TableInfo';
export type DBInfoResponse = {
export interface DBInfoResponse {
using_local_db?: boolean;
stats_by_table?: {
files?: TableInfo;
playlists?: TableInfo;
categories?: TableInfo;
subscriptions?: TableInfo;
users?: TableInfo;
roles?: TableInfo;
download_queue?: TableInfo;
files?: TableInfo,
playlists?: TableInfo,
categories?: TableInfo,
subscriptions?: TableInfo,
users?: TableInfo,
roles?: TableInfo,
download_queue?: TableInfo,
};
};
}

View File

@@ -2,16 +2,11 @@
/* tslint:disable */
/* eslint-disable */
import type { Category } from './Category';
export type DatabaseFile = {
export interface DatabaseFile {
id: string;
title: string;
/**
* Backup if thumbnailPath is not defined
*/
thumbnailURL: string;
thumbnailPath?: string;
isAudio: boolean;
/**
* In seconds
@@ -19,15 +14,9 @@ export type DatabaseFile = {
duration: number;
url: string;
uploader: string;
/**
* In bytes
*/
size: number;
path: string;
upload_date: string;
uid: string;
sharingEnabled?: boolean;
category?: Category;
view_count?: number;
local_view_count?: number;
};
}

View File

@@ -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;
};

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type DeleteCategoryRequest = {
export interface DeleteCategoryRequest {
category_uid: string;
};
}

View File

@@ -2,7 +2,8 @@
/* tslint:disable */
/* eslint-disable */
export type DeleteMp3Mp4Request = {
export interface DeleteMp3Mp4Request {
uid: string;
blacklistMode?: boolean;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import { FileType } from './FileType';
export type DeletePlaylistRequest = {
export interface DeletePlaylistRequest {
playlist_id: string;
type: FileType;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { SubscriptionRequestData } from './SubscriptionRequestData';
import { SubscriptionRequestData } from './SubscriptionRequestData';
export type DeleteSubscriptionFileRequest = {
export interface DeleteSubscriptionFileRequest {
file: string;
file_uid?: string;
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.
*/
deleteForever?: boolean;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type DeleteUserRequest = {
export interface DeleteUserRequest {
uid: string;
};
}

View File

@@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type Dictionary<T> = {
[key: string]: T;
}

View File

@@ -2,7 +2,8 @@
/* tslint:disable */
/* eslint-disable */
export type Download = {
export interface Download {
uid: string;
ui_uid?: string;
running: boolean;
@@ -22,4 +23,4 @@ export type Download = {
user_uid?: string;
sub_id?: string;
sub_name?: string;
};
}

View File

@@ -2,8 +2,9 @@
/* tslint:disable */
/* eslint-disable */
export type DownloadArchiveRequest = {
export interface DownloadArchiveRequest {
sub: {
archive_dir: string;
archive_dir: string,
};
};
}

View File

@@ -2,13 +2,13 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import { FileType } from './FileType';
export type DownloadFileRequest = {
export interface DownloadFileRequest {
uid?: string;
uuid?: string;
sub_id?: string;
playlist_id?: string;
url?: string;
type?: FileType;
};
}

View File

@@ -2,10 +2,10 @@
/* tslint:disable */
/* eslint-disable */
import type { CropFileSettings } from './CropFileSettings';
import type { FileType } from './FileType';
import { CropFileSettings } from './CropFileSettings';
import { FileType } from './FileType';
export type DownloadRequest = {
export interface DownloadRequest {
url: string;
/**
* Video format code. Overrides other quality options.
@@ -41,4 +41,4 @@ export type DownloadRequest = {
maxBitrate?: string;
type?: FileType;
cropFileSettings?: CropFileSettings;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Download } from './Download';
import { Download } from './Download';
export type DownloadResponse = {
export interface DownloadResponse {
download?: Download;
};
}

View File

@@ -2,10 +2,10 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import type { Subscription } from './Subscription';
import { FileType } from './FileType';
import { Subscription } from './Subscription';
export type DownloadTwitchChatByVODIDRequest = {
export interface DownloadTwitchChatByVODIDRequest {
/**
* File ID
*/
@@ -20,4 +20,4 @@ export type DownloadTwitchChatByVODIDRequest = {
*/
uuid?: string;
sub?: Subscription;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { TwitchChatMessage } from './TwitchChatMessage';
import { TwitchChatMessage } from './TwitchChatMessage';
export type DownloadTwitchChatByVODIDResponse = {
export interface DownloadTwitchChatByVODIDResponse {
chat: Array<TwitchChatMessage>;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type DownloadVideosForSubscriptionRequest = {
export interface DownloadVideosForSubscriptionRequest {
subID: string;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type binary = {
export interface File {
id?: string;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export enum FileType {
AUDIO = 'audio',
VIDEO = 'video',

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type GenerateArgsResponse = {
export interface GenerateArgsResponse {
args?: Array<string>;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type GenerateNewApiKeyResponse = {
export interface GenerateNewApiKeyResponse {
new_api_key: string;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Category } from './Category';
import { Category } from './Category';
export type GetAllCategoriesResponse = {
export interface GetAllCategoriesResponse {
categories: Array<Category>;
};
}

View File

@@ -2,9 +2,10 @@
/* tslint:disable */
/* eslint-disable */
export type GetAllDownloadsRequest = {
export interface GetAllDownloadsRequest {
/**
* Filters downloads with the array
*/
uids?: Array<string> | null;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Download } from './Download';
import { Download } from './Download';
export type GetAllDownloadsResponse = {
export interface GetAllDownloadsResponse {
downloads?: Array<Download>;
};
}

View File

@@ -2,13 +2,13 @@
/* tslint:disable */
/* eslint-disable */
import type { DatabaseFile } from './DatabaseFile';
import type { Playlist } from './Playlist';
import { DatabaseFile } from './DatabaseFile';
import { Playlist } from './Playlist';
export type GetAllFilesResponse = {
export interface GetAllFilesResponse {
files: Array<DatabaseFile>;
/**
* All video playlists
*/
playlists: Array<Playlist>;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Subscription } from './Subscription';
import { Subscription } from './Subscription';
export type GetAllSubscriptionsResponse = {
export interface GetAllSubscriptionsResponse {
subscriptions: Array<Subscription>;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Task } from './Task';
import { Task } from './Task';
export type GetAllTasksResponse = {
export interface GetAllTasksResponse {
tasks?: Array<Task>;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { DBBackup } from './DBBackup';
import { DBBackup } from './DBBackup';
export type GetDBBackupsResponse = {
export interface GetDBBackupsResponse {
tasks?: Array<DBBackup>;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type GetDownloadRequest = {
export interface GetDownloadRequest {
download_uid: string;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Download } from './Download';
import { Download } from './Download';
export type GetDownloadResponse = {
export interface GetDownloadResponse {
download?: Download;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type GetFileFormatsRequest = {
export interface GetFileFormatsRequest {
url?: string;
};
}

View File

@@ -2,9 +2,11 @@
/* tslint:disable */
/* eslint-disable */
export type GetFileFormatsResponse = {
import { File } from './File';
export interface GetFileFormatsResponse {
success: boolean;
result: {
formats?: Array<any>;
formats?: Array<any>,
};
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import { FileType } from './FileType';
export type GetFileRequest = {
export interface GetFileRequest {
/**
* Video UID
*/
@@ -14,4 +14,4 @@ export type GetFileRequest = {
* User UID
*/
uuid?: string;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { DatabaseFile } from './DatabaseFile';
import { DatabaseFile } from './DatabaseFile';
export type GetFileResponse = {
export interface GetFileResponse {
success: boolean;
file?: DatabaseFile;
};
}

View File

@@ -2,10 +2,10 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import type { Subscription } from './Subscription';
import { FileType } from './FileType';
import { Subscription } from './Subscription';
export type GetFullTwitchChatRequest = {
export interface GetFullTwitchChatRequest {
/**
* File ID
*/
@@ -16,4 +16,4 @@ export type GetFullTwitchChatRequest = {
*/
uuid?: string;
sub?: Subscription;
};
}

View File

@@ -2,7 +2,8 @@
/* tslint:disable */
/* eslint-disable */
export type GetFullTwitchChatResponse = {
export interface GetFullTwitchChatResponse {
success: boolean;
error?: string;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type GetLogsRequest = {
export interface GetLogsRequest {
lines?: number;
};
}

View File

@@ -2,10 +2,11 @@
/* tslint:disable */
/* eslint-disable */
export type GetLogsResponse = {
export interface GetLogsResponse {
/**
* Number of lines to retrieve from the bottom
*/
logs?: string;
success?: boolean;
};
}

View File

@@ -2,13 +2,13 @@
/* tslint:disable */
/* eslint-disable */
import type { DatabaseFile } from './DatabaseFile';
import type { Playlist } from './Playlist';
import { DatabaseFile } from './DatabaseFile';
import { Playlist } from './Playlist';
export type GetMp3sResponse = {
export interface GetMp3sResponse {
mp3s: Array<DatabaseFile>;
/**
* All audio playlists
*/
playlists: Array<Playlist>;
};
}

View File

@@ -2,13 +2,13 @@
/* tslint:disable */
/* eslint-disable */
import type { DatabaseFile } from './DatabaseFile';
import type { Playlist } from './Playlist';
import { DatabaseFile } from './DatabaseFile';
import { Playlist } from './Playlist';
export type GetMp4sResponse = {
export interface GetMp4sResponse {
mp4s: Array<DatabaseFile>;
/**
* All video playlists
*/
playlists: Array<Playlist>;
};
}

View File

@@ -2,11 +2,11 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import { FileType } from './FileType';
export type GetPlaylistRequest = {
export interface GetPlaylistRequest {
playlist_id: string;
type?: FileType;
uuid?: string;
include_file_metadata?: boolean;
};
}

View File

@@ -2,11 +2,11 @@
/* tslint:disable */
/* eslint-disable */
import type { FileType } from './FileType';
import type { Playlist } from './Playlist';
import { FileType } from './FileType';
import { Playlist } from './Playlist';
export type GetPlaylistResponse = {
export interface GetPlaylistResponse {
playlist: Playlist;
type: FileType;
success: boolean;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type GetPlaylistsRequest = {
export interface GetPlaylistsRequest {
include_categories?: boolean;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Playlist } from './Playlist';
import { Playlist } from './Playlist';
export type GetPlaylistsResponse = {
export interface GetPlaylistsResponse {
playlists: Array<Playlist>;
};
}

View File

@@ -2,15 +2,15 @@
/* tslint:disable */
/* eslint-disable */
import type { UserPermission } from './UserPermission';
import { UserPermission } from './UserPermission';
export type GetRolesResponse = {
export interface GetRolesResponse {
roles: {
admin?: {
permissions?: Array<UserPermission>;
};
permissions?: Array<UserPermission>,
},
user?: {
permissions?: Array<UserPermission>;
permissions?: Array<UserPermission>,
},
};
};
};
}

View File

@@ -2,7 +2,8 @@
/* tslint:disable */
/* eslint-disable */
export type GetSubscriptionRequest = {
export interface GetSubscriptionRequest {
/**
* Subscription ID
*/
@@ -11,4 +12,4 @@ export type GetSubscriptionRequest = {
* Subscription name
*/
name?: string;
};
}

View File

@@ -2,9 +2,9 @@
/* tslint:disable */
/* eslint-disable */
import type { Subscription } from './Subscription';
import { Subscription } from './Subscription';
export type GetSubscriptionResponse = {
export interface GetSubscriptionResponse {
subscription: Subscription;
files: Array<any>;
};
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
export type GetTaskRequest = {
export interface GetTaskRequest {
task_key: string;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { Task } from './Task';
import { Task } from './Task';
export type GetTaskResponse = {
export interface GetTaskResponse {
task?: Task;
};
}

View File

@@ -2,8 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { User } from './User';
import { User } from './User';
export type GetUsersResponse = {
export interface GetUsersResponse {
users: Array<User>;
};
}

View File

@@ -2,11 +2,12 @@
/* tslint:disable */
/* eslint-disable */
export type IncrementViewCountRequest = {
export interface IncrementViewCountRequest {
file_uid: string;
sub_id?: string;
/**
* User UID
*/
uuid?: string;
};
}

View File

@@ -2,7 +2,8 @@
/* tslint:disable */
/* eslint-disable */
export type LoginRequest = {
export interface LoginRequest {
username: string;
password: string;
};
}

Some files were not shown because too many files have changed in this diff Show More