├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bot ├── __init__.py ├── admin_command.py ├── audio_extraction_params.py ├── audio_request_handler.py ├── bot_utils.py ├── exceptions.py ├── generic_message_handler.py ├── mp3_splitter.py ├── telegram_notifier.py ├── video_bot.py └── video_provider.py ├── common ├── __init__.py └── helper.py ├── config ├── __init__.py └── bot_config.py ├── docs ├── 20200710215605063_698259815.png └── tele-tube-mobile.gif ├── env.cfg.sample ├── requirements ├── base.txt └── dev.txt ├── scripts ├── setup_bot.sh ├── setup_dependencies.sh ├── start_screen.sh ├── stop_bot.sh └── stop_screen.sh └── tele-vdo-rider.py /.gitignore: -------------------------------------------------------------------------------- 1 | env.cfg 2 | **/.dropbox_key 3 | pyenv/** 4 | docker/** 5 | *.db 6 | *.db.md 7 | .idea 8 | mysql-data 9 | data 10 | notebooks 11 | tmp-data 12 | __pycache__ 13 | output_dir/ 14 | venv/ 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Deskriders Dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PROJECTNAME=$(shell basename "$(PWD)") 2 | 3 | .SILENT: ; # no need for @ 4 | 5 | setup: ## Setup Virtual Env 6 | python3 -m venv venv 7 | ./venv/bin/pip3 install -r requirements/dev.txt 8 | 9 | deps: ## Install dependencies 10 | ./venv/bin/pip3 install -r requirements/dev.txt 11 | 12 | lint: ## Runs black for code formatting 13 | ./venv/bin/black . --exclude venv 14 | 15 | clean: ## Clean package 16 | find . -type d -name '__pycache__' | xargs rm -rf 17 | rm -rf build dist 18 | 19 | deploy: clean ## Copies any changed file to the server 20 | ssh ${PROJECTNAME} -C 'bash -l -c "mkdir -vp ./tele-vdo-rider"' 21 | rsync -avzr \ 22 | env.cfg \ 23 | tele-vdo-rider.py \ 24 | bot \ 25 | common \ 26 | config \ 27 | requirements \ 28 | scripts \ 29 | ${PROJECTNAME}:./tele-vdo-rider 30 | 31 | start: deploy ## Sets up a screen session on the server and start the app 32 | ssh ${PROJECTNAME} -C 'bash -l -c "./tele-vdo-rider/scripts/setup_bot.sh ${PROJECTNAME}"' 33 | 34 | stop: deploy ## Stop any running screen session on the server 35 | ssh ${PROJECTNAME} -C 'bash -l -c "./tele-vdo-rider/scripts/stop_bot.sh ${PROJECTNAME}"' 36 | 37 | server: deploy ## Sets up dependencies required to run this bot 38 | ssh ${PROJECTNAME} -C 'bash -l -c "./tele-vdo-rider/scripts/setup_dependencies.sh"' 39 | 40 | ssh: ## SSH into the target VM 41 | ssh ${PROJECTNAME} 42 | 43 | run: lint ## Run bot locally 44 | ./venv/bin/python3 tele-vdo-rider.py 45 | 46 | .PHONY: help 47 | .DEFAULT_GOAL := help 48 | 49 | help: Makefile 50 | echo 51 | echo " Choose a command run in "$(PROJECTNAME)":" 52 | echo 53 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 54 | echo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Tube Telegram Rider 2 | 3 | [![GitHub license](https://img.shields.io/github/license/namuan/tele-vdo-rider.svg)](https://github.com/namuan/tele-vdo-rider/blob/master/LICENSE) [![Twitter Follow](https://img.shields.io/twitter/follow/deskriders_twt.svg?style=social&label=Follow)](https://twitter.com/deskriders_twt) 4 | 5 | Telegram Bot 🤖 to convert videos to mp3 at your service. 6 | It uses [youtube-dl](https://ytdl-org.github.io/youtube-dl/index.html) so videos from any [supported websites](https://ytdl-org.github.io/youtube-dl/supportedsites.html) can be used. 7 | 8 | ![](docs/tele-tube-mobile.gif) 9 | 10 | ✅ Support Youtube playlists along with a number of other websites. 11 | 12 | ✅ Convert to the best possible audio track 13 | 14 | ✅ Work around Telegram size limitation by chunking large MP3 files 15 | 16 | ### Clone project 17 | 18 | ```bash 19 | git clone https://github.com/namuan/tele-vdo-rider.git 20 | ``` 21 | 22 | ### Running it locally 23 | 24 | To run it, you'll need to create a new bot using [@botfather](https://t.me/botfather). 25 | Note down the *bot token* once your bot is registered. 26 | Copy `env.cfg.sample` to `env.cfg` and set the token value for `TELEGRAM_BOT_TOKEN` variable. 27 | 28 | ```bash 29 | cp env.cfg.sample env.cfg 30 | ``` 31 | 32 | Then we'll setup a local python virtual environment and install required dependencies. 33 | Make sure you have `python3` installed before running the following command. 34 | 35 | ```bash 36 | make setup 37 | ``` 38 | 39 | We also need to set up `ffmpeg` which is used to convert Video -> MP3. 40 | On a mac, it is a single command using `brew`. 41 | 42 | ```bash 43 | brew install ffmpeg 44 | ``` 45 | 46 | You'll find [instructions](https://ffmpeg.org/download.html) to set up on other platforms. 47 | 48 | Next, run the bot 49 | 50 | ```bash 51 | make run 52 | ``` 53 | 54 | If previous commands worked then this will start the bot. 55 | Try adding your new bot on Telegram and send a youtube video. 56 | 57 | Here is a good one to try. 58 | [The first 20 hours -- how to learn anything | Josh Kaufman | TEDxCSU](https://www.youtube.com/watch?v=5MgBikgcWnY) 59 | 60 | ### Self-Hosting 61 | 62 | Although running locally is fine for testing, you may want to run it in background to avoid any disruptions. 63 | Here is a quick guide for setting it up on a VPS or RaspberryPi (Once you get past installing ffmpeg on it 😡). 64 | 65 | **Step 1: Setup VPS or use existing server(Raspberry Pi)** 66 | Start a new VPS with **Ubuntu** on [Vultr](https://www.vultr.com/?ref=7306977) (Affiliate Link) or [DigitalOcean](https://m.do.co/c/da51ec30754c) (Affiliate Link). 67 | 68 | ![](docs/20200710215605063_698259815.png) 69 | 70 | ☕️ Wait for it to come up online. 71 | 72 | > 🧹 Remember: Clean up - Make sure you delete this server if not used to avoid incurring cost. 73 | 74 | **Step 2: Checking connectivity** 75 | 76 | Once you have the server running, we'll try to connect to it. 77 | It is better to set up a dedicated host entry as below. 78 | Some commands in the `Makefile` assumes that the host entry matches the project directory. 79 | 80 | > 👉 Tip: Splitting SSH Config - I used to use [poet](https://github.com/awendt/poet) to split ssh files but from [OpenSSH 7.3](http://man.openbsd.org/ssh_config#Include) it supports the `Include` directive to allow multiple ssh config files. 81 | > It makes it easy and manageable for working with many SSH entries 82 | 83 | ``` 84 | Host tele-vdo-rider 85 | User root 86 | HostName xx.xx.xx.xx 87 | Port 22 88 | IdentitiesOnly yes 89 | IdentityFile ~/.ssh/dfiles 90 | ``` 91 | 92 | So if you have the above entry under ~/.ssh, running the following command will try to connect and ssh into the server. 93 | 94 | ```bash 95 | $ make ssh 96 | ``` 97 | 98 | Make sure this works before continuing, and note that you may have to enter the Password from the VPS provider (Vultr/DigitalOcean). 99 | 100 | **Step 3: Installing dependencies** 101 | 102 | We also need to install a few dependencies if they are missing from the server. 103 | The following command will take care of that. 104 | 105 | ```bash 106 | # ssh into server 107 | $ make server 108 | ``` 109 | 110 | **Step 4: Starting up Bot** 111 | 112 | Again, we'll use the make command to start the bot in a screen session. 113 | 114 | ```bash 115 | make start 116 | ``` 117 | 118 | The bot is running once the command finishes. 119 | Try sending another Youtube video to see it in action. 120 | 121 | **Step 5: Testing if Bot is running** 122 | 123 | If there is anything wrong, you can see what is going on the server. 124 | 125 | ```bash 126 | # ssh 127 | make ssh 128 | 129 | # check screen sessions 130 | screen -ls 131 | 132 | # attach to existing screen session 133 | screen -x tele-vdo-rider 134 | 135 | # detach from a session 136 | Ctrl + A then D 137 | ``` 138 | 139 | Make sure that you detach from screen session before leaving the server to keep the bot running. 140 | 141 | **Step 6: [Optional] Updating Bot** 142 | 143 | Run the following command from your local machine, and it should update the bot and restart the session automatically. 144 | 145 | ```bash 146 | make start 147 | ``` 148 | 149 | ## Credits 150 | 151 | * [python-telegram-api](https://github.com/python-telegram-bot) 152 | * [ffmpeg](https://ffmpeg.org/) 153 | * [mp3splt](http://mp3splt.sourceforge.net/mp3splt_page/home.php) 154 | * [yt2audiobot](https://github.com/gise88/yt2audiobot) 155 | * [Telegram](https://telegram.org/) 156 | 157 | ## License 158 | 159 | See [LICENSE](LICENSE) 160 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | from .bot_utils import * 2 | from .admin_command import * 3 | -------------------------------------------------------------------------------- /bot/admin_command.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import threading 5 | import time 6 | 7 | from bot.video_bot import updater 8 | from . import restrict_access 9 | 10 | 11 | # This needs to be run on a new thread because calling 'updater.stop()' inside a 12 | # handler (shutdown_cmd) causes a deadlock because it waits for itself to finish 13 | def shutdown(): 14 | updater.stop() 15 | updater.is_idle = False 16 | 17 | 18 | def start_cmd(updater, bot=None, update=None): 19 | logging.info("PodTubeBot is running!") 20 | 21 | 22 | @restrict_access 23 | def shutdown_cmd(bot, update, chat_data): 24 | logging.info("Shutting down...") 25 | 26 | # See comments on the 'shutdown' function 27 | threading.Thread(target=shutdown).start() 28 | 29 | 30 | @restrict_access 31 | def restart_cmd(bot, update, chat_data): 32 | logging.info("Bot is restarting...") 33 | 34 | time.sleep(0.2) 35 | os.execl(sys.executable, sys.executable, *sys.argv) 36 | -------------------------------------------------------------------------------- /bot/audio_extraction_params.py: -------------------------------------------------------------------------------- 1 | from bot.audio_request_handler import AudioRequestHandler 2 | from config.bot_config import OUTPUT_FORMAT, PREFERRED_AUDIO_CODEC 3 | 4 | YOUTUBE_REGEX = "^((http(s)?:\/\/)?)(www\.)?(m\.)?((youtube\.com\/)|(youtu.be\/))[\S]+" 5 | 6 | YOUTUBE_EXTRACT_AUDIO_PARAMS = { 7 | "outtmpl": OUTPUT_FORMAT, 8 | "format": "bestaudio/best", 9 | "socket_timeout": 10, 10 | "postprocessors": [ 11 | { 12 | "key": "FFmpegExtractAudio", 13 | "preferredcodec": PREFERRED_AUDIO_CODEC, 14 | "preferredquality": "192", 15 | } 16 | ], 17 | "retries": 10, 18 | "prefer_ffmpeg": True, 19 | "keepvideo": True, 20 | } 21 | 22 | SOUND_CLOUD_EXTRACT_AUDIO_PARAMS = { 23 | "outtmpl": OUTPUT_FORMAT, 24 | "format": "bestaudio/best", 25 | "socket_timeout": 10, 26 | "retries": 10, 27 | "prefer_ffmpeg": True, 28 | "keepvideo": True, 29 | } 30 | 31 | 32 | def create_audio_extraction_params(video_extractor, notifier): 33 | extraction_param = ( 34 | SOUND_CLOUD_EXTRACT_AUDIO_PARAMS 35 | if is_sound_cloud_video(video_extractor) 36 | else YOUTUBE_EXTRACT_AUDIO_PARAMS 37 | ) 38 | return AudioRequestHandler(extraction_param, notifier) 39 | 40 | 41 | def is_yt_video(extractor): 42 | return extractor == "youtube" 43 | 44 | 45 | def is_sound_cloud_video(extractor): 46 | return extractor == "soundcloud" 47 | -------------------------------------------------------------------------------- /bot/audio_request_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import youtube_dl as yt 5 | 6 | from bot.exceptions import FileIsTooLargeException 7 | from bot.mp3_splitter import Mp3Splitter 8 | from common.helper import format_size, rename_file 9 | from config.bot_config import PREFERRED_AUDIO_CODEC, AUDIO_OUTPUT_DIR 10 | from config import bot_cfg 11 | 12 | split_file = int(bot_cfg("SPLIT_FILE", 0)) # 0 == False 13 | 14 | 15 | class AudioRequestHandler: 16 | def __init__(self, extraction_param, notifier): 17 | self.extraction_param = extraction_param 18 | self.notifier = notifier 19 | self.video_info = None 20 | 21 | def get_downloaded_file_abspath(self): 22 | filename = self.video_id() + "." + PREFERRED_AUDIO_CODEC 23 | return os.path.abspath(os.path.join(AUDIO_OUTPUT_DIR, filename)) 24 | 25 | def video_id(self): 26 | return self.video_info["id"] 27 | 28 | def video_title(self): 29 | return self.video_info["title"] 30 | 31 | def video_url(self): 32 | return self.video_info["webpage_url"] 33 | 34 | def process_video(self, yt_video): 35 | self.video_info = yt_video 36 | ydl = yt.YoutubeDL(self.extraction_param) 37 | ydl.download([self.video_url()]) 38 | downloaded_filename = self.get_downloaded_file_abspath() 39 | file_size = os.path.getsize(downloaded_filename) 40 | 41 | filename = rename_file( 42 | self.get_downloaded_file_abspath(), 43 | self.video_title(), 44 | ) 45 | logging.info("Downloaded file now saved at: {}".format(filename)) 46 | 47 | formatted_file_size = format_size(file_size) 48 | downloaded_video_message = "🔈 File size: {}".format(formatted_file_size) 49 | self.notifier.progress_update(downloaded_video_message) 50 | logging.info(downloaded_video_message) 51 | 52 | # if the extracted audio file is larger than 50M 53 | allowed_file_size = 50 54 | if file_size >> 20 > allowed_file_size: 55 | file_size_warning = "😱 File size {} > allowed {} therefore trying to chunk into smaller files".format( 56 | formatted_file_size, allowed_file_size 57 | ) 58 | self.notifier.progress_update(file_size_warning) 59 | logging.info(file_size_warning) 60 | 61 | if bool(split_file): 62 | mp3_splitter = Mp3Splitter(filename) 63 | for chunk_filename in mp3_splitter.split_chunks(): 64 | yield {"filename": chunk_filename} 65 | else: 66 | raise FileIsTooLargeException( 67 | "File too big to transfer. Copy with FTP/SFTP" 68 | ) 69 | else: 70 | yield { 71 | "filename": filename, 72 | } 73 | -------------------------------------------------------------------------------- /bot/bot_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from config import bot_cfg 4 | 5 | 6 | # Decorator to restrict access if user is not the same as in config 7 | def restrict_access(func): 8 | def _restrict_access(bot, update, chat_data): 9 | chat_id = get_chat_id(update) 10 | if str(chat_id) != bot_cfg("TELEGRAM_USER_ID"): 11 | # Inform owner of bot 12 | msg = "Access denied for user %s" % chat_id 13 | bot.send_message(bot_cfg("TELEGRAM_USER_ID"), text=msg) 14 | 15 | logging.info(msg) 16 | return 17 | else: 18 | return func(bot, update, chat_data) 19 | 20 | return _restrict_access 21 | 22 | 23 | # Return chat ID for an update object 24 | def get_chat_id(update=None): 25 | if update.message: 26 | return update.message.chat_id 27 | elif update.callback_query: 28 | return update.callback_query.from_user["id"] 29 | 30 | 31 | # Handle all telegram and telegram.ext related errors 32 | def handle_telegram_error(update, error): 33 | error_str = "Update '%s' caused error '%s'" % (update, error) 34 | logging.error(error_str) 35 | -------------------------------------------------------------------------------- /bot/exceptions.py: -------------------------------------------------------------------------------- 1 | class FileIsTooLargeException(ValueError): 2 | pass 3 | 4 | 5 | class DownloadError(ValueError): 6 | pass 7 | -------------------------------------------------------------------------------- /bot/generic_message_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from telegram.ext import MessageHandler, Filters 4 | 5 | from bot.video_provider import VideoProvider 6 | 7 | 8 | class GenericMessageHandler: 9 | def __init__(self, dispatcher): 10 | self.video_link_handler = MessageHandler( 11 | Filters.text & (~Filters.command), self.create_video_provider 12 | ) 13 | dispatcher.add_handler(self.video_link_handler) 14 | 15 | @staticmethod 16 | def create_video_provider(update, context): 17 | bot = context.bot 18 | cid = update.effective_chat.id 19 | video_link = update.message.text 20 | logging.info("Received message: {}".format(video_link)) 21 | try: 22 | video_provider = VideoProvider(bot, cid) 23 | reply_message = bot.send_message( 24 | cid, 25 | "Got it. 👀 at 📼".format(video_link), 26 | disable_web_page_preview=True, 27 | ) 28 | video_provider.process(video_link, reply_message.message_id) 29 | bot.delete_message(cid, reply_message.message_id) 30 | logging.info("Finished processing {}".format(video_link)) 31 | except Exception as e: 32 | bot.send_message( 33 | "🆘 Looks like something went wrong. " 34 | "\nWe'll have a look and try to fix this issue." 35 | ) 36 | logging.exception( 37 | "Error processing request for {} and video link: {}".format( 38 | cid, video_link 39 | ), 40 | ) 41 | -------------------------------------------------------------------------------- /bot/mp3_splitter.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import subprocess 4 | 5 | from config import bot_cfg 6 | 7 | split_time = bot_cfg("SPLIT_TIME_FOR_OVERSIZED_FILES") 8 | 9 | 10 | class Mp3Splitter(object): 11 | def __init__(self, original_file_path): 12 | self.original_file_path = original_file_path 13 | self.file_directory = os.path.dirname(original_file_path) 14 | self.file_name = os.path.basename(original_file_path) 15 | 16 | def split_chunks(self): 17 | subprocess.call( 18 | 'mp3splt -t {} -o "@n @f" "{}"'.format(split_time, self.original_file_path), 19 | shell=True, 20 | ) 21 | os.remove(self.original_file_path) 22 | return [ 23 | f for f in glob.glob("{}/* {}".format(self.file_directory, self.file_name)) 24 | ] 25 | -------------------------------------------------------------------------------- /bot/telegram_notifier.py: -------------------------------------------------------------------------------- 1 | class TelegramNotifier: 2 | def __init__(self, bot, chat_id, update_message_id): 3 | self.bot = bot 4 | self.chat_id = chat_id 5 | self.update_message_id = update_message_id 6 | 7 | def progress_update(self, update_message): 8 | self.bot.edit_message_text( 9 | update_message, 10 | self.chat_id, 11 | self.update_message_id, 12 | disable_web_page_preview=True, 13 | ) 14 | -------------------------------------------------------------------------------- /bot/video_bot.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import Updater 2 | 3 | from config import bot_cfg 4 | 5 | 6 | class VideoBot: 7 | def __init__(self): 8 | self.bot_token = bot_cfg("TELEGRAM_BOT_TOKEN") 9 | self.updater = Updater(token=self.bot_token, use_context=True) 10 | self.dispatcher = self.updater.dispatcher 11 | 12 | 13 | bot = VideoBot() 14 | updater = bot.updater 15 | dispatcher = bot.dispatcher 16 | -------------------------------------------------------------------------------- /bot/video_provider.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import youtube_dl as yt 4 | 5 | from bot.audio_extraction_params import create_audio_extraction_params 6 | from bot.exceptions import FileIsTooLargeException 7 | from bot.telegram_notifier import TelegramNotifier 8 | 9 | 10 | class Video: 11 | def __init__(self, video_info): 12 | self.info = video_info 13 | 14 | 15 | class VideoProvider: 16 | def __init__(self, bot, chat_id): 17 | self.bot = bot 18 | self.chat_id = chat_id 19 | self.video_info = None 20 | 21 | def process(self, video_link, update_message_id): 22 | notifier = TelegramNotifier(self.bot, self.chat_id, update_message_id) 23 | yt_downloader = yt.YoutubeDL({"socket_timeout": 10}) 24 | self.video_info = yt_downloader.extract_info(video_link, download=False) 25 | notifier.progress_update("‍🤖 processing video") 26 | request_handler = create_audio_extraction_params( 27 | self.video_info.get("extractor"), notifier 28 | ) 29 | try: 30 | for yt_video in self.yt_videos(): 31 | logging.info("Processing Video -> {}".format(yt_video.info)) 32 | for data in request_handler.process_video(yt_video.info): 33 | filename = data["filename"] 34 | audio = open(filename, "rb") 35 | 36 | notifier.progress_update( 37 | "Almost there. Uploading {} 🔈".format( 38 | os.path.basename(filename) 39 | ) 40 | ) 41 | 42 | self.bot.send_chat_action(self.chat_id, "upload_audio") 43 | self.bot.send_audio( 44 | self.chat_id, 45 | audio, 46 | caption="Downloaded using @podtubebot", 47 | timeout=120, 48 | ) 49 | except FileIsTooLargeException as e: 50 | logging.error("[File Is Too Large] %s", e) 51 | self.bot.send_message(self.chat_id, str(e), disable_web_page_preview=True) 52 | except Exception as e: 53 | logging.exception("Something bad happened: %s", e) 54 | self.bot.send_message(self.chat_id, str(e), disable_web_page_preview=True) 55 | 56 | notifier.progress_update("Done! ✅") 57 | 58 | def yt_videos(self): 59 | if not self.is_yt_playlist(): 60 | yield Video(self.video_info) 61 | else: 62 | for entry in self.get_playlist_videos(): 63 | yield Video(entry) 64 | 65 | def is_yt_playlist(self): 66 | if "playlist" in self.get_type(): 67 | return True 68 | return False 69 | 70 | def get_playlist_videos(self): 71 | return self.video_info["entries"] 72 | 73 | def get_type(self): 74 | return self.video_info["extractor"] 75 | -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- 1 | from .helper import * 2 | -------------------------------------------------------------------------------- /common/helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import time 4 | from datetime import datetime 5 | 6 | 7 | def datetime_from_timestamp(unix_timestamp): 8 | return datetime.fromtimestamp(int(unix_timestamp)).strftime("%Y-%m-%d %H:%M:%S") 9 | 10 | 11 | def datetime_now(): 12 | return datetime_from_timestamp(time.time()) 13 | 14 | 15 | def load_json(file_name): 16 | with open(file_name, "r") as json_file: 17 | return json.loads(json_file.read()) 18 | 19 | 20 | def save_json(file_name, data): 21 | with open(file_name, "w") as json_file: 22 | return json_file.write(json.dumps(data)) 23 | 24 | 25 | def format_size(size): 26 | units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"] 27 | size = float(size) 28 | i = 0 29 | while size >= 1024.0 and i < len(units): 30 | i += 1 31 | size /= 1024.0 32 | return "%.2f %s" % (size, units[i]) 33 | 34 | 35 | def rename_file(old_filename, new_filename): 36 | full_path, filename = os.path.split(old_filename) 37 | filename, extension = os.path.splitext(filename) 38 | temp_filename = os.path.join(full_path, new_filename + extension) 39 | os.rename(old_filename, temp_filename) 40 | return temp_filename 41 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from .bot_config import config as bot_cfg 4 | 5 | 6 | def init_logger(): 7 | handlers = [ 8 | logging.StreamHandler(), 9 | ] 10 | 11 | logging.basicConfig( 12 | handlers=handlers, 13 | format="%(asctime)s - %(filename)s:%(lineno)d - %(message)s", 14 | datefmt="%Y-%m-%d %H:%M:%S", 15 | level=logging.INFO, 16 | ) 17 | logging.captureWarnings(capture=True) 18 | -------------------------------------------------------------------------------- /config/bot_config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | from pathlib import Path 4 | 5 | AUDIO_OUTPUT_DIR = Path("~/OutputDir").expanduser().joinpath("videos-to-mp3") 6 | OUTPUT_FORMAT = os.path.join(AUDIO_OUTPUT_DIR, "%(id)s.%(ext)s") 7 | PREFERRED_AUDIO_CODEC = "mp3" 8 | 9 | 10 | def config(key, default_value=None): 11 | c = configparser.ConfigParser() 12 | c.read("env.cfg") 13 | 14 | return c.get("ALL", key) or default_value 15 | -------------------------------------------------------------------------------- /docs/20200710215605063_698259815.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/tele-vdo-rider/ad58fab90691425093eac7fb4c8627bf8d706f37/docs/20200710215605063_698259815.png -------------------------------------------------------------------------------- /docs/tele-tube-mobile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/tele-vdo-rider/ad58fab90691425093eac7fb4c8627bf8d706f37/docs/tele-tube-mobile.gif -------------------------------------------------------------------------------- /env.cfg.sample: -------------------------------------------------------------------------------- 1 | [ALL] 2 | TELEGRAM_BOT_TOKEN = xxx 3 | TELEGRAM_USER_ID = xxx 4 | SPLIT_TIME_FOR_OVERSIZED_FILES = 10.0 5 | SPLIT_FILE = 0 6 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | requests 2 | telegram 3 | python-telegram-bot 4 | youtube-dl 5 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | black -------------------------------------------------------------------------------- /scripts/setup_bot.sh: -------------------------------------------------------------------------------- 1 | cd $1 || exit 2 | test -d venv || python3 -m venv venv 3 | #venv/bin/pip3 install -r requirements/base.txt 4 | bash ./scripts/start_screen.sh tele-vdo-rider 'venv/bin/python3 tele-vdo-rider.py' -------------------------------------------------------------------------------- /scripts/setup_dependencies.sh: -------------------------------------------------------------------------------- 1 | # update apt 2 | sudo apt -y update 3 | 4 | # install python/pip (version >3) 5 | sudo apt -y install python3-pip 6 | 7 | # install ffmpeg and mp3splt 8 | sudo apt -y install mp3splt ffmpeg -------------------------------------------------------------------------------- /scripts/start_screen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [[ $# -eq 0 ]] ; then 3 | echo 'COMMAND required as first argument. python3.6 ...' 4 | exit 0 5 | fi 6 | 7 | SCREEN_NAME=$1 8 | SCREEN_CMD=$2 9 | 10 | screen -X -S "${SCREEN_NAME}" stuff "^C" 11 | screen -X -S "${SCREEN_NAME}" quit 12 | screen -d -m -S "${SCREEN_NAME}" 13 | screen -S "${SCREEN_NAME}" -p 0 -X stuff "${SCREEN_CMD}$(printf \\r)" -------------------------------------------------------------------------------- /scripts/stop_bot.sh: -------------------------------------------------------------------------------- 1 | cd $1 || exit 2 | bash ./scripts/stop_screen.sh tele-vdo-rider -------------------------------------------------------------------------------- /scripts/stop_screen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [[ $# -eq 0 ]]; then 3 | echo 'COMMAND required as first argument. python3.6 ...' 4 | exit 0 5 | fi 6 | 7 | SCREEN_NAME=$1 8 | 9 | screen -X -S "${SCREEN_NAME}" stuff "^C" 10 | screen -X -S "${SCREEN_NAME}" quit 11 | 12 | -------------------------------------------------------------------------------- /tele-vdo-rider.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """ 3 | python3 tele-vdo-rider.py 4 | """ 5 | from telegram.ext import CommandHandler 6 | 7 | from bot import * 8 | from bot.generic_message_handler import GenericMessageHandler 9 | from bot.video_bot import dispatcher, updater 10 | from config import init_logger 11 | 12 | if __name__ == "__main__": 13 | init_logger() 14 | dispatcher.add_error_handler(handle_telegram_error) 15 | 16 | # Add command handlers to dispatcher 17 | dispatcher.add_handler(CommandHandler("start", start_cmd)) 18 | dispatcher.add_handler(CommandHandler("restart", restart_cmd, pass_chat_data=True)) 19 | dispatcher.add_handler( 20 | CommandHandler("shutdown", shutdown_cmd, pass_chat_data=True) 21 | ) 22 | 23 | # Register message handler 24 | GenericMessageHandler(dispatcher) 25 | 26 | updater.start_polling() 27 | 28 | updater.idle() 29 | --------------------------------------------------------------------------------