# افزودن فیلم و سریال با پیش‌نویس و تکمیل دستی

فایل اجرایی تغییرکرده: `bot.php`. بات دوم در این مرحله تغییر نکرده است. فایل PHP کامل است و ماژول خارجی جدیدی برای اجرا لازم ندارد.

## معماری موجود و محل اتصال

پروژه مدل ORM مجزا برای Movie، Series یا Episode ندارد:

| مسئولیت | ساختار موجود |
|---|---|
| اطلاعات دستی محتوا | `movie_custom_info` با کلید اصلی `imdb_id` |
| فایل و قسمت | `file` با `imdb_id`، `season`، `episode`، `is_dubbed` |
| شناسه Telegram | `file.file_id` و پیام آرشیو در `channel_message_id` |
| وضعیت ادمین | `user.step` و `user.getFile` |
| دریافت اطلاعات | `reliableTmdbRequest` و تنظیمات احراز هویت فعلی |
| تشخیص فایل | `parseMovieFilename`، `parseSeriesFilename` و `trackingLanguageEvidence` |
| کپشن | `generateMovieAutoCaption` و `generateSeriesAutoCaption` |
| مجوز | `isAdmin` و مجوز موجود `add_movie` برای فیلم |

`cdHandle` پس از شناسایی/ثبت کاربر و پیش از زنجیرهٔ handlerهای قدیمی فراخوانی می‌شود. حالت‌های `add_movie_imdb` و `wait_imdb_series` به پیش‌نویس هدایت می‌شوند. callbackهای `admin_new_movie_files_*`، `add_movie_existing_*` و `auto_distribute_series_*` نیز همین مسیر را آغاز می‌کنند.

sessionهای قدیمی `auto_add_movie_*`، `auto_distribute_*`، `auto_distribute_season_*`، `upload_series_*`، `add_file_to_series_*`، `add_movie_files|...` و `admin_new_movie_files` اگر هنگام ارتقا هنوز فعال باشند، با دریافت اولین فایل به پیش‌نویس منتقل می‌شوند؛ آن فایل ثبت نمی‌شود و باید پس از تأیید دوباره ارسال شود. ابزارهای مستقل ویرایش فایل موجود، تریلر و آپلود عمومی بدون IMDb به این جریان تبدیل نشده‌اند.

## جریان کار

۱. ادمین از منوی فعلی افزودن فیلم/سریال، IMDb یا نام را وارد می‌کند.

۲. با IMDb، `/find` و سپس جزئیات movie/tv فراخوانی می‌شود. در ورودی نام، حداکثر پنج نتیجه نشان داده می‌شود و ادمین نتیجه را انتخاب می‌کند. IMDb واقعی از جزئیات انتخاب‌شده دریافت می‌شود؛ شناسهٔ ساختگی ایجاد نمی‌شود. اگر به دست نیامد، دکمهٔ ورود IMDb نمایش داده می‌شود.

۳. نه فیلد اطلاعاتی همراه ✅/❌ نمایش داده می‌شوند؛ هر نه فیلد حتی پس از پر شدن قابل ویرایش‌اند. پاسخ هر فیلد باید Reply به آخرین پیام درخواست همان فیلد باشد؛ این کنترل جلوی اعمال پاسخ قدیمی روی فیلد دیگری را می‌گیرد. امتیاز بین ۰ تا ۱۰، سال میلادی معتبر و مدت به دقیقه اعتبارسنجی می‌شوند. برای خالی کردن مقدار اختیاری، `-` بفرستید.

۴. خرابی کامل TMDB پنل دستی را متوقف نمی‌کند. پاسخ ناقص حفظ می‌شود. تلاش مجدد با `🔄 تلاش مجدد TMDB` مقادیر دستی را تغییر نمی‌دهد؛ اطلاعات قبلی موجود در دیتابیس هم هنگام باز کردن پیش‌نویس محافظت می‌شوند. برای جایگزینی مقدار محافظت‌شده، ادمین همان فیلد را ویرایش می‌کند.

۵. برای تأیید، IMDb معتبر و حداقل یکی از نام‌های فارسی/انگلیسی لازم است. کشور، ژانر، امتیاز، سال، محدودیت سنی، مدت و خلاصه اجباری نیستند؛ پروژه برای این اطلاعات ستون NOT NULL ندارد. از مقدار `adult` هم ردهٔ سنی حدس زده نمی‌شود؛ ردهٔ ثبت‌شده در release_dates/content_ratings با اولویت IR، US و GB استفاده می‌شود.

۶. فقط با `✅ تایید و افزودن فایل`، اطلاعات به `movie_custom_info` upsert می‌شود. تأیید مجدد رکورد جدید نمی‌سازد. اگر ادمین دیگری همان IMDb را پس از باز شدن پیش‌نویس تغییر داده باشد، تأیید دوم متوقف می‌شود؛ ادمین باید پیش‌نویس را لغو و محتوا را دوباره باز کند.

۷. پس از commit موفق، دریافت چند Document/Video فعال می‌شود. تشخیص کیفیت از نام فایل، تشخیص زبان با تابع فعلی مبتنی بر شواهد، و فصل/قسمت با parser فعلی انجام می‌شود. در نبود نام قابل تشخیص فصل/قسمت، فایل ثبت نمی‌شود و ادمین باید نامی مثل `S01E03` بفرستد. فایل بی‌نام یا بدون شواهد زبانی به‌صورت خودکار «دوبله پارسی» فرض نمی‌شود. این مسیر ویدیو را برای MediaInfo دانلود نمی‌کند.

۸. کپشن‌سازهای فعلی اطلاعات تأییدشده را از همان `movie_custom_info` می‌خوانند و برای این محتوا به TMDB وابسته نیستند. کیفیت، وضعیت زبان، IMDb، اطلاعات محتوا و فصل/قسمت در کپشن قرار می‌گیرد. متن برای HTML escape و با توجه به سقف کپشن کوتاه می‌شود؛ متن کامل در اطلاعات محتوا باقی می‌ماند.

۹. فایل با `file_id` به کانال ذخیره‌سازی موجود ارسال می‌شود؛ سپس در جدول فعلی `file` ثبت می‌شود. نبود کانال یا شکست ارسال آرشیو باعث ثبت فایل ناقص نمی‌شود. `file_id` مشابه در همان IMDb، همان ردیف فایل را به‌روزرسانی می‌کند و اعلان مجدد ایجاد نمی‌کند. کیفیت‌ها و قسمت‌های متفاوت رکورد فایل مستقل دارند، نه رکورد محتوای تکراری. این تشخیص تکرار بر اساس `file_id` است، نه fingerprint بایت‌های ویدیو.

۱۰. `🗄 اتمام اپلود` و `❌ لغو` session و پیش‌نویس را پاک می‌کنند. لغو پیش از تأیید، هیچ محتوای نهایی باقی نمی‌گذارد. لغو پس از تأیید، اطلاعات و فایل‌های معتبر قبلاً ثبت‌شده را حذف نمی‌کند.

## تغییرات دیتابیس

`cdEnsure` در اولین استفاده از جریان ادمین، تغییرات افزایشی زیر را اعمال می‌کند:

- جدول موقت `admin_content_drafts`: ستون‌های `admin_id`، `draft_id`، `imdb_id`، `media_type`، `status`، `payload` و `expires_at`؛ کلید اصلی مرکب `(admin_id,draft_id)`.
- تنها ستون افزوده به مدل اطلاعات نهایی موجود: `movie_custom_info.metadata_json` از نوع MEDIUMTEXT nullable، برای rating/year/country/genres/age/runtime و نوع محتوا/شناسه TMDB. نام‌ها و خلاصه همچنان در ستون‌های قبلی نیز ذخیره می‌شوند.
- schema جدول `file`، جدول watched و جدول Follow تغییر نکرده است.

`user.step=content_draft` و `user.getFile=draft_id` است. پیش‌نویس ۲۴ ساعت اعتبار دارد و ذخیرهٔ ویرایش زمان آن را تمدید می‌کند. منقضی شدن، پیش‌نویس را غیرقابل‌تأیید می‌کند؛ هنگام مراجعهٔ بعدی پاک می‌شود. برای هر ادمین فقط یک پیش‌نویس فعال نگه داشته می‌شود.

قفل اختصاصی هر ادمین از تداخل webhookهای او جلوگیری می‌کند؛ قفل IMDb تخصیص شناسهٔ فایل/تأیید یک محتوا را بین ادمین‌ها هماهنگ می‌کند. ثبت metadata و تغییر وضعیت draft در یک transaction انجام می‌شوند. خطای دیتابیس شامل SQLSTATE/errno/متن اصلی در لاگ باقی می‌ماند.

## توابع

- پیش‌نویس و router: `cdHandle`، `cdStart`، `cdGet`، `cdSave`، `cdCancel`، `cdEnsure`.
- دریافت و ادغام اطلاعات: `cdTmdb`، `cdFetch`، `cdMerge`، `cdAdoptExisting`.
- پنل و اعتبارسنجی: `cdFields`، `cdPanel`، `cdValue`، `cdRequired`، `cdUploadPrompt`.
- تأیید/آپلود: `cdConfirm`، `cdUpload`؛ این مسیر جانشین دریافت بدون تأیید برای ورودی‌های افزودن فوق است و از parserها، API wrapper، جداول و اعلان‌های موجود استفاده می‌کند.
- کپشن: همان `generateMovieAutoCaption` و `generateSeriesAutoCaption` اصلاح شده‌اند؛ `cdMetadata` فقط اطلاعات مدل موجود را می‌خواند و Caption Builder مستقلی نیست.
- `cdLog` و `cdEscape` برای لاگ و متن امن.

## اعتبارسنجی و محدودیت عملیاتی

۳۰ assertion در `tests/content_drafts_test.php` موفق بود. این تست router و توابع واقعی فایل bot.php را استخراج و اجرا می‌کند؛ فقط مرز Telegram، TMDB و MySQL شبیه‌سازی شده است. سناریوهای فیلم، سریال، چند فایل، چند ادمین، callback ادمین دیگر، تأیید تکراری، لغو، انقضا، اطلاعات ناقص، جست‌وجوی نام، duplicate محتوا/فایل، تعارض ویرایش، مجوز لغوشده و شکست ارسال آرشیو پوشش داده شده‌اند. نوع و تعداد پارامترهای SQL و طول callback نیز کنترل شده‌اند. نحو PHP هم بررسی شد.

اجرا خارج از مسیر عمومی وب:

```sh
php tests/content_drafts_test.php /path/to/bot.php
```

این تست‌ها جای تست با MySQL و Telegram واقعی را نمی‌گیرند؛ در این محیط به سرور شما متصل نشده‌ایم. کاربر دیتابیس باید مجوز CREATE و ALTER اولیه را داشته باشد و جداول اطلاعات/پیش‌نویس از transaction پشتیبانی کنند. تنظیمات API فعلی و کانال ذخیره‌سازی فعلی استفاده می‌شوند؛ کلیدی داخل کد قرار نگرفته است.

ارسال Telegram و commit دیتابیس یک transaction توزیع‌شده نیستند: اگر ارسال به آرشیو موفق شود اما ثبت دیتابیس شکست بخورد، ممکن است پیام بدون اتصال در کانال بماند. در چنین خطایی لاگ `[content_draft]` را بررسی کنید؛ هیچ رکورد فایل نیمه‌کاره یا محتوای تأییدنشده به‌عنوان فایل نهایی ثبت نمی‌شود.

برای نصب، فقط `bot.php` را جایگزین کنید و `config.php` فعلی را نگه دارید. فایل تست را به‌عنوان webhook نصب نکنید.

مرجع اتصال TMDB: [Find By ID](https://developer.themoviedb.org/reference/find-by-id)، [Movie Details](https://developer.themoviedb.org/reference/movie-details)، [TV Details](https://developer.themoviedb.org/reference/tv-series-details).

## پوستر دستی و نمایش محتوای داخلی با IMDb

پوستر به‌صورت Telegram Photo دریافت می‌شود. دکمهٔ `🖼 افزودن پوستر ❌` در پنل draft، درخواست عکس با ForceReply می‌فرستد. پاسخ باید متعلق به همان ادمین و همان پیام درخواست باشد. بزرگ‌ترین PhotoSize انتخاب و فقط file_id ذخیره می‌شود؛ getFile، دانلود عکس، فایل موقت یا پردازش تصویری وجود ندارد. پس از دریافت، دکمه `🖼 پوستر ✅` می‌شود و با کلیک دوباره قابل تعویض است. پوستر اختیاری است.

تغییر افزایشی این مرحله تنها ستون `movie_custom_info.poster_file_id TEXT NULL` است؛ `cdEnsure` آن را اضافه می‌کند. پیش از تأیید، شناسهٔ پوستر در JSON همان `admin_content_drafts.payload` نگهداری می‌شود. با تأیید، همراه اطلاعات محتوا در همان transaction به رکورد نهایی منتقل می‌شود. پوستر جدید نیاز به جدول جداگانه ندارد.

پنل مدیریت محتوای موجود در همان `sendMovieInfo` دکمه‌های `🖼 تغییر پوستر` و `🗑 حذف پوستر` دارد. callbackهای `cp:set:tt...` و `cp:del:tt...` توسط `cdPosterHandle` کنترل می‌شوند. تغییر پوستر از `user.step=content_poster` و `getFile=IMDb|prompt_message_id` استفاده می‌کند؛ draft فعال را تصاحب نمی‌کند. /cancel وضعیت را پاک می‌کند. `cdWritePoster` قفل همان IMDb را می‌گیرد تا با تأیید پیش‌نویس ادمین دیگر تداخل نکند. تغییر/حذف فقط ستون پوستر را دست می‌زند؛ اطلاعات دستی دست‌نخورده می‌مانند.

### نمایش و دریافت فایل

- `showPosterById` و مسیر deep link تابع `send` ابتدا محتوای تأییدشده را از `movie_custom_info` می‌خوانند.
- `cdDisplayInfo` دادهٔ داخلی را به شکل ورودی همان نمایشگر `sendMovieInfo` تبدیل می‌کند؛ نمایشگر جداگانهٔ فیلم یا سیستم دانلود جدید ساخته نشده است.
- اولویت تصویر: `poster_file_id` دستی، سپس مسیر پوستر TMDB ذخیره‌شده در metadata. اگر مسیر موجود نباشد ولی TMDB ID واقعی موجود باشد، یک تلاش محدود برای دریافت تصویر انجام می‌شود؛ شکست آن نمایش متن و فایل‌ها را متوقف نمی‌کند. محتوای بدون شناسهٔ TMDB و با پوستر دستی، برای نمایش هیچ درخواست TMDB لازم ندارد.
- Retry اطلاعات TMDB تنها مسیر پوستر خودکار را به‌روز می‌کند و ستون file_id دستی را بازنویسی نمی‌کند. حذف پوستر دستی به تصویر خودکار یا نمایش متن برمی‌گردد.
- اطلاعات شامل هر نه فیلد و IMDb است. اگر متن در سقف کپشن عکس جا نشود، ابتدا عکس و سپس متن کامل همراه کنترل‌ها ارسال می‌شود. `cdSendInfo` فقط ارسال/ویرایش پیام مشترک را مدیریت می‌کند. شکست sendPhoto نیز مانع ارائهٔ متن و کنترل فایل نمی‌شود.
- کیفیت‌های فیلم با همان `buildMovieQualitiesKeyboard` ساخته می‌شوند؛ callback/دیپ‌لینک فایل، گزارش خرابی و رنگ watched همان قبلی‌اند.
- فصل‌ها با همان `user_get_series_*` و قسمت‌ها با handler قبلی نمایش داده می‌شوند. بازگشت از فصل یا کیفیت به اطلاعات دیگر به موفقیت `/find` وابسته نیست.
- `Follow`، لایک، نظرات و امتیازدهی حفظ شده‌اند. callbackهای نظرات/امتیاز/بازگشت اکنون علاوه بر شناسهٔ عددی TMDB، IMDb معتبر را هم قبول می‌کنند و از همان جداول قبلی استفاده می‌کنند. قابلیت‌های صرفاً وابسته به TMDB مثل گالری بازیگران و مشابه‌ها، اگر TMDB ID واقعی وجود نداشته باشد نمایش داده نمی‌شوند؛ شناسهٔ جعلی صفر ساخته نمی‌شود.
- `dsRemember` اکنون editMessageMedia را نیز ثبت می‌کند تا تغییر پوستر/بازگشت به اطلاعات، همگام‌سازی watched را قطع نکند.
- ساختار `file` و بات دوم تغییر نکرده‌اند؛ فایل دستی همان imdb_id/season/episode/file_id/channel_message_id را دارد و همان مسیر قسمت قبل/بعد و تغییر فصل را طی می‌کند.

توابع افزوده: `cdPhotoId`، `cdPosterId`، `cdDisplayInfo`، `cdSendInfo`، `cdWritePoster` و `cdPosterHandle`. توابع پیش‌نویس، `sendMovieInfo`، `showPosterById`، `send`، `getTmdbMovieInfo`، handlerهای بازگشت/کیفیت/امتیاز/نظرات و whitelist تابع `dsRemember` اصلاح شدند.

### آزمون این مرحله

مجموع ۴۳ assertion موفق: ۳۰ تست قبلی و ۱۳ تست پوستر/نمایش داخلی. موارد جدید شامل ذخیرهٔ draft، انتخاب PhotoSize بزرگ، تعویض پوستر، حفظ در retry، انتقال در تأیید، جست‌وجوی IMDb بدون TMDB، نمایش پوستر سریال و callback فصل/Follow، تغییر/حذف مدیریتی، fallback تصویر خودکار/متن و خلاصهٔ بلند است. renderer واقعی اجرا شده؛ تابع کیفیت در تست renderer شبیه‌سازی شده و بدنهٔ تولید کیفیت در پروژه تغییر نکرده است. مرزهای Telegram/MySQL/TMDB شبیه‌سازی‌شده‌اند؛ این تست‌ها به معنی دانلود زندهٔ فایل یا تست شبکهٔ دو ربات نیستند.

نسخهٔ کامل جدید `bot.php` را جایگزین کنید. `config.php` و `bot2.php` تغییری لازم ندارند. پوسترهای Telegram به هویت بات مربوط‌اند؛ این ستون برای ارسال توسط همان بات اصلی است.
