ferogram 0.1.2__cp312-abi3-android_24_arm64_v8a.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.
ferogram/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ import os
15
+ import sys
16
+
17
+ _main_file = getattr(sys.modules.get("__main__"), "__file__", None)
18
+ if _main_file:
19
+ _name = os.path.splitext(os.path.basename(_main_file))[0]
20
+ if _name == "ferogram":
21
+ raise ImportError(
22
+ "\n\nYour script is named 'ferogram.py' which shadows the ferogram package "
23
+ "and causes circular imports.\n"
24
+ "Rename your file to something like 'bot.py', 'main.py', or 'app.py'."
25
+ )
26
+
27
+ from .client import Client
28
+ from . import filters
29
+
30
+ __all__ = ["Client", "filters"]
Binary file
ferogram/client.py ADDED
@@ -0,0 +1,350 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import inspect
18
+ import logging
19
+ import os
20
+ from typing import Any, Callable
21
+
22
+ from ._ferogram import Client as _RustClient, PasswordToken, User, Dialog
23
+ from ._ferogram import Message, CallbackQuery
24
+ from ._ferogram import (
25
+ MessageDeletion, InlineQuery, InlineSend, UserStatus,
26
+ ChatAction, ParticipantUpdate, JoinRequest, MessageReaction,
27
+ PollVote, BotStopped, RawUpdate,
28
+ )
29
+ from .raw import _tl
30
+ from .raw.generated._tl_schema import _SCHEMA_BY_CID
31
+
32
+ __all__ = ["Client"]
33
+
34
+ _log = logging.getLogger("ferogram")
35
+
36
+ _Handler = tuple[Callable, list[Callable]] # (func, filters)
37
+
38
+ _ALL_EVENTS = (
39
+ "message",
40
+ "edited_message",
41
+ "message_deleted",
42
+ "callback_query",
43
+ "inline_query",
44
+ "inline_send",
45
+ "user_status",
46
+ "chat_action",
47
+ "participant_update",
48
+ "join_request",
49
+ "message_reaction",
50
+ "poll_vote",
51
+ "bot_stopped",
52
+ "raw_update",
53
+ )
54
+
55
+
56
+ class Client:
57
+ def __init__(
58
+ self,
59
+ session: str = "ferogram",
60
+ *,
61
+ api_id: int | None = None,
62
+ api_hash: str | None = None,
63
+ bot_token: str | None = None,
64
+ phone: str | None = None,
65
+ password: str | None = None,
66
+ ) -> None:
67
+ self.session = session
68
+ self.api_id = api_id or int(os.environ.get("API_ID", 0)) or None
69
+ self.api_hash = api_hash or os.environ.get("API_HASH")
70
+ self.bot_token = bot_token or os.environ.get("BOT_TOKEN")
71
+ self._phone = phone
72
+ self._password = password
73
+ self._raw: _RustClient | None = None
74
+
75
+ self._handlers: dict[str, list[_Handler]] = {e: [] for e in _ALL_EVENTS}
76
+
77
+ def _require_creds(self) -> tuple[int, str]:
78
+ if not self.api_id or not self.api_hash:
79
+ raise ValueError("api_id and api_hash required.")
80
+ return self.api_id, self.api_hash
81
+
82
+ @property
83
+ def _client(self) -> _RustClient:
84
+ if self._raw is None:
85
+ raise RuntimeError("Call await app.start() first.")
86
+ return self._raw
87
+
88
+ # handler decorators
89
+
90
+ def on_message(self, *filters: Callable) -> Callable:
91
+ """Decorator: handle incoming messages."""
92
+ def decorator(func: Callable) -> Callable:
93
+ self._handlers["message"].append((func, list(filters)))
94
+ return func
95
+ return decorator
96
+
97
+ def on_edited_message(self, *filters: Callable) -> Callable:
98
+ """Decorator: handle edited messages."""
99
+ def decorator(func: Callable) -> Callable:
100
+ self._handlers["edited_message"].append((func, list(filters)))
101
+ return func
102
+ return decorator
103
+
104
+ def on_message_deleted(self, *filters: Callable) -> Callable:
105
+ """Decorator: handle message deletions."""
106
+ def decorator(func: Callable) -> Callable:
107
+ self._handlers["message_deleted"].append((func, list(filters)))
108
+ return func
109
+ return decorator
110
+
111
+ def on_callback_query(self, *filters: Callable) -> Callable:
112
+ """Decorator: handle inline button presses."""
113
+ def decorator(func: Callable) -> Callable:
114
+ self._handlers["callback_query"].append((func, list(filters)))
115
+ return func
116
+ return decorator
117
+
118
+ def on_inline_query(self, *filters: Callable) -> Callable:
119
+ """Decorator: handle @bot inline queries (bots only)."""
120
+ def decorator(func: Callable) -> Callable:
121
+ self._handlers["inline_query"].append((func, list(filters)))
122
+ return func
123
+ return decorator
124
+
125
+ def on_inline_send(self, *filters: Callable) -> Callable:
126
+ """Decorator: user chose an inline result (bots only)."""
127
+ def decorator(func: Callable) -> Callable:
128
+ self._handlers["inline_send"].append((func, list(filters)))
129
+ return func
130
+ return decorator
131
+
132
+ def on_user_status(self, *filters: Callable) -> Callable:
133
+ """Decorator: user came online or went offline."""
134
+ def decorator(func: Callable) -> Callable:
135
+ self._handlers["user_status"].append((func, list(filters)))
136
+ return func
137
+ return decorator
138
+
139
+ def on_chat_action(self, *filters: Callable) -> Callable:
140
+ """Decorator: user is typing / uploading / recording."""
141
+ def decorator(func: Callable) -> Callable:
142
+ self._handlers["chat_action"].append((func, list(filters)))
143
+ return func
144
+ return decorator
145
+
146
+ def on_participant_update(self, *filters: Callable) -> Callable:
147
+ """Decorator: member joined, left, was promoted, banned, etc."""
148
+ def decorator(func: Callable) -> Callable:
149
+ self._handlers["participant_update"].append((func, list(filters)))
150
+ return func
151
+ return decorator
152
+
153
+ def on_join_request(self, *filters: Callable) -> Callable:
154
+ """Decorator: user requested to join via invite link (bots only)."""
155
+ def decorator(func: Callable) -> Callable:
156
+ self._handlers["join_request"].append((func, list(filters)))
157
+ return func
158
+ return decorator
159
+
160
+ def on_message_reaction(self, *filters: Callable) -> Callable:
161
+ """Decorator: reaction added/removed on a bot message (bots only)."""
162
+ def decorator(func: Callable) -> Callable:
163
+ self._handlers["message_reaction"].append((func, list(filters)))
164
+ return func
165
+ return decorator
166
+
167
+ def on_poll_vote(self, *filters: Callable) -> Callable:
168
+ """Decorator: user voted in a poll sent by the bot (bots only)."""
169
+ def decorator(func: Callable) -> Callable:
170
+ self._handlers["poll_vote"].append((func, list(filters)))
171
+ return func
172
+ return decorator
173
+
174
+ def on_bot_stopped(self, *filters: Callable) -> Callable:
175
+ """Decorator: user stopped or restarted the bot."""
176
+ def decorator(func: Callable) -> Callable:
177
+ self._handlers["bot_stopped"].append((func, list(filters)))
178
+ return func
179
+ return decorator
180
+
181
+ def on_raw_update(self, *filters: Callable) -> Callable:
182
+ """Decorator: receive RawUpdate for any TL update not mapped to a typed event.
183
+
184
+ Useful for handling obscure update types not yet covered by dedicated
185
+ handlers. The update object has .constructor_id (u32) and .type_name (str).
186
+ """
187
+ def decorator(func: Callable) -> Callable:
188
+ self._handlers["raw_update"].append((func, list(filters)))
189
+ return func
190
+ return decorator
191
+
192
+ # dispatch
193
+
194
+ async def _dispatch(self, event_type: str, update: Any) -> None:
195
+ for func, fltrs in self._handlers.get(event_type, []):
196
+ if all(f(update) for f in fltrs):
197
+ try:
198
+ result = func(self, update)
199
+ if inspect.isawaitable(result):
200
+ await result
201
+ except Exception as exc:
202
+ _log.error("handler error in %s: %s", event_type, exc, exc_info=True)
203
+
204
+ # update loop
205
+
206
+ async def _run_updates(self) -> None:
207
+ _log.debug("update loop started")
208
+ while True:
209
+ result = await self._client.next_update()
210
+ if result is None:
211
+ _log.debug("update stream closed")
212
+ break
213
+ event_type, update = result
214
+ _log.debug("dispatching %s", event_type)
215
+ asyncio.create_task(self._dispatch(event_type, update))
216
+
217
+ # lifecycle
218
+
219
+ async def start(self) -> "Client":
220
+ if self._raw is not None:
221
+ return self
222
+ api_id, api_hash = self._require_creds()
223
+ _log.info("connecting (session=%r)", self.session + (".session" if not self.session.endswith(".session") else ""))
224
+ session_path = self.session if self.session.endswith(".session") else self.session + ".session"
225
+ self._raw = await _RustClient.builder(api_id, api_hash, session_path).connect()
226
+ if not await self._raw.is_authorized():
227
+ if self.bot_token:
228
+ await self._raw.bot_sign_in(self.bot_token)
229
+ _log.info("signed in as bot")
230
+ else:
231
+ await self._interactive_login()
232
+ await self._raw.save_session()
233
+ else:
234
+ _log.info("reusing existing session")
235
+ return self
236
+
237
+ async def _interactive_login(self) -> None:
238
+ phone = self._phone or input("Phone (+countrycode): ")
239
+ token = await self._client.request_login_code(phone)
240
+ pw_token = await self._client.sign_in(token, input("Code: "))
241
+ if pw_token is not None:
242
+ hint = pw_token.hint
243
+ pwd = self._password or input(f"2FA password (hint: {hint}): " if hint else "2FA password: ")
244
+ await self._client.check_password(pw_token, pwd)
245
+ _log.info("signed in as user")
246
+
247
+ async def stop(self) -> None:
248
+ if self._raw:
249
+ await self._raw.sign_out()
250
+ self._raw = None
251
+ _log.info("signed out")
252
+
253
+ async def run_until_disconnected(self) -> None:
254
+ await self.start()
255
+ try:
256
+ await self._run_updates()
257
+ except (KeyboardInterrupt, asyncio.CancelledError):
258
+ pass
259
+
260
+ def run(self) -> None:
261
+ """Blocking run: start, dispatch updates, stop on Ctrl-C."""
262
+ try:
263
+ asyncio.run(self.run_until_disconnected())
264
+ except KeyboardInterrupt:
265
+ pass
266
+
267
+ async def __aenter__(self) -> "Client":
268
+ return await self.start()
269
+
270
+ async def __aexit__(self, *_: Any) -> None:
271
+ pass
272
+
273
+ # messaging
274
+
275
+ async def send_message(self, peer: str, text: str) -> Message:
276
+ return await self._client.send_message(peer, text)
277
+
278
+ async def send_html(self, peer: str, html: str) -> Message:
279
+ return await self._client.send_html(peer, html)
280
+
281
+ async def send_markdown(self, peer: str, md: str) -> Message:
282
+ return await self._client.send_markdown(peer, md)
283
+
284
+ async def edit_message(self, peer: str, message_id: int, new_text: str) -> None:
285
+ await self._client.edit_message(peer, message_id, new_text)
286
+
287
+ async def delete_message(self, message_id: int, revoke: bool = True) -> None:
288
+ await self._client.delete_messages([message_id], revoke)
289
+
290
+ async def delete_messages(self, message_ids: list[int], revoke: bool = True) -> None:
291
+ await self._client.delete_messages(message_ids, revoke)
292
+
293
+ async def forward_messages(self, destination: str, source: str, message_ids: list[int]) -> None:
294
+ await self._client.forward_messages(destination, source, message_ids)
295
+
296
+ async def pin_message(self, peer: str, message_id: int) -> None:
297
+ await self._client.pin_message(peer, message_id)
298
+
299
+ async def unpin_message(self, peer: str, message_id: int) -> None:
300
+ await self._client.unpin_message(peer, message_id)
301
+
302
+ async def mark_as_read(self, peer: str) -> None:
303
+ await self._client.mark_as_read(peer)
304
+
305
+ async def send_reaction(self, peer: str, message_id: int, emoji: str) -> None:
306
+ """Send a reaction emoji to a message."""
307
+ await self._client.send_reaction(peer, message_id, emoji)
308
+
309
+ # media
310
+
311
+ async def send_photo(self, peer: str, path: str, caption: str = "") -> Message:
312
+ import os
313
+ if not os.path.isfile(path):
314
+ raise FileNotFoundError(f"No such file: {path!r}")
315
+ return await self._client.send_photo(peer, path, caption)
316
+
317
+ async def send_document(self, peer: str, path: str, caption: str = "", mime_type: str | None = None) -> Message:
318
+ import os
319
+ if not os.path.isfile(path):
320
+ raise FileNotFoundError(f"No such file: {path!r}")
321
+ return await self._client.send_document(peer, path, caption, mime_type)
322
+
323
+ async def send_file(self, peer: str, path: str, caption: str = "", mime_type: str | None = None) -> Message:
324
+ import os
325
+ if not os.path.isfile(path):
326
+ raise FileNotFoundError(f"No such file: {path!r}")
327
+ return await self._client.send_file(peer, path, caption, mime_type)
328
+
329
+ # account
330
+
331
+ async def get_me(self) -> User:
332
+ return await self._client.get_me()
333
+
334
+ async def get_dialogs(self, limit: int = 100) -> list[Dialog]:
335
+ return await self._client.get_dialogs(limit)
336
+
337
+ async def export_session_string(self) -> str:
338
+ return await self._client.export_session_string()
339
+
340
+ # raw invoke
341
+
342
+ async def invoke(self, func: Any) -> dict:
343
+ """Invoke a raw TL function object. Returns a deserialized dict."""
344
+ tl_bytes = func.to_bytes()
345
+ resp_bytes = await self._client.invoke_raw(tl_bytes)
346
+ return _tl.deserialize(resp_bytes, _SCHEMA_BY_CID)
347
+
348
+ def __repr__(self) -> str:
349
+ state = "connected" if self._raw else "disconnected"
350
+ return f"Client(session={self.session!r}, {state})"
ferogram/filters.py ADDED
@@ -0,0 +1,174 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ # Filters for use with handler decorators.
15
+ # Each filter is a callable: filter(update) -> bool
16
+
17
+ from __future__ import annotations
18
+ import re
19
+ from typing import Callable, Any
20
+
21
+
22
+ Filter = Callable[[Any], bool]
23
+
24
+
25
+ def _make(fn: Callable) -> Filter:
26
+ return fn
27
+
28
+
29
+ # ---- message filters ----
30
+
31
+ # passes for any update
32
+ all = _make(lambda _: True)
33
+
34
+ # only private chats
35
+ private = _make(lambda m: m.from_id is not None and m.chat_id == m.from_id)
36
+
37
+ # only group/channel chats (chat_id < 0)
38
+ group = _make(lambda m: m.chat_id < 0)
39
+
40
+ # message has text
41
+ text = _make(lambda m: bool(getattr(m, "text", None)))
42
+
43
+ # message has a photo
44
+ photo = _make(lambda m: getattr(m, "has_photo", False))
45
+
46
+ # message has any media
47
+ media = _make(lambda m: getattr(m, "has_media", False))
48
+
49
+ # outgoing message
50
+ outgoing = _make(lambda m: getattr(m, "outgoing", False))
51
+
52
+ # incoming message
53
+ incoming = _make(lambda m: not getattr(m, "outgoing", True))
54
+
55
+ # bot was mentioned in the message
56
+ mentioned = _make(lambda m: getattr(m, "mentioned", False))
57
+
58
+ # message is part of an album/grouped media
59
+ album = _make(lambda m: getattr(m, "grouped_id", None) is not None)
60
+
61
+ # message is a reply to another message
62
+ reply = _make(lambda m: getattr(m, "reply_to_message_id", None) is not None)
63
+
64
+
65
+ def command(*names: str, prefix: str = "/") -> Filter:
66
+ """Match bot commands: /start, /help, etc."""
67
+ lower = {n.lstrip(prefix).lower() for n in names}
68
+ def check(m: Any) -> bool:
69
+ t = getattr(m, "text", None) or ""
70
+ if not t.startswith(prefix):
71
+ return False
72
+ cmd = t[len(prefix):].split()[0].split("@")[0].lower()
73
+ return cmd in lower
74
+ return check
75
+
76
+
77
+ def regex(pattern: str | re.Pattern, flags: int = 0) -> Filter:
78
+ """Match message text against a regex."""
79
+ compiled = re.compile(pattern, flags) if isinstance(pattern, str) else pattern
80
+ def check(m: Any) -> bool:
81
+ t = getattr(m, "text", None) or ""
82
+ return bool(compiled.search(t))
83
+ return check
84
+
85
+
86
+ def user(*user_ids: int) -> Filter:
87
+ """Only pass updates from specific user ids."""
88
+ ids = set(user_ids)
89
+ return _make(lambda m: getattr(m, "from_id", None) in ids)
90
+
91
+
92
+ def chat(*chat_ids: int) -> Filter:
93
+ """Only pass updates from specific chat ids."""
94
+ ids = set(chat_ids)
95
+ return _make(lambda m: getattr(m, "chat_id", None) in ids)
96
+
97
+
98
+ # ---- callback query filters ----
99
+
100
+ def data(value: str) -> Filter:
101
+ """Match callback_query data exactly."""
102
+ return _make(lambda q: getattr(q, "data", None) == value)
103
+
104
+
105
+ def data_regex(pattern: str | re.Pattern, flags: int = 0) -> Filter:
106
+ """Match callback_query data against a regex."""
107
+ compiled = re.compile(pattern, flags) if isinstance(pattern, str) else pattern
108
+ return _make(lambda q: bool(compiled.search(getattr(q, "data", "") or "")))
109
+
110
+
111
+ # ---- inline query filters ----
112
+
113
+ def inline(pattern: str | re.Pattern | None = None, flags: int = 0) -> Filter:
114
+ """Match inline query text. Pass None to match any inline query."""
115
+ if pattern is None:
116
+ return _make(lambda q: True)
117
+ compiled = re.compile(pattern, flags) if isinstance(pattern, str) else pattern
118
+ return _make(lambda q: bool(compiled.search(getattr(q, "query", "") or "")))
119
+
120
+
121
+ # ---- user status filters ----
122
+
123
+ online = _make(lambda s: getattr(s, "online", False))
124
+ offline = _make(lambda s: not getattr(s, "online", True))
125
+
126
+ def status(value: str) -> Filter:
127
+ """Match specific status string: 'online', 'offline', 'recently', etc."""
128
+ return _make(lambda s: getattr(s, "status", None) == value)
129
+
130
+
131
+ # ---- chat action filters ----
132
+
133
+ def action(name: str) -> Filter:
134
+ """Match a specific chat action string, e.g. 'typing', 'upload_photo'."""
135
+ return _make(lambda a: getattr(a, "action", None) == name)
136
+
137
+ typing = action("typing")
138
+
139
+
140
+ # ---- reaction filters ----
141
+
142
+ def reaction(*emojis: str) -> Filter:
143
+ """Match any of the given emoji in new_reactions."""
144
+ s = set(emojis)
145
+ return _make(lambda r: bool(set(getattr(r, "new_reactions", [])) & s))
146
+
147
+
148
+ # ---- raw update filters ----
149
+
150
+ def constructor(cid: int) -> Filter:
151
+ """Match a RawUpdate by constructor_id (hex int, e.g. 0x9e84bc99)."""
152
+ return _make(lambda r: getattr(r, "constructor_id", None) == cid)
153
+
154
+ def update_type(name: str) -> Filter:
155
+ """Match a RawUpdate by type_name string."""
156
+ return _make(lambda r: getattr(r, "type_name", None) == name)
157
+
158
+
159
+ # ---- logic combinators ----
160
+
161
+ def and_(*filters: Filter) -> Filter:
162
+ return _make(lambda m: all(f(m) for f in filters))
163
+
164
+ def or_(*filters: Filter) -> Filter:
165
+ return _make(lambda m: any(f(m) for f in filters))
166
+
167
+ def not_(f: Filter) -> Filter:
168
+ return _make(lambda m: not f(m))
169
+
170
+
171
+ # aliases
172
+ AND = and_
173
+ OR = or_
174
+ NOT = not_
ferogram/logging.py ADDED
@@ -0,0 +1,49 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ import logging
15
+ import sys
16
+
17
+ _LOG = logging.getLogger("ferogram")
18
+
19
+
20
+ def setup(level: int = logging.INFO, fmt: str | None = None) -> None:
21
+ """Configure ferogram's logger to write to stderr.
22
+
23
+ Call once at startup, before app.run(), if you want log output.
24
+
25
+ Parameters
26
+ ----------
27
+ level : logging level constant, e.g. logging.DEBUG / logging.INFO
28
+ fmt : optional format string; defaults to a sensible one-line format
29
+ """
30
+ if _LOG.handlers:
31
+ return
32
+ handler = logging.StreamHandler(sys.stderr)
33
+ handler.setFormatter(logging.Formatter(
34
+ fmt or "%(asctime)s [%(levelname)s] ferogram: %(message)s",
35
+ datefmt="%H:%M:%S",
36
+ ))
37
+ _LOG.addHandler(handler)
38
+ _LOG.setLevel(level)
39
+
40
+
41
+ def get_logger() -> logging.Logger:
42
+ return _LOG
43
+
44
+
45
+ # module-level shortcuts
46
+ debug = _LOG.debug
47
+ info = _LOG.info
48
+ warning = _LOG.warning
49
+ error = _LOG.error
ferogram/py.typed ADDED
File without changes
@@ -0,0 +1,23 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ # ferogram.raw - direct access to the Telegram API
15
+ # Public import path:
16
+ # from ferogram.raw.api.functions import GetHistory
17
+ # from ferogram.raw.api.types import InputPeerUsername
18
+
19
+ from . import tl as _tl
20
+ from . import api
21
+ from .generated import functions, types
22
+
23
+ __all__ = ["api", "functions", "types", "_tl"]
@@ -0,0 +1,15 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ from .functions import * # noqa
15
+ from .types import * # noqa
@@ -0,0 +1,16 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ # Public import path for TL functions.
15
+ # Use: from ferogram.raw.api.functions import GetHistory
16
+ from ..generated.functions import * # noqa
@@ -0,0 +1,16 @@
1
+ # Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ #
4
+ # ferogram is a high-performance Telegram MTProto framework written in Rust.
5
+ # ferogram-py provides Python bindings built on top of the Rust core for
6
+ # building Telegram clients, bots, and applications with a simple API.
7
+ #
8
+ # Rust core: https://github.com/ankit-chaubey/ferogram
9
+ # Python bindings: https://github.com/ankit-chaubey/ferogram-py
10
+ #
11
+ # If you use or modify this code, keep this notice at the top of the file
12
+ # and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
+
14
+ # Public import path for TL types.
15
+ # Use: from ferogram.raw.api.types import InputPeerUsername
16
+ from ..generated.types import * # noqa