#!/bin/sh # تحقّق بعد الرفع: تأكد أن ملفات السبيس صارت النسخة المُصلَحة فعلًا. # شغّله داخل السبيس (Terminal) أو محليًا على المجلد الذي يحتوي الملفات. set -e DIR="${1:-.}" cd "$DIR" echo "== الملفات الموجودة ==" ls -1 Dockerfile flv_bridge.py relay.py youtube_manager.py echo echo "== 1) Dockerfile يحمل YOUTUBE_SEND_MODE ==" grep -q 'YOUTUBE_SEND_MODE=copy' Dockerfile && echo " OK" || { echo " FAIL — Dockerfile قديم"; exit 1; } echo echo "== 2) flv_bridge.py فيه FormatChanged ==" grep -q 'class FormatChanged' flv_bridge.py && echo " OK" || { echo " FAIL"; exit 1; } grep -q '_video_signature' flv_bridge.py && echo " OK — فحص Sequence Header موجود" || { echo " FAIL"; exit 1; } echo echo "== 3) relay.py فيه send_mode / canvas / note_format_change ==" for token in 'def send_mode' 'def canvas' 'def note_format_change' 'except FormatChanged' 'YOUTUBE_SEND_MODE'; do grep -q "$token" relay.py && echo " OK — $token" || { echo " FAIL — ناقص $token"; exit 1; } done echo echo "== 4) youtube_manager.py فيه معالجة FormatChanged ==" grep -q 'from flv_bridge import FormatChanged' youtube_manager.py && echo " OK — import" || { echo " FAIL"; exit 1; } grep -q 'except FormatChanged' youtube_manager.py && echo " OK — sender_loop" || { echo " FAIL"; exit 1; } echo echo "== 5) الملفات تُترجَم بدون أخطاء ==" python3 -m py_compile flv_bridge.py relay.py youtube_manager.py app.py && echo " OK" echo echo "== 6) السلوك الفعلي: الوضع الافتراضي = copy، والأوامر تُبنى صحيحًا ==" python3 - <<'PY' import importlib.util, os, shutil, sys def load(n, p): s = importlib.util.spec_from_file_location(n, p); m = importlib.util.module_from_spec(s) sys.modules[n] = m; s.loader.exec_module(m); return m load('flv_bridge', 'flv_bridge.py'); rl = load('relay', 'relay.py') r = rl.Relay(shutil.which('ffmpeg') or '/usr/bin/ffmpeg', lambda u: [], lambda u, m: None) for mode in ('copy', 'fixed1080', 'fixed720'): os.environ['YOUTUBE_SEND_MODE'] = mode assert r.send_mode() == mode, mode cmd = ' '.join(r.command('rtmps://a.rtmps.youtube.com:443/live2/KEY')) if mode == 'copy': assert '-c:v copy' in cmd and '-rtmp_enhanced_codecs hvc1' in cmd, cmd assert 'libx264' not in cmd else: assert 'libx264' in cmd and 'pad=' in cmd and '-sc_threshold 0' in cmd, cmd assert '-c:v copy' not in cmd print(' OK — %-9s -> %s' % (mode, cmd[:110] + '...')) os.environ.pop('YOUTUBE_SEND_MODE', None) assert r.send_mode() == 'copy', 'default must stay copy' print(' OK — بدون المتغير: الافتراضي copy (لا تغيير في السلوك)') PY echo echo "== 7) اختبار حي: بث يغيّر مقاسه في المنتصف ==" python3 - <<'PY' import importlib.util, os, shutil, struct, subprocess, sys def load(n, p): s = importlib.util.spec_from_file_location(n, p); m = importlib.util.module_from_spec(s) sys.modules[n] = m; s.loader.exec_module(m); return m fb = load('flv_bridge', 'flv_bridge.py') ff = shutil.which('ffmpeg') if not ff: print(' SKIP — ffmpeg غير موجود'); sys.exit(0) def enc(path, size, codec): cmd = [ff, '-hide_banner', '-loglevel', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc2=size=%s:rate=30' % size, '-t', '3', '-c:v', codec] if codec == 'libx265': cmd += ['-x265-params', 'keyint=30:min-keyint=30:log-level=error', '-rtmp_enhanced_codecs', 'hvc1'] else: cmd += ['-g', '30'] cmd += ['-f', 'flv', path] subprocess.run(cmd, check=True) def parse(p): d = open(p, 'rb').read(); h = struct.unpack('>I', d[5:9])[0] + 4 pos = h; tags = [] while pos + 11 <= len(d): t = d[pos]; sz = int.from_bytes(d[pos+1:pos+4], 'big') ts = int.from_bytes(d[pos+4:pos+7], 'big') | (d[pos+7] << 24) tags.append([t, ts, d[pos+11:pos+11+sz]]); pos += 11 + sz + 4 return d[:h], tags def write(p, head, tags): with open(p, 'wb') as f: f.write(head) for t, ts, b in tags: f.write(bytes([t]) + len(b).to_bytes(3, 'big') + (ts & 0xFFFFFF).to_bytes(3, 'big') + bytes([(ts >> 24) & 0xFF]) + b'\0\0\0' + b + (11 + len(b)).to_bytes(4, 'big')) for codec, ext in (('libx265', 'hevc'), ('libx264', 'h264')): enc('/tmp/_a.flv', '1080x1920', codec); enc('/tmp/_b.flv', '720x1280', codec) ha, ta = parse('/tmp/_a.flv'); _, tb = parse('/tmp/_b.flv') base = max(x[1] for x in ta) + 34 merged = ta + [[t, ts + base, b] for t, ts, b in tb if t != 18] write('/tmp/_sw.flv', ha, merged) b = fb.FLVBridge() raw = open('/tmp/_sw.flv', 'rb').read() raised = False try: for i in range(0, len(raw), 65536): b.feed(raw[i:i+65536]) except fb.FormatChanged: raised = True print(' %s: FormatChanged %s' % (ext.upper(), 'OK — رُصد التغيّر' if raised else 'FAIL')) assert raised # detection off (fixed canvas mode) must swallow it b2 = fb.FLVBridge(detect_format_change=False); n = 0 for i in range(0, len(raw), 65536): n += len(b2.feed(raw[i:i+65536])) print(' %s: detect off -> emitted %d bytes, changes seen %d OK' % (ext.upper(), n, b2.format_changes)) PY echo echo "== 8) توكن تيك توك: الصيغ والأولويات وعدم التسريب ==" python3 verify_tokens.py || { echo " FAIL"; exit 1; } echo echo "== 9) كل طلبات تيك توك تمر عبر التوكن ==" COUNT=$(grep -c "tiktok_auth.headers(" app.py relay.py youtube_manager.py | awk -F: '{s+=$2} END {print s}') echo " مواضع tiktok_auth.headers(): $COUNT (المتوقع 5)" [ "$COUNT" -ge 5 ] || { echo " FAIL — يوجد طلب لا يحمل الجلسة"; exit 1; } grep -q "tiktok_auth.py" Dockerfile && echo " OK — Dockerfile ينسخ tiktok_auth.py" || { echo " FAIL"; exit 1; } grep -q "TIKTOK_SESSIONID" Dockerfile && echo " OK — Dockerfile يعلن متغيرات التوكن" || { echo " FAIL"; exit 1; } grep -q "api/tiktok/token" app.py && echo " OK — نقاط API للتوكن موجودة" || { echo " FAIL"; exit 1; } grep -q "AgeRestricted" app.py && echo " OK — كشف التقييد العمري موجود" || { echo " FAIL"; exit 1; } grep -q "renderTiktok" index.html && echo " OK — لوحة التوكن في الواجهة" || { echo " FAIL"; exit 1; } echo echo "✅ كل الفحوصات نجحت — الملفات مُصلَحة وجاهزة."