Creating a Telegram Bot Using Python (Downloading and Sending Videos/Audio from YouTube)

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)

Creating a Telegram Bot Using Python (Downloading and Sending Videos/Audio from YouTube)

 Hello friends, I’m here with a rather lengthy post. Actually, it’s not that long, but since I want to explain some of the code in this project and the working principles, it will get extended.

In this project, we will write a bot for Telegram using Python. This bot will receive messages via Telegram, search YouTube, show the results to the user, and then send the selected video/song as a file to them.

Additionally, we will keep statistics such as the number of searches, the number of audio/video files downloaded, and the number of unique users who use the bot in a simple database system (sqlite). Finally, we will also have a very basic log system that simply serves errors to us as a ".txt" file.

Don't worry about the messiness of the code, as I will share the whole code as one unit on GitHub at the bottom of the page.

Starting the Project

In this part of the article, I will explain the scripts we created in our project and why we created them, because I don’t want to keep the post too long. In the following parts, I will not explain the code much as text, only as comments inside the code sections.

Note: Instead of creating a project by copy-pasting the code here, I recommend downloading the version I shared on Github at the end of the page. 
Due to line breaks in the article, you may get errors if you try to copy and paste, and you may have to fix these errors yourself.

Python Libraries Used in Our Project

This project is not written completely from scratch; it uses many different Python libraries. You can install these with pip or pip3. Let’s look at the libraries we used.
  • sqlite3
    • We use it to create a database and to keep statistics.
  • python-telegram-bot
    • We use it to receive and send messages with Telegram.
  • pytube
    • We use it to search and perform downloads on YouTube.
  • termcolor
    • We use it for coloring in the terminal.
These are the libraries you need to install with pip, but there are other libraries I also use. I do not list all here, but I will provide information about them as comments in the code.

Summary of Scripts Used in the Project

Our project consists of 8 scripts.
  1. Base.py
    1. The script that combines all scripts and allows us to use the scripts in a simpler and more understandable way.
  2. ColoredPrint.py
    1. This script contains functions that we created for printing colored text in the terminal.
  3. DBManager.py
    1. The script containing the functions related to database operations.
  4. LogManager.py
    1. The script we created for keeping log records.
  5. Settings.py
    1. Our script that maintains the settings.
  6. TelegramBot.py
    1. The script that enables us to communicate with Telegram.
  7. YouTubeManager.py
    1. The script where we perform operations related to YouTube.
  8. MailManager.py
    1. The script that contains functions we can use to send emails.
    2. Currently, I’m not using this script, but if I do such a project in the future, I prepared this page to automatically email statistics/errors.
Our project consists of 3 folders.
  1. Videos
    1. This is the folder where the videos are downloaded.
  2. Audios
    1. This is the folder where the audios are downloaded.
  3. Logs
    1. This is the folder where the log records are stored.
The main reason we write our code split into this many bits is for it to be more readable and organized. At the same time, we prepare each function as much as possible to return values so that, when we need to update later, instead of searching through thousands of lines, we can easily edit what we are looking for on the related page.

Note: You will see a lot of expressions like "#region Sample-Text" in the script pages, all of you know what these are for but I’ll explain anyway, we use them to catalog and see the code more properly.

Anyway, now that we have summarized our project, we can move on to our code.

Project Code (Scripts)

Settings.py

In our project, we have some standard or future-modifiable variables, such as Telegram's API key.
Instead of searching through each script to edit them, we’ve created a script called "settings.py" for easier edits, and from here we can make faster updates.

# This project created by urhoba
# www.urhoba.net

#region DB Settings
class DBSettings: # We created a class for database settings.
    sqliteDB = "db.urhoba" # inside this class we define the name of our sqlite database file.
#endregion

#region Mail Settings 
class MailSettings: # We created a class for sending mail.
    fromMail = "ahmetbohur@urhoba.net" # the username of our SMTP mail.
    fromPassword = "" # the password of our SMTP mail.
    smtpHost = "smtp.yandex.com" # the provider/server for our SMTP mail.
    smtpPort = 465 # the port address of our SMTP mail.
#endregion

#region Download Settings
class DownloadSettings: # Settings for downloaded files.
    musicFolder = "songs" # The folder name for downloaded audio files.
    videoFolder = "videos" # The folder name for downloaded video files.
#endregion

#region Telegram Settings
class TelegramSettings: # Our class for Telegram settings.
    telegramAPI = "" # We write our Telegram API address here.
#endregion


ColoredPrint.py

Our ColoredPrint script is the script we created to make coloring text easier while printing to the terminal.

from termcolor import colored # we import colored from the termcolor library.

#region Green Print
def GreenPrint(text): # We create a function to print in green.
    print(colored(text, 'green')) # We print the incoming text in green.
#endregion

#region Red Print
def RedPrint(text): # We create a function to print in red.
    print(colored(text, 'red')) # We print the incoming text in red.
#endregion

LogManager.py

This is our script that we created to keep error records of code or points where we get errors. 
The error we receive is kept in the "logs" folder as a .txt file.
The error file for each class is different, and we specify this in the class by accessing this script.
# This project created by urhoba
# www.urhoba.net

from datetime import date, datetime # Library we use to get the time information of when the error occurred.
import locale # Library for outputting time information in system language.
import os # Library for file operations.
import ColoredPrint # Library for also printing error to terminal.

#region Locale Settings 
locale.setlocale(locale.LC_ALL, '') # We set the time info to be in system language.
#endregion

class LogManager: # We create a class called LogManager
#region Init    
    def __init__(self, logFileName) -> None: # When calling this class, we want the file name to create an error file.
        self._logFileName = logFileName # we write the file name
#endregion

#region Add Log
    def AddLog(self, errorText): # We create a function called AddLog(errorMessage).
        _nowTime = datetime.now() # We get the date.
        _nowTime = datetime.strftime(_nowTime, "%c") # Make the date a string.
        logFile = open(os.getcwd()+"/logs/"+self._logFileName + ".txt", "a") # We create the log file.
        try:
            with logFile as f: # We open the log file.
                f.write(f"\nDate : {_nowTime}\nError: {errorText}\n") # We write the new error at the bottom of the log file.
            ColoredPrint.RedPrint(f'Date : {_nowTime}\nError: {errorText}\n') # We print the error to the screen.
        except:
            ColoredPrint.RedPrint("An issue occurred while creating the Log!") # If an error occurs during log file creation, we print to screen.
        finally:
            logFile.close() # We close the log file.
#endregion


YouTubeManager.py

This is the main script where we do operations related to YouTube. Here, we write the features such as searching on YouTube, downloading audio and video. 
In addition, there are also a few system functions. 
These are functions I made for trial purposes: deleting downloaded files or preventing the same data from being downloaded again if reused in an actual project.
# This project created by urhoba
# www.urhoba.net

from pytube import YouTube # Library for downloading video/audio from YouTube via PyTube.
from pytube import Search # Library for searching on YouTube via PyTube.
import ssl # SSL library, we use this on MAC devices to fix PyTube SSL errors.

import os # Library for file operations.
import os.path # Specifically the library I use to check if a file exists.
import re # Library for detecting REGEX (I haven’t used it here yet but I may at any time. If unused in your project, remember to remove it for output!)

from LogManager import LogManager # We import our LogManager script.
from Settings import DownloadSettings # We import the DownloadSettings class from our Settings script.
import ColoredPrint # We import our class for printing colored text to the screen.

#region SSL Settings
ssl._create_default_https_context = ssl._create_stdlib_context # We fix the MAC device SSL error here.
#endregion

class YouTubeManager:
#region Init    
    def __init__(self) -> None:
        self.logManager = LogManager("YouTubeManagerLog") # We create logManager to create log record by using our LogManager class.
#endregion

#region Search Modules    
    def SearchVideo(self, searchQuery): # We created a function to perform searches and asked for the searchQuery value.
        try:
            search = Search(searchQuery) # We search on YouTube and if there is no problem, assign the result to search.
            return search.results # We return the results with search.result.
        except:
            self.logManager.AddLog(f"An issue occurred while searching for video! \nSearch query : {searchQuery}") # If there is an error, we add to the log with logManager
            ColoredPrint.RedPrint(f"An issue occurred while searching for video! \nSearch query : {searchQuery}") # Uselessly, we also print to the screen. (I call it useless because logManager already handles this.)

#endregion

#region File Modules
    def FileCheck(self, file): # We create a function to check if file exists, we send the file path here.
        if os.path.isfile(file) and os.access(file, os.R_OK): # We check if file exists.
            return True # If file exists
        else: # if not
            return False # returns False.

    def DeleteFile(self, file): # We create a delete function so that files do not use up space on the server.
        os.remove(file) # We delete the file.

    def FileNameFormatter(self, name): # Some operating systems do not support certain characters (e.g. /\) so we use this function for formatting.
        validFileName = "".join([c for c in name if c.isalpha() or c.isdigit() or c==' ']).rstrip() # We remove unsupported characters.
        return validFileName # Return formatted name.
#endregion

#region Download Modules
    def DownloadVideo(self, videoURL): # We create a function to download video. Important point: we send the video hash value to this function. For example: watch?v=YXwSDEG3pTQ, the YXwSDEG3pTQ part in the url is what you use for YouTube videos.
        try:
            youtube = YouTube(f"https://www.youtube.com/watch?v={videoURL}") # We fetch video information.
            videoTitle = youtube.title # We get the video title.
            fileName = self.FileNameFormatter(videoTitle) + ".mp4" # We check the video name and add it to our variable.
            if self.FileCheck(DownloadSettings.videoFolder+"/"+fileName) == False: # We check if file exists.
                youtube.streams.get_highest_resolution().download(output_path=DownloadSettings.videoFolder, filename=fileName) # If not, we download.
            return videoTitle, videoURL, DownloadSettings.videoFolder + "/" + fileName # We send the video title, url and file path.
        except:
            self.logManager.AddLog(f"An issue occurred while downloading video! \nVideo link : {videoURL}") # On error, we log.
            ColoredPrint.RedPrint(f"An issue occurred while downloading video! \nVideo link : {videoURL}") # We print to screen.
            return False # If error, return False.

    def DownloadAudio(self, songURL): # Function to download audio.
        try:
            youtube = YouTube(f"https://www.youtube.com/watch?v={songURL}") # We get the video info.
            songTitle = youtube.title # We get video title.
            fileName = self.FileNameFormatter(songTitle) + ".mp4" # We check the video title.
            if self.FileCheck(DownloadSettings.musicFolder+"/"+fileName) == False: # We check if file exists.
                youtube.streams.get_audio_only().download(output_path=DownloadSettings.musicFolder, filename=fileName) # If there isn't one, we download.
            return songTitle, songURL,DownloadSettings.musicFolder + "/" + fileName # Return values like in video function.
        except:
            self.logManager.AddLog(f"An issue occurred while downloading audio! \nAudio link : {songURL}") # On error, we log.
            ColoredPrint.RedPrint(f"An issue occurred while downloading audio! \nAudio link : {songURL}") # We print to screen.
            return False # If there is an error, return False.
#endregion

DBManager.py

This is the script we created to handle database operations. With this script, we can store and process data such as the number of downloads or the number of users easily.
# This project created by urhoba
# www.urhoba.net

import sqlite3 # We use SQLite3 as the database, so we import the library.
from sqlite3.dbapi2 import Cursor # This is an unused library (automatically created). Remove if not used.
from LogManager import LogManager # We import our logManager script for errors.
from Settings import DBSettings # We import the DBSettings class from our settings script.
import ColoredPrint # We import our class for printing colored text.

class DBManager:
#region Init    
    def __init__(self) -> None: # When DBManager is called.
        self._CreateVideoDB() # Call the function to create the video db.
        self._CreateAudioDB() # Call the function for audio db.
        self._CreateUserDB() # Call the function for user db.
        self._CreateBotStatsDB() # Call the function for bot stats.
        self.logManager = LogManager("DBManagerLog") # Create logManager for logging.
#endregion

#region Connect
    def _Connect(self): # Function to connect to the db. 
        self.db = sqlite3.connect(DBSettings.sqliteDB) # Create db variable and connect.
        ColoredPrint.GreenPrint("Connected to database.") # Indicate connection.
#endregion

#region Create DB Modules # DB create functions.
    def _CreateVideoDB(self): # Function to create video db.
        try:
            self._Connect() # Connect to db.
            cursor = self.db.cursor() # Create cursor.
            sqlQuery = "CREATE TABLE IF NOT EXISTS videos (videoTitle, videoID, videoFile, videoDownloadCount)" # SQL query
            cursor.execute(sqlQuery) # Create db.
            ColoredPrint.GreenPrint("Video db created.") # Indicate creation. (If db exists, doesn't recreate but prints created.)
        except:
            self.logManager.AddLog("An error occurred while creating video db!") # If error, log.
            ColoredPrint.RedPrint("An error occurred while creating video db!") # Print to screen.
        finally:
            self.db.close() # Close db connection.
# All db create functions above repeat same logic, so not explained again below.
    def _CreateAudioDB(self):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "CREATE TABLE IF NOT EXISTS audios (audioTitle, audioID, audioFile, audioDownloadCount)"
            cursor.execute(sqlQuery)
            ColoredPrint.GreenPrint("Audio db created.")
        except:
            self.logManager.AddLog("An error occurred while creating audio db!")
            ColoredPrint.RedPrint("An error occurred while creating audio db!")
        finally:
            self.db.close()   

    def _CreateUserDB(self):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "CREATE TABLE IF NOT EXISTS users (userID, userName, userVideoDownloadCount, userAudioDownloadCount, userSearchCount)"
            cursor.execute(sqlQuery)
            ColoredPrint.GreenPrint("User db created.")
        except:
            self.logManager.AddLog("An error occurred while creating user db!")
            ColoredPrint.RedPrint("An error occurred while creating user db!")
        finally:
            self.db.close()

    def _CreateBotStatsDB(self):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "CREATE TABLE IF NOT EXISTS stats (botID, audioDownloadCount, videoDownloadCount, searchCount, userCount)"
            cursor.execute(sqlQuery)
            ColoredPrint.GreenPrint("Stats db created.")
        except:
            self.logManager.AddLog("An error occurred while creating stats db!")
            ColoredPrint.RedPrint("An error occurred while creating stats db!")
        finally:
            self.db.close()        
         
#endregion

#region Add Modules # Functions for adding data to created dbs.
    def AddVideo(self, videoTitle, videoID, videoFile): # Function to add video
        try:
            self._Connect() # Connect to db.
            cursor = self.db.cursor() # Create cursor.
            sqlQuery = "INSERT INTO videos (videoTitle, videoID, videoFile, videoDownloadCount) VALUES (?,?,?,?)" # SQL query
            sqlValues = (videoTitle, videoID, videoFile, 1) # Get values.
            cursor.execute(sqlQuery, sqlValues) # Add data.
            self.db.commit() # Commit.
            ColoredPrint.GreenPrint(f"Video added. \nVideo Title : {videoTitle} \nVideo ID : {videoID}") # Print to screen.
        except: # If error, log and print screen.
            self.logManager.AddLog(f"An error occurred while adding video to db! \nVideo Title : {videoTitle} \nVideo ID : {videoID}\nVideo File : {videoFile}")
            ColoredPrint.RedPrint(f"An error occurred while adding video to db! \nVideo Title : {videoTitle} \nVideo ID : {videoID}\nVideo File : {videoFile}")
        finally:
            self.db.close() # Close db connection.
# All Add Modules repeat same logic as above, so not explained individually.
    def AddAudio(self, audioTitle, audioID, audioFile):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "INSERT INTO audios (audioTitle, audioID, audioFile, audioDownloadCount) VALUES (?,?,?,?)"
            sqlValues = (audioTitle, audioID, audioFile, 1)
            cursor.execute(sqlQuery, sqlValues)
            self.db.commit()
            ColoredPrint.GreenPrint(f"Audio added. \nAudio Title : {audioTitle} \nAudio ID : {audioID}")
        except:
            self.logManager.AddLog(f"An error occurred while adding audio to db! \nAudio Title : {audioTitle} \nAudio ID : {audioID}\nAudio File : {audioFile}")
            ColoredPrint.RedPrint(f"An error occurred while adding audio to db! \nAudio Title : {audioTitle} \nAudio ID : {audioID}\nAudio File : {audioFile}")
        finally:
            self.db.close()

    def AddUser(self, userID, userName):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "INSERT INTO users (userID, userName, userVideoDownloadCount, userAudioDownloadCount, userSearchCount) VALUES (?,?,?,?,?)"
            sqlValues = (userID, userName, 0, 0, 0)
            cursor.execute(sqlQuery, sqlValues)
            self.db.commit()
            ColoredPrint.GreenPrint(f"User added. \nUsername : {userName}\nUser ID : {userID}")
        except:
            self.logManager.AddLog(f"An error occurred while adding user to db! \nUser ID : {userID}\nUsername : {userName}")
            ColoredPrint.RedPrint(f"An error occurred while adding user to db! \nUser ID : {userID}\nUsername : {userName}")
        finally:
            self.db.close()

    def AddBot(self, botID = 1):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "INSERT INTO stats (botID, audioDownloadCount, videoDownloadCount, searchCount, userCount) VALUES (?,?,?,?,?)"
            sqlValues = (botID, 0, 0, 0, 0)
            cursor.execute(sqlQuery, sqlValues)
            self.db.commit()
            ColoredPrint.GreenPrint(f"Bot added. \nBot ID : {botID}")
        except:
            self.logManager.AddLog(f"An error occurred while adding bot to db! \nBot ID : {botID}")
            ColoredPrint.RedPrint(f"An error occurred while adding bot to db! \nBot ID : {botID}")
        finally:
            self.db.close()        
#endregion
  
#region Check Modules # Functions to check if data exists.
    def CheckVideoWithVideoID(self, videoID): # Function to check if video exists in db by video ID.
        try:
            self._Connect() # Connect to db.
            cursor = self.db.cursor() # Create cursor.
            sqlQuery = "SELECT * FROM videos WHERE videoID = '%s'" % videoID # SQL by videoID
            check = cursor.execute(sqlQuery).fetchall() # Search.
            if len(check) > 0: # If check > 0, video exists.
                return True # Return True.
            else: # If check == 0
                return False # Return False.
        except: # Catch errors and return False.
            self.logManager.AddLog(f"Issue occurred while checking video in db! \nvideo ID : {videoID}")
            ColoredPrint.RedPrint(f"Issue occurred while checking video in db! \nvideo ID : {videoID}")
            return False
        finally:
            self.db.close() # Close db connection.
# All Check Modules repeat same logic, so not explained further.
    def CheckAudioWithAudioID(self, audioID):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "SELECT * FROM audios WHERE audioID = '%s'" % audioID
            check = cursor.execute(sqlQuery).fetchall()
            if len(check) > 0:
                return True
            else:
                return False
        except:
            self.logManager.AddLog(f"Issue occurred while checking audio in db! \nAudio ID : {audioID}")
            ColoredPrint.RedPrint(f"Issue occurred while checking audio in db! \nAudio ID : {audioID}")
            return False
        finally:
            self.db.close()
   
    def CheckUserWithID(self, userID):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "SELECT * FROM users WHERE userID = %s" % userID
            check = cursor.execute(sqlQuery).fetchall()
            if len(check) > 0:
                return True
            else:
                return False
        except:
            self.logManager.AddLog(f"Issue occurred while checking user in db! \nUser ID : {userID}")
            ColoredPrint.RedPrint(f"Issue occurred while checking user in db! \nUser ID : {userID}")
            return False
        finally:
            self.db.close()

    def CheckBotStatsWithID(self, botID = 1):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "SELECT * FROM stats WHERE botID = %s" % botID
            check = cursor.execute(sqlQuery).fetchall()
            if len(check) > 0:
                return True
            else:
                return False
        except:
            self.logManager.AddLog(f"Issue occurred while checking bot in db! \nBot ID : {botID}")
            ColoredPrint.RedPrint(f"Issue occurred while checking bot in db! \nBot ID : {botID}")
            return False
        finally:
            self.db.close()        
#endregion

#region Download - Search Count Update Modules # Functions to update data in the db.
    def AudioDownloadCountUpdate(self, audioID): # Function to update audio download count by audioID.
        try: 
            self._Connect() # Connect to db. 
            cursor = self.db.cursor() # Create cursor.
            sqlQuery = " UPDATE audios SET audioDownloadCount = audioDownloadCount + 1 WHERE audioID = '%s'" % audioID # SQL query
            cursor.execute(sqlQuery) # Add data.
            self.db.commit() # Save.
            return True # Indicate saved.
        except:
            self.logManager.AddLog(f"Issue occurred while increasing audio download count in db! \nAudio ID : {audioID}")
            ColoredPrint.RedPrint(f"Issue occurred while increasing audio download count in db! \nAudio ID : {audioID}")
            return False # If error, return False.
        finally:
            self.db.close() # Close connection.
    
    def VideoDownloadCountUpdate(self, videoID):
        try: 
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = " UPDATE videos SET videoDownloadCount = videoDownloadCount + 1 WHERE videoID = '%s'" % videoID
            cursor.execute(sqlQuery)
            self.db.commit()
            return True
        except:
            self.logManager.AddLog(f"Issue occurred while increasing video download count in db! \nVideo ID : {videoID}")
            ColoredPrint.RedPrint(f"Issue occurred while increasing video download count in db! \nVideo ID : {videoID}")
            return False
        finally:
            self.db.close()
    
    def UserVideoDownloadCountUpdate(self, userID):
        try: 
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = " UPDATE users SET userVideoDownloadCount = userVideoDownloadCount + 1 WHERE userID = %s" % userID
            cursor.execute(sqlQuery)
            self.db.commit()
            return True
        except:
            self.logManager.AddLog(f"Issue occurred while increasing user video download count in db! \nUser ID : {userID}")
            ColoredPrint.RedPrint(f"Issue occurred while increasing user video download count in db! \nUser ID : {userID}")
            return False
        finally:
            self.db.close()

    def UserAudioDownloadCountUpdate(self, userID):
        try: 
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = " UPDATE users SET userAudioDownloadCount = userAudioDownloadCount + 1 WHERE userID = %s" % userID
            cursor.execute(sqlQuery)
            self.db.commit()
            return True
        except:
            self.logManager.AddLog(f"Issue occurred while increasing user audio download count in db! \nUser ID : {userID}")
            ColoredPrint.RedPrint(f"Issue occurred while increasing user audio download count in db! \nUser ID : {userID}")
            return False
        finally:
            self.db.close()

    def UserSearchCountUpdate(self, userID):
        try: 
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = " UPDATE users SET userSearchCount = userSearchCount + 1 WHERE userID = %s" % userID
            cursor.execute(sqlQuery)
            self.db.commit()
            return True
        except:
            self.logManager.AddLog(f"Issue occurred while increasing user search count in db! \nUser ID : {userID}")
            ColoredPrint.RedPrint(f"Issue occurred while increasing user search count in db! \nUser ID : {userID}")
            return False
        finally:
            self.db.close()

    def BotAudioCountUpdate(self, botID = 1):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "UPDATE stats SET audioDownloadCount = audioDownloadCount + 1 WHERE botID = %s" % botID
            cursor.execute(sqlQuery)
            self.db.commit()
        except:
            self.logManager.AddLog(f"Issue occurred while increasing audio count in db! \nBot ID : {botID} ")
            ColoredPrint.RedPrint(f"Issue occurred while increasing audio count in db! \nBot ID : {botID} ")
        finally:
            self.db.close()
            
    def BotVideoCountUpdate(self, botID = 1):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "UPDATE stats SET videoDownloadCount = videoDownloadCount + 1 WHERE botID = %s" % botID
            cursor.execute(sqlQuery)
            self.db.commit()
        except:
            self.logManager.AddLog(f"Issue occurred while increasing video count in db! \nBot ID : {botID} ")
            ColoredPrint.RedPrint(f"Issue occurred while increasing video count in db! \nBot ID : {botID} ")
        finally:
            self.db.close()

    def BotSearchCountUpdate(self, botID = 1):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "UPDATE stats SET searchCount = searchCount + 1 WHERE botID = %s" % botID
            cursor.execute(sqlQuery)
            self.db.commit()
        except:
            self.logManager.AddLog(f"Issue occurred while increasing search count in db! \nBot ID : {botID} ")
            ColoredPrint.RedPrint(f"Issue occurred while increasing search count in db! \nBot ID : {botID} ")
        finally:
            self.db.close()

    def BotUserCountUpdate(self, botID = 1):
        try:
            self._Connect()
            cursor = self.db.cursor()
            sqlQuery = "UPDATE stats SET userCount = userCount + 1 WHERE botID = %s" % botID
            cursor.execute(sqlQuery)
            self.db.commit()
        except:
            self.logManager.AddLog(f"Issue occurred while increasing user count in db! \nBot ID : {botID} ")
            ColoredPrint.RedPrint(f"Issue occurred while increasing user count in db! \nBot ID : {botID} ")
        finally:
            self.db.close()

#endregion

#region Get Data Modules
    def BotDataGet(self, botID = 1): # Function to get bot stats
        try:
            self._Connect() # Connect to db.
            cursor = self.db.cursor() # Create cursor.
            sqlQuery = "SELECT * FROM stats WHERE botID = %s" %botID # Fetch data from db by botID
            datas = cursor.execute(sqlQuery).fetchone() # Search.
            return datas # Return data.
        except:
            self.logManager.AddLog(f"An issue occurred while fetching bot data from db! \nBot ID : {botID}")
            ColoredPrint.RedPrint(f"An issue occurred while fetching bot data from db! \nBot ID : {botID}")
        finally:
            self.db.close() # Close db connection.
#endregion


Base.py

Base.py is our script for combining other scripts and using the separately created classes together.
# This project created by urhoba
# www.urhoba.net

from telegram import user
from YouTubeManager import YouTubeManager # We import the script we created for YouTube.
from DBManager import DBManager # We import the DBManager script.

class UrhobA:
#region Init    
    def __init__(self) -> None:
        self.yt = YouTubeManager() # We create our YouTubeManager class.
        self.dbMan = DBManager() # We create our DBManager class.
        if self.dbMan.CheckBotStatsWithID() == False: # We check the bot's state.
            self.dbMan.AddBot() # If there's no bot, we create a new bot (This is done only once for the first user; it's never used again.)
#endregion

#region User Modules
    def CreateUser(self, userID, userName): # Function to create user record with user info.
        if self.dbMan.CheckUserWithID(userID) == False: # We check if user exists.
            self.dbMan.AddUser(userID, userName) # If not, we create a new user.
            self.dbMan.BotUserCountUpdate() # We increase the user count in bot stats in db.
#endregion

#region File Modules
    def DeleteFile(self, fileFolder): # Function to delete file.
        self.yt.DeleteFile(fileFolder) # We call the delete function from YouTube script.
#endregion

#region Counter Update Modules
    def SearchCountUpdateUser(self, userID, userName): # Function to increase user's search count
        if self.dbMan.CheckUserWithID(userID) == True: # We check the user.
            self.dbMan.UserSearchCountUpdate(userID) # If user exists, we increase search count.
            self.dbMan.BotSearchCountUpdate() # We also increase for bot.
        else:
            self.CreateUser(userID, userName) # If user does not exist, create one (You can later call this again to add data or else it is skipped).
def VideoDownloadCountUpdateUser(self, userID, userName): # Function to increase video download count. if self.dbMan.CheckUserWithID(userID) == True: # Check if user exists. self.dbMan.UserVideoDownloadCountUpdate(userID) # If found, increase count. self.dbMan.BotVideoCountUpdate() # Increase for bot too. else: self.CreateUser(userID, userName) # If not, create user (same as above). def AudioDownloadCountUpdateUser(self, userID, userName): # Function to increase audio download count. if self.dbMan.CheckUserWithID(userID) == True: # Check if user exists. self.dbMan.UserAudioDownloadCountUpdate(userID) # If found, increase count. self.dbMan.BotAudioCountUpdate() # Increase for bot too. else: self.CreateUser(userID, userName) # If not, create user (same as above). #endregion #region Search Modules def SearchVideo(self, searchQuery): # YouTube search function. result = self.yt.SearchVideo(searchQuery) # We do the search. return result # Return results. #endregion #region Download Modules def DownloadVideo(self, video_id): # Video download function. result = self.yt.DownloadVideo(video_id) # download video. if result == False: # Check if video downloaded. return False # If not, return False. else: # If downloaded if self.dbMan.CheckVideoWithVideoID(video_id): # Check if video exists. self.dbMan.VideoDownloadCountUpdate(video_id) # If so, increase count. else: # If not self.dbMan.AddVideo(result[0], result[1], result[2]) # Add video. return result[2] # Return file path. # All steps for video download are repeated for audio file. def DownloadAudio(self, video_id): result = self.yt.DownloadAudio(video_id) if result == False: return False else: if self.dbMan.CheckAudioWithAudioID(video_id): self.dbMan.AudioDownloadCountUpdate(video_id) else: self.dbMan.AddAudio(result[0], result[1], result[2]) return result[2] #endregion #region Get Stats Modules def GetBotDatas(self, botID = 1): # Function for fetching bot stats. if self.dbMan.CheckBotStatsWithID() == True: # Check if bot exists. datas = self.dbMan.BotDataGet() # If so, fetch data. return datas # Return data. #endregion

Telegram.py

Telegram.py is the script that enables communication between the user and the bot via Telegram, and also makes the bot work with functions from Base.py.
# This project created by urhoba
# www.urhoba.net

from logging import info # Log library.
from telegram import * # Telegram bot library.
from telegram.ext import * # Telegram bot extension library.

from LogManager import LogManager # LogManager script.
from Settings import TelegramSettings # Settings script.
from Base import UrhobA # Base script.

import ColoredPrint # Script for colored terminal output

#region Start Command
def StartCommand(update, context): # Function for /start command.
    text = f'''
Hello, {update.message.from_user.first_name}!
I’ll help you find the video or song you are searching for on YouTube and send it to you as a file.
Just enter the name of the video or song you want to search for. 😉 

⚠️ To learn how to use it
You can use the
/help command.
    '''
    urhoba = UrhobA() # Access UrhobA class from base script.
    urhoba.CreateUser(update.message.from_user.id, update.message.from_user.username) # Create user.
    context.bot.send_message(chat_id=update.message.chat_id, text=text, parse_mode=ParseMode.HTML) # Send message.
#endregion

#region Help 
def HelpCommand(update, context): # /help function.
    text = f'''
Hello, {update.message.from_user.first_name}.
I see you need help, let’s help you out.

Just enter the name of the video or song you want to find.

Among the results:
🎬 Button lets you download the video. 
🎧 Button lets you download audio.

🔗 The button takes you to our website.
    '''
    context.bot.send_message(chat_id=update.message.chat_id, text=text, parse_mode=ParseMode.HTML) # Send message.

#endregion

#region UrhobA
def UrhobACommand(update, context): # Function for /urhoba command.
    text = f"""
Hello, {update.message.from_user.first_name}.
You can go to our website by clicking the link below.

🔗 UrhobA
    """
    context.bot.send_message(chat_id=update.message.chat_id, text=text, parse_mode=ParseMode.HTML)
#endregion

#region Stats Command # Function for /stats command.
def StatsCommand(update, context):
    urhoba = UrhobA() # Create UrhobA class.
    statDatas = urhoba.GetBotDatas() # Fetch bot data
    text = f'''
📊 Statistics 📊
🔍 Searches performed : {statDatas[3]}
🎬 Video downloads : {statDatas[2]}
🎧 Audio downloads : {statDatas[1]}
😁 Number of users : {statDatas[4]}

🔗 UrhobA     
    '''
    context.bot.send_message(chat_id=update.message.chat_id, text=text, parse_mode=ParseMode.HTML) # Send message.

#endregion

#region Search Modules
def SearchCommand(update, context): # In Telegram groups, since direct messaging for search is not possible, this function works with /search command.
    searchText = update.message.text.split("/search")[1].strip() # Removes "/search" part. For example: "/search Ezhel - Mayrig" returns "Ezhel - Mayrig".
    Search(update, context, searchText) # Call the search function below.

def Search(update, context, query = None): # Function when user types anything to the bot.
    text = "Finding it now. 🔍🔍"  # Our search in progress message.
    context.bot.send_message(chat_id=update.message.chat_id, text=text, parse_mode=ParseMode.HTML) # Send message.
    urhoba = UrhobA() # Create Urhoba class
    if query == None: # Check if coming from SearchCommand.
        searchText = update.message.text # Take the plain text.
    else: # If from SearchCommand.
        searchText = query # Take value passed.

    searchResult = urhoba.SearchVideo(searchText) # Search via Base.py.
    try:
        if len(searchResult) > 0: # If results more than 0
            buttons = [] # Create button array.
            for video in searchResult: # Iterate over search results and create buttons.
                buttonOne = [InlineKeyboardButton(text=f'{video.title}', callback_data='none')]
                buttonTwo = [InlineKeyboardButton(text=f'🎬', callback_data=f'videourhoba{video.video_id}'),
                    InlineKeyboardButton(text=f'🔗', url="https://www.urhoba.net"), 
                        InlineKeyboardButton(text=f'🎧', callback_data=f'audiourhoba{video.video_id}')]
                buttons.append(buttonOne) # Add buttonOne to array.
                buttons.append(buttonTwo) # Add buttonTwo to array.
            replyMarkup = InlineKeyboardMarkup(buttons) # Assign all buttons.
            context.bot.send_message(chat_id=update.message.chat_id, text=f"Search result:\n{searchText}",
                                    parse_mode=ParseMode.HTML, reply_markup=replyMarkup) # Send results + buttons.
        else: # If results not more than 0
            text = "I couldn't find what you’re looking for. 😢" # Indicate that nothing was found.
            context.bot.send_message(chat_id=update.message.chat_id, text=text, parse_mode=ParseMode.HTML) # Send message.
        urhoba.SearchCountUpdateUser(update.message.from_user.id, update.message.from_user.username) # Increase search count.
    except:
        text = "An issue occurred while searching for your request! 😢\nWe’ll look into this soon." # User message in case of error.
        context.bot.send_message(chat_id=update.message.chat_id, text=text, parse_mode=ParseMode.HTML) # Send message.
        logManager = LogManager("TelegramLog") # Log error.
        logManager.AddLog(f'Telegram (YouTube) search issue!\n Search Text : {searchText} \n Search result : {searchResult}')
#endregion

#region Button Call Back Modules (Download - Search Result)
def ButtonCallBack(update: Update, context: CallbackQueryHandler): # Function that detects user clicking one of the above buttons.
    query = update.callback_query # Get value.
    query.answer() # Process.
    queryData = query.data.split("urhoba") # Split action.
    urhoba = UrhobA() # Call Urhoba class.
    text = 'Don’t forget to visit our site. 🥰 \n🔗 UrhobA' # Prepare message.
    if queryData[0] == "video": # If video
        query.edit_message_text(text=f"⏳ Video file is being prepared.\n⚠️ Sending videos can take longer than audios, please be patient.", parse_mode=ParseMode.HTML) # Send info message.
        urhoba.VideoDownloadCountUpdateUser(update.callback_query.from_user.id, update.callback_query.from_user.username) # Increase download count.
        videoFolder = urhoba.DownloadVideo(queryData[1]) # Get video file.
        if videoFolder == False: # If video file empty.
            query.message.reply_text(text='An issue occurred while preparing video file! 😢\nWe’ll look into this soon.', parse_mode=ParseMode.HTML) # State video could not be sent.
        else:
            try:
                query.message.reply_video(video=open(videoFolder, 'rb'), supports_streaming=True, timeout=10000) # Try sending video.
                query.message.reply_text(text=text,parse_mode=ParseMode.HTML) # Send text after video.
            except:
                query.message.reply_text(text='An issue occurred while sending video file! 😢\nWe’ll look into this soon.', parse_mode=ParseMode.HTML)
            finally:
                urhoba.DeleteFile(videoFolder) # Since I’m using it for demo, I delete the video from the server.
    elif queryData[0] == "audio": # If audio, repeat as above for videos.
        query.edit_message_text(text=f"⏳ Audio file is being prepared.", parse_mode=ParseMode.HTML)
        urhoba.AudioDownloadCountUpdateUser(update.callback_query.from_user.id, update.callback_query.from_user.username)
        audioFolder = urhoba.DownloadAudio(queryData[1])
        if audioFolder == False:
            query.message.reply_text(text='An issue occurred while preparing audio file! 😢\nWe’ll look into this soon.', parse_mode=ParseMode.HTML)
        else:
            try:
                query.message.reply_audio(audio=open(audioFolder, 'rb'))
                query.message.reply_text(text=text,parse_mode=ParseMode.HTML)
            except:
                query.message.reply_text(text='An issue occurred while sending audio file! 😢\nWe’ll look into this soon.', parse_mode=ParseMode.HTML)
            finally:
                urhoba.DeleteFile(audioFolder)
    else: # If song title button clicked, send warning.
        text = "⚠️ Please only use the 🎬 or 🎧 buttons."
        query.message.reply_text(text=text,parse_mode=ParseMode.HTML)


#endregion

#region Legal Info
def LegalInfoCommand(update, context): # Function for /legal command.
    returnedMessage = """
We built UrhobABot with the idea that a legal stream recording tool for the internet that was clean, easy, and not spammy needed to exist. 
According to the EFF.org "The law is clear that simply providing the public with a tool for copying digital media does not give rise to copyright liability".    
    """
    context.bot.send_message(chat_id=update.message.chat_id, text=returnedMessage, parse_mode=ParseMode.HTML)
#endregion

#region Error Handler # Error catching function
def ErrorExcept(update, context):
    logManager = LogManager("TelegramLog")
    logManager.AddLog(f"Telegram bot error : Update {update} caused error {context.error}")
#endregion

#region Main
def main():
    updater = Updater(TelegramSettings.telegramAPI, use_context=True) # Create Telegram bot.
    dp = updater.dispatcher # Create handler.

	# Catch commands.
    dp.add_handler(CommandHandler("start", StartCommand))
    dp.add_handler(CommandHandler("legal", LegalInfoCommand))
    dp.add_handler(CommandHandler("help", HelpCommand))
    dp.add_handler(CommandHandler("stats", StatsCommand))
    dp.add_handler(CommandHandler("urhoba", UrhobACommand))

	# Catch /search inputs.
    dp.add_handler(CommandHandler("search", SearchCommand))
	
    # Catch text messages.
    dp.add_handler(MessageHandler(Filters.text & ~Filters.command, Search))

	# Catch button clicks.
    dp.add_handler(CallbackQueryHandler(ButtonCallBack))

    dp.add_error_handler(ErrorExcept) # Catch errors.
    updater.start_polling(1) # Start bot.
    updater.idle() # Set bot to listen mode.
#endregion

ColoredPrint.GreenPrint("Bot started!") # Indicate bot started.
main() # Run the bot.


MailManager.py

This is a script I wrote for the future in case I want to email log files to myself, but is currently inactive.
Allows you to send mail over SMTP.
# This project created by urhoba
# www.urhoba.net

from Settings import MailSettings # We import our script with mail settings.
from LogManager import LogManager # We import logManager for error handling.

import smtplib # The library for sending emails.
from email.mime.text import MIMEText # Library for altering MIME text type.
from email.mime.multipart import MIMEMultipart # For using multiple MIME types together.

import ColoredPrint # We import the script for colored text to terminal.

class MailManager:
#region Init    
    def __init__(self) -> None:
    	# Assign mail settings to variables.
        self.fromMail = MailSettings.fromMail
        self.fromPassword = MailSettings.fromPassword
        self.smtpHost = MailSettings.smtpHost
        self.smtpPort = MailSettings.smtpPort
        self.logManager = LogManager("MailManager")
#endregion

#region Send Mail
    def SendMail(self, subject, content, toMail): # Our function to send mail, can be used outside this project as SendMail("Subject", "Content", "receiver@domain.com")
        try:
            message = MIMEMultipart("alternative") # Set MIME type.
            message["Subject"] = subject # Specify subject.
            message["From"] = self.fromMail # Specify sender.
            message["To"] = toMail # Specify receiver.

            _content = MIMEText(content.encode('utf-8'), _charset='utf-8') # Set content and charset.
            message.attach(_content) # Attach content.

            with smtplib.SMTP_SSL(self.smtpHost, self.smtpPort) as server: # Connect to server to send.
                server.login(self.fromMail, self.fromPassword) # Login.
                server.sendmail(self.fromMail, toMail, message.as_string()) # Send message.
        except Exception as e: # Exception handling.
            self.logManager.AddLog(f"Mail could not be sent!\nError Code: {e}\nMail subject : {subject} \nMail content : {content} \nReceiver : {toMail}")
            ColoredPrint.RedPrint(f"Mail could not be sent!\nError Code: {e}\nMail subject : {subject} \nMail content : {content} \nReceiver : {toMail}")

#endregion

Github Repos

Below I leave 2 Github repos. One, urhoba-blog, contains the exact same code as here and will not be updated unless the page is updated.
The other is dedicated to this bot, with fixes for bugs and new features added.


Screenshots from Our Bot

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)

Python Kullanarak Telegram Botu Yapma (YouTube'den Video / Ses İndirip Gönderen)