-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase.py
More file actions
276 lines (226 loc) · 9.92 KB
/
Copy pathdatabase.py
File metadata and controls
276 lines (226 loc) · 9.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# database.py - (c) Converted from Go-Filter-Bot by Jisin0
from __future__ import annotations
import logging
import re
import sys
from dataclasses import dataclass, field
from typing import Any, Optional
from pymongo import MongoClient
from pymongo.collection import Collection
from pymongo.database import Database as MongoDatabase
import config
logger = logging.getLogger(__name__)
# ── In-memory caches ───────────────────────────────────────────────────────────
_cached_settings: dict[int, "ChatSettings"] = {}
_connection_cache: dict[int, int] = {} # user_id → chat_id
GLOBAL_FILTER_CHAT_ID: int = 101 # special chat id for global filters
@dataclass
class ChatSettings:
stopped: list[str] = field(default_factory=list)
@dataclass
class Filter:
id: str
chat_id: int
text: str
content: str = ""
file_id: str = ""
markup: list[list[dict[str, str]]] = field(default_factory=list)
alerts: list[str] = field(default_factory=list)
length: int = 0
media_type: str = ""
@dataclass
class User:
id: int
connected_chat: int = 0
# ── Database class ─────────────────────────────────────────────────────────────
class Database:
def __init__(self) -> None:
if not config.MONGODB_URI:
logger.critical("You must set your 'MONGODB_URI' environmental variable :(")
sys.exit(1)
self.client: MongoClient = MongoClient(config.MONGODB_URI)
db: MongoDatabase = self.client["Adv_Auto_Filter"]
self.db: MongoDatabase = db
self.ucol: Collection = db["Users"]
self.col: Collection = db["Main"]
self.mcol: Collection = db["Manual_Filters"]
# ── User operations ────────────────────────────────────────────────────────
def add_user(self, user_id: int) -> None:
if not self.ucol.find_one({"_id": user_id}):
try:
self.ucol.insert_one({"_id": user_id})
except Exception as e:
logger.error("db.add_user: %s", e)
def stats(self) -> str:
users = self.ucol.count_documents({})
mfilters = self.mcol.count_documents({})
chats = self.col.count_documents({})
return (
f"╭ ▸ <b>Users</b> : <code>{users}</code> \n"
f"├ ▸ <b>Filters</b> : <code>{mfilters}</code>\n"
f"╰ ▸ <b>Groups</b> : <code>{chats}</code>"
)
# ── Connection operations ──────────────────────────────────────────────────
def get_connection(self, user_id: int) -> tuple[int, bool]:
if user_id in _connection_cache:
c = _connection_cache[user_id]
return c, c != 0
doc = self.ucol.find_one({"_id": user_id})
if not doc:
_connection_cache[user_id] = 0
return 0, False
connected = doc.get("connected", 0)
_connection_cache[user_id] = connected
return connected, connected != 0
def connect_user(self, user_id: int, chat_id: int) -> None:
try:
self.ucol.update_one(
{"_id": user_id},
{"$set": {"connected": chat_id}},
upsert=True,
)
except Exception as e:
logger.error("db.connect_user: %s", e)
_connection_cache.pop(user_id, None)
def delete_connection(self, user_id: int) -> None:
try:
self.ucol.update_one({"_id": user_id}, {"$unset": {"connected": ""}})
except Exception as e:
logger.error("db.delete_connection: %s", e)
_connection_cache.pop(user_id, None)
# ── Manual filter operations ───────────────────────────────────────────────
def save_mfilter(self, f: Filter) -> None:
doc = {
"_id": f.id,
"group_id": f.chat_id,
"text": f.text,
"content": f.content,
"file": f.file_id,
"button": f.markup,
"alert": f.alerts,
"length": f.length,
"mediaType": f.media_type,
}
try:
self.mcol.insert_one(doc)
except Exception as e:
logger.error("db.save_mfilter: %s", e)
def get_mfilter(self, chat_id: int, key: str) -> tuple[Optional[dict], bool]:
doc = self.mcol.find_one({"group_id": chat_id, "text": key})
if doc:
return doc, True
return None, False
def delete_mfilter(self, chat_id: int, key: str) -> None:
try:
self.mcol.delete_one({"group_id": chat_id, "text": key})
except Exception as e:
logger.error("db.delete_mfilter: %s", e)
def get_mfilters(self, chat_id: int) -> list[dict]:
pipeline = [
{"$match": {"group_id": chat_id}},
{"$sort": {"length": -1}},
]
return list(self.mcol.aggregate(pipeline))
def string_mfilter(self, chat_id: int) -> str:
docs = self.get_mfilters(chat_id)
return "".join(f"\n• <code>{d.get('text', '')}</code>" for d in docs)
def search_mfilter_classic(self, chat_id: int, input_text: str) -> list[Filter]:
"""Fetch all filters for chat and do regex matching locally."""
results: list[Filter] = []
docs = self.get_mfilters(chat_id)
for doc in docs:
text = doc.get("text", "")
pattern = re.compile(r"(?i)( |^|[^\w])" + re.escape(text) + r"( |$|[^\w])")
if pattern.search(input_text):
results.append(_doc_to_filter(doc))
return results
def search_mfilter_new(
self, chat_id: int, fields: list[str], multi_filter: bool
) -> list[Filter]:
"""Push regex query to MongoDB."""
if not fields:
return []
escaped = [re.escape(f) for f in fields]
pattern = "(?i).*\\b(" + "|".join(escaped) + ")\\b.*"
mongo_filter = {"group_id": chat_id, "text": {"$regex": pattern}}
if not multi_filter:
doc = self.mcol.find_one(mongo_filter)
if doc:
return [_doc_to_filter(doc)]
return []
docs = list(self.mcol.find(mongo_filter).sort("length", -1))
return [_doc_to_filter(d) for d in docs]
# ── Global filter / chat settings ─────────────────────────────────────────
def get_cached_setting(self, chat_id: int) -> ChatSettings:
if chat_id not in _cached_settings:
self.recache_settings(chat_id)
return _cached_settings.get(chat_id, ChatSettings())
def recache_settings(self, chat_id: int) -> None:
doc = self.col.find_one({"_id": chat_id})
if not doc:
_cached_settings[chat_id] = ChatSettings()
else:
_cached_settings[chat_id] = ChatSettings(
stopped=doc.get("stopped", [])
)
def set_default_settings(self, chat_id: int) -> None:
if not self.col.find_one({"_id": chat_id}):
try:
self.col.insert_one({"_id": chat_id})
except Exception:
pass
def set_chat_setting(self, chat_id: int, key: str, value: Any) -> None:
self.set_default_settings(chat_id)
try:
self.col.update_one({"_id": chat_id}, {"$set": {key: value}})
except Exception as e:
logger.error("db.set_chat_setting: %s", e)
self.recache_settings(chat_id)
def stop_gfilter(self, chat_id: int, key: str) -> None:
try:
self.col.update_one(
{"_id": chat_id},
{"$addToSet": {"stopped": key}},
upsert=True,
)
except Exception as e:
logger.error("db.stop_gfilter: %s", e)
self.recache_settings(chat_id)
def start_gfilter(self, chat_id: int, key: str) -> None:
settings = self.get_cached_setting(chat_id)
new_stopped = [k for k in settings.stopped if k != key]
try:
self.col.update_one(
{"_id": chat_id}, {"$set": {"stopped": new_stopped}}
)
except Exception as e:
logger.error("db.start_gfilter: %s", e)
self.recache_settings(chat_id)
def get_alert(self, unique_id: str, index: int) -> str:
doc = self.mcol.find_one({"_id": unique_id})
if not doc:
return "404: Content Not Found !"
alerts = doc.get("alert", [])
if index >= len(alerts):
return "404: Content Not Found !"
return alerts[index]
# ── Helper ─────────────────────────────────────────────────────────────────────
def _doc_to_filter(doc: dict) -> Filter:
return Filter(
id=str(doc.get("_id", "")),
chat_id=doc.get("group_id", 0),
text=doc.get("text", ""),
content=doc.get("content", ""),
file_id=doc.get("file", ""),
markup=doc.get("button", []),
alerts=doc.get("alert", []),
length=doc.get("length", 0),
media_type=doc.get("mediaType", ""),
)
# ── Singleton ──────────────────────────────────────────────────────────────────
_db_instance: Optional[Database] = None
def get_db() -> Database:
global _db_instance
if _db_instance is None:
_db_instance = Database()
return _db_instance