├── bot ├── GreyMatter's ├── helpers │ ├── GreyMatter's │ ├── downloader.py │ └── uploader.py ├── plugins │ ├── GreyMatter's │ ├── non-auth-user.py │ ├── cancel.py │ ├── start.py │ ├── authentication.py │ ├── help.py │ └── upload.py ├── youtube │ ├── GreyMatter's │ ├── __init__.py │ ├── auth.py │ └── youtube.py ├── __main__.py ├── config.py ├── utubebot.py └── translations.py ├── Dockerfile └── requirements.txt /bot/GreyMatter's: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/helpers/GreyMatter's: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/plugins/GreyMatter's: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/youtube/GreyMatter's: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/youtube/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import GoogleAuth 2 | from .youtube import YouTube 3 | 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM harshpreets63/random:simple 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY . . 6 | 7 | RUN pip3 install -r requirements.txt 8 | 9 | CMD python3 -m bot -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from .utubebot import UtubeBot 4 | from .config import Config 5 | 6 | 7 | if __name__ == "__main__": 8 | logging.basicConfig(level=logging.DEBUG if Config.DEBUG else logging.INFO) 9 | logging.getLogger("pyrogram").setLevel( 10 | logging.INFO if Config.DEBUG else logging.WARNING 11 | ) 12 | 13 | UtubeBot().run() 14 | -------------------------------------------------------------------------------- /bot/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | class Config: 4 | 5 | BOT_TOKEN = "" 6 | 7 | SESSION_NAME = "" 8 | 9 | API_ID = "" 10 | 11 | API_HASH = "" 12 | 13 | CLIENT_ID = "" 14 | 15 | CLIENT_SECRET = "" 16 | 17 | AUTH_USERS = [942731625] 18 | 19 | VIDEO_DESCRIPTION = "" 20 | 21 | VIDEO_CATEGORY = "" 22 | 23 | VIDEO_TITLE_PREFIX = "" 24 | 25 | VIDEO_TITLE_SUFFIX = "" 26 | 27 | DEBUG = bool() 28 | 29 | UPLOAD_MODE = "unlisted" 30 | 31 | CRED_FILE = "auth_token.txt" -------------------------------------------------------------------------------- /bot/utubebot.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client 2 | 3 | from .config import Config 4 | 5 | 6 | class UtubeBot(Client): 7 | def __init__(self): 8 | super().__init__( 9 | session_name=Config.SESSION_NAME, 10 | bot_token=Config.BOT_TOKEN, 11 | api_id=Config.API_ID, 12 | api_hash=Config.API_HASH, 13 | plugins=dict(root="bot.plugins"), 14 | workers=6, 15 | ) 16 | self.DOWNLOAD_WORKERS = 6 17 | self.counter = 0 18 | self.download_controller = {} 19 | -------------------------------------------------------------------------------- /bot/plugins/non-auth-user.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pyrogram import filters as Filters 4 | from pyrogram.types import Message 5 | 6 | from ..utubebot import UtubeBot 7 | from ..config import Config 8 | 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | @UtubeBot.on_message( 14 | Filters.private & Filters.incoming & ~Filters.user(Config.AUTH_USERS) 15 | ) 16 | async def _non_auth_usr_msg(c: UtubeBot, m: Message): 17 | await m.delete(True) 18 | log.info( 19 | f"{Config.AUTH_USERS} Unauthorised user {m.chat} contacted. Message {m} deleted!!" 20 | ) 21 | -------------------------------------------------------------------------------- /bot/plugins/cancel.py: -------------------------------------------------------------------------------- 1 | from pyrogram import filters as Filters 2 | from pyrogram.types import CallbackQuery 3 | 4 | from ..utubebot import UtubeBot 5 | 6 | 7 | @UtubeBot.on_callback_query( 8 | Filters.create(lambda _, __, query: query.data.startswith("cncl+")) 9 | ) 10 | async def cncl(c: UtubeBot, q: CallbackQuery) -> None: 11 | _, pid = q.data.split("+") 12 | if not c.download_controller.get(pid, False): 13 | await q.answer("Your process is not currently active!", show_alert=True) 14 | return 15 | c.download_controller[pid] = False 16 | await q.answer("Your process will be cancelled soon!", show_alert=True) 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | async-lru==1.0.2 2 | cachetools==4.2.2 3 | certifi==2021.5.30 4 | charset-normalizer==2.0.6 5 | google-api-core==2.0.1 6 | google-api-python-client==2.22.0 7 | google-auth==2.1.0 8 | google-auth-httplib2==0.1.0 9 | google-auth-oauthlib==0.4.6 10 | googleapis-common-protos==1.53.0 11 | httplib2==0.19.1 12 | idna==3.2 13 | oauth2client==4.1.3 14 | oauthlib==3.1.1 15 | pyaes==1.6.1 16 | pyasn1==0.4.8 17 | pyasn1-modules==0.2.8 18 | pyparsing==2.4.7 19 | Pyrogram==1.2.9 20 | PySocks==1.7.1 21 | requests==2.26.0 22 | requests-oauthlib==1.3.0 23 | rsa==4.7.2 24 | six==1.16.0 25 | TgCrypto==1.2.2 26 | uritemplate==3.0.1 27 | urllib3==1.26.7 28 | -------------------------------------------------------------------------------- /bot/plugins/start.py: -------------------------------------------------------------------------------- 1 | from pyrogram import filters as Filters 2 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message 3 | 4 | from ..translations import Messages as tr 5 | from ..config import Config 6 | from ..utubebot import UtubeBot 7 | 8 | 9 | @UtubeBot.on_message( 10 | Filters.private 11 | & Filters.incoming 12 | & Filters.command("start") 13 | & Filters.user(Config.AUTH_USERS) 14 | ) 15 | async def _start(c: UtubeBot, m: Message): 16 | await m.reply_chat_action("typing") 17 | await m.reply_text( 18 | text=tr.START_MSG.format(m.from_user.first_name), 19 | quote=True, 20 | disable_web_page_preview=True, 21 | ) 22 | -------------------------------------------------------------------------------- /bot/helpers/downloader.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from typing import Optional, Tuple, Union 4 | 5 | from pyrogram.types import Message 6 | 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | class Downloader: 12 | def __init__(self, m: Message): 13 | self.m = m 14 | self.status: Optional[bool] = None 15 | self.callback: Optional[callable] = None 16 | self.args: Optional[tuple] = None 17 | self.message: Optional[str] = None 18 | self.start_time: Optional[float] = None 19 | self.downloaded_file: Optional[str] = None 20 | 21 | async def start(self, progress: callable = None, *args) -> Tuple[bool, str]: 22 | self.callback = progress 23 | self.args = args 24 | 25 | await self._download() 26 | 27 | return self.status, self.message 28 | 29 | async def _download(self) -> None: 30 | try: 31 | self.start_time = time.time() 32 | 33 | self.downloaded_file = await self.m.reply_to_message.download( 34 | progress=self._callback 35 | ) 36 | 37 | log.debug(self.downloaded_file) 38 | 39 | if not self.downloaded_file: 40 | self.status = False 41 | self.message = ( 42 | "Download failed either because user cancelled or telegram refused!" 43 | ) 44 | else: 45 | self.status = True 46 | self.message = self.downloaded_file 47 | 48 | except Exception as e: 49 | log.error(e, exc_info=True) 50 | self.status = False 51 | self.message = f"Error occuered during download.\nError details: {e}" 52 | 53 | async def _callback(self, cur: Union[int, float], tot: Union[int, float]) -> None: 54 | if not self.callback: 55 | return 56 | 57 | await self.callback(cur, tot, self.start_time, "Downloading...", *self.args) 58 | -------------------------------------------------------------------------------- /bot/youtube/auth.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import httplib2 3 | import os 4 | 5 | from apiclient import discovery 6 | from oauth2client.client import ( 7 | OAuth2WebServerFlow, 8 | FlowExchangeError, 9 | OAuth2Credentials, 10 | ) 11 | from oauth2client.file import Storage 12 | 13 | 14 | class AuthCodeInvalidError(Exception): 15 | pass 16 | 17 | 18 | class InvalidCredentials(Exception): 19 | pass 20 | 21 | 22 | class NoCredentialFile(Exception): 23 | pass 24 | 25 | 26 | class GoogleAuth: 27 | OAUTH_SCOPE = ["https://www.googleapis.com/auth/youtube.upload"] 28 | REDIRECT_URI = "https://localhost:1/" 29 | API_SERVICE_NAME = "youtube" 30 | API_VERSION = "v3" 31 | 32 | def __init__(self, CLIENT_ID: str, CLIENT_SECRET: str): 33 | self.flow = OAuth2WebServerFlow( 34 | CLIENT_ID, CLIENT_SECRET, self.OAUTH_SCOPE, redirect_uri=self.REDIRECT_URI 35 | ) 36 | self.credentials: Optional[OAuth2Credentials] = None 37 | 38 | def GetAuthUrl(self) -> str: 39 | return self.flow.step1_get_authorize_url() 40 | 41 | def Auth(self, code: str) -> None: 42 | try: 43 | self.credentials = self.flow.step2_exchange(code) 44 | except FlowExchangeError as e: 45 | raise AuthCodeInvalidError(e) 46 | except Exception: 47 | raise 48 | 49 | def authorize(self): 50 | try: 51 | if self.credentials: 52 | http = httplib2.Http() 53 | self.credentials.refresh(http) 54 | http = self.credentials.authorize(http) 55 | return discovery.build( 56 | self.API_SERVICE_NAME, self.API_VERSION, http=http 57 | ) 58 | else: 59 | raise InvalidCredentials("No credentials!") 60 | except Exception: 61 | raise 62 | 63 | def LoadCredentialsFile(self, cred_file: str) -> None: 64 | if not os.path.isfile(cred_file): 65 | raise NoCredentialFile( 66 | "No credential file named {} is found.".format(cred_file) 67 | ) 68 | storage = Storage(cred_file) 69 | self.credentials = storage.get() 70 | 71 | def SaveCredentialsFile(self, cred_file: str) -> None: 72 | storage = Storage(cred_file) 73 | storage.put(self.credentials) 74 | -------------------------------------------------------------------------------- /bot/plugins/authentication.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pyrogram import filters as Filters 4 | from pyrogram.types import Message 5 | 6 | from ..youtube import GoogleAuth 7 | from ..config import Config 8 | from ..translations import Messages as tr 9 | from ..utubebot import UtubeBot 10 | 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | @UtubeBot.on_message( 16 | Filters.private 17 | & Filters.incoming 18 | & Filters.command("authorise") 19 | & Filters.user(Config.AUTH_USERS) 20 | ) 21 | async def _auth(c: UtubeBot, m: Message) -> None: 22 | if len(m.command) == 1: 23 | await m.reply_text(tr.NO_AUTH_CODE_MSG, True) 24 | return 25 | 26 | code = m.command[1] 27 | 28 | try: 29 | auth = GoogleAuth(Config.CLIENT_ID, Config.CLIENT_SECRET) 30 | 31 | auth.Auth(code) 32 | 33 | auth.SaveCredentialsFile(Config.CRED_FILE) 34 | 35 | msg = await m.reply_text(tr.AUTH_SUCCESS_MSG, True) 36 | 37 | with open(Config.CRED_FILE, "r") as f: 38 | cred_data = f.read() 39 | 40 | log.debug(f"Authentication success, auth data saved to {Config.CRED_FILE}") 41 | 42 | msg2 = await msg.reply_text(cred_data, parse_mode=None) 43 | await msg2.reply_text( 44 | "This is your authorisation data! Save this for later use. Reply /save_auth_data to the authorisation " 45 | "data to re authorise later.", 46 | True, 47 | ) 48 | 49 | except Exception as e: 50 | log.error(e, exc_info=True) 51 | await m.reply_text(tr.AUTH_FAILED_MSG.format(e), True) 52 | 53 | 54 | @UtubeBot.on_message( 55 | Filters.private 56 | & Filters.incoming 57 | & Filters.command("save_auth_data") 58 | & Filters.reply 59 | & Filters.user(Config.AUTH_USERS) 60 | ) 61 | async def _save_auth_data(c: UtubeBot, m: Message) -> None: 62 | auth_data = m.reply_to_message.text 63 | try: 64 | with open(Config.CRED_FILE, "w") as f: 65 | f.write(auth_data) 66 | 67 | auth = GoogleAuth(Config.CLIENT_ID, Config.CLIENT_SECRET) 68 | auth.LoadCredentialsFile(Config.CRED_FILE) 69 | auth.authorize() 70 | 71 | await m.reply_text(tr.AUTH_DATA_SAVE_SUCCESS, True) 72 | log.debug(f"Authentication success, auth data saved to {Config.CRED_FILE}") 73 | except Exception as e: 74 | log.error(e, exc_info=True) 75 | await m.reply_text(tr.AUTH_FAILED_MSG.format(e), True) 76 | -------------------------------------------------------------------------------- /bot/plugins/help.py: -------------------------------------------------------------------------------- 1 | from pyrogram import filters as Filters 2 | from pyrogram.types import ( 3 | InlineKeyboardMarkup, 4 | InlineKeyboardButton, 5 | Message, 6 | CallbackQuery, 7 | ) 8 | 9 | from ..youtube import GoogleAuth 10 | from ..config import Config 11 | from ..translations import Messages as tr 12 | from ..utubebot import UtubeBot 13 | 14 | 15 | def map_btns(pos): 16 | if pos == 1: 17 | button = [[InlineKeyboardButton(text="-->", callback_data="help+2")]] 18 | elif pos == len(tr.HELP_MSG) - 1: 19 | auth = GoogleAuth(Config.CLIENT_ID, Config.CLIENT_SECRET) 20 | url = auth.GetAuthUrl() 21 | button = [ 22 | [InlineKeyboardButton(text="<--", callback_data=f"help+{pos-1}")], 23 | [InlineKeyboardButton(text="Login URL", url=url)], 24 | ] 25 | else: 26 | button = [ 27 | [ 28 | InlineKeyboardButton(text="<--", callback_data=f"help+{pos-1}"), 29 | InlineKeyboardButton(text="-->", callback_data=f"help+{pos+1}"), 30 | ], 31 | ] 32 | return button 33 | 34 | 35 | @UtubeBot.on_message( 36 | Filters.private 37 | & Filters.incoming 38 | & Filters.command("help") 39 | & Filters.user(Config.AUTH_USERS) 40 | ) 41 | async def _help(c: UtubeBot, m: Message): 42 | await m.reply_chat_action("typing") 43 | await m.reply_text( 44 | text=tr.HELP_MSG[1], 45 | reply_markup=InlineKeyboardMarkup(map_btns(1)), 46 | ) 47 | 48 | 49 | help_callback_filter = Filters.create( 50 | lambda _, __, query: query.data.startswith("help+") 51 | ) 52 | 53 | 54 | @UtubeBot.on_callback_query(help_callback_filter) 55 | async def help_answer(c: UtubeBot, q: CallbackQuery): 56 | pos = int(q.data.split("+")[1]) 57 | await q.answer() 58 | await q.edit_message_text( 59 | text=tr.HELP_MSG[pos], reply_markup=InlineKeyboardMarkup(map_btns(pos)) 60 | ) 61 | 62 | 63 | auth = GoogleAuth(Config.CLIENT_ID, Config.CLIENT_SECRET) 64 | url = auth.GetAuthUrl() 65 | 66 | @UtubeBot.on_message( 67 | Filters.private 68 | & Filters.incoming 69 | & Filters.command("login") 70 | & Filters.user(Config.AUTH_USERS) 71 | ) 72 | async def _login(c: UtubeBot, m: Message): 73 | await m.reply_chat_action("typing") 74 | await m.reply_text( 75 | text=tr.LOGIN_MSG, 76 | reply_markup=InlineKeyboardMarkup( 77 | [[InlineKeyboardButton(text="Authentication URL", url=url)]] 78 | ) 79 | ) 80 | -------------------------------------------------------------------------------- /bot/helpers/uploader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import asyncio 4 | import logging 5 | from typing import Optional, Tuple 6 | 7 | from ..youtube import GoogleAuth, YouTube 8 | from ..config import Config 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class Uploader: 15 | def __init__(self, file: str, title: Optional[str] = None): 16 | self.file = file 17 | self.title = title 18 | self.video_category = { 19 | 1: "Film & Animation", 20 | 2: "Autos & Vehicles", 21 | 10: "Music", 22 | 15: "Pets & Animal", 23 | 17: "Sports", 24 | 19: "Travel & Events", 25 | 20: "Gaming", 26 | 22: "People & Blogs", 27 | 23: "Comedy", 28 | 24: "Entertainment", 29 | 25: "News & Politics", 30 | 26: "Howto & Style", 31 | 27: "Education", 32 | 28: "Science & Technology", 33 | 29: "Nonprofits & Activism", 34 | } 35 | 36 | async def start(self, progress: callable = None, *args) -> Tuple[bool, str]: 37 | self.progress = progress 38 | self.args = args 39 | 40 | await self._upload() 41 | 42 | return self.status, self.message 43 | 44 | async def _upload(self) -> None: 45 | try: 46 | loop = asyncio.get_running_loop() 47 | 48 | auth = GoogleAuth(Config.CLIENT_ID, Config.CLIENT_SECRET) 49 | 50 | if not os.path.isfile(Config.CRED_FILE): 51 | log.debug(f"{Config.CRED_FILE} does not exist") 52 | self.status = False 53 | self.message = "Upload failed because you did not authenticate me." 54 | return 55 | 56 | auth.LoadCredentialsFile(Config.CRED_FILE) 57 | google = await loop.run_in_executor(None, auth.authorize) 58 | if Config.VIDEO_CATEGORY and Config.VIDEO_CATEGORY in self.video_category: 59 | categoryId = Config.VIDEO_CATEGORY 60 | else: 61 | categoryId = random.choice(list(self.video_category)) 62 | 63 | categoryName = self.video_category[categoryId] 64 | title = self.title if self.title else os.path.basename(self.file) 65 | title = ( 66 | (Config.VIDEO_TITLE_PREFIX + title + Config.VIDEO_TITLE_SUFFIX) 67 | .replace("<", "") 68 | .replace(">", "")[:100] 69 | ) 70 | description = ( 71 | Config.VIDEO_DESCRIPTION 72 | )[:5000] 73 | if not Config.UPLOAD_MODE: 74 | privacyStatus = "private" 75 | else: 76 | privacyStatus = Config.UPLOAD_MODE 77 | 78 | properties = dict( 79 | title=title, 80 | description=description, 81 | category=categoryId, 82 | privacyStatus=privacyStatus, 83 | ) 84 | 85 | log.debug(f"payload for {self.file} : {properties}") 86 | 87 | youtube = YouTube(google) 88 | r = await loop.run_in_executor( 89 | None, youtube.upload_video, self.file, properties 90 | ) 91 | 92 | log.debug(r) 93 | 94 | video_id = r["id"] 95 | self.status = True 96 | self.message = ( 97 | f"Title: {title}\n Link: https://youtu.be/{video_id}" 98 | f"\n\nCategory ID: {categoryName} | Category Code: {categoryId} |" 99 | ) 100 | except Exception as e: 101 | log.error(e, exc_info=True) 102 | self.status = False 103 | self.message = f"Error occuered during upload.\nError details: {e}" 104 | -------------------------------------------------------------------------------- /bot/youtube/youtube.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | import logging 4 | from httplib2 import HttpLib2Error 5 | from http.client import ( 6 | NotConnected, 7 | IncompleteRead, 8 | ImproperConnectionState, 9 | CannotSendRequest, 10 | CannotSendHeader, 11 | ResponseNotReady, 12 | BadStatusLine, 13 | ) 14 | 15 | from apiclient import http, errors, discovery 16 | 17 | 18 | log = logging.getLogger(__name__) 19 | 20 | 21 | class MaxRetryExceeded(Exception): 22 | pass 23 | 24 | 25 | class UploadFailed(Exception): 26 | pass 27 | 28 | 29 | class YouTube: 30 | 31 | MAX_RETRIES = 10 32 | 33 | RETRIABLE_EXCEPTIONS = ( 34 | HttpLib2Error, 35 | IOError, 36 | NotConnected, 37 | IncompleteRead, 38 | ImproperConnectionState, 39 | CannotSendRequest, 40 | CannotSendHeader, 41 | ResponseNotReady, 42 | BadStatusLine, 43 | ) 44 | 45 | RETRIABLE_STATUS_CODES = [500, 502, 503, 504] 46 | 47 | def __init__(self, auth: discovery.Resource, chunksize: int = -1): 48 | self.youtube = auth 49 | self.request = None 50 | self.chunksize = chunksize 51 | self.response = None 52 | self.error = None 53 | self.retry = 0 54 | 55 | def upload_video( 56 | self, video: str, properties: dict, progress: callable = None, *args 57 | ) -> dict: 58 | self.progress = progress 59 | self.progress_args = args 60 | self.video = video 61 | self.properties = properties 62 | 63 | body = dict( 64 | snippet=dict( 65 | title=self.properties.get("title"), 66 | description=self.properties.get("description"), 67 | categoryId=self.properties.get("category"), 68 | ), 69 | status=dict(privacyStatus=self.properties.get("privacyStatus")), 70 | ) 71 | 72 | media_body = http.MediaFileUpload( 73 | self.video, 74 | chunksize=self.chunksize, 75 | resumable=True, 76 | ) 77 | 78 | self.request = self.youtube.videos().insert( 79 | part=",".join(body.keys()), body=body, media_body=media_body 80 | ) 81 | self._resumable_upload() 82 | return self.response 83 | 84 | def _resumable_upload(self) -> dict: 85 | response = None 86 | while response is None: 87 | try: 88 | status, response = self.request.next_chunk() 89 | if response is not None: 90 | if "id" in response: 91 | self.response = response 92 | else: 93 | self.response = None 94 | raise UploadFailed( 95 | "The file upload failed with an unexpected response:{}".format( 96 | response 97 | ) 98 | ) 99 | except errors.HttpError as e: 100 | if e.resp.status in self.RETRIABLE_STATUS_CODES: 101 | self.error = "A retriable HTTP error {} occurred:\n {}".format( 102 | e.resp.status, e.content 103 | ) 104 | else: 105 | raise 106 | except self.RETRIABLE_EXCEPTIONS as e: 107 | self.error = "A retriable error occurred: {}".format(e) 108 | 109 | if self.error is not None: 110 | log.debug(self.error) 111 | self.retry += 1 112 | 113 | if self.retry > self.MAX_RETRIES: 114 | raise MaxRetryExceeded("No longer attempting to retry.") 115 | 116 | max_sleep = 2 ** self.retry 117 | sleep_seconds = random.random() * max_sleep 118 | 119 | log.debug( 120 | "Sleeping {} seconds and then retrying...".format(sleep_seconds) 121 | ) 122 | time.sleep(sleep_seconds) 123 | 124 | 125 | def print_response(response: dict) -> None: 126 | for key, value in response.items(): 127 | print(key, " : ", value, "\n\n") 128 | -------------------------------------------------------------------------------- /bot/translations.py: -------------------------------------------------------------------------------- 1 | class Messages: 2 | 3 | START_MSG = ( 4 | "**__Hi there {}.\n\nI'm Youtube Uploader Bot. Made with ❤️ by @GreyMatter_Bots. You can use me to upload any telegram video to youtube once you authorise me.__**" 5 | "\n\n**__You can know more from /help.__**" 6 | "\n**__Or use /login to get started.__**" 7 | ) 8 | 9 | HELP_MSG = [ 10 | ".", 11 | "Hi there.\n\nFirst things first. You should be aware that youtube processes each and every video uploaded, " 12 | "and its AI is amazing that it flags the video for copyrights if it finds copywrited content as soon as its " 13 | "uploaded, and you will not be able to publish the video.\n\nRead through all the pages to know how I work.", 14 | "**Lets learn how I work.**\n\n**Step 1:** __You authorise me to upload to your youtube channel. More about " 15 | "this in comming pages.__\n\n**Step 2:** __You forward any Telegram video to me.__\n\n**Step 3:** __You reply __" 16 | "__/upload __to the forwarded video file. You can also specify some title in the upload command, but its " 17 | "optional though. Title will follow the __`/upload`. __If no title is given, filename will be used as title.__" 18 | "\n\n**Step 4:** __I remotely download the file and uploads to your Youtube channel.__\n\n**Step 5:** __I " 19 | "send you the Youtube link after upload.__", 20 | "**Create your youtube channel**\n\nThere is no point in using me if you dont have a Youtube Channel. So go " 21 | "through the given steps to create one.\n\n**Step 1:** __Sign in to YouTube on a computer or using the mobile." 22 | "__\n\n**Step 2:** __Try any action that requires a channel, such as uploading a video, posting a comment, " 23 | "or creating a playlist.__\n\n**Step 3:** __If you don't yet have a channel, you'll see a prompt to create " 24 | "a channel.__\n\n**Step 4:** __Check the details and confirm to create your new channel.__", 25 | "**Verify your YouTube account**\n\nYoutube take spam and abuse very seriously. So you are asked to verify " 26 | "your Youtube account. Once you've verified your account, you will be able to upload videos longer than 15 " 27 | "minutes. If you haven't verified your account every video uploaded which are longer than 15 minutes will be " 28 | "removed.\n[Verify your Youtube account here.](http://www.youtube.com/verify)\n\n__Remember to verify your " 29 | "project, else your uploads will be kept private.__", 30 | "**Now lets authorise.**\n\nYou need to give me the access to upload videos to your Youtube account. For that " 31 | "open the given link and allow access and copy the code. Come back here and type `/authorise` `copied-code` and " 32 | "send it.\n\n**Fear not!**\nI'm not a hacker or someone who wants to creep into people's privacy. I respect " 33 | "one's privacy. I'm here just to help anyone who wants help. If I was a hacker I won't be sitting here " 34 | "writing Telegram Bots.", 35 | ] 36 | 37 | LOGIN_MSG = ( 38 | "**__You Want To Login. Great.__**" 39 | "\n\n**__You need to give me the access to upload videos to your Youtube account.\n\nFor that open the given button below and allow access and copy the code. Come back here and send your code in this formate:\n /authorise your_code (eg: 4/4waa...)__**" 40 | ) 41 | 42 | NOT_A_REPLY_MSG = "Please reply to some video file." 43 | 44 | NOT_A_MEDIA_MSG = "No media file found. " + NOT_A_REPLY_MSG 45 | 46 | NOT_A_VALID_MEDIA_MSG = "This is not a valid media" 47 | 48 | DAILY_QOUTA_REACHED = "Looks like you are trying to upload more than 6 videos today! By default youtube only allows about 6 uploads daily, so this request might fail!!" 49 | 50 | PROCESSING = "Processing....." 51 | 52 | NOT_AUTHENTICATED_MSG = "You have not authenticated me to upload video to any account. see /help to authenticate" 53 | 54 | NO_AUTH_CODE_MSG = "There is no code. Please provide some code" 55 | 56 | AUTH_SUCCESS_MSG = "Congrats, you have successfully authenticated me to upload to Youtube.\nHappy uploading!" 57 | 58 | AUTH_FAILED_MSG = "Authentication failed\nDetails:{}" 59 | 60 | AUTH_DATA_SAVE_SUCCESS = "Successfully saved the given auth data!" 61 | -------------------------------------------------------------------------------- /bot/plugins/upload.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import string 4 | import random 5 | import logging 6 | import asyncio 7 | import datetime 8 | from typing import Tuple, Union 9 | 10 | from pyrogram import StopTransmission 11 | from pyrogram import filters as Filters 12 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message 13 | 14 | from ..translations import Messages as tr 15 | from ..helpers.downloader import Downloader 16 | from ..helpers.uploader import Uploader 17 | from ..config import Config 18 | from ..utubebot import UtubeBot 19 | 20 | 21 | log = logging.getLogger(__name__) 22 | 23 | 24 | @UtubeBot.on_message( 25 | Filters.private 26 | & Filters.incoming 27 | & Filters.command("upload") 28 | & Filters.user(Config.AUTH_USERS) 29 | ) 30 | async def _upload(c: UtubeBot, m: Message): 31 | if not os.path.exists(Config.CRED_FILE): 32 | await m.reply_text(tr.NOT_AUTHENTICATED_MSG, True) 33 | return 34 | 35 | if not m.reply_to_message: 36 | await m.reply_text(tr.NOT_A_REPLY_MSG, True) 37 | return 38 | 39 | message = m.reply_to_message 40 | 41 | if not message.media: 42 | await m.reply_text(tr.NOT_A_MEDIA_MSG, True) 43 | return 44 | 45 | if not valid_media(message): 46 | await m.reply_text(tr.NOT_A_VALID_MEDIA_MSG, True) 47 | return 48 | 49 | if c.counter >= 6: 50 | await m.reply_text(tr.DAILY_QOUTA_REACHED, True) 51 | 52 | snt = await m.reply_text(tr.PROCESSING, True) 53 | c.counter += 1 54 | download_id = get_download_id(c.download_controller) 55 | c.download_controller[download_id] = True 56 | 57 | download = Downloader(m) 58 | status, file = await download.start(progress, snt, c, download_id) 59 | log.debug(status, file) 60 | c.download_controller.pop(download_id) 61 | 62 | if not status: 63 | c.counter -= 1 64 | c.counter = max(0, c.counter) 65 | await snt.edit_text(text=file, parse_mode="markdown") 66 | return 67 | 68 | try: 69 | await snt.edit_text("Downloaded to local, Now starting to upload to youtube...") 70 | except Exception as e: 71 | log.warning(e, exc_info=True) 72 | pass 73 | 74 | title = " ".join(m.command[1:]) 75 | upload = Uploader(file, title) 76 | status, link = await upload.start(progress, snt) 77 | log.debug(status, link) 78 | if not status: 79 | c.counter -= 1 80 | c.counter = max(0, c.counter) 81 | await snt.edit_text(text=link, parse_mode="markdown") 82 | 83 | 84 | def get_download_id(storage: dict) -> str: 85 | while True: 86 | download_id = "".join([random.choice(string.ascii_letters) for i in range(3)]) 87 | if download_id not in storage: 88 | break 89 | return download_id 90 | 91 | 92 | def valid_media(media: Message) -> bool: 93 | if media.video: 94 | return True 95 | elif media.video_note: 96 | return True 97 | elif media.animation: 98 | return True 99 | elif media.document and "video" in media.document.mime_type: 100 | return True 101 | else: 102 | return False 103 | 104 | 105 | def human_bytes( 106 | num: Union[int, float], split: bool = False 107 | ) -> Union[str, Tuple[int, str]]: 108 | base = 1024.0 109 | sufix_list = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] 110 | for unit in sufix_list: 111 | if abs(num) < base: 112 | if split: 113 | return round(num, 2), unit 114 | return f"{round(num, 2)} {unit}" 115 | num /= base 116 | 117 | 118 | async def progress( 119 | cur: Union[int, float], 120 | tot: Union[int, float], 121 | start_time: float, 122 | status: str, 123 | snt: Message, 124 | c: UtubeBot, 125 | download_id: str, 126 | ): 127 | if not c.download_controller.get(download_id): 128 | raise StopTransmission 129 | 130 | try: 131 | diff = int(time.time() - start_time) 132 | 133 | if (int(time.time()) % 5 == 0) or (cur == tot): 134 | await asyncio.sleep(1) 135 | speed, unit = human_bytes(cur / diff, True) 136 | curr = human_bytes(cur) 137 | tott = human_bytes(tot) 138 | eta = datetime.timedelta(seconds=int(((tot - cur) / (1024 * 1024)) / speed)) 139 | elapsed = datetime.timedelta(seconds=diff) 140 | progress = round((cur * 100) / tot, 2) 141 | text = f"{status}\n\n{progress}% done.\n{curr} of {tott}\nSpeed: {speed} {unit}PS" 142 | f"\nETA: {eta}\nElapsed: {elapsed}" 143 | await snt.edit_text( 144 | text=text, 145 | reply_markup=InlineKeyboardMarkup( 146 | [[InlineKeyboardButton("Cancel!🚫", f"cncl+{download_id}")]] 147 | ), 148 | ) 149 | 150 | except Exception as e: 151 | log.info(e) 152 | pass 153 | --------------------------------------------------------------------------------