├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bot ├── __init__.py ├── __main__.py ├── drive │ ├── __init__.py │ └── drive.py ├── modules │ ├── __init__.py │ ├── auth.py │ ├── clone_and_count.py │ ├── search.py │ └── start.py └── utils │ ├── __init__.py │ ├── database.py │ ├── errors.py │ ├── filters.py │ ├── formatter.py │ ├── ikb.py │ ├── message.py │ └── parser.py ├── generate_drive_token.py ├── heroku.yml ├── requirements.txt └── sample_config.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pickle 2 | *.json 3 | config.py 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | WORKDIR /usr/src/app 4 | RUN chmod 777 /usr/src/app 5 | RUN apt-get -qq update 6 | RUN DEBIAN_FRONTEND="noninteractive" apt-get -qq install python3 python3-pip software-properties-common 7 | 8 | #Updating Libraries 9 | RUN pip3 install -U pip 10 | COPY requirements.txt . 11 | RUN pip3 install --no-cache-dir -U -r requirements.txt 12 | 13 | #Copying All Source 14 | COPY . . 15 | 16 | #Starting Bot 17 | CMD ["python3", "-m", "bot"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Akshay Rajput 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GDriveBot 2 | 3 | ## Credits 4 | [@SVR666](https://github.com/SVR666) For Drive module. 5 |
6 | [@TheHamkerCat](https://github.com/TheHamkerCat) For some functions and repo. 7 |
8 | [@Dank-del](https://github.com/Dank-del) For Import Modules. 9 |
10 | [@Pokurt](https://github.com/pokurt) For Capture Errors and else. 11 |
12 | [@Levi](https://github.com/l3v11) For Mongo idea 13 |
14 | [@Yuuki](https://github.com/xcscxr) For GDtot and Appdrive 15 |
16 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | from os.path import exists 2 | from asyncio import get_event_loop 3 | from pyrogram import Client 4 | from pyromod import listen 5 | from logging import basicConfig, FileHandler, StreamHandler, getLogger, INFO, WARNING 6 | from motor.motor_asyncio import AsyncIOMotorClient as MongoClient 7 | 8 | basicConfig( 9 | format='%(asctime)s - %(name)s - [%(levelname)s] - %(message)s', 10 | handlers=[ 11 | FileHandler("logs.txt"), 12 | StreamHandler() 13 | ], 14 | level=INFO 15 | ) 16 | 17 | LOGGER = getLogger(__name__) 18 | getLogger("pyrogram").setLevel(WARNING) 19 | 20 | is_config = exists("config.py") 21 | 22 | if is_config: 23 | from config import * 24 | else: 25 | from sample_config import * 26 | 27 | mongo_client = MongoClient(MONGO_URL) 28 | db = mongo_client.bot 29 | 30 | ALLOWED_CHAT = SUDO_CHATS_ID 31 | 32 | async def load_auth(): 33 | global ALLOWED_CHAT 34 | authdb = db.auths 35 | auths = await authdb.find_one({"auth": "auth"}) 36 | auths = [] if not auths else auths["authorize"] 37 | if OWNER_ID not in auths: 38 | auths.append(OWNER_ID) 39 | 40 | for user_id in ALLOWED_CHAT: 41 | if user_id not in auths: 42 | auths.append(user_id) 43 | await authdb.update_one( 44 | {"auth": "auth"}, 45 | {"$set": {"authorize": auths}}, 46 | upsert=True, 47 | ) 48 | ALLOWED_CHAT = (ALLOWED_CHAT + auths) if auths else ALLOWED_CHAT 49 | 50 | 51 | 52 | loop = get_event_loop() 53 | loop.run_until_complete(load_auth()) 54 | 55 | # Input Your Own ID or Hash if u want 56 | app = Client( 57 | ":memory:", 58 | bot_token=BOT_TOKEN, 59 | api_id=API_ID, 60 | api_hash=API_HASH 61 | ) 62 | 63 | app.start() -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module, reload as reloads 2 | from asyncio import get_event_loop, sleep, exceptions 3 | from pyrogram import idle 4 | 5 | from bot.modules import ALL_MODULES 6 | from bot.utils.database import clean_restart 7 | from bot import app, LOG_CHAT 8 | 9 | async def start_bot(): 10 | for module in ALL_MODULES: 11 | imported_module = import_module(f'bot.modules.{module}') 12 | reloads(imported_module) 13 | 14 | data = await clean_restart() 15 | try: 16 | if data: 17 | await app.edit_message_text( 18 | data["chat_id"], 19 | data["message_id"], 20 | "**Restarted Successfully**", 21 | ) 22 | 23 | else: 24 | await app.send_message(LOG_CHAT, "Bot started!") 25 | except Exception: 26 | pass 27 | 28 | await idle() 29 | await app.stop() 30 | 31 | loop = get_event_loop() 32 | 33 | if __name__ == "__main__": 34 | try: 35 | try: 36 | loop.run_until_complete(start_bot()) 37 | except exceptions.CancelledError: 38 | pass 39 | loop.run_until_complete(sleep(3.0)) 40 | finally: 41 | loop.close() 42 | -------------------------------------------------------------------------------- /bot/drive/__init__.py: -------------------------------------------------------------------------------- 1 | from .drive import GoogleDriveHelper -------------------------------------------------------------------------------- /bot/drive/drive.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import pickle 4 | import json 5 | import logging 6 | from tkinter import Button 7 | 8 | from urllib.parse import urlparse, parse_qs 9 | from random import randrange 10 | from google.oauth2 import service_account 11 | from google.auth.transport.requests import Request 12 | from google_auth_oauthlib.flow import InstalledAppFlow 13 | from googleapiclient.discovery import build 14 | from googleapiclient.errors import HttpError 15 | from tenacity import * 16 | 17 | from bot.utils import get_readable_file_size, ikb 18 | from bot import DRIVE_ID, LOGGER, FOLDER_ID, IS_TEAM_DRIVE, USE_SERVICE_ACCOUNTS 19 | 20 | if USE_SERVICE_ACCOUNTS: 21 | SERVICE_ACCOUNT_INDEX = randrange(len(os.listdir("accounts"))) 22 | 23 | class GoogleDriveHelper: 24 | def __init__(self, is_tg: bool = True): 25 | # Redirect URI for installed apps, can be left as is 26 | self.__G_DRIVE_DIR_MIME_TYPE = "application/vnd.google-apps.folder" 27 | self.__G_DRIVE_BASE_DOWNLOAD_URL = "https://drive.google.com/uc?id={}&export=download" 28 | self.__G_DRIVE_DIR_BASE_DOWNLOAD_URL = "https://drive.google.com/drive/folders/{}" 29 | self.__G_DRIVE_TOKEN_FILE = "token.pickle" 30 | self.__OAUTH_SCOPE = ['https://www.googleapis.com/auth/drive'] 31 | self.__service = self.authorize() 32 | self.path = [] 33 | 34 | @staticmethod 35 | def getIdFromUrl(link: str): 36 | if "folders" in link or "file" in link: 37 | regex = r"https://drive\.google\.com/(drive)?/?u?/?\d?/?(mobile)?/?(file)?(folders)?/?d?/([-\w]+)[?+]?/?(w+)?" 38 | res = re.search(regex, link) 39 | if res is None: 40 | raise IndexError("Drive ID not found") 41 | return res.group(5) 42 | parsed = urlparse(link) 43 | return parse_qs(parsed.query)['id'][0] 44 | 45 | def switchServiceAccount(self): 46 | global SERVICE_ACCOUNT_INDEX 47 | service_account_count = len(os.listdir("accounts")) 48 | if SERVICE_ACCOUNT_INDEX == service_account_count - 1: 49 | SERVICE_ACCOUNT_INDEX = 0 50 | SERVICE_ACCOUNT_INDEX += 1 51 | LOGGER.info(f"Authorizing with {SERVICE_ACCOUNT_INDEX}.json file") 52 | self.__service = self.authorize() 53 | 54 | def authorize(self): 55 | # Get credentials 56 | credentials = None 57 | if not USE_SERVICE_ACCOUNTS: 58 | if os.path.exists(self.__G_DRIVE_TOKEN_FILE): 59 | with open(self.__G_DRIVE_TOKEN_FILE, 'rb') as f: 60 | credentials = json.loads(f) 61 | if credentials is None or not credentials.valid: 62 | if credentials and credentials.expired and credentials.refresh_token: 63 | credentials.refresh(Request()) 64 | else: 65 | flow = InstalledAppFlow.from_client_secrets_file( 66 | 'credentials.json', self.__OAUTH_SCOPE) 67 | LOGGER.info(flow) 68 | credentials = flow.run_console(port=0) 69 | 70 | # Save the credentials for the next run 71 | with open(self.__G_DRIVE_TOKEN_FILE, 'wb') as token: 72 | pickle.dump(credentials, token) 73 | else: 74 | LOGGER.info(f"Authorizing with {SERVICE_ACCOUNT_INDEX}.json service account") 75 | credentials = service_account.Credentials.from_service_account_file( 76 | f'accounts/{SERVICE_ACCOUNT_INDEX}.json', 77 | scopes=self.__OAUTH_SCOPE) 78 | return build('drive', 'v3', credentials=credentials, cache_discovery=False) 79 | 80 | def alt_authorize(self): 81 | credentials = None 82 | if USE_SERVICE_ACCOUNTS and not self.alt_auth: 83 | self.alt_auth = True 84 | if os.path.exists(self.__G_DRIVE_TOKEN_FILE): 85 | LOGGER.info("Authorize with token.pickle") 86 | with open(self.__G_DRIVE_TOKEN_FILE, 'rb') as f: 87 | credentials = pickle.load(f) 88 | if credentials is None or not credentials.valid: 89 | if credentials and credentials.expired and credentials.refresh_token: 90 | credentials.refresh(Request()) 91 | else: 92 | flow = InstalledAppFlow.from_client_secrets_file( 93 | 'credentials.json', self.__OAUTH_SCOPE) 94 | LOGGER.info(flow) 95 | credentials = flow.run_console(port=0) 96 | # Save the credentials for the next run 97 | with open(self.__G_DRIVE_TOKEN_FILE, 'wb') as token: 98 | pickle.dump(credentials, token) 99 | return build('drive', 'v3', credentials=credentials, cache_discovery=False) 100 | return None 101 | 102 | @staticmethod 103 | def getIdFromUrl(link: str): 104 | if "folders" in link or "file" in link: 105 | regex = r"https://drive\.google\.com/(drive)?/?u?/?\d?/?(mobile)?/?(file)?(folders)?/?d?/([-\w]+)[?+]?/?(w+)?" 106 | res = re.search(regex, link) 107 | if res is None: 108 | raise IndexError("Drive ID not found") 109 | return res.group(5) 110 | parsed = urlparse(link) 111 | return parse_qs(parsed.query)['id'][0] 112 | 113 | def deleteFile(self, link: str): 114 | try: 115 | file_id = self.getIdFromUrl(link) 116 | except (KeyError, IndexError): 117 | msg = "Drive ID not found" 118 | LOGGER.error(f"{msg}") 119 | return msg 120 | msg = '' 121 | try: 122 | res = self.__service.files().delete(fileId=file_id, supportsTeamDrives=IS_TEAM_DRIVE).execute() 123 | msg = "Successfully deleted" 124 | except HttpError as err: 125 | if "File not found" in str(err): 126 | msg = "No such file exists" 127 | elif "insufficientFilePermissions" in str(err): 128 | msg = "Insufficient file permissions" 129 | token_service = self.alt_authorize() 130 | if token_service is not None: 131 | self.__service = token_service 132 | return self.deleteFile(link) 133 | else: 134 | msg = str(err) 135 | LOGGER.error(f"{msg}") 136 | finally: 137 | return msg 138 | 139 | def switchServiceAccount(self): 140 | global SERVICE_ACCOUNT_INDEX 141 | service_account_count = len(os.listdir("accounts")) 142 | if SERVICE_ACCOUNT_INDEX == service_account_count - 1: 143 | SERVICE_ACCOUNT_INDEX = 0 144 | SERVICE_ACCOUNT_INDEX += 1 145 | LOGGER.info(f"Authorizing with {SERVICE_ACCOUNT_INDEX}.json file") 146 | self.__service = self.authorize() 147 | 148 | def __set_permission(self, drive_id): 149 | permissions = { 150 | 'role': 'reader', 151 | 'type': 'anyone', 152 | 'value': None, 153 | 'withLink': True 154 | } 155 | return self.__service.permissions().create(supportsTeamDrives=True, fileId=drive_id, 156 | body=permissions).execute() 157 | 158 | def setPerm(self, link: str): 159 | try: 160 | file_id = self.getIdFromUrl(link) 161 | except (KeyError, IndexError): 162 | msg = "Drive ID not found" 163 | LOGGER.error(f"{msg}") 164 | return msg 165 | msg = '' 166 | try: 167 | res = self.__set_permission(file_id) 168 | msg = "Successfully set permissions" 169 | except HttpError as err: 170 | if "File not found" in str(err): 171 | msg = "No such file exists" 172 | elif "insufficientFilePermissions" in str(err): 173 | msg = "Insufficient file permissions" 174 | token_service = self.alt_authorize() 175 | if token_service is not None: 176 | self.__service = token_service 177 | return self.setPerm(link) 178 | else: 179 | msg = str(err) 180 | LOGGER.error(f"{msg}") 181 | finally: 182 | return msg 183 | 184 | @retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5), 185 | retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG)) 186 | def copyFile(self, file_id, dest_id): 187 | body = { 188 | 'parents': [dest_id] 189 | } 190 | try: 191 | return ( 192 | self.__service.files() 193 | .copy(supportsAllDrives=True, fileId=file_id, body=body) 194 | .execute() 195 | ) 196 | 197 | except HttpError as err: 198 | if err.resp.get('content-type', '').startswith('application/json'): 199 | reason = json.loads(err.content).get('error').get('errors')[0].get('reason') 200 | if reason in ['userRateLimitExceeded', 'dailyLimitExceeded']: 201 | if USE_SERVICE_ACCOUNTS: 202 | self.switchServiceAccount() 203 | return self.copyFile(file_id, dest_id) 204 | else: 205 | LOGGER.info(f"Warning: {reason}") 206 | raise err 207 | else: 208 | raise err 209 | 210 | @retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5), 211 | retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG)) 212 | def getFileMetadata(self, file_id): 213 | return self.__service.files().get(supportsAllDrives=True, fileId=file_id, 214 | fields="name, id, mimeType, size").execute() 215 | 216 | @retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5), 217 | retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG)) 218 | def getFilesByFolderId(self, folder_id): 219 | page_token = None 220 | query = f"'{folder_id}' in parents and trashed = false" 221 | files = [] 222 | while True: 223 | response = self.__service.files().list(supportsTeamDrives=True, 224 | includeTeamDriveItems=True, 225 | q=query, 226 | spaces='drive', 227 | pageSize=200, 228 | fields='nextPageToken, files(id, name, mimeType, size)', 229 | pageToken=page_token).execute() 230 | files.extend(iter(response.get('files', []))) 231 | page_token = response.get('nextPageToken', None) 232 | if page_token is None: 233 | break 234 | return files 235 | 236 | def clone(self, link, parent_id: FOLDER_ID): 237 | self.transferred_size = 0 238 | self.total_files = 0 239 | self.total_folders = 0 240 | try: 241 | file_id = self.getIdFromUrl(link) 242 | except (KeyError, IndexError): 243 | msg = "Drive ID not found" 244 | LOGGER.error(f"{msg}") 245 | return msg 246 | msg = "" 247 | try: 248 | meta = self.getFileMetadata(file_id) 249 | if meta.get("mimeType") == self.__G_DRIVE_DIR_MIME_TYPE: 250 | dir_id = self.create_directory(meta.get('name'), parent_id) 251 | result = self.cloneFolder(meta.get('name'), meta.get('name'), meta.get('id'), dir_id) 252 | msg += f'Filename: {meta.get("name")}' 253 | msg += f'\nSize: {get_readable_file_size(self.transferred_size)}' 254 | msg += '\nType: Folder' 255 | msg += f"\nSubFolders: {self.total_folders}" 256 | msg += f"\nFiles: {self.total_files}" 257 | link = self.__G_DRIVE_DIR_BASE_DOWNLOAD_URL.format(dir_id) 258 | else: 259 | file = self.copyFile(meta.get('id'), parent_id) 260 | try: 261 | typ = file.get('mimeType') 262 | except: 263 | typ = 'File' 264 | msg += f'Filename: {file.get("name")}' 265 | try: 266 | msg += f'\nSize: {get_readable_file_size(int(meta.get("size", 0)))}' 267 | msg += f'\nType: {typ}' 268 | link = self.__G_DRIVE_BASE_DOWNLOAD_URL.format(file.get("id")) 269 | except TypeError: 270 | pass 271 | button = ikb({"Drive Link": link}) 272 | except Exception as err: 273 | button = None 274 | if isinstance(err, RetryError): 275 | LOGGER.info(f"Total attempts: {err.last_attempt.attempt_number}") 276 | err = err.last_attempt.exception() 277 | err = str(err).replace('>', '').replace('<', '') 278 | LOGGER.error(err) 279 | if "User rate limit exceeded" in str(err): 280 | msg = "User rate limit exceeded" 281 | elif "File not found" in str(err): 282 | token_service = self.alt_authorize() 283 | if token_service is not None: 284 | self.__service = token_service 285 | return self.clone(link) 286 | msg = "No such file exists" 287 | else: 288 | msg = str(err) 289 | LOGGER.error(f"{msg}") 290 | return msg, button 291 | 292 | def cloneFolder(self, name, local_path, folder_id, parent_id): 293 | LOGGER.info(f"Syncing: {local_path}") 294 | files = self.getFilesByFolderId(folder_id) 295 | new_id = None 296 | if len(files) == 0: 297 | return parent_id 298 | for file in files: 299 | if file.get('mimeType') == self.__G_DRIVE_DIR_MIME_TYPE: 300 | self.total_folders += 1 301 | file_path = os.path.join(local_path, file.get('name')) 302 | current_dir_id = self.create_directory(file.get('name'), parent_id) 303 | new_id = self.cloneFolder(file.get('name'), file_path, file.get('id'), current_dir_id) 304 | else: 305 | try: 306 | self.total_files += 1 307 | self.transferred_size += int(file.get('size', 0)) 308 | except TypeError: 309 | pass 310 | try: 311 | self.copyFile(file.get('id'), parent_id) 312 | new_id = parent_id 313 | except Exception as e: 314 | if isinstance(e, RetryError): 315 | LOGGER.info(f"Total attempts: {e.last_attempt.attempt_number}") 316 | err = e.last_attempt.exception() 317 | else: 318 | err = e 319 | LOGGER.error(err) 320 | return new_id 321 | 322 | @retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5), 323 | retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG)) 324 | def create_directory(self, directory_name, parent_id): 325 | file_metadata = { 326 | "name": directory_name, 327 | "mimeType": self.__G_DRIVE_DIR_MIME_TYPE 328 | } 329 | if parent_id is not None: 330 | file_metadata["parents"] = [parent_id] 331 | file = self.__service.files().create(supportsTeamDrives=True, body=file_metadata).execute() 332 | file_id = file.get("id") 333 | if not IS_TEAM_DRIVE: 334 | self.__set_permission(file_id) 335 | LOGGER.info("Created: {}".format(file.get("name"))) 336 | return file_id 337 | 338 | def count(self, link): 339 | try: 340 | file_id = self.getIdFromUrl(link) 341 | except (KeyError, IndexError): 342 | msg = "Drive ID not found" 343 | LOGGER.error(f"{msg}") 344 | return msg 345 | msg = "" 346 | try: 347 | meta = self.getFileMetadata(file_id) 348 | mime_type = meta.get('mimeType') 349 | if mime_type == self.__G_DRIVE_DIR_MIME_TYPE: 350 | self.gDrive_directory(meta) 351 | msg += f'Name: {meta.get("name")}' 352 | msg += f'\nSize: {get_readable_file_size(self.total_bytes)}' 353 | msg += '\nType: Folder' 354 | msg += f'\nSubFolders: {self.total_folders}' 355 | link = self.__G_DRIVE_DIR_BASE_DOWNLOAD_URL.format(file_id) 356 | else: 357 | msg += f'Name: {meta.get("name")}' 358 | if mime_type is None: 359 | mime_type = 'File' 360 | self.total_files += 1 361 | self.gDrive_file(meta) 362 | msg += f'\nSize: {get_readable_file_size(self.total_bytes)}' 363 | msg += f'\nType: {mime_type}' 364 | link = self.__G_DRIVE_BASE_DOWNLOAD_URL.format(file_id) 365 | msg += f'\nFiles: {self.total_files}' 366 | button = ikb({"Drive Link": link}) 367 | except Exception as err: 368 | button = None 369 | if isinstance(err, RetryError): 370 | LOGGER.info(f"Total attempts: {err.last_attempt.attempt_number}") 371 | err = err.last_attempt.exception() 372 | err = str(err).replace('>', '').replace('<', '') 373 | LOGGER.error(err) 374 | if "File not found" in str(err): 375 | token_service = self.alt_authorize() 376 | if token_service is not None: 377 | self.__service = token_service 378 | return self.count(link) 379 | msg = "No such file exists" 380 | else: 381 | msg = str(err) 382 | LOGGER.error(f"{msg}") 383 | return msg, button 384 | 385 | def gDrive_file(self, filee): 386 | size = int(filee.get('size', 0)) 387 | self.total_bytes += size 388 | 389 | def gDrive_directory(self, drive_folder): 390 | files = self.getFilesByFolderId(drive_folder['id']) 391 | if len(files) == 0: 392 | return 393 | for filee in files: 394 | shortcut_details = filee.get('shortcutDetails') 395 | if shortcut_details is not None: 396 | mime_type = shortcut_details['targetMimeType'] 397 | file_id = shortcut_details['targetId'] 398 | filee = self.getFileMetadata(file_id) 399 | else: 400 | mime_type = filee.get('mimeType') 401 | if mime_type == self.__G_DRIVE_DIR_MIME_TYPE: 402 | self.total_folders += 1 403 | self.gDrive_directory(filee) 404 | else: 405 | self.total_files += 1 406 | self.gDrive_file(filee) 407 | 408 | def drive_query(self, parent_id, fileName): 409 | fileName = fileName.replace("'","\\'").replace('"','\\"') 410 | gquery = " and ".join([f"name contains '{x}'" for x in fileName.split()]) 411 | query = f"'{parent_id}' in parents and ({gquery})" 412 | return ( 413 | self.__service.files() 414 | .list( 415 | supportsTeamDrives=True, 416 | includeTeamDriveItems=True, 417 | q=query, 418 | spaces='drive', 419 | pageSize=200, 420 | fields='files(id, name, mimeType, size)', 421 | orderBy='modifiedTime desc', 422 | ) 423 | .execute()["files"] 424 | ) 425 | 426 | def drive_list(self, fileName): 427 | data = [] 428 | for _, parent_id in enumerate(DRIVE_ID, start=-1): 429 | response = self.drive_query(parent_id, fileName) 430 | for file in response: 431 | if file['mimeType'] == "application/vnd.google-apps.folder": 432 | data.append( 433 | { 434 | "type": "folder", 435 | "name": file['name'], 436 | "size": "None", 437 | "mimeType": "Folder", 438 | "drive_url": self.__G_DRIVE_DIR_BASE_DOWNLOAD_URL.format(file['id']) 439 | } 440 | ) 441 | else: 442 | data.append( 443 | { 444 | "type": "file", 445 | "name": file['name'], 446 | "size": get_readable_file_size(file.get('size')), 447 | "mimeType": file["mimeType"], 448 | "drive_url": self.__G_DRIVE_BASE_DOWNLOAD_URL.format(file['id']) 449 | } 450 | ) 451 | # if len(data) == 0: 452 | # return {"error": "Found Literally Nothing"} 453 | return data 454 | 455 | drive = GoogleDriveHelper() 456 | -------------------------------------------------------------------------------- /bot/modules/__init__.py: -------------------------------------------------------------------------------- 1 | # From https://github.com/Dank-del/EsseX/blob/master/thebot/modules/__init__.py 2 | from glob import glob 3 | from os.path import dirname, basename, isfile 4 | 5 | '''Methods Directory.''' 6 | 7 | def __list_all_modules(): 8 | # This generates a list of modules in this folder for the * in __main__ to work. 9 | mod_paths = glob(f'{dirname(__file__)}/*.py') 10 | return [ 11 | basename(f)[:-3] for f in mod_paths if isfile(f) 12 | and f.endswith(".py") 13 | and not f.endswith('__init__.py') 14 | ] 15 | 16 | 17 | ALL_MODULES = sorted(__list_all_modules()) 18 | __all__ = ALL_MODULES + ["ALL_MODULES"] -------------------------------------------------------------------------------- /bot/modules/auth.py: -------------------------------------------------------------------------------- 1 | from bot.utils import capture_error, add_auth, rmv_auth, auth_chat, command 2 | from bot import app, OWNER_ID, LOGGER 3 | 4 | @app.on_message(command(['auth', 'unauth', 'chat'], sudo=True)) 5 | @capture_error 6 | async def auth_chat(_, message): 7 | ALLOWED_CHAT = await auth_chat() 8 | target = str(message.command[0]).split("@", maxsplit=1)[0] 9 | msg = await message.reply_text('`Processing....`', quote=True) 10 | if not message.reply_to_message: 11 | try: 12 | ids = int(message.text.split(None, 1)[1]) 13 | except IndexError: 14 | ids = message.chat.id 15 | except ValueError: 16 | return await msg.edit('`Send Valid Chat ID !`') 17 | else: 18 | if message.reply_to_message.from_user: 19 | ids = message.reply_to_message.from_user.id 20 | else: 21 | return await msg.delete() 22 | 23 | if 'chat' in target: 24 | txt = '' 25 | for chat in ALLOWED_CHAT: 26 | txt += f'➤ `{chat}`\n' 27 | check = bool(message.chat.id in ALLOWED_CHAT) 28 | return await msg.edit(f'**[ Authorized Chat ID ({len(ALLOWED_CHAT)}) ]**\n\n' + txt + f'\n**Is Authorized Chat :** `{check}`') 29 | 30 | if 'un' not in target: 31 | if ids in ALLOWED_CHAT: 32 | return await msg.edit('`This Chat ID Already In Allowed Chat !`') 33 | else: 34 | LOGGER.info(f"Auth : {ids}") 35 | await add_auth(ids) 36 | return await msg.edit('`Success Add This Chat ID In Allowed Chat For Next 24 Hours !`') 37 | else: 38 | if ids not in ALLOWED_CHAT: 39 | return await msg.edit('`This Chat ID Not In Allowed Chat !`') 40 | else: 41 | await rmv_auth(ids) 42 | LOGGER.info(f"Unauth : {ids}") 43 | return await msg.edit('`Success Remove This Chat ID From Allowed Chat !`') -------------------------------------------------------------------------------- /bot/modules/clone_and_count.py: -------------------------------------------------------------------------------- 1 | from bot.drive import GoogleDriveHelper 2 | from bot.utils import ( 3 | new_thread, capture_error, sendMessage, command, 4 | editMessage, gdtot, appdrive, is_supported, FSubs 5 | ) 6 | from bot import app, LOGGER 7 | 8 | cmds = ['clone', 'count'] 9 | 10 | @app.on_message(command(cmds, allow_chat=True)) 11 | @capture_error 12 | @new_thread 13 | async def clone(_, message): 14 | await FSubs(message) 15 | args = message.text.split(" ", maxsplit=1) 16 | link = args[1] if len(args) > 1 else '' 17 | user_id = message.from_user.id if message.from_user else message.sender_chat.id 18 | 19 | msg = await sendMessage(message, f"Processing: {link}") 20 | if link == '': 21 | return await editMessage(msg, f"/{message.command[0]} URL") 22 | 23 | deletes = False # Seems stupid lmao 24 | LOGGER.info(f'User: {user_id} : {link}') 25 | try: 26 | check, types = is_supported(link) 27 | if not check: 28 | return await editMessage(msg, "`Link Not Supported`") 29 | if 'new.gdtot' in types: 30 | deletes = True 31 | link = gdtot(link) 32 | elif ('appdrive' or 'driveapp') in link: 33 | apdict = appdrive(link) 34 | link = apdict.get('gdrive_link') 35 | if apdict.get('link_type') == 'login': 36 | deletes = True 37 | except Exception as e: 38 | LOGGER.error(f"ERROR - {user_id} - {link} : {e}") 39 | return await editMessage(msg, str(e)) 40 | 41 | await editMessage(msg, f"**Cloning :** `{link}`") 42 | LOGGER.info(f"Cloning - {user_id} - : {link}") 43 | target = str(message.command[0]).split("@")[0] 44 | try: 45 | gd = GoogleDriveHelper() 46 | if cmds[1] in target: 47 | result, button = gd.count(link) 48 | else: 49 | result, button = gd.clone(link) 50 | if deletes: 51 | LOGGER.info(f"Deleting: {link}") 52 | gd.deleteFile(link) 53 | return await editMessage(msg, result, button) 54 | except Exception as e: 55 | if deletes: 56 | LOGGER.info(f"Deleting: {link}") 57 | gd.deleteFile(link) 58 | return await editMessage(msg, str(e)) 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /bot/modules/search.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from pyrogram.errors.exceptions.bad_request_400 import MessageEmpty, MessageNotModified 4 | 5 | from bot.drive import GoogleDriveHelper, drive 6 | from bot.utils import ( 7 | ikb, FSubs, capture_error, command, 8 | get_readable_time, sendMessage, editMessage 9 | ) 10 | from bot import app, RESULTS_COUNT, ALLOWED_CHAT 11 | 12 | i = 0 13 | ii = 0 14 | m = None 15 | keyboard = None 16 | data = None 17 | 18 | @app.on_message(command("search", allow_chat=True)) 19 | @capture_error 20 | async def search(_, message): 21 | global i, m, data 22 | await FSubs(message) 23 | start = time.time() # soon 24 | if len(message.command) < 2: 25 | await sendMessage(message, '/seach Filename') 26 | return 27 | query = message.text.split(' ',maxsplit=1)[1] 28 | m = await sendMessage(message, "**Searching....**") 29 | drive = GoogleDriveHelper() 30 | data = drive.drive_list(query) 31 | 32 | results = len(data) 33 | i = 0 34 | i += RESULTS_COUNT 35 | 36 | if results == 0: 37 | await editMessage(message, "Found Literally Nothing.") 38 | return 39 | 40 | text = f"**Total Results:** __{results}__ **in {get_readable_time(time.time() - start)}\n" 41 | for count in range(min(i, results)): 42 | if data[count]['type'] == "file": 43 | text += f"\n📄 **[{data[count]['name']} ({data[count]['size']})]({data[count]['drive_url']})**" 44 | else: 45 | text += f"\n📂 **[{data[count]['name']}]({data[count]['drive_url']})**" 46 | if len(data) > RESULTS_COUNT: 47 | button = ikb({"<< Previous": "previous", "Next >>": "next"}) 48 | else: 49 | button = None 50 | try: 51 | await editMessage(message, text, button) 52 | except (MessageEmpty, MessageNotModified): 53 | pass 54 | 55 | 56 | @app.on_callback_query(filters.regex("previous")) 57 | async def previous_callbacc(_, query): 58 | global i, ii, m, data 59 | if i < RESULTS_COUNT: 60 | await query.answer( 61 | "Already at 1st page, Can't go back.", 62 | show_alert=True 63 | ) 64 | return 65 | ii -= RESULTS_COUNT 66 | i -= RESULTS_COUNT 67 | text = "" 68 | 69 | for count in range(ii, i): 70 | try: 71 | if data[count]['type'] == "file": 72 | text += f"\n📄 **[{data[count]['name']} ({data[count]['size']})]({data[count]['drive_url']})**" 73 | else: 74 | text += f"\n📂 **[{data[count]['name']}]({data[count]['drive_url']})**" 75 | except IndexError: 76 | continue 77 | 78 | button = ikb({"<< Previous": "previous", "Next >>": "next"}) 79 | try: 80 | await editMessage(query.message, text, button) 81 | except (MessageEmpty, MessageNotModified): 82 | pass 83 | 84 | 85 | @app.on_callback_query(filters.regex("next")) 86 | async def next_callbacc(_, query): 87 | global i, ii, m, data 88 | ii = i 89 | i += RESULTS_COUNT 90 | text = "" 91 | 92 | for count in range(ii, i): 93 | try: 94 | if data[count]['type'] == "file": 95 | text += f"\n📄 **[{data[count]['name']} ({data[count]['size']})]({data[count]['drive_url']})**" 96 | else: 97 | text += f"\n📂 **[{data[count]['name']}]({data[count]['drive_url']})**" 98 | except IndexError: 99 | continue 100 | 101 | button = ikb({"<< Previous": "previous", "Next >>": "next"}) 102 | try: 103 | await editMessage(query.message, text, button) 104 | except (MessageEmpty, MessageNotModified): 105 | pass -------------------------------------------------------------------------------- /bot/modules/start.py: -------------------------------------------------------------------------------- 1 | from bot.utils import capture_error, sendMessage, FSubs, command 2 | from bot import app 3 | 4 | @app.on_message(command("start")) 5 | @capture_error 6 | async def start_command(_, message): 7 | await FSubs(message) 8 | await sendMessage(message, "What did you expect to happen? Try /help") 9 | 10 | 11 | @app.on_message(command("help", allow_chat=True)) 12 | @capture_error 13 | async def help_command(_, message): 14 | await FSubs(message) 15 | await sendMessage(message, "/search [Query]") -------------------------------------------------------------------------------- /bot/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .database import * 2 | from .errors import capture_error 3 | from .filters import is_authorize, command 4 | from .formatter import get_readable_file_size, get_readable_time, is_supported, new_thread 5 | from .ikb import keyboard, ikb 6 | from .message import fsub, sendMessage, editMessage 7 | from .parser import gdtot, appdrive 8 | -------------------------------------------------------------------------------- /bot/utils/database.py: -------------------------------------------------------------------------------- 1 | from bot import db 2 | 3 | authdb = db.auths 4 | folderdb = db.folders 5 | searchdb = db.search 6 | restartdb = db.restart 7 | # Later 8 | 9 | async def start_restart(chat_id: int, message_id: int): 10 | await restartdb.update_one( 11 | {"something": "something"}, 12 | { 13 | "$set": { 14 | "chat_id": chat_id, 15 | "message_id": message_id, 16 | } 17 | }, 18 | upsert=True, 19 | ) 20 | 21 | 22 | async def clean_restart() -> dict: 23 | data = await restartdb.find_one({"something": "something"}) 24 | if not data: 25 | return {} 26 | await restartdb.delete_one({"something": "something"}) 27 | return { 28 | "chat_id": data["chat_id"], 29 | "message_id": data["message_id"], 30 | } 31 | 32 | 33 | async def auth_chat() -> list: 34 | auths = await authdb.find_one({"auth": "auth"}) 35 | if not auths: 36 | return [] 37 | return auths["authorize"] 38 | 39 | 40 | async def add_auth(chat_id: int) -> bool: 41 | auths = await auth_chat() 42 | auths.append(chat_id) 43 | await authdb.update_one( 44 | {"auth": "auth"}, {"$set": {"authorize": auths}}, upsert=True 45 | ) 46 | return True 47 | 48 | 49 | async def rmv_auth(user_id: int) -> bool: 50 | auths = await auth_chat() 51 | auths.remove(user_id) 52 | await authdb.update_one( 53 | {"auth": "auth"}, {"$set": {"authorize": auths}}, upsert=True 54 | ) 55 | return 56 | 57 | # Todo list 58 | # DB for drive id (Search) dict(list(dict)) ? 59 | # DB for dest id, max 10 (Clone) -------------------------------------------------------------------------------- /bot/utils/errors.py: -------------------------------------------------------------------------------- 1 | """ WRITTEN BY @pokurt, https://github.com/pokurt""" 2 | from sys import exc_info 3 | from traceback import format_exception 4 | from time import sleep 5 | from functools import wraps 6 | 7 | from pyrogram.errors import FloodWait 8 | from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden 9 | from bot import app, LOG_CHAT 10 | 11 | 12 | def split_limits(text): 13 | if len(text) < 2048: 14 | return [text] 15 | 16 | lines = text.splitlines(True) 17 | small_msg = "" 18 | result = [] 19 | for line in lines: 20 | if len(small_msg) + len(line) < 2048: 21 | small_msg += line 22 | else: 23 | result.append(small_msg) 24 | small_msg = line 25 | result.append(small_msg) 26 | 27 | return result 28 | 29 | 30 | def capture_error(func): 31 | @wraps(func) 32 | async def capture(client, message, *args, **kwargs): 33 | try: 34 | return await func(client, message, *args, **kwargs) 35 | except ChatWriteForbidden: 36 | await client.leave_chat(message.chat.id) 37 | return 38 | except Exception as err: 39 | exc_type, exc_obj, exc_tb = exc_info() 40 | errors = format_exception( 41 | etype=exc_type, 42 | value=exc_obj, 43 | tb=exc_tb, 44 | ) 45 | # time = NOW.strftime("%d/%m/%Y %H:%M:%S") 46 | if not message.sender_chat: 47 | error_feedback = split_limits( 48 | "**JUSIDAMA ERROR || {}**\n\n `{}` (`@{}`) | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( 49 | 0 if not message.from_user else message.from_user.first_name, 50 | "" if not message.from_user.username else message.from_user.username, 51 | 0 if not message.from_user else message.from_user.id, 52 | 0 if not message.chat else message.chat.id, 53 | message.text or message.caption, 54 | "".join(errors), 55 | ), 56 | ) 57 | else: 58 | error_feedback = split_limits( 59 | "**JUSIDAMA ERROR || {}**\n\n `{}` (`@{}`) | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( 60 | 0 if not message.sender_chat else message.sender_chat.title, 61 | "" if not message.sender_chat.username else message.sender_chat.username, 62 | 0 if not message.sender_chat else message.sender_chat.id, 63 | 0 if not message.chat else message.chat.id, 64 | message.text or message.caption, 65 | "".join(errors), 66 | ), 67 | ) 68 | for x in error_feedback: 69 | try: 70 | await app.send_message(LOG_CHAT ,x) 71 | except FloodWait as e: 72 | sleep(e.x) 73 | raise err 74 | 75 | return capture -------------------------------------------------------------------------------- /bot/utils/filters.py: -------------------------------------------------------------------------------- 1 | from pyrogram import filters 2 | from .database import * 3 | from bot import app, OWNER_ID, ALLOWED_CHAT 4 | 5 | async def is_authorize(chat_id: int) -> bool: 6 | chats = await auth_chat() 7 | return bool(chat_id in chats) 8 | 9 | def command(command, sudo: bool = False, allow_chat: bool = False): 10 | BOT_USERNAME = (app.get_me()).username 11 | if isinstance(command, list): 12 | cmds = [] 13 | for i in command: 14 | cmds.extend([i, i + '@' + BOT_USERNAME]) 15 | command = filters.command(cmds) 16 | else: 17 | command = filters.command([command, command + '@' + BOT_USERNAME]) 18 | if sudo: 19 | command = command & filters.user(OWNER_ID) 20 | elif allow_chat: 21 | command = command & filters.chat(ALLOWED_CHAT) 22 | return command & ~filters.edited 23 | -------------------------------------------------------------------------------- /bot/utils/formatter.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from urllib.parse import urlparse 3 | 4 | def is_supported(url: str): 5 | link = urlparse(url).netloc 6 | check = any(i in link for i in ["drive.google.com", "new.gdtot", "appdrive", "driveapp"]) 7 | return check, link 8 | 9 | def get_readable_time(seconds: int) -> str: 10 | count = 0 11 | ping_time = "" 12 | time_list = [] 13 | time_suffix_list = ["s", "m", "h", "days"] 14 | while count < 4: 15 | count += 1 16 | remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) 17 | if seconds == 0 and remainder == 0: 18 | break 19 | time_list.append(int(result)) 20 | seconds = int(remainder) 21 | 22 | for i, _ in enumerate(time_list): 23 | time_list[i] = str(time_list[i]) + time_suffix_list[i] 24 | 25 | if len(time_list) == 4: 26 | ping_time += f'{time_list.pop()}, ' 27 | 28 | time_list.reverse() 29 | ping_time += ":".join(time_list) 30 | return ping_time 31 | 32 | def get_readable_file_size(size_in_bytes) -> str: 33 | SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] 34 | if size_in_bytes is None: 35 | return '0B' 36 | index = 0 37 | size_in_bytes = int(size_in_bytes) 38 | while size_in_bytes >= 1024: 39 | size_in_bytes /= 1024 40 | index += 1 41 | try: 42 | return f'{round(size_in_bytes, 2)}{SIZE_UNITS[index]}' 43 | except IndexError: 44 | return 'File too large' 45 | 46 | def new_thread(fn): 47 | def wrapper(*args, **kwargs): 48 | thread = Thread(target=fn, args=args, kwargs=kwargs) 49 | thread.start() 50 | return thread 51 | return wrapper -------------------------------------------------------------------------------- /bot/utils/ikb.py: -------------------------------------------------------------------------------- 1 | # From https://github.com/TheHamkerCat/WilliamButcherBot/blob/dev/wbb/core/keyboard.py 2 | 3 | from re import findall 4 | from pykeyboard import InlineKeyboard 5 | from pyrogram.types import InlineKeyboardButton as Ikb 6 | 7 | 8 | def get_urls_from_text(text: str) -> bool: 9 | regex = r"""(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-] 10 | [.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|( 11 | \([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\ 12 | ()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".strip() 13 | return [x[0] for x in findall(regex, str(text))] 14 | 15 | def keyboard(buttons_list, row_width: int = 2): 16 | """ 17 | Buttons builder, pass buttons in a list and it will 18 | return pyrogram.types.IKB object 19 | Ex: keyboard([["click here", "https://google.com"]]) 20 | if theres, a url, it will make url button, else callback button 21 | """ 22 | buttons = InlineKeyboard(row_width=row_width) 23 | data = [ 24 | ( 25 | Ikb(text=str(i[0]), callback_data=str(i[1])) 26 | if not get_urls_from_text(i[1]) 27 | else Ikb(text=str(i[0]), url=str(i[1])) 28 | ) 29 | for i in buttons_list 30 | ] 31 | buttons.add(*data) 32 | return buttons 33 | 34 | 35 | def ikb(data: dict, row_width: int = 2): 36 | """ 37 | Converts a dict to pyrogram buttons 38 | Ex: dict_to_keyboard({"click here": "this is callback data"}) 39 | """ 40 | return keyboard(data.items(), row_width=row_width) -------------------------------------------------------------------------------- /bot/utils/message.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from pyrogram.types import Message 4 | from pyrogram.errors.exceptions.bad_request_400 import ChatAdminRequired, UserNotParticipant 5 | from pyrogram.errors import FloodWait, ChatWriteForbidden 6 | 7 | from bot.utils import ikb, is_authorize 8 | from bot import app, MUST_JOIN 9 | 10 | async def sendMessage(message: Message, text: str, button=None): 11 | try: 12 | return await message.reply_text( 13 | text, quote=True, 14 | disable_web_page_preview=True, disable_notification=True, 15 | reply_markup=button, 16 | ) 17 | except FloodWait as e: 18 | await sleep(e.x) 19 | return await sendMessage(message, text, button) 20 | 21 | async def editMessage(message: Message, text, button=None): 22 | try: 23 | return await message.edit( 24 | text, disable_web_page_preview=True, 25 | disable_notification=True, reply_markup=button 26 | ) 27 | except FloodWait as e: 28 | sleep(e.x) 29 | return editMessage(message, text, button) 30 | 31 | async def FSubs(message: Message): 32 | await check_auth(message) 33 | try: 34 | user = await app.get_chat_member(MUST_JOIN, message.from_user.id) 35 | if user.status == "kicked": 36 | return await sendMessage(message, "Sorry Sir, You are Banned to use me.") 37 | except UserNotParticipant: 38 | if MUST_JOIN.isalpha(): 39 | link = f'https://t.me/{MUST_JOIN}' 40 | else: 41 | link = (await app.get_chat(MUST_JOIN)).invite_link 42 | try: 43 | return await sendMessage(message, f"You must join [@Jusidama]({link}) to use search Gdrive. After joining try again !", ikb({"✨ Join Channel ✨": link})) 44 | except ChatWriteForbidden: 45 | pass 46 | except ChatAdminRequired: 47 | return await sendMessage(message, f"I'm not admin in the MUST_JOIN chat : {MUST_JOIN} !") 48 | except Exception as e: 49 | return await sendMessage(message, f"**ERROR:** `{e}`") 50 | 51 | async def check_auth(message: Message): 52 | reply = message.reply_to_message 53 | if reply: 54 | chat_id = reply.from_user.id if reply.from_user else reply.sender_chat.id 55 | else: 56 | chat_id = message.from_user.id if message.from_user else message.sender_chat.id 57 | 58 | check = await is_authorize(chat_id) 59 | if not check: 60 | return await sendMessage(message, "You are not authorize to use this bot !") 61 | else: 62 | pass -------------------------------------------------------------------------------- /bot/utils/parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import base64 3 | import requests 4 | 5 | from lxml import etree 6 | from urllib.parse import urlparse 7 | 8 | from bot import APPDRIVE_EMAIL, APPDRIVE_PASS, GDTOT_CRYPT 9 | 10 | account = { 11 | 'email': APPDRIVE_EMAIL, 12 | 'passwd': APPDRIVE_PASS 13 | } 14 | 15 | def account_login(client, url, email, password): 16 | data = { 17 | 'email': email, 18 | 'password': password 19 | } 20 | client.post(f'https://{urlparse(url).netloc}/login', data=data) 21 | 22 | def gen_payload(data, boundary=f'{"-"*6}_'): 23 | data_string = '' 24 | for item in data: 25 | data_string += f'{boundary}\r\n' 26 | data_string += f'Content-Disposition: form-data; name="{item}"\r\n\r\n{data[item]}\r\n' 27 | data_string += f'{boundary}--\r\n' 28 | return data_string 29 | 30 | def parse_info(data): 31 | info = re.findall(r'>(.*?)<\/li>', data) 32 | info_parsed = {} 33 | for item in info: 34 | kv = [s.strip() for s in item.split(':', maxsplit=1)] 35 | info_parsed[kv[0].lower()] = kv[1] 36 | return info_parsed 37 | 38 | def appdrive(url: str) -> str: 39 | client = requests.Session() 40 | client.headers.update({ 41 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36" 42 | }) 43 | account_login(client, url, account['email'], account['passwd']) 44 | res = client.get(url) 45 | key = re.findall(r'"key",\s+"(.*?)"', res.text)[0] 46 | ddl_btn = etree.HTML(res.content).xpath("//button[@id='drc']") 47 | info_parsed = parse_info(res.text) 48 | info_parsed['error'] = False 49 | info_parsed['link_type'] = 'login' # direct/login 50 | headers = { 51 | "Content-Type": f"multipart/form-data; boundary={'-'*4}_", 52 | } 53 | data = { 54 | 'type': 1, 55 | 'key': key, 56 | 'action': 'original' 57 | } 58 | if len(ddl_btn): 59 | info_parsed['link_type'] = 'direct' 60 | data['action'] = 'direct' 61 | while data['type'] <= 3: 62 | try: 63 | response = client.post(url, data=gen_payload(data), headers=headers).json() 64 | break 65 | except: data['type'] += 1 66 | if 'url' in response: 67 | info_parsed['gdrive_link'] = response['url'] 68 | elif 'error' in response and response['error']: 69 | info_parsed['error'] = True 70 | info_parsed['error_message'] = response['message'] 71 | if urlparse(url).netloc == 'driveapp.in' and not info_parsed['error']: 72 | res = client.get(info_parsed['gdrive_link']) 73 | drive_link = etree.HTML(res.content).xpath("//a[contains(@class,'btn')]/@href")[0] 74 | info_parsed['gdrive_link'] = drive_link 75 | if not info_parsed['error']: 76 | return info_parsed 77 | else: 78 | raise Exception(info_parsed['error_message']) 79 | 80 | def gdtot(url: str) -> str: 81 | client = requests.Session() 82 | client.cookies.update({'crypt': GDTOT_CRYPT}) 83 | res = client.get(url) 84 | res = client.get(f"https://new.gdtot.top/dld?id={url.split('/')[-1]}") 85 | matches = re.findall(r'gd=(.*?)&', res.text) 86 | try: 87 | decoded_id = base64.b64decode(str(matches[0])).decode('utf-8') 88 | except: 89 | raise Exception("Unable to parse link") 90 | return f'https://drive.google.com/open?id={decoded_id}' -------------------------------------------------------------------------------- /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 ( 13 | (credentials is None or not credentials.valid) 14 | and credentials 15 | and credentials.expired 16 | and credentials.refresh_token 17 | ): 18 | credentials.refresh(Request()) 19 | else: 20 | flow = InstalledAppFlow.from_client_secrets_file( 21 | 'credentials.json', __OAUTH_SCOPE) 22 | credentials = flow.run_console(port=0) 23 | 24 | # Save the credentials for the next run 25 | with open(__G_DRIVE_TOKEN_FILE, 'wb') as token: 26 | pickle.dump(credentials, token) 27 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | run: 5 | worker: python3 -m bot 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Telegram Depedencies 2 | pyrogram 3 | TgCrypto 4 | pykeyboard 5 | pyromod 6 | 7 | # Google Depedencies 8 | google-api-python-client>=1.7.11,<1.7.20 9 | google-auth-httplib2>=0.0.3,<0.1.0 10 | google-auth-oauthlib>=0.4.1,<0.10.0 11 | tenacity 12 | 13 | # MongoDB Depedencies 14 | dnspython 15 | motor 16 | 17 | # Other Depedencies 18 | lxml 19 | asyncio 20 | requests 21 | pybase64 22 | python-dotenv 23 | urllib3 -------------------------------------------------------------------------------- /sample_config.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from dotenv import load_dotenv 3 | 4 | load_dotenv('config.env', override=True) 5 | 6 | HEROKU = bool( 7 | environ.get('DYNO') 8 | ) 9 | 10 | if HEROKU: 11 | BOT_TOKEN = environ.get('BOT_TOKEN') 12 | RESULTS_COUNT = int(environ.get('RESULTS_COUNT', 4)) # NOTE Number of results to show, 4 is better 13 | SUDO_CHATS_ID = list(str(environ.get('SUDO_CHATS_ID')).split(' ')) 14 | CHANNEL = environ.get('CHANNEL', "@Jusidama") 15 | MONGO_URL = environ.get('MONGO_URL', None) 16 | LOG_CHAT = int(environ.get('LOG_CHAT')) 17 | API_ID = int(environ.get('API_ID')) 18 | API_HASH = environ.get('API_HASH') 19 | APPDRIVE_EMAIL = environ.get('APPDRIVE_EMAIL') 20 | APPDRIVE_PASS = environ.get('APPDRIVE_PASS') 21 | GDTOT_CRYPT = environ.get('GDTOT_CRYPT') 22 | IS_TEAM_DRIVE = bool(str(environ.get('IS_TEAM_DRIVE')).lower() == 'true') 23 | USE_SERVICE_ACCOUNTS = bool(str(environ.get('USE_SERVICE_ACCOUNTS')).lower() == 'true') 24 | FOLDER_ID = environ.get('FOLDER_ID') 25 | OWNER_ID = int(environ.get('OWNER_ID')) 26 | else: 27 | BOT_TOKEN = "1629027959:AAEaTw4s2qaAL3mYP3fQRnE" 28 | RESULTS_COUNT = 4 # NOTE Number of results to show, 4 is better 29 | SUDO_CHATS_ID = [-1001485393652, -1005456463651] 30 | CHANNEL = "@Jusidama" 31 | LOG_CHAT = -1001485393652 32 | API_ID = 6 33 | API_HASH = "eb06d4abfb49dc3eeb1aeb98ae0f581e" 34 | MONGO_URL = "mongodb+srv://username:password@cluster0.ksiis.mongodb.net/YourDataBaseName?retryWrites=true&w=majority" 35 | APPDRIVE_EMAIL = "" 36 | APPDRIVE_PASS = "" 37 | GDTOT_CRYPT = "" 38 | IS_TEAM_DRIVE = False 39 | FOLDER_ID = "" 40 | USE_SERVICE_ACCOUNTS = False 41 | OWNER_ID = int(environ.get('OWNER_ID')) 42 | 43 | # Later in mongo 44 | 45 | DRIVE_NAME = [ 46 | "Root", # folder 1 name 47 | "Cartoon", # folder 2 name 48 | "Course", # folder 3 name 49 | "Movies", # .... 50 | "Series", # ...... 51 | "Others" # and soo onnnn folder n names 52 | ] 53 | 54 | DRIVE_ID = [ 55 | "1B9A3QqQqF31IuW2om3Qhr-wkiVLloxw8", # folder 1 id 56 | "12wNJTjNnR-CNBOTnLHqe-1vqFvCRLecn", # folder 2 id 57 | "11nZcObsJJHojHYg43dBS0_eVvJrSD7Nf", # and so onn... folder n id 58 | "10_hTMK8HE8k144wOTth_3x1hC2kZL-LR", 59 | "1-oTctBpyFcydDNiptLL09Enwte0dClCq", 60 | "1B9A3QqQqF31IuW2om3Qhr-wkiVLloxw8" 61 | ] 62 | 63 | INDEX_URL = [ 64 | "https://dl.null.tech/0:", # folder 1 index link 65 | "https://dl.null.tech/0:/Cartoon", # folder 2 index link 66 | "https://dl.null.tech/0:/Course", # and soo on folder n link 67 | "https://dl.null.tech/0:/MOVIES", 68 | "https://dl.null.tech/0:/Series", 69 | "https://dl.null.tech/0:/Roms" 70 | ] 71 | --------------------------------------------------------------------------------