SimpleGram 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - name: Set up Python
13
+ uses: actions/setup-python@v5
14
+ with:
15
+ python-version: '3.10'
16
+ - name: Install build tools
17
+ run: |
18
+ pip install build twine
19
+ - name: Build package
20
+ run: python -m build
21
+ - name: Publish to PyPI
22
+ env:
23
+ TWINE_USERNAME: __token__
24
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
25
+ run: twine upload dist/*
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: SimpleGram
3
+ Version: 0.1.0
4
+ Summary: A simple Python library that prints 'Hola Mundo'
5
+ Home-page: https://github.com/ale-techdev/SimpleGram
6
+ Author: TechDev
7
+ Author-email: TechDev <alejandro.techdev@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/ale-techdev/SimpleGram
10
+ Project-URL: Repository, https://github.com/ale-techdev/SimpleGram
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Requires-Python: >=3.7
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: telethon
24
+ Dynamic: author
25
+ Dynamic: home-page
26
+ Dynamic: requires-python
27
+
28
+ Para instalar la plantilla de SimpleGram en la carpeta actual usa:
29
+ `simplegram install`
30
+
31
+ Para iniciar el bot de telegram usa:
32
+ `simplegram start`
33
+
34
+ Con nombre personalizado:
35
+ `simplegram -n "SimpleGram" start`
@@ -0,0 +1,8 @@
1
+ Para instalar la plantilla de SimpleGram en la carpeta actual usa:
2
+ `simplegram install`
3
+
4
+ Para iniciar el bot de telegram usa:
5
+ `simplegram start`
6
+
7
+ Con nombre personalizado:
8
+ `simplegram -n "SimpleGram" start`
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: SimpleGram
3
+ Version: 0.1.0
4
+ Summary: A simple Python library that prints 'Hola Mundo'
5
+ Home-page: https://github.com/ale-techdev/SimpleGram
6
+ Author: TechDev
7
+ Author-email: TechDev <alejandro.techdev@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/ale-techdev/SimpleGram
10
+ Project-URL: Repository, https://github.com/ale-techdev/SimpleGram
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Requires-Python: >=3.7
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: telethon
24
+ Dynamic: author
25
+ Dynamic: home-page
26
+ Dynamic: requires-python
27
+
28
+ Para instalar la plantilla de SimpleGram en la carpeta actual usa:
29
+ `simplegram install`
30
+
31
+ Para iniciar el bot de telegram usa:
32
+ `simplegram start`
33
+
34
+ Con nombre personalizado:
35
+ `simplegram -n "SimpleGram" start`
@@ -0,0 +1,19 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ .github/workflows/publish.yml
5
+ SimpleGram.egg-info/PKG-INFO
6
+ SimpleGram.egg-info/SOURCES.txt
7
+ SimpleGram.egg-info/dependency_links.txt
8
+ SimpleGram.egg-info/entry_points.txt
9
+ SimpleGram.egg-info/requires.txt
10
+ SimpleGram.egg-info/top_level.txt
11
+ simplegram/__init__.py
12
+ simplegram/template/config.py
13
+ simplegram/template/commands/backup.py
14
+ simplegram/template/commands/restore.py
15
+ simplegram/template/commands/search.py
16
+ simplegram/template/commands/start.py
17
+ simplegram/utils/.none
18
+ simplegram/utils/FastTelethon.py
19
+ simplegram/utils/system.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ simplegram = simplegram.__init__:main
@@ -0,0 +1 @@
1
+ telethon
@@ -0,0 +1 @@
1
+ simplegram
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "SimpleGram"
7
+ version = "0.1.0"
8
+ authors = [
9
+ {name = "TechDev", email = "alejandro.techdev@gmail.com"},
10
+ ]
11
+ description = "A simple Python library that prints 'Hola Mundo'"
12
+ readme = "README.md"
13
+ license = {text = "MIT"}
14
+ requires-python = ">=3.7"
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.7",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ ]
27
+ dependencies = [
28
+ "telethon"
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/ale-techdev/SimpleGram"
33
+ Repository = "https://github.com/ale-techdev/SimpleGram"
34
+
35
+ [project.scripts]
36
+ simplegram = "simplegram.__init__:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="SimpleGram",
8
+ version="0.1.0",
9
+ author="TechDev",
10
+ author_email="alejandro.techdev@gmail.com",
11
+ description="FrameWork basado en Telethon'",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ url="https://github.com/ale-techdev/SimpleGram",
15
+ packages=find_packages(),
16
+ classifiers=[
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.7",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ ],
28
+ python_requires=">=3.7",
29
+ entry_points={
30
+ "console_scripts": [
31
+ "pylibtd=pylibtd.__init__:main",
32
+ ],
33
+ },
34
+ )
@@ -0,0 +1,60 @@
1
+ from telethon import TelegramClient
2
+ import os, sys, shutil
3
+
4
+ def main():
5
+ cmd = sys.argv[1:]
6
+ def start(vars):
7
+ current_working_dir = os.getcwd()
8
+ if current_working_dir not in sys.path:
9
+ sys.path.insert(0, current_working_dir)
10
+ try:
11
+ import commands
12
+ except:
13
+ print("\n [ ERROR ] No hay comandos establecidos")
14
+ sys.exit()
15
+ try:
16
+ from config import SESSION_NAME, BOT_TOKEN, API_HASH, API_ID
17
+ except:
18
+ print("\n [ ERROR ] No hay archivo de configuración o faltan variables")
19
+ sys.exit()
20
+ from .utils.system import System
21
+ client = TelegramClient(SESSION_NAME, api_id=API_ID, api_hash=API_HASH).start(bot_token=BOT_TOKEN)
22
+ system = System(vars["-n"], client)
23
+ system.setCommands(commands)
24
+ print(f"\n [ INFO ] {vars['-n']} INICIADO")
25
+ client.run_until_disconnected()
26
+ def install(vars):
27
+ pathcurrent = os.path.dirname(os.path.abspath(__file__))
28
+ origen = pathcurrent+"/template"
29
+ destino = "."
30
+ for item in os.listdir(origen):
31
+ s = os.path.join(origen, item)
32
+ d = os.path.join(destino, item)
33
+ if os.path.isdir(s):
34
+ shutil.copytree(s, d, dirs_exist_ok=True)
35
+ else:
36
+ shutil.copy2(s, d)
37
+ print("\n [ INFO ] PLANTILLA INSTALADA")
38
+ functions = {"start": start, "install": install, "s": start, "i": install}
39
+ skip = False
40
+ index = 0
41
+ vars = {"-n": "SimpleGram"}
42
+ toexecute = []
43
+ for c in cmd:
44
+ if skip:
45
+ index += 1
46
+ skip = False
47
+ continue
48
+ if "-" in c or "--" in c:
49
+ skip = True
50
+ vars[c] = cmd[index+1]
51
+ continue
52
+ for o in functions.keys():
53
+ if c == o:
54
+ toexecute.append(functions[o])
55
+ index += 1
56
+ for exe in toexecute:
57
+ exe(vars)
58
+
59
+ if __name__ == "__main__":
60
+ main()
@@ -0,0 +1,12 @@
1
+ import pickle
2
+
3
+ data = {"label": "Crea un backup del sistema", "level": "admin"}
4
+
5
+ async def newEvent(system, event, parameters):
6
+ system_backup = pickle.dumps({"name": system.name, "users": system.users, "operators": system.operators})
7
+ with open("./system.backup", "wb") as bu:
8
+ bu.write(system_backup)
9
+ await event.reply(
10
+ system.name,
11
+ file="./system.backup"
12
+ )
@@ -0,0 +1,12 @@
1
+ import pickle
2
+
3
+ data = {"label": "Restaura una copia de seguridad", "level": "admin"}
4
+
5
+ async def newFile(system, event, file):
6
+ sender = event.sender
7
+ if file.split('/')[-1].endswith(".backup"):
8
+ system_backup = pickle.loads(open(file, "rb").read())
9
+ system.name = system_backup["name"]
10
+ system.operators = system_backup["operators"]
11
+ system.users = system_backup["users"]
12
+ await event.reply(f"`Backup restaurado`")
@@ -0,0 +1,77 @@
1
+ import os
2
+ from telethon import Button
3
+
4
+ data = {"label": "Buscar un usuario de telegram", "level": "operator"}
5
+
6
+ async def change(system, event, parameters):
7
+ client = system.client
8
+ userID = int(parameters[0])
9
+ state = parameters[1]
10
+ if userID in system.users:
11
+ del system.users[userID]
12
+ elif userID in system.operators:
13
+ del system.operators[userID]
14
+ user = await client.get_entity(int(userID))
15
+ if state == "user":
16
+ system.users[userID] = {"username": user.username, "permission": "user"}
17
+ elif state == "operator":
18
+ system.operators[userID] = {"username": user.username, "permission": "operator"}
19
+ text, photo, buttons = await profile(system, event, userID, user=user)
20
+ msg = await event.get_message()
21
+ await msg.edit(text, file=photo, buttons=buttons)
22
+ try:
23
+ os.unlink(photo)
24
+ except:
25
+ pass
26
+
27
+ async def profile(system, event, search, user=None):
28
+ sender = event.sender
29
+ client = system.client
30
+ if not user:
31
+ user = await client.get_entity(search)
32
+ photo = await client.download_profile_photo(user)
33
+ state = "No"
34
+ buttonsAdmin = [[Button.inline("Otorgar acceso", f"change {user.id} user".encode("utf-8"))]]
35
+ buttonsOperator = [[Button.inline("Otorgar acceso", f"change {user.id} user".encode("utf-8"))]]
36
+ if user.id in system.users:
37
+ state = "Usuario"
38
+ buttonsAdmin = [[Button.inline("Ascender a Operador", f"change {user.id} operator".encode("utf-8"))], [Button.inline("Denegar acceso", f"change {user.id} none".encode("utf-8"))]]
39
+ buttonsOperator = [[Button.inline("Denegar acceso", f"change {user.id} none".encode("utf-8"))]]
40
+ elif user.id in system.operators:
41
+ state = "Operador"
42
+ buttonsAdmin = [[Button.inline("Degradar a Usuario", f"change {user.id} user".encode("utf-8"))], [Button.inline("Denegar acceso", f"change {user.id} none".encode("utf-8"))]]
43
+ buttonsOperator = []
44
+ elif user.id in system.admin:
45
+ state = "Administrador"
46
+ buttonsAdmin = []
47
+ buttonsOperator = []
48
+ permission = system.isMember(sender)["permission"]
49
+ if permission == "admin":
50
+ buttons = buttonsAdmin
51
+ elif permission == "operator":
52
+ buttons = buttonsOperator
53
+ else:
54
+ buttons = []
55
+ return f"""**Nombre:** __{user.first_name} {user.last_name if user.last_name else ""}__\n**ID:** __{user.id}__\n**Miembro:** {state}\n\n@{user.username}""", photo, None if buttons == [] else buttons
56
+
57
+ async def newEvent(system, event, parameters):
58
+ if len(parameters) == 0:
59
+ await event.reply("Falta el parámetro **UserID o @Username**")
60
+ else:
61
+ try:
62
+ search = parameters[0]
63
+ if "@" in search:
64
+ search = search.replace("@", "")
65
+ else:
66
+ search = int(search)
67
+ msg = await event.reply("Espere un momento")
68
+ text, photo, buttons = await profile(system, event, search)
69
+ await msg.edit(text, file=photo, buttons=buttons)
70
+ system.callbacks["change"] = change
71
+ try:
72
+ os.unlink(photo)
73
+ except:
74
+ pass
75
+ except:
76
+ await event.reply("No existe el usuario buscado")
77
+
@@ -0,0 +1,6 @@
1
+ data = {"label": "Comenzar sistema", "level": "user"}
2
+
3
+ async def newEvent(system, event, parameters):
4
+ sender = event.sender
5
+ await event.reply(
6
+ f"**{system.name}**")
@@ -0,0 +1,16 @@
1
+ API_ID = None
2
+ API_HASH = None
3
+ BOT_TOKEN = None
4
+ SESSION_NAME = "SIMPLEGRAM"
5
+
6
+ ACCESS = False
7
+
8
+ MESSAGES = {
9
+ "newAdmin": "Se le ha otorgado el estado de **administrador**",
10
+ "newOperator": "Se le ha otorgado el estado de **operador**",
11
+ "newUser": "Se le ha otorgado el estado de **usuario**",
12
+ "permissionDenied": "Usted no tiene permiso para acceder al sistema",
13
+ "noCommandPermission": "No tiene suficiente **acceso** para usar este comando",
14
+ "noCommandExists": "No existe ese comando"
15
+ }
16
+
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,308 @@
1
+ # copied from https://github.com/tulir/mautrix-telegram/blob/master/mautrix_telegram/util/parallel_file_transfer.py
2
+ # Copyright (C) 2021 Tulir Asokan
3
+ import asyncio
4
+ import hashlib
5
+ import inspect
6
+ import logging
7
+ import math
8
+ import os
9
+ from collections import defaultdict
10
+ from typing import Optional, List, AsyncGenerator, Union, Awaitable, DefaultDict, Tuple, BinaryIO
11
+
12
+ from telethon import utils, helpers, TelegramClient
13
+ from telethon.crypto import AuthKey
14
+ from telethon.network import MTProtoSender
15
+ from telethon.tl.alltlobjects import LAYER
16
+ from telethon.tl.functions import InvokeWithLayerRequest
17
+ from telethon.tl.functions.auth import ExportAuthorizationRequest, ImportAuthorizationRequest
18
+ from telethon.tl.functions.upload import (GetFileRequest, SaveFilePartRequest,
19
+ SaveBigFilePartRequest)
20
+ from telethon.tl.types import (Document, InputFileLocation, InputDocumentFileLocation,
21
+ InputPhotoFileLocation, InputPeerPhotoFileLocation, TypeInputFile,
22
+ InputFileBig, InputFile)
23
+
24
+ try:
25
+ from mautrix.crypto.attachments import async_encrypt_attachment
26
+ except ImportError:
27
+ async_encrypt_attachment = None
28
+
29
+ log: logging.Logger = logging.getLogger("telethon")
30
+
31
+ TypeLocation = Union[Document, InputDocumentFileLocation, InputPeerPhotoFileLocation,
32
+ InputFileLocation, InputPhotoFileLocation]
33
+
34
+
35
+ class DownloadSender:
36
+ client: TelegramClient
37
+ sender: MTProtoSender
38
+ request: GetFileRequest
39
+ remaining: int
40
+ stride: int
41
+
42
+ def __init__(self, client: TelegramClient, sender: MTProtoSender, file: TypeLocation, offset: int, limit: int,
43
+ stride: int, count: int) -> None:
44
+ self.sender = sender
45
+ self.client = client
46
+ self.request = GetFileRequest(file, offset=offset, limit=limit)
47
+ self.stride = stride
48
+ self.remaining = count
49
+
50
+ async def next(self) -> Optional[bytes]:
51
+ if not self.remaining:
52
+ return None
53
+ result = await self.client._call(self.sender, self.request)
54
+ self.remaining -= 1
55
+ self.request.offset += self.stride
56
+ return result.bytes
57
+
58
+ def disconnect(self) -> Awaitable[None]:
59
+ return self.sender.disconnect()
60
+
61
+
62
+ class UploadSender:
63
+ client: TelegramClient
64
+ sender: MTProtoSender
65
+ request: Union[SaveFilePartRequest, SaveBigFilePartRequest]
66
+ part_count: int
67
+ stride: int
68
+ previous: Optional[asyncio.Task]
69
+ loop: asyncio.AbstractEventLoop
70
+
71
+ def __init__(self, client: TelegramClient, sender: MTProtoSender, file_id: int, part_count: int, big: bool,
72
+ index: int,
73
+ stride: int, loop: asyncio.AbstractEventLoop) -> None:
74
+ self.client = client
75
+ self.sender = sender
76
+ self.part_count = part_count
77
+ if big:
78
+ self.request = SaveBigFilePartRequest(file_id, index, part_count, b"")
79
+ else:
80
+ self.request = SaveFilePartRequest(file_id, index, b"")
81
+ self.stride = stride
82
+ self.previous = None
83
+ self.loop = loop
84
+
85
+ async def next(self, data: bytes) -> None:
86
+ if self.previous:
87
+ await self.previous
88
+ self.previous = self.loop.create_task(self._next(data))
89
+
90
+ async def _next(self, data: bytes) -> None:
91
+ self.request.bytes = data
92
+ log.debug(f"Sending file part {self.request.file_part}/{self.part_count}"
93
+ f" with {len(data)} bytes")
94
+ await self.client._call(self.sender, self.request)
95
+ self.request.file_part += self.stride
96
+
97
+ async def disconnect(self) -> None:
98
+ if self.previous:
99
+ await self.previous
100
+ return await self.sender.disconnect()
101
+
102
+
103
+ class ParallelTransferrer:
104
+ client: TelegramClient
105
+ loop: asyncio.AbstractEventLoop
106
+ dc_id: int
107
+ senders: Optional[List[Union[DownloadSender, UploadSender]]]
108
+ auth_key: AuthKey
109
+ upload_ticker: int
110
+
111
+ def __init__(self, client: TelegramClient, dc_id: Optional[int] = None) -> None:
112
+ self.client = client
113
+ self.loop = self.client.loop
114
+ self.dc_id = dc_id or self.client.session.dc_id
115
+ self.auth_key = (None if dc_id and self.client.session.dc_id != dc_id
116
+ else self.client.session.auth_key)
117
+ self.senders = None
118
+ self.upload_ticker = 0
119
+
120
+ async def _cleanup(self) -> None:
121
+ await asyncio.gather(*[sender.disconnect() for sender in self.senders])
122
+ self.senders = None
123
+
124
+ @staticmethod
125
+ def _get_connection_count(file_size: int, max_count: int = 20,
126
+ full_size: int = 100 * 1024 * 1024) -> int:
127
+ if file_size > full_size:
128
+ return max_count
129
+ return math.ceil((file_size / full_size) * max_count)
130
+
131
+ async def _init_download(self, connections: int, file: TypeLocation, part_count: int,
132
+ part_size: int) -> None:
133
+ minimum, remainder = divmod(part_count, connections)
134
+
135
+ def get_part_count() -> int:
136
+ nonlocal remainder
137
+ if remainder > 0:
138
+ remainder -= 1
139
+ return minimum + 1
140
+ return minimum
141
+
142
+ # The first cross-DC sender will export+import the authorization, so we always create it
143
+ # before creating any other senders.
144
+ self.senders = [
145
+ await self._create_download_sender(file, 0, part_size, connections * part_size,
146
+ get_part_count()),
147
+ *await asyncio.gather(
148
+ *[self._create_download_sender(file, i, part_size, connections * part_size,
149
+ get_part_count())
150
+ for i in range(1, connections)])
151
+ ]
152
+
153
+ async def _create_download_sender(self, file: TypeLocation, index: int, part_size: int,
154
+ stride: int,
155
+ part_count: int) -> DownloadSender:
156
+ return DownloadSender(self.client, await self._create_sender(), file, index * part_size, part_size,
157
+ stride, part_count)
158
+
159
+ async def _init_upload(self, connections: int, file_id: int, part_count: int, big: bool
160
+ ) -> None:
161
+ self.senders = [
162
+ await self._create_upload_sender(file_id, part_count, big, 0, connections),
163
+ *await asyncio.gather(
164
+ *[self._create_upload_sender(file_id, part_count, big, i, connections)
165
+ for i in range(1, connections)])
166
+ ]
167
+
168
+ async def _create_upload_sender(self, file_id: int, part_count: int, big: bool, index: int,
169
+ stride: int) -> UploadSender:
170
+ return UploadSender(self.client, await self._create_sender(), file_id, part_count, big, index, stride,
171
+ loop=self.loop)
172
+
173
+ async def _create_sender(self) -> MTProtoSender:
174
+ dc = await self.client._get_dc(self.dc_id)
175
+ sender = MTProtoSender(self.auth_key, loggers=self.client._log)
176
+ await sender.connect(self.client._connection(dc.ip_address, dc.port, dc.id,
177
+ loggers=self.client._log,
178
+ proxy=self.client._proxy))
179
+ if not self.auth_key:
180
+ log.debug(f"Exporting auth to DC {self.dc_id}")
181
+ auth = await self.client(ExportAuthorizationRequest(self.dc_id))
182
+ self.client._init_request.query = ImportAuthorizationRequest(id=auth.id,
183
+ bytes=auth.bytes)
184
+ req = InvokeWithLayerRequest(LAYER, self.client._init_request)
185
+ await sender.send(req)
186
+ self.auth_key = sender.auth_key
187
+ return sender
188
+
189
+ async def init_upload(self, file_id: int, file_size: int, part_size_kb: Optional[float] = None,
190
+ connection_count: Optional[int] = None) -> Tuple[int, int, bool]:
191
+ connection_count = connection_count or self._get_connection_count(file_size)
192
+ part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024
193
+ part_count = (file_size + part_size - 1) // part_size
194
+ is_large = file_size > 10 * 1024 * 1024
195
+ await self._init_upload(connection_count, file_id, part_count, is_large)
196
+ return part_size, part_count, is_large
197
+
198
+ async def upload(self, part: bytes) -> None:
199
+ await self.senders[self.upload_ticker].next(part)
200
+ self.upload_ticker = (self.upload_ticker + 1) % len(self.senders)
201
+
202
+ async def finish_upload(self) -> None:
203
+ await self._cleanup()
204
+
205
+ async def download(self, file: TypeLocation, file_size: int,
206
+ part_size_kb: Optional[float] = None,
207
+ connection_count: Optional[int] = None) -> AsyncGenerator[bytes, None]:
208
+ connection_count = connection_count or self._get_connection_count(file_size)
209
+ part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024
210
+ part_count = math.ceil(file_size / part_size)
211
+ log.debug("Starting parallel download: "
212
+ f"{connection_count} {part_size} {part_count} {file!s}")
213
+ await self._init_download(connection_count, file, part_count, part_size)
214
+
215
+ part = 0
216
+ while part < part_count:
217
+ tasks = []
218
+ for sender in self.senders:
219
+ tasks.append(self.loop.create_task(sender.next()))
220
+ for task in tasks:
221
+ data = await task
222
+ if not data:
223
+ break
224
+ yield data
225
+ part += 1
226
+ log.debug(f"Part {part} downloaded")
227
+
228
+ log.debug("Parallel download finished, cleaning up connections")
229
+ await self._cleanup()
230
+
231
+
232
+ parallel_transfer_locks: DefaultDict[int, asyncio.Lock] = defaultdict(lambda: asyncio.Lock())
233
+
234
+
235
+ def stream_file(file_to_stream: BinaryIO, chunk_size=1024):
236
+ while True:
237
+ data_read = file_to_stream.read(chunk_size)
238
+ if not data_read:
239
+ break
240
+ yield data_read
241
+
242
+
243
+ async def _internal_transfer_to_telegram(client: TelegramClient,
244
+ response: BinaryIO,
245
+ progress_callback: callable
246
+ ) -> Tuple[TypeInputFile, int]:
247
+ file_id = helpers.generate_random_long()
248
+ file_size = os.path.getsize(response.name)
249
+
250
+ hash_md5 = hashlib.md5()
251
+ uploader = ParallelTransferrer(client)
252
+ part_size, part_count, is_large = await uploader.init_upload(file_id, file_size)
253
+ buffer = bytearray()
254
+ for data in stream_file(response):
255
+ if progress_callback:
256
+ r = progress_callback(response.tell(), file_size)
257
+ if inspect.isawaitable(r):
258
+ await r
259
+ if not is_large:
260
+ hash_md5.update(data)
261
+ if len(buffer) == 0 and len(data) == part_size:
262
+ await uploader.upload(data)
263
+ continue
264
+ new_len = len(buffer) + len(data)
265
+ if new_len >= part_size:
266
+ cutoff = part_size - len(buffer)
267
+ buffer.extend(data[:cutoff])
268
+ await uploader.upload(bytes(buffer))
269
+ buffer.clear()
270
+ buffer.extend(data[cutoff:])
271
+ else:
272
+ buffer.extend(data)
273
+ if len(buffer) > 0:
274
+ await uploader.upload(bytes(buffer))
275
+ await uploader.finish_upload()
276
+ if is_large:
277
+ return InputFileBig(file_id, part_count, "upload"), file_size
278
+ else:
279
+ return InputFile(file_id, part_count, "upload", hash_md5.hexdigest()), file_size
280
+
281
+
282
+ async def download_file(client: TelegramClient,
283
+ location: TypeLocation,
284
+ out: BinaryIO,
285
+ progress_callback: callable = None
286
+ ) -> BinaryIO:
287
+ size = location.size
288
+ dc_id, location = utils.get_input_location(location)
289
+ # We lock the transfers because telegram has connection count limits
290
+ downloader = ParallelTransferrer(client, dc_id)
291
+ downloaded = downloader.download(location, size)
292
+ async for x in downloaded:
293
+ out.write(x)
294
+ if progress_callback:
295
+ r = progress_callback(out.tell(), size)
296
+ if inspect.isawaitable(r):
297
+ await r
298
+
299
+ return out
300
+
301
+
302
+ async def upload_file(client: TelegramClient,
303
+ file: BinaryIO,
304
+ progress_callback: callable = None,
305
+
306
+ ) -> TypeInputFile:
307
+ res = (await _internal_transfer_to_telegram(client, file, progress_callback))[0]
308
+ return res
@@ -0,0 +1,226 @@
1
+ from telethon import events, Button
2
+ import pkgutil, importlib, shlex, time, os, asyncio, mimetypes, unicodedata, re, random, urllib, sys
3
+ current_working_dir = os.getcwd()
4
+ if current_working_dir not in sys.path:
5
+ sys.path.insert(0, current_working_dir)
6
+ from config import ACCESS, MESSAGES
7
+ from ..utils.FastTelethon import download_file
8
+
9
+ class FileGram:
10
+ @classmethod
11
+ async def fileManager(self, system, event, parameters):
12
+ file = system.files[parameters[0]]
13
+ if parameters[1] == "delete":
14
+ try:
15
+ os.unlink(file)
16
+ except:
17
+ pass
18
+ msg = await event.get_message()
19
+ await msg.edit("`[ --- ELIMINADO --- ]`\n\n__Este archivo ha sido eliminado manualmente.__", buttons=None)
20
+
21
+ def sanitize_filename(text: str, ext: str, max_length: int = 200) -> str:
22
+ if not text:
23
+ return f"document_{time.time()}{ext}"
24
+ first_line = text.splitlines()[0].strip()
25
+ if not first_line:
26
+ return f"document_{time.time()}{ext}"
27
+ nfkd = unicodedata.normalize('NFKD', first_line)
28
+ ascii_only = ''.join(c for c in nfkd if ord(c) < 128)
29
+ cleaned = re.sub(r'[<>:"/\\|?*\x00-\x1f]', '_', ascii_only)
30
+ cleaned = cleaned.strip(' .')
31
+ reserved_names = {
32
+ 'CON', 'PRN', 'AUX', 'NUL',
33
+ *(f'COM{i}' for i in range(1, 10)),
34
+ *(f'LPT{i}' for i in range(1, 10))
35
+ }
36
+ if cleaned.upper() in reserved_names:
37
+ cleaned = f"_{cleaned}"
38
+ if len(cleaned) == 0:
39
+ cleaned = f"document_{time.time()}{ext}"
40
+ elif len(cleaned) > max_length:
41
+ cleaned = cleaned[:max_length].rstrip(' ._')
42
+ return cleaned
43
+ def formating(mensajes, datos):
44
+ mensajes_formateados = {}
45
+ for clave, valor in mensajes.items():
46
+ mensajes_formateados[clave] = valor.format(**datos)
47
+ return mensajes_formateados
48
+ class Timer:
49
+ def __init__(self, time_between=2):
50
+ self.start_time = time.time()
51
+ self.time_between = time_between
52
+
53
+ def can_send(self):
54
+ if time.time() > (self.start_time + self.time_between):
55
+ self.start_time = time.time()
56
+ return True
57
+ return False
58
+ class Download:
59
+ def __init__(self, system, event, autoprocess=None, refresh=3):
60
+ self.timer = Timer(time_between=refresh)
61
+ self.last = {"current": 0, "text": ""}
62
+ self.event = event
63
+ self.system = system
64
+ self.start_time = time.time()
65
+ self.finish_time = 0
66
+ self.autoprocess = autoprocess
67
+ self.filename = None
68
+ self.totalsize = 0
69
+ def genbar(self, porcentaje, ancho=20):
70
+ porcentaje = max(0, min(100, porcentaje))
71
+ completos = int(round(ancho * porcentaje / 100))
72
+ vacios = ancho - completos
73
+ barra = f"⟦{'▣' * completos}{'▢' * vacios}⟧"
74
+ return barra
75
+ def sizeof_fmt(self, num, suffix='B'):
76
+ for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
77
+ if abs(num) < 1024.0:
78
+ return "%3.2f%s%s" % (num, unit, suffix)
79
+ num /= 1024.0
80
+ return "%.2f%s%s" % (num, 'Yi', suffix)
81
+ async def callback(self, current, total):
82
+ self.totalsize = total
83
+ if self.timer.can_send():
84
+ if self.last["current"] == current:
85
+ return
86
+ velocity = (current - self.last["current"])/3
87
+ self.last["current"] = current
88
+ text = f"**[ {round(current*100/total,2)} % ]** `{self.filename}`\n\n__{self.sizeof_fmt(current)} / {self.sizeof_fmt(total)} - {self.sizeof_fmt(velocity)}ps__\n`{self.genbar(current*100/total)}`"
89
+ if not text == self.last["text"]:
90
+ self.last["text"] = text
91
+ await self.msg.edit(text)
92
+ async def start(self):
93
+ self.msg = await self.event.reply("Espere un momento...")
94
+ os.makedirs("./files", exist_ok=True)
95
+ ext = mimetypes.guess_extension(self.event.message.document.mime_type or 'application/octet-stream') or ''
96
+ if self.event.file.name:
97
+ self.filename = self.event.file.name.replace("[","").replace("]","")
98
+ else:
99
+ self.filename = f"""document_{str(time.time()).replace(".","_")}{ext}"""
100
+ with open("./files/"+self.filename, "wb") as out:
101
+ await download_file(self.event.client, self.event.document, out, progress_callback=self.callback)
102
+ self.finish_time = time.time()
103
+ self.total_time = self.finish_time - self.start_time
104
+ code = str(random.randint(1000000,9999999))
105
+ self.system.files[code] = "./files/"+self.filename
106
+ buttons = []
107
+ newFiles = []
108
+ for cmd in pkgutil.iter_modules(self.system.commands.__path__):
109
+ submodule = importlib.import_module(self.system.commands.__name__+"."+cmd.name)
110
+ importlib.reload(submodule)
111
+ if hasattr(submodule, "newFile"):
112
+ newFiles.append(submodule)
113
+ if not newFiles == []:
114
+ await self.system.client.delete_messages(self.event.sender.id, self.msg.id)
115
+ for f in newFiles:
116
+ if os.path.exists(self.system.files[code]):
117
+ await f.newFile(self.system, self.event, self.system.files[code])
118
+ return
119
+ buttons.append([Button.inline("ELIMINAR", f"file {code} delete".encode())])
120
+ await self.msg.edit(f"`[ --- ARCHIVO DESCARGADO --- ]`\n\n**[ {self.sizeof_fmt(self.totalsize)} ]** {self.filename}", buttons=buttons)
121
+ self.system.callbacks["file"] = FileGram.fileManager
122
+
123
+
124
+ class System:
125
+ def __init__(self, system_name, client):
126
+ self.name = system_name
127
+ self.admin = {}
128
+ self.operators = {}
129
+ self.users = {}
130
+ self.queue = {}
131
+ self.autoprocess = None
132
+ self.callbacks = {}
133
+ self.files = {}
134
+ self.commands = None
135
+ self.client = client
136
+ self.plugins = []
137
+ @client.on(events.NewMessage())
138
+ async def newMessage(event):
139
+ await self.newEvent(event)
140
+ @client.on(events.NewMessage(pattern="/"))
141
+ async def newCommand(event):
142
+ global MESSAGES
143
+ member = self.isMember(event.sender)
144
+ if not member:
145
+ return
146
+ MESSAGES = formating(MESSAGES, {"username": event.sender.username, "userid": event.sender.id})
147
+ input = shlex.split(event.text)
148
+ command = input[0][1:]
149
+ parameters = input[1:]
150
+ for cmd in pkgutil.iter_modules(self.commands.__path__):
151
+ if command == cmd.name:
152
+ submodule = importlib.import_module(self.commands.__name__+"."+cmd.name)
153
+ importlib.reload(submodule)
154
+ level_access = False
155
+ if not hasattr(submodule, "data"):
156
+ submodule.data["level"] = "user"
157
+ if not "level" in submodule.data:
158
+ submodule.data["level"] = "user"
159
+ if member["permission"] == "user" and submodule.data["level"] == "user":
160
+ level_access = True
161
+ elif member["permission"] == "operator" and (submodule.data["level"] == "user" or submodule.data["level"] == "operator"):
162
+ level_access = True
163
+ elif member["permission"] == "admin":
164
+ level_access = True
165
+ if level_access:
166
+ if hasattr(submodule, "newEvent"):
167
+ await submodule.newEvent(self, event, parameters)
168
+ return
169
+ else:
170
+ await event.respond(MESSAGES["noCommandPermission"])
171
+ await event.respond(MESSAGES["noCommandExists"])
172
+ @client.on(events.CallbackQuery)
173
+ async def newCallback(event):
174
+ sender = event.sender
175
+ data = shlex.split(event.data.decode('utf-8'))
176
+ await self.callbacks[data[0]](self, event, data[1:])
177
+ @client.on(events.NewMessage(incoming=True, func=lambda e: e.file))
178
+ async def newFile(event):
179
+ member = self.isMember(event.sender)
180
+ if not member:
181
+ return
182
+ async def worker(userid):
183
+ while True:
184
+ try:
185
+ process = await self.queue[userid].get()
186
+ except:
187
+ await asyncio.sleep(2)
188
+ continue
189
+ try:
190
+ await process.start()
191
+ except:
192
+ await client.send_message(event.sender.id, "`[ --- ERROR FATAL --- ]`\n\n__Hubo un error en alguno de los procesos de la cola.__")
193
+ finally:
194
+ self.queue[userid].task_done()
195
+ if not event.sender.id in self.queue:
196
+ self.queue[event.sender.id] = asyncio.Queue()
197
+ asyncio.create_task(worker(event.sender.id))
198
+ download = Download(self, event, self.autoprocess)
199
+ await self.queue[event.sender.id].put(download)
200
+ await self.queue[event.sender.id].join()
201
+
202
+ async def newEvent(self, event):
203
+ global MESSAGES
204
+ sender = event.sender
205
+ MESSAGES = formating(MESSAGES, {"username": event.sender.username, "userid": event.sender.id})
206
+ member = self.isMember(event.sender)
207
+ if member:
208
+ return
209
+ if self.admin == {}:
210
+ self.admin = {sender.id: {"username": sender.username ,"permission": "admin"}}
211
+ await event.respond(MESSAGES["newAdmin"])
212
+ elif ACCESS and not sender.id in self.users:
213
+ self.users[sender.id] = {"username": sender.username ,"permission": "user"}
214
+ await event.respond(MESSAGES["newUser"])
215
+ elif not (ACCESS and sender.id in self.users):
216
+ await event.respond(MESSAGES["permissionDenied"])
217
+ def setCommands(self, commands):
218
+ self.commands = commands
219
+ def isMember(self, sender):
220
+ if sender.id in self.admin:
221
+ return self.admin[sender.id]
222
+ elif sender.id in self.users:
223
+ return self.users[sender.id]
224
+ elif sender.id in self.operators:
225
+ return self.operators[sender.id]
226
+ return None