From 59e3bc900f315e1f8826581f847bc26436cb7d75 Mon Sep 17 00:00:00 2001
From: Macsim <134152147+MuRuLOSE@users.noreply.github.com>
Date: Mon, 23 Mar 2026 18:56:54 +0300
Subject: [PATCH] Removed stream.py because of security measures
---
fiksofficial/python-modules/stream.py | 436 +-------------------------
1 file changed, 1 insertion(+), 435 deletions(-)
diff --git a/fiksofficial/python-modules/stream.py b/fiksofficial/python-modules/stream.py
index 771b5f2..bd048d4 100644
--- a/fiksofficial/python-modules/stream.py
+++ b/fiksofficial/python-modules/stream.py
@@ -1,435 +1 @@
-
-import asyncio
-import mimetypes
-import os
-import subprocess
-import time
-
-from .. import loader, utils
-from ..inline.types import InlineCall
-
-def detect_type(path: str) -> str:
- mime, _ = mimetypes.guess_type(path)
- if not mime:
- return "video"
- if mime.startswith("video"):
- return "video"
- if mime.startswith("audio"):
- return "audio"
- if mime.startswith("image"):
- return "image"
- return "video"
-
-TYPE_ICON = {"video": "🎬", "audio": "🎵", "image": "🖼️"}
-PRESETS = ["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow"]
-TUNES = ["zerolatency", "film", "animation", "grain", "stillimage", "fastdecode"]
-SCALES = ["off", "426x240", "640x360", "854x480", "1280x720", "1920x1080", "2560x1440"]
-FPS_OPT = [24, 25, 30, 48, 60]
-
-def build_cmd(file_path: str, rtmp_url: str, cfg: dict) -> list:
- preset = cfg.get("preset", "veryfast")
- tune = cfg.get("tune", "zerolatency")
- vbr = cfg.get("vbitrate", "2000k")
- abr = cfg.get("abitrate", "128k")
- fps = str(cfg.get("fps", 30))
- res = cfg.get("resolution", None)
- threads = str(cfg.get("threads", 0))
- gop = str(int(fps) * 2)
- bufsize = str(int(vbr.replace("k", "")) * 2) + "k"
- ftype = detect_type(file_path)
-
- base = ["ffmpeg", "-re", "-stream_loop", "-1", "-threads", threads]
- vf_scale = f",scale={res}" if res else ""
- common_v = [
- "-c:v", "libx264", "-preset", preset, "-tune", tune,
- "-pix_fmt", "yuv420p", "-profile:v", "baseline",
- "-r", fps, "-g", gop, "-keyint_min", gop, "-sc_threshold", "0",
- "-b:v", vbr, "-maxrate", vbr, "-bufsize", bufsize,
- ]
- common_a = ["-c:a", "aac", "-b:a", abr, "-ar", "44100"]
- out = ["-f", "flv", rtmp_url]
-
- if ftype == "video":
- vf = ["-vf", f"scale=trunc(iw/2)*2:trunc(ih/2)*2{vf_scale}"] if res else []
- return base + ["-i", file_path] + common_v + vf + common_a + out
- if ftype == "audio":
- size = res or "1280x720"
- return (
- base
- + ["-i", file_path, "-f", "lavfi", "-i", f"color=c=black:s={size}:r={fps}"]
- + ["-shortest"] + common_v + common_a
- + ["-map", "1:v:0", "-map", "0:a:0"] + out
- )
- if ftype == "image":
- scale_vf = f"scale=trunc(iw/2)*2:trunc(ih/2)*2{vf_scale}"
- return (
- base
- + ["-loop", "1", "-i", file_path, "-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo"]
- + ["-vf", scale_vf] + common_v
- + ["-shortest"] + common_a
- + ["-map", "0:v:0", "-map", "1:a:0"] + out
- )
- raise ValueError(f"Unsupported: {ftype}")
-
-@loader.tds
-class StreamMod(loader.Module):
- """📡 RTMP media streaming"""
- strings = {
- "name": "Stream",
- "status_active": "▶️ Stream is live\n\n{icon} {file}\n⏱ Time: {elapsed}\n🔢 PID: {pid}\n📡 {rtmp}\n🎥 {vbr} | {fps}fps | {preset}\n🔊 {abr}\n📋 Queue: {queue}",
- "status_idle": "⏸ Stream is not active",
- "status_queue": "\n📋 Queue: {n}",
- "stopped": "⏹ Stream stopped.",
- "no_rtmp": "❌ RTMP not configured!\nTap a button to set it up.",
- "downloading": "⏳ Downloading…",
- "dl_failed": "❌ Failed to download file.",
- "queued": "📋 Added to queue ({n})\n{icon} {file}",
- "not_running": "Not running",
- "queue_empty": "Queue is empty",
- "queue_header": "📋 Queue:\n",
- "settings_title": "⚙️ Stream settings",
- "btn_stop": "⏹ Stop",
- "btn_queue": "📋 Queue",
- "btn_refresh": "🔄 Refresh",
- "btn_settings": "⚙️ Settings",
- "btn_status": "📊 Status",
- "btn_back": "🔙 Back",
- "btn_preset": "🎞 Preset: {v}",
- "btn_tune": "🎭 Tune: {v}",
- "btn_vbr": "🎥 Video: {v}",
- "btn_abr": "🔊 Audio: {v}",
- "btn_fps": "📐 FPS: {v}",
- "btn_res": "🖥 Res: {v}",
- "btn_threads": "🧵 Threads: {v}",
- "btn_rtmps": "📡 RTMP URL",
- "btn_key": "🔑 Stream key",
- "btn_set_rtmps": "📡 Set RTMP URL",
- "btn_set_key": "🔑 Set stream key",
- "ph_vbr": "Video bitrate, e.g. 2000k",
- "ph_abr": "Audio bitrate, e.g. 128k",
- "ph_threads": "Thread count (0 = auto)",
- "ph_rtmps": "rtmp://a.rtmp.youtube.com/live2",
- "ph_key": "Stream key...",
- }
-
- strings_ru = {
- "_cls_doc": "📡 RTMP стриминг медиафайлов",
- "status_active": "▶️ Трансляция идёт\n\n{icon} {file}\n⏱ Время: {elapsed}\n🔢 PID: {pid}\n📡 {rtmp}\n🎥 {vbr} | {fps}fps | {preset}\n🔊 {abr}\n📋 В очереди: {queue}",
- "status_idle": "⏸ Трансляция не активна",
- "status_queue": "\n📋 В очереди: {n}",
- "stopped": "⏹ Трансляция остановлена.",
- "no_rtmp": "❌ RTMP не настроен!\nНажми кнопку чтобы задать прямо сейчас.",
- "downloading": "⏳ Скачиваю…",
- "dl_failed": "❌ Не удалось скачать файл.",
- "queued": "📋 Добавлено в очередь ({n} шт.)\n{icon} {file}",
- "not_running": "Не запущено",
- "queue_empty": "Очередь пуста",
- "queue_header": "📋 Очередь:\n",
- "settings_title": "⚙️ Настройки трансляции",
- "btn_stop": "⏹ Стоп",
- "btn_queue": "📋 Очередь",
- "btn_refresh": "🔄 Обновить",
- "btn_settings": "⚙️ Настройки",
- "btn_status": "📊 Статус",
- "btn_back": "🔙 Назад",
- "btn_preset": "🎞 Пресет: {v}",
- "btn_tune": "🎭 Tune: {v}",
- "btn_vbr": "🎥 Видео: {v}",
- "btn_abr": "🔊 Аудио: {v}",
- "btn_fps": "📐 FPS: {v}",
- "btn_res": "🖥 Разр: {v}",
- "btn_threads": "🧵 Треды: {v}",
- "btn_rtmps": "📡 RTMP URL",
- "btn_key": "🔑 Ключ",
- "btn_set_rtmps": "📡 Задать RTMP URL",
- "btn_set_key": "🔑 Задать ключ",
- "ph_vbr": "Битрейт видео, напр. 2000k",
- "ph_abr": "Битрейт аудио, напр. 128k",
- "ph_threads": "Потоков (0 = авто)",
- "ph_rtmps": "rtmp://a.rtmp.youtube.com/live2",
- "ph_key": "Ключ трансляции...",
- }
-
- def __init__(self):
- self._proc: subprocess.Popen | None = None
- self._file: str | None = None
- self._started: float | None = None
- self._queue: list[str] = []
- self._qtask: asyncio.Task | None = None
- self.config = loader.ModuleConfig(
- loader.ConfigValue("rtmps", "", "Base RTMP URL (rtmp://...)"),
- loader.ConfigValue("key", "", "Stream key"),
- loader.ConfigValue("preset", "veryfast", "x264 preset",
- validator=loader.validators.Choice(PRESETS)),
- loader.ConfigValue("tune", "zerolatency","x264 tune",
- validator=loader.validators.Choice(TUNES)),
- loader.ConfigValue("vbitrate", "2000k", "Video bitrate (e.g. 1500k, 3000k)"),
- loader.ConfigValue("abitrate", "128k", "Audio bitrate (e.g. 64k, 192k)"),
- loader.ConfigValue("fps", 30, "Frames per second",
- validator=loader.validators.Integer(minimum=1, maximum=120)),
- loader.ConfigValue("resolution", "", "Output resolution (e.g. 1280x720, empty = no scaling)"),
- loader.ConfigValue("threads", 0, "FFmpeg thread count (0 = auto)",
- validator=loader.validators.Integer(minimum=0, maximum=64)),
- loader.ConfigValue("loop", True, "Loop the file indefinitely",
- validator=loader.validators.Boolean()),
- loader.ConfigValue("reconnect", True, "Auto-restart on stream disconnect",
- validator=loader.validators.Boolean()),
- )
-
- def _s(self, key: str, **kw) -> str:
- return self.strings[key].format(**kw) if kw else self.strings[key]
-
- def _running(self) -> bool:
- return self._proc is not None and self._proc.poll() is None
-
- def _stop(self):
- if self._proc:
- try:
- self._proc.terminate()
- self._proc.wait(timeout=5)
- except Exception:
- try:
- self._proc.kill()
- except Exception:
- pass
- self._proc = None
- if self._file and os.path.exists(self._file):
- try:
- os.remove(self._file)
- except Exception:
- pass
- self._file = None
- self._started = None
-
- def _launch(self, path: str):
- cfg = {k: self.config[k] for k in ("preset", "tune", "vbitrate", "abitrate", "fps", "threads")}
- cfg["resolution"] = self.config["resolution"] or None
- rtmp = f"{self.config['rtmps'].rstrip('/')}/{self.config['key']}"
- self._proc = subprocess.Popen(build_cmd(path, rtmp, cfg), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- self._file = path
- self._started = time.time()
-
- def _elapsed(self) -> str:
- if not self._started:
- return "00:00:00"
- e = int(time.time() - self._started)
- return f"{e//3600:02d}:{(e%3600)//60:02d}:{e%60:02d}"
-
- def _status_text(self) -> str:
- if not self._running():
- txt = self._s("status_idle")
- if self._queue:
- txt += self._s("status_queue", n=len(self._queue))
- return txt
- ftype = detect_type(self._file or "")
- rtmp = f"{self.config['rtmps'].rstrip('/')}/{self.config['key'][:4]}***"
- return self._s(
- "status_active",
- icon=TYPE_ICON.get(ftype, "📄"),
- file=os.path.basename(self._file or "?"),
- elapsed=self._elapsed(),
- pid=self._proc.pid if self._proc else "—",
- rtmp=rtmp,
- vbr=self.config["vbitrate"],
- fps=self.config["fps"],
- preset=self.config["preset"],
- abr=self.config["abitrate"],
- queue=len(self._queue),
- )
-
- def _res_label(self) -> str:
- r = self.config["resolution"]
- return r if r else "auto"
-
- def _thr_label(self) -> str:
- t = self.config["threads"]
- return str(t) if t else "auto"
-
- def _main_markup(self) -> list:
- running = self._running()
- return [
- [
- {"text": self._s("btn_stop"), "callback": self._cb_stop} if running
- else {"text": self._s("btn_queue"), "callback": self._cb_queue},
- {"text": self._s("btn_refresh"), "callback": self._cb_refresh},
- ],
- [
- {"text": self._s("btn_settings"), "callback": self._cb_settings},
- {"text": self._s("btn_status"), "callback": self._cb_status},
- ],
- ]
-
- def _settings_markup(self) -> list:
- return [
- [
- {"text": self._s("btn_preset", v=self.config["preset"]), "callback": self._cb_set_preset},
- {"text": self._s("btn_tune", v=self.config["tune"]), "callback": self._cb_set_tune},
- ],
- [
- {"text": self._s("btn_vbr", v=self.config["vbitrate"]),
- "input": self._s("ph_vbr"), "handler": self._ih_vbr},
- {"text": self._s("btn_abr", v=self.config["abitrate"]),
- "input": self._s("ph_abr"), "handler": self._ih_abr},
- ],
- [
- {"text": self._s("btn_fps", v=self.config["fps"]), "callback": self._cb_set_fps},
- {"text": self._s("btn_res", v=self._res_label()), "callback": self._cb_set_res},
- ],
- [
- {"text": self._s("btn_threads", v=self._thr_label()),
- "input": self._s("ph_threads"), "handler": self._ih_threads},
- ],
- [
- {"text": self._s("btn_rtmps"),
- "input": self._s("ph_rtmps"), "handler": self._ih_rtmps},
- {"text": self._s("btn_key"),
- "input": self._s("ph_key"), "handler": self._ih_key},
- ],
- [{"text": self._s("btn_back"), "callback": self._cb_back}],
- ]
-
- async def _ih_vbr(self, call: InlineCall, query: str):
- q = query.strip()
- if q.endswith("k") and q[:-1].isdigit():
- self.config["vbitrate"] = q
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _ih_abr(self, call: InlineCall, query: str):
- q = query.strip()
- if q.endswith("k") and q[:-1].isdigit():
- self.config["abitrate"] = q
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _ih_threads(self, call: InlineCall, query: str):
- q = query.strip()
- if q.isdigit():
- self.config["threads"] = int(q)
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _ih_rtmps(self, call: InlineCall, query: str):
- q = query.strip()
- if q.startswith("rtmp"):
- self.config["rtmps"] = q.rstrip("/")
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _ih_key(self, call: InlineCall, query: str):
- q = query.strip()
- if q:
- self.config["key"] = q
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _cb_refresh(self, call: InlineCall):
- await call.edit(self._status_text(), reply_markup=self._main_markup())
-
- async def _cb_status(self, call: InlineCall):
- await call.answer(self._elapsed() if self._running() else self._s("not_running"))
-
- async def _cb_stop(self, call: InlineCall):
- self._queue.clear()
- if self._qtask:
- self._qtask.cancel()
- self._qtask = None
- self._stop()
- await call.edit(self._s("stopped"), reply_markup=self._main_markup())
-
- async def _cb_queue(self, call: InlineCall):
- if not self._queue:
- await call.answer(self._s("queue_empty"), show_alert=True)
- return
- lines = [f"{i}. {TYPE_ICON.get(detect_type(f), '📄')} {os.path.basename(f)}"
- for i, f in enumerate(self._queue, 1)]
- await call.answer(self._s("queue_header") + "\n".join(lines), show_alert=True)
-
- async def _cb_back(self, call: InlineCall):
- await call.edit(self._status_text(), reply_markup=self._main_markup())
-
- async def _cb_settings(self, call: InlineCall):
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _cb_set_preset(self, call: InlineCall):
- cur = self.config["preset"]
- self.config["preset"] = PRESETS[(PRESETS.index(cur) + 1) % len(PRESETS)]
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _cb_set_tune(self, call: InlineCall):
- cur = self.config["tune"]
- self.config["tune"] = TUNES[(TUNES.index(cur) + 1) % len(TUNES)]
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _cb_set_fps(self, call: InlineCall):
- cur = self.config["fps"]
- self.config["fps"] = FPS_OPT[(FPS_OPT.index(cur) + 1) % len(FPS_OPT)] if cur in FPS_OPT else 30
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- async def _cb_set_res(self, call: InlineCall):
- cur = self.config["resolution"] or "off"
- idx = SCALES.index(cur) if cur in SCALES else 0
- nxt = SCALES[(idx + 1) % len(SCALES)]
- self.config["resolution"] = "" if nxt == "off" else nxt
- await call.edit(self._s("settings_title"), reply_markup=self._settings_markup())
-
- @loader.command(ru_doc="[ответ на медиа] – запустить трансляцию")
- async def stream(self, message):
- """[reply to media] — start stream or add to queue"""
- if not self.config["rtmps"] or not self.config["key"]:
- await self.inline.form(
- self._s("no_rtmp"),
- message=message,
- reply_markup=[
- [{"text": self._s("btn_set_rtmps"), "input": self._s("ph_rtmps"), "handler": self._ih_rtmps}],
- [{"text": self._s("btn_set_key"), "input": self._s("ph_key"), "handler": self._ih_key}],
- ],
- )
- return
-
- reply = await message.get_reply_message()
- if not reply or not reply.media:
- await self.inline.form(
- self._status_text(),
- message=message,
- reply_markup=self._main_markup(),
- )
- return
-
- status = await utils.answer(message, self._s("downloading"))
- path = await reply.download_media(file=f"/tmp/stream_{int(time.time())}")
- if not path:
- await status.edit(self._s("dl_failed"))
- return
- await status.delete()
-
- if self._running():
- self._queue.append(path)
- await self.inline.form(
- self._s("queued", n=len(self._queue), icon=TYPE_ICON.get(detect_type(path), "📄"), file=os.path.basename(path)),
- message=message,
- reply_markup=self._main_markup(),
- )
- return
-
- self._stop()
- self._launch(path)
- await self.inline.form(
- self._status_text(),
- message=message,
- reply_markup=self._main_markup(),
- )
-
- @loader.command(ru_doc="– панель управления трансляцией")
- async def streamctl(self, message):
- """– open stream control panel"""
- await self.inline.form(
- self._status_text(),
- message=message,
- reply_markup=self._main_markup(),
- )
-
- @loader.command(ru_doc="– остановить трансляцию и очистить очередь")
- async def streamstop(self, message):
- """– stop stream and clear queue"""
- self._queue.clear()
- if self._qtask:
- self._qtask.cancel()
- self._qtask = None
- self._stop()
- await utils.answer(message, self._s("stopped"))
\ No newline at end of file
+# Security issue in this module. RTMP Key doesn't hide in config with vaildator Hidden, because of that, we will wait for update from developer to fix it