From 880eb28e9f5f6b7a34e79f38117cf1d0b5fbcfb3 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 30 Mar 2018 22:41:34 +0200
Subject: [PATCH 01/18] Use double quotes
---
pyrogram/client/client.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 98cf5e65..ef3e0f26 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2293,7 +2293,7 @@ class Client:
)
if isinstance(r, types.upload.File):
- with tempfile.NamedTemporaryFile('wb', delete=False) as f:
+ with tempfile.NamedTemporaryFile("wb", delete=False) as f:
file_name = f.name
while True:
@@ -2332,7 +2332,7 @@ class Client:
cdn_session.start()
try:
- with tempfile.NamedTemporaryFile('wb', delete=False) as f:
+ with tempfile.NamedTemporaryFile("wb", delete=False) as f:
file_name = f.name
while True:
From 387bbbf090b53995aaededa1970ca06880562540 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 1 Apr 2018 17:38:22 +0200
Subject: [PATCH 02/18] Add new force_sms parameter to force Telegram sending
the code via SMS
---
pyrogram/client/client.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index ef3e0f26..07869a62 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -136,6 +136,7 @@ class Client:
phone_number: str = None,
phone_code: str or callable = None,
password: str = None,
+ force_sms: bool = False,
first_name: str = None,
last_name: str = None,
workers: int = 4):
@@ -149,6 +150,7 @@ class Client:
self.phone_code = phone_code
self.first_name = first_name
self.last_name = last_name
+ self.force_sms = force_sms
self.workers = workers
@@ -346,6 +348,14 @@ class Client:
phone_registered = r.phone_registered
phone_code_hash = r.phone_code_hash
+ if self.force_sms:
+ self.send(
+ functions.auth.ResendCode(
+ phone_number=self.phone_number,
+ phone_code_hash=phone_code_hash
+ )
+ )
+
while True:
self.phone_code = (
input("Enter phone code: ") if self.phone_code is None
From fecea07db68d692f5f7bad850fae555dab316b04 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 1 Apr 2018 18:17:20 +0200
Subject: [PATCH 03/18] Document force_sms parameter
---
pyrogram/client/client.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 07869a62..4f1ac855 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -110,6 +110,10 @@ class Client:
Pass your Two-Step Verification password (if you have one) to avoid entering it
manually. Only applicable for new sessions.
+ force_sms (``str``, optional):
+ Pass True to force Telegram sending the authorization code via SMS.
+ Only applicable for new sessions.
+
first_name (``str``, optional):
Pass a First Name to avoid entering it manually. It will be used to automatically
create a new Telegram account in case the phone number you passed is not registered yet.
From 21ab5295c4077d42518df64c250d00a8bb309ab1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 1 Apr 2018 18:18:06 +0200
Subject: [PATCH 04/18] Update first_name and last_name parameters' docs
---
pyrogram/client/client.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 4f1ac855..23a1568b 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -117,10 +117,11 @@ class Client:
first_name (``str``, optional):
Pass a First Name to avoid entering it manually. It will be used to automatically
create a new Telegram account in case the phone number you passed is not registered yet.
+ Only applicable for new sessions.
last_name (``str``, optional):
Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can
- be an empty string: ""
+ be an empty string: "". Only applicable for new sessions.
workers (``int``, optional):
Thread pool size for handling incoming updates. Defaults to 4.
From 1849c26b5e062a6478f02bf0f733c441329ea8a4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 2 Apr 2018 10:02:30 +0200
Subject: [PATCH 05/18] Revert "Remove old code and use a better error message"
This reverts commit 7f13eef
---
pyrogram/crypto/aes.py | 57 +++++++++++++++++++++++++++++++++++-------
1 file changed, 48 insertions(+), 9 deletions(-)
diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py
index 8ca72535..05a01044 100644
--- a/pyrogram/crypto/aes.py
+++ b/pyrogram/crypto/aes.py
@@ -16,33 +16,52 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
+import logging
+
+log = logging.getLogger(__name__)
+
try:
import tgcrypto
-except ImportError as e:
- e.msg = (
- "TgCrypto is missing and Pyrogram can't run without. "
- "Please install it using \"pip3 install tgcrypto\". "
+except ImportError:
+ log.warning(
+ "TgCrypto is missing! "
+ "Pyrogram will work the same, but at a much slower speed. "
"More info: https://docs.pyrogram.ml/resources/TgCrypto"
)
-
- raise e
+ is_fast = False
+ import pyaes
+else:
+ log.info("Using TgCrypto")
+ is_fast = True
+# TODO: Ugly IFs
class AES:
@classmethod
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
- return tgcrypto.ige_encrypt(data, key, iv)
+ if is_fast:
+ return tgcrypto.ige_encrypt(data, key, iv)
+ else:
+ return cls.ige(data, key, iv, True)
@classmethod
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
- return tgcrypto.ige_decrypt(data, key, iv)
+ if is_fast:
+ return tgcrypto.ige_decrypt(data, key, iv)
+ else:
+ return cls.ige(data, key, iv, False)
@staticmethod
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
replace = int.to_bytes(offset // 16, 4, "big")
iv = iv[:-4] + replace
- return tgcrypto.ctr_decrypt(data, key, iv)
+ if is_fast:
+ return tgcrypto.ctr_decrypt(data, key, iv)
+ else:
+ ctr = pyaes.AESModeOfOperationCTR(key)
+ ctr._counter._counter = list(iv)
+ return ctr.decrypt(data)
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
@@ -51,3 +70,23 @@ class AES:
len(a),
"big",
)
+
+ @classmethod
+ def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
+ cipher = pyaes.AES(key)
+
+ iv_1 = iv[:16]
+ iv_2 = iv[16:]
+
+ data = [data[i: i + 16] for i in range(0, len(data), 16)]
+
+ if encrypt:
+ for i, chunk in enumerate(data):
+ iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
+ iv_2 = chunk
+ else:
+ for i, chunk in enumerate(data):
+ iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
+ iv_1 = chunk
+
+ return b"".join(data)
From 5f0ab753efd70b758d95db3b1483e9811c68db22 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 2 Apr 2018 11:04:39 +0200
Subject: [PATCH 06/18] Update requirements.txt
---
requirements.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 21c697f1..3216c15d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-pysocks
-tgcrypto
\ No newline at end of file
+pyaes>=1.6.1
+pysocks>=1.6.8
\ No newline at end of file
From 8b3ba8a5733584cf471d973f41782eda35f49197 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 2 Apr 2018 11:04:59 +0200
Subject: [PATCH 07/18] Add requirements_extras.txt
---
requirements_extras.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 requirements_extras.txt
diff --git a/requirements_extras.txt b/requirements_extras.txt
new file mode 100644
index 00000000..1d101a7e
--- /dev/null
+++ b/requirements_extras.txt
@@ -0,0 +1 @@
+tgcrypto>=1.0.4
\ No newline at end of file
From d1517ae8857792aa00e98fc8bc2e29b576085fe0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 2 Apr 2018 11:07:23 +0200
Subject: [PATCH 08/18] Update setup.py
---
setup.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/setup.py b/setup.py
index c82c1a9d..7a8e0132 100644
--- a/setup.py
+++ b/setup.py
@@ -25,8 +25,8 @@ from compiler.api import compiler as api_compiler
from compiler.error import compiler as error_compiler
-def requirements():
- with open("requirements.txt", encoding="utf-8") as r:
+def read(file: str) -> list:
+ with open(file, encoding="utf-8") as r:
return [i.strip() for i in r]
@@ -82,5 +82,6 @@ setup(
python_requires="~=3.4",
packages=find_packages(exclude=["compiler*"]),
zip_safe=False,
- install_requires=requirements()
+ install_requires=read("requirements.txt"),
+ extras_require={"tgcrypto": read("requirements_extras.txt")}
)
From e6ee76792bd59a5f180a052e480d41372a9e185b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 2 Apr 2018 11:07:58 +0200
Subject: [PATCH 09/18] Add requirements_extras.txt to MANIFEST.in
---
MANIFEST.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MANIFEST.in b/MANIFEST.in
index f818e13a..a1d19d94 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
## Include
-include COPYING COPYING.lesser NOTICE requirements.txt
+include COPYING COPYING.lesser NOTICE requirements.txt requirements_extras.txt
recursive-include compiler *.py *.tl *.tsv *.txt
## Exclude
From b5304ca23ac197e7e07400245a3d1074eaa9d07e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 2 Apr 2018 11:11:38 +0200
Subject: [PATCH 10/18] Use fully qualified channel id
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 23a1568b..005fda4f 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -649,7 +649,7 @@ class Client:
if not isinstance(message, types.MessageEmpty):
diff = self.send(
functions.updates.GetChannelDifference(
- channel=self.resolve_peer(update.message.to_id.channel_id),
+ channel=self.resolve_peer(int("-100" + str(update.message.to_id.channel_id))),
filter=types.ChannelMessagesFilter(
ranges=[types.MessageRange(
min_id=update.message.id,
From 2f2a381686150f60caf7adb9e66efdc73ae683e9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 2 Apr 2018 12:14:22 +0200
Subject: [PATCH 11/18] Add extra GetDialogs step
---
pyrogram/client/client.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 005fda4f..1da96844 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -914,6 +914,15 @@ class Client:
offset_date = parse_dialogs(dialogs)
log.info("Entities count: {}".format(len(self.peers_by_id)))
+ self.send(
+ functions.messages.GetDialogs(
+ 0, 0, types.InputPeerEmpty(),
+ self.DIALOGS_AT_ONCE, True
+ )
+ )
+
+ log.info("Entities count: {}".format(len(self.peers_by_id)))
+
def resolve_peer(self, peer_id: int or str):
"""Use this method to get the *InputPeer* of a known *peer_id*.
From e69fea4bb577c542403e9680c3cc25615b90b997 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 3 Apr 2018 11:40:08 +0200
Subject: [PATCH 12/18] More readable exception handling
---
pyrogram/client/client.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 1da96844..237b00f7 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -802,15 +802,15 @@ class Client:
Raises:
:class:`Error `
"""
- if self.is_started:
- r = self.session.send(data)
+ if not self.is_started:
+ raise ConnectionError("Client has not been started")
- self.fetch_peers(getattr(r, "users", []))
- self.fetch_peers(getattr(r, "chats", []))
+ r = self.session.send(data)
- return r
- else:
- raise ConnectionError("client '{}' is not started".format(self.session_name))
+ self.fetch_peers(getattr(r, "users", []))
+ self.fetch_peers(getattr(r, "chats", []))
+
+ return r
def load_config(self):
parser = ConfigParser()
From 10452dc545cc455f4e10d2d1e6948f2fb9b4828b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 3 Apr 2018 11:45:19 +0200
Subject: [PATCH 13/18] Don't allow start() to be called more than once
---
pyrogram/client/client.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 237b00f7..08e0e117 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -194,6 +194,9 @@ class Client:
Raises:
:class:`Error `
"""
+ if self.is_started:
+ raise ConnectionError("Client has already been started")
+
if self.BOT_TOKEN_RE.match(self.session_name):
self.token = self.session_name
self.session_name = self.session_name.split(":")[0]
From fcf0e4515f9ac99d7b8168890ba946e08740153b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 3 Apr 2018 14:54:34 +0200
Subject: [PATCH 14/18] Don't try to stop a non-started Client
---
pyrogram/client/client.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 08e0e117..be04db75 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -245,6 +245,9 @@ class Client:
"""Use this method to manually stop the Client.
Requires no parameters.
"""
+ if not self.is_started:
+ raise ConnectionError("Client is already stopped")
+
self.is_started = False
self.session.stop()
From f8b272a9255f70d3158049152f488bf7fafe3118 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 5 Apr 2018 11:31:01 +0200
Subject: [PATCH 15/18] Allow passing phone numbers with white spaces E.g.:
"+39 123 456 7890"
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index be04db75..a7a91f93 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -961,7 +961,7 @@ class Client:
except (AttributeError, binascii.Error, struct.error):
pass
- peer_id = peer_id.lower().strip("@+")
+ peer_id = re.sub(r"[@+\s]", "", peer_id.lower())
try:
int(peer_id)
From cce937e54b616a2e9a901789839053dcc62f5363 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 5 Apr 2018 11:43:56 +0200
Subject: [PATCH 16/18] Set correct type hint
---
pyrogram/session/session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 46d722fc..5d54ff1f 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -87,7 +87,7 @@ class Session:
test_mode: bool,
proxy: type,
auth_key: bytes,
- api_id: str,
+ api_id: int,
is_cdn: bool = False,
client: pyrogram = None):
if not Session.notice_displayed:
From 942c20d08b7189ea1ae1f0190bff029b605ffd63 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 5 Apr 2018 12:55:34 +0200
Subject: [PATCH 17/18] Use separate api_id and api_hash parameters Instead of
a tuple (api_id, api_hash)
---
pyrogram/client/client.py | 61 ++++++++++++++++++++-------------------
1 file changed, 31 insertions(+), 30 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index a7a91f93..ccadfb87 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -81,10 +81,13 @@ class Client:
For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number.
- api_key (``tuple``, optional):
- Your Telegram API Key as tuple: *(api_id, api_hash)*.
- E.g.: *(12345, "0123456789abcdef0123456789abcdef")*. This is an alternative way to pass it if you
- don't want to use the *config.ini* file.
+ api_id (``int``, optional):
+ The *api_id* part of your Telegram API Key, as integer. E.g.: 12345
+ This is an alternative way to pass it if you don't want to use the *config.ini* file.
+
+ api_hash (``str``, optional):
+ The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef"
+ This is an alternative way to pass it if you don't want to use the *config.ini* file.
proxy (``dict``, optional):
Your SOCKS5 Proxy settings as dict,
@@ -135,7 +138,8 @@ class Client:
def __init__(self,
session_name: str,
- api_key: tuple or APIKey = None,
+ api_id: int = None,
+ api_hash: str = None,
proxy: dict or Proxy = None,
test_mode: bool = False,
phone_number: str = None,
@@ -146,7 +150,8 @@ class Client:
last_name: str = None,
workers: int = 4):
self.session_name = session_name
- self.api_key = api_key
+ self.api_id = api_id
+ self.api_hash = api_hash
self.proxy = proxy
self.test_mode = test_mode
@@ -209,7 +214,7 @@ class Client:
self.test_mode,
self.proxy,
self.auth_key,
- self.api_key.api_id,
+ self.api_id,
client=self
)
@@ -265,8 +270,8 @@ class Client:
r = self.send(
functions.auth.ImportBotAuthorization(
flags=0,
- api_id=self.api_key.api_id,
- api_hash=self.api_key.api_hash,
+ api_id=self.api_id,
+ api_hash=self.api_hash,
bot_auth_token=self.token
)
)
@@ -281,7 +286,7 @@ class Client:
self.test_mode,
self.proxy,
self.auth_key,
- self.api_key.api_id,
+ self.api_id,
client=self
)
@@ -314,8 +319,8 @@ class Client:
r = self.send(
functions.auth.SendCode(
self.phone_number,
- self.api_key.api_id,
- self.api_key.api_hash
+ self.api_id,
+ self.api_hash
)
)
except (PhoneMigrate, NetworkMigrate) as e:
@@ -329,7 +334,7 @@ class Client:
self.test_mode,
self.proxy,
self.auth_key,
- self.api_key.api_id,
+ self.api_id,
client=self
)
self.session.start()
@@ -337,8 +342,8 @@ class Client:
r = self.send(
functions.auth.SendCode(
self.phone_number,
- self.api_key.api_id,
- self.api_key.api_hash
+ self.api_id,
+ self.api_hash
)
)
break
@@ -822,18 +827,14 @@ class Client:
parser = ConfigParser()
parser.read("config.ini")
- if self.api_key is not None:
- self.api_key = APIKey(
- api_id=int(self.api_key[0]),
- api_hash=self.api_key[1]
- )
- elif parser.has_section("pyrogram"):
- self.api_key = APIKey(
- api_id=parser.getint("pyrogram", "api_id"),
- api_hash=parser.get("pyrogram", "api_hash")
- )
+ if self.api_id and self.api_hash:
+ pass
else:
- raise AttributeError("No API Key found")
+ if parser.has_section("pyrogram"):
+ self.api_id = parser.getint("pyrogram", "api_id")
+ self.api_hash = parser.get("pyrogram", "api_hash")
+ else:
+ raise AttributeError("No API Key found")
if self.proxy is not None:
self.proxy = Proxy(
@@ -2191,7 +2192,7 @@ class Client:
file_id = file_id or self.rnd_id()
md5_sum = md5() if not is_big and not is_missing_part else None
- session = Session(self.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_key.api_id)
+ session = Session(self.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_id)
session.start()
try:
@@ -2274,7 +2275,7 @@ class Client:
self.test_mode,
self.proxy,
Auth(dc_id, self.test_mode, self.proxy).create(),
- self.api_key.api_id
+ self.api_id
)
session.start()
@@ -2291,7 +2292,7 @@ class Client:
self.test_mode,
self.proxy,
self.auth_key,
- self.api_key.api_id
+ self.api_id
)
session.start()
@@ -2355,7 +2356,7 @@ class Client:
self.test_mode,
self.proxy,
Auth(r.dc_id, self.test_mode, self.proxy).create(),
- self.api_key.api_id,
+ self.api_id,
is_cdn=True
)
From 73fbe600573fee4d0f77b2cd1bd83c245d3d7456 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 5 Apr 2018 13:05:27 +0200
Subject: [PATCH 18/18] Remove APIKey class
---
pyrogram/client/client.py | 6 ------
1 file changed, 6 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index ccadfb87..31b4b895 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -54,12 +54,6 @@ from .style import Markdown, HTML
log = logging.getLogger(__name__)
-class APIKey:
- def __init__(self, api_id: int, api_hash: str):
- self.api_id = api_id
- self.api_hash = api_hash
-
-
class Proxy:
def __init__(self, enabled: bool, hostname: str, port: int, username: str, password: str):
self.enabled = enabled