├── .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 |
--------------------------------------------------------------------------------