`Release v1.3.0`
Browse files### Features
✨ Add 10 new attractive themes to admin panel
- Includes Crimson Elegance, Coral Bliss, Cyber Neon, Forest Earth, Lavender Dream
- Includes Golden Luxury, Arctic Frost, Cherry Cola, Emerald Tech, Midnight Carbon
- Enhanced theme customization options for better UI/UX
✨ Add genre filter support across all platforms
- Mobile app genre filtering
- Web interface genre filtering
- TV application genre filtering
### Bug Fixes
🐛 Fix missing IMDb ID in TMDb fallback code
- Properly handle IMDb ID extraction when TMDb search fails
- Improved metadata retrieval reliability
🐛 Fix start command behavior
- Resolved minor issues with bot initialization
### Enhancements
♻️ Implement automatic file cleanup on deletion
- Delete Telegram channel files when content is removed from admin panel
- Prevents orphaned files and saves storage space
- Added file deletion for movies, TV shows, episodes, seasons, and quality variants
### Breaking Changes
None
### Dependencies
No new dependencies added
- Backend/__init__.py +1 -1
- Backend/fastapi/themes.py +132 -0
- Backend/helper/database.py +103 -2
- Backend/helper/metadata.py +2 -2
- Backend/pyrofork/plugins/start.py +0 -6
- pyproject.toml +1 -1
- uv.lock +1 -1
|
@@ -11,4 +11,4 @@ StartTime = time()
|
|
| 11 |
USE_DEFAULT_ID: str = None
|
| 12 |
db = Database()
|
| 13 |
|
| 14 |
-
__version__ = "1.
|
|
|
|
| 11 |
USE_DEFAULT_ID: str = None
|
| 12 |
db = Database()
|
| 13 |
|
| 14 |
+
__version__ = "1.3.0"
|
|
@@ -64,11 +64,143 @@ THEMES = {
|
|
| 64 |
"text_secondary": "#CBD5E1"
|
| 65 |
},
|
| 66 |
"css_classes": "theme-dark-professional"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
}
|
| 68 |
}
|
| 69 |
|
|
|
|
| 70 |
def get_theme(theme_name: str = "purple_gradient"):
|
| 71 |
return THEMES.get(theme_name, THEMES["purple_gradient"])
|
| 72 |
|
|
|
|
| 73 |
def get_all_themes():
|
| 74 |
return THEMES
|
|
|
|
| 64 |
"text_secondary": "#CBD5E1"
|
| 65 |
},
|
| 66 |
"css_classes": "theme-dark-professional"
|
| 67 |
+
},
|
| 68 |
+
"crimson_elegance": {
|
| 69 |
+
"name": "Crimson Elegance",
|
| 70 |
+
"colors": {
|
| 71 |
+
"primary": "#DC2626",
|
| 72 |
+
"secondary": "#991B1B",
|
| 73 |
+
"accent": "#F87171",
|
| 74 |
+
"background": "#FEF2F2",
|
| 75 |
+
"card": "#FFFFFF",
|
| 76 |
+
"text": "#1F2937",
|
| 77 |
+
"text_secondary": "#6B7280"
|
| 78 |
+
},
|
| 79 |
+
"css_classes": "theme-crimson-elegance"
|
| 80 |
+
},
|
| 81 |
+
"coral_bliss": {
|
| 82 |
+
"name": "Coral Bliss",
|
| 83 |
+
"colors": {
|
| 84 |
+
"primary": "#FF6B6B",
|
| 85 |
+
"secondary": "#EE5A6F",
|
| 86 |
+
"accent": "#FFB347",
|
| 87 |
+
"background": "#FFF5F5",
|
| 88 |
+
"card": "#FFFFFF",
|
| 89 |
+
"text": "#2D3748",
|
| 90 |
+
"text_secondary": "#718096"
|
| 91 |
+
},
|
| 92 |
+
"css_classes": "theme-coral-bliss"
|
| 93 |
+
},
|
| 94 |
+
"cyber_neon": {
|
| 95 |
+
"name": "Cyber Neon",
|
| 96 |
+
"colors": {
|
| 97 |
+
"primary": "#00FFF0",
|
| 98 |
+
"secondary": "#FF00FF",
|
| 99 |
+
"accent": "#00FF88",
|
| 100 |
+
"background": "#0A0E27",
|
| 101 |
+
"card": "#1A1F3A",
|
| 102 |
+
"text": "#FFFFFF",
|
| 103 |
+
"text_secondary": "#A0AEC0"
|
| 104 |
+
},
|
| 105 |
+
"css_classes": "theme-cyber-neon"
|
| 106 |
+
},
|
| 107 |
+
"forest_earth": {
|
| 108 |
+
"name": "Forest Earth",
|
| 109 |
+
"colors": {
|
| 110 |
+
"primary": "#2D5016",
|
| 111 |
+
"secondary": "#50623A",
|
| 112 |
+
"accent": "#86A789",
|
| 113 |
+
"background": "#F5F5DC",
|
| 114 |
+
"card": "#FFFFFF",
|
| 115 |
+
"text": "#1C1C1C",
|
| 116 |
+
"text_secondary": "#5C5C5C"
|
| 117 |
+
},
|
| 118 |
+
"css_classes": "theme-forest-earth"
|
| 119 |
+
},
|
| 120 |
+
"lavender_dream": {
|
| 121 |
+
"name": "Lavender Dream",
|
| 122 |
+
"colors": {
|
| 123 |
+
"primary": "#B08BBB",
|
| 124 |
+
"secondary": "#9370DB",
|
| 125 |
+
"accent": "#DDA0DD",
|
| 126 |
+
"background": "#FAF5FF",
|
| 127 |
+
"card": "#FFFFFF",
|
| 128 |
+
"text": "#2D3748",
|
| 129 |
+
"text_secondary": "#718096"
|
| 130 |
+
},
|
| 131 |
+
"css_classes": "theme-lavender-dream"
|
| 132 |
+
},
|
| 133 |
+
"golden_luxury": {
|
| 134 |
+
"name": "Golden Luxury",
|
| 135 |
+
"colors": {
|
| 136 |
+
"primary": "#D4AF37",
|
| 137 |
+
"secondary": "#B8860B",
|
| 138 |
+
"accent": "#FFD700",
|
| 139 |
+
"background": "#FFFEF7",
|
| 140 |
+
"card": "#FFFFFF",
|
| 141 |
+
"text": "#1A202C",
|
| 142 |
+
"text_secondary": "#4A5568"
|
| 143 |
+
},
|
| 144 |
+
"css_classes": "theme-golden-luxury"
|
| 145 |
+
},
|
| 146 |
+
"arctic_frost": {
|
| 147 |
+
"name": "Arctic Frost",
|
| 148 |
+
"colors": {
|
| 149 |
+
"primary": "#B0E0E6",
|
| 150 |
+
"secondary": "#4682B4",
|
| 151 |
+
"accent": "#87CEEB",
|
| 152 |
+
"background": "#F0F8FF",
|
| 153 |
+
"card": "#FFFFFF",
|
| 154 |
+
"text": "#1E3A5F",
|
| 155 |
+
"text_secondary": "#5B7C99"
|
| 156 |
+
},
|
| 157 |
+
"css_classes": "theme-arctic-frost"
|
| 158 |
+
},
|
| 159 |
+
"cherry_cola": {
|
| 160 |
+
"name": "Cherry Cola",
|
| 161 |
+
"colors": {
|
| 162 |
+
"primary": "#BF1922",
|
| 163 |
+
"secondary": "#8B0000",
|
| 164 |
+
"accent": "#DC143C",
|
| 165 |
+
"background": "#FFF0F0",
|
| 166 |
+
"card": "#FFFFFF",
|
| 167 |
+
"text": "#1F2937",
|
| 168 |
+
"text_secondary": "#6B7280"
|
| 169 |
+
},
|
| 170 |
+
"css_classes": "theme-cherry-cola"
|
| 171 |
+
},
|
| 172 |
+
"emerald_tech": {
|
| 173 |
+
"name": "Emerald Tech",
|
| 174 |
+
"colors": {
|
| 175 |
+
"primary": "#047857",
|
| 176 |
+
"secondary": "#065F46",
|
| 177 |
+
"accent": "#10B981",
|
| 178 |
+
"background": "#ECFDF5",
|
| 179 |
+
"card": "#FFFFFF",
|
| 180 |
+
"text": "#1F2937",
|
| 181 |
+
"text_secondary": "#4B5563"
|
| 182 |
+
},
|
| 183 |
+
"css_classes": "theme-emerald-tech"
|
| 184 |
+
},
|
| 185 |
+
"midnight_carbon": {
|
| 186 |
+
"name": "Midnight Carbon",
|
| 187 |
+
"colors": {
|
| 188 |
+
"primary": "#3B82F6",
|
| 189 |
+
"secondary": "#1E40AF",
|
| 190 |
+
"accent": "#60A5FA",
|
| 191 |
+
"background": "#111827",
|
| 192 |
+
"card": "#1F2937",
|
| 193 |
+
"text": "#F9FAFB",
|
| 194 |
+
"text_secondary": "#D1D5DB"
|
| 195 |
+
},
|
| 196 |
+
"css_classes": "theme-midnight-carbon"
|
| 197 |
}
|
| 198 |
}
|
| 199 |
|
| 200 |
+
|
| 201 |
def get_theme(theme_name: str = "purple_gradient"):
|
| 202 |
return THEMES.get(theme_name, THEMES["purple_gradient"])
|
| 203 |
|
| 204 |
+
|
| 205 |
def get_all_themes():
|
| 206 |
return THEMES
|
|
@@ -654,10 +654,40 @@ class Database:
|
|
| 654 |
# Delete a Movie or Tvshow completely
|
| 655 |
async def delete_document(self, media_type: str, tmdb_id: int, db_index: int) -> bool:
|
| 656 |
db_key = f"storage_{db_index}"
|
|
|
|
| 657 |
if media_type == "Movie":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
result = await self.dbs[db_key]["movie"].delete_one({"tmdb_id": tmdb_id})
|
| 659 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
result = await self.dbs[db_key]["tv"].delete_one({"tmdb_id": tmdb_id})
|
|
|
|
| 661 |
if result.deleted_count > 0:
|
| 662 |
LOGGER.info(f"{media_type} with tmdb_id {tmdb_id} deleted successfully.")
|
| 663 |
return True
|
|
@@ -669,12 +699,29 @@ class Database:
|
|
| 669 |
async def delete_movie_quality(self, tmdb_id: int, db_index: int, quality: str) -> bool:
|
| 670 |
db_key = f"storage_{db_index}"
|
| 671 |
movie = await self.dbs[db_key]["movie"].find_one({"tmdb_id": tmdb_id})
|
|
|
|
| 672 |
if not movie or "telegram" not in movie:
|
| 673 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
original_len = len(movie["telegram"])
|
| 675 |
movie["telegram"] = [q for q in movie["telegram"] if q.get("quality") != quality]
|
|
|
|
| 676 |
if len(movie["telegram"]) == original_len:
|
| 677 |
-
return False
|
|
|
|
| 678 |
movie['updated_on'] = datetime.utcnow()
|
| 679 |
result = await self.dbs[db_key]["movie"].replace_one({"tmdb_id": tmdb_id}, movie)
|
| 680 |
return result.modified_count > 0
|
|
@@ -683,17 +730,35 @@ class Database:
|
|
| 683 |
async def delete_tv_episode(self, tmdb_id: int, db_index: int, season_number: int, episode_number: int) -> bool:
|
| 684 |
db_key = f"storage_{db_index}"
|
| 685 |
tv = await self.dbs[db_key]["tv"].find_one({"tmdb_id": tmdb_id})
|
|
|
|
| 686 |
if not tv or "seasons" not in tv:
|
| 687 |
return False
|
|
|
|
| 688 |
found = False
|
| 689 |
for season in tv["seasons"]:
|
| 690 |
if season.get("season_number") == season_number:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 691 |
original_len = len(season["episodes"])
|
| 692 |
season["episodes"] = [ep for ep in season["episodes"] if ep.get("episode_number") != episode_number]
|
| 693 |
found = original_len > len(season["episodes"])
|
| 694 |
break
|
|
|
|
| 695 |
if not found:
|
| 696 |
return False
|
|
|
|
| 697 |
tv['updated_on'] = datetime.utcnow()
|
| 698 |
result = await self.dbs[db_key]["tv"].replace_one({"tmdb_id": tmdb_id}, tv)
|
| 699 |
return result.modified_count > 0
|
|
@@ -702,12 +767,31 @@ class Database:
|
|
| 702 |
async def delete_tv_season(self, tmdb_id: int, db_index: int, season_number: int) -> bool:
|
| 703 |
db_key = f"storage_{db_index}"
|
| 704 |
tv = await self.dbs[db_key]["tv"].find_one({"tmdb_id": tmdb_id})
|
|
|
|
| 705 |
if not tv or "seasons" not in tv:
|
| 706 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
original_len = len(tv["seasons"])
|
| 708 |
tv["seasons"] = [s for s in tv["seasons"] if s.get("season_number") != season_number]
|
|
|
|
| 709 |
if len(tv["seasons"]) == original_len:
|
| 710 |
-
return False
|
|
|
|
| 711 |
tv['updated_on'] = datetime.utcnow()
|
| 712 |
result = await self.dbs[db_key]["tv"].replace_one({"tmdb_id": tmdb_id}, tv)
|
| 713 |
return result.modified_count > 0
|
|
@@ -716,23 +800,40 @@ class Database:
|
|
| 716 |
async def delete_tv_quality(self, tmdb_id: int, db_index: int, season_number: int, episode_number: int, quality: str) -> bool:
|
| 717 |
db_key = f"storage_{db_index}"
|
| 718 |
tv = await self.dbs[db_key]["tv"].find_one({"tmdb_id": tmdb_id})
|
|
|
|
| 719 |
if not tv or "seasons" not in tv:
|
| 720 |
return False
|
|
|
|
| 721 |
found = False
|
| 722 |
for season in tv["seasons"]:
|
| 723 |
if season.get("season_number") == season_number:
|
| 724 |
for episode in season["episodes"]:
|
| 725 |
if episode.get("episode_number") == episode_number and "telegram" in episode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
original_len = len(episode["telegram"])
|
| 727 |
episode["telegram"] = [q for q in episode["telegram"] if q.get("quality") != quality]
|
| 728 |
found = original_len > len(episode["telegram"])
|
| 729 |
break
|
|
|
|
| 730 |
if not found:
|
| 731 |
return False
|
| 732 |
tv['updated_on'] = datetime.utcnow()
|
| 733 |
result = await self.dbs[db_key]["tv"].replace_one({"tmdb_id": tmdb_id}, tv)
|
| 734 |
return result.modified_count > 0
|
| 735 |
|
|
|
|
| 736 |
# Get per-DB statistics (movies, tv shows, used size, etc.)
|
| 737 |
async def get_database_stats(self):
|
| 738 |
stats = []
|
|
|
|
| 654 |
# Delete a Movie or Tvshow completely
|
| 655 |
async def delete_document(self, media_type: str, tmdb_id: int, db_index: int) -> bool:
|
| 656 |
db_key = f"storage_{db_index}"
|
| 657 |
+
|
| 658 |
if media_type == "Movie":
|
| 659 |
+
doc = await self.dbs[db_key]["movie"].find_one({"tmdb_id": tmdb_id})
|
| 660 |
+
if doc and "telegram" in doc:
|
| 661 |
+
for quality in doc["telegram"]:
|
| 662 |
+
try:
|
| 663 |
+
old_id = quality.get("id")
|
| 664 |
+
if old_id:
|
| 665 |
+
decoded_data = await decode_string(old_id)
|
| 666 |
+
chat_id = int(f"-100{decoded_data['chat_id']}")
|
| 667 |
+
msg_id = int(decoded_data['msg_id'])
|
| 668 |
+
create_task(delete_message(chat_id, msg_id))
|
| 669 |
+
except Exception as e:
|
| 670 |
+
LOGGER.error(f"Failed to queue file for deletion: {e}")
|
| 671 |
+
|
| 672 |
result = await self.dbs[db_key]["movie"].delete_one({"tmdb_id": tmdb_id})
|
| 673 |
else:
|
| 674 |
+
doc = await self.dbs[db_key]["tv"].find_one({"tmdb_id": tmdb_id})
|
| 675 |
+
if doc and "seasons" in doc:
|
| 676 |
+
for season in doc["seasons"]:
|
| 677 |
+
for episode in season.get("episodes", []):
|
| 678 |
+
for quality in episode.get("telegram", []):
|
| 679 |
+
try:
|
| 680 |
+
old_id = quality.get("id")
|
| 681 |
+
if old_id:
|
| 682 |
+
decoded_data = await decode_string(old_id)
|
| 683 |
+
chat_id = int(f"-100{decoded_data['chat_id']}")
|
| 684 |
+
msg_id = int(decoded_data['msg_id'])
|
| 685 |
+
create_task(delete_message(chat_id, msg_id))
|
| 686 |
+
except Exception as e:
|
| 687 |
+
LOGGER.error(f"Failed to queue file for deletion: {e}")
|
| 688 |
+
|
| 689 |
result = await self.dbs[db_key]["tv"].delete_one({"tmdb_id": tmdb_id})
|
| 690 |
+
|
| 691 |
if result.deleted_count > 0:
|
| 692 |
LOGGER.info(f"{media_type} with tmdb_id {tmdb_id} deleted successfully.")
|
| 693 |
return True
|
|
|
|
| 699 |
async def delete_movie_quality(self, tmdb_id: int, db_index: int, quality: str) -> bool:
|
| 700 |
db_key = f"storage_{db_index}"
|
| 701 |
movie = await self.dbs[db_key]["movie"].find_one({"tmdb_id": tmdb_id})
|
| 702 |
+
|
| 703 |
if not movie or "telegram" not in movie:
|
| 704 |
return False
|
| 705 |
+
|
| 706 |
+
for q in movie["telegram"]:
|
| 707 |
+
if q.get("quality") == quality:
|
| 708 |
+
try:
|
| 709 |
+
old_id = q.get("id")
|
| 710 |
+
if old_id:
|
| 711 |
+
decoded_data = await decode_string(old_id)
|
| 712 |
+
chat_id = int(f"-100{decoded_data['chat_id']}")
|
| 713 |
+
msg_id = int(decoded_data['msg_id'])
|
| 714 |
+
create_task(delete_message(chat_id, msg_id))
|
| 715 |
+
except Exception as e:
|
| 716 |
+
LOGGER.error(f"Failed to queue file for deletion: {e}")
|
| 717 |
+
break
|
| 718 |
+
|
| 719 |
original_len = len(movie["telegram"])
|
| 720 |
movie["telegram"] = [q for q in movie["telegram"] if q.get("quality") != quality]
|
| 721 |
+
|
| 722 |
if len(movie["telegram"]) == original_len:
|
| 723 |
+
return False
|
| 724 |
+
|
| 725 |
movie['updated_on'] = datetime.utcnow()
|
| 726 |
result = await self.dbs[db_key]["movie"].replace_one({"tmdb_id": tmdb_id}, movie)
|
| 727 |
return result.modified_count > 0
|
|
|
|
| 730 |
async def delete_tv_episode(self, tmdb_id: int, db_index: int, season_number: int, episode_number: int) -> bool:
|
| 731 |
db_key = f"storage_{db_index}"
|
| 732 |
tv = await self.dbs[db_key]["tv"].find_one({"tmdb_id": tmdb_id})
|
| 733 |
+
|
| 734 |
if not tv or "seasons" not in tv:
|
| 735 |
return False
|
| 736 |
+
|
| 737 |
found = False
|
| 738 |
for season in tv["seasons"]:
|
| 739 |
if season.get("season_number") == season_number:
|
| 740 |
+
for ep in season["episodes"]:
|
| 741 |
+
if ep.get("episode_number") == episode_number:
|
| 742 |
+
for quality in ep.get("telegram", []):
|
| 743 |
+
try:
|
| 744 |
+
old_id = quality.get("id")
|
| 745 |
+
if old_id:
|
| 746 |
+
decoded_data = await decode_string(old_id)
|
| 747 |
+
chat_id = int(f"-100{decoded_data['chat_id']}")
|
| 748 |
+
msg_id = int(decoded_data['msg_id'])
|
| 749 |
+
create_task(delete_message(chat_id, msg_id))
|
| 750 |
+
except Exception as e:
|
| 751 |
+
LOGGER.error(f"Failed to queue file for deletion: {e}")
|
| 752 |
+
break
|
| 753 |
+
|
| 754 |
original_len = len(season["episodes"])
|
| 755 |
season["episodes"] = [ep for ep in season["episodes"] if ep.get("episode_number") != episode_number]
|
| 756 |
found = original_len > len(season["episodes"])
|
| 757 |
break
|
| 758 |
+
|
| 759 |
if not found:
|
| 760 |
return False
|
| 761 |
+
|
| 762 |
tv['updated_on'] = datetime.utcnow()
|
| 763 |
result = await self.dbs[db_key]["tv"].replace_one({"tmdb_id": tmdb_id}, tv)
|
| 764 |
return result.modified_count > 0
|
|
|
|
| 767 |
async def delete_tv_season(self, tmdb_id: int, db_index: int, season_number: int) -> bool:
|
| 768 |
db_key = f"storage_{db_index}"
|
| 769 |
tv = await self.dbs[db_key]["tv"].find_one({"tmdb_id": tmdb_id})
|
| 770 |
+
|
| 771 |
if not tv or "seasons" not in tv:
|
| 772 |
return False
|
| 773 |
+
|
| 774 |
+
for season in tv["seasons"]:
|
| 775 |
+
if season.get("season_number") == season_number:
|
| 776 |
+
for episode in season.get("episodes", []):
|
| 777 |
+
for quality in episode.get("telegram", []):
|
| 778 |
+
try:
|
| 779 |
+
old_id = quality.get("id")
|
| 780 |
+
if old_id:
|
| 781 |
+
decoded_data = await decode_string(old_id)
|
| 782 |
+
chat_id = int(f"-100{decoded_data['chat_id']}")
|
| 783 |
+
msg_id = int(decoded_data['msg_id'])
|
| 784 |
+
create_task(delete_message(chat_id, msg_id))
|
| 785 |
+
except Exception as e:
|
| 786 |
+
LOGGER.error(f"Failed to queue file for deletion: {e}")
|
| 787 |
+
break
|
| 788 |
+
|
| 789 |
original_len = len(tv["seasons"])
|
| 790 |
tv["seasons"] = [s for s in tv["seasons"] if s.get("season_number") != season_number]
|
| 791 |
+
|
| 792 |
if len(tv["seasons"]) == original_len:
|
| 793 |
+
return False
|
| 794 |
+
|
| 795 |
tv['updated_on'] = datetime.utcnow()
|
| 796 |
result = await self.dbs[db_key]["tv"].replace_one({"tmdb_id": tmdb_id}, tv)
|
| 797 |
return result.modified_count > 0
|
|
|
|
| 800 |
async def delete_tv_quality(self, tmdb_id: int, db_index: int, season_number: int, episode_number: int, quality: str) -> bool:
|
| 801 |
db_key = f"storage_{db_index}"
|
| 802 |
tv = await self.dbs[db_key]["tv"].find_one({"tmdb_id": tmdb_id})
|
| 803 |
+
|
| 804 |
if not tv or "seasons" not in tv:
|
| 805 |
return False
|
| 806 |
+
|
| 807 |
found = False
|
| 808 |
for season in tv["seasons"]:
|
| 809 |
if season.get("season_number") == season_number:
|
| 810 |
for episode in season["episodes"]:
|
| 811 |
if episode.get("episode_number") == episode_number and "telegram" in episode:
|
| 812 |
+
for q in episode["telegram"]:
|
| 813 |
+
if q.get("quality") == quality:
|
| 814 |
+
try:
|
| 815 |
+
old_id = q.get("id")
|
| 816 |
+
if old_id:
|
| 817 |
+
decoded_data = await decode_string(old_id)
|
| 818 |
+
chat_id = int(f"-100{decoded_data['chat_id']}")
|
| 819 |
+
msg_id = int(decoded_data['msg_id'])
|
| 820 |
+
create_task(delete_message(chat_id, msg_id))
|
| 821 |
+
except Exception as e:
|
| 822 |
+
LOGGER.error(f"Failed to queue file for deletion: {e}")
|
| 823 |
+
break
|
| 824 |
+
|
| 825 |
original_len = len(episode["telegram"])
|
| 826 |
episode["telegram"] = [q for q in episode["telegram"] if q.get("quality") != quality]
|
| 827 |
found = original_len > len(episode["telegram"])
|
| 828 |
break
|
| 829 |
+
|
| 830 |
if not found:
|
| 831 |
return False
|
| 832 |
tv['updated_on'] = datetime.utcnow()
|
| 833 |
result = await self.dbs[db_key]["tv"].replace_one({"tmdb_id": tmdb_id}, tv)
|
| 834 |
return result.modified_count > 0
|
| 835 |
|
| 836 |
+
|
| 837 |
# Get per-DB statistics (movies, tv shows, used size, etc.)
|
| 838 |
async def get_database_stats(self):
|
| 839 |
stats = []
|
|
@@ -158,7 +158,7 @@ async def fetch_tv_metadata(title, season, episode, encoded_string, year=None, q
|
|
| 158 |
if use_tmdb and tv_details:
|
| 159 |
return {
|
| 160 |
"tmdb_id": tv_details.id,
|
| 161 |
-
"imdb_id":
|
| 162 |
"title": tv_details.name,
|
| 163 |
"year": getattr(tv_details.first_air_date, "year", 0),
|
| 164 |
"rate": getattr(tv_details, "vote_average", 0) or 0,
|
|
@@ -234,7 +234,7 @@ async def fetch_movie_metadata(title, encoded_string, year=None, quality=None, d
|
|
| 234 |
if use_tmdb and movie_details:
|
| 235 |
return {
|
| 236 |
"tmdb_id": movie_details.id,
|
| 237 |
-
"imdb_id":
|
| 238 |
"title": movie_details.title,
|
| 239 |
"year": getattr(movie_details.release_date, "year", 0),
|
| 240 |
"rate": getattr(movie_details, "vote_average", 0) or 0,
|
|
|
|
| 158 |
if use_tmdb and tv_details:
|
| 159 |
return {
|
| 160 |
"tmdb_id": tv_details.id,
|
| 161 |
+
"imdb_id": tv_details.external_ids.imdb_id,
|
| 162 |
"title": tv_details.name,
|
| 163 |
"year": getattr(tv_details.first_air_date, "year", 0),
|
| 164 |
"rate": getattr(tv_details, "vote_average", 0) or 0,
|
|
|
|
| 234 |
if use_tmdb and movie_details:
|
| 235 |
return {
|
| 236 |
"tmdb_id": movie_details.id,
|
| 237 |
+
"imdb_id": movie_details.external_ids.imdb_id,
|
| 238 |
"title": movie_details.title,
|
| 239 |
"year": getattr(movie_details.release_date, "year", 0),
|
| 240 |
"rate": getattr(movie_details, "vote_average", 0) or 0,
|
|
@@ -2,17 +2,11 @@ from pyrogram import filters, Client, enums
|
|
| 2 |
from Backend.helper.custom_filter import CustomFilters
|
| 3 |
from pyrogram.types import Message
|
| 4 |
from Backend.config import Telegram
|
| 5 |
-
from urllib.parse import urlparse
|
| 6 |
|
| 7 |
@Client.on_message(filters.command('start') & filters.private & CustomFilters.owner, group=10)
|
| 8 |
async def send_start_message(client: Client, message: Message):
|
| 9 |
try:
|
| 10 |
-
parsed_url = urlparse(Telegram.BASE_URL)
|
| 11 |
base_url = Telegram.BASE_URL
|
| 12 |
-
|
| 13 |
-
if parsed_url.hostname.replace('.', '').isdigit() and Telegram.PORT:
|
| 14 |
-
base_url = f"{parsed_url.scheme}://{parsed_url.hostname}:{Telegram.PORT}"
|
| 15 |
-
|
| 16 |
addon_url = f"{base_url}/stremio/manifest.json"
|
| 17 |
|
| 18 |
await message.reply_text(
|
|
|
|
| 2 |
from Backend.helper.custom_filter import CustomFilters
|
| 3 |
from pyrogram.types import Message
|
| 4 |
from Backend.config import Telegram
|
|
|
|
| 5 |
|
| 6 |
@Client.on_message(filters.command('start') & filters.private & CustomFilters.owner, group=10)
|
| 7 |
async def send_start_message(client: Client, message: Message):
|
| 8 |
try:
|
|
|
|
| 9 |
base_url = Telegram.BASE_URL
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
addon_url = f"{base_url}/stremio/manifest.json"
|
| 11 |
|
| 12 |
await message.reply_text(
|
|
@@ -1,6 +1,6 @@
|
|
| 1 |
[project]
|
| 2 |
name = "Telegram-Stremio"
|
| 3 |
-
version = "1.
|
| 4 |
description = "A powerful, self-hosted Telegram Stremio Media Server built with FastAPI, MongoDB, and PyroFork seamlessly integrated with Stremio for automated media streaming and discovery."
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
|
|
|
| 1 |
[project]
|
| 2 |
name = "Telegram-Stremio"
|
| 3 |
+
version = "1.3.0"
|
| 4 |
description = "A powerful, self-hosted Telegram Stremio Media Server built with FastAPI, MongoDB, and PyroFork seamlessly integrated with Stremio for automated media streaming and discovery."
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
|
@@ -901,7 +901,7 @@ wheels = [
|
|
| 901 |
|
| 902 |
[[package]]
|
| 903 |
name = "telegram-stremio"
|
| 904 |
-
version = "1.2.
|
| 905 |
source = { virtual = "." }
|
| 906 |
dependencies = [
|
| 907 |
{ name = "aiofiles" },
|
|
|
|
| 901 |
|
| 902 |
[[package]]
|
| 903 |
name = "telegram-stremio"
|
| 904 |
+
version = "1.2.1"
|
| 905 |
source = { virtual = "." }
|
| 906 |
dependencies = [
|
| 907 |
{ name = "aiofiles" },
|