python-telegram 2.0.0__py3-none-macosx_11_0_arm64.whl

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.
Binary file
telegram/py.typed ADDED
File without changes
telegram/tdjson.py ADDED
@@ -0,0 +1,185 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes.util
4
+ import importlib.resources
5
+ import json
6
+ import logging
7
+ import os
8
+ import platform
9
+ from ctypes import CDLL, CFUNCTYPE, c_char_p, c_double, c_int, c_longlong, c_void_p
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ TDLIB_PATH_ENV_VAR = "PYTHON_TELEGRAM_TDLIB_PATH"
16
+
17
+ _OVERRIDES_HINT = (
18
+ "Only the linux x86_64, linux aarch64, macOS arm64 and macOS x86_64 wheels bundle a "
19
+ "libtdjson; the sdist never does. Install tdlib system-wide so ctypes can find it, or "
20
+ f"point python-telegram at your own build with the {TDLIB_PATH_ENV_VAR} environment "
21
+ "variable or the TDJson(library_path=...) argument."
22
+ )
23
+
24
+
25
+ class ClientDestroyedError(RuntimeError):
26
+ """Raised when a TDJson client is used after it has been stopped"""
27
+
28
+
29
+ class TDLibNotFoundError(OSError):
30
+ """
31
+ Raised when no libtdjson can be found or loaded.
32
+
33
+ Subclasses OSError because that is what ctypes.CDLL raises, so callers that
34
+ already handle a failed load keep working.
35
+ """
36
+
37
+
38
+ def _bundled_lib_path() -> Path:
39
+ name = "libtdjson.dylib" if platform.system().lower() == "darwin" else "libtdjson.so"
40
+
41
+ return Path(str(importlib.resources.files("telegram").joinpath(f"lib/{name}")))
42
+
43
+
44
+ def _resolve_tdjson_library() -> tuple[str, str]:
45
+ """Returns the path of the libtdjson to load and the name of the source it came from."""
46
+ env_path = os.environ.get(TDLIB_PATH_ENV_VAR)
47
+
48
+ if env_path:
49
+ return env_path, "env"
50
+
51
+ system_library = ctypes.util.find_library("tdjson")
52
+
53
+ if system_library is not None:
54
+ return system_library, "system"
55
+
56
+ bundled = _bundled_lib_path()
57
+
58
+ if bundled.is_file():
59
+ return str(bundled), "bundled"
60
+
61
+ raise TDLibNotFoundError(
62
+ f"No libtdjson found for {platform.system()} {platform.machine()}. "
63
+ f"{TDLIB_PATH_ENV_VAR} is not set, ctypes.util.find_library('tdjson') found nothing, "
64
+ f"and there is no bundled binary at {bundled}. {_OVERRIDES_HINT}"
65
+ )
66
+
67
+
68
+ class TDJson:
69
+ def __init__(self, library_path: str | None = None, verbosity: int = 2) -> None:
70
+ if library_path is None:
71
+ library_path, source = _resolve_tdjson_library()
72
+ else:
73
+ source = "library_path"
74
+
75
+ logger.info('Using shared library "%s" (found via: %s)', library_path, source)
76
+
77
+ self._build_client(library_path, verbosity)
78
+
79
+ def __del__(self) -> None:
80
+ if hasattr(self, "_td_json_client_destroy"):
81
+ self.stop()
82
+
83
+ def _build_client(self, library_path: str, verbosity: int) -> None:
84
+ try:
85
+ self._tdjson = CDLL(library_path)
86
+ except OSError as error:
87
+ raise TDLibNotFoundError(
88
+ f"Failed to load libtdjson from {library_path} on "
89
+ f"{platform.system()} {platform.machine()}: {error}. {_OVERRIDES_HINT}"
90
+ ) from error
91
+
92
+ # load TDLib functions from shared library
93
+ self._td_json_client_create = self._tdjson.td_json_client_create
94
+ self._td_json_client_create.restype = c_void_p
95
+ self._td_json_client_create.argtypes = []
96
+
97
+ self.td_json_client: int | None = self._td_json_client_create()
98
+
99
+ self._td_json_client_receive = self._tdjson.td_json_client_receive
100
+ self._td_json_client_receive.restype = c_char_p
101
+ self._td_json_client_receive.argtypes = [c_void_p, c_double]
102
+
103
+ self._td_json_client_send = self._tdjson.td_json_client_send
104
+ self._td_json_client_send.restype = None
105
+ self._td_json_client_send.argtypes = [c_void_p, c_char_p]
106
+
107
+ self._td_json_client_execute = self._tdjson.td_json_client_execute
108
+ self._td_json_client_execute.restype = c_char_p
109
+ self._td_json_client_execute.argtypes = [c_void_p, c_char_p]
110
+
111
+ self._td_json_client_destroy = self._tdjson.td_json_client_destroy
112
+ self._td_json_client_destroy.restype = None
113
+ self._td_json_client_destroy.argtypes = [c_void_p]
114
+
115
+ self._td_set_log_file_path = self._tdjson.td_set_log_file_path
116
+ self._td_set_log_file_path.restype = c_int
117
+ self._td_set_log_file_path.argtypes = [c_char_p]
118
+
119
+ self._td_set_log_max_file_size = self._tdjson.td_set_log_max_file_size
120
+ self._td_set_log_max_file_size.restype = None
121
+ self._td_set_log_max_file_size.argtypes = [c_longlong]
122
+
123
+ self._td_set_log_verbosity_level = self._tdjson.td_set_log_verbosity_level
124
+ self._td_set_log_verbosity_level.restype = None
125
+ self._td_set_log_verbosity_level.argtypes = [c_int]
126
+
127
+ self._td_set_log_verbosity_level(verbosity)
128
+
129
+ fatal_error_callback_type = CFUNCTYPE(None, c_char_p)
130
+
131
+ self._td_set_log_fatal_error_callback = self._tdjson.td_set_log_fatal_error_callback
132
+ self._td_set_log_fatal_error_callback.restype = None
133
+ self._td_set_log_fatal_error_callback.argtypes = [fatal_error_callback_type]
134
+
135
+ # initialize TDLib log with desired parameters
136
+ def on_fatal_error_callback(error_message: str) -> None:
137
+ logger.error("TDLib fatal error: %s", error_message)
138
+
139
+ self._c_on_fatal_error_callback = fatal_error_callback_type(on_fatal_error_callback)
140
+ self._td_set_log_fatal_error_callback(self._c_on_fatal_error_callback)
141
+
142
+ def _get_client(self) -> int:
143
+ """
144
+ Returns the client handle, or raises if the client has been destroyed.
145
+
146
+ tdlib dereferences this handle, so passing a destroyed (NULL) one
147
+ crashes the whole process instead of raising.
148
+ """
149
+ if self.td_json_client is None:
150
+ raise ClientDestroyedError("The tdlib client is stopped and cannot be used anymore")
151
+
152
+ return self.td_json_client
153
+
154
+ def send(self, query: dict[Any, Any]) -> None:
155
+ dumped_query = json.dumps(query).encode("utf-8")
156
+ self._td_json_client_send(self._get_client(), dumped_query)
157
+ logger.debug("[me ==>] Sent %s", dumped_query)
158
+
159
+ def receive(self) -> None | dict[Any, Any]:
160
+ result_str = self._td_json_client_receive(self._get_client(), 1.0)
161
+
162
+ if result_str:
163
+ result: dict[Any, Any] = json.loads(result_str.decode("utf-8"))
164
+ logger.debug("[me <==] Received %s", result)
165
+
166
+ return result
167
+
168
+ return None
169
+
170
+ def td_execute(self, query: dict[Any, Any]) -> dict[Any, Any] | Any:
171
+ dumped_query = json.dumps(query).encode("utf-8")
172
+ result_str = self._td_json_client_execute(self._get_client(), dumped_query)
173
+
174
+ if result_str:
175
+ result: dict[Any, Any] = json.loads(result_str.decode("utf-8"))
176
+
177
+ return result
178
+
179
+ return None
180
+
181
+ def stop(self) -> None:
182
+ if self.td_json_client is None:
183
+ return
184
+ self._td_json_client_destroy(self.td_json_client)
185
+ self.td_json_client = None
telegram/text.py ADDED
@@ -0,0 +1,46 @@
1
+ """Since telegram.text is based on a third-party module telegram-text,
2
+ you can find more examples of how to use markup components on
3
+ telegram-text.alinsky.tech or github.com/SKY-ALIN/telegram-text
4
+ """
5
+
6
+ from telegram_text import (
7
+ Bold,
8
+ Chain,
9
+ Code,
10
+ Hashtag,
11
+ InlineCode,
12
+ InlineUser,
13
+ Italic,
14
+ Link,
15
+ OrderedList,
16
+ PlainText,
17
+ Spoiler,
18
+ Strikethrough,
19
+ Text,
20
+ TOMLSection,
21
+ Underline,
22
+ UnorderedList,
23
+ User,
24
+ )
25
+ from telegram_text.bases import Element
26
+
27
+ __all__ = [
28
+ "Bold",
29
+ "Chain",
30
+ "Code",
31
+ "Element",
32
+ "Hashtag",
33
+ "InlineCode",
34
+ "InlineUser",
35
+ "Italic",
36
+ "Link",
37
+ "OrderedList",
38
+ "PlainText",
39
+ "Spoiler",
40
+ "Strikethrough",
41
+ "TOMLSection",
42
+ "Text",
43
+ "Underline",
44
+ "UnorderedList",
45
+ "User",
46
+ ]
telegram/utils.py ADDED
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import threading
5
+ import uuid
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ if TYPE_CHECKING:
9
+ from telegram.client import Telegram
10
+
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class AsyncResult:
16
+ """
17
+ tdlib is asynchronous, and this class helps you get results back.
18
+ After each API call, you receive AsyncResult object, which you can use to get results back.
19
+ """
20
+
21
+ def __init__(self, client: Telegram, result_id: str | None = None) -> None:
22
+ self.client = client
23
+
24
+ if result_id:
25
+ self.id = result_id
26
+ else:
27
+ self.id = uuid.uuid4().hex
28
+
29
+ self.request: dict[Any, Any] | None = None
30
+ self.ok_received = False
31
+ self.error = False
32
+ self.error_info: dict[Any, Any] | None = None
33
+ self.update: dict[Any, Any] | None = None
34
+ self._ready = threading.Event()
35
+
36
+ def __str__(self) -> str:
37
+ return f"AsyncResult <{self.id}>"
38
+
39
+ def wait(self, timeout: float | None = None, raise_exc: bool = False) -> None:
40
+ """
41
+ Blocking method to wait for the result
42
+ """
43
+ result = self._ready.wait(timeout=timeout)
44
+ if result is False:
45
+ raise TimeoutError()
46
+ if raise_exc and self.error:
47
+ raise RuntimeError(f"Telegram error: {self.error_info}")
48
+
49
+ def parse_update(self, update: dict[Any, Any]) -> bool:
50
+ update_type = update.get("@type")
51
+
52
+ logger.debug("update id=%s type=%s received", self.id, update_type)
53
+
54
+ if update_type == "ok":
55
+ self.ok_received = True
56
+ if self.id == "updateAuthorizationState":
57
+ # For updateAuthorizationState commands tdlib sends
58
+ # @type: ok responses
59
+ # but we want to wait longer to receive the new authorization state
60
+ return False
61
+ elif update_type == "error":
62
+ self.error = True
63
+ self.error_info = update
64
+ else:
65
+ self.update = update
66
+
67
+ self._ready.set()
68
+
69
+ return True
telegram/worker.py ADDED
@@ -0,0 +1,51 @@
1
+ import logging
2
+ import threading
3
+ from queue import Empty, Queue
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class BaseWorker:
9
+ """
10
+ Base worker class.
11
+ Each worker must implement the run method to start listening to the queue
12
+ and calling handler functions
13
+ """
14
+
15
+ def __init__(self, queue: Queue):
16
+ self._is_enabled = True
17
+ self._queue = queue
18
+
19
+ def run(self) -> None:
20
+ raise NotImplementedError()
21
+
22
+ def stop(self) -> None:
23
+ raise NotImplementedError()
24
+
25
+
26
+ class SimpleWorker(BaseWorker):
27
+ """Simple one-thread worker"""
28
+
29
+ def run(self) -> None:
30
+ self._thread = threading.Thread(target=self._run_thread)
31
+ self._thread.daemon = True
32
+ self._thread.start()
33
+
34
+ def _run_thread(self) -> None:
35
+ logger.info("[SimpleWorker] started")
36
+
37
+ while self._is_enabled:
38
+ try:
39
+ handler, update = self._queue.get(timeout=0.5)
40
+ except Empty:
41
+ continue
42
+
43
+ try:
44
+ handler(update)
45
+ except Exception:
46
+ logger.exception("Error in update handler %s", handler)
47
+ self._queue.task_done()
48
+
49
+ def stop(self) -> None:
50
+ self._is_enabled = False
51
+ self._thread.join()