From 9d8c243b73661514a195af54f2e28fa76eeb61ad Mon Sep 17 00:00:00 2001 From: Grigory Frolov <2168057+gynsus@users.noreply.github.com> Date: Sun, 11 Oct 2026 11:20:08 +0300 Subject: [PATCH 1/3] feat: auto first comment after publish (YouTube + Instagram) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A per-channel meta.first_comment (≤2200, Instagram's comment limit) is posted by FirstCommentPoster right after a successful publish — YouTube via commentThreads.insert, Instagram via /{media}/comments. Failures are logged and never fail the live post; Stories and in-review posts get no comment. Connect flows request the comment scopes. Reworked for the post_platforms-into-posts merge: the poster takes the post itself and the rules use the prefix-parameterized meta keys. --- .../Controllers/Auth/InstagramController.php | 2 + .../Auth/InstagramFacebookController.php | 2 + app/Jobs/PublishToSocialPlatform.php | 10 ++ app/Services/Social/FirstCommentPoster.php | 102 +++++++++++++++++ app/Support/PostPlatformMetaRules.php | 8 ++ lang/ar/posts.php | 7 ++ lang/de/posts.php | 7 ++ lang/el/posts.php | 7 ++ lang/en/posts.php | 7 ++ lang/es/posts.php | 7 ++ lang/fr/posts.php | 7 ++ lang/it/posts.php | 7 ++ lang/ja/posts.php | 7 ++ lang/ko/posts.php | 7 ++ lang/nl/posts.php | 7 ++ lang/pl/posts.php | 7 ++ lang/pt-BR/posts.php | 7 ++ lang/ru/posts.php | 7 ++ lang/tr/posts.php | 7 ++ lang/uk/posts.php | 7 ++ lang/zh/posts.php | 7 ++ .../posts/editor/InstagramSettings.vue | 47 +++++++- .../posts/editor/YouTubeSettings.vue | 37 +++++++ tests/Feature/Api/PostApiPlatformMetaTest.php | 33 ++++++ .../Feature/Mcp/PostPlatformMetaToolTest.php | 20 ++++ .../Social/FirstCommentPosterTest.php | 104 ++++++++++++++++++ tests/Unit/PostPlatformMetaRulesTest.php | 5 +- 27 files changed, 479 insertions(+), 3 deletions(-) create mode 100644 app/Services/Social/FirstCommentPoster.php create mode 100644 tests/Feature/Services/Social/FirstCommentPosterTest.php diff --git a/app/Http/Controllers/Auth/InstagramController.php b/app/Http/Controllers/Auth/InstagramController.php index 1d6f62297..7fd113e4a 100644 --- a/app/Http/Controllers/Auth/InstagramController.php +++ b/app/Http/Controllers/Auth/InstagramController.php @@ -24,6 +24,8 @@ class InstagramController extends SocialController 'instagram_business_basic', 'instagram_business_content_publish', 'instagram_business_manage_insights', + // first comment after publish (FirstCommentPoster) + 'instagram_business_manage_comments', ]; public function connect(Request $request): Response diff --git a/app/Http/Controllers/Auth/InstagramFacebookController.php b/app/Http/Controllers/Auth/InstagramFacebookController.php index 222b23f11..0eb1016f4 100644 --- a/app/Http/Controllers/Auth/InstagramFacebookController.php +++ b/app/Http/Controllers/Auth/InstagramFacebookController.php @@ -44,6 +44,8 @@ class InstagramFacebookController extends MetaController 'instagram_basic', 'instagram_content_publish', 'instagram_manage_insights', + // first comment after publish (FirstCommentPoster) + 'instagram_manage_comments', ]; public function connect(Request $request): Response diff --git a/app/Jobs/PublishToSocialPlatform.php b/app/Jobs/PublishToSocialPlatform.php index 90110b27b..ede1a2902 100644 --- a/app/Jobs/PublishToSocialPlatform.php +++ b/app/Jobs/PublishToSocialPlatform.php @@ -30,6 +30,7 @@ use App\Services\Social\Telegram\TelegramPublisher; use App\Services\Social\ThreadsPublisher; use App\Services\Social\TikTokPublisher; +use App\Services\Social\FirstCommentPoster; use App\Services\Social\XPublisher; use App\Services\Social\YouTubePublisher; use App\Support\Social\GoogleBusinessDerivativeCleaner; @@ -130,6 +131,15 @@ public function handle(): void $result = $publisher->publish($this->post); $this->recordPublishResult($result); + + // The post is live; a failed first comment must never fail it — + // FirstCommentPoster logs and swallows its own errors. Posts + // held in pending review (Google Business) get no comment. + if ($this->post->publish_status === PublishStatus::Published + && ($externalId = (string) data_get($result, 'id')) !== '') { + app(FirstCommentPoster::class)->post($this->post, $externalId); + } + break; } catch (PlatformUnavailableException $e) { $this->rescheduleForRetry($e); diff --git a/app/Services/Social/FirstCommentPoster.php b/app/Services/Social/FirstCommentPoster.php new file mode 100644 index 000000000..39f9a3771 --- /dev/null +++ b/app/Services/Social/FirstCommentPoster.php @@ -0,0 +1,102 @@ +meta, 'first_comment')); + + if ($comment === '') { + return; + } + + try { + match ($post->platform) { + Platform::YouTube => $this->postYouTubeComment($post, $externalId, $comment), + Platform::Instagram, Platform::InstagramFacebook => $this->postInstagramComment($post, $externalId, $comment), + default => Log::warning('First comment is not supported for this platform', [ + 'platform' => $post->platform->value, + 'post_id' => $post->id, + ]), + }; + } catch (\Throwable $e) { + Log::warning('First comment failed (post itself is published)', [ + 'platform' => $post->platform->value, + 'post_id' => $post->id, + 'error' => $e->getMessage(), + ]); + } + } + + /** + * commentThreads.insert needs the youtube.force-ssl scope — requested at + * connect time since the platform was added, so every account has it. + */ + private function postYouTubeComment(Post $post, string $videoId, string $comment): void + { + $account = $post->socialAccount; + $api = rtrim((string) config('trypost.platforms.youtube.data_api'), '/'); + + $response = $this->socialHttp() + ->withToken($account->access_token) + ->post("{$api}/commentThreads?part=snippet", [ + 'snippet' => [ + 'videoId' => $videoId, + 'topLevelComment' => [ + 'snippet' => ['textOriginal' => $comment], + ], + ], + ]); + + if ($response->failed()) { + Log::warning('YouTube first comment failed', [ + 'post_id' => $post->id, + 'status' => $response->status(), + 'body' => $this->redactResponseBody($response->body()), + ]); + } + } + + /** + * Needs instagram_business_manage_comments (direct) / instagram_manage_comments + * (via Facebook Page) — accounts connected before the scope was requested + * get a 4xx here, which is logged and ignored; reconnecting fixes it. + */ + private function postInstagramComment(Post $post, string $mediaId, string $comment): void + { + $account = $post->socialAccount; + $baseUrl = $account->platform->instagramGraphBaseUrl(); + + $response = $this->socialHttp()->post("{$baseUrl}/{$mediaId}/comments", [ + 'message' => $comment, + 'access_token' => $account->access_token, + ]); + + if ($response->failed()) { + Log::warning('Instagram first comment failed', [ + 'post_id' => $post->id, + 'status' => $response->status(), + 'body' => $this->redactResponseBody($response->body()), + ]); + } + } +} diff --git a/app/Support/PostPlatformMetaRules.php b/app/Support/PostPlatformMetaRules.php index cff025377..5721b836b 100644 --- a/app/Support/PostPlatformMetaRules.php +++ b/app/Support/PostPlatformMetaRules.php @@ -80,6 +80,11 @@ public static function rules(string $prefix = 'meta'): array "{$prefix}.brand_content_toggle" => ['sometimes', 'boolean'], "{$prefix}.brand_organic_toggle" => ['sometimes', 'boolean'], + // First comment posted right after a successful publish (YouTube and + // Instagram). Capped at Instagram's comment limit, the stricter of + // the two. + "{$prefix}.first_comment" => ['sometimes', 'nullable', 'string', 'max:2200'], + "{$prefix}.board_id" => ['sometimes', 'nullable', 'string'], "{$prefix}.title" => ['sometimes', 'nullable', 'string', 'max:100'], "{$prefix}.link" => ['sometimes', 'nullable', 'url:http,https', 'max:2048'], @@ -177,6 +182,7 @@ public static function messages(string $prefix = 'meta'): array "{$prefix}.title.max" => __('posts.form.pinterest.title_max'), "{$prefix}.event.end_date.after_or_equal" => __('posts.form.google_business.event_end_date_before_start'), "{$prefix}.event.title.max" => __('posts.form.google_business.title_max'), + "{$prefix}.first_comment.max" => __('posts.form.first_comment.max'), ]; } @@ -193,6 +199,7 @@ public static function attributes(string $prefix = 'meta'): array "{$prefix}.link" => __('posts.form.pinterest.link'), "{$prefix}.event.title" => __('posts.form.google_business.event_title'), "{$prefix}.call_to_action.url" => __('posts.form.google_business.cta_url'), + "{$prefix}.first_comment" => __('posts.form.first_comment.label'), ]; } @@ -215,6 +222,7 @@ public static function documentation(): string 'Facebook, Bluesky, LinkedIn: link_preview (default true; false publishes a text post with a link without its preview card).', 'LinkedIn (profile and page): document_title (≤300, the title shown on a PDF document post; defaults to the file name).', 'Google Business Profile: topic_type STANDARD (default)|EVENT|OFFER; call_to_action {action_type: '.implode('|', array_column(CtaAction::cases(), 'value')).', url} (not on OFFER; url required unless NONE or CALL); event {title ≤'.TopicType::TITLE_MAX_LENGTH.', start_date, end_date (YYYY-MM-DD), start_time, end_time (HH:MM)} (required on EVENT and OFFER, the title being the offer title); offer {coupon_code, redeem_online_url, terms_conditions ≤5000} (OFFER only).', + 'YouTube Shorts and Instagram: first_comment (≤2200, posted by the account right after a successful publish).', 'Telegram, Facebook reels and stories: no settings. Stories (Instagram and Facebook) publish no text.', 'Threads of posts: thread_replies (list of up to '.ThreadReplies::MAX_REPLIES.' replies published under the post as a thread, each {text, media} where media is a list of up to 4 media items ({url}, {id} or {upload_token}, with optional meta.alt_text) for that reply alone; a plain string is a text-only reply) on Bluesky, Mastodon and X only; each reply needs text or media, its media follows the rules of a post on that network, and its text must fit the account limit (Bluesky 300, Mastodon 500 including the content warning, which every reply repeats, X 280 or 25000 for accounts with long posts).', ]); diff --git a/lang/ar/posts.php b/lang/ar/posts.php index 359071350..01b0e3d3e 100644 --- a/lang/ar/posts.php +++ b/lang/ar/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'السفر والفعاليات', ], ], + 'first_comment' => [ + 'label' => 'التعليق الأول', + 'placeholder' => 'رابط وإضافات للتعليق الأول…', + 'hint' => 'يُنشر باسم قناتك فور نشر الفيديو.', + 'hint_instagram' => 'يُنشر فور النشر — المكان الكلاسيكي للروابط التي لا تكون قابلة للنقر في وصف Instagram. أعد ربط الحساب مرة واحدة لمنح إذن التعليقات.', + 'max' => 'لا يمكن أن يتجاوز التعليق الأول 2200 حرف.', + ], 'ai_generated' => [ 'label' => 'بالذكاء الاصطناعي', 'hint' => 'أخبر المشاهدين بأن المحتوى أُنشئ أو عُدّل بالذكاء الاصطناعي.', diff --git a/lang/de/posts.php b/lang/de/posts.php index 34fdd6f91..1eb0d5c7f 100644 --- a/lang/de/posts.php +++ b/lang/de/posts.php @@ -397,6 +397,13 @@ 'travel_and_events' => 'Reisen & Events', ], ], + 'first_comment' => [ + 'label' => 'Erster Kommentar', + 'placeholder' => 'Link und Extras für den ersten Kommentar…', + 'hint' => 'Wird direkt nach der Veröffentlichung vom Kanal gepostet.', + 'hint_instagram' => 'Wird direkt nach dem Beitrag gepostet — der klassische Ort für Links, die in Instagram-Beschreibungen nicht klickbar sind. Konto einmal neu verbinden, um die Kommentar-Berechtigung zu erteilen.', + 'max' => 'Der erste Kommentar darf 2200 Zeichen nicht überschreiten.', + ], 'ai_generated' => [ 'label' => 'KI-generiert', 'hint' => 'Zeige deinen Zuschauern, dass dein Inhalt mit KI erstellt oder bearbeitet wurde.', diff --git a/lang/el/posts.php b/lang/el/posts.php index 40a5fd543..36a479474 100644 --- a/lang/el/posts.php +++ b/lang/el/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Ταξίδια και εκδηλώσεις', ], ], + 'first_comment' => [ + 'label' => 'Πρώτο σχόλιο', + 'placeholder' => 'Σύνδεσμος και πρόσθετα για το πρώτο σχόλιο…', + 'hint' => 'Δημοσιεύεται από το κανάλι σας αμέσως μετά τη δημοσίευση του βίντεο.', + 'hint_instagram' => 'Δημοσιεύεται αμέσως μετά την ανάρτηση — το κλασικό σημείο για συνδέσμους που οι λεζάντες του Instagram δεν κάνουν κλικαρίσιμους. Επανασυνδέστε τον λογαριασμό μία φορά για να δώσετε το δικαίωμα σχολίων.', + 'max' => 'Το πρώτο σχόλιο δεν μπορεί να υπερβαίνει τους 2200 χαρακτήρες.', + ], 'ai_generated' => [ 'label' => 'Με AI', 'hint' => 'Ενημερώστε τους θεατές ότι το περιεχόμενο δημιουργήθηκε ή επεξεργάστηκε με AI.', diff --git a/lang/en/posts.php b/lang/en/posts.php index d469dee19..95e310329 100644 --- a/lang/en/posts.php +++ b/lang/en/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Travel & Events', ], ], + 'first_comment' => [ + 'label' => 'First comment', + 'placeholder' => 'Link and extras for the first comment…', + 'hint' => 'Posted by your channel right after the video is published.', + 'hint_instagram' => 'Posted right after publishing — the classic spot for links Instagram captions can\'t make clickable. Requires reconnecting the account once to grant the comments permission.', + 'max' => 'First comment may not exceed 2200 characters.', + ], 'ai_generated' => [ 'label' => 'AI-generated', 'hint' => 'Tell viewers your content was generated or edited with AI.', diff --git a/lang/es/posts.php b/lang/es/posts.php index a699ac95f..0adee0475 100644 --- a/lang/es/posts.php +++ b/lang/es/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Viajes y eventos', ], ], + 'first_comment' => [ + 'label' => 'Primer comentario', + 'placeholder' => 'Enlace y extras para el primer comentario…', + 'hint' => 'Se publica desde tu canal justo después de publicar el vídeo.', + 'hint_instagram' => 'Se publica justo después de la publicación: el lugar clásico para enlaces que Instagram no hace clicables en la descripción. Reconecta la cuenta una vez para conceder el permiso de comentarios.', + 'max' => 'El primer comentario no puede superar los 2200 caracteres.', + ], 'ai_generated' => [ 'label' => 'Generado con IA', 'hint' => 'Indica a los espectadores que el contenido se creó o editó con IA.', diff --git a/lang/fr/posts.php b/lang/fr/posts.php index 721c6040c..d040a4412 100644 --- a/lang/fr/posts.php +++ b/lang/fr/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Voyages et événements', ], ], + 'first_comment' => [ + 'label' => 'Premier commentaire', + 'placeholder' => 'Lien et compléments pour le premier commentaire…', + 'hint' => 'Publié par votre chaîne juste après la mise en ligne de la vidéo.', + 'hint_instagram' => 'Publié juste après la publication — l\'endroit classique pour les liens que les légendes Instagram ne rendent pas cliquables. Reconnectez le compte une fois pour accorder l\'autorisation de commenter.', + 'max' => 'Le premier commentaire ne peut pas dépasser 2200 caractères.', + ], 'ai_generated' => [ 'label' => 'Généré par IA', 'hint' => 'Indiquez aux spectateurs que le contenu a été généré ou modifié par IA.', diff --git a/lang/it/posts.php b/lang/it/posts.php index 4a48423f9..c0a6b641e 100644 --- a/lang/it/posts.php +++ b/lang/it/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Viaggi ed eventi', ], ], + 'first_comment' => [ + 'label' => 'Primo commento', + 'placeholder' => 'Link ed extra per il primo commento…', + 'hint' => 'Pubblicato dal tuo canale subito dopo la pubblicazione del video.', + 'hint_instagram' => 'Pubblicato subito dopo il post: il posto classico per i link che le didascalie di Instagram non rendono cliccabili. Ricollega l\'account una volta per concedere il permesso sui commenti.', + 'max' => 'Il primo commento non può superare i 2200 caratteri.', + ], 'ai_generated' => [ 'label' => 'Generato con IA', 'hint' => 'Comunica agli spettatori che il contenuto è stato generato o modificato con l\'IA.', diff --git a/lang/ja/posts.php b/lang/ja/posts.php index 5b76c7947..294ad4766 100644 --- a/lang/ja/posts.php +++ b/lang/ja/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => '旅行とイベント', ], ], + 'first_comment' => [ + 'label' => '最初のコメント', + 'placeholder' => '最初のコメントに入れるリンクや補足…', + 'hint' => '動画公開直後にチャンネルから投稿されます。', + 'hint_instagram' => '投稿直後にコメントされます。Instagramのキャプションでクリックできないリンクの定番の置き場所です。コメント権限を付与するには一度アカウントを再連携してください。', + 'max' => '最初のコメントは2200文字を超えられません。', + ], 'ai_generated' => [ 'label' => 'AI 生成', 'hint' => 'コンテンツが AI で生成・編集されたことを視聴者に伝えます。', diff --git a/lang/ko/posts.php b/lang/ko/posts.php index bc3b61238..571fd0e18 100644 --- a/lang/ko/posts.php +++ b/lang/ko/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => '여행/이벤트', ], ], + 'first_comment' => [ + 'label' => '첫 댓글', + 'placeholder' => '첫 댓글에 넣을 링크와 추가 내용…', + 'hint' => '동영상 게시 직후 채널 명의로 작성됩니다.', + 'hint_instagram' => '게시 직후 작성됩니다 — Instagram 캡션에서 클릭되지 않는 링크를 넣는 고전적인 방법입니다. 댓글 권한을 부여하려면 계정을 한 번 다시 연결하세요.', + 'max' => '첫 댓글은 2200자를 초과할 수 없습니다.', + ], 'ai_generated' => [ 'label' => 'AI 생성', 'hint' => '콘텐츠가 AI로 생성 또는 편집되었음을 시청자에게 알립니다.', diff --git a/lang/nl/posts.php b/lang/nl/posts.php index 6616a1edd..a818a4007 100644 --- a/lang/nl/posts.php +++ b/lang/nl/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Reizen en evenementen', ], ], + 'first_comment' => [ + 'label' => 'Eerste reactie', + 'placeholder' => 'Link en extra\'s voor de eerste reactie…', + 'hint' => 'Direct na publicatie door je kanaal geplaatst.', + 'hint_instagram' => 'Direct na het bericht geplaatst — dé plek voor links die Instagram-bijschriften niet klikbaar maken. Verbind het account één keer opnieuw om de reactietoestemming te geven.', + 'max' => 'De eerste reactie mag niet langer zijn dan 2200 tekens.', + ], 'ai_generated' => [ 'label' => 'AI-gegenereerd', 'hint' => 'Laat kijkers weten dat je content met AI is gemaakt of bewerkt.', diff --git a/lang/pl/posts.php b/lang/pl/posts.php index 24374ca05..38e1ac53d 100644 --- a/lang/pl/posts.php +++ b/lang/pl/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Podróże i wydarzenia', ], ], + 'first_comment' => [ + 'label' => 'Pierwszy komentarz', + 'placeholder' => 'Link i dodatki do pierwszego komentarza…', + 'hint' => 'Publikowany przez Twój kanał zaraz po opublikowaniu filmu.', + 'hint_instagram' => 'Publikowany zaraz po poście — klasyczne miejsce na linki, których opisy na Instagramie nie czynią klikalnymi. Połącz konto ponownie, aby nadać uprawnienie do komentarzy.', + 'max' => 'Pierwszy komentarz nie może przekraczać 2200 znaków.', + ], 'ai_generated' => [ 'label' => 'Treść AI', 'hint' => 'Poinformuj widzów, że treść została wygenerowana lub edytowana przez AI.', diff --git a/lang/pt-BR/posts.php b/lang/pt-BR/posts.php index 7d6856cab..f04080d04 100644 --- a/lang/pt-BR/posts.php +++ b/lang/pt-BR/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Viagens e eventos', ], ], + 'first_comment' => [ + 'label' => 'Primeiro comentário', + 'placeholder' => 'Link e extras para o primeiro comentário…', + 'hint' => 'Publicado pelo seu canal logo após o vídeo ir ao ar.', + 'hint_instagram' => 'Publicado logo após a postagem — o lugar clássico para links que as legendas do Instagram não tornam clicáveis. Reconecte a conta uma vez para conceder a permissão de comentários.', + 'max' => 'O primeiro comentário não pode exceder 2200 caracteres.', + ], 'ai_generated' => [ 'label' => 'Gerado por IA', 'hint' => 'Avise aos espectadores que o conteúdo foi gerado ou editado com IA.', diff --git a/lang/ru/posts.php b/lang/ru/posts.php index 7c0d0b927..f295971f3 100644 --- a/lang/ru/posts.php +++ b/lang/ru/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Путешествия', ], ], + 'first_comment' => [ + 'label' => 'Первый комментарий', + 'placeholder' => 'Ссылка и дополнения для первого комментария…', + 'hint' => 'Публикуется от имени канала сразу после выхода ролика.', + 'hint_instagram' => 'Публикуется сразу после поста — классическое место для ссылок, которые в описании Instagram некликабельны. Требуется один раз переподключить аккаунт, чтобы выдать право на комментарии.', + 'max' => 'Первый комментарий не может быть длиннее 2200 знаков.', + ], 'ai_generated' => [ 'label' => 'Создано ИИ', 'hint' => 'Сообщите зрителям, что контент создан или изменён с помощью ИИ.', diff --git a/lang/tr/posts.php b/lang/tr/posts.php index d45bd2a3a..d616bc5dd 100644 --- a/lang/tr/posts.php +++ b/lang/tr/posts.php @@ -397,6 +397,13 @@ 'travel_and_events' => 'Seyahat ve Etkinlikler', ], ], + 'first_comment' => [ + 'label' => 'İlk yorum', + 'placeholder' => 'İlk yorum için bağlantı ve ekler…', + 'hint' => 'Video yayınlandıktan hemen sonra kanalınız tarafından gönderilir.', + 'hint_instagram' => 'Gönderiden hemen sonra paylaşılır — Instagram açıklamalarında tıklanamayan bağlantılar için klasik yer. Yorum iznini vermek için hesabı bir kez yeniden bağlayın.', + 'max' => 'İlk yorum 2200 karakteri aşamaz.', + ], 'ai_generated' => [ 'label' => 'AI ile üretildi', 'hint' => 'İzleyicilere içeriğin AI ile üretildiğini veya düzenlendiğini bildirin.', diff --git a/lang/uk/posts.php b/lang/uk/posts.php index 4783f3423..2e898b11c 100644 --- a/lang/uk/posts.php +++ b/lang/uk/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => 'Подорожі та події', ], ], + 'first_comment' => [ + 'label' => 'Перший коментар', + 'placeholder' => 'Посилання та доповнення для першого коментаря…', + 'hint' => 'Публікується від імені каналу одразу після виходу відео.', + 'hint_instagram' => 'Публікується одразу після поста — класичне місце для посилань, які в описі Instagram неклікабельні. Потрібно один раз перепідключити акаунт, щоб надати право на коментарі.', + 'max' => 'Перший коментар не може перевищувати 2200 знаків.', + ], 'ai_generated' => [ 'label' => 'Створено AI', 'hint' => 'Повідомте глядачам, що контент створено або змінено за допомогою AI.', diff --git a/lang/zh/posts.php b/lang/zh/posts.php index 4de091b98..7ffd9882a 100644 --- a/lang/zh/posts.php +++ b/lang/zh/posts.php @@ -395,6 +395,13 @@ 'travel_and_events' => '旅游和活动', ], ], + 'first_comment' => [ + 'label' => '首条评论', + 'placeholder' => '首条评论中的链接和补充内容…', + 'hint' => '视频发布后立即以频道名义发表。', + 'hint_instagram' => '发布后立即评论——这是放置 Instagram 描述中无法点击的链接的经典位置。需重新连接一次账号以授予评论权限。', + 'max' => '首条评论不能超过 2200 个字符。', + ], 'ai_generated' => [ 'label' => 'AI 生成', 'hint' => '告诉观众内容是由 AI 生成或编辑的。', diff --git a/resources/js/components/posts/editor/InstagramSettings.vue b/resources/js/components/posts/editor/InstagramSettings.vue index e92d5f015..c13782a9b 100644 --- a/resources/js/components/posts/editor/InstagramSettings.vue +++ b/resources/js/components/posts/editor/InstagramSettings.vue @@ -1,10 +1,13 @@