File size: 12,182 Bytes
5dc4327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
import datetime
from sqlalchemy import Column, Integer, TEXT, DateTime, inspect, func
from mfinder.db.settings_sql import BASE, SESSION
from mfinder.db.files_sql import Files, clean_movie_title, INSERTION_LOCK, reconnect_session, OperationalError

class SearchAnalytics(BASE):
    __tablename__ = "search_analytics"
    query = Column(TEXT, primary_key=True)
    search_count = Column(Integer, default=1)
    last_searched = Column(DateTime, default=datetime.datetime.utcnow)

    def __init__(self, query):
        self.query = query.strip().lower()
        self.search_count = 1
        self.last_searched = datetime.datetime.utcnow()

# Ensure table exists in DB automatically on module load
try:
    session = SESSION()
    bind = session.get_bind()
    inspector = inspect(bind)
    if not inspector.has_table("search_analytics"):
        SearchAnalytics.__table__.create(bind=bind, checkfirst=True)
except Exception as _e:
    pass

async def log_search_query(query_text: str):
    """Logs or increments search query count in analytics database."""
    if not query_text or len(query_text.strip()) < 2:
        return
    
    clean_q = query_text.strip().lower()
    session = SESSION()
    try:
        record = session.query(SearchAnalytics).filter_by(query=clean_q).first()
        if record:
            record.search_count += 1
            record.last_searched = datetime.datetime.utcnow()
        else:
            record = SearchAnalytics(query=clean_q)
            session.add(record)
        session.commit()
    except Exception as e:
        session.rollback()
    finally:
        try:
            SESSION.close()
        except Exception:
            pass

async def cleanup_old_search_analytics():
    """Deletes search records older than 7 days to maintain a clean weekly trending cycle."""
    session = SESSION()
    try:
        one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
        session.query(SearchAnalytics).filter(SearchAnalytics.last_searched < one_week_ago).delete()
        session.commit()
    except Exception:
        session.rollback()
    finally:
        try:
            SESSION.close()
        except Exception:
            pass

def extract_season(text: str) -> str:
    if not text:
        return None
    import re
    # Match S02, s2, S-2, s_2, S(2), Season 2, Season-02, s02e03, etc.
    match = re.search(r'\b[sS](?:eason)?[\s_.-]*\(?(\d+)\)?(?:\b|[eE])', text)
    if match:
        season_num = int(match.group(1))
        return f"S{season_num}"
    return None

async def get_latest_uploaded_movies(limit: int = 25) -> list:
    """

    Retrieves the latest distinct uploaded movies from the database files table,

    sorted by newest created_at timestamp first.

    Returns list of tuples: (cleaned_title, file_count, year, season_str)

    """
    retries = 3
    while retries > 0:
        try:
            with INSERTION_LOCK:
                files = (
                    SESSION.query(Files.file_name, Files.caption)
                    .order_by(Files.created_at.desc(), Files.file_name.asc())
                    .limit(300)
                    .all()
                )

                movie_counts = {}
                movie_years = {}
                movie_seasons = {}
                for f_name, caption in files:
                    title = clean_movie_title(f_name)
                    if title and len(title) >= 2:
                        movie_counts[title] = movie_counts.get(title, 0) + 1
                        if title not in movie_years:
                            import re
                            year_match = re.search(r'\b(19\d{2}|20[0-2]\d|2030)\b', f_name)
                            if year_match:
                                movie_years[title] = year_match.group(1)
                                
                        # Extract season
                        season = extract_season(f_name)
                        if not season and caption:
                            season = extract_season(caption)
                        if season:
                            if title not in movie_seasons:
                                movie_seasons[title] = set()
                            movie_seasons[title].add(season)

                latest_list = []
                for title, count in movie_counts.items():
                    year = movie_years.get(title)
                    seasons = movie_seasons.get(title)
                    season_str = None
                    if seasons:
                        sorted_seasons = sorted(list(seasons), key=lambda s: int(s[1:]))
                        if len(sorted_seasons) > 1:
                            season_str = " & ".join(sorted_seasons)
                        else:
                            season_str = sorted_seasons[0]
                            
                    latest_list.append((title, count, year, season_str))
                    if len(latest_list) >= limit:
                        break

                return latest_list
        except OperationalError:
            reconnect_session()
            retries -= 1
        except Exception as e:
            retries -= 1
        finally:
            try:
                SESSION.close()
            except Exception:
                pass
    return []

async def get_top_trending_movies(limit: int = 10) -> list:
    """

    Retrieves top searched queries from the 7-day weekly cycle in search_analytics.

    Returns list of tuples: (movie_title, file_count)

    """
    # Trigger weekly cleanup of old search records
    await cleanup_old_search_analytics()

    retries = 3
    while retries > 0:
        try:
            with INSERTION_LOCK:
                one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
                top_records = (
                    SESSION.query(SearchAnalytics)
                    .filter(SearchAnalytics.last_searched >= one_week_ago)
                    .order_by(SearchAnalytics.search_count.desc(), SearchAnalytics.last_searched.desc())
                    .limit(50)
                    .all()
                )

                if not top_records:
                    return []

                trending_movies = []
                seen_titles = set()

                for rec in top_records:
                    q = rec.query
                    files = (
                        SESSION.query(Files.file_name, Files.caption)
                        .filter(Files.file_name.ilike(f"%{q}%"))
                        .limit(50)
                        .all()
                    )

                    if files:
                        for f_name, caption in files:
                            title = clean_movie_title(f_name)
                            if title and len(title) >= 2:
                                t_key = title.lower()
                                if t_key not in seen_titles:
                                    seen_titles.add(t_key)
                                    count = SESSION.query(Files).filter(Files.file_name.ilike(f"%{title}%")).count()
                                    
                                    # Extract year and seasons from matched files
                                    import re
                                    year = None
                                    seasons = set()
                                    for fn, cap in files:
                                        if clean_movie_title(fn) == title:
                                            year_match = re.search(r'\b(19\d{2}|20[0-2]\d|2030)\b', fn)
                                            if year_match:
                                                year = year_match.group(1)
                                            season = extract_season(fn)
                                            if not season and cap:
                                                season = extract_season(cap)
                                            if season:
                                                seasons.add(season)
                                            
                                    season_str = None
                                    if seasons:
                                        sorted_seasons = sorted(list(seasons), key=lambda s: int(s[1:]))
                                        if len(sorted_seasons) > 1:
                                            season_str = " & ".join(sorted_seasons)
                                        else:
                                            season_str = sorted_seasons[0]
                                            
                                    trending_movies.append((title, count if count > 0 else len(files), year, season_str))
                                    if len(trending_movies) >= limit:
                                        break
                    if len(trending_movies) >= limit:
                        break

                return trending_movies
        except OperationalError:
            reconnect_session()
            retries -= 1
        except Exception:
            retries -= 1
        finally:
            try:
                SESSION.close()
            except Exception:
                pass
    return []

from sqlalchemy import BigInteger

class UserInteraction(BASE):
    __tablename__ = "user_interactions"
    user_id = Column(BigInteger, primary_key=True)
    last_active = Column(DateTime, default=datetime.datetime.utcnow)

    def __init__(self, user_id):
        self.user_id = user_id
        self.last_active = datetime.datetime.utcnow()

# Ensure table exists in DB automatically on module load
try:
    session = SESSION()
    bind = session.get_bind()
    inspector = inspect(bind)
    if not inspector.has_table("user_interactions"):
        UserInteraction.__table__.create(bind=bind, checkfirst=True)
except Exception as _e:
    pass

async def log_user_interaction(user_id: int):
    if not user_id:
        return
    session = SESSION()
    try:
        record = session.query(UserInteraction).filter_by(user_id=user_id).first()
        if record:
            record.last_active = datetime.datetime.utcnow()
        else:
            record = UserInteraction(user_id=user_id)
            session.add(record)
        session.commit()
    except Exception as e:
        session.rollback()
    finally:
        try:
            SESSION.close()
        except Exception:
            pass

async def get_today_active_users_count() -> int:
    session = SESSION()
    try:
        now = datetime.datetime.utcnow()
        local_now = now + datetime.timedelta(hours=5, minutes=30)
        local_today_start = datetime.datetime(local_now.year, local_now.month, local_now.day)
        utc_today_start = local_today_start - datetime.timedelta(hours=5, minutes=30)
        count = session.query(UserInteraction).filter(UserInteraction.last_active >= utc_today_start).count()
        return count
    except Exception:
        return 0
    finally:
        try:
            SESSION.close()
        except Exception:
            pass

def get_movie_quality_sync(title: str) -> str:
    from mfinder.db.files_sql import Files
    from mfinder.utils.helpers import detect_file_quality
    session = SESSION()
    try:
        files = (
            session.query(Files.file_name)
            .filter(Files.file_name.ilike(f"%{title}%"))
            .order_by(Files.created_at.desc())
            .limit(10)
            .all()
        )
        if not files:
            return "HD"
        for (f_name,) in files:
            if detect_file_quality(f_name) == "Theatre Print [Clear Audio]":
                return "Theatre Print [Clear Audio]"
        return "HD"
    except Exception:
        return "HD"
    finally:
        try:
            session.close()
        except Exception:
            pass