Spaces:
Running
Running
| 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 | |