ParsMeet 1.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.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: ParsMeet
3
+ Version: 1.2.0
4
+ Summary: Python library for interacting with CodeMeet platform
5
+ Author-email: MrLunar-ir <mrlunar.ir@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/MrLunar-ir/ParsMeet
8
+ Project-URL: Repository, https://github.com/MrLunar-ir/ParsMeet
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx>=0.27.0
12
+ Requires-Dist: websockets>=12.0
13
+
14
+ README.md
@@ -0,0 +1,7 @@
1
+ from .bot import Bot, Markdown
2
+ from .client import Client
3
+ from .models import Room, User
4
+ from .exceptions import ParsMeetError, ParsMeetAuthError
5
+ from .database import Database
6
+
7
+ __all__ = ["Bot", "Markdown", "Client", "Room", "User", "ParsMeetError", "ParsMeetAuthError", "Database"]
@@ -0,0 +1,2 @@
1
+ from .auth import AuthAPI
2
+ from .rooms import RoomsAPI
@@ -0,0 +1,18 @@
1
+ import httpx
2
+ import asyncio
3
+ from ..exceptions import ParsMeetAuthError
4
+
5
+ class AuthAPI:
6
+ def __init__(self, client: httpx.AsyncClient):
7
+ self.client = client
8
+
9
+ async def _login(self, username: str, password: str) -> str:
10
+ token = self.client.headers.get('Authorization', '').replace('Bearer ', '')
11
+ url = f"/bot{token}/getMe"
12
+ response = await self.client.get(url)
13
+ if response.status_code == 200:
14
+ return response.json().get("result", {}).get("username", "unknown")
15
+ raise ParsMeetAuthError("Invalid credentials")
16
+
17
+ def login(self, username: str, password: str) -> str:
18
+ return asyncio.run(self._login(username, password))
@@ -0,0 +1,78 @@
1
+ import httpx
2
+ from datetime import datetime
3
+ from ..models import Room
4
+
5
+ class RoomsAPI:
6
+ def __init__(self, client: httpx.AsyncClient, loop):
7
+ self.client = client
8
+ self.loop = loop
9
+
10
+ def _run(self, coro):
11
+ return self.loop.run_until_complete(coro)
12
+
13
+ async def _create_room(self, name: str) -> Room:
14
+ return Room(id="mock_room_id", name=name, created_at=datetime.now())
15
+
16
+ def create_room(self, name: str) -> Room:
17
+ return self._run(self._create_room(name))
18
+
19
+ async def _send_message(self, chat_id: str, text: str, parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
20
+ url = f"/bot{self.client.headers.get('Authorization', '').split(' ')[-1]}/sendMessage"
21
+ payload = {"chat_id": chat_id, "text": text, "parse_mode": parse_mode}
22
+ if reply_markup:
23
+ payload["reply_markup"] = reply_markup
24
+ response = await self.client.post(url, json=payload)
25
+ return response.json() if response.status_code == 200 else {"error": response.text}
26
+
27
+ def send_message(self, chat_id: str, text: str, parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
28
+ return self._run(self._send_message(chat_id, text, parse_mode, reply_markup))
29
+
30
+ async def _edit_message(self, chat_id: str, message_id: int, text: str, parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
31
+ url = f"/bot{self.client.headers.get('Authorization', '').split(' ')[-1]}/editMessageText"
32
+ payload = {"chat_id": chat_id, "message_id": message_id, "text": text, "parse_mode": parse_mode}
33
+ if reply_markup:
34
+ payload["reply_markup"] = reply_markup
35
+ response = await self.client.post(url, json=payload)
36
+ return response.json() if response.status_code == 200 else {"error": response.text}
37
+
38
+ def edit_message(self, chat_id: str, message_id: int, text: str, parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
39
+ return self._run(self._edit_message(chat_id, message_id, text, parse_mode, reply_markup))
40
+
41
+ async def _delete_message(self, chat_id: str, message_id: int) -> dict:
42
+ url = f"/bot{self.client.headers.get('Authorization', '').split(' ')[-1]}/deleteMessage"
43
+ payload = {"chat_id": chat_id, "message_id": message_id}
44
+ response = await self.client.post(url, json=payload)
45
+ return response.json() if response.status_code == 200 else {"error": response.text}
46
+
47
+ def delete_message(self, chat_id: str, message_id: int) -> dict:
48
+ return self._run(self._delete_message(chat_id, message_id))
49
+
50
+ async def _send_photo(self, chat_id: str, photo: str, caption: str = "", parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
51
+ url = f"/bot{self.client.headers.get('Authorization', '').split(' ')[-1]}/sendPhoto"
52
+ payload = {"chat_id": chat_id, "photo": photo, "caption": caption, "parse_mode": parse_mode}
53
+ if reply_markup:
54
+ payload["reply_markup"] = reply_markup
55
+ response = await self.client.post(url, json=payload)
56
+ return response.json() if response.status_code == 200 else {"error": response.text}
57
+
58
+ def send_photo(self, chat_id: str, photo: str, caption: str = "", parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
59
+ return self._run(self._send_photo(chat_id, photo, caption, parse_mode, reply_markup))
60
+
61
+ async def _send_document(self, chat_id: str, document: str, caption: str = "", parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
62
+ url = f"/bot{self.client.headers.get('Authorization', '').split(' ')[-1]}/sendDocument"
63
+ payload = {"chat_id": chat_id, "document": document, "caption": caption, "parse_mode": parse_mode}
64
+ if reply_markup:
65
+ payload["reply_markup"] = reply_markup
66
+ response = await self.client.post(url, json=payload)
67
+ return response.json() if response.status_code == 200 else {"error": response.text}
68
+
69
+ def send_document(self, chat_id: str, document: str, caption: str = "", parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
70
+ return self._run(self._send_document(chat_id, document, caption, parse_mode, reply_markup))
71
+
72
+ async def _set_my_commands(self, commands: list) -> dict:
73
+ url = f"/bot{self.client.headers.get('Authorization', '').split(' ')[-1]}/setMyCommands"
74
+ response = await self.client.post(url, json={"commands": commands})
75
+ return response.json() if response.status_code == 200 else {"error": response.text}
76
+
77
+ def set_my_commands(self, commands: list) -> dict:
78
+ return self._run(self._set_my_commands(commands))
@@ -0,0 +1,243 @@
1
+ import asyncio
2
+ import httpx
3
+ import threading
4
+ import time
5
+ import re
6
+ from .api.auth import AuthAPI
7
+ from .api.rooms import RoomsAPI
8
+ from .database import Database
9
+
10
+ class Markdown:
11
+ @staticmethod
12
+ def bold(text): return f"**{text}**"
13
+ @staticmethod
14
+ def italic(text): return f"__{text}__"
15
+ @staticmethod
16
+ def code(text): return f"`{text}`"
17
+ @staticmethod
18
+ def spoiler(text): return f"||{text}||"
19
+ @staticmethod
20
+ def link(text, url): return f"[{text}]({url})"
21
+
22
+ YELLOW = "\033[93m"
23
+ RESET = "\033[0m"
24
+
25
+ class Bot:
26
+ def __init__(self, token: str, base_url: str = "https://botapi.codemeet.chat"):
27
+ self.token = token
28
+ self.loop = asyncio.new_event_loop()
29
+ asyncio.set_event_loop(self.loop)
30
+ self._http_client = httpx.AsyncClient(
31
+ base_url=base_url,
32
+ headers={"Authorization": f"Bearer {token}"},
33
+ timeout=None
34
+ )
35
+ self.auth = AuthAPI(self._http_client)
36
+ self.rooms = RoomsAPI(self._http_client, self.loop)
37
+ self._handlers = {}
38
+ self.db = Database()
39
+ self._last_update_id = 0
40
+ self._auto_task = None
41
+ self._stop_auto = False
42
+ self._stop = False
43
+ self._paused = False
44
+ self._input_thread = None
45
+ self._ads_filter_enabled = False
46
+ self._ads_pattern = re.compile(
47
+ r'(https?://|www\.|t\.me/|@\w+|telegram\.me|bit\.ly|tinyurl\.com|'
48
+ r'@[a-zA-Z0-9_]{4,}|[0-9]{5,}|joinchat|/join/|ads?|promo|'
49
+ r'\b(?:ad|sponsor|buy now|click here|offer|discount|free)\b)',
50
+ re.IGNORECASE
51
+ )
52
+
53
+ def _run(self, coro):
54
+ return self.loop.run_until_complete(coro)
55
+
56
+ def _trigger(self, event_name: str, data):
57
+ if event_name in self._handlers:
58
+ for handler in self._handlers[event_name]:
59
+ handler(data)
60
+
61
+ def on_save_message(self, func=None):
62
+ def decorator(actual_func):
63
+ if "save_message" not in self._handlers:
64
+ self._handlers["save_message"] = []
65
+ self._handlers["save_message"].append(actual_func)
66
+ return actual_func
67
+ if func is None:
68
+ return decorator
69
+ return decorator(func)
70
+
71
+ def on_message_group(self, func=None):
72
+ def decorator(actual_func):
73
+ if "message_group" not in self._handlers:
74
+ self._handlers["message_group"] = []
75
+ self._handlers["message_group"].append(actual_func)
76
+ return actual_func
77
+ if func is None:
78
+ return decorator
79
+ return decorator(func)
80
+
81
+ def on_send_message(self, func=None):
82
+ def decorator(actual_func):
83
+ if "send_message" not in self._handlers:
84
+ self._handlers["send_message"] = []
85
+ self._handlers["send_message"].append(actual_func)
86
+ return actual_func
87
+ if func is None:
88
+ return decorator
89
+ return decorator(func)
90
+
91
+ def on_callback_query(self, func=None):
92
+ def decorator(actual_func):
93
+ if "callback_query" not in self._handlers:
94
+ self._handlers["callback_query"] = []
95
+ self._handlers["callback_query"].append(actual_func)
96
+ return actual_func
97
+ if func is None:
98
+ return decorator
99
+ return decorator(func)
100
+
101
+ def send_message(self, chat_id: str, text: str, parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
102
+ result = self._run(self.rooms._send_message(chat_id, text, parse_mode, reply_markup))
103
+ self._trigger("send_message", {"chat_id": chat_id, "text": text})
104
+ return result
105
+
106
+ def edit_message(self, chat_id: str, message_id: int, text: str, parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
107
+ return self._run(self.rooms._edit_message(chat_id, message_id, text, parse_mode, reply_markup))
108
+
109
+ def delete_message(self, chat_id: str, message_id: int) -> dict:
110
+ return self._run(self.rooms._delete_message(chat_id, message_id))
111
+
112
+ def send_photo(self, chat_id: str, photo: str, caption: str = "", parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
113
+ return self._run(self.rooms._send_photo(chat_id, photo, caption, parse_mode, reply_markup))
114
+
115
+ def send_document(self, chat_id: str, document: str, caption: str = "", parse_mode: str = "Markdown", reply_markup: dict = None) -> dict:
116
+ return self._run(self.rooms._send_document(chat_id, document, caption, parse_mode, reply_markup))
117
+
118
+ def set_my_commands(self, commands: list) -> dict:
119
+ return self._run(self.rooms._set_my_commands(commands))
120
+
121
+ def enable_ads_filter(self):
122
+ self._ads_filter_enabled = True
123
+ print("Ads filter enabled.")
124
+
125
+ def disable_ads_filter(self):
126
+ self._ads_filter_enabled = False
127
+ print("Ads filter disabled.")
128
+
129
+ def auto_send(self, interval: int, callback_func):
130
+ def run_auto():
131
+ self._stop_auto = False
132
+ while not self._stop_auto:
133
+ time.sleep(interval)
134
+ if self._stop_auto:
135
+ break
136
+ result = callback_func()
137
+ if result:
138
+ self.send_message(result["chat_id"], result["text"], result.get("parse_mode", "Markdown"))
139
+ self._auto_task = threading.Thread(target=run_auto, daemon=True)
140
+ self._auto_task.start()
141
+
142
+ def stop_auto_send(self):
143
+ self._stop_auto = True
144
+
145
+ def _listen_input(self):
146
+ while not self._stop:
147
+ try:
148
+ cmd = input(f"{YELLOW}bot Console >>>{RESET} ").strip().lower()
149
+ if cmd == "bot.off()":
150
+ print("Shutting down bot...")
151
+ self.off()
152
+ break
153
+ elif cmd == "bot.pause()":
154
+ self._paused = True
155
+ print("Bot paused.")
156
+ elif cmd == "bot.on()":
157
+ if self._paused:
158
+ self._paused = False
159
+ print("Bot resumed.")
160
+ else:
161
+ print("Bot is already running!")
162
+ elif cmd == "bot.filter.on()":
163
+ self.enable_ads_filter()
164
+ elif cmd == "bot.filter.off()":
165
+ self.disable_ads_filter()
166
+ else:
167
+ print("Invalid command. Valid: bot.off(), bot.pause(), bot.on(), bot.filter.on(), bot.filter.off()")
168
+ except EOFError:
169
+ break
170
+
171
+ def off(self):
172
+ self._stop = True
173
+ self.stop_auto_send()
174
+ self._paused = False
175
+
176
+ def _check_ads(self, text):
177
+ if not self._ads_filter_enabled:
178
+ return False
179
+ return bool(self._ads_pattern.search(text))
180
+
181
+ async def _get_updates(self, timeout: int = 5) -> list:
182
+ if self._paused:
183
+ await asyncio.sleep(1)
184
+ return []
185
+ url = f"/bot{self.token}/getUpdates"
186
+ params = {"timeout": timeout, "offset": self._last_update_id + 1}
187
+ try:
188
+ response = await self._http_client.get(url, params=params)
189
+ if response.status_code == 200:
190
+ return response.json().get("result", [])
191
+ except Exception:
192
+ return []
193
+ return []
194
+
195
+ def run(self, timeout: int = 5):
196
+ print(f"Bot {self.get_me()} is running...")
197
+ print("Type 'bot.off()' to stop, 'bot.pause()' to pause, 'bot.on()' to resume.")
198
+ print("Type 'bot.filter.on()' to enable ads filter, 'bot.filter.off()' to disable.")
199
+ self._input_thread = threading.Thread(target=self._listen_input, daemon=True)
200
+ self._input_thread.start()
201
+ while not self._stop:
202
+ updates = self._run(self._get_updates(timeout))
203
+ for update in updates:
204
+ if "message" in update:
205
+ msg = update["message"]
206
+ if msg.get("from", {}).get("is_bot", False) or msg.get("from", {}).get("username") == self.get_me():
207
+ self._last_update_id = update["update_id"]
208
+ continue
209
+ self._last_update_id = update["update_id"]
210
+ chat_id = msg["chat"]["id"]
211
+ text = msg.get("text", "")
212
+ username = msg.get("from", {}).get("username", "Unknown")
213
+
214
+ # فیلتر تبلیغات
215
+ if self._check_ads(text):
216
+ try:
217
+ self.delete_message(chat_id, msg["message_id"])
218
+ print(f"Deleted ad message from {username}: {text}")
219
+ except Exception:
220
+ pass
221
+
222
+ self.db.save_message(chat_id, text)
223
+ self._trigger("save_message", {"chat_id": chat_id, "text": text, "username": username})
224
+ self._trigger("message_group", {"chat_id": chat_id, "text": text, "username": username})
225
+ elif "callback_query" in update:
226
+ cb = update["callback_query"]
227
+ self._last_update_id = update["update_id"]
228
+ chat_id = cb["message"]["chat"]["id"]
229
+ message_id = cb["message"]["message_id"]
230
+ callback_data = cb.get("data", "")
231
+ username = cb.get("from", {}).get("username", "Unknown")
232
+ self._trigger("callback_query", {"chat_id": chat_id, "message_id": message_id, "data": callback_data, "username": username})
233
+ self.close()
234
+ print("Bot stopped.")
235
+
236
+ def get_me(self):
237
+ return self._run(self.auth._login("", ""))
238
+
239
+ def close(self):
240
+ self.stop_auto_send()
241
+ self.db.close()
242
+ self._run(self._http_client.aclose())
243
+ self.loop.close()
@@ -0,0 +1,27 @@
1
+ import httpx
2
+ import asyncio
3
+ from .api.auth import AuthAPI
4
+ from .api.rooms import RoomsAPI
5
+
6
+ class Client:
7
+ def __init__(self, api_key: str, base_url: str = "https://botapi.codemeet.chat"):
8
+ self.api_key = api_key
9
+ self.base_url = base_url
10
+ self._http_client = httpx.AsyncClient(
11
+ base_url=base_url,
12
+ headers={"Authorization": f"Bearer {api_key}"}
13
+ )
14
+ self.auth = AuthAPI(self._http_client)
15
+ self.rooms = RoomsAPI(self._http_client)
16
+
17
+ def _run(self, coro):
18
+ return asyncio.run(coro)
19
+
20
+ def close(self):
21
+ return self._run(self._http_client.aclose())
22
+
23
+ def __enter__(self):
24
+ return self
25
+
26
+ def __exit__(self, *args):
27
+ self.close()
@@ -0,0 +1,33 @@
1
+ import sqlite3
2
+ import os
3
+ from datetime import datetime
4
+
5
+ class Database:
6
+ def __init__(self, db_name="ParsMeetSaveMessage.db"):
7
+ self.db_name = db_name
8
+ self.conn = sqlite3.connect(db_name)
9
+ self.cursor = self.conn.cursor()
10
+ self._create_table()
11
+
12
+ def _create_table(self):
13
+ self.cursor.execute("""
14
+ CREATE TABLE IF NOT EXISTS messages (
15
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16
+ chat_id TEXT,
17
+ text TEXT,
18
+ timestamp TEXT
19
+ )
20
+ """)
21
+ self.conn.commit()
22
+
23
+ def save_message(self, chat_id, text):
24
+ timestamp = datetime.now().isoformat()
25
+ self.cursor.execute("INSERT INTO messages (chat_id, text, timestamp) VALUES (?, ?, ?)", (chat_id, text, timestamp))
26
+ self.conn.commit()
27
+
28
+ def get_all_messages(self):
29
+ self.cursor.execute("SELECT * FROM messages ORDER BY id DESC")
30
+ return self.cursor.fetchall()
31
+
32
+ def close(self):
33
+ self.conn.close()
@@ -0,0 +1,8 @@
1
+ class ParsMeetError(Exception):
2
+ pass
3
+
4
+ class ParsMeetAuthError(ParsMeetError):
5
+ pass
6
+
7
+ class ParsMeetRoomNotFoundError(ParsMeetError):
8
+ pass
@@ -0,0 +1,20 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import List, Optional
3
+ from datetime import datetime
4
+
5
+ @dataclass
6
+ class User:
7
+ id: str
8
+ username: str
9
+
10
+ @dataclass
11
+ class Room:
12
+ id: str
13
+ name: str
14
+ created_at: datetime
15
+ participants: List[User] = field(default_factory=list)
16
+
17
+ @dataclass
18
+ class CodePayload:
19
+ language: str
20
+ code: str
@@ -0,0 +1 @@
1
+ from .socket import RealtimeClient
File without changes
@@ -0,0 +1,11 @@
1
+ import websockets
2
+
3
+ class RealtimeClient:
4
+ def __init__(self, room_id: str, token: str):
5
+ self.room_id = room_id
6
+ self.token = token
7
+
8
+ async def connect(self):
9
+ uri = f"wss://botapi.codemeet.chat/ws/{self.room_id}?token={self.token}"
10
+ async with websockets.connect(uri) as websocket:
11
+ print(f"Connected to room {self.room_id}")
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: ParsMeet
3
+ Version: 1.2.0
4
+ Summary: Python library for interacting with CodeMeet platform
5
+ Author-email: MrLunar-ir <mrlunar.ir@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/MrLunar-ir/ParsMeet
8
+ Project-URL: Repository, https://github.com/MrLunar-ir/ParsMeet
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx>=0.27.0
12
+ Requires-Dist: websockets>=12.0
13
+
14
+ README.md
@@ -0,0 +1,20 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ ParsMeet/__init__.py
5
+ ParsMeet/bot.py
6
+ ParsMeet/client.py
7
+ ParsMeet/database.py
8
+ ParsMeet/exceptions.py
9
+ ParsMeet/models.py
10
+ ParsMeet.egg-info/PKG-INFO
11
+ ParsMeet.egg-info/SOURCES.txt
12
+ ParsMeet.egg-info/dependency_links.txt
13
+ ParsMeet.egg-info/requires.txt
14
+ ParsMeet.egg-info/top_level.txt
15
+ ParsMeet/api/__init__.py
16
+ ParsMeet/api/auth.py
17
+ ParsMeet/api/rooms.py
18
+ ParsMeet/realtime/__init__.py
19
+ ParsMeet/realtime/py.typed
20
+ ParsMeet/realtime/socket.py
@@ -0,0 +1,2 @@
1
+ httpx>=0.27.0
2
+ websockets>=12.0
@@ -0,0 +1 @@
1
+ ParsMeet
@@ -0,0 +1 @@
1
+ README.md
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ParsMeet"
7
+ version = "1.2.0"
8
+ description = "Python library for interacting with CodeMeet platform"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "MrLunar-ir", email = "mrlunar.ir@gmail.com" }]
13
+ dependencies = [
14
+ "httpx>=0.27.0",
15
+ "websockets>=12.0",
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/MrLunar-ir/ParsMeet"
20
+ Repository = "https://github.com/MrLunar-ir/ParsMeet"
21
+
22
+ [tool.setuptools.packages.find]
23
+ include = ["ParsMeet*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="ParsMeet",
5
+ version="1.2.0",
6
+ packages=find_packages(),
7
+ install_requires=["httpx>=0.27.0", "websockets>=12.0"],
8
+ )