Compare commits

..

21 Commits

Author SHA1 Message Date
a399a91ab0 fixed typo
All checks were successful
push new builds to gitea, dockerhub / build (push) Successful in 1m27s
2025-08-29 20:22:34 -04:00
ec72c5657b reindex files to force unix line endings in .sh files
All checks were successful
push new builds to gitea, dockerhub / build (push) Successful in 1m26s
2025-08-29 16:24:33 -04:00
f8f625f0bf fix typo
All checks were successful
push new builds to gitea, dockerhub / build (push) Successful in 1m6s
2025-08-29 16:11:29 -04:00
22fb495e0d fix docker syntax
Some checks failed
push new builds to gitea, dockerhub / build (push) Failing after 2m40s
2025-08-29 15:29:03 -04:00
33d5de6a77 update ytdlp on container start; cleanup gitea workflow
Some checks failed
push new builds to gitea, dockerhub / build (push) Failing after 38s
2025-08-29 15:22:10 -04:00
d539915486 cleanup and remove github workflow
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 1m6s
2025-08-24 22:42:15 -04:00
f173c64b70 Revert "removed github workflow"
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 26s
This reverts commit 750dc265ca.
2025-08-24 22:40:20 -04:00
750dc265ca removed github workflow
Some checks failed
weekly, if new yt-dlp version, build + push containers to docker / build (push) Failing after 27s
2025-08-24 22:29:45 -04:00
846d07f099 fixed gitea api url
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 1m7s
2025-08-24 22:25:19 -04:00
adca108376 chagnged to link using api
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 1m45s
2025-08-24 22:16:05 -04:00
748e3fb22a gitea vars redux
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 58s
2025-08-24 19:00:35 -04:00
0968aa84b8 fixed username
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 59s
2025-08-24 17:59:28 -04:00
b74f70c42e added gitea repo variables
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 49s
2025-08-24 17:56:31 -04:00
07cdae823a gitea registry /youdis, tags ben/youdis
Some checks failed
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Failing after 2m32s
2025-08-24 17:20:05 -04:00
81619074d7 fixed youdis shortsha syntax
All checks were successful
weekly, if new yt-dlp, build + push container to docker/gitea / build (push) Successful in 2m29s
2025-08-24 15:35:21 -04:00
690f97ed82 consol. dev build tag; push directly to youdis container repo
Some checks failed
build + push containers to gitea (every) and docker (daily) / build (push) Failing after 56s
2025-08-24 15:28:44 -04:00
b772383d14 collapsed to single job
All checks were successful
build + push containers to gitea (every) and docker (daily) / build (push) Successful in 3m2s
2025-08-24 14:57:32 -04:00
53fbcc5a8a fix gitea user 2025-08-24 14:28:34 -04:00
713f316679 fixed: secrets cannot begin with "GITEA_" or "GITHUB_" 2025-08-24 14:24:14 -04:00
08bcc3d3bd split jobs into versioining, dockerhub, gitea 2025-08-23 14:43:48 -04:00
0abf3a5adb added ytdlp/youdis version tags, check youdis:latest before build
Some checks failed
build + push containers to gitea (every) and docker (daily) / docker (push) Has been cancelled
2025-08-23 14:36:30 -04:00
18 changed files with 383 additions and 396 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.sh text eol=lf

View File

@@ -1,70 +1,72 @@
name: build + push containers to gitea (every) and docker (daily) name: push new builds to gitea, dockerhub
on: on:
push: push:
branches: [ "master" ] branches: ["master"]
schedule: workflow_dispatch:
- cron: '0 0 * * 0' # weekly check
workflow_dispatch: jobs:
build:
jobs: runs-on: ubuntu-latest
dockerhub: steps:
runs-on: ubuntu-latest - uses: actions/checkout@v4
steps:
- uses: actions/checkout@v4 - name: Get youdis version
id: youdis
- name: Get current version run: |
id: current YOUDIS_VER=$(cat youdis-version.txt)
run: | if [ -z "$YOUDIS_VER" ]; then
CURRENT=$(cat version.txt || echo "none") echo "youdis version empty" >&2
echo "version=$CURRENT" >> $GITHUB_OUTPUT exit 1
fi
- name: check latest yt-dlp release echo "youdis-version=$YOUDIS_VER" >> $GITHUB_OUTPUT
id: latest echo "shortsha=${GITHUB_SHA::5}" >> $GITHUB_OUTPUT
run: |
LATEST=$(curl -s https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest | jq -r .tag_name || echo "error") - name: Decide if build needed
if [ "$LATEST" == "error" ]; then id: check_build
echo "failed to fetch latest version" >&2 run: |
exit 1 SHOULD_BUILD=false
fi if [ "${{ steps.youdis.outputs.youdis-version }}" != "$(docker inspect --format='{{ index .Config.Labels "YOUDIS_VERSION" }}' eulaly/youdis:latest || echo none)" ]; then
echo "yt-dlp version=$LATEST" >> $GITHUB_OUTPUT SHOULD_BUILD=true
fi
- name: Update version file if changed echo "should_build=$SHOULD_BUILD" >> $GITHUB_OUTPUT
run: echo ${{ steps.latest.outputs.version }} > version.txt
if: ${{ steps.current.outputs.version != steps.latest.outputs.version }} - name: Login to Dockerhub
uses: docker/login-action@v3
- name: Login to Dockerhub with:
uses: docker/login-action@v2 username: ${{ secrets.DOCKERHUB_USERNAME }}
with: password: ${{ secrets.DOCKERHUB_TOKEN }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push to Dockerhub
if: ${{ steps.check_build.outputs.should_build == 'true' }}
- name: Build and push to Dockerhub uses: docker/build-push-action@v5
uses: docker/build-push-action@v5 with:
with: push: true
push: true tags: |
tags: | eulaly/youdis:latest
eulaly/youdis:latest eulaly/youdis:${{ steps.youdis.outputs.shortsha }}
eulaly/youdis:${{ steps.latest.outputs.version }} labels: |
if: ${{ steps.current.outputs.version != steps.latest.outputs.version }} YOUDIS_VERSION=${{ steps.youdis.outputs.youdis-version }}
gitea: - name: Login to Gitea
runs-on: ubuntu-latest uses: docker/login-action@v3
steps: with:
- uses: actions/checkout@v4 registry: git.hgsky.me
username: ${{ secrets.USERNAME }}
- name: Login to Gitea (local) password: ${{ secrets.YOUDIS_PASSWORD }}
uses: docker/login-action@v2
with: - name: Build and push to Gitea
registry: git.hgsky.me if: ${{ steps.check_build.outputs.should_build == 'true' }}
username: ${{ secrets.GITEA_USERNAME }} uses: docker/build-push-action@v5
password: ${{ secrets.GITEA_TOKEN }} with:
push: true
- name: Build and push to Gitea (all pushes) tags: |
uses: docker/build-push-action@v5 git.hgsky.me/ben/youdis:latest
with: git.hgsky.me/ben/youdis:${{ steps.youdis.outputs.shortsha }}
push: true labels: |
tags: | YOUDIS_VERSION=${{ steps.youdis.outputs.youdis-version }}
git.hgsky.me/eulaly/youdis:latest
git.hgsky.me/eulaly/youdis:dev-${{ github.sha }} - name: link container in gitea
run: |
curl -X POST -H "Authorization: token ${{ secrets.SUPER }}" https://git.hgsky.me/api/v1/packages/ben/container/youdis/-/link/youdis

View File

@@ -1,44 +0,0 @@
name: Check yt-dlp and rebuild if new
on:
schedule:
- cron: '0 0 * * 0'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get current version
id: current
run: |
CURRENT=$(cat version.txt || echo "none")
echo "version=$CURRENT" >> $GITHUB_OUTPUT
- name: Check latest version
id: latest
run: |
LATEST=$(curl -s https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest | jq -r .tag_name || echo "error")
if [ "$LATEST" == "error" ]; then
echo "failed to fetch latest version" >&2
exit 1
fi
echo "version=$LATEST" >> $GITHUB_OUTPUT
- name: Update version file if changed
run: echo ${{ steps.latest.outputs.version }} > version.txt
if: ${{ steps.current.outputs.version != steps.latest.outputs.version }}
- name: Login to Dockerhub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push docker image
uses: docker/build-push-action@v5
with:
push: true
tags: eulaly/youdis:latest,eulaly/youdis:${{ steps.latest.outputs.version }}
if: ${{ steps.current.outputs.version != steps.latest.outputs.version }}

24
.gitignore vendored
View File

@@ -1,13 +1,13 @@
*.pyc *.pyc
*.org *.org
*.ps1 *.ps1
.env .env
#* #*
.#* .#*
__pycache__/ __pycache__/
venv/ venv/
archive/ archive/
config/ config/
downloads/ downloads/
data.json data.json

48
LICENSE
View File

@@ -1,24 +1,24 @@
This is free and unencumbered software released into the public domain. This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any binary, for any purpose, commercial or non-commercial, and by any
means. means.
In jurisdictions that recognize copyright laws, the author or authors In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this relinquishment in perpetuity of all present and future rights to this
software under copyright law. software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org> For more information, please refer to <https://unlicense.org>

View File

@@ -1,8 +1,8 @@
build and run the docker container build and run the docker container
``` ```
api_token = [discord bot token] api_token = [discord bot token]
-v [downloads]:/downloads -v [downloads]:/downloads
-v [config]:/config -v [config]:/config
``` ```
config contains data to persist across container updates, i.e., unraid appdata, config contains data to persist across container updates, i.e., unraid appdata,
such as users.json (authorized users) and yt-dlp's archive.txt such as users.json (authorized users) and yt-dlp's archive.txt

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,8 +0,0 @@
#!/bin/sh
#copy yt-dlp.conf if missing
mkdir -p /config
if [ ! -f /config/yt-dlp.conf ]; then
cp /app/default-yt-dlp.conf /config/yt-dlp.conf
fi
exec "$@"

View File

@@ -1,11 +1,11 @@
# yt-dlp config file (yt-dlp.conf or .config/yt-dlp/config) # yt-dlp config file (yt-dlp.conf or .config/yt-dlp/config)
--simulate --simulate
-f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"
--embed-chapters --embed-chapters
--embed-info-json --embed-info-json
--write-playlist-metafiles --write-playlist-metafiles
#--paths "{"home":"./downloads"}" #--paths "{"home":"./downloads"}"
--download-archive "/config/archive.txt" --download-archive "/config/archive.txt"
--restrict-filenames --restrict-filenames
--output "/downloads/%(uploader)s/%(playlist_title)s/%(playlist_index)s%(playlist_index& - )s%(title)s.%(ext)s" --output "/downloads/%(uploader)s/%(playlist_title)s/%(playlist_index)s%(playlist_index& - )s%(title)s.%(ext)s"
# --split-chapters # --split-chapters

View File

@@ -1,14 +1,16 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM python:3.12.1-alpine3.18 FROM python:3.12.1-alpine3.18
WORKDIR /app WORKDIR /app
RUN apk update && \ RUN apk update && \
apk add --no-cache build-base ffmpeg && \ apk add --no-cache build-base ffmpeg && \
rm -rf /var/cache/apk/* rm -rf /var/cache/apk/*
COPY requirements.txt requirements.txt COPY requirements.txt .
RUN python3 -m pip install --no-cache-dir -r requirements.txt RUN python3 -m pip install --no-cache-dir -r requirements.txt
COPY youdis.py youdis.py
COPY default-yt-dlp.conf /app/default-yt-dlp.conf COPY youdis.py default-yt-dlp.conf update-ytdlp.sh run-youdis.sh /app/
COPY create-ytdlp-config.sh /app/create-ytdlp-config.sh RUN chmod +x /app/update-ytdlp.sh /app/run-youdis.sh
RUN chmod 755 /app/create-ytdlp-config.sh
ENTRYPOINT ["/app/create-ytdlp-config.sh"] COPY weekly-restart /etc/cron.d/
CMD ["python3", "youdis.py"] RUN chmod 0644 /etc/cron.d/weekly-restart
ENTRYPOINT ["/app/run-youdis.sh"]

21
run-youdis.sh Normal file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
set -e
#start crond in bg
crond -l 2
echo "checking for /config/yt-dlp.conf"
#copy yt-dlp.conf if missing
mkdir -p /config
if [ ! -f /config/yt-dlp.conf ]; then
echo "yt-dlp.conf not found, setting default"
cp /app/default-yt-dlp.conf /config/yt-dlp.conf
echo "created yt-dlp.conf"
fi
python3 -m pip install --no-cache-dir --upgrade --pre "yt-dlp[default]"
VERSION=$(python3 -m pip show yt-dlp 2>/dev/null | awk '/Version:/ {print $2}')
echo "updated yt-dlp to $VERSION"
echo "starting youdis"
exec python3 /app/youdis.py

View File

@@ -1,18 +1,18 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<Container version="2"> <Container version="2">
<Name>youdis</Name> <Name>youdis</Name>
<Repository>eulaly/youdis</Repository> <Repository>eulaly/youdis</Repository>
<Registry>https://hub.docker.com/r/eulaly/youdis/</Registry> <Registry>https://hub.docker.com/r/eulaly/youdis/</Registry>
<Network>bridge</Network> <Network>bridge</Network>
<Shell>sh</Shell> <Shell>sh</Shell>
<Privileged>false</Privileged> <Privileged>false</Privileged>
<Support>[Unraid Support Thread]</Support> <Support>[Unraid Support Thread]</Support>
<Project>https://github.com/eulaly/youdis</Project> <Project>https://github.com/eulaly/youdis</Project>
<Overview>Discord bot-based wrapper for yt-dlp. Let your friends download videos to your server! Supports playlists, requires a configured Discord bot.</Overview> <Overview>Discord bot-based wrapper for yt-dlp. Let your friends download videos to your server! Supports playlists, requires a configured Discord bot.</Overview>
<Category>Downloaders: Tools:</Category> <Category>Downloaders: Tools:</Category>
<TemplateURL>https://raw.githubusercontent.com/eulaly/unraid-templates/refs/heads/master/unraid-ca-template.xml</TemplateURL> <TemplateURL>https://raw.githubusercontent.com/eulaly/unraid-templates/refs/heads/master/unraid-ca-template.xml</TemplateURL>
<Icon>https://github.com/eulaly/youdis/blob/c978a2326984efa9670678687ed1a1473478d753/yt_dlp.png</Icon> <Icon>https://github.com/eulaly/youdis/blob/c978a2326984efa9670678687ed1a1473478d753/yt_dlp.png</Icon>
<Config Name="api_token" Target="api_token" Default="" Mode="" Description="Discord bot token" Type="Variable" Display="always" Required="true" Mask="true"/> <Config Name="api_token" Target="api_token" Default="" Mode="" Description="Discord bot token" Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="Downloads" Target="/downloads" Default="" Mode="rw" Description="Video download location" Type="Path" Display="always" Required="false"/> <Config Name="Downloads" Target="/downloads" Default="" Mode="rw" Description="Video download location" Type="Path" Display="always" Required="false"/>
<Config Name="Config" Target="/config" Default="/mnt/user/appdata/youdis/config" Mode="rw" Description="Config location (archive.txt, users.json)" Type="Path" Display="always" Required="false"/> <Config Name="Config" Target="/config" Default="/mnt/user/appdata/youdis/config" Mode="rw" Description="Config location (archive.txt, users.json)" Type="Path" Display="always" Required="false"/>
</Container> </Container>

12
update-ytdlp.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
set -e
echo "updating yt-dlp"
echo "killing youdis"
pkill -f youdis.py || true
python3 -m pip install --no-cache-dir --upgrade --pre "yt-dlp[default]"
VERSION=$(python3 -m pip show yt-dlp 2>/dev/null | awk '/Version:/ {print $2}')
echo "updated yt-dlp to $VERSION"
echo "restarting youdis"
python3 /app/youdis.py &

View File

@@ -1 +0,0 @@
20250823-b37e943

1
weekly-restart Normal file
View File

@@ -0,0 +1 @@
0 3 * * 0 root /app/update-ytdlp.sh

View File

1
youdis-version.txt Normal file
View File

@@ -0,0 +1 @@
20250829-ec72c56

314
youdis.py
View File

@@ -1,157 +1,157 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
''' '''
youdis v1.1 youdis v1.1
bot for downloading youtube videos using yt-dlp bot for downloading youtube videos using yt-dlp
discord-py-interactions 5.11.0 has new option discord-py-interactions 5.11.0 has new option
requires python>=3.9 requires python>=3.9
''' '''
# match_filter: info_dict -> Raise utils.DownloadCancelled(msg) ? interrupt # match_filter: info_dict -> Raise utils.DownloadCancelled(msg) ? interrupt
import interactions import interactions
from os import getenv from os import getenv
from pathlib import Path from pathlib import Path
import yt_dlp import yt_dlp
import json import json
import asyncio import asyncio
import threading import threading
userFile = Path('/config/users.json') userFile = Path('/config/users.json')
userFile.touch(exist_ok=True) userFile.touch(exist_ok=True)
bot = interactions.Client(intents=interactions.Intents.DEFAULT,default_scope=2147491904) bot = interactions.Client(intents=interactions.Intents.DEFAULT,default_scope=2147491904)
userFile.parent.mkdir(exist_ok=True, parents=True) userFile.parent.mkdir(exist_ok=True, parents=True)
try: try:
with open(userFile, 'x') as f: with open(userFile, 'x') as f:
print(f'users.json not found; saving to {userFile}') print(f'users.json not found; saving to {userFile}')
except FileExistsError: except FileExistsError:
with open(userFile, 'r') as f: with open(userFile, 'r') as f:
authorized_users = json.load(f).get('authorized_users') authorized_users = json.load(f).get('authorized_users')
print(f'authorized_users:{authorized_users}') print(f'authorized_users:{authorized_users}')
title = '' title = ''
async def send_message(ctx, message): async def send_message(ctx, message):
await ctx.author.send(message) await ctx.author.send(message)
def download_video(url, options): def download_video(url, options):
with yt_dlp.YoutubeDL(options) as ydl: with yt_dlp.YoutubeDL(options) as ydl:
ydl.download(url) ydl.download(url)
def create_hook(ctx,loop): def create_hook(ctx,loop):
def hook(d): def hook(d):
global title global title
status = d.get('status') status = d.get('status')
if status == 'error': if status == 'error':
msg = f'error; video probably already exists, have you checked archive.txt' msg = f'error; video probably already exists, have you checked archive.txt'
asyncio.run_coroutine_threadsafe(send_message(ctx,msg),loop) asyncio.run_coroutine_threadsafe(send_message(ctx,msg),loop)
elif d.get('info_dict').get('title') != title: elif d.get('info_dict').get('title') != title:
title = d.get('info_dict').get('title') title = d.get('info_dict').get('title')
playlist_index = d.get('info_dict').get('playlist_index') playlist_index = d.get('info_dict').get('playlist_index')
playlist_count = d.get('info_dict').get('playlist_count') playlist_count = d.get('info_dict').get('playlist_count')
filename = d.get('filename') filename = d.get('filename')
url = d.get('info_dict').get('webpage_url') url = d.get('info_dict').get('webpage_url')
msg = f'{status} {playlist_index} of {playlist_count}: {filename} <{url}>' msg = f'{status} {playlist_index} of {playlist_count}: {filename} <{url}>'
asyncio.run_coroutine_threadsafe(send_message(ctx,msg),loop) asyncio.run_coroutine_threadsafe(send_message(ctx,msg),loop)
return hook return hook
@interactions.slash_command(name="youtube",description="download video from youtube to server") @interactions.slash_command(name="youtube",description="download video from youtube to server")
@interactions.slash_option( @interactions.slash_option(
name='url', name='url',
opt_type=interactions.OptionType.STRING, opt_type=interactions.OptionType.STRING,
required=True, required=True,
description='url target' description='url target'
) )
async def youtube(ctx: interactions.SlashContext, url:str): async def youtube(ctx: interactions.SlashContext, url:str):
print(f'{ctx.author.id} requested {url}') print(f'{ctx.author.id} requested {url}')
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
hook = create_hook(ctx,loop) hook = create_hook(ctx,loop)
msg = '' msg = ''
# use api_to_cli and paste cli options to get the output you need # use api_to_cli and paste cli options to get the output you need
yoptions = { yoptions = {
'format':'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', 'format':'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
'fragment_tries': 10, 'fragment_tries': 10,
'restrictfilenames':True, 'restrictfilenames':True,
'paths': {'home':'/downloads'}, 'paths': {'home':'/downloads'},
'retries':10, 'retries':10,
'writeinfojson':False, 'writeinfojson':False,
'allow_playlist_files':True, 'allow_playlist_files':True,
'noplaylist':True, 'noplaylist':True,
'download_archive':'/config/archive.txt', 'download_archive':'/config/archive.txt',
'progress_hooks':[hook], 'progress_hooks':[hook],
'outtmpl': '%(uploader)s/%(playlist_title)s/%(playlist_index)s%(playlist_index& - )s%(title)s.%(ext)s', 'outtmpl': '%(uploader)s/%(playlist_title)s/%(playlist_index)s%(playlist_index& - )s%(title)s.%(ext)s',
'outtmpl_na_placeholder':'', 'outtmpl_na_placeholder':'',
} }
# check that user is authorized # check that user is authorized
if ctx.author.id not in authorized_users: if ctx.author.id not in authorized_users:
if ctx.author.id == 127831327012683776: if ctx.author.id == 127831327012683776:
await ctx.author.send('potato stop') await ctx.author.send('potato stop')
await ctx.author.send('you are not authorized to use this command. message my owner to be added.') await ctx.author.send('you are not authorized to use this command. message my owner to be added.')
return return
else: else:
await ctx.channel.send(f'Downloading from <{url}>. Status updates via DM.') await ctx.channel.send(f'Downloading from <{url}>. Status updates via DM.')
#await ctx.defer() #if you need up to 15m to respond #await ctx.defer() #if you need up to 15m to respond
# 1/2 - download in separate thread, else progress_hook blocks downstream async ctx.send # 1/2 - download in separate thread, else progress_hook blocks downstream async ctx.send
download_thread = threading.Thread(target=download_video, args=(url,yoptions)) download_thread = threading.Thread(target=download_video, args=(url,yoptions))
download_thread.start() download_thread.start()
await asyncio.to_thread(download_thread.join) await asyncio.to_thread(download_thread.join)
# 2/2 - replace the above with this next try: # 2/2 - replace the above with this next try:
#try: #try:
# await asyncio.to_thread(download_video, url, yoptions) # await asyncio.to_thread(download_video, url, yoptions)
#except Exception as e: #except Exception as e:
# print(f"download failed: {e}") # print(f"download failed: {e}")
# await ctx.author.send(f"download failed: {str(e)}") # await ctx.author.send(f"download failed: {str(e)}")
@interactions.slash_command(name="interrupt",description="cancel current job") @interactions.slash_command(name="interrupt",description="cancel current job")
@interactions.check(interactions.is_owner()) @interactions.check(interactions.is_owner())
async def _interrupt(ctx): async def _interrupt(ctx):
# interrupt here # interrupt here
print('interrupting current job - not implemented') print('interrupting current job - not implemented')
await ctx.author.send('interrupting current job - not implemented') await ctx.author.send('interrupting current job - not implemented')
@interactions.slash_command(name="adduser",description="authorize target user") @interactions.slash_command(name="adduser",description="authorize target user")
@interactions.slash_option( @interactions.slash_option(
name="user", name="user",
opt_type=interactions.OptionType.USER, opt_type=interactions.OptionType.USER,
required=True, required=True,
description='enable this bot for target user', description='enable this bot for target user',
) )
@interactions.check(interactions.is_owner()) @interactions.check(interactions.is_owner())
async def _adduser(ctx: interactions.SlashContext, user:interactions.OptionType.USER): async def _adduser(ctx: interactions.SlashContext, user:interactions.OptionType.USER):
if str(user.id) not in authorized_users: if str(user.id) not in authorized_users:
authorized_users.append(str(user.id)) authorized_users.append(str(user.id))
with open(userFile,'w') as f: #overwrite file - fix later if other params come up with open(userFile,'w') as f: #overwrite file - fix later if other params come up
json.dump({'authorized_users':authorized_users}) json.dump({'authorized_users':authorized_users})
print('react:checkmark') print('react:checkmark')
await ctx.message.add_reaction('') await ctx.message.add_reaction('')
@interactions.slash_command(name="removeuser",description="deauthorize target user") @interactions.slash_command(name="removeuser",description="deauthorize target user")
@interactions.slash_option( @interactions.slash_option(
name="user", name="user",
opt_type=interactions.OptionType.USER, opt_type=interactions.OptionType.USER,
required=True, required=True,
description='disable this bot for target user', description='disable this bot for target user',
) )
@interactions.check(interactions.is_owner()) @interactions.check(interactions.is_owner())
async def _removeuser(ctx: interactions.SlashContext, user:interactions.OptionType.USER): async def _removeuser(ctx: interactions.SlashContext, user:interactions.OptionType.USER):
if str(user.id) in authorized_users: if str(user.id) in authorized_users:
# ? ? ? fix pls # ? ? ? fix pls
i = index(authorized_users(str(user.id))) i = index(authorized_users(str(user.id)))
# update list, rewrite json # update list, rewrite json
print('react:checkmark') print('react:checkmark')
await ctx.message.add_reaction('') await ctx.message.add_reaction('')
async def dl_hook(d): async def dl_hook(d):
msg = f'{d["status"]} {d["filename"]}' msg = f'{d["status"]} {d["filename"]}'
print(msg) print(msg)
await ctx.author.send(msg) await ctx.author.send(msg)
api_token = getenv('api_token') api_token = getenv('api_token')
if not api_token: if not api_token:
raise ValueError('API token not set. Retrieve from your Discord bot.') raise ValueError('API token not set. Retrieve from your Discord bot.')
bot.start(api_token) bot.start(api_token)