hidmart 0.2.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.
hidmart-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.4
2
+ Name: hidmart
3
+ Version: 0.2.0
4
+ Summary: Async Python framework for building token-based bots for Bale Messenger
5
+ Author: HidMart Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/programmersatlantis-hash/HidMart
8
+ Project-URL: Repository, https://github.com/programmersatlantis-hash/HidMart
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx<1.0,>=0.27
12
+
13
+ # HidMart
14
+
15
+ <div align="center">
16
+
17
+ # HidMart
18
+
19
+ ### Async Python Framework for Bale Messenger Bots
20
+
21
+ [![Python](https://img.shields.io/badge/Python-3.9%2B-blue?logo=python)](https://www.python.org/)
22
+ [![GitHub](https://img.shields.io/badge/GitHub-HidMart-black?logo=github)](https://github.com/programmersatlantis-hash/HidMart)
23
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/programmersatlantis-hash/HidMart)
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ Install the latest version of **HidMart** directly from GitHub.
32
+
33
+ ### Install with pip
34
+
35
+ ```bash
36
+ pip install https://github.com/programmersatlantis-hash/HidMart/archive/main.zip --force-reinstall
@@ -0,0 +1,24 @@
1
+ # HidMart
2
+
3
+ <div align="center">
4
+
5
+ # HidMart
6
+
7
+ ### Async Python Framework for Bale Messenger Bots
8
+
9
+ [![Python](https://img.shields.io/badge/Python-3.9%2B-blue?logo=python)](https://www.python.org/)
10
+ [![GitHub](https://img.shields.io/badge/GitHub-HidMart-black?logo=github)](https://github.com/programmersatlantis-hash/HidMart)
11
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/programmersatlantis-hash/HidMart)
12
+
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ Install the latest version of **HidMart** directly from GitHub.
20
+
21
+ ### Install with pip
22
+
23
+ ```bash
24
+ pip install https://github.com/programmersatlantis-hash/HidMart/archive/main.zip --force-reinstall
@@ -0,0 +1,29 @@
1
+ from .bot import Bot
2
+
3
+ from .types import (
4
+ Message,
5
+ User,
6
+ Chat,
7
+ )
8
+
9
+ from .exceptions import (
10
+ HidMartError,
11
+ APIError,
12
+ NetworkError,
13
+ InvalidTokenError,
14
+ )
15
+
16
+
17
+ __version__ = "0.2.0"
18
+
19
+
20
+ __all__ = [
21
+ "Bot",
22
+ "Message",
23
+ "User",
24
+ "Chat",
25
+ "HidMartError",
26
+ "APIError",
27
+ "NetworkError",
28
+ "InvalidTokenError",
29
+ ]
@@ -0,0 +1,425 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ from .client import BaleClient
5
+ from .types import Message
6
+ from .handlers import (
7
+ CommandHandler,
8
+ MessageHandler,
9
+ TextHandler,
10
+ )
11
+
12
+
13
+ logger = logging.getLogger("hidmart")
14
+
15
+
16
+ class Bot:
17
+
18
+ def __init__(
19
+ self,
20
+ token: str,
21
+ poll_interval: float = 1.0,
22
+ timeout: int = 25,
23
+ ):
24
+
25
+ if not token:
26
+ raise ValueError(
27
+ "Bot token is required"
28
+ )
29
+
30
+ self.token = token
31
+
32
+ self.poll_interval = poll_interval
33
+ self.timeout = timeout
34
+
35
+ self.client = BaleClient(
36
+ token
37
+ )
38
+
39
+ self.handlers = []
40
+
41
+ self.running = False
42
+
43
+ self.offset = None
44
+
45
+ self.me = None
46
+
47
+ # -------------------------
48
+ # Handlers
49
+ # -------------------------
50
+
51
+ def on_command(self, *commands):
52
+
53
+ if not commands:
54
+ raise ValueError(
55
+ "At least one command is required"
56
+ )
57
+
58
+ def decorator(callback):
59
+
60
+ for command in commands:
61
+
62
+ self.handlers.append(
63
+ CommandHandler(
64
+ command,
65
+ callback,
66
+ )
67
+ )
68
+
69
+ return callback
70
+
71
+ return decorator
72
+
73
+ def on_message(self):
74
+
75
+ def decorator(callback):
76
+
77
+ self.handlers.append(
78
+ MessageHandler(
79
+ callback
80
+ )
81
+ )
82
+
83
+ return callback
84
+
85
+ return decorator
86
+
87
+ def on_text(self, text):
88
+
89
+ def decorator(callback):
90
+
91
+ self.handlers.append(
92
+ TextHandler(
93
+ text,
94
+ callback,
95
+ )
96
+ )
97
+
98
+ return callback
99
+
100
+ return decorator
101
+
102
+ # -------------------------
103
+ # API methods
104
+ # -------------------------
105
+
106
+ async def send_message(
107
+ self,
108
+ chat_id,
109
+ text,
110
+ **kwargs,
111
+ ):
112
+
113
+ return await self.client.send_message(
114
+ chat_id,
115
+ text,
116
+ **kwargs,
117
+ )
118
+
119
+ async def send_photo(
120
+ self,
121
+ chat_id,
122
+ photo,
123
+ caption=None,
124
+ **kwargs,
125
+ ):
126
+
127
+ return await self.client.send_photo(
128
+ chat_id,
129
+ photo,
130
+ caption,
131
+ **kwargs,
132
+ )
133
+
134
+ async def send_video(
135
+ self,
136
+ chat_id,
137
+ video,
138
+ caption=None,
139
+ **kwargs,
140
+ ):
141
+
142
+ return await self.client.send_video(
143
+ chat_id,
144
+ video,
145
+ caption,
146
+ **kwargs,
147
+ )
148
+
149
+ async def send_audio(
150
+ self,
151
+ chat_id,
152
+ audio,
153
+ caption=None,
154
+ **kwargs,
155
+ ):
156
+
157
+ return await self.client.send_audio(
158
+ chat_id,
159
+ audio,
160
+ caption,
161
+ **kwargs,
162
+ )
163
+
164
+ async def send_document(
165
+ self,
166
+ chat_id,
167
+ document,
168
+ caption=None,
169
+ **kwargs,
170
+ ):
171
+
172
+ return await self.client.send_document(
173
+ chat_id,
174
+ document,
175
+ caption,
176
+ **kwargs,
177
+ )
178
+
179
+ async def send_voice(
180
+ self,
181
+ chat_id,
182
+ voice,
183
+ caption=None,
184
+ **kwargs,
185
+ ):
186
+
187
+ return await self.client.send_voice(
188
+ chat_id,
189
+ voice,
190
+ caption,
191
+ **kwargs,
192
+ )
193
+
194
+ async def send_location(
195
+ self,
196
+ chat_id,
197
+ latitude,
198
+ longitude,
199
+ **kwargs,
200
+ ):
201
+
202
+ return await self.client.send_location(
203
+ chat_id,
204
+ latitude,
205
+ longitude,
206
+ **kwargs,
207
+ )
208
+
209
+ async def edit_message_text(
210
+ self,
211
+ chat_id,
212
+ message_id,
213
+ text,
214
+ **kwargs,
215
+ ):
216
+
217
+ return await self.client.edit_message_text(
218
+ chat_id,
219
+ message_id,
220
+ text,
221
+ **kwargs,
222
+ )
223
+
224
+ async def delete_message(
225
+ self,
226
+ chat_id,
227
+ message_id,
228
+ ):
229
+
230
+ return await self.client.delete_message(
231
+ chat_id,
232
+ message_id,
233
+ )
234
+
235
+ async def get_me(self):
236
+
237
+ self.me = await self.client.get_me()
238
+
239
+ return self.me
240
+
241
+ async def get_chat(self, chat_id):
242
+
243
+ return await self.client.get_chat(
244
+ chat_id
245
+ )
246
+
247
+ async def get_chat_member(
248
+ self,
249
+ chat_id,
250
+ user_id,
251
+ ):
252
+
253
+ return await self.client.get_chat_member(
254
+ chat_id,
255
+ user_id,
256
+ )
257
+
258
+ async def get_updates(
259
+ self,
260
+ offset=None,
261
+ timeout=None,
262
+ limit=None,
263
+ ):
264
+
265
+ if timeout is None:
266
+ timeout = self.timeout
267
+
268
+ return await self.client.get_updates(
269
+ offset=offset,
270
+ timeout=timeout,
271
+ limit=limit,
272
+ )
273
+
274
+ # -------------------------
275
+ # Update processing
276
+ # -------------------------
277
+
278
+ async def process_update(
279
+ self,
280
+ update,
281
+ ):
282
+
283
+ if not isinstance(update, dict):
284
+ return
285
+
286
+ message_data = update.get(
287
+ "message"
288
+ )
289
+
290
+ if not message_data:
291
+ return
292
+
293
+ message = Message.from_dict(
294
+ message_data,
295
+ bot=self,
296
+ )
297
+
298
+ for handler in self.handlers:
299
+
300
+ try:
301
+
302
+ if hasattr(
303
+ handler,
304
+ "matches",
305
+ ):
306
+
307
+ if not handler.matches(
308
+ message
309
+ ):
310
+ continue
311
+
312
+ await handler.run(
313
+ message
314
+ )
315
+
316
+ except Exception:
317
+
318
+ logger.exception(
319
+ "Handler error"
320
+ )
321
+
322
+ # -------------------------
323
+ # Polling
324
+ # -------------------------
325
+
326
+ async def polling(self):
327
+
328
+ self.running = True
329
+
330
+ logger.info(
331
+ "HidMart polling started"
332
+ )
333
+
334
+ while self.running:
335
+
336
+ try:
337
+
338
+ updates = await self.get_updates(
339
+ offset=self.offset
340
+ )
341
+
342
+ if not updates:
343
+
344
+ await asyncio.sleep(
345
+ self.poll_interval
346
+ )
347
+
348
+ continue
349
+
350
+ for update in updates:
351
+
352
+ update_id = update.get(
353
+ "update_id"
354
+ )
355
+
356
+ if update_id is not None:
357
+
358
+ self.offset = (
359
+ update_id + 1
360
+ )
361
+
362
+ await self.process_update(
363
+ update
364
+ )
365
+
366
+ except asyncio.CancelledError:
367
+
368
+ break
369
+
370
+ except Exception:
371
+
372
+ logger.exception(
373
+ "Polling error"
374
+ )
375
+
376
+ await asyncio.sleep(
377
+ 3
378
+ )
379
+
380
+ logger.info(
381
+ "HidMart polling stopped"
382
+ )
383
+
384
+ async def start(self):
385
+
386
+ self.running = True
387
+
388
+ self.me = await self.get_me()
389
+
390
+ logger.info(
391
+ "Bot started: %s",
392
+ self.me,
393
+ )
394
+
395
+ await self.polling()
396
+
397
+ async def stop(self):
398
+
399
+ self.running = False
400
+
401
+ await self.client.close()
402
+
403
+ async def _run(self):
404
+
405
+ try:
406
+
407
+ await self.start()
408
+
409
+ finally:
410
+
411
+ await self.client.close()
412
+
413
+ def run(self):
414
+
415
+ try:
416
+
417
+ asyncio.run(
418
+ self._run()
419
+ )
420
+
421
+ except KeyboardInterrupt:
422
+
423
+ logger.info(
424
+ "HidMart stopped by user"
425
+ )
@@ -0,0 +1,357 @@
1
+ import asyncio
2
+ from typing import Any, Dict, Optional
3
+
4
+ import httpx
5
+
6
+ from .exceptions import (
7
+ APIError,
8
+ NetworkError,
9
+ InvalidTokenError,
10
+ )
11
+
12
+
13
+ class BaleClient:
14
+ """
15
+ Low-level asynchronous client for Bale Bot API.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ token: str,
21
+ base_url: str = "https://tapi.bale.ai",
22
+ timeout: float = 30.0,
23
+ max_retries: int = 3,
24
+ ):
25
+ if not token:
26
+ raise ValueError("Bot token is required")
27
+
28
+ self.token = token
29
+ self.base_url = base_url.rstrip("/")
30
+ self.timeout = timeout
31
+ self.max_retries = max_retries
32
+
33
+ self.http = httpx.AsyncClient(
34
+ timeout=httpx.Timeout(timeout)
35
+ )
36
+
37
+ def _url(self, method: str) -> str:
38
+ return f"{self.base_url}/bot{self.token}/{method}"
39
+
40
+ async def call(
41
+ self,
42
+ method: str,
43
+ data: Optional[Dict[str, Any]] = None,
44
+ ) -> Any:
45
+ """
46
+ Call a Bale Bot API method.
47
+ """
48
+
49
+ payload = data or {}
50
+
51
+ last_error = None
52
+
53
+ for attempt in range(self.max_retries + 1):
54
+
55
+ try:
56
+ response = await self.http.post(
57
+ self._url(method),
58
+ json=payload,
59
+ )
60
+
61
+ response.raise_for_status()
62
+
63
+ try:
64
+ result = response.json()
65
+ except ValueError as exc:
66
+ raise APIError(
67
+ "Invalid JSON response from Bale API"
68
+ ) from exc
69
+
70
+ if not result.get("ok", False):
71
+
72
+ error_code = result.get("error_code")
73
+ description = result.get(
74
+ "description",
75
+ "Bale API request failed",
76
+ )
77
+
78
+ if error_code == 401:
79
+ raise InvalidTokenError(
80
+ description
81
+ )
82
+
83
+ raise APIError(
84
+ description=description,
85
+ error_code=error_code,
86
+ )
87
+
88
+ return result.get("result")
89
+
90
+ except InvalidTokenError:
91
+ raise
92
+
93
+ except APIError:
94
+ raise
95
+
96
+ except httpx.HTTPStatusError as exc:
97
+ last_error = exc
98
+
99
+ if attempt >= self.max_retries:
100
+ raise NetworkError(
101
+ f"HTTP error: {exc.response.status_code}"
102
+ ) from exc
103
+
104
+ except httpx.RequestError as exc:
105
+ last_error = exc
106
+
107
+ if attempt >= self.max_retries:
108
+ raise NetworkError(
109
+ f"Network error: {exc}"
110
+ ) from exc
111
+
112
+ except httpx.HTTPError as exc:
113
+ last_error = exc
114
+
115
+ if attempt >= self.max_retries:
116
+ raise NetworkError(
117
+ f"HTTP error: {exc}"
118
+ ) from exc
119
+
120
+ if attempt < self.max_retries:
121
+ await asyncio.sleep(2 ** attempt)
122
+
123
+ raise NetworkError(
124
+ f"Request failed: {last_error}"
125
+ )
126
+
127
+ async def get_me(self):
128
+ return await self.call("getMe")
129
+
130
+ async def get_updates(
131
+ self,
132
+ offset: Optional[int] = None,
133
+ timeout: int = 25,
134
+ limit: Optional[int] = None,
135
+ ):
136
+ data = {
137
+ "timeout": timeout,
138
+ }
139
+
140
+ if offset is not None:
141
+ data["offset"] = offset
142
+
143
+ if limit is not None:
144
+ data["limit"] = limit
145
+
146
+ return await self.call(
147
+ "getUpdates",
148
+ data,
149
+ )
150
+
151
+ async def send_message(
152
+ self,
153
+ chat_id,
154
+ text: str,
155
+ **kwargs,
156
+ ):
157
+ data = {
158
+ "chat_id": chat_id,
159
+ "text": text,
160
+ }
161
+
162
+ data.update(kwargs)
163
+
164
+ return await self.call(
165
+ "sendMessage",
166
+ data,
167
+ )
168
+
169
+ async def send_photo(
170
+ self,
171
+ chat_id,
172
+ photo,
173
+ caption: Optional[str] = None,
174
+ **kwargs,
175
+ ):
176
+ data = {
177
+ "chat_id": chat_id,
178
+ "photo": photo,
179
+ }
180
+
181
+ if caption is not None:
182
+ data["caption"] = caption
183
+
184
+ data.update(kwargs)
185
+
186
+ return await self.call(
187
+ "sendPhoto",
188
+ data,
189
+ )
190
+
191
+ async def send_video(
192
+ self,
193
+ chat_id,
194
+ video,
195
+ caption: Optional[str] = None,
196
+ **kwargs,
197
+ ):
198
+ data = {
199
+ "chat_id": chat_id,
200
+ "video": video,
201
+ }
202
+
203
+ if caption is not None:
204
+ data["caption"] = caption
205
+
206
+ data.update(kwargs)
207
+
208
+ return await self.call(
209
+ "sendVideo",
210
+ data,
211
+ )
212
+
213
+ async def send_audio(
214
+ self,
215
+ chat_id,
216
+ audio,
217
+ caption: Optional[str] = None,
218
+ **kwargs,
219
+ ):
220
+ data = {
221
+ "chat_id": chat_id,
222
+ "audio": audio,
223
+ }
224
+
225
+ if caption is not None:
226
+ data["caption"] = caption
227
+
228
+ data.update(kwargs)
229
+
230
+ return await self.call(
231
+ "sendAudio",
232
+ data,
233
+ )
234
+
235
+ async def send_document(
236
+ self,
237
+ chat_id,
238
+ document,
239
+ caption: Optional[str] = None,
240
+ **kwargs,
241
+ ):
242
+ data = {
243
+ "chat_id": chat_id,
244
+ "document": document,
245
+ }
246
+
247
+ if caption is not None:
248
+ data["caption"] = caption
249
+
250
+ data.update(kwargs)
251
+
252
+ return await self.call(
253
+ "sendDocument",
254
+ data,
255
+ )
256
+
257
+ async def send_voice(
258
+ self,
259
+ chat_id,
260
+ voice,
261
+ caption: Optional[str] = None,
262
+ **kwargs,
263
+ ):
264
+ data = {
265
+ "chat_id": chat_id,
266
+ "voice": voice,
267
+ }
268
+
269
+ if caption is not None:
270
+ data["caption"] = caption
271
+
272
+ data.update(kwargs)
273
+
274
+ return await self.call(
275
+ "sendVoice",
276
+ data,
277
+ )
278
+
279
+ async def send_location(
280
+ self,
281
+ chat_id,
282
+ latitude: float,
283
+ longitude: float,
284
+ **kwargs,
285
+ ):
286
+ data = {
287
+ "chat_id": chat_id,
288
+ "latitude": latitude,
289
+ "longitude": longitude,
290
+ }
291
+
292
+ data.update(kwargs)
293
+
294
+ return await self.call(
295
+ "sendLocation",
296
+ data,
297
+ )
298
+
299
+ async def edit_message_text(
300
+ self,
301
+ chat_id,
302
+ message_id,
303
+ text: str,
304
+ **kwargs,
305
+ ):
306
+ data = {
307
+ "chat_id": chat_id,
308
+ "message_id": message_id,
309
+ "text": text,
310
+ }
311
+
312
+ data.update(kwargs)
313
+
314
+ return await self.call(
315
+ "editMessageText",
316
+ data,
317
+ )
318
+
319
+ async def delete_message(
320
+ self,
321
+ chat_id,
322
+ message_id,
323
+ ):
324
+ return await self.call(
325
+ "deleteMessage",
326
+ {
327
+ "chat_id": chat_id,
328
+ "message_id": message_id,
329
+ },
330
+ )
331
+
332
+ async def get_chat(
333
+ self,
334
+ chat_id,
335
+ ):
336
+ return await self.call(
337
+ "getChat",
338
+ {
339
+ "chat_id": chat_id,
340
+ },
341
+ )
342
+
343
+ async def get_chat_member(
344
+ self,
345
+ chat_id,
346
+ user_id,
347
+ ):
348
+ return await self.call(
349
+ "getChatMember",
350
+ {
351
+ "chat_id": chat_id,
352
+ "user_id": user_id,
353
+ },
354
+ )
355
+
356
+ async def close(self):
357
+ await self.http.aclose()
@@ -0,0 +1,25 @@
1
+ class HidMartError(Exception):
2
+ """Base exception for HidMart."""
3
+
4
+
5
+ class APIError(HidMartError):
6
+ """Raised when Bale API returns an error."""
7
+
8
+ def __init__(self, description=None, error_code=None):
9
+ self.description = description or "Unknown API error"
10
+ self.error_code = error_code
11
+
12
+ if error_code is not None:
13
+ message = f"[{error_code}] {self.description}"
14
+ else:
15
+ message = self.description
16
+
17
+ super().__init__(message)
18
+
19
+
20
+ class NetworkError(HidMartError):
21
+ """Raised when a network request fails."""
22
+
23
+
24
+ class InvalidTokenError(HidMartError):
25
+ """Raised when the bot token is invalid."""
@@ -0,0 +1,58 @@
1
+ import inspect
2
+
3
+
4
+ class Handler:
5
+
6
+ def __init__(self, callback):
7
+ self.callback = callback
8
+
9
+ async def run(self, message):
10
+ result = self.callback(message)
11
+
12
+ if inspect.isawaitable(result):
13
+ await result
14
+
15
+
16
+ class CommandHandler(Handler):
17
+
18
+ def __init__(self, command, callback):
19
+ super().__init__(callback)
20
+
21
+ self.commands = {
22
+ command.lstrip("/").lower()
23
+ }
24
+
25
+ def matches(self, message):
26
+
27
+ if not message.text:
28
+ return False
29
+
30
+ text = message.text.strip()
31
+
32
+ if not text.startswith("/"):
33
+ return False
34
+
35
+ command = text[1:].split()[0].lower()
36
+
37
+ return command in self.commands
38
+
39
+
40
+ class MessageHandler(Handler):
41
+
42
+ def matches(self, message):
43
+ return bool(message.text)
44
+
45
+
46
+ class TextHandler(Handler):
47
+
48
+ def __init__(self, text, callback):
49
+ super().__init__(callback)
50
+
51
+ self.text = text
52
+
53
+ def matches(self, message):
54
+
55
+ if not message.text:
56
+ return False
57
+
58
+ return message.text == self.text
@@ -0,0 +1,139 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Any, Dict
3
+
4
+
5
+ @dataclass
6
+ class User:
7
+
8
+ id: int
9
+
10
+ first_name: Optional[str] = None
11
+ last_name: Optional[str] = None
12
+ username: Optional[str] = None
13
+
14
+ @classmethod
15
+ def from_dict(cls, data: Dict[str, Any]):
16
+
17
+ return cls(
18
+ id=data.get("id"),
19
+ first_name=data.get("first_name"),
20
+ last_name=data.get("last_name"),
21
+ username=data.get("username"),
22
+ )
23
+
24
+ @property
25
+ def full_name(self):
26
+
27
+ parts = []
28
+
29
+ if self.first_name:
30
+ parts.append(self.first_name)
31
+
32
+ if self.last_name:
33
+ parts.append(self.last_name)
34
+
35
+ return " ".join(parts)
36
+
37
+
38
+ @dataclass
39
+ class Chat:
40
+
41
+ id: int
42
+
43
+ type: Optional[str] = None
44
+ title: Optional[str] = None
45
+ username: Optional[str] = None
46
+
47
+ @classmethod
48
+ def from_dict(cls, data: Dict[str, Any]):
49
+
50
+ return cls(
51
+ id=data.get("id"),
52
+ type=data.get("type"),
53
+ title=data.get("title"),
54
+ username=data.get("username"),
55
+ )
56
+
57
+ @property
58
+ def is_private(self):
59
+
60
+ return self.type == "private"
61
+
62
+ @property
63
+ def is_group(self):
64
+
65
+ return self.type in (
66
+ "group",
67
+ "supergroup",
68
+ )
69
+
70
+
71
+ @dataclass
72
+ class Message:
73
+
74
+ message_id: int
75
+
76
+ chat: Chat
77
+
78
+ from_user: Optional[User] = None
79
+
80
+ text: Optional[str] = None
81
+
82
+ raw: Optional[Dict[str, Any]] = None
83
+
84
+ bot: Any = None
85
+
86
+ @classmethod
87
+ def from_dict(
88
+ cls,
89
+ data: Dict[str, Any],
90
+ bot=None,
91
+ ):
92
+
93
+ user_data = data.get("from")
94
+ chat_data = data.get("chat", {})
95
+
96
+ return cls(
97
+ message_id=data.get("message_id"),
98
+ chat=Chat.from_dict(chat_data),
99
+ from_user=(
100
+ User.from_dict(user_data)
101
+ if user_data
102
+ else None
103
+ ),
104
+ text=data.get("text"),
105
+ raw=data,
106
+ bot=bot,
107
+ )
108
+
109
+ @property
110
+ def id(self):
111
+
112
+ return self.message_id
113
+
114
+ @property
115
+ def sender(self):
116
+
117
+ return self.from_user
118
+
119
+ async def reply(self, text: str, **kwargs):
120
+
121
+ return await self.bot.send_message(
122
+ chat_id=self.chat.id,
123
+ text=text,
124
+ **kwargs,
125
+ )
126
+
127
+ async def answer(self, text: str, **kwargs):
128
+
129
+ return await self.reply(
130
+ text,
131
+ **kwargs,
132
+ )
133
+
134
+ async def delete(self):
135
+
136
+ return await self.bot.delete_message(
137
+ chat_id=self.chat.id,
138
+ message_id=self.message_id,
139
+ )
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.4
2
+ Name: hidmart
3
+ Version: 0.2.0
4
+ Summary: Async Python framework for building token-based bots for Bale Messenger
5
+ Author: HidMart Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/programmersatlantis-hash/HidMart
8
+ Project-URL: Repository, https://github.com/programmersatlantis-hash/HidMart
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx<1.0,>=0.27
12
+
13
+ # HidMart
14
+
15
+ <div align="center">
16
+
17
+ # HidMart
18
+
19
+ ### Async Python Framework for Bale Messenger Bots
20
+
21
+ [![Python](https://img.shields.io/badge/Python-3.9%2B-blue?logo=python)](https://www.python.org/)
22
+ [![GitHub](https://img.shields.io/badge/GitHub-HidMart-black?logo=github)](https://github.com/programmersatlantis-hash/HidMart)
23
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/programmersatlantis-hash/HidMart)
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ Install the latest version of **HidMart** directly from GitHub.
32
+
33
+ ### Install with pip
34
+
35
+ ```bash
36
+ pip install https://github.com/programmersatlantis-hash/HidMart/archive/main.zip --force-reinstall
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ hidmart/__init__.py
4
+ hidmart/bot.py
5
+ hidmart/client.py
6
+ hidmart/exceptions.py
7
+ hidmart/handlers.py
8
+ hidmart/types.py
9
+ hidmart.egg-info/PKG-INFO
10
+ hidmart.egg-info/SOURCES.txt
11
+ hidmart.egg-info/dependency_links.txt
12
+ hidmart.egg-info/requires.txt
13
+ hidmart.egg-info/top_level.txt
14
+ tests/test_basic.py
@@ -0,0 +1 @@
1
+ httpx<1.0,>=0.27
@@ -0,0 +1 @@
1
+ hidmart
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hidmart"
7
+ version = "0.2.0"
8
+ description = "Async Python framework for building token-based bots for Bale Messenger"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+
13
+ authors = [
14
+ {name = "HidMart Team"}
15
+ ]
16
+
17
+ dependencies = [
18
+ "httpx>=0.27,<1.0"
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/programmersatlantis-hash/HidMart"
23
+ Repository = "https://github.com/programmersatlantis-hash/HidMart"
24
+
25
+ [tool.setuptools.packages.find]
26
+ include = ["hidmart*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,69 @@
1
+ from hidmart import (
2
+ Bot,
3
+ Message,
4
+ User,
5
+ Chat,
6
+ APIError,
7
+ NetworkError,
8
+ )
9
+
10
+
11
+ def test_bot_creation():
12
+
13
+ bot = Bot(
14
+ "TEST_TOKEN"
15
+ )
16
+
17
+ assert bot.token == "TEST_TOKEN"
18
+
19
+ assert bot.handlers == []
20
+
21
+ assert bot.offset is None
22
+
23
+
24
+ def test_user():
25
+
26
+ user = User(
27
+ id=123,
28
+ first_name="Ali",
29
+ last_name="Test",
30
+ username="ali",
31
+ )
32
+
33
+ assert user.id == 123
34
+
35
+ assert user.username == "ali"
36
+
37
+ assert user.full_name == "Ali Test"
38
+
39
+
40
+ def test_chat():
41
+
42
+ chat = Chat(
43
+ id=123,
44
+ type="private",
45
+ )
46
+
47
+ assert chat.id == 123
48
+
49
+ assert chat.is_private is True
50
+
51
+ assert chat.is_group is False
52
+
53
+
54
+ def test_message():
55
+
56
+ chat = Chat(
57
+ id=123,
58
+ type="private",
59
+ )
60
+
61
+ message = Message(
62
+ message_id=1,
63
+ chat=chat,
64
+ text="Hello",
65
+ )
66
+
67
+ assert message.id == 1
68
+
69
+ assert message.text == "Hello"