KekikStream 0.2.0__tar.gz → 0.5.3__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.
Files changed (68) hide show
  1. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/CLI/__init__.py +1 -1
  2. kekikstream-0.5.3/KekikStream/CLI/pypi_kontrol.py +30 -0
  3. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/ExtractorBase.py +13 -2
  4. kekikstream-0.5.3/KekikStream/Core/ExtractorLoader.py +82 -0
  5. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/ExtractorModels.py +2 -0
  6. kekikstream-0.5.3/KekikStream/Core/MediaHandler.py +108 -0
  7. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/PluginBase.py +6 -5
  8. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/PluginLoader.py +3 -5
  9. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/PluginModels.py +6 -16
  10. kekikstream-0.5.3/KekikStream/Extractors/ContentX.py +80 -0
  11. kekikstream-0.5.3/KekikStream/Extractors/FourCX.py +7 -0
  12. kekikstream-0.5.3/KekikStream/Extractors/FourPichive.py +7 -0
  13. kekikstream-0.5.3/KekikStream/Extractors/FourPlayRu.py +7 -0
  14. kekikstream-0.5.3/KekikStream/Extractors/HDStreamAble.py +7 -0
  15. kekikstream-0.5.3/KekikStream/Extractors/Hotlinger.py +7 -0
  16. kekikstream-0.5.3/KekikStream/Extractors/MixPlayHD.py +42 -0
  17. kekikstream-0.5.3/KekikStream/Extractors/Odnoklassniki.py +106 -0
  18. kekikstream-0.5.3/KekikStream/Extractors/OkRuHTTP.py +7 -0
  19. kekikstream-0.5.3/KekikStream/Extractors/OkRuSSL.py +7 -0
  20. kekikstream-0.5.3/KekikStream/Extractors/PeaceMakerst.py +57 -0
  21. kekikstream-0.5.3/KekikStream/Extractors/Pichive.py +7 -0
  22. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/PixelDrain.py +1 -1
  23. kekikstream-0.5.3/KekikStream/Extractors/PlayRu.py +7 -0
  24. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/RapidVid.py +9 -10
  25. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/SibNet.py +1 -1
  26. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/Sobreatsesuyp.py +5 -6
  27. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/TRsTX.py +5 -6
  28. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/TauVideo.py +11 -10
  29. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/TurboImgz.py +9 -12
  30. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/VidMoly.py +25 -33
  31. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/VidMoxy.py +1 -1
  32. kekikstream-0.5.3/KekikStream/Extractors/VideoSeyred.py +47 -0
  33. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/MediaManager.py +1 -1
  34. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/UIManager.py +4 -0
  35. kekikstream-0.5.3/KekikStream/Plugins/DiziBox.py +138 -0
  36. kekikstream-0.5.3/KekikStream/Plugins/Dizilla.py +95 -0
  37. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/FilmMakinesi.py +8 -8
  38. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/FullHDFilmizlesene.py +5 -4
  39. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/JetFilmizle.py +4 -8
  40. kekikstream-0.5.3/KekikStream/Plugins/RecTV.py +111 -0
  41. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/SezonlukDizi.py +5 -5
  42. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/SineWix.py +4 -1
  43. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/UgurFilm.py +3 -3
  44. kekikstream-0.5.3/KekikStream/__init__.py +255 -0
  45. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/__main__.py +1 -1
  46. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/requirements.txt +2 -1
  47. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/PKG-INFO +4 -5
  48. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/SOURCES.txt +18 -1
  49. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/requires.txt +2 -1
  50. {kekikstream-0.2.0 → kekikstream-0.5.3}/PKG-INFO +4 -5
  51. {kekikstream-0.2.0 → kekikstream-0.5.3}/README.md +1 -3
  52. {kekikstream-0.2.0 → kekikstream-0.5.3}/setup.py +3 -2
  53. kekikstream-0.2.0/KekikStream/CLI/check_update.py +0 -33
  54. kekikstream-0.2.0/KekikStream/Core/ExtractorLoader.py +0 -61
  55. kekikstream-0.2.0/KekikStream/Core/MediaHandler.py +0 -68
  56. kekikstream-0.2.0/KekikStream/__init__.py +0 -236
  57. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/__init__.py +0 -0
  58. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/CloseLoad.py +0 -0
  59. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/MailRu.py +0 -0
  60. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/ExtractorManager.py +0 -0
  61. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/PluginManager.py +0 -0
  62. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/__init__.py +0 -0
  63. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/dependency_links.txt +0 -0
  64. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/entry_points.txt +0 -0
  65. {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/top_level.txt +0 -0
  66. {kekikstream-0.2.0 → kekikstream-0.5.3}/LICENSE +0 -0
  67. {kekikstream-0.2.0 → kekikstream-0.5.3}/MANIFEST.in +0 -0
  68. {kekikstream-0.2.0 → kekikstream-0.5.3}/setup.cfg +0 -0
@@ -1,4 +1,4 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
3
  from Kekik.cli import konsol, cikis_yap, hata_salla, log_salla, hata_yakala, bellek_temizle, temizle
4
- from .check_update import check_and_update_package
4
+ from .pypi_kontrol import pypi_kontrol_guncelle
@@ -0,0 +1,30 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from . import konsol
4
+ from rich.panel import Panel
5
+ from pkg_resources import get_distribution
6
+ from requests import get
7
+ from subprocess import check_call
8
+ import sys
9
+
10
+ def pypi_kontrol_guncelle(paket_adi: str):
11
+ try:
12
+ konsol.print(f"[bold cyan] {paket_adi} Güncellemesi kontrol ediliyor...[/bold cyan]")
13
+ mevcut_surum = get_distribution(paket_adi).version
14
+ konsol.print(Panel(f"[cyan]Yüklü sürüm:[/cyan] [bold yellow]{mevcut_surum}[/bold yellow]"))
15
+
16
+ istek = get(f"https://pypi.org/pypi/{paket_adi}/json")
17
+ if istek.status_code == 200:
18
+ son_surum = istek.json()["info"]["version"]
19
+ konsol.print(Panel(f"[cyan]En son sürüm:[/cyan] [bold green]{son_surum}[/bold green]"))
20
+
21
+ if mevcut_surum != son_surum:
22
+ konsol.print(f"[bold red]{paket_adi} güncelleniyor...[/bold red]")
23
+ check_call([sys.executable, "-m", "pip", "install", "--upgrade", paket_adi, "--break-system-packages"])
24
+ konsol.print(f"[bold green]{paket_adi} güncellendi![/bold green]")
25
+ else:
26
+ konsol.print(f"[bold green]{paket_adi} zaten güncel.[/bold green]")
27
+ else:
28
+ konsol.print("[bold red]PyPI'ye erişilemiyor. Güncelleme kontrolü atlanıyor.[/bold red]")
29
+ except Exception as hata:
30
+ konsol.print(f"[bold red]Güncelleme kontrolü sırasında hata oluştu: {hata}[/bold red]")
@@ -2,6 +2,7 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from httpx import AsyncClient, Timeout
5
+ from cloudscraper import CloudScraper
5
6
  from typing import Optional
6
7
  from .ExtractorModels import ExtractResult
7
8
 
@@ -15,8 +16,9 @@ class ExtractorBase(ABC):
15
16
  "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5)",
16
17
  "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
17
18
  },
18
- timeout = Timeout(10.0),
19
+ timeout = Timeout(10.0)
19
20
  )
21
+ self.cloudscraper = CloudScraper()
20
22
 
21
23
  def can_handle_url(self, url: str) -> bool:
22
24
  """URL'nin bu extractor tarafından işlenip işlenemeyeceğini kontrol eder."""
@@ -28,4 +30,13 @@ class ExtractorBase(ABC):
28
30
  pass
29
31
 
30
32
  async def close(self):
31
- await self.oturum.aclose()
33
+ await self.oturum.aclose()
34
+
35
+ def fix_url(self, url: str) -> str:
36
+ if not url:
37
+ return ""
38
+
39
+ if url.startswith("http") or url.startswith("{\""):
40
+ return url
41
+
42
+ return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
@@ -0,0 +1,82 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from ..CLI import konsol, cikis_yap
4
+ from .ExtractorBase import ExtractorBase
5
+ from pathlib import Path
6
+ import os, importlib.util
7
+
8
+ class ExtractorLoader:
9
+ def __init__(self, extractors_dir: str):
10
+ self.local_extractors_dir = Path(extractors_dir)
11
+ self.global_extractors_dir = Path(__file__).parent.parent / extractors_dir
12
+ if not self.local_extractors_dir.exists() and not self.global_extractors_dir.exists():
13
+ konsol.log(f"[red][!] Extractor dizini bulunamadı: {self.global_extractors_dir}[/red]")
14
+ cikis_yap(False)
15
+
16
+ def load_all(self) -> list[ExtractorBase]:
17
+ extractors = []
18
+
19
+ # Global Extractor'ları yükle
20
+ if self.global_extractors_dir.exists():
21
+ konsol.log(f"[green][*] Global Extractor dizininden yükleniyor: {self.global_extractors_dir}[/green]")
22
+ global_extractors = self._load_from_directory(self.global_extractors_dir)
23
+ konsol.log(f"[green]Global Extractor'lar: {[e.__name__ for e in global_extractors]}[/green]")
24
+ extractors.extend(global_extractors)
25
+
26
+ # Yerel Extractor'ları yükle
27
+ if self.local_extractors_dir.exists():
28
+ konsol.log(f"[green][*] Yerel Extractor dizininden yükleniyor: {self.local_extractors_dir}[/green]")
29
+ local_extractors = self._load_from_directory(self.local_extractors_dir)
30
+ konsol.log(f"[green]Yerel Extractor'lar: {[e.__name__ for e in local_extractors]}[/green]")
31
+ extractors.extend(local_extractors)
32
+
33
+ # Benzersizliği sağlama (modül adı + sınıf adı bazında)
34
+ unique_extractors = []
35
+ seen = set()
36
+ for ext in extractors:
37
+ identifier = f"{ext.__module__}.{ext.__name__}"
38
+ if identifier not in seen:
39
+ unique_extractors.append(ext)
40
+ seen.add(identifier)
41
+
42
+ konsol.log(f"[blue]Sonuç Extractor'lar: {[e.__name__ for e in unique_extractors]}[/blue]")
43
+
44
+ if not unique_extractors:
45
+ konsol.log("[yellow][!] Yüklenecek bir Extractor bulunamadı![/yellow]")
46
+
47
+ return unique_extractors
48
+
49
+ def _load_from_directory(self, directory: Path) -> list[ExtractorBase]:
50
+ extractors = []
51
+ for file in os.listdir(directory):
52
+ if file.endswith(".py") and not file.startswith("__"):
53
+ module_name = file[:-3]
54
+ konsol.log(f"[cyan]Modül yükleniyor: {module_name}[/cyan]")
55
+ if extractor := self._load_extractor(directory, module_name):
56
+ konsol.log(f"[magenta]Extractor bulundu: {extractor.__name__}[/magenta]")
57
+ extractors.append(extractor)
58
+
59
+ konsol.log(f"[yellow]{directory} dizininden yüklenen Extractor'lar: {[e.__name__ for e in extractors]}[/yellow]")
60
+ return extractors
61
+
62
+ def _load_extractor(self, directory: Path, module_name: str):
63
+ try:
64
+ path = directory / f"{module_name}.py"
65
+ spec = importlib.util.spec_from_file_location(module_name, path)
66
+ if not spec or not spec.loader:
67
+ return None
68
+
69
+ module = importlib.util.module_from_spec(spec)
70
+ spec.loader.exec_module(module)
71
+
72
+ # Yalnızca doğru modülden gelen ExtractorBase sınıflarını yükle
73
+ for attr in dir(module):
74
+ obj = getattr(module, attr)
75
+ if obj.__module__ == module_name and isinstance(obj, type) and issubclass(obj, ExtractorBase) and obj is not ExtractorBase:
76
+ konsol.log(f"[green]Yüklenen sınıf: {module_name}.{obj.__name__} ({obj.__module__}.{obj.__name__})[/green]")
77
+ return obj
78
+
79
+ except Exception as hata:
80
+ konsol.log(f"[red][!] Extractor yüklenirken hata oluştu: {module_name}\nHata: {hata}")
81
+
82
+ return None
@@ -3,11 +3,13 @@
3
3
  from pydantic import BaseModel
4
4
  from typing import List, Optional
5
5
 
6
+
6
7
  class Subtitle(BaseModel):
7
8
  """Altyazı modeli."""
8
9
  name : str
9
10
  url : str
10
11
 
12
+
11
13
  class ExtractResult(BaseModel):
12
14
  """Extractor'ın döndürmesi gereken sonuç modeli."""
13
15
  name : str
@@ -0,0 +1,108 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from ..CLI import konsol
4
+ from .ExtractorModels import ExtractResult
5
+ import subprocess, os
6
+
7
+ class MediaHandler:
8
+ def __init__(self, title: str = "KekikStream", headers: dict = None):
9
+ if headers is None:
10
+ headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5)"}
11
+
12
+ self.headers = headers
13
+ self.title = title
14
+
15
+ def play_media(self, extract_data: ExtractResult):
16
+ if subprocess.check_output(['uname', '-o']).strip() == b'Android':
17
+ return self.play_with_android_mxplayer(extract_data)
18
+
19
+ if "Cookie" in self.headers or extract_data.subtitles:
20
+ return self.play_with_mpv(extract_data)
21
+
22
+ return self.play_with_vlc(extract_data)
23
+
24
+ def play_with_vlc(self, extract_data: ExtractResult):
25
+ try:
26
+ vlc_command = ["vlc", "--quiet"]
27
+
28
+ if self.title:
29
+ vlc_command.extend([
30
+ f"--meta-title={self.title}",
31
+ f"--input-title-format={self.title}"
32
+ ])
33
+
34
+ if "User-Agent" in self.headers:
35
+ vlc_command.append(f"--http-user-agent={self.headers.get('User-Agent')}")
36
+
37
+ if "Referer" in self.headers:
38
+ vlc_command.append(f"--http-referrer={self.headers.get('Referer')}")
39
+
40
+ vlc_command.extend(
41
+ f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
42
+ )
43
+ vlc_command.append(extract_data.url)
44
+
45
+ with open(os.devnull, "w") as devnull:
46
+ subprocess.run(vlc_command, stdout=devnull, stderr=devnull, check=True)
47
+
48
+ except subprocess.CalledProcessError as hata:
49
+ konsol.print(f"[red]VLC oynatma hatası: {hata}[/red]")
50
+ konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
51
+ except FileNotFoundError:
52
+ konsol.print("[red]VLC bulunamadı! VLC kurulu olduğundan emin olun.[/red]")
53
+ konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
54
+
55
+ def play_with_mpv(self, extract_data: ExtractResult):
56
+ try:
57
+ mpv_command = ["mpv", "--really-quiet"]
58
+
59
+ if self.title:
60
+ mpv_command.append(f"--force-media-title={self.title}")
61
+
62
+ for key, value in self.headers.items():
63
+ mpv_command.append(f"--http-header-fields={key}: {value}")
64
+
65
+ mpv_command.extend(
66
+ f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
67
+ )
68
+ mpv_command.append(extract_data.url)
69
+
70
+ with open(os.devnull, "w") as devnull:
71
+ subprocess.run(mpv_command, stdout=devnull, stderr=devnull, check=True)
72
+
73
+ except subprocess.CalledProcessError as hata:
74
+ konsol.print(f"[red]mpv oynatma hatası: {hata}[/red]")
75
+ konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
76
+ except FileNotFoundError:
77
+ konsol.print("[red]mpv bulunamadı! mpv kurulu olduğundan emin olun.[/red]")
78
+ konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
79
+
80
+ def play_with_android_mxplayer(self, extract_data: ExtractResult):
81
+ paketler = [
82
+ "com.mxtech.videoplayer.ad/.ActivityScreen", # Free sürüm
83
+ "com.mxtech.videoplayer.pro/.ActivityScreen" # Pro sürüm
84
+ ]
85
+
86
+ for paket in paketler:
87
+ try:
88
+ android_command = [
89
+ "am", "start",
90
+ "-a", "android.intent.action.VIEW",
91
+ "-d", extract_data.url,
92
+ "-n", paket
93
+ ]
94
+
95
+ if self.title:
96
+ android_command.extend(["--es", "title", self.title])
97
+
98
+ with open(os.devnull, "w") as devnull:
99
+ subprocess.run(android_command, stdout=devnull, stderr=devnull, check=True)
100
+
101
+ return
102
+
103
+ except subprocess.CalledProcessError as hata:
104
+ konsol.print(f"[red]{paket} oynatma hatası: {hata}[/red]")
105
+ konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
106
+ except FileNotFoundError:
107
+ konsol.print(f"Paket: {paket}, Hata: MX Player kurulu değil")
108
+ konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
@@ -2,6 +2,7 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from httpx import AsyncClient, Timeout
5
+ from cloudscraper import CloudScraper
5
6
  from .PluginModels import SearchResult, MovieInfo
6
7
  from .MediaHandler import MediaHandler
7
8
  from urllib.parse import urljoin
@@ -21,6 +22,9 @@ class PluginBase(ABC):
21
22
  timeout = Timeout(10.0),
22
23
  )
23
24
  self.media_handler = MediaHandler()
25
+ self.cloudscraper = CloudScraper()
26
+ self.oturum.headers.update(self.cloudscraper.headers)
27
+ self.oturum.cookies.update(self.cloudscraper.cookies)
24
28
 
25
29
  @abstractmethod
26
30
  async def search(self, query: str) -> list[SearchResult]:
@@ -43,14 +47,11 @@ class PluginBase(ABC):
43
47
  def fix_url(self, url: str) -> str:
44
48
  if not url:
45
49
  return ""
46
-
50
+
47
51
  if url.startswith("http") or url.startswith("{\""):
48
52
  return url
49
53
 
50
- if url.startswith("//"):
51
- return f"https:{url}"
52
-
53
- return urljoin(self.main_url, url)
54
+ return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
54
55
 
55
56
  @staticmethod
56
57
  def clean_title(title: str) -> str:
@@ -3,9 +3,7 @@
3
3
  from ..CLI import konsol, cikis_yap
4
4
  from .PluginBase import PluginBase
5
5
  from pathlib import Path
6
- import importlib.util
7
- import os
8
- import traceback
6
+ import os, importlib.util, traceback
9
7
 
10
8
  class PluginLoader:
11
9
  def __init__(self, plugins_dir: str):
@@ -20,11 +18,11 @@ class PluginLoader:
20
18
 
21
19
  if self.global_plugins_dir.exists():
22
20
  konsol.log(f"[green][*] Global Plugin dizininden yükleniyor: {self.global_plugins_dir}[/green]")
23
- plugins.update(self._load_from_directory(self.global_plugins_dir))
21
+ plugins |= self._load_from_directory(self.global_plugins_dir)
24
22
 
25
23
  if self.local_plugins_dir.exists():
26
24
  konsol.log(f"[green][*] Yerel Plugin dizininden yükleniyor: {self.local_plugins_dir}[/green]")
27
- plugins.update(self._load_from_directory(self.local_plugins_dir))
25
+ plugins |= self._load_from_directory(self.local_plugins_dir)
28
26
 
29
27
  if not plugins:
30
28
  konsol.print("[yellow][!] Yüklenecek bir Plugin bulunamadı![/yellow]")
@@ -23,17 +23,12 @@ class MovieInfo(BaseModel):
23
23
  actors : Optional[str] = None
24
24
  duration : Optional[int] = None
25
25
 
26
- @field_validator("tags", mode="before")
26
+ @field_validator("tags", "actors", mode="before")
27
27
  @classmethod
28
- def convert_tags(cls, value):
28
+ def convert_lists(cls, value):
29
29
  return ", ".join(value) if isinstance(value, list) else value
30
30
 
31
- @field_validator("actors", mode="before")
32
- @classmethod
33
- def convert_actors(cls, value):
34
- return ", ".join(value) if isinstance(value, list) else value
35
-
36
- @field_validator("rating", mode="before")
31
+ @field_validator("rating", "year", mode="before")
37
32
  @classmethod
38
33
  def ensure_string(cls, value):
39
34
  return str(value) if value is not None else value
@@ -57,17 +52,12 @@ class SeriesInfo(BaseModel):
57
52
  actors : Optional[str] = None
58
53
  episodes : Optional[List[Episode]] = None
59
54
 
60
- @field_validator("tags", mode="before")
61
- @classmethod
62
- def convert_tags(cls, value):
63
- return ", ".join(value) if isinstance(value, list) else value
64
-
65
- @field_validator("actors", mode="before")
55
+ @field_validator("tags", "actors", mode="before")
66
56
  @classmethod
67
- def convert_actors(cls, value):
57
+ def convert_lists(cls, value):
68
58
  return ", ".join(value) if isinstance(value, list) else value
69
59
 
70
- @field_validator("rating", mode="before")
60
+ @field_validator("rating", "year", mode="before")
71
61
  @classmethod
72
62
  def ensure_string(cls, value):
73
63
  return str(value) if value is not None else value
@@ -0,0 +1,80 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4
+ import re
5
+
6
+ class ContentX(ExtractorBase):
7
+ name = "ContentX"
8
+ main_url = "https://contentx.me"
9
+
10
+ async def extract(self, url, referer=None) -> list[ExtractResult]:
11
+ if referer:
12
+ self.oturum.headers.update({"Referer": referer})
13
+
14
+ istek = await self.oturum.get(url)
15
+ istek.raise_for_status()
16
+ i_source = istek.text
17
+
18
+ i_extract = re.search(r"window\.openPlayer\('([^']+)'", i_source)
19
+ if not i_extract:
20
+ raise ValueError("i_extract is null")
21
+ i_extract_value = i_extract[1]
22
+
23
+ subtitles = []
24
+ sub_urls = set()
25
+ for match in re.finditer(r'"file":"([^"]+)","label":"([^"]+)"', i_source):
26
+ sub_url, sub_lang = match.groups()
27
+
28
+ if sub_url in sub_urls:
29
+ continue
30
+
31
+ sub_urls.add(sub_url)
32
+ subtitles.append(
33
+ Subtitle(
34
+ name = sub_lang.replace("\\u0131", "ı")
35
+ .replace("\\u0130", "İ")
36
+ .replace("\\u00fc", "ü")
37
+ .replace("\\u00e7", "ç"),
38
+ url = self.fix_url(sub_url.replace("\\", ""))
39
+ )
40
+ )
41
+
42
+ vid_source_request = await self.oturum.get(f"{self.main_url}/source2.php?v={i_extract_value}", headers={"Referer": referer or self.main_url})
43
+ vid_source_request.raise_for_status()
44
+
45
+ vid_source = vid_source_request.text
46
+ vid_extract = re.search(r'file":"([^"]+)"', vid_source)
47
+ if not vid_extract:
48
+ raise ValueError("vidExtract is null")
49
+
50
+ m3u_link = vid_extract[1].replace("\\", "")
51
+ results = [
52
+ ExtractResult(
53
+ name = self.name,
54
+ url = m3u_link,
55
+ referer = url,
56
+ subtitles = subtitles
57
+ )
58
+ ]
59
+
60
+ if i_dublaj := re.search(r',\"([^"]+)\",\"Türkçe"', i_source):
61
+ dublaj_value = i_dublaj[1]
62
+ dublaj_source_request = await self.oturum.get(f"{self.main_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or self.main_url})
63
+ dublaj_source_request.raise_for_status()
64
+
65
+ dublaj_source = dublaj_source_request.text
66
+ dublaj_extract = re.search(r'file":"([^"]+)"', dublaj_source)
67
+ if not dublaj_extract:
68
+ raise ValueError("dublajExtract is null")
69
+
70
+ dublaj_link = dublaj_extract[1].replace("\\", "")
71
+ results.append(
72
+ ExtractResult(
73
+ name = f"{self.name} Türkçe Dublaj",
74
+ url = dublaj_link,
75
+ referer = url,
76
+ subtitles = []
77
+ )
78
+ )
79
+
80
+ return results[0] if len(results) == 1 else results
@@ -0,0 +1,7 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Extractors.ContentX import ContentX
4
+
5
+ class FourCX(ContentX):
6
+ name = "FourCX"
7
+ main_url = "https://four.contentx.me"
@@ -0,0 +1,7 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Extractors.ContentX import ContentX
4
+
5
+ class FourPichive(ContentX):
6
+ name = "FourPichive"
7
+ main_url = "https://four.pichive.online"
@@ -0,0 +1,7 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Extractors.ContentX import ContentX
4
+
5
+ class FourPlayRu(ContentX):
6
+ name = "FourPlayRu"
7
+ main_url = "https://four.playru.net"
@@ -0,0 +1,7 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Extractors.PeaceMakerst import PeaceMakerst
4
+
5
+ class HDStreamAble(PeaceMakerst):
6
+ name = "HDStreamAble"
7
+ main_url = "https://hdstreamable.com"
@@ -0,0 +1,7 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Extractors.ContentX import ContentX
4
+
5
+ class Hotlinger(ContentX):
6
+ name = "Hotlinger"
7
+ main_url = "https://hotlinger.com"
@@ -0,0 +1,42 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult
4
+ from Kekik.Sifreleme import AESManager
5
+ import re, json
6
+
7
+ class MixPlayHD(ExtractorBase):
8
+ name = "MixPlayHD"
9
+ main_url = "https://mixplayhd.com"
10
+
11
+ async def extract(self, url, referer=None) -> ExtractResult:
12
+ if referer:
13
+ self.oturum.headers.update({"Referer": referer})
14
+
15
+ istek = await self.oturum.get(url)
16
+ istek.raise_for_status()
17
+
18
+ be_player_match = re.search(r"bePlayer\('([^']+)',\s*'(\{[^\}]+\})'\);", istek.text)
19
+ if not be_player_match:
20
+ raise ValueError("bePlayer not found in the response.")
21
+
22
+ be_player_pass = be_player_match[1]
23
+ be_player_data = be_player_match[2]
24
+
25
+ try:
26
+ decrypted_data = AESManager.decrypt(be_player_data, be_player_pass).replace("\\", "")
27
+ decrypted_json = json.loads(decrypted_data)
28
+ except Exception as hata:
29
+ raise RuntimeError(f"Decryption failed: {hata}") from hata
30
+
31
+ if video_url_match := re.search(
32
+ pattern = r'"video_location":"([^"]+)"',
33
+ string = decrypted_json.get("schedule", {}).get("client", ""),
34
+ ):
35
+ return ExtractResult(
36
+ name = self.name,
37
+ url = video_url_match[1],
38
+ referer = self.main_url,
39
+ subtitles = []
40
+ )
41
+ else:
42
+ raise ValueError("M3U8 video URL not found in the decrypted data.")
@@ -0,0 +1,106 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult
4
+ import re, json
5
+
6
+ class Odnoklassniki(ExtractorBase):
7
+ name = "Odnoklassniki"
8
+ main_url = "https://odnoklassniki.ru"
9
+
10
+ async def extract(self, url, referer=None) -> ExtractResult:
11
+ if referer:
12
+ self.oturum.headers.update({"Referer": referer})
13
+
14
+ self.oturum.headers.update({
15
+ "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
16
+ })
17
+
18
+ if "/video/" in url:
19
+ url = url.replace("/video/", "/videoembed/")
20
+
21
+ try:
22
+ istek = await self.fetch_with_redirects(url)
23
+ istek.raise_for_status()
24
+ except Exception as hata:
25
+ raise RuntimeError(f"Failed to fetch the URL: {url}, Error: {hata}") from hata
26
+
27
+ response_text = (
28
+ istek.text.replace("\\"", "\"")
29
+ .replace("\\\\", "\\")
30
+ .replace(r"\\u", "\\u")
31
+ )
32
+ response_text = re.sub(
33
+ r"\\u([0-9A-Fa-f]{4})",
34
+ lambda match: chr(int(match[1], 16)),
35
+ response_text
36
+ )
37
+
38
+ videos_match = re.search(r'"videos":(\[.*?\])', response_text)
39
+ if not videos_match:
40
+ raise ValueError("No video data found in the response.")
41
+
42
+ try:
43
+ videos = json.loads(videos_match[1])
44
+ except json.JSONDecodeError as hata:
45
+ raise ValueError("Failed to parse video data.") from hata
46
+
47
+ quality_order = {
48
+ "ULTRA": 6, # 4K veya daha yüksek
49
+ "QUAD": 5, # 1440p
50
+ "FULL": 4, # 1080p
51
+ "HD": 3, # 720p
52
+ "SD": 2, # 480p
53
+ "LOW": 1, # 360p
54
+ "MOBILE": 0 # 144p
55
+ }
56
+
57
+ # Kaliteye göre en iyi videoyu seçme
58
+ best_video = None
59
+ best_quality_score = -1
60
+
61
+ for video in videos:
62
+ video_url = video.get("url")
63
+ quality_name = video.get("name", "").upper()
64
+
65
+ if not video_url or not quality_name:
66
+ continue
67
+
68
+ # Kalite sıralamasına göre puanla
69
+ quality_score = quality_order.get(quality_name, -1)
70
+ if quality_score > best_quality_score:
71
+ best_quality_score = quality_score
72
+ best_video = video_url
73
+
74
+ if not best_video:
75
+ raise ValueError("No valid video URLs found.")
76
+
77
+ if best_video.startswith("//"):
78
+ best_video = f"https:{best_video}"
79
+
80
+ return ExtractResult(
81
+ name = self.name,
82
+ url = best_video,
83
+ referer = self.main_url,
84
+ subtitles = []
85
+ )
86
+
87
+ async def fetch_with_redirects(self, url, max_redirects=5):
88
+ """Yönlendirmeleri takip eden bir fonksiyon"""
89
+ redirects = 0
90
+ while redirects < max_redirects:
91
+ istek = await self.oturum.get(url, follow_redirects=False)
92
+
93
+ if istek.status_code not in [301, 302]:
94
+ break # Yönlendirme yoksa çık
95
+
96
+ redirected_url = istek.headers.get("Location")
97
+ if not redirected_url:
98
+ raise ValueError("Redirect location not found.")
99
+
100
+ url = redirected_url if redirected_url.startswith("http") else f"https://{redirected_url}"
101
+ redirects += 1
102
+
103
+ if redirects == max_redirects:
104
+ raise RuntimeError(f"Max redirects ({max_redirects}) reached.")
105
+
106
+ return istek
@@ -0,0 +1,7 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Extractors.Odnoklassniki import Odnoklassniki
4
+
5
+ class OkRuHTTP(Odnoklassniki):
6
+ name = "OkRuHTTP"
7
+ main_url = "http://ok.ru"