2 |
3 |
7 | 8 | ## This Is A Telegram Bot Written In Python For Searching Files In Our Beloved Google Drive.It Can Search In Multiple Shared Drive/Team Drives. 9 |
10 | 11 | 12 | Here Are Some Things To Get You Started.👇 13 | 14 | ### 👉[How To Deploy](https://github.com/iamLiquidX/SearchX/wiki/How-To-Deploy) 15 | 16 | 17 | For The Most Recent Changes, Please Check The Changelog.👇 18 | 19 | ### 👉[Changelog](https://github.com/iamLiquidX/SearchX/wiki/Changelog) 20 | 21 | If Bot Still Getting Stuck On Any Task, Check Solution.👇 22 | 23 | ### 👉[Fix](https://github.com/iamLiquidX/SearchX/wiki/Fix-To-Getting-Stuck) 24 | 25 | 26 | 27 | # Credits 👇 28 | 1.[Sreeraj V R](https://github.com/SVR666)- Created Search Bot. 29 | 2.[AnimeKaizoku](https://github.com/AnimeKaizoku)- Fixes & Improvement. 30 | -------------------------------------------------------------------------------- /authorized_chats.txt: -------------------------------------------------------------------------------- 1 | #Enter The Chat IDs & User ID Here, Whom You Wanna Give Access To.One Per Line. 2 | -------------------------------------------------------------------------------- /bot/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import time 4 | 5 | import telegram.ext as tg 6 | from dotenv import load_dotenv 7 | 8 | from telegraph import Telegraph 9 | 10 | botStartTime = time.time() 11 | if os.path.exists('log.txt'): 12 | with open('log.txt', 'r+') as f: 13 | f.truncate(0) 14 | 15 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 16 | handlers=[logging.FileHandler('log.txt'), logging.StreamHandler()], 17 | level=logging.INFO) 18 | 19 | load_dotenv('config.env') 20 | 21 | def getConfig(name: str): 22 | return os.environ[name] 23 | 24 | LOGGER = logging.getLogger(__name__) 25 | 26 | try: 27 | if bool(getConfig('_____REMOVE_THIS_LINE_____')): 28 | logging.error('The README.md file there to be read! Exiting now!') 29 | exit() 30 | except KeyError: 31 | pass 32 | 33 | BOT_TOKEN = None 34 | 35 | AUTHORIZED_CHATS = set() 36 | 37 | AUTHORIZED_CHATS = set() 38 | if os.path.exists('authorized_chats.txt'): 39 | with open('authorized_chats.txt', 'r+') as f: 40 | lines = f.readlines() 41 | for line in lines: 42 | AUTHORIZED_CHATS.add(int(line.split()[0])) 43 | 44 | try: 45 | BOT_TOKEN = getConfig('BOT_TOKEN') 46 | OWNER_ID = int(getConfig('OWNER_ID')) 47 | telegraph_token = getConfig('TELEGRAPH_TOKEN') 48 | except KeyError as e: 49 | LOGGER.error("One or more env variables missing! Exiting now") 50 | exit(1) 51 | 52 | DRIVE_NAME = [] 53 | DRIVE_ID = [] 54 | INDEX_URL = [] 55 | 56 | if os.path.exists('drive_folder'): 57 | with open('drive_folder', 'r+') as f: 58 | lines = f.readlines() 59 | for line in lines: 60 | temp = line.strip().split() 61 | DRIVE_NAME.append(temp[0].replace("_", " ")) 62 | DRIVE_ID.append(temp[1]) 63 | try: 64 | INDEX_URL.append(temp[2]) 65 | except IndexError as e: 66 | INDEX_URL.append(None) 67 | 68 | if DRIVE_ID : 69 | pass 70 | else : 71 | LOGGER.error("The README.md file there to be read! Exiting now!") 72 | exit(1) 73 | 74 | telegra_ph = Telegraph(access_token=telegraph_token) 75 | 76 | updater = tg.Updater(token=BOT_TOKEN,use_context=True) 77 | bot = updater.bot 78 | dispatcher = updater.dispatcher 79 | -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler, run_async 2 | from bot import dispatcher, updater, botStartTime 3 | from bot.helper.telegram_helper.bot_commands import BotCommands 4 | from bot.helper.telegram_helper.message_utils import * 5 | from .helper.telegram_helper.filters import CustomFilters 6 | from .modules import authorize, list 7 | 8 | @run_async 9 | def start(update, context): 10 | LOGGER.info('UID: {} - UN: {} - MSG: {}'.format(update.message.chat.id,update.message.chat.username,update.message.text)) 11 | if update.message.chat.type == "private" : 12 | sendMessage(f"Hey {update.message.chat.first_name}. Welcome to SearchX Bot", context.bot, update) 13 | else : 14 | sendMessage("I'm alive :)", context.bot, update) 15 | 16 | @run_async 17 | def log(update, context): 18 | sendLogFile(context.bot, update) 19 | 20 | def main(): 21 | 22 | start_handler = CommandHandler(BotCommands.StartCommand, start, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user) 23 | log_handler = CommandHandler(BotCommands.LogCommand, log, filters=CustomFilters.owner_filter) 24 | 25 | dispatcher.add_handler(start_handler) 26 | dispatcher.add_handler(log_handler) 27 | 28 | updater.start_polling() 29 | LOGGER.info("Yeah I'm running!") 30 | updater.idle() 31 | 32 | main() 33 | -------------------------------------------------------------------------------- /bot/helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamLiquidX/SearchX/af163ee1d9398701551e6486422c8abf88907ac9/bot/helper/__init__.py -------------------------------------------------------------------------------- /bot/helper/drive_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamLiquidX/SearchX/af163ee1d9398701551e6486422c8abf88907ac9/bot/helper/drive_utils/__init__.py -------------------------------------------------------------------------------- /bot/helper/drive_utils/gdriveTools.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | import re 4 | import requests 5 | import logging 6 | 7 | from google.auth.transport.requests import Request 8 | from google_auth_oauthlib.flow import InstalledAppFlow 9 | from googleapiclient.discovery import build 10 | from googleapiclient.errors import HttpError 11 | 12 | from telegram import InlineKeyboardMarkup 13 | from bot.helper.telegram_helper import button_builder 14 | from bot import DRIVE_NAME, DRIVE_ID, INDEX_URL, telegra_ph 15 | 16 | LOGGER = logging.getLogger(__name__) 17 | logging.getLogger('googleapiclient.discovery').setLevel(logging.ERROR) 18 | 19 | SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] 20 | TELEGRAPHLIMIT = 90 21 | 22 | class GoogleDriveHelper: 23 | def __init__(self, name=None, listener=None): 24 | self.__G_DRIVE_TOKEN_FILE = "token.pickle" 25 | # Check https://developers.google.com/drive/scopes for all available scopes 26 | self.__OAUTH_SCOPE = ['https://www.googleapis.com/auth/drive'] 27 | self.__service = self.authorize() 28 | self.telegraph_content = [] 29 | self.path = [] 30 | 31 | def get_readable_file_size(self,size_in_bytes) -> str: 32 | if size_in_bytes is None: 33 | return '0B' 34 | index = 0 35 | size_in_bytes = int(size_in_bytes) 36 | while size_in_bytes >= 1024: 37 | size_in_bytes /= 1024 38 | index += 1 39 | try: 40 | return f'{round(size_in_bytes, 2)}{SIZE_UNITS[index]}' 41 | except IndexError: 42 | return 'File too large' 43 | 44 | 45 | def authorize(self): 46 | # Get credentials 47 | credentials = None 48 | if os.path.exists(self.__G_DRIVE_TOKEN_FILE): 49 | with open(self.__G_DRIVE_TOKEN_FILE, 'rb') as f: 50 | credentials = pickle.load(f) 51 | if credentials is None or not credentials.valid: 52 | if credentials and credentials.expired and credentials.refresh_token: 53 | credentials.refresh(Request()) 54 | else: 55 | flow = InstalledAppFlow.from_client_secrets_file( 56 | 'credentials.json', self.__OAUTH_SCOPE) 57 | LOGGER.info(flow) 58 | credentials = flow.run_console(port=0) 59 | 60 | # Save the credentials for the next run 61 | with open(self.__G_DRIVE_TOKEN_FILE, 'wb') as token: 62 | pickle.dump(credentials, token) 63 | return build('drive', 'v3', credentials=credentials, cache_discovery=False) 64 | 65 | def get_recursive_list(self, file, rootid = "root"): 66 | rtnlist = [] 67 | if not rootid: 68 | rootid = file.get('teamDriveId') 69 | if rootid == "root": 70 | rootid = self.__service.files().get(fileId = 'root', fields="id").execute().get('id') 71 | x = file.get("name") 72 | y = file.get("id") 73 | while(y != rootid): 74 | rtnlist.append(x) 75 | file = self.__service.files().get( 76 | fileId=file.get("parents")[0], 77 | supportsAllDrives=True, 78 | fields='id, name, parents' 79 | ).execute() 80 | x = file.get("name") 81 | y = file.get("id") 82 | rtnlist.reverse() 83 | return rtnlist 84 | 85 | def drive_query(self, parent_id, search_type, fileName): 86 | query = "" 87 | if search_type is not None: 88 | if search_type == '-d': 89 | query += "mimeType = 'application/vnd.google-apps.folder' and " 90 | elif search_type == '-f': 91 | query += "mimeType != 'application/vnd.google-apps.folder' and " 92 | var=re.split('[ ._,\\[\\]-]',fileName) 93 | for text in var: 94 | query += f"name contains '{text}' and " 95 | query += "trashed=false" 96 | if parent_id != "root": 97 | response = self.__service.files().list(supportsTeamDrives=True, 98 | includeTeamDriveItems=True, 99 | teamDriveId=parent_id, 100 | q=query, 101 | corpora='drive', 102 | spaces='drive', 103 | pageSize=1000, 104 | fields='files(id, name, mimeType, size, teamDriveId, parents)', 105 | orderBy='folder, modifiedTime desc').execute()["files"] 106 | else: 107 | response = self.__service.files().list(q=query + " and 'me' in owners", 108 | pageSize=1000, 109 | spaces='drive', 110 | fields='files(id, name, mimeType, size, parents)', 111 | orderBy='folder, modifiedTime desc').execute()["files"] 112 | return response 113 | 114 | def edit_telegraph(self): 115 | nxt_page = 1 116 | prev_page = 0 117 | for content in self.telegraph_content : 118 | if nxt_page == 1 : 119 | content += f'Next' 120 | nxt_page += 1 121 | else : 122 | if prev_page <= self.num_of_path: 123 | content += f'Previous' 124 | prev_page += 1 125 | if nxt_page < self.num_of_path: 126 | content += f' | Next' 127 | nxt_page += 1 128 | telegra_ph.edit_page(path = self.path[prev_page], 129 | title = 'SearchX', 130 | html_content=content) 131 | return 132 | 133 | def drive_list(self, fileName): 134 | search_type = None 135 | if re.search("^-d ", fileName, re.IGNORECASE): 136 | search_type = '-d' 137 | fileName = fileName[ 2 : len(fileName)] 138 | elif re.search("^-f ", fileName, re.IGNORECASE): 139 | search_type = '-f' 140 | fileName = fileName[ 2 : len(fileName)] 141 | if len(fileName) > 2: 142 | remove_list = ['A', 'a', 'X', 'x'] 143 | if fileName[1] == ' ' and fileName[0] in remove_list: 144 | fileName = fileName[ 2 : len(fileName) ] 145 | msg = '' 146 | INDEX = -1 147 | content_count = 0 148 | reached_max_limit = False 149 | add_title_msg = True 150 | for parent_id in DRIVE_ID : 151 | add_drive_title = True 152 | response = self.drive_query(parent_id, search_type, fileName) 153 | #LOGGER.info(f"my a: {response}") 154 | 155 | INDEX += 1 156 | if response: 157 | 158 | for file in response: 159 | 160 | if add_title_msg == True: 161 | msg = f'{file.get('name')}
(folder){file.get('name')}
({self.get_readable_file_size(file.get('size'))}){users}
\n', context.bot, update)
87 |
88 |
89 | send_auth_handler = CommandHandler(command=BotCommands.AuthorizedUsersCommand, callback=sendAuthChats,
90 | filters=CustomFilters.owner_filter)
91 | authorize_handler = CommandHandler(command=BotCommands.AuthorizeCommand, callback=authorize,
92 | filters=CustomFilters.owner_filter)
93 | unauthorize_handler = CommandHandler(command=BotCommands.UnAuthorizeCommand, callback=unauthorize,
94 | filters=CustomFilters.owner_filter)
95 |
96 | dispatcher.add_handler(send_auth_handler)
97 | dispatcher.add_handler(authorize_handler)
98 | dispatcher.add_handler(unauthorize_handler)
99 |
100 |
101 |
--------------------------------------------------------------------------------
/bot/modules/list.py:
--------------------------------------------------------------------------------
1 | from telegram.ext import CommandHandler, run_async
2 | from bot.helper.drive_utils.gdriveTools import GoogleDriveHelper
3 | from bot import LOGGER, dispatcher
4 | from bot.helper.telegram_helper.message_utils import sendMessage, editMessage
5 | from bot.helper.telegram_helper.filters import CustomFilters
6 | from bot.helper.telegram_helper.bot_commands import BotCommands
7 |
8 | @run_async
9 | def list_drive(update,context):
10 | try:
11 | search = update.message.text.split(' ',maxsplit=1)[1]
12 | except IndexError:
13 | sendMessage('Send A Search Key Along With Command', context.bot, update)
14 | return
15 |
16 | reply = sendMessage('Searching...', context.bot, update)
17 |
18 | LOGGER.info(f"Searching: {search}")
19 |
20 | gdrive = GoogleDriveHelper(None)
21 | msg, button = gdrive.drive_list(search)
22 |
23 | editMessage(msg,reply,button)
24 |
25 |
26 | list_handler = CommandHandler(BotCommands.ListCommand, list_drive,filters=CustomFilters.authorized_chat | CustomFilters.authorized_user)
27 | dispatcher.add_handler(list_handler)
28 |
--------------------------------------------------------------------------------
/captain-definition:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": 2,
3 | "dockerfilePath": "./Dockerfile"
4 | }
5 |
--------------------------------------------------------------------------------
/config.env:
--------------------------------------------------------------------------------
1 | # ENTER BOT TOKEN (Get your BOT_TOKEN by talking to @botfather)
2 | BOT_TOKEN = ""
3 | OWNER_ID =
4 | TELEGRAPH_TOKEN = ""
5 |
--------------------------------------------------------------------------------
/drive_folder:
--------------------------------------------------------------------------------
1 | #Enter You Drive Name, Drive ID, Index Url Here, Give Space Between Them. See Example in Guide.
2 |
--------------------------------------------------------------------------------
/driveid.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | print("\n\n"\
4 | " Bot can search files recursively, but you have to add the list of drives you want to search.\n"\
5 | " Use the following format: (You can use 'root' in the ID in case you wan to use main drive.)\n"\
6 | " teamdrive NAME --> anything that u likes\n"\
7 | " teamdrive ID --> id of teamdrives in which u likes to search ('root' for main drive)\n"\
8 | " teamdrive INDEX URL --> enter index url for this drive.\n" \
9 | " goto the respective drive and copy the url from address bar\n")
10 | msg = ''
11 | if os.path.exists('drive_folder'):
12 | with open('drive_folder', 'r+') as f:
13 | lines = f.read()
14 | if not re.match(r'^\s*$', lines):
15 | print(lines)
16 | print("\n\n"\
17 | " DO YOU WISH TO KEEP THE ABOVE DETAILS THAT YOU PREVIOUSLY ADDED???? ENTER (y/n)\n"\
18 | " IF NOTHING SHOWS ENTER n")
19 | while (1):
20 | choice = input()
21 | if choice == 'y' or choice == 'Y':
22 | msg = f'{lines}'
23 | break
24 | elif choice == 'n' or choice == 'N':
25 | break
26 | else:
27 | print("\n\n DO YOU WISH TO KEEP THE ABOVE DETAILS ???? y/n <=== this is option ..... OPEN YOUR EYES & READ...")
28 | num = int(input(" How Many Drive/Folder You Likes To Add : "))
29 | count = 1
30 | while count <= num :
31 | print(f"\n > DRIVE - {count}\n")
32 | name = input(" Enter Drive NAME (anything) : ")
33 | id = input(" Enter Drive ID : ")
34 | index = input(" Enter Drive INDEX URL (optional) : ")
35 | if not name or not id:
36 | print("\n\n ERROR : Dont leave the name/id without filling.")
37 | exit(1)
38 | name=name.replace(" ", "_")
39 | if index:
40 | if index[-1] == "/":
41 | index = index[:-1]
42 | else:
43 | index = ''
44 | count+=1
45 | msg += f"{name} {id} {index}\n"
46 | with open('drive_folder', 'w') as file:
47 | file.truncate(0)
48 | file.write(msg)
49 | print("\n\n Done!")
50 |
--------------------------------------------------------------------------------
/generate_drive_token.py:
--------------------------------------------------------------------------------
1 | import pickle
2 | import os
3 | from google_auth_oauthlib.flow import InstalledAppFlow
4 | from google.auth.transport.requests import Request
5 |
6 | credentials = None
7 | __G_DRIVE_TOKEN_FILE = "token.pickle"
8 | __OAUTH_SCOPE = ["https://www.googleapis.com/auth/drive"]
9 | if os.path.exists(__G_DRIVE_TOKEN_FILE):
10 | with open(__G_DRIVE_TOKEN_FILE, 'rb') as f:
11 | credentials = pickle.load(f)
12 | if credentials is None or not credentials.valid:
13 | if credentials and credentials.expired and credentials.refresh_token:
14 | credentials.refresh(Request())
15 | else:
16 | flow = InstalledAppFlow.from_client_secrets_file(
17 | 'credentials.json', __OAUTH_SCOPE)
18 | credentials = flow.run_console(port=0)
19 |
20 | # Save the credentials for the next run
21 | with open(__G_DRIVE_TOKEN_FILE, 'wb') as token:
22 | pickle.dump(credentials, token)
--------------------------------------------------------------------------------
/heroku.yml:
--------------------------------------------------------------------------------
1 | build:
2 | docker:
3 | worker: Dockerfile
4 | run:
5 | worker: bash start.sh
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | python-telegram-bot==12.6.1
3 | google-api-python-client>=1.7.11,<1.7.20
4 | google-auth-httplib2>=0.0.3,<0.1.0
5 | google-auth-oauthlib>=0.4.1,<0.10.0
6 | python-dotenv>=0.10
7 | telegraph
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | python3 -m bot
--------------------------------------------------------------------------------
/telegraph_token.py:
--------------------------------------------------------------------------------
1 | from telegraph import Telegraph
2 |
3 | telegraph = Telegraph()
4 | telegraph.create_account(short_name=input("Enter a username for your Telegra.ph : "))
5 |
6 | print(f"Your Telegra.ph token ==> {telegraph.get_access_token()}")
--------------------------------------------------------------------------------