# گزارش خرابی فیلم و فصل

فقط `bot.php` تغییر کرده است. ثبت فیلم/سریال، Trailer، آمار ادمین‌ها، فایل ربات دوم و دکمه‌های دانلود تغییر نکرده‌اند.

## محل دکمه‌ها

- در `buildMovieQualitiesKeyboard`، «⚠️ گزارش خرابی فیلم» بلافاصله بعد از تمام کیفیت‌ها و قبل از بازگشت اضافه شده است. هر دو مسیر `/start get_...` و `req_movie_...` که این سازنده را استفاده می‌کنند، پوشش داده می‌شوند.
- در Handler `sub_series_...`، زیر سطرهای قسمت‌ها و پیش از رسید پرداخت.
- در Handler `user_get_series_...`، هم شاخهٔ اشتراکی قفل‌شده و هم شاخهٔ عادی، زیر سطرهای قسمت‌ها و پیش از پرداخت/اعلان قسمت جدید/بازگشت.
- اگر نمایش دکمه‌های قسمت‌ها در تنظیمات خاموش باشد و فصل مستقیم ارسال شود، بعد از ارسال فایل‌ها یک پیام شمارهٔ فصل با دکمهٔ گزارش آن فصل نمایش داده می‌شود.
- ترتیب کیفیت‌ها و قسمت‌ها، لینک‌های ربات دوم و callbackهای بازگشت یا صفحه‌بندی دست‌نخورده‌اند. گزارش، همان منوی قبلی را ویرایش نمی‌کند.

## شناسه و اعتبارسنجی

`report_movie:tt1234567` برای فیلم و `report_season:tt1234567:1` برای فصل. سازنده `damageReportButton`، IMDb و شمارهٔ فصل واقعی مسیر نمایش را دریافت می‌کند. از متن دکمه چیزی استخراج نمی‌شود. طول بیشینهٔ callback زیر ۶۴ بایت است و فصل صفر نیز پشتیبانی می‌شود.

`damageReportHandle` پیش از Handlerهای عمومی فراخوانی می‌شود و فقط این دو پیشوند را می‌گیرد. قالب نامعتبر، شمارهٔ منفی، محتوای حذف‌شده یا پنهان و فصل ناموجود رد می‌شوند. شناسهٔ کاربر از callback_query.from گرفته می‌شود. چت باید خصوصی و متعلق به همان کاربر باشد؛ کاربر نباید مسدود باشد و باید عضویت الزامی و در محتوای اشتراکی، اشتراک لازم را داشته باشد. بنابراین دیده‌شدن دکمه در منوی قفل‌شده به‌معنای اجازهٔ ارسال گزارش نیست.

`damageReportContent` جدول `file` را با پارامترهای bindشده می‌خواند: IMDb دقیق، is_visible=1 و نوع video/document. گزارش فیلم فقط رکورد فاقد نشانهٔ سریال را می‌پذیرد؛ فصل علاوه بر نشانهٔ سریال باید season برابر داشته باشد. تشخیص سریال از ستون‌های season/episode و قالب ساختاری id موجود پروژه است.

نام از `movie_custom_info.fa_title/en_title` خوانده می‌شود؛ در نبود نام سفارشی، تابع موجود `cinemaTitle` از کپشن همان رکورد استفاده می‌کند. نام کاربر و عنوان برای HTML escape می‌شوند. پیام ادمین شامل عنوان، IMDb، شناسه و نام کاربر، نام کاربری در صورت وجود و برای سریال شمارهٔ فصل است.

## ثبت و جلوگیری از تکرار

سیستم اختصاصی گزارش خرابی در کد موجود نبود؛ جدول `forwarded_posts_log` فقط لاگ ارسال پست است و معنای متفاوتی دارد. جدول جدید ایجاد نشده است. از الگوی موجود ذخیرهٔ داده در `settings` استفاده می‌شود:

| فیلد | مقدار |
|---|---|
| type | damage_report |
| type_id | SHA-256 از user_id + IMDb + movie یا season:number |
| columnOne | JSON محتوا، کاربر، متن گزارش، زمان و وضعیت ارسال هر ادمین |
| columnTwo | زمان ثبت Unix |

یک رکورد جاری برای هر کاربر/محتوا نگه‌داری می‌شود. بازهٔ جلوگیری از گزارش تازه ۲۴ ساعت از created_at است؛ پس از آن رکورد همان کلید برای گزارش تازه به‌روز می‌شود. این جدول آرشیو نامحدود تمام گزارش‌های قبلی نیست.

قفل MySQL GET_LOCK با کلید گزارش جلوی مسابقهٔ کلیک‌های هم‌زمان را می‌گیرد. گزارش قبل از اطلاع‌رسانی ذخیره می‌شود. کلیک دوباره در یک دقیقهٔ اول فقط پیام ثبت قبلی می‌دهد.

## ارسال به ادمین‌ها و خطا

فهرست از `getAllAdminIds` موجود پروژه گرفته می‌شود و کش فهرست پیش از خواندن حذف می‌شود تا تغییرات اخیر لحاظ شوند. شناسه‌های تکراری و ادمین‌های مسدود حذف می‌شوند. گیرندهٔ ثابت جدیدی تعریف نشده است.

ارسال با `MySGRBot('sendMessage', ...)` موجود انجام می‌شود. وضعیت هر ادمین جداگانه ذخیره می‌شود:

- sent: پاسخ موفق Telegram؛ دوباره ارسال نمی‌شود.
- failed: پاسخ خطای صریح Telegram؛ با کلیک کاربر پس از حداقل ۶۰ ثانیه فقط گیرنده‌های ناموفق دوباره امتحان می‌شوند.
- sending/unknown: ارسال ناتمام یا نتیجهٔ نامعلوم، مثل timeout؛ برای جلوگیری از پیام تکراری، خودکار دوباره ارسال نمی‌شود. وضعیت در رکورد گزارش باقی می‌ماند و در پاسخ کاربر ادعا نمی‌شود که همهٔ ادمین‌ها پیام را گرفته‌اند.

اگر همهٔ ادمین‌های فعال پاسخ موفق داشته باشند: «✅ گزارش خرابی شما برای ادمین ارسال شد.» در نبود ادمین، گزارش ثبت می‌شود و کاربر از نبود گیرنده مطلع می‌شود. خطای ثبت دیتابیس یا ارسال ناقص، پیام موفقیت کامل ندارد. وضعیت نامعلوم نیازمند بررسی ادمین است؛ تضمین دقیقاً یک‌بار تحویل از API پیام‌رسان ممکن نیست.

## توابع افزوده

`damageReportButton`، `damageReportContent`، `damageReportText`، `damageReportStore`، `damageReportNotify` و `damageReportHandle`.

هیچ ستون، جدول یا مدل جدیدی اضافه نشده است. تنها نوع رکورد damage_report به settings افزوده می‌شود. عملیات UPDATE/INSERT گزارش با همان `dsQuery` پروژه اجرا می‌شود. سایر لاگ‌ها و رکوردهای settings دست‌نخورده‌اند.

## بررسی انجام‌شده

- نحو کامل PHP بدون خطا.
- ۱۸ آزمون محلی با وابستگی‌های شبیه‌سازی‌شده: callback فیلم و فصل صفر، حد بایت، رد ورودی نامعتبر، HTML، ارسال به همهٔ ادمین‌های فعال بدون تکرار، استقلال فیلم و فصل‌ها، retry گیرندهٔ ناموفق، timeout، قفل هم‌زمانی، حفظ Handlerهای دیگر، عضویت و اشتراک، نبود محتوا و تطبیق فصل واقعی.
- بررسی جایگذاری در سازندهٔ کیفیت، هر سه شبکهٔ قسمت‌ها و حالت ارسال مستقیم فصل؛ callbackهای قبلی حفظ شدند.

اتصال زندهٔ MySQL و ارسال واقعی Telegram در این محیط آزمایش نشده و هیچ پیام واقعی برای کاربران یا ادمین‌ها ارسال نشده است.
